From 3692f17e55493c3b3923c91b7c3adde5e48978a0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 11:04:52 +0100 Subject: [PATCH 01/15] :zap: Adapt call style for set-lists component --- frontend/src/app/main/refs.cljs | 4 ++ .../app/main/ui/workspace/tokens/sets.cljs | 41 +++++++++++-------- .../app/main/ui/workspace/tokens/sidebar.cljs | 38 ++++++++++++----- 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index c5de2cd79..c884885da 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -453,9 +453,13 @@ (def workspace-token-themes-no-hidden (l/derived #(remove ctob/hidden-temporary-theme? %) workspace-token-themes)) +;; FIXME: deprecated (def workspace-selected-token-set-name (l/derived dwts/get-selected-token-set-name st/state)) +(def selected-token-set-name + (l/derived (l/key :selected-token-set-name) workspace-local)) + (def workspace-ordered-token-sets (l/derived #(or (some-> % ctob/get-sets) []) 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 43b0065b8..57588d202 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -445,23 +445,32 @@ :on-edit-submit-set on-update-token-set :on-edit-submit-group on-update-token-set-group}])])) +(mf/defc sets-list* + [{:keys [tokens-lib selected-token-set-name]}] + + (let [token-sets + (ctob/get-set-tree tokens-lib) + + token-set-selected? + (mf/use-fn + (mf/deps token-sets selected-token-set-name) + (fn [set-name] + (= set-name selected-token-set-name))) + + active-token-set-names + (mf/deref refs/workspace-active-set-names) + + token-set-active? + (mf/use-fn + (mf/deps active-token-set-names) + (fn [set-name] + (get active-token-set-names set-name))) + + token-set-group-active? + (mf/use-fn + (fn [group-path] + @(refs/token-sets-at-path-all-active group-path)))] -(mf/defc sets-list - [{:keys []}] - (let [token-sets (mf/deref refs/workspace-token-sets-tree) - selected-token-set-name (mf/deref refs/workspace-selected-token-set-name) - token-set-selected? (mf/use-fn - (mf/deps token-sets selected-token-set-name) - (fn [set-name] - (= set-name selected-token-set-name))) - active-token-set-names (mf/deref refs/workspace-active-set-names) - token-set-active? (mf/use-fn - (mf/deps active-token-set-names) - (fn [set-name] - (get active-token-set-names set-name))) - token-set-group-active? (mf/use-fn - (fn [group-path] - @(refs/token-sets-at-path-all-active group-path)))] [:& controlled-sets-list {:token-sets token-sets :token-set-selected? token-set-selected? diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 54397f0d9..2f7c594bc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -29,7 +29,7 @@ [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]] [app.main.ui.workspace.tokens.errors :as wte] - [app.main.ui.workspace.tokens.sets :refer [sets-list]] + [app.main.ui.workspace.tokens.sets :refer [sets-list*]] [app.main.ui.workspace.tokens.sets-context :as sets-context] [app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu]] [app.main.ui.workspace.tokens.style-dictionary :as sd] @@ -239,18 +239,31 @@ (mf/defc theme-sets-list* {::mf/private true} - [] - (let [token-sets (mf/deref refs/workspace-ordered-token-sets) - {:keys [new-path] :as ctx} (sets-context/use-context)] + [{:keys [tokens-lib]}] + (let [token-sets + (ctob/get-sets tokens-lib) + + selected-token-set-name + (mf/deref refs/selected-token-set-name) + + selected-token-set-name + (if selected-token-set-name + selected-token-set-name + (-> token-sets first :name)) + + {:keys [new-path] :as ctx} + (sets-context/use-context)] + (if (and (empty? token-sets) (not new-path)) [:> add-set-button* {:style "inline"}] [:& h/sortable-container {} - [:& sets-list]]))) + [:> sets-list* {:tokens-lib tokens-lib + :selected-token-set-name selected-token-set-name}]]))) (mf/defc themes-sets-tab* {::mf/private true} - [{:keys [resize-height]}] + [{:keys [resize-height] :as props}] (let [permissions (mf/use-ctx ctx/permissions) @@ -269,7 +282,7 @@ (when can-edit? [:> add-set-button* {:style "header"}])]] - [:> theme-sets-list* {}]]]])) + [:> theme-sets-list* props]]]])) (mf/defc tokens-tab* [] @@ -406,14 +419,19 @@ 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 "0.6" :y false nil)] + (use-resize-hook :tokens 200 38 400 :y false nil) + + tokens-lib + (mf/deref refs/tokens-lib)] + [:div {:class (stl/css :sidebar-wrapper)} - [:> themes-sets-tab* {:resize-height size-pages-opened}] + [:> themes-sets-tab* {:resize-height size-pages-opened + :tokens-lib tokens-lib}] [:article {:class (stl/css :tokens-section-wrapper) :data-testid "tokens-sidebar"} [:div {:class (stl/css :resize-area-horiz) :on-pointer-down on-pointer-down-pages :on-lost-pointer-capture on-lost-pointer-capture-pages :on-pointer-move on-pointer-move-pages}] - [:> tokens-tab*]] + [:> tokens-tab* {:tokens-lib tokens-lib}]] [:& import-export-button]])) From df6a6795488457af217ec94c14c6ab7adf98ae90 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 11:05:34 +0100 Subject: [PATCH 02/15] :zap: Remove duplicated operations from tokens sidebar --- .../app/main/ui/workspace/tokens/changes.cljs | 2 +- .../app/main/ui/workspace/tokens/sidebar.cljs | 36 ++++++-- .../ui/workspace/tokens/style_dictionary.cljs | 83 +++++++++++-------- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index cab2069fe..28474304e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -38,7 +38,7 @@ Optionally remove attributes from `attributes-to-remove`, this is useful for applying a single attribute from an attributes set while removing other applied tokens from this set." - [{:keys [attributes attributes-to-remove token shape-ids on-update-shape] :as _props}] + [{:keys [attributes attributes-to-remove token shape-ids on-update-shape]}] (ptk/reify ::apply-token ptk/WatchEvent (watch [_ state _] diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 2f7c594bc..21f987f0e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -285,7 +285,7 @@ [:> theme-sets-list* props]]]])) (mf/defc tokens-tab* - [] + [{:keys [tokens-lib]}] (let [objects (mf/deref refs/workspace-page-objects) selected (mf/deref refs/selected-shapes) open-status (mf/deref ref:token-type-open-status) @@ -295,16 +295,36 @@ (into [] (keep (d/getf objects)) selected)) active-theme-tokens - (sd/use-active-theme-tokens) + (mf/with-memo [tokens-lib] + (if tokens-lib + (ctob/get-active-themes-set-tokens tokens-lib) + {})) + + ;; Resolve tokens as second step + active-theme-tokens + (sd/use-resolved-tokens* active-theme-tokens) + + ;; This only checks for the currently explicitly selected set + ;; name, it is ephimeral and can be nil + selected-token-set-name + (mf/deref refs/selected-token-set-name) + + ;; If we have not selected any set explicitly we just + ;; select the first one from the list of sets + selected-token-set-tokens + (if selected-token-set-name + (-> (ctob/get-set tokens-lib selected-token-set-name) + (get :tokens)) + (-> (ctob/get-sets tokens-lib) + (first) + (get :tokens))) tokens - (sd/use-resolved-workspace-tokens) + (mf/with-memo [active-theme-tokens selected-token-set-tokens] + (merge active-theme-tokens selected-token-set-tokens)) - selected-token-set-tokens - (mf/deref refs/workspace-selected-token-set-tokens) - - selected-token-set-name - (mf/deref refs/workspace-selected-token-set-name) + tokens + (sd/use-resolved-tokens* tokens) tokens-by-type (mf/with-memo [tokens selected-token-set-tokens] diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 28d72e34e..945ec37b8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -166,10 +166,10 @@ ([tokens-tree get-token] (resolve-tokens-tree+ tokens-tree get-token (StyleDictionary. default-config))) ([tokens-tree get-token style-dictionary] - (-> style-dictionary - (add-tokens tokens-tree) - (build-dictionary) - (p/then #(process-sd-tokens % get-token))))) + (let [sdict (-> style-dictionary + (add-tokens tokens-tree) + (build-dictionary))] + (p/fmap #(process-sd-tokens % get-token) sdict)))) (defn sd-token-name [^js sd-token] (.. sd-token -original -name)) @@ -177,8 +177,10 @@ (defn sd-token-uuid [^js sd-token] (uuid (.-uuid (.-id ^js sd-token)))) -(defn resolve-tokens+ [tokens] - (resolve-tokens-tree+ (ctob/tokens-tree tokens) #(get tokens (sd-token-name %)))) +(defn resolve-tokens+ + [tokens] + (let [tokens-tree (ctob/tokens-tree tokens)] + (resolve-tokens-tree+ tokens-tree #(get tokens (sd-token-name %))))) (defn resolve-tokens-interactive+ "Interactive check of resolving tokens. @@ -267,36 +269,45 @@ :or {cache-atom !tokens-cache} :as config}] (let [tokens-state (mf/use-state (get @cache-atom tokens))] - (mf/use-effect - (mf/deps tokens config) - (fn [] - (let [cached (get @cache-atom tokens)] - (cond - (nil? tokens) nil - ;; The tokens are already processing somewhere - (p/promise? cached) (-> cached - (p/then #(reset! tokens-state %)) - #_(p/catch js/console.error)) - ;; Get the cached entry - (some? cached) (reset! tokens-state cached) - ;; No cached entry, start processing - :else (let [promise+ (if interactive? - (resolve-tokens-interactive+ tokens) - (resolve-tokens+ tokens))] - (swap! cache-atom assoc tokens promise+) - (p/then promise+ (fn [resolved-tokens] - (swap! cache-atom assoc tokens resolved-tokens) - (reset! tokens-state resolved-tokens)))))))) + + ;; FIXME: this with effect with trigger all the time because + ;; `config` will be always a different instance + + (mf/with-effect [tokens config] + (let [cached (get @cache-atom tokens)] + (cond + (nil? tokens) nil + ;; The tokens are already processing somewhere + (p/promise? cached) (-> cached + (p/then #(reset! tokens-state %)) + #_(p/catch js/console.error)) + ;; Get the cached entry + (some? cached) (reset! tokens-state cached) + ;; No cached entry, start processing + :else (let [promise+ (if interactive? + (resolve-tokens-interactive+ tokens) + (resolve-tokens+ tokens))] + (swap! cache-atom assoc tokens promise+) + (p/then promise+ (fn [resolved-tokens] + (swap! cache-atom assoc tokens resolved-tokens) + (reset! tokens-state resolved-tokens))))))) @tokens-state)) -(defn use-resolved-workspace-tokens [] - (let [active-theme-tokens (mf/deref refs/workspace-active-theme-sets-tokens) - selected-token-set-tokens (mf/deref refs/workspace-selected-token-set-tokens) - prefer-selected-token-set-tokens (merge active-theme-tokens selected-token-set-tokens)] - (use-resolved-tokens prefer-selected-token-set-tokens))) +(defn use-resolved-tokens* + "This hook will return the unresolved tokens as state until they are + processed, then the state will be updated with the resolved tokens. -(defn use-active-theme-tokens - "A hook that returns active tokens for the current active theme" - [] - (-> (mf/deref refs/workspace-active-theme-sets-tokens) - (use-resolved-tokens {:cache-atom !theme-tokens-cache}))) + This is a cache-less, simplified version of use-resolved-tokens + hook." + [tokens & {:keys [interactive?]}] + (let [state* (mf/use-state tokens)] + (mf/with-effect [tokens interactive?] + (when (seq tokens) + (let [promise (if interactive? + (resolve-tokens-interactive+ tokens) + (resolve-tokens+ tokens))] + + (->> promise + (p/fmap (fn [resolved-tokens] + (reset! state* resolved-tokens))))))) + @state*)) From baa5258a4351b63a60cfccbdd2c21c944d8c3bfb Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 13:25:31 +0100 Subject: [PATCH 03/15] :zap: Declare transducer statically for path split function --- common/src/app/common/types/tokens_lib.cljc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 88834e12d..dade7bf81 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -26,13 +26,15 @@ (def valid-groupable-item? (sm/validator schema:groupable-item)) +(def ^:private xf:clean-string + (comp (map str/trim) + (remove str/empty?))) + (defn split-path "Decompose a string in the form 'one.two.three' into a vector of strings, removing spaces." [path separator] - (let [xf (comp (map str/trim) - (remove str/empty?))] - (->> (str/split path separator) - (into [] xf)))) + (->> (str/split path separator) + (into [] xf:clean-string))) (defn join-path "Regenerate a path as a string, from a vector." From 5446464d7eecb1e90c0c0cb7e2bf525354d5b495 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 13:25:55 +0100 Subject: [PATCH 04/15] :paperclip: Add fixme comment for confusing name --- common/src/app/common/types/tokens_lib.cljc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index dade7bf81..39c4c6b9c 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -106,7 +106,10 @@ (defn get-token-path [path] (get-path path token-separator)) -(defn split-token-path [path] +;; FIXME: misleading name, we are spliting name into path, not +;; spliting path into path +(defn split-token-path + [path] (split-path path token-separator)) (defrecord Token [name type value description modified-at]) From 4a4cd9492a2b1784c70d5069b1a3a94728ba04d7 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 13:27:06 +0100 Subject: [PATCH 05/15] :recycle: Refactor token set auto selection mechanism This is a general purpose change that also allows perform a best effort on selection sets when the name is changed (per example by moving it into other group). --- common/src/app/common/types/tokens_lib.cljc | 73 +++++-------------- frontend/src/app/main/data/tokens.cljs | 48 ++++++------ .../data/workspace/tokens/selected_set.cljs | 10 +-- .../app/main/ui/workspace/tokens/sets.cljs | 5 +- .../app/main/ui/workspace/tokens/sidebar.cljs | 51 +++++++++---- .../ui/workspace/tokens/style_dictionary.cljs | 1 - 6 files changed, 82 insertions(+), 106 deletions(-) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 39c4c6b9c..5c7784c30 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -252,71 +252,35 @@ 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-token-set-path token-set-name) - (add-token-set-paths-prefix))) +(defn split-token-set-name + [name] + (split-path name set-separator)) (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 get-token-set-final-name [path] - (-> (split-token-set-path path) - (last))) +(defn get-token-set-final-name + [name] + (-> (split-token-set-name name) + (peek))) (defn set-name->prefixed-full-path [name-str] - (-> (split-token-set-path name-str) + (-> (split-token-set-name name-str) (set-full-path->set-prefixed-full-path))) (defn get-token-set-prefixed-path [token-set] (let [path (get-path token-set set-separator)] (set-full-path->set-prefixed-full-path path))) -(defn get-prefixed-token-set-final-prefix [prefixed-path-str] - (some-> (get-token-set-final-name prefixed-path-str) - (split-set-str-path-prefix) - (first))) - -(defn set-name-string->prefixed-set-path-string [name-str] - (-> (set-name->prefixed-full-path name-str) - (join-set-path))) - -(defn prefixed-set-path-string->set-path [path-str] - (->> (split-token-set-path path-str) +(defn prefixed-set-path-string->set-name-string [path-str] + (->> (split-token-set-name path-str) (map (fn [path-part] (or (-> (split-set-str-path-prefix path-part) (second)) - path-part))))) - -(defn prefixed-set-path-string->set-name-string [path-str] - (->> (prefixed-set-path-string->set-path path-str) + path-part))) (join-set-path))) -(defn prefixed-set-path-final-group? - "Predicate if the given prefixed path string ends with a group." - [prefixed-path-str] - (= (get-prefixed-token-set-final-prefix prefixed-path-str) set-group-prefix)) - -(defn prefixed-set-path-final-set? - "Predicate if the given prefixed path string ends with a set." - [prefixed-path-str] - (= (get-prefixed-token-set-final-prefix prefixed-path-str) set-prefix)) - (defn replace-last-path-name "Replaces the last element in a `path` vector with `name`." [path name] @@ -363,7 +327,7 @@ (defrecord TokenSet [name description modified-at tokens] ITokenSet (update-name [_ set-name] - (TokenSet. (-> (split-token-set-path name) + (TokenSet. (-> (split-token-set-name name) (drop-last) (concat [set-name]) (join-set-path)) @@ -406,7 +370,8 @@ (vals tokens)) (get-set-prefixed-path-string [_] - (set-name-string->prefixed-set-path-string name)) + (-> (set-name->prefixed-full-path name) + (join-set-path))) (get-tokens-tree [_] (tokens-tree tokens)) @@ -754,7 +719,7 @@ used for managing active sets without a user created theme.") ;; Set (and v (instance? TokenSet v)) [{:group? false - :path (split-token-set-path (:name v)) + :path (split-token-set-name (:name v)) :parent-path parent :depth depth :set v}] @@ -880,7 +845,7 @@ Will return a value that matches this schema: this))) (delete-set-path [_ prefixed-set-name] - (let [prefixed-set-path (split-token-set-path prefixed-set-name) + (let [prefixed-set-path (split-token-set-name prefixed-set-name) set-node (get-in sets prefixed-set-path) set-group? (not (instance? TokenSet set-node)) set-name-string (prefixed-set-path-string->set-name-string prefixed-set-name)] @@ -988,13 +953,13 @@ Will return a value that matches this schema: (->> (tree-seq d/ordered-map? vals sets) (filter (partial instance? TokenSet)))) - (get-path-sets [_ path] - (some->> (get-in sets (split-token-set-path path)) + (get-path-sets [_ name] + (some->> (get-in sets (split-token-set-name name)) (tree-seq d/ordered-map? vals) (filter (partial instance? TokenSet)))) (get-sets-at-prefix-path [_ prefixed-path-str] - (some->> (get-in sets (split-token-set-path prefixed-path-str)) + (some->> (get-in sets (split-token-set-name prefixed-path-str)) (tree-seq d/ordered-map? vals) (filter (partial instance? TokenSet)))) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index de295d071..053ab0764 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -108,6 +108,8 @@ (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) +(declare set-selected-token-set-name) + (defn create-token-set [set-name token-set] (let [new-token-set (-> token-set (update :name #(if (empty? %) set-name (ctob/join-set-path [% set-name]))))] @@ -117,7 +119,7 @@ (let [changes (-> (pcb/empty-changes it) (pcb/add-token-set new-token-set))] (rx/of - (dwts/set-selected-token-set-name (:name new-token-set)) + (set-selected-token-set-name (:name new-token-set)) (dch/commit-changes changes))))))) (defn rename-token-set-group [set-group-path set-group-fname] @@ -138,7 +140,7 @@ changes (-> (pcb/empty-changes it) (pcb/update-token-set token-set prev-token-set))] (rx/of - (dwts/set-selected-token-set-name (:name token-set)) + (set-selected-token-set-name (:name token-set)) (dch/commit-changes changes)))))) (defn toggle-token-set [{:keys [token-set-name]}] @@ -164,18 +166,11 @@ ptk/WatchEvent (watch [it state _] (let [data (dsh/lookup-file-data state) - update-token-set-change (some-> lib - (ctob/get-sets) - (first) - (:name) - (dwts/set-selected-token-set-name)) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) (pcb/set-tokens-lib lib))] - (rx/of - (dch/commit-changes changes) - update-token-set-change - (wtu/update-workspace-tokens)))))) + (rx/of (dch/commit-changes changes) + (wtu/update-workspace-tokens)))))) (defn delete-token-set-path [group? path] (ptk/reify ::delete-token-set-path @@ -221,20 +216,13 @@ (ptk/reify ::drop-token-set ptk/WatchEvent (watch [it state _] - (let [undo-id (js/Symbol)] - (try - (when-let [changes (clt/generate-move-token-set (pcb/empty-changes it) (get-tokens-lib state) drop-opts)] - (rx/of - (dwu/start-undo-transaction undo-id) - (dch/commit-changes changes) - (some-> (get-in changes [:redo-changes 0 :to-path]) - (ctob/join-set-path) - (dwts/set-selected-token-set-name)) - (wtu/update-workspace-tokens) - (dwu/commit-undo-transaction undo-id))) - (catch js/Error e - (rx/of - (drop-error (ex-data e))))))))) + (try + (when-let [changes (clt/generate-move-token-set (pcb/empty-changes it) (get-tokens-lib state) drop-opts)] + (rx/of (dch/commit-changes changes) + (wtu/update-workspace-tokens))) + (catch js/Error e + (rx/of + (drop-error (ex-data e)))))))) (defn update-create-token [{:keys [token prev-token-name]}] @@ -267,7 +255,7 @@ (pcb/set-token set-name (or prev-token-name (:name token)) token)))] (rx/of - (dwts/set-selected-token-set-name set-name) + (set-selected-token-set-name set-name) (when-not prev-token-name (ptk/event ::ev/event {::ev/name "create-tokens"})) (dch/commit-changes changes)))))) @@ -345,3 +333,11 @@ ptk/UpdateEvent (update [_ state] (assoc-in state [:workspace-local :token-set-context-menu] nil)))) + +(defn set-selected-token-set-name + [name] + (ptk/reify ::set-selected-token-set-name + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local assoc :selected-token-set-name name)))) + diff --git a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs index 748a8acee..50f54552a 100644 --- a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs +++ b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs @@ -9,8 +9,7 @@ Will default to the first set." (:require [app.common.types.tokens-lib :as ctob] - [app.main.data.helpers :as dsh] - [potok.v2.core :as ptk])) + [app.main.data.helpers :as dsh])) (defn get-selected-token-set-name [state] (or (get-in state [:workspace-local :selected-token-set-name]) @@ -33,10 +32,3 @@ (defn get-selected-token-set-tokens [state] (some-> (get-selected-token-set state) :tokens)) - -(defn set-selected-token-set-name - [name] - (ptk/reify ::set-selected-token-set-path-from-name - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local assoc :selected-token-set-name name)))) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 57588d202..a3d04a4c1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -12,7 +12,6 @@ [app.common.types.tokens-lib :as ctob] [app.main.data.event :as ev] [app.main.data.tokens :as wdt] - [app.main.data.workspace.tokens.selected-set :as dwts] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] @@ -34,7 +33,7 @@ (st/emit! (wdt/toggle-token-set-group group-path))) (defn on-select-token-set-click [set-name] - (st/emit! (dwts/set-selected-token-set-name set-name))) + (st/emit! (wdt/set-selected-token-set-name set-name))) (defn on-update-token-set [set-name token-set] (st/emit! (wdt/update-token-set (:name token-set) (ctob/update-name token-set set-name)))) @@ -449,7 +448,7 @@ [{:keys [tokens-lib selected-token-set-name]}] (let [token-sets - (ctob/get-set-tree tokens-lib) + (some-> tokens-lib (ctob/get-set-tree)) token-set-selected? (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 21f987f0e..7454489bf 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -240,17 +240,17 @@ (mf/defc theme-sets-list* {::mf/private true} [{:keys [tokens-lib]}] - (let [token-sets - (ctob/get-sets tokens-lib) + (let [;; FIXME: This is an inneficient operation just for being + ;; ability to check if there are some sets and lookup the + ;; first one when no set is selected, should be REFACTORED; is + ;; inneficient because instead of return the sets as-is (tree) + ;; it firstly makes it a plain seq from tree. + token-sets + (some-> tokens-lib (ctob/get-sets)) selected-token-set-name (mf/deref refs/selected-token-set-name) - selected-token-set-name - (if selected-token-set-name - selected-token-set-name - (-> token-sets first :name)) - {:keys [new-path] :as ctx} (sets-context/use-context)] @@ -309,15 +309,15 @@ selected-token-set-name (mf/deref refs/selected-token-set-name) + selected-token-set + (when selected-token-set-name + (some-> tokens-lib (ctob/get-set selected-token-set-name))) + ;; If we have not selected any set explicitly we just ;; select the first one from the list of sets selected-token-set-tokens - (if selected-token-set-name - (-> (ctob/get-set tokens-lib selected-token-set-name) - (get :tokens)) - (-> (ctob/get-sets tokens-lib) - (first) - (get :tokens))) + (when selected-token-set + (get selected-token-set :tokens)) tokens (mf/with-memo [active-theme-tokens selected-token-set-tokens] @@ -340,6 +340,31 @@ (mf/with-memo [tokens-by-type] (get-sorted-token-groups tokens-by-type))] + (mf/with-effect [tokens-lib selected-token-set-name] + (when tokens-lib + (if selected-token-set-name + ;; WORKAROUND: because we don't have a stable reference (by + ;; ID per example) to token sets, when a set is moved the + ;; name/path of the set changes and now can point to not + ;; existing object; on this cases we perform a best effort + ;; search around all existing sets that matches the + ;; name (and not the path) and select it if it is found + (when-not (ctob/get-set tokens-lib selected-token-set-name) + (let [selected-name (ctob/get-token-set-final-name selected-token-set-name) + match (->> (ctob/get-sets tokens-lib) + (map :name) + (filter (fn [name] + (let [path (ctob/split-token-set-name name)] + (= (peek path) selected-name)))) + (first))] + (when match + (st/emit! (dt/set-selected-token-set-name match))))) + + (let [match (->> (ctob/get-sets tokens-lib) + (first) + (:name))] + (st/emit! (dt/set-selected-token-set-name match)))))) + [:* [:& token-context-menu] [:& title-bar {:all-clickable true diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 945ec37b8..1e8d41600 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -5,7 +5,6 @@ [app.common.logging :as l] [app.common.transit :as t] [app.common.types.tokens-lib :as ctob] - [app.main.refs :as refs] [app.main.ui.workspace.tokens.errors :as wte] [app.main.ui.workspace.tokens.tinycolor :as tinycolor] [app.main.ui.workspace.tokens.token :as wtt] From efe204c3464cfd0a8f6b060e5b4e2b74a9a11ed3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 15:02:44 +0100 Subject: [PATCH 06/15] :sparkles: Simplify access to can-edit? using react context --- frontend/src/app/main/ui.cljs | 13 +++++++------ frontend/src/app/main/ui/context.cljs | 1 + .../src/app/main/ui/workspace/tokens/sidebar.cljs | 15 +++++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index bf6b1ebaa..328f2b3e4 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -133,13 +133,14 @@ (fn [] (st/emit! (dtm/finalize-team team-id)))) - (let [team (mf/deref refs/team)] + (let [{:keys [permissions] :as team} (mf/deref refs/team)] (when (= team-id (:id team)) - [:& (mf/provider ctx/current-team-id) {:value team-id} - [:& (mf/provider ctx/permissions) {:value (:permissions team)} - ;; The `:key` is mandatory here because we want to reinitialize - ;; all dom tree instead of simple rerender. - [:* {:key (str team-id)} children]]]))) + [:> (mf/provider ctx/current-team-id) {:value team-id} + [:> (mf/provider ctx/permissions) {:value permissions} + [:> (mf/provider ctx/can-edit?) {:value (:can-edit permissions)} + ;; The `:key` is mandatory here because we want to reinitialize + ;; all dom tree instead of simple rerender. + [:* {:key (str team-id)} children]]]]))) (mf/defc page* {::mf/props :obj diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 09f12f995..abee67fb4 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -34,3 +34,4 @@ (def sidebar (mf/create-context nil)) (def permissions (mf/create-context nil)) +(def can-edit? (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 7454489bf..a13abf726 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -9,6 +9,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.json :as json] [app.common.types.tokens-lib :as ctob] [app.main.data.event :as ev] [app.main.data.modal :as modal] @@ -72,6 +73,9 @@ (let [{:keys [modal title]} (get wtch/token-properties type) + can-edit? + (mf/use-ctx ctx/can-edit?) + tokens (mf/with-memo [tokens] (vec (sort-by :name tokens))) @@ -115,14 +119,12 @@ (dom/stop-propagation event) (when (seq selected-shapes) (st/emit! (wtch/toggle-token {:token token - :shapes selected-shapes}))))) - tokens-count (count tokens) - can-edit? (:can-edit (deref refs/permissions))] + :shapes selected-shapes})))))] [:div {:on-click on-toggle-open-click} [:& cmm/asset-section {:icon (token-section-icon type) :title title - :assets-count tokens-count + :assets-count (count tokens) :open? is-open} [:& cmm/asset-section-block {:role :title-button} (when can-edit? @@ -168,11 +170,8 @@ (let [ordered-themes (mf/deref refs/workspace-token-themes-no-hidden) - permissions - (mf/use-ctx ctx/permissions) - can-edit? - (get permissions :can-edit) + (mf/use-ctx ctx/can-edit?) open-modal (mf/use-fn From 91b6d498fe99810bf4e4823c1bbb08761948cacc Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 15:03:13 +0100 Subject: [PATCH 07/15] :zap: Add minor efficiency improvements to tokens import export --- .../app/main/ui/workspace/tokens/sidebar.cljs | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index a13abf726..6a92a239c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -385,12 +385,15 @@ :active-theme-tokens active-theme-tokens :tokens []}])])) -(mf/defc import-export-button - {::mf/wrap-props false} - [{:keys []}] - (let [show-menu* (mf/use-state false) +(mf/defc import-export-button* + [] + (let [input-ref (mf/use-ref) + + show-menu* (mf/use-state false) show-menu? (deref show-menu*) - can-edit? (:can-edit (deref refs/permissions)) + + can-edit? + (mf/use-ctx ctx/can-edit?) open-menu (mf/use-fn @@ -404,34 +407,37 @@ (dom/stop-propagation event) (reset! show-menu* false))) - input-ref (mf/use-ref) on-display-file-explorer - (mf/use-fn - #(.click (mf/ref-val input-ref))) + (mf/use-fn #(dom/click (mf/ref-val input-ref))) on-import - (fn [event] - (let [file (-> event .-target .-files (aget 0))] - (->> (wapi/read-file-as-text file) - (sd/process-json-stream) - (rx/subs! (fn [lib] - (st/emit! (ptk/event ::ev/event {::ev/name "import-tokens"})) - (st/emit! (dt/import-tokens-lib lib))) - (fn [err] - (js/console.error err) - (st/emit! (ntf/show {:content (wte/humanize-errors [(ex-data err)]) - :type :toast - :level :error}))))) - (set! (.-value (mf/ref-val input-ref)) ""))) + (mf/use-fn + (fn [event] + (let [file (-> (dom/get-target event) + (dom/get-files) + (first))] + (->> (wapi/read-file-as-text file) + (sd/process-json-stream) + (rx/subs! (fn [lib] + (st/emit! (ptk/data-event ::ev/event {::ev/name "import-tokens"}) + (dt/import-tokens-lib lib))) + (fn [err] + (js/console.error err) + (st/emit! (ntf/show {:content (wte/humanize-errors [(ex-data err)]) + :type :toast + :level :error}))))) + (-> (mf/ref-val input-ref) + (dom/set-value! ""))))) - on-export (fn [] - (st/emit! (ptk/event ::ev/event {::ev/name "export-tokens"})) - (let [tokens-json (some-> (deref refs/tokens-lib) - (ctob/encode-dtcg) - (clj->js) - (js/JSON.stringify nil 2))] - (->> (wapi/create-blob (or tokens-json "{}") "application/json") - (dom/trigger-download "tokens.json"))))] + on-export + (mf/use-fn + (fn [] + (st/emit! (ptk/data-event ::ev/event {::ev/name "export-tokens"})) + (let [tokens-json (some-> (deref refs/tokens-lib) + (ctob/encode-dtcg) + (json/encode :key-fn identity))] + (->> (wapi/create-blob (or tokens-json "{}") "application/json") + (dom/trigger-download "tokens.json")))))] [:div {:class (stl/css :import-export-button-wrapper)} (when can-edit? @@ -478,4 +484,4 @@ :on-lost-pointer-capture on-lost-pointer-capture-pages :on-pointer-move on-pointer-move-pages}] [:> tokens-tab* {:tokens-lib tokens-lib}]] - [:& import-export-button]])) + [:> import-export-button*]])) From 1fb48a1e8af171f4a67042078195e14130b46277 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 15:17:52 +0100 Subject: [PATCH 08/15] :zap: Adapt call style convention for token sets context menu component --- .../app/main/ui/workspace/tokens/sets.cljs | 4 +- .../workspace/tokens/sets_context_menu.cljs | 38 ++++++++++--------- .../app/main/ui/workspace/tokens/sidebar.cljs | 4 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index a3d04a4c1..09398f824 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -119,7 +119,7 @@ (st/emit! (wdt/show-token-set-context-menu {:position (dom/get-client-position event) - :group? true + :is-group true :path tree-path}))))) on-collapse-click @@ -227,7 +227,7 @@ (st/emit! (wdt/show-token-set-context-menu {:position (dom/get-client-position event) - :group? false + :is-group false :path tree-path}))))) on-double-click 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 b95da308f..55da39183 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.sets-context-menu (:require-macros [app.main.style :as stl]) (:require + [app.common.data.macros :as dm] [app.main.data.tokens :as wdt] [app.main.refs :as refs] [app.main.store :as st] @@ -59,24 +60,27 @@ [:& menu-entry {:title (tr "labels.rename") :on-click edit-name}] [:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]])) -(mf/defc sets-context-menu +(mf/defc sets-context-menu* [] - (let [mdata (mf/deref sets-menu-ref) - top (+ (get-in mdata [:position :y]) 5) - left (+ (get-in mdata [:position :x]) 5) - width (mf/use-state 0) - dropdown-ref (mf/use-ref)] - (mf/use-effect - (mf/deps mdata) - (fn [] - (when-let [node (mf/ref-val dropdown-ref)] - (reset! width (.-offsetWidth node))))) - [:& dropdown {:show (boolean mdata) - :on-close #(st/emit! wdt/hide-token-set-context-menu)} + (let [{:keys [position is-group path]} + (mf/deref sets-menu-ref) + + position-top + (+ (dm/get-prop position :y) 5) + + position-left + (+ (dm/get-prop position :x) 5) + + on-close + (mf/use-fn + #(st/emit! wdt/hide-token-set-context-menu))] + + [:& dropdown {:show (some? position) + :on-close on-close} [:div {:class (stl/css :token-set-context-menu) :data-testid "tokens-context-menu-for-set" - :ref dropdown-ref - :style {:top top :left left} + :style {:top position-top + :left position-left} :on-context-menu prevent-default} - [:& menu {:group? (:group? mdata) - :path (:path mdata)}]]])) + [:& menu {:group? is-group + :path path}]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 6a92a239c..683011757 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -32,7 +32,7 @@ [app.main.ui.workspace.tokens.errors :as wte] [app.main.ui.workspace.tokens.sets :refer [sets-list*]] [app.main.ui.workspace.tokens.sets-context :as sets-context] - [app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu]] + [app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu*]] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.theme-select :refer [theme-select]] [app.main.ui.workspace.tokens.token-pill :refer [token-pill*]] @@ -270,7 +270,7 @@ (get permissions :can-edit)] [:& sets-context/provider {} - [:& sets-context-menu] + [:> sets-context-menu*] [:article {:data-testid "token-themes-sets-sidebar" :class (stl/css :sets-section-wrapper) :style {"--resize-height" (str resize-height "px")}} From b22843812701a7b84ae082c4cd79376602048f31 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Feb 2025 15:26:00 +0100 Subject: [PATCH 09/15] :zap: Refactor the rest fot token sets context menu components --- .../workspace/tokens/sets_context_menu.cljs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) 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 55da39183..01fcc161f 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 @@ -18,7 +18,7 @@ [okulary.core :as l] [rumext.v2 :as mf])) -(def sets-menu-ref +(def ^:private ref:tokens-sets-context-menu (l/derived :token-set-context-menu refs/workspace-local)) (defn- prevent-default @@ -26,44 +26,46 @@ (dom/prevent-default event) (dom/stop-propagation event)) -(mf/defc menu-entry - {::mf/props :obj} +(mf/defc menu-entry* [{:keys [title value on-click]}] [:li {:class (stl/css :context-menu-item) :data-value value :on-click on-click} [:span {:class (stl/css :title)} title]]) -(mf/defc menu - [{:keys [group? path]}] +(mf/defc menu* + [{:keys [is-group path]}] (let [{:keys [on-create on-edit]} (sets-context/use-context) + create-set-at-path (mf/use-fn - (mf/deps path) + (mf/deps path on-create) #(on-create path)) edit-name (mf/use-fn - (mf/deps group?) + (mf/deps is-group on-edit) (fn [] - (let [path (if group? + (let [path (if is-group (sets-context/set-group-path->id path) (sets-context/set-path->id path))] (on-edit path)))) delete-set (mf/use-fn - #(st/emit! (wdt/delete-token-set-path group? path)))] + (mf/deps is-group path) + #(st/emit! (wdt/delete-token-set-path is-group path)))] + [:ul {:class (stl/css :context-list)} - (when group? - [:& menu-entry {:title (tr "workspace.token.add-set-to-group") :on-click create-set-at-path}]) - [:& menu-entry {:title (tr "labels.rename") :on-click edit-name}] - [:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]])) + (when is-group + [:> menu-entry* {:title (tr "workspace.token.add-set-to-group") :on-click create-set-at-path}]) + [:> menu-entry* {:title (tr "labels.rename") :on-click edit-name}] + [:> menu-entry* {:title (tr "labels.delete") :on-click delete-set}]])) (mf/defc sets-context-menu* [] (let [{:keys [position is-group path]} - (mf/deref sets-menu-ref) + (mf/deref ref:tokens-sets-context-menu) position-top (+ (dm/get-prop position :y) 5) @@ -82,5 +84,4 @@ :style {:top position-top :left position-left} :on-context-menu prevent-default} - [:& menu {:group? is-group - :path path}]]])) + [:> menu* {:is-group is-group :path path}]]])) From e8c85d13ffc10cd87d762b484c54592a78dc4569 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Feb 2025 15:58:06 +0100 Subject: [PATCH 10/15] :recycle: Remove sets context abstraction --- common/src/app/common/types/tokens_lib.cljc | 62 ++ frontend/src/app/main/data/tokens.cljs | 160 +++-- frontend/src/app/main/data/workspace.cljs | 1 + frontend/src/app/main/refs.cljs | 16 +- .../ui/workspace/tokens/context_menu.cljs | 25 +- .../ui/workspace/tokens/modals/themes.cljs | 19 +- .../app/main/ui/workspace/tokens/sets.cljs | 569 ++++++++++-------- .../app/main/ui/workspace/tokens/sets.scss | 16 + .../ui/workspace/tokens/sets_context.cljs | 54 -- .../workspace/tokens/sets_context_menu.cljs | 34 +- .../app/main/ui/workspace/tokens/sidebar.cljs | 95 +-- .../app/main/ui/workspace/tokens/sidebar.scss | 6 - 12 files changed, 579 insertions(+), 478 deletions(-) delete mode 100644 frontend/src/app/main/ui/workspace/tokens/sets_context.cljs diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 5c7784c30..8bf9a9ff2 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -691,6 +691,7 @@ used for managing active sets without a user created theme.") (filter some?) first)))) +;; DEPRECATED (defn walk-sets-tree-seq "Walk sets tree as a flat list. @@ -739,6 +740,67 @@ used for managing active sets without a user created theme.") (cons item (mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v'))))))))))] (walk (or nodes (d/ordered-map)) nil))) + +(defn sets-tree-seq + "Get tokens sets tree as a flat list + + Options: + `:skip-children-pred`: predicate to skip iterating over a set groups children by checking the path of the set group + `:new-editing-set-path`: append a an item with `:new?` at the given path" + + [tree & {:keys [skip-children-pred new-at-path] + :or {skip-children-pred (constantly false)}}] + (let [walk (fn walk [[k v :as node] parent depth] + (lazy-seq + (cond + ;; New set + (= :is-new k) + (let [tset (make-token-set :name (if (empty? parent) + "" + (join-set-path parent)))] + [{:is-new true + :is-group false + :id "" + :parent-path parent + :token-set tset + :depth depth}]) + + ;; Set + (and v (instance? TokenSet v)) + (let [name (:name v)] + [{:is-group false + :path (split-token-set-name name) + :id name + :parent-path parent + :depth depth + :token-set v}]) + + ;; Set group + (and v (d/ordered-map? v)) + (let [unprefixed-path (last (split-set-str-path-prefix k)) + path (conj parent unprefixed-path) + item {:is-group true + :path path + :id (join-set-path path) + :parent-path parent + :depth depth}] + + (if (skip-children-pred path) + [item] + (let [v (cond-> v + (= path new-at-path) + (assoc :is-new true))] + (cons item + (mapcat #(walk % path (inc depth)) v)))))))) + + tree (cond-> tree + (= [] new-at-path) + (assoc :is-new true))] + (->> tree + (mapcat #(walk % [] 0)) + (map-indexed (fn [index item] + (assoc item :index index)))))) + (defn flatten-nested-tokens-json "Recursively flatten the dtcg token structure, joining keys with '.'." [tokens token-path] diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 053ab0764..1ddc6e996 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -19,13 +19,23 @@ [app.main.data.notifications :as ntf] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.tokens.selected-set :as dwts] - [app.main.data.workspace.undo :as dwu] [app.main.ui.workspace.tokens.update :as wtu] [app.util.i18n :refer [tr]] [beicon.v2.core :as rx] [cuerdas.core :as str] [potok.v2.core :as ptk])) +(declare set-selected-token-set-name) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TOKENS Getters +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn get-tokens-lib + [state] + (-> (dsh/lookup-file-data state) + (get :tokens-lib))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -43,15 +53,6 @@ (watch [_ _ _] (rx/of (dwsh/update-shapes [id] #(merge % attrs)))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; TOKENS Getters -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn get-tokens-lib - [state] - (-> (dsh/lookup-file-data state) - (get :tokens-lib))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TOKENS Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -108,19 +109,21 @@ (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) -(declare set-selected-token-set-name) +(defn create-token-set + [set-name token-set] + (ptk/reify ::create-token-set + ptk/UpdateEvent + (update [_ state] + ;; Clear possible local state + (update state :workspace-tokens dissoc :token-set-new-path)) -(defn create-token-set [set-name token-set] - (let [new-token-set (-> token-set - (update :name #(if (empty? %) set-name (ctob/join-set-path [% set-name]))))] - (ptk/reify ::create-token-set - ptk/WatchEvent - (watch [it _ _] - (let [changes (-> (pcb/empty-changes it) - (pcb/add-token-set new-token-set))] - (rx/of - (set-selected-token-set-name (:name new-token-set)) - (dch/commit-changes changes))))))) + ptk/WatchEvent + (watch [it _ _] + (let [token-set (update token-set :name #(if (empty? %) set-name (ctob/join-set-path [% set-name]))) + changes (-> (pcb/empty-changes it) + (pcb/add-token-set token-set))] + (rx/of (set-selected-token-set-name (:name token-set)) + (dch/commit-changes changes)))))) (defn rename-token-set-group [set-group-path set-group-fname] (ptk/reify ::rename-token-set-group @@ -143,14 +146,18 @@ (set-selected-token-set-name (:name token-set)) (dch/commit-changes changes)))))) -(defn toggle-token-set [{:keys [token-set-name]}] +(defn toggle-token-set + [name] + (assert (string? name) "expected a string for `name`") (ptk/reify ::toggle-token-set ptk/WatchEvent (watch [_ state _] - (let [changes (clt/generate-toggle-token-set (pcb/empty-changes) (get-tokens-lib state) token-set-name)] - (rx/of - (dch/commit-changes changes) - (wtu/update-workspace-tokens)))))) + (let [tlib (get-tokens-lib state) + changes (-> (pcb/empty-changes) + (clt/generate-toggle-token-set tlib name))] + + (rx/of (dch/commit-changes changes) + (wtu/update-workspace-tokens)))))) (defn toggle-token-set-group [group-path] (ptk/reify ::toggle-token-set-group @@ -224,6 +231,7 @@ (rx/of (drop-error (ex-data e)))))))) +;; FIXME: the the name is very confusing (defn update-create-token [{:keys [token prev-token-name]}] (ptk/reify ::update-create-token @@ -241,13 +249,15 @@ add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path}) base-changes (pcb/add-token-set (pcb/empty-changes) token-set)] (cond - (not tokens-lib) (-> base-changes - (pcb/add-token-theme hidden-theme) - (pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) + (not tokens-lib) + (-> base-changes + (pcb/add-token-theme hidden-theme) + (pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) - add-to-hidden-theme? (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] - (-> base-changes - (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) + add-to-hidden-theme? + (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] + (-> base-changes + (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) :else base-changes)) (-> (pcb/empty-changes it) @@ -295,49 +305,81 @@ (update-create-token {:token (assoc token :name copy-name)}))))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TOKEN UI OPS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn set-token-type-section-open [token-type open?] (ptk/reify ::set-token-type-section-open ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-local :token-type-open-status token-type] open?)))) + (update-in state [:workspace-tokens :open-status-by-type] assoc token-type open?)))) -;; === Token Context Menu +(defn assign-token-context-menu + [{:keys [position] :as params}] + + (when params + (assert (gpt/point? position) "expected a point instance for `position` param")) -(defn show-token-context-menu - [{:keys [position _token-name] :as params}] - (dm/assert! (gpt/point? position)) (ptk/reify ::show-token-context-menu ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-local :token-context-menu] params)))) + (if params + (update state :workspace-tokens assoc :token-context-menu params) + (update state :workspace-tokens dissoc :token-context-menu))))) -(def hide-token-context-menu - (ptk/reify ::hide-token-context-menu +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TOKEN-SET UI OPS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn assign-token-set-context-menu + [{:keys [position] :as params}] + (when params + (assert (gpt/point? position) "expected valid point for `position` param")) + + (ptk/reify ::assign-token-set-context-menu ptk/UpdateEvent (update [_ state] - (assoc-in state [:workspace-local :token-context-menu] nil)))) - -;; === Token Set Context Menu - -(defn show-token-set-context-menu - [{:keys [position _token-set-name] :as params}] - (dm/assert! (gpt/point? position)) - (ptk/reify ::show-token-set-context-menu - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :token-set-context-menu] params)))) - -(def hide-token-set-context-menu - (ptk/reify ::hide-token-set-context-menu - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :token-set-context-menu] nil)))) + (if params + (update state :workspace-tokens assoc :token-set-context-menu params) + (update state :workspace-tokens dissoc :token-set-context-menu))))) (defn set-selected-token-set-name [name] (ptk/reify ::set-selected-token-set-name ptk/UpdateEvent (update [_ state] - (update state :workspace-local assoc :selected-token-set-name name)))) + (update state :workspace-tokens assoc :selected-token-set-name name)))) +(defn start-token-set-edition + [edition-id] + (assert (string? edition-id) "expected a string for `edition-id`") + + (ptk/reify ::start-token-set-edition + ptk/UpdateEvent + (update [_ state] + (update state :workspace-tokens assoc :token-set-edition-id edition-id)))) + +(defn start-token-set-creation + [path] + (assert (vector? path) "expected a vector for `path`") + + (ptk/reify ::start-token-set-creation + ptk/UpdateEvent + (update [_ state] + (update state :workspace-tokens assoc :token-set-new-path path)))) + +(defn clear-token-set-edition + [] + (ptk/reify ::clear-token-set-edition + ptk/UpdateEvent + (update [_ state] + (update state :workspace-tokens dissoc :token-set-edition-id)))) + +(defn clear-token-set-creation + [] + (ptk/reify ::clear-token-set-creation + ptk/UpdateEvent + (update [_ state] + (update state :workspace-tokens dissoc :token-set-new-path)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index c181104c4..c406cf480 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -399,6 +399,7 @@ :workspace-media-objects :workspace-persistence :workspace-presence + :workspace-tokens :workspace-ready :workspace-undo) (update :workspace-global dissoc :read-only?) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index c884885da..b7def70de 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -128,6 +128,10 @@ (def workspace-selrect-transform (l/derived :workspace-selrect-transform st/state)) +(def workspace-tokens + "All tokens related ephimeral state" + (l/derived :workspace-tokens st/state)) + ;; TODO: rename to workspace-selected (?) ;; Don't use directly from components, this is a proxy to improve performance of selected-shapes (def ^:private selected-shapes-data @@ -453,12 +457,8 @@ (def workspace-token-themes-no-hidden (l/derived #(remove ctob/hidden-temporary-theme? %) workspace-token-themes)) -;; FIXME: deprecated -(def workspace-selected-token-set-name - (l/derived dwts/get-selected-token-set-name st/state)) - (def selected-token-set-name - (l/derived (l/key :selected-token-set-name) workspace-local)) + (l/derived (l/key :selected-token-set-name) workspace-tokens)) (def workspace-ordered-token-sets (l/derived #(or (some-> % ctob/get-sets) []) tokens-lib)) @@ -480,10 +480,7 @@ (def workspace-active-theme-paths-no-hidden (l/derived #(disj % ctob/hidden-token-theme-path) workspace-active-theme-paths)) -(def workspace-active-set-names - (l/derived (d/nilf ctob/get-active-themes-set-names) tokens-lib)) - -;; FIXME: deprecated, it should not be implemented with ref +;; FIXME: deprecated, it should not be implemented with ref (still used in form) (def workspace-active-theme-sets-tokens (l/derived #(or (some-> % ctob/get-active-themes-set-tokens) {}) tokens-lib)) @@ -496,7 +493,6 @@ (def workspace-selected-token-set-tokens (l/derived #(or (dwts/get-selected-token-set-tokens %) {}) st/state)) - (def plugins-permissions-peek (l/derived (fn [state] (dm/get-in state [:plugins-permissions-peek :data])) diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index 77b2e16d2..9a90fd810 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -218,15 +218,15 @@ :no-selectable true :action (fn [event] (let [{:keys [key fields]} modal] - (st/emit! dt/hide-token-context-menu) (dom/stop-propagation event) - (modal/show! key {:x (.-clientX ^js event) - :y (.-clientY ^js event) - :position :right - :fields fields - :action "edit" - :selected-token-set-name selected-token-set-name - :token token})))} + (st/emit! (dt/assign-token-set-context-menu nil) + (modal/show key {:x (.-clientX ^js event) + :y (.-clientY ^js event) + :position :right + :fields fields + :action "edit" + :selected-token-set-name selected-token-set-name + :token token}))))} {:title (tr "workspace.token.duplicate") :no-selectable true :action #(st/emit! (dt/duplicate-token (:name token)))} @@ -252,8 +252,8 @@ ;; Components ------------------------------------------------------------------ -(def tokens-menu-ref - (l/derived :token-context-menu refs/workspace-local)) +(def ^:private tokens-menu-ref + (l/derived :token-context-menu refs/workspace-tokens)) (defn- prevent-default [event] @@ -355,7 +355,7 @@ selected-shapes (into [] (keep (d/getf objects)) selected) token-name (:token-name mdata) token (mf/deref (refs/workspace-selected-token-set-token token-name)) - selected-token-set-name (mf/deref refs/workspace-selected-token-set-name)] + selected-token-set-name (mf/deref refs/selected-token-set-name)] [:ul {:class (stl/css :context-list)} [:& menu-tree {:submenu-offset width :submenu-direction direction @@ -394,8 +394,9 @@ (reset! dropdown-direction* (if is-outside? "up" "down")) (mf/set-ref-val! dropdown-direction-change* (inc (mf/ref-val dropdown-direction-change*))))))) + ;; FIXME: perf optimization [:& dropdown {:show is-open? - :on-close #(st/emit! dt/hide-token-context-menu)} + :on-close #(st/emit! (dt/assign-token-context-menu nil))} [:div {:class (stl/css :token-context-menu) :data-testid "tokens-context-menu-for-token" :ref dropdown-ref diff --git a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs index 7c44b4ef7..c68c0287e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -25,7 +25,6 @@ [app.main.ui.icons :as i] [app.main.ui.workspace.tokens.components.controls.input-tokens :refer [input-tokens*]] [app.main.ui.workspace.tokens.sets :as wts] - [app.main.ui.workspace.tokens.sets-context :as sets-context] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [app.util.keyboard :as k] @@ -339,8 +338,8 @@ token-set-active? (mf/use-fn (mf/deps theme-state) - (fn [set-name] - (get-in theme-state [:sets set-name]))) + (fn [name] + (contains? (:sets theme-state) name))) on-toggle-token-set (mf/use-fn @@ -381,16 +380,15 @@ (tr "workspace.token.set-selection-theme")] [:div {:class (stl/css :sets-list-wrapper)} - [:& wts/controlled-sets-list + [:> wts/controlled-sets-list* {:token-sets token-sets - :token-set-selected? (constantly false) - :token-set-active? token-set-active? - :token-set-group-active? token-set-group-active? + :is-token-set-active token-set-active? + :is-token-set-group-active token-set-group-active? :on-select on-click-token-set + :can-edit false :on-toggle-token-set on-toggle-token-set :on-toggle-token-set-group on-toggle-token-set-group - :origin "theme-modal" - :context sets-context/static-context}]] + :origin "theme-modal"}]] [:div {:class (stl/css :edit-theme-footer)} [:> button* {:variant "secondary" @@ -432,5 +430,4 @@ :aria-label (tr "labels.close") :variant "action" :icon "close"}] - [:& sets-context/provider {} - [:& themes-modal-body]]]])) + [:& themes-modal-body]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 09398f824..77670edd1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -7,18 +7,17 @@ (ns app.main.ui.workspace.tokens.sets (:require-macros [app.main.style :as stl]) (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.types.tokens-lib :as ctob] [app.main.data.event :as ev] - [app.main.data.tokens :as wdt] + [app.main.data.tokens :as dt] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.context :as ctx] [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]] [app.util.keyboard :as kbd] @@ -26,42 +25,59 @@ [potok.v2.core :as ptk] [rumext.v2 :as mf])) -(defn on-toggle-token-set-click [token-set-name] - (st/emit! (wdt/toggle-token-set {:token-set-name token-set-name}))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn on-toggle-token-set-group-click [group-path] - (st/emit! (wdt/toggle-token-set-group group-path))) +(defn- on-start-creation + [] + (st/emit! (dt/start-token-set-creation []))) -(defn on-select-token-set-click [set-name] - (st/emit! (wdt/set-selected-token-set-name set-name))) +(defn- on-toggle-token-set-click [name] + (st/emit! (dt/toggle-token-set name))) + +(defn- on-toggle-token-set-group-click [group-path] + (st/emit! (dt/toggle-token-set-group group-path))) + +(defn- on-select-token-set-click [set-name] + (st/emit! (dt/set-selected-token-set-name set-name))) (defn on-update-token-set [set-name token-set] - (st/emit! (wdt/update-token-set (:name token-set) (ctob/update-name token-set set-name)))) + (st/emit! (dt/update-token-set (:name token-set) (ctob/update-name token-set set-name)))) -(defn on-update-token-set-group [set-group-path set-group-fname] - (st/emit! (wdt/rename-token-set-group set-group-path set-group-fname))) +(defn- on-update-token-set-group [path name] + (st/emit! (dt/rename-token-set-group path name))) -(defn on-create-token-set [set-name token-set] - (st/emit! (ptk/event ::ev/event {::ev/name "create-tokens-set"})) - (st/emit! (wdt/create-token-set set-name token-set))) +(defn- on-create-token-set [name token-set] + (st/emit! (ptk/data-event ::ev/event {::ev/name "create-token-set" :name name}) + (dt/create-token-set name token-set))) -(mf/defc editing-label +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; COMPONENTS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(mf/defc editing-label* + {::mf/private true} [{:keys [default-value on-cancel on-submit]}] (let [ref (mf/use-ref) - on-submit-valid (mf/use-fn - (fn [event] - (let [value (str/trim (dom/get-target-val event))] - (if (or (str/empty? value) - (= value default-value)) - (on-cancel) - (do - (on-submit value) - (on-cancel)))))) - on-key-down (mf/use-fn - (fn [event] - (cond - (kbd/enter? event) (on-submit-valid event) - (kbd/esc? event) (on-cancel))))] + + on-submit-valid + (mf/use-fn + (mf/deps on-cancel on-submit default-value) + (fn [event] + (let [value (str/trim (dom/get-target-val event))] + (if (or (str/empty? value) + (= value default-value)) + (on-cancel) + (on-submit value))))) + + on-key-down + (mf/use-fn + (mf/deps on-submit-valid on-cancel) + (fn [event] + (cond + (kbd/enter? event) (on-submit-valid event) + (kbd/esc? event) (on-cancel))))] [:input {:class (stl/css :editing-node) :type "text" @@ -71,17 +87,12 @@ :auto-focus true :default-value default-value}])) -(mf/defc checkbox +(mf/defc checkbox* [{:keys [checked aria-label on-click disabled]}] - (let [all? (true? checked) - mixed? (= checked "mixed") - checked? (or all? mixed?) - on-click - (mf/use-fn - (mf/deps disabled) - (fn [e] - (when-not disabled - (on-click e))))] + (let [all? (true? checked) + mixed? (= checked "mixed") + checked? (or all? mixed?)] + [:div {:role "checkbox" :aria-checked (dm/str checked) :disabled disabled @@ -90,37 +101,59 @@ :class (stl/css-case :checkbox-style true :checkbox-checked-style checked? :checkbox-disabled-checked (and checked? disabled) - :checkbox-disabled disabled) - :on-click on-click} + :checkbox-disabled disabled) + :on-click (when-not disabled on-click)} - (when checked? + (when ^boolean checked? [:> icon* {:aria-label aria-label :class (stl/css :check-icon) :size "s" :icon-id (if mixed? ic/remove ic/tick)}])])) -(mf/defc sets-tree-set-group - [{:keys [label tree-depth tree-path active? selected? draggable? on-toggle-collapse on-toggle editing-id editing? on-edit on-edit-reset on-edit-submit collapsed-paths tree-index]}] - (let [active?' (active? tree-path) - editing?' (editing? editing-id) - collapsed? (some? (get @collapsed-paths tree-path)) - can-edit? (:can-edit (deref refs/permissions)) - ;; Used by playwright to get the correct item by label - label-id (str editing-id "-label") +(mf/defc inline-add-button* + [] + (let [can-edit? (mf/use-ctx ctx/can-edit?)] + (if can-edit? + [:div {:class (stl/css :empty-sets-wrapper)} + [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} + (tr "workspace.token.no-sets-yet")] + [:button {:on-click on-start-creation + :class (stl/css :create-set-button)} + (tr "workspace.token.create-one")]] + [:div {:class (stl/css :empty-sets-wrapper)} + [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} + (tr "workspace.token.no-sets-yet")]]))) + +(mf/defc add-button* + [] + [:> icon-button* {:variant "ghost" + :icon "add" + :on-click on-start-creation + :aria-label (tr "workspace.token.add set")}]) + +(mf/defc sets-tree-set-group* + {::mf/private true} + [{:keys [id label tree-depth tree-path is-active is-selected is-draggable is-collapsed tree-index on-drop + on-toggle-collapse on-toggle is-editing on-start-edition on-reset-edition on-edit-submit]}] + + (let [can-edit? + (mf/use-ctx ctx/can-edit?) + + label-id + (str id "-label") on-context-menu (mf/use-fn - (mf/deps editing?' editing-id can-edit?) + (mf/deps is-editing can-edit?) (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) - :is-group true - :path tree-path}))))) + (when-not is-editing + (st/emit! (dt/assign-token-set-context-menu + {:position (dom/get-client-position event) + :is-group true + :path tree-path}))))) on-collapse-click (mf/use-fn @@ -129,9 +162,7 @@ (on-toggle-collapse tree-path))) on-double-click - (mf/use-fn - (mf/deps editing-id can-edit?) - #(on-edit editing-id)) + (mf/use-fn (mf/deps id) #(on-start-edition id)) on-checkbox-click (mf/use-fn @@ -145,24 +176,18 @@ on-drop (mf/use-fn - (mf/deps tree-index collapsed-paths) + (mf/deps tree-index on-drop) (fn [position data] - (let [props {:from-index (:index data) - :to-index tree-index - :position position - :collapsed-paths @collapsed-paths}] - (if (:group? data) - (st/emit! (wdt/drop-token-set-group props)) - (st/emit! (wdt/drop-token-set props)))))) + (on-drop tree-index position data))) [dprops dref] (h/use-sortable :data-type "penpot/token-set" :on-drop on-drop :data {:index tree-index - :group? true} + :is-group true} :detect-center? true - :draggable? draggable?)] + :draggable? is-draggable)] [:div {:ref dref :role "button" @@ -171,7 +196,7 @@ :style {"--tree-depth" tree-depth} :class (stl/css-case :set-item-container true :set-item-group true - :selected-set selected? + :selected-set is-selected :dnd-over (= (:over dprops) :center) :dnd-over-top (= (:over dprops) :top) :dnd-over-bot (= (:over dprops) :bot)) @@ -180,68 +205,64 @@ {:class (stl/css :set-item-group-collapse-button) :on-click on-collapse-click :aria-label (tr "labels.collapse") - :icon (if collapsed? "arrow-right" "arrow-down") + :icon (if is-collapsed "arrow-right" "arrow-down") :variant "action"}] - (if editing?' - [:& editing-label + (if is-editing + [:> editing-label* {:default-value label - :on-cancel on-edit-reset - :on-create on-edit-reset + :on-cancel on-reset-edition :on-submit on-edit-submit'}] [:* [:div {:class (stl/css :set-name) :on-double-click on-double-click :id label-id} label] - [:& checkbox + [:> checkbox* {:on-click on-checkbox-click :disabled (not can-edit?) - :checked (case active?' + :checked (case is-active :all true :partial "mixed" :none false) :arial-label (tr "workspace.token.select-set")}]])])) -(mf/defc sets-tree-set - [{:keys [set label tree-depth tree-path tree-index selected? on-select active? draggable? on-toggle editing-id editing? on-edit on-edit-reset on-edit-submit collapsed-paths]}] - (let [set-name (.-name set) - editing?' (editing? editing-id) - active?' (some? (active? set-name)) - can-edit? (:can-edit (deref refs/permissions)) +(mf/defc sets-tree-set* + [{:keys [id set label tree-depth tree-path tree-index is-selected is-active is-draggable is-editing + on-select on-drop on-toggle on-start-edition on-reset-edition on-edit-submit]}] + (let [set-name (get set :name) + can-edit? (mf/use-ctx ctx/can-edit?) on-click (mf/use-fn - (mf/deps editing?' tree-path) + (mf/deps is-editing tree-path) (fn [event] (dom/stop-propagation event) - (when-not editing?' - (on-select set-name)))) + (when-not is-editing + (when (fn? on-select) + (on-select set-name))))) on-context-menu (mf/use-fn - (mf/deps editing?' tree-path can-edit?) + (mf/deps is-editing tree-path can-edit?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (when (and can-edit? (not editing?')) - (st/emit! - (wdt/show-token-set-context-menu - {:position (dom/get-client-position event) - :is-group false - :path tree-path}))))) + (when (and can-edit? (not is-editing)) + (st/emit! (dt/assign-token-set-context-menu + {:position (dom/get-client-position event) + :is-group false + :path tree-path}))))) on-double-click - (mf/use-fn - (mf/deps editing-id) - (fn [] - (on-edit editing-id))) + (mf/use-fn (mf/deps id) #(on-start-edition id)) on-checkbox-click (mf/use-fn - (mf/deps set-name) + (mf/deps set-name on-toggle) (fn [event] (dom/stop-propagation event) - (on-toggle set-name))) + (when (fn? on-toggle) + (on-toggle set-name)))) on-edit-submit' (mf/use-fn @@ -252,20 +273,14 @@ (mf/use-fn (mf/deps tree-path) (fn [_] - (when-not selected? + (when-not is-selected (on-select tree-path)))) on-drop (mf/use-fn - (mf/deps tree-index collapsed-paths) + (mf/deps tree-index on-drop) (fn [position data] - (let [props {:from-index (:index data) - :to-index tree-index - :position position - :collapsed-paths @collapsed-paths}] - (if (:group? data) - (st/emit! (wdt/drop-token-set-group props)) - (st/emit! (wdt/drop-token-set props)))))) + (on-drop tree-index position data))) [dprops dref] (h/use-sortable @@ -273,210 +288,280 @@ :on-drag on-drag :on-drop on-drop :data {:index tree-index - :group? false} - :draggable? draggable?)] + :is-group false} + :draggable? is-draggable) + + drop-over + (get dprops :over)] [:div {:ref dref :role "button" :data-testid "tokens-set-item" :style {"--tree-depth" tree-depth} :class (stl/css-case :set-item-container true - :selected-set selected? - :dnd-over (= (:over dprops) :center) - :dnd-over-top (= (:over dprops) :top) - :dnd-over-bot (= (:over dprops) :bot)) + :selected-set is-selected + :dnd-over (= drop-over :center) + :dnd-over-top (= drop-over :top) + :dnd-over-bot (= drop-over :bot)) :on-click on-click :on-context-menu on-context-menu - :aria-checked active?'} + :aria-checked is-active} + [:> icon* {:icon-id "document" :class (stl/css-case :icon true :root-icon (not tree-depth))}] - (if editing?' - [:& editing-label + (if is-editing + [:> editing-label* {:default-value label - :on-cancel on-edit-reset - :on-create on-edit-reset + :on-cancel on-reset-edition :on-submit on-edit-submit'}] [:* [:div {:class (stl/css :set-name) :on-double-click on-double-click} label] - [:& checkbox + [:> checkbox* {:on-click on-checkbox-click :disabled (not can-edit?) :arial-label (tr "workspace.token.select-set") - :checked active?'}]])])) + :checked is-active}]])])) -(mf/defc sets-tree - [{:keys [draggable? - active? - selected? - group-active? - editing? - on-edit-reset +(mf/defc token-sets-tree* + [{:keys [is-draggable + selected + is-token-set-group-active + is-token-set-active + on-start-edition + on-reset-edition on-edit-submit-set on-edit-submit-group on-select on-toggle-set on-toggle-set-group - set-node] - :as props}] - (let [{:keys [on-edit new-path] :as ctx} (sets-context/use-context) - collapsed-paths (mf/use-state #{}) + token-sets + new-path + edition-id]}] + + (let [collapsed-paths* (mf/use-state #{}) + collapsed-paths (deref collapsed-paths*) + collapsed? (mf/use-fn - #(contains? @collapsed-paths %)) + (mf/deps collapsed-paths) + (partial contains? collapsed-paths)) + + on-drop + (mf/use-fn + (mf/deps collapsed-paths) + (fn [tree-index position data] + (let [props {:from-index (:index data) + :to-index tree-index + :position position + :collapsed-paths collapsed-paths}] + (if (:is-group data) + (st/emit! (dt/drop-token-set-group props)) + (st/emit! (dt/drop-token-set props)))))) on-toggle-collapse (mf/use-fn (fn [path] - (swap! collapsed-paths #(if (contains? % path) - (disj % path) - (conj % path)))))] - (for [[index {:keys [new? group? path parent-path depth] :as node}] - (d/enumerate (ctob/walk-sets-tree-seq set-node {:skip-children-pred #(contains? @collapsed-paths %) - :new-editing-set-path new-path}))] - (cond - group? - (let [editing-id (sets-context/set-group-path->id path)] - [:& sets-tree-set-group - {:key editing-id - :label (last path) - :active? group-active? - :selected? false - :draggable? draggable? - :on-select on-select - :tree-path path - :tree-depth depth - :tree-index index - :tree-parent-path parent-path - :editing-id editing-id - :editing? editing? - :on-edit on-edit - :on-edit-reset on-edit-reset - :on-edit-submit on-edit-submit-group - :collapsed? (collapsed? path) - :on-toggle-collapse on-toggle-collapse - :on-toggle on-toggle-set-group - :collapsed-paths collapsed-paths}]) + (swap! collapsed-paths* #(if (contains? % path) + (disj % path) + (conj % path)))))] - new? - (let [editing-id (sets-context/set-path->id path)] - [:& sets-tree-set - {:key editing-id - :set (ctob/make-token-set :name (if (empty? parent-path) - "" - (ctob/join-set-path parent-path))) - :label "" - :active? (constantly true) - :selected? (constantly true) - :on-select (constantly nil) - :tree-path path - :tree-depth depth - :tree-index index - :tree-parent-path parent-path - :on-toggle (constantly nil) - :editing-id editing-id - :editing? (constantly true) - :on-edit-reset on-edit-reset - :on-edit-submit on-create-token-set}]) + (for [{:keys [id token-set index is-new is-group path parent-path depth] :as node} + (ctob/sets-tree-seq token-sets + {:skip-children-pred collapsed? + :new-at-path new-path})] + (cond + ^boolean is-group + [:> sets-tree-set-group* + {:key index + :label (peek path) + :id id + :is-active (is-token-set-group-active path) + :is-selected false + :is-draggable is-draggable + :is-editing (= edition-id id) + :is-collapsed (collapsed? path) + :on-select on-select + + :tree-path path + :tree-depth depth + :tree-index index + :tree-parent-path parent-path + + :on-drop on-drop + :on-start-edition on-start-edition + :on-reset-edition on-reset-edition + :on-edit-submit on-edit-submit-group + :on-toggle-collapse on-toggle-collapse + :on-toggle on-toggle-set-group}] + + ^boolean is-new + [:> sets-tree-set* + {:key index + :set token-set + :label "" + :id id + :is-editing true + :is-active true + :is-selected true + + :tree-path path + :tree-depth depth + :tree-index index + :tree-parent-path parent-path + + :on-drop on-drop + :on-reset-edition on-reset-edition + :on-edit-submit on-create-token-set}] :else - (let [editing-id (sets-context/set-path->id path)] - [:& sets-tree-set - {:key editing-id - :set (:set node) - :label (last path) - :active? active? - :selected? (selected? (get-in node [:set :name])) - :draggable? draggable? - :on-select on-select - :tree-path path - :tree-depth depth - :tree-index index - :tree-parent-path parent-path - :on-toggle on-toggle-set - :editing-id editing-id - :editing? editing? - :on-edit on-edit - :on-edit-reset on-edit-reset - :on-edit-submit on-edit-submit-set - :collapsed-paths collapsed-paths}]))))) + [:> sets-tree-set* + {:key index + :set token-set + :id id + :label (peek path) + :is-editing (= edition-id id) + :is-active (is-token-set-active id) + :is-selected (= selected id) + :is-draggable is-draggable + :on-select on-select + :tree-path path + :tree-depth depth + :tree-index index + :tree-parent-path parent-path + :on-toggle on-toggle-set + :edition-id edition-id + :on-start-edition on-start-edition + :on-drop on-drop + :on-reset-edition on-reset-edition + :on-edit-submit on-edit-submit-set}])))) -(mf/defc controlled-sets-list +(mf/defc controlled-sets-list* + {::mf/props :obj} [{:keys [token-sets + selected on-update-token-set on-update-token-set-group - token-set-selected? - token-set-active? - token-set-group-active? + is-token-set-active + is-token-set-group-active on-create-token-set on-toggle-token-set on-toggle-token-set-group + on-start-edition + on-reset-edition origin on-select - context] - :as _props}] - (let [{:keys [editing? on-edit on-reset new-path] :as ctx} (or context (sets-context/use-context)) - theme-modal? (= origin "theme-modal") - can-edit? (:can-edit (deref refs/permissions)) - draggable? (and (not theme-modal?) can-edit?)] + new-path + edition-id]}] + + (assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop") + (assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop") + + (let [theme-modal? (= origin "theme-modal") + can-edit? (mf/use-ctx ctx/can-edit?) + draggable? (and (not theme-modal?) can-edit?) + empty-state? (and theme-modal? + (empty? token-sets) + (not new-path)) + + ;; NOTE: on-reset-edition and on-start-edition function can + ;; come as nil, in this case we need to provide a safe + ;; fallback for them + on-reset-edition + (mf/use-fn + (mf/deps on-reset-edition) + (fn [v] + (when (fn? on-reset-edition) + (on-reset-edition v)))) + + on-start-edition + (mf/use-fn + (mf/deps on-start-edition) + (fn [v] + (when (fn? on-start-edition) + (on-start-edition v))))] + [:fieldset {:class (stl/css :sets-list)} - (if (and theme-modal? - (empty? token-sets) - (not new-path)) + (if ^boolean empty-state? [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} (tr "workspace.token.no-sets-create")] - [:& sets-tree - {:draggable? draggable? - :set-node token-sets - :selected? token-set-selected? + + [:> token-sets-tree* + {:is-draggable draggable? + :new-path new-path + :edition-id edition-id + :token-sets token-sets + :selected selected :on-select on-select - :active? token-set-active? - :group-active? token-set-group-active? + :is-token-set-active is-token-set-active + :is-token-set-group-active is-token-set-group-active :on-toggle-set on-toggle-token-set :on-toggle-set-group on-toggle-token-set-group - :editing? editing? :on-create-token-set on-create-token-set - :on-edit on-edit - :on-edit-reset on-reset + :on-start-edition on-start-edition + :on-reset-edition on-reset-edition :on-edit-submit-set on-update-token-set :on-edit-submit-group on-update-token-set-group}])])) (mf/defc sets-list* - [{:keys [tokens-lib selected-token-set-name]}] + [{:keys [tokens-lib selected new-path edition-id]}] (let [token-sets (some-> tokens-lib (ctob/get-set-tree)) - token-set-selected? - (mf/use-fn - (mf/deps token-sets selected-token-set-name) - (fn [set-name] - (= set-name selected-token-set-name))) + can-edit? + (mf/use-ctx ctx/can-edit?) - active-token-set-names - (mf/deref refs/workspace-active-set-names) + active-token-sets-names + (mf/with-memo [tokens-lib] + (some-> tokens-lib (ctob/get-active-themes-set-names))) token-set-active? (mf/use-fn - (mf/deps active-token-set-names) - (fn [set-name] - (get active-token-set-names set-name))) + (mf/deps active-token-sets-names) + (fn [name] + (contains? active-token-sets-names name))) token-set-group-active? (mf/use-fn (fn [group-path] - @(refs/token-sets-at-path-all-active group-path)))] + ;; FIXME + @(refs/token-sets-at-path-all-active group-path))) - [:& controlled-sets-list + on-reset-edition + (mf/use-fn + (mf/deps can-edit?) + (fn [_] + (when can-edit? + (st/emit! (dt/clear-token-set-edition) + (dt/clear-token-set-creation))))) + + on-start-edition + (mf/use-fn + (mf/deps can-edit?) + (fn [id] + (when can-edit? + (st/emit! (dt/start-token-set-edition id)))))] + + [:> controlled-sets-list* {:token-sets token-sets - :token-set-selected? token-set-selected? - :token-set-active? token-set-active? - :token-set-group-active? token-set-group-active? + + :is-token-set-active token-set-active? + :is-token-set-group-active token-set-group-active? :on-select on-select-token-set-click + + :selected selected + :new-path new-path + :edition-id edition-id + :origin "set-panel" + :can-edit can-edit? + :on-start-edition on-start-edition + :on-reset-edition on-reset-edition + :on-toggle-token-set on-toggle-token-set-click :on-toggle-token-set-group on-toggle-token-set-group-click :on-update-token-set on-update-token-set diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.scss b/frontend/src/app/main/ui/workspace/tokens/sets.scss index 0b14ef57c..d87fbfd43 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.scss +++ b/frontend/src/app/main/ui/workspace/tokens/sets.scss @@ -4,6 +4,7 @@ // // Copyright (c) KALEIDOS INC +@use "../../ds/typography.scss" as *; @import "refactor/common-refactor.scss"; .sets-list { @@ -12,6 +13,21 @@ overflow-y: auto; } +.empty-sets-wrapper { + padding: $s-12; + padding-inline-start: $s-24; + color: var(--color-foreground-secondary); +} + +.create-set-button { + @include use-typography("body-small"); + background-color: transparent; + border: none; + appearance: none; + color: var(--color-accent-primary); + cursor: pointer; +} + .set-item-container { @include bodySmallTypography; display: flex; diff --git a/frontend/src/app/main/ui/workspace/tokens/sets_context.cljs b/frontend/src/app/main/ui/workspace/tokens/sets_context.cljs deleted file mode 100644 index 0bb6c6756..000000000 --- a/frontend/src/app/main/ui/workspace/tokens/sets_context.cljs +++ /dev/null @@ -1,54 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.ui.workspace.tokens.sets-context - (:require - [app.common.data.macros :as dm] - [rumext.v2 :as mf])) - -(defn set-group-path->id [set-group-path] - (dm/str "group-" set-group-path)) - -(defn set-path->id [set-path] - (dm/str "set-" set-path)) - -(def initial {}) - -(def context (mf/create-context initial)) - -(def static-context - {:editing? (constantly false) - :on-edit (constantly nil) - :on-create (constantly nil) - :on-reset (constantly nil)}) - -(mf/defc provider - {::mf/wrap-props false} - [props] - (let [children (unchecked-get props "children") - state (mf/use-state initial)] - [:& (mf/provider context) {:value state} - children])) - -(defn use-context [] - (let [ctx (mf/use-ctx context) - {:keys [editing-id new-path]} @ctx - editing? (mf/use-callback - (mf/deps editing-id) - #(= editing-id %)) - on-edit (mf/use-fn - (fn [editing-id] - (reset! ctx (assoc @ctx :editing-id editing-id)))) - on-create (mf/use-fn - (fn [path] - (swap! ctx assoc :editing-id (random-uuid) :new-path path))) - on-reset (mf/use-fn - #(reset! ctx initial))] - {:editing? editing? - :new-path new-path - :on-edit on-edit - :on-create on-create - :on-reset on-reset})) 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 01fcc161f..b82bc8970 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 @@ -8,18 +8,18 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] - [app.main.data.tokens :as wdt] + [app.common.types.tokens-lib :as ctob] + [app.main.data.tokens :as dt] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] - [app.main.ui.workspace.tokens.sets-context :as sets-context] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [okulary.core :as l] [rumext.v2 :as mf])) -(def ^:private ref:tokens-sets-context-menu - (l/derived :token-set-context-menu refs/workspace-local)) +(def ^:private ref:token-sets-context-menu + (l/derived :token-set-context-menu refs/workspace-tokens)) (defn- prevent-default [event] @@ -34,27 +34,22 @@ [:span {:class (stl/css :title)} title]]) (mf/defc menu* + {::mf/private true} [{:keys [is-group path]}] - (let [{:keys [on-create on-edit]} (sets-context/use-context) - - create-set-at-path - (mf/use-fn - (mf/deps path on-create) - #(on-create path)) + (let [create-set-at-path + (mf/use-fn (mf/deps path) #(st/emit! (dt/start-token-set-creation path))) edit-name (mf/use-fn - (mf/deps is-group on-edit) + (mf/deps path) (fn [] - (let [path (if is-group - (sets-context/set-group-path->id path) - (sets-context/set-path->id path))] - (on-edit path)))) + (let [name (ctob/join-set-path path)] + (st/emit! (dt/start-token-set-edition name))))) delete-set (mf/use-fn (mf/deps is-group path) - #(st/emit! (wdt/delete-token-set-path is-group path)))] + #(st/emit! (dt/delete-token-set-path is-group path)))] [:ul {:class (stl/css :context-list)} (when is-group @@ -62,10 +57,10 @@ [:> menu-entry* {:title (tr "labels.rename") :on-click edit-name}] [:> menu-entry* {:title (tr "labels.delete") :on-click delete-set}]])) -(mf/defc sets-context-menu* +(mf/defc token-set-context-menu* [] (let [{:keys [position is-group path]} - (mf/deref ref:tokens-sets-context-menu) + (mf/deref ref:token-sets-context-menu) position-top (+ (dm/get-prop position :y) 5) @@ -74,8 +69,7 @@ (+ (dm/get-prop position :x) 5) on-close - (mf/use-fn - #(st/emit! wdt/hide-token-set-context-menu))] + (mf/use-fn #(st/emit! (dt/assign-token-set-context-menu nil)))] [:& dropdown {:show (some? position) :on-close on-close} diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 683011757..de90cee4a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -8,7 +8,6 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] - [app.common.data.macros :as dm] [app.common.json :as json] [app.common.types.tokens-lib :as ctob] [app.main.data.event :as ev] @@ -30,9 +29,8 @@ [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.context-menu :refer [token-context-menu]] [app.main.ui.workspace.tokens.errors :as wte] - [app.main.ui.workspace.tokens.sets :refer [sets-list*]] - [app.main.ui.workspace.tokens.sets-context :as sets-context] - [app.main.ui.workspace.tokens.sets-context-menu :refer [sets-context-menu*]] + [app.main.ui.workspace.tokens.sets :as tsets] + [app.main.ui.workspace.tokens.sets-context-menu :refer [token-set-context-menu*]] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.theme-select :refer [theme-select]] [app.main.ui.workspace.tokens.token-pill :refer [token-pill*]] @@ -47,7 +45,7 @@ [shadow.resource])) (def ref:token-type-open-status - (l/derived #(dm/get-in % [:workspace-local :token-type-open-status]) st/state)) + (l/derived (l/key :open-status-by-type) refs/workspace-tokens)) ;; Components ------------------------------------------------------------------ @@ -85,7 +83,7 @@ (fn [event token] (dom/prevent-default event) (dom/stop-propagation event) - (st/emit! (dt/show-token-context-menu + (st/emit! (dt/assign-token-context-menu {:type :token :position (dom/get-client-position event) :errors (:errors token) @@ -200,43 +198,7 @@ (tr "workspace.token.no-permission-themes"))} [:& theme-select]]))])) -(mf/defc add-set-button* - {::mf/private true} - [{:keys [style]}] - (let [{:keys [on-create new-path]} - (sets-context/use-context) - - permissions - (mf/use-ctx ctx/permissions) - - can-edit? - (get permissions :can-edit) - - on-click - (mf/use-fn - (mf/deps on-create) - (fn [] - (on-create [])))] - - (if (= style "inline") - (when-not new-path - (if can-edit? - [:div {:class (stl/css :empty-sets-wrapper)} - [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} - (tr "workspace.token.no-sets-yet")] - [:button {:on-click on-click - :class (stl/css :create-theme-button)} - (tr "workspace.token.create-one")]] - [:div {:class (stl/css :empty-sets-wrapper)} - [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} - (tr "workspace.token.no-sets-yet")]])) - (when can-edit? - [:> icon-button* {:variant "ghost" - :icon "add" - :on-click on-click - :aria-label (tr "workspace.token.add set")}])))) - -(mf/defc theme-sets-list* +(mf/defc token-sets-list* {::mf/private true} [{:keys [tokens-lib]}] (let [;; FIXME: This is an inneficient operation just for being @@ -250,27 +212,31 @@ selected-token-set-name (mf/deref refs/selected-token-set-name) - {:keys [new-path] :as ctx} - (sets-context/use-context)] + {:keys [token-set-edition-id + token-set-new-path]} + (mf/deref refs/workspace-tokens)] (if (and (empty? token-sets) - (not new-path)) - [:> add-set-button* {:style "inline"}] - [:& h/sortable-container {} - [:> sets-list* {:tokens-lib tokens-lib - :selected-token-set-name selected-token-set-name}]]))) + (not token-set-new-path)) -(mf/defc themes-sets-tab* + (when-not token-set-new-path + [:> tsets/inline-add-button*]) + + [:> h/sortable-container {} + [:> tsets/sets-list* + {:tokens-lib tokens-lib + :new-path token-set-new-path + :edition-id token-set-edition-id + :selected selected-token-set-name}]]))) + +(mf/defc token-sets-section* {::mf/private true} [{:keys [resize-height] :as props}] - (let [permissions - (mf/use-ctx ctx/permissions) + (let [can-edit? + (mf/use-ctx ctx/can-edit?)] - can-edit? - (get permissions :can-edit)] - - [:& sets-context/provider {} - [:> sets-context-menu*] + [:* + [:> token-set-context-menu*] [:article {:data-testid "token-themes-sets-sidebar" :class (stl/css :sets-section-wrapper) :style {"--resize-height" (str resize-height "px")}} @@ -279,11 +245,11 @@ [:div {:class (stl/css :sidebar-header)} [:& title-bar {:title (tr "labels.sets")} (when can-edit? - [:> add-set-button* {:style "header"}])]] + [:> tsets/add-button*])]] - [:> theme-sets-list* props]]]])) + [:> token-sets-list* props]]]])) -(mf/defc tokens-tab* +(mf/defc tokens-section* [{:keys [tokens-lib]}] (let [objects (mf/deref refs/workspace-page-objects) selected (mf/deref refs/selected-shapes) @@ -475,13 +441,14 @@ (mf/deref refs/tokens-lib)] [:div {:class (stl/css :sidebar-wrapper)} - [:> themes-sets-tab* {:resize-height size-pages-opened - :tokens-lib tokens-lib}] + [:> token-sets-section* + {:resize-height size-pages-opened + :tokens-lib tokens-lib}] [:article {:class (stl/css :tokens-section-wrapper) :data-testid "tokens-sidebar"} [:div {:class (stl/css :resize-area-horiz) :on-pointer-down on-pointer-down-pages :on-lost-pointer-capture on-lost-pointer-capture-pages :on-pointer-move on-pointer-move-pages}] - [:> tokens-tab* {:tokens-lib tokens-lib}]] + [:> tokens-section* {:tokens-lib tokens-lib}]] [:> import-export-button*]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.scss b/frontend/src/app/main/ui/workspace/tokens/sidebar.scss index ed49eea66..c77111e7a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.scss +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.scss @@ -54,12 +54,6 @@ color: var(--color-foreground-secondary); } -.empty-sets-wrapper { - padding: $s-12; - padding-inline-start: $s-24; - color: var(--color-foreground-secondary); -} - .sidebar-header { display: flex; align-items: center; From b0a6e5c946d946119f28fed909604508efaf01a6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Feb 2025 11:46:15 +0100 Subject: [PATCH 11/15] :bug: Fix regression: close context menu on edit token --- frontend/src/app/main/ui/workspace/tokens/context_menu.cljs | 2 +- frontend/src/app/main/ui/workspace/tokens/sidebar.cljs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index 9a90fd810..70d8d4f7f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -219,7 +219,7 @@ :action (fn [event] (let [{:keys [key fields]} modal] (dom/stop-propagation event) - (st/emit! (dt/assign-token-set-context-menu nil) + (st/emit! (dt/assign-token-context-menu nil) (modal/show key {:x (.-clientX ^js event) :y (.-clientY ^js event) :position :right diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index de90cee4a..166d22319 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -82,7 +82,6 @@ (mf/use-fn (fn [event token] (dom/prevent-default event) - (dom/stop-propagation event) (st/emit! (dt/assign-token-context-menu {:type :token :position (dom/get-client-position event) From a029ec18a6642ba42e0d6cb3bc19b65c464351d9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Feb 2025 12:26:46 +0100 Subject: [PATCH 12/15] :bug: Fix incorrect handling of selected token set --- frontend/src/app/main/data/tokens.cljs | 55 +++++++++++-------- .../data/workspace/tokens/selected_set.cljs | 2 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 1ddc6e996..4fc18c170 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -31,6 +31,8 @@ ;; TOKENS Getters ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; FIXME: lookup rename + (defn get-tokens-lib [state] (-> (dsh/lookup-file-data state) @@ -237,32 +239,37 @@ (ptk/reify ::update-create-token ptk/WatchEvent (watch [it state _] - (let [data (dsh/lookup-file-data state) - token-set (dwts/get-selected-token-set state) - set-name (or (:name token-set) "Global") - changes (if (not token-set) - ;; No set created add a global set - (let [tokens-lib (get-tokens-lib state) - token-set (ctob/make-token-set :name set-name :tokens {(:name token) token}) - hidden-theme (ctob/make-hidden-token-theme :sets [set-name]) - active-theme-paths (some-> tokens-lib ctob/get-active-theme-paths) - add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path}) - base-changes (pcb/add-token-set (pcb/empty-changes) token-set)] - (cond - (not tokens-lib) - (-> base-changes - (pcb/add-token-theme hidden-theme) - (pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) + (let [data (dsh/lookup-file-data state) + selected (dm/get-in state [:workspace-tokens :selected-token-set-name]) - add-to-hidden-theme? - (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] - (-> base-changes - (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) + tokens-lib (get-tokens-lib state) + token-set (if selected + (some-> tokens-lib (ctob/get-set selected)) + (some-> tokens-lib (ctob/get-sets) (first))) - :else base-changes)) - (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/set-token set-name (or prev-token-name (:name token)) token)))] + set-name (or (:name token-set) "Global") + changes (if (not token-set) + ;; No set created add a global set + (let [token-set (ctob/make-token-set :name set-name :tokens {(:name token) token}) + hidden-theme (ctob/make-hidden-token-theme :sets [set-name]) + active-theme-paths (some-> tokens-lib ctob/get-active-theme-paths) + add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path}) + base-changes (pcb/add-token-set (pcb/empty-changes) token-set)] + (cond + (not tokens-lib) + (-> base-changes + (pcb/add-token-theme hidden-theme) + (pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{})) + + add-to-hidden-theme? + (let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)] + (-> base-changes + (pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme))) + + :else base-changes)) + (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/set-token set-name (or prev-token-name (:name token)) token)))] (rx/of (set-selected-token-set-name set-name) diff --git a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs index 50f54552a..b8d02aa1b 100644 --- a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs +++ b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs @@ -12,7 +12,7 @@ [app.main.data.helpers :as dsh])) (defn get-selected-token-set-name [state] - (or (get-in state [:workspace-local :selected-token-set-name]) + (or (get-in state [:workspace-tokens :selected-token-set-name]) (some-> (dsh/lookup-file-data state) (get :tokens-lib) (ctob/get-sets) From 064e51d24e6f842001e80d57f3518d5c91a29bb4 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Feb 2025 12:50:23 +0100 Subject: [PATCH 13/15] :bug: Clear token set edition state on rename --- .../src/app/main/ui/workspace/tokens/sets.cljs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 77670edd1..14a2ad4a0 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -36,14 +36,16 @@ (defn- on-toggle-token-set-click [name] (st/emit! (dt/toggle-token-set name))) -(defn- on-toggle-token-set-group-click [group-path] - (st/emit! (dt/toggle-token-set-group group-path))) +(defn- on-toggle-token-set-group-click [path] + (st/emit! (dt/toggle-token-set-group path))) -(defn- on-select-token-set-click [set-name] - (st/emit! (dt/set-selected-token-set-name set-name))) +(defn- on-select-token-set-click [name] + (st/emit! (dt/set-selected-token-set-name name))) -(defn on-update-token-set [set-name token-set] - (st/emit! (dt/update-token-set (:name token-set) (ctob/update-name token-set set-name)))) +(defn on-update-token-set + [name token-set] + (st/emit! (dt/clear-token-set-edition) + (dt/update-token-set (:name token-set) (ctob/update-name token-set name)))) (defn- on-update-token-set-group [path name] (st/emit! (dt/rename-token-set-group path name))) From 50afc4c507b629ac218b8c7153240f6de9968ac8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Feb 2025 12:56:27 +0100 Subject: [PATCH 14/15] :sparkles: Remove selection workaround for undo Always select the first one when no match is found --- .../app/main/ui/workspace/tokens/sidebar.cljs | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 166d22319..afae091ec 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -305,29 +305,14 @@ (get-sorted-token-groups tokens-by-type))] (mf/with-effect [tokens-lib selected-token-set-name] - (when tokens-lib - (if selected-token-set-name - ;; WORKAROUND: because we don't have a stable reference (by - ;; ID per example) to token sets, when a set is moved the - ;; name/path of the set changes and now can point to not - ;; existing object; on this cases we perform a best effort - ;; search around all existing sets that matches the - ;; name (and not the path) and select it if it is found - (when-not (ctob/get-set tokens-lib selected-token-set-name) - (let [selected-name (ctob/get-token-set-final-name selected-token-set-name) - match (->> (ctob/get-sets tokens-lib) - (map :name) - (filter (fn [name] - (let [path (ctob/split-token-set-name name)] - (= (peek path) selected-name)))) - (first))] - (when match - (st/emit! (dt/set-selected-token-set-name match))))) - - (let [match (->> (ctob/get-sets tokens-lib) - (first) - (:name))] - (st/emit! (dt/set-selected-token-set-name match)))))) + (when (and tokens-lib + (or (nil? selected-token-set-name) + (and selected-token-set-name + (not (ctob/get-set tokens-lib selected-token-set-name))))) + (let [match (->> (ctob/get-sets tokens-lib) + (first) + (:name))] + (st/emit! (dt/set-selected-token-set-name match))))) [:* [:& token-context-menu] From 7497371b32d9fe920c00b2a03aacf2f1761969d8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Feb 2025 16:38:36 +0100 Subject: [PATCH 15/15] :bug: Fix issue on renaming group --- frontend/src/app/main/data/tokens.cljs | 8 ++++---- frontend/src/app/main/ui/workspace/tokens/sets.cljs | 3 ++- .../app/main/ui/workspace/tokens/sets_context_menu.cljs | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 4fc18c170..576a3eb94 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -181,7 +181,8 @@ (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) -(defn delete-token-set-path [group? path] +(defn delete-token-set-path + [group? path] (ptk/reify ::delete-token-set-path ptk/WatchEvent (watch [it state _] @@ -189,9 +190,8 @@ changes (-> (pcb/empty-changes it) (pcb/with-library-data data) (pcb/delete-token-set-path group? path))] - (rx/of - (dch/commit-changes changes) - (wtu/update-workspace-tokens)))))) + (rx/of (dch/commit-changes changes) + (wtu/update-workspace-tokens)))))) (defn drop-error [{:keys [error to-path]}] (ptk/reify ::drop-error diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 14a2ad4a0..0f9e47f92 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -48,7 +48,8 @@ (dt/update-token-set (:name token-set) (ctob/update-name token-set name)))) (defn- on-update-token-set-group [path name] - (st/emit! (dt/rename-token-set-group path name))) + (st/emit! (dt/clear-token-set-edition) + (dt/rename-token-set-group path name))) (defn- on-create-token-set [name token-set] (st/emit! (ptk/data-event ::ev/event {::ev/name "create-token-set" :name name}) 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 b82bc8970..cf0c46ddf 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 @@ -39,14 +39,14 @@ (let [create-set-at-path (mf/use-fn (mf/deps path) #(st/emit! (dt/start-token-set-creation path))) - edit-name + on-edit (mf/use-fn (mf/deps path) (fn [] (let [name (ctob/join-set-path path)] (st/emit! (dt/start-token-set-edition name))))) - delete-set + on-delete (mf/use-fn (mf/deps is-group path) #(st/emit! (dt/delete-token-set-path is-group path)))] @@ -54,8 +54,8 @@ [:ul {:class (stl/css :context-list)} (when is-group [:> menu-entry* {:title (tr "workspace.token.add-set-to-group") :on-click create-set-at-path}]) - [:> 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.rename") :on-click on-edit}] + [:> menu-entry* {:title (tr "labels.delete") :on-click on-delete}]])) (mf/defc token-set-context-menu* []