Render sets and set groups tree

This commit is contained in:
Florian Schroedl 2024-11-19 16:43:37 +01:00
parent 1d2c7dd20e
commit a19d85fb10
10 changed files with 373 additions and 237 deletions

View file

@ -410,6 +410,11 @@
[:type [:= :add-token-set]] [:type [:= :add-token-set]]
[:token-set ::ctot/token-set]]] [:token-set ::ctot/token-set]]]
[:add-token-sets
[:map {:title "AddTokenSetsChange"}
[:type [:= :add-token-sets]]
[:token-sets [:sequential ::ctot/token-set]]]]
[:mod-token-set [:mod-token-set
[:map {:title "ModTokenSetChange"} [:map {:title "ModTokenSetChange"}
[:type [:= :mod-token-set]] [:type [:= :mod-token-set]]
@ -427,6 +432,11 @@
[:type [:= :del-token-set]] [:type [:= :del-token-set]]
[:name :string]]] [:name :string]]]
[:del-token-set-path
[:map {:title "DelTokenSetPathChange"}
[:type [:= :del-token-set-path]]
[:path :string]]]
[:set-tokens-lib [:set-tokens-lib
[:map {:title "SetTokensLib"} [:map {:title "SetTokensLib"}
[:type [:= :set-tokens-lib]] [:type [:= :set-tokens-lib]]
@ -1046,6 +1056,12 @@
(ctob/ensure-tokens-lib) (ctob/ensure-tokens-lib)
(ctob/add-set (ctob/make-token-set token-set))))) (ctob/add-set (ctob/make-token-set token-set)))))
(defmethod process-change :add-token-sets
[data {:keys [token-sets]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/add-sets (map ctob/make-token-set token-sets)))))
(defmethod process-change :mod-token-set (defmethod process-change :mod-token-set
[data {:keys [name token-set]}] [data {:keys [name token-set]}]
(update data :tokens-lib (fn [lib] (update data :tokens-lib (fn [lib]
@ -1066,6 +1082,12 @@
(ctob/ensure-tokens-lib) (ctob/ensure-tokens-lib)
(ctob/delete-set name)))) (ctob/delete-set name))))
(defmethod process-change :del-token-set-path
[data {:keys [path]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/delete-set-path path))))
;; === Operations ;; === Operations
(def ^:private decode-shape (def ^:private decode-shape

View file

@ -818,15 +818,15 @@
(update :undo-changes conj {:type :mod-token-set :name (:name token-set) :token-set (or prev-token-set token-set)}) (update :undo-changes conj {:type :mod-token-set :name (:name token-set) :token-set (or prev-token-set token-set)})
(apply-changes-local))) (apply-changes-local)))
(defn delete-token-set (defn delete-token-set-path
[changes token-set-name] [changes token-set-path]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-token-theme (some-> (get library-data :tokens-lib) prev-token-sets (some-> (get library-data :tokens-lib)
(ctob/get-set token-set-name))] (ctob/get-path-sets token-set-path))]
(-> changes (-> changes
(update :redo-changes conj {:type :del-token-set :name token-set-name}) (update :redo-changes conj {:type :del-token-set-path :path token-set-path})
(update :undo-changes conj {:type :add-token-set :token-set prev-token-theme}) (update :undo-changes conj {:type :add-token-sets :token-sets prev-token-sets})
(apply-changes-local)))) (apply-changes-local))))
(defn move-token-set-before (defn move-token-set-before

View file

@ -183,6 +183,14 @@
(def set-separator "/") (def set-separator "/")
(defn join-set-path [set-path]
(join-path set-path set-separator))
(defn split-set-prefix [set-path]
(some->> set-path
(re-matches #"^([SG]-)(.*)")
(rest)))
(defn add-set-prefix [set-name] (defn add-set-prefix [set-name]
(str set-prefix set-name)) (str set-prefix set-name))
@ -199,14 +207,29 @@
set-name (add-set-prefix (last paths))] set-name (add-set-prefix (last paths))]
(conj set-path set-name))) (conj set-path set-name)))
(defn split-token-set-path [token-set-path]
(split-path token-set-path set-separator))
(defn split-token-set-name [token-set-name] (defn split-token-set-name [token-set-name]
(-> (split-path token-set-name set-separator) (-> (split-token-set-path token-set-name)
(add-token-set-paths-prefix))) (add-token-set-paths-prefix)))
(defn get-token-set-path [token-set] (defn get-token-set-path [token-set]
(let [path (get-path token-set set-separator)] (let [path (get-path token-set set-separator)]
(add-token-set-paths-prefix path))) (add-token-set-paths-prefix path)))
(defn set-name->set-path-string [set-name]
(-> (split-token-set-name set-name)
(join-set-path)))
(defn set-path->set-name [set-path]
(->> (split-token-set-path set-path)
(map (fn [path-part]
(or (-> (split-set-prefix path-part)
(second))
path-part)))
(join-set-path)))
(defn tokens-tree (defn tokens-tree
"Convert tokens into a nested tree with their `:name` as the path. "Convert tokens into a nested tree with their `:name` as the path.
Optionally use `update-token-fn` option to transform the token." Optionally use `update-token-fn` option to transform the token."
@ -234,16 +257,27 @@
{:tokens-tree {} :ids {}} tokens)) {:tokens-tree {} :ids {}} tokens))
(defprotocol ITokenSet (defprotocol ITokenSet
(update-name [_ set-name] "change a token set name while keeping the path")
(add-token [_ token] "add a token at the end of the list") (add-token [_ token] "add a token at the end of the list")
(update-token [_ token-name f] "update a token in the list") (update-token [_ token-name f] "update a token in the list")
(delete-token [_ token-name] "delete a token from the list") (delete-token [_ token-name] "delete a token from the list")
(get-token [_ token-name] "return token by token-name") (get-token [_ token-name] "return token by token-name")
(get-tokens [_] "return an ordered sequence of all tokens in the set") (get-tokens [_] "return an ordered sequence of all tokens in the set")
(get-set-path [_] "returns name of set converted to the path with prefix identifiers")
(get-tokens-tree [_] "returns a tree of tokens split & nested by their name path") (get-tokens-tree [_] "returns a tree of tokens split & nested by their name path")
(get-dtcg-tokens-tree [_] "returns tokens tree formated to the dtcg spec")) (get-dtcg-tokens-tree [_] "returns tokens tree formated to the dtcg spec"))
(defrecord TokenSet [name description modified-at tokens] (defrecord TokenSet [name description modified-at tokens]
ITokenSet ITokenSet
(update-name [_ set-name]
(TokenSet. (-> (split-token-set-path name)
(drop-last)
(concat [set-name])
(join-set-path))
description
(dt/now)
tokens))
(add-token [_ token] (add-token [_ token]
(dm/assert! "expected valid token" (check-token! token)) (dm/assert! "expected valid token" (check-token! token))
(TokenSet. name (TokenSet. name
@ -278,6 +312,9 @@
(get-tokens [_] (get-tokens [_]
(vals tokens)) (vals tokens))
(get-set-path [_]
(set-name->set-path-string name))
(get-tokens-tree [_] (get-tokens-tree [_]
(tokens-tree tokens)) (tokens-tree tokens))
@ -325,11 +362,14 @@
(add-sets [_ token-set] "add a collection of sets to the library, at the end") (add-sets [_ token-set] "add a collection of sets to the library, at the end")
(update-set [_ set-name f] "modify a set in the ilbrary") (update-set [_ set-name f] "modify a set in the ilbrary")
(delete-set [_ set-name] "delete a set in the library") (delete-set [_ set-name] "delete a set in the library")
(delete-set-path [_ set-path] "delete a set in the library")
(move-set-before [_ set-name before-set-name] "move a set with `set-name` before a set with `before-set-name` in the library. (move-set-before [_ set-name before-set-name] "move a set with `set-name` before a set with `before-set-name` in the library.
When `before-set-name` is nil, move set to bottom") When `before-set-name` is nil, move set to bottom")
(set-count [_] "get the total number if sets in the library") (set-count [_] "get the total number if sets in the library")
(get-set-tree [_] "get a nested tree of all sets in the library") (get-set-tree [_] "get a nested tree of all sets in the library")
(get-in-set-tree [_ path] "get `path` in nested tree of all sets in the library")
(get-sets [_] "get an ordered sequence of all sets in the library") (get-sets [_] "get an ordered sequence of all sets in the library")
(get-path-sets [_ path] "get an ordered sequence of sets at `path` in the library")
(get-ordered-set-names [_] "get an ordered sequence of all sets names in the library") (get-ordered-set-names [_] "get an ordered sequence of all sets names in the library")
(get-set [_ set-name] "get one set looking for name") (get-set [_ set-name] "get one set looking for name")
(get-neighbor-set-name [_ set-name index-offset] "get neighboring set name offset by `index-offset`")) (get-neighbor-set-name [_ set-name index-offset] "get neighboring set name offset by `index-offset`"))
@ -381,6 +421,8 @@ When `before-set-name` is nil, move set to bottom")
(set-sets [_ set-names] "set the active token sets") (set-sets [_ set-names] "set the active token sets")
(disable-set [_ set-name] "disable set in theme") (disable-set [_ set-name] "disable set in theme")
(toggle-set [_ set-name] "toggle a set enabled / disabled in the theme") (toggle-set [_ set-name] "toggle a set enabled / disabled in the theme")
(update-set-name [_ prev-set-name set-name] "update set-name from `prev-set-name` to `set-name` when it exists")
(theme-path [_] "get `token-theme-path` from theme") (theme-path [_] "get `token-theme-path` from theme")
(theme-matches-group-name [_ group name] "if a theme matches the given group & name") (theme-matches-group-name [_ group name] "if a theme matches the given group & name")
(hidden-temporary-theme? [_] "if a theme is the (from the user ui) hidden temporary theme")) (hidden-temporary-theme? [_] "if a theme is the (from the user ui) hidden temporary theme"))
@ -403,6 +445,16 @@ When `before-set-name` is nil, move set to bottom")
(disj sets set-name) (disj sets set-name)
(conj sets set-name)))) (conj sets set-name))))
(update-set-name [this prev-set-name set-name]
(if (get sets prev-set-name)
(TokenTheme. name
group
description
is-source
(dt/now)
(conj (disj sets prev-set-name) set-name))
this))
(theme-path [_] (theme-path [_]
(token-theme-path group name)) (token-theme-path group name))
@ -569,36 +621,49 @@ When `before-set-name` is nil, move set to bottom")
(add-sets [this token-sets] (add-sets [this token-sets]
(reduce (reduce
(fn [lib set] (fn [lib set]
(add-set lib set)) (add-set lib set))
this token-sets)) this token-sets))
(update-set [this set-name f] (update-set [this set-name f]
(let [path (split-token-set-name set-name) (let [path (split-token-set-name set-name)
set (get-in sets path)] set (get-in sets path)]
(if set (if set
(let [set' (-> (make-token-set (f set)) (let [set' (-> (make-token-set (f set))
(assoc :modified-at (dt/now))) (assoc :modified-at (dt/now)))
path' (get-token-set-path set')] path' (get-token-set-path set')
name-changed? (not= (:name set) (:name set'))]
(check-token-set! set') (check-token-set! set')
(TokensLib. (if (= (:name set) (:name set')) (if name-changed?
(d/oassoc-in sets path set') (TokensLib. (-> sets
(-> sets
(d/oassoc-in-before path path' set') (d/oassoc-in-before path path' set')
(d/dissoc-in path))) (d/dissoc-in path))
themes (walk/postwalk
active-themes)) (fn [form]
(if (instance? TokenTheme form)
(update-set-name form (:name set) (:name set'))
form))
themes)
active-themes)
(TokensLib. (d/oassoc-in sets path set')
themes
active-themes)))
this))) this)))
(delete-set [_ set-name] (delete-set-path [_ set-path]
(let [path (split-token-set-name set-name)] (let [path (split-token-set-path set-path)
set-node (get-in sets path)
set-group? (not (instance? TokenSet set-node))]
(TokensLib. (d/dissoc-in sets path) (TokensLib. (d/dissoc-in sets path)
(walk/postwalk ;; TODO: When deleting a set-group, also deactivate the child sets
(fn [form] (if set-group?
(if (instance? TokenTheme form) themes
(disable-set form set-name) (walk/postwalk
form)) (fn [form]
themes) (if (instance? TokenTheme form)
(disable-set form set-path)
form))
themes))
active-themes))) active-themes)))
;; TODO Handle groups and nesting ;; TODO Handle groups and nesting
@ -620,10 +685,18 @@ When `before-set-name` is nil, move set to bottom")
(get-set-tree [_] (get-set-tree [_]
sets) sets)
(get-in-set-tree [_ path]
(get-in sets path))
(get-sets [_] (get-sets [_]
(->> (tree-seq d/ordered-map? vals sets) (->> (tree-seq d/ordered-map? vals sets)
(filter (partial instance? TokenSet)))) (filter (partial instance? TokenSet))))
(get-path-sets [_ path]
(some->> (get-in sets (split-token-set-path path))
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet))))
(get-ordered-set-names [this] (get-ordered-set-names [this]
(map :name (get-sets this))) (map :name (get-sets this)))

