diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 0b3f3d28b..7bf9a01ad 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -410,6 +410,11 @@ [:type [:= :add-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 [:map {:title "ModTokenSetChange"} [:type [:= :mod-token-set]] @@ -427,6 +432,11 @@ [:type [:= :del-token-set]] [:name :string]]] + [:del-token-set-path + [:map {:title "DelTokenSetPathChange"} + [:type [:= :del-token-set-path]] + [:path :string]]] + [:set-tokens-lib [:map {:title "SetTokensLib"} [:type [:= :set-tokens-lib]] @@ -1046,6 +1056,12 @@ (ctob/ensure-tokens-lib) (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 [data {:keys [name token-set]}] (update data :tokens-lib (fn [lib] @@ -1066,6 +1082,12 @@ (ctob/ensure-tokens-lib) (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 (def ^:private decode-shape diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 9da21cfda..0640c784f 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -818,15 +818,15 @@ (update :undo-changes conj {:type :mod-token-set :name (:name token-set) :token-set (or prev-token-set token-set)}) (apply-changes-local))) -(defn delete-token-set - [changes token-set-name] +(defn delete-token-set-path + [changes token-set-path] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token-theme (some-> (get library-data :tokens-lib) - (ctob/get-set token-set-name))] + prev-token-sets (some-> (get library-data :tokens-lib) + (ctob/get-path-sets token-set-path))] (-> changes - (update :redo-changes conj {:type :del-token-set :name token-set-name}) - (update :undo-changes conj {:type :add-token-set :token-set prev-token-theme}) + (update :redo-changes conj {:type :del-token-set-path :path token-set-path}) + (update :undo-changes conj {:type :add-token-sets :token-sets prev-token-sets}) (apply-changes-local)))) (defn move-token-set-before diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 827af70aa..d05033979 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -183,6 +183,14 @@ (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] (str set-prefix set-name)) @@ -199,14 +207,29 @@ set-name (add-set-prefix (last paths))] (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] - (-> (split-path token-set-name set-separator) + (-> (split-token-set-path token-set-name) (add-token-set-paths-prefix))) (defn get-token-set-path [token-set] (let [path (get-path token-set set-separator)] (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 "Convert tokens into a nested tree with their `:name` as the path. Optionally use `update-token-fn` option to transform the token." @@ -234,16 +257,27 @@ {:tokens-tree {} :ids {}} tokens)) (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") (update-token [_ token-name f] "update a token in the list") (delete-token [_ token-name] "delete a token from the list") (get-token [_ token-name] "return token by token-name") (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-dtcg-tokens-tree [_] "returns tokens tree formated to the dtcg spec")) (defrecord TokenSet [name description modified-at tokens] 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] (dm/assert! "expected valid token" (check-token! token)) (TokenSet. name @@ -278,6 +312,9 @@ (get-tokens [_] (vals tokens)) + (get-set-path [_] + (set-name->set-path-string name)) + (get-tokens-tree [_] (tokens-tree tokens)) @@ -325,11 +362,14 @@ (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") (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. When `before-set-name` is nil, move set to bottom") (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-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-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-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`")) @@ -381,6 +421,8 @@ When `before-set-name` is nil, move set to bottom") (set-sets [_ set-names] "set the active token sets") (disable-set [_ set-name] "disable set in 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-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")) @@ -403,6 +445,16 @@ When `before-set-name` is nil, move set to bottom") (disj 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 [_] (token-theme-path group name)) @@ -569,36 +621,49 @@ When `before-set-name` is nil, move set to bottom") (add-sets [this token-sets] (reduce - (fn [lib set] - (add-set lib set)) - this token-sets)) + (fn [lib set] + (add-set lib set)) + this token-sets)) (update-set [this set-name f] (let [path (split-token-set-name set-name) set (get-in sets path)] (if set - (let [set' (-> (make-token-set (f set)) - (assoc :modified-at (dt/now))) - path' (get-token-set-path set')] + (let [set' (-> (make-token-set (f set)) + (assoc :modified-at (dt/now))) + path' (get-token-set-path set') + name-changed? (not= (:name set) (:name set'))] (check-token-set! set') - (TokensLib. (if (= (:name set) (:name set')) - (d/oassoc-in sets path set') - (-> sets + (if name-changed? + (TokensLib. (-> sets (d/oassoc-in-before path path' set') - (d/dissoc-in path))) - themes - active-themes)) + (d/dissoc-in path)) + (walk/postwalk + (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))) - (delete-set [_ set-name] - (let [path (split-token-set-name set-name)] + (delete-set-path [_ set-path] + (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) - (walk/postwalk - (fn [form] - (if (instance? TokenTheme form) - (disable-set form set-name) - form)) - themes) + ;; TODO: When deleting a set-group, also deactivate the child sets + (if set-group? + themes + (walk/postwalk + (fn [form] + (if (instance? TokenTheme form) + (disable-set form set-path) + form)) + themes)) active-themes))) ;; TODO Handle groups and nesting @@ -620,10 +685,18 @@ When `before-set-name` is nil, move set to bottom") (get-set-tree [_] sets) + (get-in-set-tree [_ path] + (get-in sets path)) + (get-sets [_] (->> (tree-seq d/ordered-map? vals sets) (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] (map :name (get-sets this))) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index eda14e846..dbd3358c7 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -95,6 +95,14 @@ (update [_ state] (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] (let [new-token-theme token-theme] (ptk/reify ::create-token-theme @@ -157,7 +165,7 @@ (let [changes (-> (pcb/empty-changes it) (pcb/add-token-set new-token-set))] (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))))))) (defn update-token-set [set-name token-set] @@ -169,7 +177,7 @@ changes (-> (pcb/empty-changes it) (pcb/update-token-set token-set prev-token-set))] (rx/of - (set-selected-token-set-id (:name token-set)) + (set-selected-token-set-id-from-name (:name token-set)) (dch/commit-changes changes)))))) (defn toggle-token-set [{:keys [token-set-name]}] @@ -202,7 +210,7 @@ (ctob/get-sets) (first) (:name) - (set-selected-token-set-id)) + (set-selected-token-set-id-from-name)) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) (pcb/set-tokens-lib lib))] @@ -211,14 +219,14 @@ update-token-set-change (wtu/update-workspace-tokens)))))) -(defn delete-token-set [token-set-name] - (ptk/reify ::delete-token-set +(defn delete-token-set-path [token-set-path] + (ptk/reify ::delete-token-set-path ptk/WatchEvent (watch [it state _] (let [data (get state :workspace-data) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/delete-token-set token-set-name))] + (pcb/delete-token-set-path token-set-path))] (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) @@ -268,7 +276,7 @@ (pcb/update-token (pcb/empty-changes) (:name token-set) token prev-token) (pcb/add-token (pcb/empty-changes) (:name token-set) token)))] (rx/of - (set-selected-token-set-id token-set-name) + (set-selected-token-set-id-from-name token-set-name) (dch/commit-changes changes)))))) (defn delete-token diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 104a504b5..f8ad9fd07 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -493,9 +493,15 @@ (def workspace-selected-token-set-id (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 (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 (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 02f670393..43d29f158 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -7,14 +7,13 @@ (ns app.main.ui.workspace.tokens.sets (:require-macros [app.main.style :as stl]) (:require - [app.main.data.notifications :as ntf] + [app.common.types.tokens-lib :as ctob] [app.main.data.tokens :as wdt] [app.main.refs :as refs] [app.main.store :as st] [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.typography.text :refer [text*]] - [app.main.ui.hooks :as h] [app.main.ui.workspace.tokens.sets-context :as sets-context] [app.util.dom :as dom] [app.util.i18n :refer [tr]] @@ -25,8 +24,8 @@ (defn on-toggle-token-set-click [token-set-name] (st/emit! (wdt/toggle-token-set {:token-set-name token-set-name}))) -(defn on-select-token-set-click [name] - (st/emit! (wdt/set-selected-token-set-id name))) +(defn on-select-token-set-click [tree-path] + (st/emit! (wdt/set-selected-token-set-id tree-path))) (defn on-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] (st/emit! (wdt/create-token-set token-set))) -(mf/defc editing-node +(mf/defc editing-label [{:keys [default-value on-cancel on-submit]}] (let [ref (mf/use-ref) on-submit-valid (mf/use-fn @@ -43,7 +42,9 @@ (if (or (str/empty? value) (= value default-value)) (on-cancel) - (on-submit value))))) + (do + (on-submit value) + (on-cancel)))))) on-key-down (mf/use-fn (fn [event] (cond @@ -58,136 +59,166 @@ :auto-focus true :default-value default-value}])) -(mf/defc sets-tree - [{:keys [token-set - token-set-active? - token-set-selected? - 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) - +(mf/defc sets-tree-set-group + [{:keys [label tree-depth tree-path selected? collapsed? on-select editing? on-edit on-edit-submit]}] + (let [editing?' (editing? tree-path) + {:keys [on-create on-reset] :as ctx} (sets-context/use-context) on-click (mf/use-fn - (mf/deps editing-node?) + (mf/deps editing? tree-path) (fn [event] (dom/stop-propagation event) - (when-not editing-node? - (on-select name)))) + (when-not (editing? tree-path) + (on-select tree-path)))) on-context-menu (mf/use-fn - (mf/deps editing-node? name) + (mf/deps editing? tree-path) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (when-not editing-node? + (when-not (editing? tree-path) (st/emit! (wdt/show-token-set-context-menu {:position (dom/get-client-position event) - :token-set-name name}))))) - - on-drag - (mf/use-fn - (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 + :tree-path tree-path})))))] + [:div {;; :ref dref + :role "button" + :style {"--tree-depth" tree-depth} :class (stl/css-case :set-item-container true - :dnd-over (= (:over dprops) :center) - :dnd-over-top (= (:over dprops) :top) - :dnd-over-bot (= (:over dprops) :bot)) + :selected-set selected?) :on-click on-click - :on-double-click on-edit-name :on-context-menu on-context-menu - :data-name name} - [:div {:class (stl/css-case :set-item-group group? - :set-item-set set? - :selected-set selected?)} - (when group? - [:> icon-button* {:on-click on-collapse - :aria-label (tr "labels.collapse") - :icon (if @collapsed? - "arrow-right" - "arrow-down") - :variant "action"}]) + :on-double-click #(on-edit tree-path)} + [:> icon-button* + {:on-click (fn [event] + (.stopPropagation event) + (swap! collapsed? not)) + :aria-label (tr "labels.collapse") + :icon (if @collapsed? "arrow-right" "arrow-down") + :variant "action"}] + [:> icon* + {:id "group" + :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") - :class (stl/css :icon)}] - (if editing-node? - [:& editing-node {:default-value name - :on-submit on-submit-edit - :on-cancel on-cancel}] - [:* - [:div {:class (stl/css :set-name)} name] - (if set? - [:button {:on-click on-toggle-set - :class (stl/css-case :checkbox-style true - :checkbox-checked-style visible?)} - (when visible? - [:> 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)])]))])]])) +(mf/defc sets-tree-set + [{:keys [set label tree-depth tree-path selected? on-select active? on-toggle editing? on-edit on-edit-submit]}] + (let [set-name (.-name set) + {:keys [on-create on-reset] :as ctx} (sets-context/use-context) + editing?' (editing? tree-path) + active?' (active? set-name) + on-click + (mf/use-fn + (mf/deps editing?' tree-path) + (fn [event] + (dom/stop-propagation event) + (when-not editing?' + (on-select tree-path)))) -(defn warn-on-try-create-token-set-group! [] - (st/emit! (ntf/show {:content (tr "workspace.token.grouping-set-alert") - :notification-type :toast - :type :warning - :timeout 3000}))) + on-context-menu + (mf/use-fn + (mf/deps editing?' tree-path) + (fn [event] + (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 [{:keys [token-sets @@ -200,59 +231,40 @@ on-select context] :as _props}] - (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))] + (let [{:keys [editing? new? on-edit on-create on-reset] :as ctx} (or context (sets-context/use-context))] [:ul {:class (stl/css :sets-list)} (if (and (= origin "theme-modal") (empty? token-sets)) [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} (tr "workspace.token.no-sets-create")] - (for [token-set token-sets] - (when token-set - (let [update-token - #(do - (on-update-token-set (:name token-set) %) - (on-reset))] - [:& sets-tree - {:key (:name token-set) - :token-set token-set - :token-set-selected? (if new? (constantly false) token-set-selected?) - :token-set-active? token-set-active? - :editing? editing? - :on-select on-select - :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}])])) + (if (and (= origin "theme-modal") + (empty? token-sets)) + [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} + (tr "workspace.token.no-sets-create")] + [:& sets-tree + {:set-node token-sets + :selected? token-set-selected? + :on-select on-select + :active? token-set-active? + :on-toggle on-toggle-token-set + :editing? editing? + :on-edit on-edit + :on-edit-submit on-update-token-set}]))])) (mf/defc sets-list [{: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) token-set-selected? (mf/use-fn (mf/deps token-sets selected-token-set-id) - (fn [set-name] - (= set-name selected-token-set-id))) - active-token-set-ids (mf/deref refs/workspace-active-set-names) + (fn [tree-path] + (= tree-path selected-token-set-id))) + active-token-set-names (mf/deref refs/workspace-active-set-names) token-set-active? (mf/use-fn - (mf/deps active-token-set-ids) - (fn [id] - (get active-token-set-ids id)))] + (mf/deps active-token-set-names) + (fn [set-name] + (get active-token-set-names set-name)))] [:& controlled-sets-list {:token-sets token-sets :token-set-selected? token-set-selected? diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.scss b/frontend/src/app/main/ui/workspace/tokens/sets.scss index 24a18a77e..10c7c83f0 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.scss +++ b/frontend/src/app/main/ui/workspace/tokens/sets.scss @@ -13,10 +13,14 @@ } .set-item-container { + @include bodySmallTypography; + display: flex; + align-items: center; width: 100%; + min-height: $s-32; cursor: pointer; color: var(--layer-row-foreground-color); - padding-left: $s-20; + padding-left: calc($s-32 * var(--tree-depth, 0)); border: $s-2 solid transparent; &.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 { @include textEllipsis; flex-grow: 1; @@ -55,6 +48,10 @@ padding-right: $s-4; } +.root-icon { + margin-left: $s-8; +} + .checkbox-style { display: flex; justify-content: center; @@ -76,7 +73,7 @@ color: var(--color-background-secondary); } -.set-item-set:hover { +.set-item-container:hover { background-color: var(--layer-row-background-color-hover); color: var(--layer-row-foreground-color-hover); box-shadow: -100px 0 0 0 var(--layer-row-background-color-hover); diff --git a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs index 1b396740d..4fb37428a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets_context_menu.cljs @@ -35,10 +35,10 @@ [:span {:class (stl/css :title)} title]]) (mf/defc menu - [{:keys [token-set-name]}] + [{:keys [tree-path]}] (let [{:keys [on-edit]} (sets-context/use-context) - edit-name (mf/use-fn #(on-edit token-set-name)) - delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set token-set-name)))] + edit-name (mf/use-fn #(on-edit tree-path)) + delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set-path tree-path)))] [:ul {:class (stl/css :context-list)} [:& menu-entry {:title (tr "labels.rename") :on-click edit-name}] [:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]])) @@ -49,8 +49,7 @@ top (+ (get-in mdata [:position :y]) 5) left (+ (get-in mdata [:position :x]) 5) width (mf/use-state 0) - dropdown-ref (mf/use-ref) - token-set-name (:token-set-name mdata)] + dropdown-ref (mf/use-ref)] (mf/use-effect (mf/deps mdata) (fn [] @@ -62,4 +61,4 @@ :ref dropdown-ref :style {:top top :left left} :on-context-menu prevent-default} - [:& menu {:token-set-name token-set-name}]]])) + [:& menu {:tree-path (:tree-path mdata)}]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 86ea7ad51..abd7f7932 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -41,7 +41,8 @@ [cuerdas.core :as str] [okulary.core :as l] [rumext.v2 :as mf] - [shadow.resource])) + [shadow.resource] + [app.main.data.tokens :as wdt])) (def lens:token-type-open-status (l/derived (l/in [:workspace-tokens :open-status]) st/state)) @@ -204,7 +205,10 @@ (let [{:keys [on-create new?]} (sets-context/use-context) on-click #(do (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") (when-not new? [:div {:class (stl/css :empty-sets-wrapper)} @@ -219,28 +223,30 @@ :aria-label (tr "workspace.token.add set")}]))) (mf/defc themes-sets-tab - [] + [{:keys [resize-height]}] (let [token-sets (mf/deref refs/workspace-ordered-token-sets) open? (mf/use-state true) on-open (mf/use-fn #(reset! open? true))] [:& sets-context/provider {} [:& sets-context-menu] - [:div {:class (stl/css :sets-sidebar)} - [:& themes-header] - [:div {:class (stl/css :sidebar-header)} - [:& title-bar {:collapsable true - :collapsed (not @open?) - :all-clickable true - :title (tr "labels.sets") - :on-collapsed #(swap! open? not)} - [:& add-set-button {:on-open on-open - :style "header"}]]] - (when @open? - [:& h/sortable-container {} - (when (empty? token-sets) + [:article {:class (stl/css :sets-section-wrapper) + :style {"--resize-height" (str resize-height "px")}} + [:div {:class (stl/css :sets-sidebar)} + [:& themes-header] + [:div {:class (stl/css :sidebar-header)} + [:& title-bar {:collapsable true + :collapsed (not @open?) + :all-clickable true + :title (tr "labels.sets") + :on-collapsed #(swap! open? not)} + [:& add-set-button {:on-open on-open + :style "header"}]]] + (when @open? + (if (empty? token-sets) [:& add-set-button {:on-open on-open - :style "inline"}]) - [:& sets-list]])]])) + :style "inline"}] + [:& h/sortable-container {} + [:& sets-list]]))]]])) (mf/defc tokens-tab [_props] @@ -343,15 +349,14 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false} [_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-pointer-move-pages :on-pointer-move size-pages-opened :size} (use-resize-hook :tokens 200 38 400 :y false nil)] [:div {:class (stl/css :sidebar-wrapper)} - [:article {:class (stl/css :sets-section-wrapper) - :style {"--resize-height" (str size-pages-opened "px")}} - [:& themes-sets-tab]] + [:& themes-sets-tab {:resize-height size-pages-opened}] [:article {:class (stl/css :tokens-section-wrapper)} [:div {:class (stl/css :resize-area-horiz) :on-pointer-down on-pointer-down-pages diff --git a/frontend/src/app/main/ui/workspace/tokens/token_set.cljs b/frontend/src/app/main/ui/workspace/tokens/token_set.cljs index 9e1af19c4..380a6b997 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_set.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_set.cljs @@ -41,16 +41,30 @@ (some-> (get-workspace-tokens-lib state) (ctob/get-sets) (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] - (when-let [id (get-selected-token-set-id state)] - (some-> (get-workspace-tokens-lib state) - (ctob/get-set id)))) + (let [set-node (get-selected-token-set-node state)] + (when (instance? ctob/TokenSet set-node) + 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] (some-> (get-selected-token-set state) :tokens)) +(defn token-group-selected? [state] + (some? (get-selected-token-set-group state))) + (defn assoc-selected-token-set-id [state id] (assoc-in state [:workspace-local :selected-token-set-id] id))