View file

@ -95,6 +95,14 @@
(update [_ state] (update [_ state]
(wtts/assoc-selected-token-set-id state id)))) (wtts/assoc-selected-token-set-id state id))))
(defn set-selected-token-set-id-from-name
[token-set-name]
(ptk/reify ::set-selected-token-set-id-from-name
ptk/UpdateEvent
(update [_ state]
(->> (ctob/set-name->set-path-string token-set-name)
(wtts/assoc-selected-token-set-id state)))))
(defn create-token-theme [token-theme] (defn create-token-theme [token-theme]
(let [new-token-theme token-theme] (let [new-token-theme token-theme]
(ptk/reify ::create-token-theme (ptk/reify ::create-token-theme
@ -157,7 +165,7 @@
(let [changes (-> (pcb/empty-changes it) (let [changes (-> (pcb/empty-changes it)
(pcb/add-token-set new-token-set))] (pcb/add-token-set new-token-set))]
(rx/of (rx/of
(set-selected-token-set-id (:name new-token-set)) (set-selected-token-set-id-from-name (:name new-token-set))
(dch/commit-changes changes))))))) (dch/commit-changes changes)))))))
(defn update-token-set [set-name token-set] (defn update-token-set [set-name token-set]
@ -169,7 +177,7 @@
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/update-token-set token-set prev-token-set))] (pcb/update-token-set token-set prev-token-set))]
(rx/of (rx/of
(set-selected-token-set-id (:name token-set)) (set-selected-token-set-id-from-name (:name token-set))
(dch/commit-changes changes)))))) (dch/commit-changes changes))))))
(defn toggle-token-set [{:keys [token-set-name]}] (defn toggle-token-set [{:keys [token-set-name]}]
@ -202,7 +210,7 @@
(ctob/get-sets) (ctob/get-sets)
(first) (first)
(:name) (:name)
(set-selected-token-set-id)) (set-selected-token-set-id-from-name))
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/set-tokens-lib lib))] (pcb/set-tokens-lib lib))]
@ -211,14 +219,14 @@
update-token-set-change update-token-set-change
(wtu/update-workspace-tokens)))))) (wtu/update-workspace-tokens))))))
(defn delete-token-set [token-set-name] (defn delete-token-set-path [token-set-path]
(ptk/reify ::delete-token-set (ptk/reify ::delete-token-set-path
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [data (get state :workspace-data) (let [data (get state :workspace-data)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/delete-token-set token-set-name))] (pcb/delete-token-set-path token-set-path))]
(rx/of (rx/of
(dch/commit-changes changes) (dch/commit-changes changes)
(wtu/update-workspace-tokens)))))) (wtu/update-workspace-tokens))))))
@ -268,7 +276,7 @@
(pcb/update-token (pcb/empty-changes) (:name token-set) token prev-token) (pcb/update-token (pcb/empty-changes) (:name token-set) token prev-token)
(pcb/add-token (pcb/empty-changes) (:name token-set) token)))] (pcb/add-token (pcb/empty-changes) (:name token-set) token)))]
(rx/of (rx/of
(set-selected-token-set-id token-set-name) (set-selected-token-set-id-from-name token-set-name)
(dch/commit-changes changes)))))) (dch/commit-changes changes))))))
(defn delete-token (defn delete-token

View file

@ -493,9 +493,15 @@
(def workspace-selected-token-set-id (def workspace-selected-token-set-id
(l/derived wtts/get-selected-token-set-id st/state)) (l/derived wtts/get-selected-token-set-id st/state))
(def workspace-token-set-group-selected?
(l/derived wtts/token-group-selected? st/state))
(def workspace-ordered-token-sets (def workspace-ordered-token-sets
(l/derived #(or (some-> % ctob/get-sets) []) tokens-lib)) (l/derived #(or (some-> % ctob/get-sets) []) tokens-lib))
(def workspace-token-sets-tree
(l/derived (d/nilf ctob/get-set-tree) tokens-lib))
(def workspace-active-theme-paths (def workspace-active-theme-paths
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))

View file

@ -7,14 +7,13 @@
(ns app.main.ui.workspace.tokens.sets (ns app.main.ui.workspace.tokens.sets
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.main.data.notifications :as ntf] [app.common.types.tokens-lib :as ctob]
[app.main.data.tokens :as wdt] [app.main.data.tokens :as wdt]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic]
[app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as h]
[app.main.ui.workspace.tokens.sets-context :as sets-context] [app.main.ui.workspace.tokens.sets-context :as sets-context]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
@ -25,8 +24,8 @@
(defn on-toggle-token-set-click [token-set-name] (defn on-toggle-token-set-click [token-set-name]
(st/emit! (wdt/toggle-token-set {:token-set-name token-set-name}))) (st/emit! (wdt/toggle-token-set {:token-set-name token-set-name})))
(defn on-select-token-set-click [name] (defn on-select-token-set-click [tree-path]
(st/emit! (wdt/set-selected-token-set-id name))) (st/emit! (wdt/set-selected-token-set-id tree-path)))
(defn on-update-token-set [set-name token-set] (defn on-update-token-set [set-name token-set]
(st/emit! (wdt/update-token-set set-name token-set))) (st/emit! (wdt/update-token-set set-name token-set)))
@ -34,7 +33,7 @@
(defn on-create-token-set [token-set] (defn on-create-token-set [token-set]
(st/emit! (wdt/create-token-set token-set))) (st/emit! (wdt/create-token-set token-set)))
(mf/defc editing-node (mf/defc editing-label
[{:keys [default-value on-cancel on-submit]}] [{:keys [default-value on-cancel on-submit]}]
(let [ref (mf/use-ref) (let [ref (mf/use-ref)
on-submit-valid (mf/use-fn on-submit-valid (mf/use-fn
@ -43,7 +42,9 @@
(if (or (str/empty? value) (if (or (str/empty? value)
(= value default-value)) (= value default-value))
(on-cancel) (on-cancel)
(on-submit value))))) (do
(on-submit value)
(on-cancel))))))
on-key-down (mf/use-fn on-key-down (mf/use-fn
(fn [event] (fn [event]
(cond (cond
@ -58,136 +59,166 @@
:auto-focus true :auto-focus true
:default-value default-value}])) :default-value default-value}]))
(mf/defc sets-tree (mf/defc sets-tree-set-group
[{:keys [token-set [{:keys [label tree-depth tree-path selected? collapsed? on-select editing? on-edit on-edit-submit]}]
token-set-active? (let [editing?' (editing? tree-path)
token-set-selected? {:keys [on-create on-reset] :as ctx} (sets-context/use-context)
editing?
on-select
on-toggle
on-edit
on-submit
on-cancel]
:as _props}]
(let [{:keys [name _children]} token-set
selected? (and set? (token-set-selected? name))
visible? (token-set-active? name)
collapsed? (mf/use-state false)
set? true #_(= type :set)
group? false #_(= type :group)
editing-node? (editing? name)
on-click on-click
(mf/use-fn (mf/use-fn
(mf/deps editing-node?) (mf/deps editing? tree-path)
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not editing-node? (when-not (editing? tree-path)
(on-select name)))) (on-select tree-path))))
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps editing-node? name) (mf/deps editing? tree-path)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when-not editing-node? (when-not (editing? tree-path)
(st/emit! (st/emit!
(wdt/show-token-set-context-menu (wdt/show-token-set-context-menu
{:position (dom/get-client-position event) {:position (dom/get-client-position event)
:token-set-name name}))))) :tree-path tree-path})))))]
[:div {;; :ref dref
on-drag :role "button"
(mf/use-fn :style {"--tree-depth" tree-depth}
(mf/deps name)
(fn [_]
(when-not selected?
(on-select name))))
on-drop
(mf/use-fn
(mf/deps name)
(fn [position data]
(st/emit! (wdt/move-token-set (:name data) name position))))
on-submit-edit
(mf/use-fn
(mf/deps on-submit token-set)
#(on-submit (assoc token-set :name %)))
on-edit-name
(mf/use-fn
(fn [e]
(let [name (-> (dom/get-current-target e)
(dom/get-data "name"))]
(on-edit name))))
on-toggle-set (fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(on-toggle name))
on-collapse (mf/use-fn #(swap! collapsed? not))
[dprops dref]
(h/use-sortable
:data-type "penpot/token-set"
:on-drag on-drag
:on-drop on-drop
:data {:name name}
:draggable? true)]
[:div {:ref dref
:class (stl/css-case :set-item-container true :class (stl/css-case :set-item-container true
:dnd-over (= (:over dprops) :center) :selected-set selected?)
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))
:on-click on-click :on-click on-click
:on-double-click on-edit-name
:on-context-menu on-context-menu :on-context-menu on-context-menu
:data-name name} :on-double-click #(on-edit tree-path)}
[:div {:class (stl/css-case :set-item-group group? [:> icon-button*
:set-item-set set? {:on-click (fn [event]
:selected-set selected?)} (.stopPropagation event)
(when group? (swap! collapsed? not))
[:> icon-button* {:on-click on-collapse :aria-label (tr "labels.collapse")
:aria-label (tr "labels.collapse") :icon (if @collapsed? "arrow-right" "arrow-down")
:icon (if @collapsed? :variant "action"}]
"arrow-right" [:> icon*
"arrow-down") {:id "group"
:variant "action"}]) :class (stl/css :icon)}]
(if editing?'
[:& editing-label
{:default-value label
:on-cancel on-reset
:on-create on-reset
:on-submit #(on-edit-submit)}]
[:div {:class (stl/css :set-name)} label])]))
[:> icon* {:id (if set? "document" "group") (mf/defc sets-tree-set
:class (stl/css :icon)}] [{:keys [set label tree-depth tree-path selected? on-select active? on-toggle editing? on-edit on-edit-submit]}]
(if editing-node? (let [set-name (.-name set)
[:& editing-node {:default-value name {:keys [on-create on-reset] :as ctx} (sets-context/use-context)
:on-submit on-submit-edit editing?' (editing? tree-path)
:on-cancel on-cancel}] active?' (active? set-name)
[:* on-click
[:div {:class (stl/css :set-name)} name] (mf/use-fn
(if set? (mf/deps editing?' tree-path)
[:button {:on-click on-toggle-set (fn [event]
:class (stl/css-case :checkbox-style true (dom/stop-propagation event)
:checkbox-checked-style visible?)} (when-not editing?'
(when visible? (on-select tree-path))))
[:> icon* {:aria-label (tr "workspace.token.select-set")
:class (stl/css :check-icon)
:size "s"
:id ic/tick}])]
nil
#_(when (and children (not @collapsed?))
[:div {:class (stl/css :set-children)}
(for [child-id children]
[:& sets-tree (assoc props :key child-id
{:key child-id}
:set-id child-id
:selected-set-id selected-token-set-id)])]))])]]))
(defn warn-on-try-create-token-set-group! [] on-context-menu
(st/emit! (ntf/show {:content (tr "workspace.token.grouping-set-alert") (mf/use-fn
:notification-type :toast (mf/deps editing?' tree-path)
:type :warning (fn [event]
:timeout 3000}))) (dom/prevent-default event)
(dom/stop-propagation event)
(when-not editing?'
(st/emit!
(wdt/show-token-set-context-menu
{:position (dom/get-client-position event)
:tree-path tree-path})))))]
[:div {;; :ref dref
:role "button"
:style {"--tree-depth" tree-depth}
:class (stl/css-case :set-item-container true
:selected-set selected?)
:on-click on-click
:on-double-click #(on-edit tree-path)
:on-context-menu on-context-menu}
[:> icon*
{:id "document"
:class (stl/css-case :icon true
:root-icon (not tree-depth))}]
(if editing?'
[:& editing-label
{:default-value label
:on-cancel on-reset
:on-create on-reset
:on-submit #(on-edit-submit set-name (ctob/update-name set %))}]
[:*
[:div {:class (stl/css :set-name)} label]
[:button {:on-click (fn [event]
(dom/stop-propagation event)
(on-toggle set-name))
:class (stl/css-case :checkbox-style true
:checkbox-checked-style active?')}
(when active?'
[:> icon* {:aria-label (tr "workspace.token.select-set")
:class (stl/css :check-icon)
:size "s"
:id ic/tick}])]])]))
(mf/defc sets-tree
[{:keys [set-path set-node tree-depth tree-path on-select selected? on-toggle active? editing? on-edit on-edit-submit]
:or {tree-depth 0}
:as props}]
(let [[set-prefix set-path'] (some-> set-path (ctob/split-set-prefix))
set? (instance? ctob/TokenSet set-node)
set-group? (= ctob/set-group-prefix set-prefix)
root? (= tree-depth 0)
collapsed? (mf/use-state false)
children? (and
(or root? set-group?)
(not @collapsed?))]
[:*
(cond
root? nil
set?
[:& sets-tree-set
{:set set-node
:active? active?
:selected? (selected? tree-path)
:on-select on-select
:label set-path'
:tree-path (or tree-path set-path)
:tree-depth tree-depth
:editing? editing?
:on-toggle on-toggle
:on-edit on-edit
:on-edit-submit on-edit-submit}]
set-group?
[:& sets-tree-set-group
{:selected? (selected? tree-path)
:on-select on-select
:label set-path'
:collapsed? collapsed?
:tree-path (or tree-path set-path)
:tree-depth tree-depth
:editing? editing?
:on-edit on-edit
:on-edit-submit on-edit-submit}])
(when children?
(for [[set-path set-node] set-node
:let [tree-path' (str (when tree-path (str tree-path "/")) set-path)]]
[:& sets-tree
{:key tree-path'
:set-path set-path
:set-node set-node
:tree-depth (when-not root? (inc tree-depth))
:tree-path tree-path'
:on-select on-select
:selected? selected?
:on-toggle on-toggle
:active? active?
:editing? editing?
:on-edit on-edit
:on-edit-submit on-edit-submit}]))]))
(mf/defc controlled-sets-list (mf/defc controlled-sets-list
[{:keys [token-sets [{:keys [token-sets
@ -200,59 +231,40 @@
on-select on-select
context] context]
:as _props}] :as _props}]
(let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context)) (let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context))]
submit-token
#(do
(on-create-token-set %)
(on-reset))]
[:ul {:class (stl/css :sets-list)} [:ul {:class (stl/css :sets-list)}
(if (and (if (and
(= origin "theme-modal") (= origin "theme-modal")
(empty? token-sets)) (empty? token-sets))
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
(tr "workspace.token.no-sets-create")] (tr "workspace.token.no-sets-create")]
(for [token-set token-sets] (if (and (= origin "theme-modal")
(when token-set (empty? token-sets))
(let [update-token [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)}
#(do (tr "workspace.token.no-sets-create")]
(on-update-token-set (:name token-set) %) [:& sets-tree
(on-reset))] {:set-node token-sets
[:& sets-tree :selected? token-set-selected?
{:key (:name token-set) :on-select on-select
:token-set token-set :active? token-set-active?
:token-set-selected? (if new? (constantly false) token-set-selected?) :on-toggle on-toggle-token-set
:token-set-active? token-set-active? :editing? editing?
:editing? editing? :on-edit on-edit
:on-select on-select :on-edit-submit on-update-token-set}]))]))
:on-edit on-edit
:on-toggle on-toggle-token-set
:on-submit update-token
:on-cancel on-reset}]))))
(when new?
[:& sets-tree
{:token-set {:name ""}
:token-set-selected? (constantly true)
:token-set-active? (constantly true)
:editing? (constantly true)
:on-select (constantly nil)
:on-edit on-create
:on-submit submit-token
:on-cancel on-reset}])]))
(mf/defc sets-list (mf/defc sets-list
[{:keys []}] [{:keys []}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets) (let [token-sets (mf/deref refs/workspace-token-sets-tree)
selected-token-set-id (mf/deref refs/workspace-selected-token-set-id) selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)
token-set-selected? (mf/use-fn token-set-selected? (mf/use-fn
(mf/deps token-sets selected-token-set-id) (mf/deps token-sets selected-token-set-id)
(fn [set-name] (fn [tree-path]
(= set-name selected-token-set-id))) (= tree-path selected-token-set-id)))
active-token-set-ids (mf/deref refs/workspace-active-set-names) active-token-set-names (mf/deref refs/workspace-active-set-names)
token-set-active? (mf/use-fn token-set-active? (mf/use-fn
(mf/deps active-token-set-ids) (mf/deps active-token-set-names)
(fn [id] (fn [set-name]
(get active-token-set-ids id)))] (get active-token-set-names set-name)))]
[:& controlled-sets-list [:& controlled-sets-list
{:token-sets token-sets {:token-sets token-sets
:token-set-selected? token-set-selected? :token-set-selected? token-set-selected?

View file

@ -13,10 +13,14 @@
} }
.set-item-container { .set-item-container {
@include bodySmallTypography;
display: flex;
align-items: center;
width: 100%; width: 100%;
min-height: $s-32;
cursor: pointer; cursor: pointer;
color: var(--layer-row-foreground-color); color: var(--layer-row-foreground-color);
padding-left: $s-20; padding-left: calc($s-32 * var(--tree-depth, 0));
border: $s-2 solid transparent; border: $s-2 solid transparent;
&.dnd-over-bot { &.dnd-over-bot {
@ -30,17 +34,6 @@
} }
} }
.set-item-set,
.set-item-group {
@include bodySmallTypography;
display: flex;
align-items: center;
min-height: $s-32;
width: 100%;
cursor: pointer;
color: var(--layer-row-foreground-color);
}
.set-name { .set-name {
@include textEllipsis; @include textEllipsis;
flex-grow: 1; flex-grow: 1;
@ -55,6 +48,10 @@
padding-right: $s-4; padding-right: $s-4;
} }
.root-icon {
margin-left: $s-8;
}
.checkbox-style { .checkbox-style {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -76,7 +73,7 @@
color: var(--color-background-secondary); color: var(--color-background-secondary);
} }
.set-item-set:hover { .set-item-container:hover {
background-color: var(--layer-row-background-color-hover); background-color: var(--layer-row-background-color-hover);
color: var(--layer-row-foreground-color-hover); color: var(--layer-row-foreground-color-hover);
box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover); box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover);

View file

@ -35,10 +35,10 @@
[:span {:class (stl/css :title)} title]]) [:span {:class (stl/css :title)} title]])
(mf/defc menu (mf/defc menu
[{:keys [token-set-name]}] [{:keys [tree-path]}]
(let [{:keys [on-edit]} (sets-context/use-context) (let [{:keys [on-edit]} (sets-context/use-context)
edit-name (mf/use-fn #(on-edit token-set-name)) edit-name (mf/use-fn #(on-edit tree-path))
delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set token-set-name)))] delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set-path tree-path)))]
[:ul {:class (stl/css :context-list)} [:ul {:class (stl/css :context-list)}
[:& menu-entry {:title (tr "labels.rename") :on-click edit-name}] [:& menu-entry {:title (tr "labels.rename") :on-click edit-name}]
[:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]])) [:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]]))
@ -49,8 +49,7 @@
top (+ (get-in mdata [:position :y]) 5) top (+ (get-in mdata [:position :y]) 5)
left (+ (get-in mdata [:position :x]) 5) left (+ (get-in mdata [:position :x]) 5)
width (mf/use-state 0) width (mf/use-state 0)
dropdown-ref (mf/use-ref) dropdown-ref (mf/use-ref)]
token-set-name (:token-set-name mdata)]
(mf/use-effect (mf/use-effect
(mf/deps mdata) (mf/deps mdata)
(fn [] (fn []
@ -62,4 +61,4 @@
:ref dropdown-ref :ref dropdown-ref
:style {:top top :left left} :style {:top top :left left}
:on-context-menu prevent-default} :on-context-menu prevent-default}
[:& menu {:token-set-name token-set-name}]]])) [:& menu {:tree-path (:tree-path mdata)}]]]))

View file

@ -41,7 +41,8 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf] [rumext.v2 :as mf]
[shadow.resource])) [shadow.resource]
[app.main.data.tokens :as wdt]))
(def lens:token-type-open-status (def lens:token-type-open-status
(l/derived (l/in [:workspace-tokens :open-status]) st/state)) (l/derived (l/in [:workspace-tokens :open-status]) st/state))
@ -204,7 +205,10 @@
(let [{:keys [on-create new?]} (sets-context/use-context) (let [{:keys [on-create new?]} (sets-context/use-context)
on-click #(do on-click #(do
(on-open) (on-open)
(on-create))] (let [set-path (some-> (js/prompt "Token Set Path")
(str/trim))]
(when-not (str/empty? set-path)
(st/emit! (wdt/create-token-set {:name set-path})))))]
(if (= style "inline") (if (= style "inline")
(when-not new? (when-not new?
[:div {:class (stl/css :empty-sets-wrapper)} [:div {:class (stl/css :empty-sets-wrapper)}
@ -219,28 +223,30 @@
:aria-label (tr "workspace.token.add set")}]))) :aria-label (tr "workspace.token.add set")}])))
(mf/defc themes-sets-tab (mf/defc themes-sets-tab
[] [{:keys [resize-height]}]
(let [token-sets (mf/deref refs/workspace-ordered-token-sets) (let [token-sets (mf/deref refs/workspace-ordered-token-sets)
open? (mf/use-state true) open? (mf/use-state true)
on-open (mf/use-fn #(reset! open? true))] on-open (mf/use-fn #(reset! open? true))]
[:& sets-context/provider {} [:& sets-context/provider {}
[:& sets-context-menu] [:& sets-context-menu]
[:div {:class (stl/css :sets-sidebar)} [:article {:class (stl/css :sets-section-wrapper)
[:& themes-header] :style {"--resize-height" (str resize-height "px")}}
[:div {:class (stl/css :sidebar-header)} [:div {:class (stl/css :sets-sidebar)}
[:& title-bar {:collapsable true [:& themes-header]
:collapsed (not @open?) [:div {:class (stl/css :sidebar-header)}
:all-clickable true [:& title-bar {:collapsable true
:title (tr "labels.sets") :collapsed (not @open?)
:on-collapsed #(swap! open? not)} :all-clickable true
[:& add-set-button {:on-open on-open :title (tr "labels.sets")
:style "header"}]]] :on-collapsed #(swap! open? not)}
(when @open? [:& add-set-button {:on-open on-open
[:& h/sortable-container {} :style "header"}]]]
(when (empty? token-sets) (when @open?
(if (empty? token-sets)
[:& add-set-button {:on-open on-open [:& add-set-button {:on-open on-open
:style "inline"}]) :style "inline"}]
[:& sets-list]])]])) [:& h/sortable-container {}
[:& sets-list]]))]]]))
(mf/defc tokens-tab (mf/defc tokens-tab
[_props] [_props]
@ -343,15 +349,14 @@
{::mf/wrap [mf/memo] {::mf/wrap [mf/memo]
::mf/wrap-props false} ::mf/wrap-props false}
[_props] [_props]
(let [{on-pointer-down-pages :on-pointer-down (let [tokens-tab? (not (mf/deref refs/workspace-token-set-group-selected?))
{on-pointer-down-pages :on-pointer-down
on-lost-pointer-capture-pages :on-lost-pointer-capture on-lost-pointer-capture-pages :on-lost-pointer-capture
on-pointer-move-pages :on-pointer-move on-pointer-move-pages :on-pointer-move
size-pages-opened :size} size-pages-opened :size}
(use-resize-hook :tokens 200 38 400 :y false nil)] (use-resize-hook :tokens 200 38 400 :y false nil)]
[:div {:class (stl/css :sidebar-wrapper)} [:div {:class (stl/css :sidebar-wrapper)}
[:article {:class (stl/css :sets-section-wrapper) [:& themes-sets-tab {:resize-height size-pages-opened}]
:style {"--resize-height" (str size-pages-opened "px")}}
[:& themes-sets-tab]]
[:article {:class (stl/css :tokens-section-wrapper)} [:article {:class (stl/css :tokens-section-wrapper)}
[:div {:class (stl/css :resize-area-horiz) [:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages :on-pointer-down on-pointer-down-pages

View file

@ -41,16 +41,30 @@
(some-> (get-workspace-tokens-lib state) (some-> (get-workspace-tokens-lib state)
(ctob/get-sets) (ctob/get-sets)
(first) (first)
(:name)))) (ctob/get-set-path))))
(defn get-selected-token-set-node [state]
(when-let [path (some-> (get-selected-token-set-id state)
(ctob/split-token-set-path))]
(some-> (get-workspace-tokens-lib state)
(ctob/get-in-set-tree path))))
(defn get-selected-token-set [state] (defn get-selected-token-set [state]
(when-let [id (get-selected-token-set-id state)] (let [set-node (get-selected-token-set-node state)]
(some-> (get-workspace-tokens-lib state) (when (instance? ctob/TokenSet set-node)
(ctob/get-set id)))) set-node)))
(defn get-selected-token-set-group [state]
(let [set-node (get-selected-token-set-node state)]
(when (and set-node (not (instance? ctob/TokenSet set-node)))
set-node)))
(defn get-selected-token-set-tokens [state] (defn get-selected-token-set-tokens [state]
(some-> (get-selected-token-set state) (some-> (get-selected-token-set state)
:tokens)) :tokens))
(defn token-group-selected? [state]
(some? (get-selected-token-set-group state)))
(defn assoc-selected-token-set-id [state id] (defn assoc-selected-token-set-id [state id]
(assoc-in state [:workspace-local :selected-token-set-id] id)) (assoc-in state [:workspace-local :selected-token-set-id] id))