From 19daffd1c013245129f3a669c70c61a1bda0b058 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 10 Jan 2025 13:42:30 +0100 Subject: [PATCH] :sparkles: Implement drag & drop for set groups :construction: PR feedback :recycle: Remove unused functions :recycle: Throw on non-allowed changes :construction: key fixes :construction: Fix tests :construction: FMT :construction: Add set group test :construction: Remove 'drop' in name :construction: Add group tests :construction: FMT :construction: Always return changes --- common/src/app/common/data.cljc | 17 + common/src/app/common/files/changes.cljc | 32 +- .../src/app/common/files/changes_builder.cljc | 56 +++- common/src/app/common/logic/tokens.cljc | 109 +++++- common/src/app/common/types/tokens_lib.cljc | 188 +++++++++-- .../test/common_tests/logic/token_test.cljc | 245 +++++++++++++- common/test/common_tests/runner.cljc | 2 + .../common_tests/types/tokens_lib_test.cljc | 97 ++++-- frontend/src/app/main/data/tokens.cljs | 112 +++---- .../main/data/workspace/tokens/common.cljs | 8 + .../data/workspace/tokens/selected_set.cljs | 37 ++ frontend/src/app/main/refs.cljs | 19 +- .../ui/workspace/tokens/context_menu.cljs | 14 +- .../app/main/ui/workspace/tokens/form.cljs | 6 +- .../app/main/ui/workspace/tokens/modals.cljs | 4 +- .../ui/workspace/tokens/modals/themes.cljs | 15 +- .../app/main/ui/workspace/tokens/sets.cljs | 317 +++++++++++------- .../ui/workspace/tokens/sets_context.cljs | 10 +- .../workspace/tokens/sets_context_menu.cljs | 21 +- .../app/main/ui/workspace/tokens/sidebar.cljs | 5 +- .../main/ui/workspace/tokens/token_set.cljs | 35 -- frontend/translations/en.po | 12 + 22 files changed, 1015 insertions(+), 346 deletions(-) create mode 100644 frontend/src/app/main/data/workspace/tokens/common.cljs create mode 100644 frontend/src/app/main/data/workspace/tokens/selected_set.cljs diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 2465a9e98..649aff34d 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -78,6 +78,23 @@ (declare index-of) +(defn oreorder-before + "Assoc a k v pair, in the order position just before the other key." + [o ks k v before-k] + (let [f (fn [o'] + (cond-> (reduce + (fn [acc [k' v']] + (cond + (and before-k (= k' before-k)) (assoc acc k v k' v') + (= k k') acc + :else (assoc acc k' v'))) + (ordered-map) + o') + (not before-k) (assoc k v)))] + (if (seq ks) + (oupdate-in o ks f) + (f o)))) + (defn oassoc-before "Assoc a k v pair, in the order position just before the other key" [o before-k k v] diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 2adf6b559..5c0e2d93a 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -418,8 +418,8 @@ [:rename-token-set-group [:map {:title "RenameTokenSetGroup"} [:type [:= :rename-token-set-group]] - [:from-path-str :string] - [:to-path-str :string]]] + [:set-group-path [:vector :string]] + [:set-group-fname :string]]] [:mod-token-set [:map {:title "ModTokenSetChange"} @@ -430,8 +430,18 @@ [:move-token-set-before [:map {:title "MoveTokenSetBefore"} [:type [:= :move-token-set-before]] - [:set-name :string] - [:before-set-name [:maybe :string]]]] + [:from-path [:vector :string]] + [:to-path [:vector :string]] + [:before-path [:maybe [:vector :string]]] + [:before-group? [:maybe :boolean]]]] + + [:move-token-set-group-before + [:map {:title "MoveTokenSetGroupBefore"} + [:type [:= :move-token-set-group-before]] + [:from-path [:vector :string]] + [:to-path [:vector :string]] + [:before-path [:maybe [:vector :string]]] + [:before-group? [:maybe :boolean]]]] [:del-token-set [:map {:title "DelTokenSetChange"} @@ -1070,11 +1080,11 @@ (ctob/add-sets (map ctob/make-token-set token-sets))))) (defmethod process-change :rename-token-set-group - [data {:keys [from-path-str to-path-str]}] + [data {:keys [set-group-path set-group-fname]}] (update data :tokens-lib (fn [lib] (-> lib (ctob/ensure-tokens-lib) - (ctob/rename-set-group from-path-str to-path-str))))) + (ctob/rename-set-group set-group-path set-group-fname))))) (defmethod process-change :mod-token-set [data {:keys [name token-set]}] @@ -1085,10 +1095,16 @@ (merge prev-set (dissoc token-set :tokens)))))))) (defmethod process-change :move-token-set-before - [data {:keys [set-name before-set-name]}] + [data {:keys [from-path to-path before-path before-group?] :as changes}] (update data :tokens-lib #(-> % (ctob/ensure-tokens-lib) - (ctob/move-set-before set-name before-set-name)))) + (ctob/move-set from-path to-path before-path before-group?)))) + +(defmethod process-change :move-token-set-group-before + [data {:keys [from-path to-path before-path before-group?]}] + (update data :tokens-lib #(-> % + (ctob/ensure-tokens-lib) + (ctob/move-set-group from-path to-path before-path before-group?)))) (defmethod process-change :del-token-set [data {:keys [name]}] diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 9ad9d0b28..fe27e4c81 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -813,12 +813,13 @@ (apply-changes-local))) (defn rename-token-set-group - [changes from-path-str to-path-str] - (-> changes - (update :redo-changes conj {:type :rename-token-set-group :from-path-str from-path-str :to-path-str to-path-str}) - ;; TODO: Figure out undo - #_(update :undo-changes conj {:type :rename-token-set-group :name (:name token-set) :token-set (or prev-token-set token-set)}) - (apply-changes-local))) + [changes set-group-path set-group-fname] + (let [undo-path (ctob/replace-last-path-name set-group-path set-group-fname) + undo-fname (last set-group-path)] + (-> changes + (update :redo-changes conj {:type :rename-token-set-group :set-group-path set-group-path :set-group-fname set-group-fname}) + (update :undo-changes conj {:type :rename-token-set-group :set-group-path undo-path :set-group-fname undo-fname}) + (apply-changes-local)))) (defn update-token-set [changes token-set prev-token-set] @@ -828,21 +829,50 @@ (apply-changes-local))) (defn delete-token-set-path - [changes token-set-path] + [changes group? path] (assert-library! changes) - (let [library-data (::library-data (meta changes)) + (let [;; TODO Move leaking prefix to library + prefixed-path (if group? + (ctob/set-group-path->set-group-prefixed-path path) + (ctob/set-full-path->set-prefixed-full-path path)) + prefixed-path-str (ctob/join-set-path prefixed-path) + library-data (::library-data (meta changes)) prev-token-sets (some-> (get library-data :tokens-lib) - (ctob/get-path-sets token-set-path))] + (ctob/get-path-sets prefixed-path-str))] (-> changes - (update :redo-changes conj {:type :del-token-set-path :path token-set-path}) + (update :redo-changes conj {:type :del-token-set-path :path prefixed-path-str}) (update :undo-changes conj {:type :add-token-sets :token-sets prev-token-sets}) (apply-changes-local)))) (defn move-token-set-before - [changes set-name before-set-name prev-before-set-name] + [changes {:keys [from-path to-path before-path before-group? prev-before-path prev-before-group?] :as opts}] (-> changes - (update :redo-changes conj {:type :move-token-set-before :set-name set-name :before-set-name before-set-name}) - (update :undo-changes conj {:type :move-token-set-before :set-name set-name :before-set-name prev-before-set-name}) + (update :redo-changes conj {:type :move-token-set-before + :from-path from-path + :to-path to-path + :before-path before-path + :before-group? before-group?}) + (update :undo-changes conj {:type :move-token-set-before + :from-path to-path + :to-path from-path + :before-path prev-before-path + :before-group? prev-before-group?}) + (apply-changes-local))) + +(defn move-token-set-group-before + [changes {:keys [from-path to-path before-path before-group? prev-before-path prev-before-group?]}] + (prn prev-before-path prev-before-group?) + (-> changes + (update :redo-changes conj {:type :move-token-set-group-before + :from-path from-path + :to-path to-path + :before-path before-path + :before-group? before-group?}) + (update :undo-changes conj {:type :move-token-set-group-before + :from-path to-path + :to-path from-path + :before-path prev-before-path + :before-group? prev-before-group?}) (apply-changes-local))) (defn set-tokens-lib diff --git a/common/src/app/common/logic/tokens.cljc b/common/src/app/common/logic/tokens.cljc index d1e7a49d8..62b240518 100644 --- a/common/src/app/common/logic/tokens.cljc +++ b/common/src/app/common/logic/tokens.cljc @@ -29,14 +29,103 @@ [changes tokens-lib set-name] (generate-update-active-sets changes tokens-lib #(ctob/toggle-set % set-name))) -(defn generate-toggle-token-set-group - "Toggle a token set group at `prefixed-set-path-str` in `tokens-lib` without modifying a user theme." - [changes tokens-lib prefixed-set-path-str] - (let [deactivate? (contains? #{:all :partial} (ctob/sets-at-path-all-active? tokens-lib prefixed-set-path-str)) - sets-names (->> (ctob/get-sets-at-prefix-path tokens-lib prefixed-set-path-str) +(defn toggle-token-set-group + "Toggle a token set group at `group-path` in `tokens-lib` for a `tokens-lib-theme`." + [group-path tokens-lib tokens-lib-theme] + (let [deactivate? (contains? #{:all :partial} (ctob/sets-at-path-all-active? tokens-lib group-path)) + sets-names (->> (ctob/get-sets-at-path tokens-lib group-path) (map :name) - (into #{})) - update-fn (if deactivate? - #(ctob/disable-sets % sets-names) - #(ctob/enable-sets % sets-names))] - (generate-update-active-sets changes tokens-lib update-fn))) + (into #{}))] + (if deactivate? + (ctob/disable-sets tokens-lib-theme sets-names) + (ctob/enable-sets tokens-lib-theme sets-names)))) + +(defn generate-toggle-token-set-group + "Toggle a token set group at `group-path` in `tokens-lib` without modifying a user theme." + [changes tokens-lib group-path] + (generate-update-active-sets changes tokens-lib #(toggle-token-set-group group-path tokens-lib %))) + +(defn vec-starts-with? [v1 v2] + (= (subvec v1 0 (min (count v1) (count v2))) v2)) + +(defn calculate-move-token-set-or-set-group + [tokens-lib {:keys [from-index to-index position collapsed-paths] + :or {collapsed-paths #{}}}] + (let [tree (-> (ctob/get-set-tree tokens-lib) + (ctob/walk-sets-tree-seq :walk-children? #(contains? collapsed-paths %))) + from (nth tree from-index) + to (nth tree to-index) + before (case position + :top to + :bot (nth tree (inc to-index) nil) + :center nil) + prev-before (if (:group? from) + (->> (drop (inc from-index) tree) + (filter (fn [element] + (<= (:depth element) (:depth from)))) + (first)) + (nth tree (inc from-index) nil)) + + drop-as-direct-group-child? (or + (= :center position) + (and + (= :bot position) + (:group? to) + (not (get collapsed-paths (:path to))))) + from-path (:path from) + to-parent-path (if drop-as-direct-group-child? + (:path to) + (into [] (butlast (:path to)))) + to-path (conj to-parent-path (last from-path)) + + identical? (or (= from-index to-index) + (and (= from-path to-path) + (case position + :top (= from-index (dec to-index)) + :bot (= from-index to-index) + nil))) + to-exists? (and + (not= (:parent-path from) to-parent-path) + (if (:group? from) + (ctob/set-group-path-exists? tokens-lib to-path) + (ctob/set-path-exists? tokens-lib to-path))) + parent-to-child-drop? (and + (not= (:parent-path from) to-parent-path) + (:group? from) + (vec-starts-with? to-path (:path from)))] + (cond + identical? nil + to-exists? + (throw (ex-info "move token set error: path exists" + {:error :path-exists + :path to-path})) + parent-to-child-drop? + (throw (ex-info "move token set error: parent-to-child" + {:error :parent-to-child + :from-path from-path + :to-path to-path})) + :else + (cond-> {:from-path from-path + :to-path to-path + :before-path nil + :before-group? nil} + before (assoc :before-path (:path before) + :before-group? (:group? before)) + prev-before (assoc :prev-before-path (:path prev-before) + :prev-before-group? (:group? prev-before)))))) + +(defn generate-move-token-set + "Create changes for dropping a token set or token set. + Throws for impossible moves." + [changes tokens-lib drop-opts] + (if-let [drop-opts' (calculate-move-token-set-or-set-group tokens-lib drop-opts)] + (pcb/move-token-set-before changes drop-opts') + changes)) + +(defn generate-move-token-set-group + "Create changes for dropping a token set or token set group. + Throws for impossible moves" + [changes tokens-lib drop-opts] + (if-let [drop-opts' (calculate-move-token-set-or-set-group tokens-lib drop-opts)] + (pcb/move-token-set-group-before changes drop-opts') + changes)) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 72e659a8e..e6f16e714 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -216,6 +216,16 @@ set-name (add-set-path-prefix (last full-path))] (conj set-path set-name))) +(defn set-group-path->set-group-prefixed-path + "Adds `set-group-prefix` (G-) to the `path` vector elements." + [path] + (mapv add-set-path-group-prefix path)) + +(defn set-group-path->set-group-prefixed-path-str + [path] + (-> (set-group-path->set-group-prefixed-path path) + (join-set-path))) + (defn split-set-prefix [set-path] (some->> set-path (re-matches #"^([SG]-)(.*)") @@ -302,6 +312,12 @@ [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] + (-> (into [] (drop-last path)) + (conj name))) + (defn tokens-tree "Convert tokens into a nested tree with their `:name` as the path. Optionally use `update-token-fn` option to transform the token." @@ -448,8 +464,8 @@ (add-sets [_ token-set] "add a collection of sets to the library, at the end") (update-set [_ set-name f] "modify a set in the library") (delete-set-path [_ set-path] "delete a set in the library") - (move-set-before [_ set-name before-set-name] "move a set with `set-name` before a set with `before-set-name` in the library. -When `before-set-name` is nil, move set to bottom") + (move-set [_ from-path to-path before-path before-group?] "Move token set at `from-path` to `to-path` and order it before `before-path` with `before-group?`.") + (move-set-group [_ from-path to-path before-path before-group?] "Move token set group at `from-path` to `to-path` and order it before `before-path` with `before-group?`.") (set-count [_] "get the total number if sets in the library") (get-set-tree [_] "get a nested tree of all sets in the library") (get-in-set-tree [_ path] "get `path` in nested tree of all sets in the library") @@ -654,6 +670,41 @@ used for managing active sets without a user created theme.") ;; === Import / Export from DTCG format +(defn walk-sets-tree-seq + [nodes & {:keys [walk-children?] + :or {walk-children? (constantly true)}}] + (let [walk (fn walk [node {:keys [parent depth] + :or {parent [] + depth 0} + :as opts}] + (lazy-seq + (if (d/ordered-map? node) + (mapcat #(walk % opts) node) + (let [[k v] node] + (cond + ;;; Set + (and v (instance? TokenSet v)) + [{:group? false + :path (split-token-set-path (:name v)) + :parent-path parent + :depth depth + :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 {:group? true + :path path + :parent-path parent + :depth depth}] + (if (walk-children? path) + [item] + (cons + item + (mapcat #(walk % (assoc opts :parent path :depth (inc depth))) v)))))))))] + (walk nodes nil))) + (defn flatten-nested-tokens-json "Recursively flatten the dtcg token structure, joining keys with '.'." [tokens token-path] @@ -683,12 +734,14 @@ used for managing active sets without a user created theme.") (defprotocol ITokensLib "A library of tokens, sets and themes." + (set-path-exists? [_ path] "if a set at `path` exists") + (set-group-path-exists? [_ path] "if a set group at `path` exists") (add-token-in-set [_ set-name token] "add token to a set") (update-token-in-set [_ set-name token-name f] "update a token in a set") (delete-token-from-set [_ set-name token-name] "delete a token from a set") (toggle-set-in-theme [_ group-name theme-name set-name] "toggle a set used / not used in a theme") (get-active-themes-set-names [_] "set of set names that are active in the the active themes") - (sets-at-path-all-active? [_ prefixed-path] "compute active state for child sets at `prefixed-path`. + (sets-at-path-all-active? [_ group-path] "compute active state for child sets at `group-path`. Will return a value that matches this schema: `:none` None of the nested sets are active `:all` All of the nested sets are active @@ -772,21 +825,87 @@ Will return a value that matches this schema: themes)) active-themes))) - ;; TODO Handle groups and nesting - (move-set-before [this set-name before-set-name] - (let [source-path (set-name->prefixed-full-path set-name) - token-set (-> (get-set this set-name) - (assoc :modified-at (dt/now))) - target-path (set-name->prefixed-full-path before-set-name)] - (if before-set-name - (TokensLib. (d/oassoc-in-before sets target-path source-path token-set) - themes - active-themes) - (TokensLib. (-> sets - (d/dissoc-in source-path) - (d/oassoc-in source-path token-set)) - themes - active-themes)))) + (move-set [_ from-path to-path before-path before-group?] + (let [prefixed-from-path (set-full-path->set-prefixed-full-path from-path) + prev-set (get-in sets prefixed-from-path)] + (if (instance? TokenSet prev-set) + (let [prefixed-to-path (set-full-path->set-prefixed-full-path to-path) + prefixed-before-path (when before-path + (if before-group? + (mapv add-set-path-group-prefix before-path) + (set-full-path->set-prefixed-full-path before-path))) + + set (assoc prev-set :name (join-set-path to-path)) + reorder? (= prefixed-from-path prefixed-to-path) + sets' + (if reorder? + (d/oreorder-before sets + (into [] (butlast prefixed-from-path)) + (last prefixed-from-path) + set + (last prefixed-before-path)) + (-> (if before-path + (d/oassoc-in-before sets prefixed-before-path prefixed-to-path set) + (d/oassoc-in sets prefixed-to-path set)) + (d/dissoc-in prefixed-from-path)))] + (TokensLib. sets' + (if reorder? + themes + (walk/postwalk + (fn [form] + (if (instance? TokenTheme form) + (update-set-name form (:name prev-set) (:name set)) + form)) + themes)) + active-themes)) + (TokensLib. sets themes active-themes)))) + + (move-set-group [this from-path to-path before-path before-group?] + (let [prefixed-from-path (set-group-path->set-group-prefixed-path from-path) + prev-set-group (get-in sets prefixed-from-path)] + (if prev-set-group + (let [from-path-str (join-set-path from-path) + to-path-str (join-set-path to-path) + prefixed-to-path (set-group-path->set-group-prefixed-path to-path) + prefixed-before-path (when before-path + (if before-group? + (set-group-path->set-group-prefixed-path before-path) + (set-full-path->set-prefixed-full-path before-path))) + reorder? (= prefixed-from-path prefixed-to-path) + sets' + (if reorder? + (d/oreorder-before sets + (into [] (butlast prefixed-from-path)) + (last prefixed-from-path) + prev-set-group + (last prefixed-before-path)) + (-> (if before-path + (d/oassoc-in-before sets prefixed-before-path prefixed-to-path prev-set-group) + (d/oassoc-in sets prefixed-to-path prev-set-group)) + (d/dissoc-in prefixed-from-path) + (d/oupdate-in prefixed-to-path (fn [sets] + (walk/prewalk + (fn [form] + (if (instance? TokenSet form) + (update form :name #(str to-path-str (str/strip-prefix % from-path-str))) + form)) + sets))))) + themes' (if reorder? + themes + (let [rename-sets-map (->> (get-sets-at-path this from-path) + (map (fn [set] + [(:name set) (str to-path-str (str/strip-prefix (:name set) from-path-str))])) + (into {}))] + (walk/postwalk + (fn [form] + (if (instance? TokenTheme form) + (update form :sets #(set (replace rename-sets-map %))) + form)) + themes)))] + (TokensLib. sets' + themes' + active-themes)) + (TokensLib. sets themes active-themes)))) (get-set-tree [_] sets) @@ -808,20 +927,22 @@ Will return a value that matches this schema: (tree-seq d/ordered-map? vals) (filter (partial instance? TokenSet)))) - (get-sets-at-path [_ path-str] - (some->> (split-token-set-path path-str) - (map add-set-path-group-prefix) + (get-sets-at-path [_ path] + (some->> (map add-set-path-group-prefix path) (get-in sets) (tree-seq d/ordered-map? vals) (filter (partial instance? TokenSet)))) - (rename-set-group [this from-path-str to-path-str] - (->> (get-sets-at-path this from-path-str) - (reduce - (fn [lib set] - (update-set lib (:name set) (fn [set'] - (update set' :name #(str to-path-str (str/strip-prefix % from-path-str)))))) - this))) + (rename-set-group [this path path-fname] + (let [from-path-str (join-set-path path) + to-path-str (-> (replace-last-path-name path path-fname) + (join-set-path)) + sets (get-sets-at-path this path)] + (reduce + (fn [lib set] + (update-set lib (:name set) (fn [set'] + (update set' :name #(str to-path-str (str/strip-prefix % from-path-str)))))) + this sets))) (get-ordered-set-names [this] (map :name (get-sets this))) @@ -938,6 +1059,12 @@ Will return a value that matches this schema: (tree-seq d/ordered-map? vals themes))) ITokensLib + (set-path-exists? [_ set-path] + (some? (get-in sets (set-full-path->set-prefixed-full-path set-path)))) + + (set-group-path-exists? [_ set-path] + (some? (get-in sets (set-group-path->set-group-prefixed-path set-path)))) + (add-token-in-set [this set-name token] (dm/assert! "expected valid token instance" (check-token! token)) (update-set this set-name #(add-token % token))) @@ -961,8 +1088,9 @@ Will return a value that matches this schema: (mapcat :sets) (get-active-themes this))) - (sets-at-path-all-active? [this prefixed-path-str] - (let [active-set-names (get-active-themes-set-names this)] + (sets-at-path-all-active? [this group-path] + (let [active-set-names (get-active-themes-set-names this) + prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)] (if (seq active-set-names) (let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path-str) (map :name) diff --git a/common/test/common_tests/logic/token_test.cljc b/common/test/common_tests/logic/token_test.cljc index c91235d8f..9a06beb99 100644 --- a/common/test/common_tests/logic/token_test.cljc +++ b/common/test/common_tests/logic/token_test.cljc @@ -60,7 +60,7 @@ (ctob/add-theme (ctob/make-hidden-token-theme)) (ctob/set-active-themes #{ctob/hidden-token-theme-path}))) - changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) "G-foo/S-bar") + changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) ["foo"]) redo (thf/apply-changes file changes) redo-lib (tht/get-tokens-lib redo) @@ -81,7 +81,7 @@ (ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child")) (ctob/add-theme (ctob/make-token-theme :name "theme")) (ctob/set-active-themes #{"/theme"}))) - changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) "G-foo/G-bar") + changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) ["foo" "bar"]) redo (thf/apply-changes file changes) redo-lib (tht/get-tokens-lib redo) @@ -103,7 +103,7 @@ :sets #{"foo/bar/baz"})) (ctob/set-active-themes #{"/theme"}))) - changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) "G-foo/G-bar") + changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file) ["foo" "bar"]) redo (thf/apply-changes file changes) redo-lib (tht/get-tokens-lib redo) @@ -115,3 +115,242 @@ ;; Undo (t/is (nil? (ctob/get-hidden-theme undo-lib))) (t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))) + +(t/deftest generate-move-token-set-test + (t/testing "Ignore dropping set to the same position:" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar/baz")))) + drop (partial clt/generate-move-token-set (pcb/empty-changes) (tht/get-tokens-lib file))] + (t/testing "on top of identical" + (t/is (= (pcb/empty-changes) + (drop {:from-index 0 + :to-index 0 + :position :top})))) + (t/testing "on bottom of identical" + (t/is (= (pcb/empty-changes) + (drop {:from-index 0 + :to-index 0 + :position :bot})))) + (t/testing "on top of next to identical" + (t/is (= (pcb/empty-changes) + (drop {:from-index 0 + :to-index 1 + :position :top})))))) + + (t/testing "Reorder sets when dropping next to a set:" + (t/testing "at top" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar")) + (ctob/add-set (ctob/make-token-set :name "baz")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 1 + :to-index 0 + :position :top}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["bar" "foo" "baz"] (vec redo-sets))) + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets))))) + + (t/testing "at bottom" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar")) + (ctob/add-set (ctob/make-token-set :name "baz")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 0 + :to-index 2 + :position :bot}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["bar" "baz" "foo"] (vec redo-sets))) + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets))))) + + (t/testing "dropping out of set group" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-set (ctob/make-token-set :name "foo")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 1 + :to-index 0 + :position :top}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["bar" "foo"] (vec redo-sets))) + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets))))) + + (t/testing "into set group" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar")) + (ctob/add-set (ctob/make-token-set :name "foo")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 2 + :to-index 1 + :position :bot}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["foo/bar" "foo/foo"] (vec redo-sets))) + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets))))) + + (t/testing "edge-cases:" + (t/testing "prevent overriding set to identical path" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "foo")))) + lib (tht/get-tokens-lib file)] + (t/is (thrown? + #?(:cljs js/Error :clj Exception) + (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 2 + :to-index 0 + :position :bot}) + #"move token set error: path exists")) + (t/is (thrown? + #?(:cljs js/Error :clj Exception) + (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 2 + :to-index 1 + :position :bot}) + #"move token set error: path exists")))) + + (t/testing "dropping below collapsed group doesnt add as child" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "foo/bar")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 0 + :to-index 1 + :position :bot + :collapsed-paths #{["foo"]}}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["foo/bar" "foo"] (vec redo-sets))) + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets)))))))) + +(t/deftest generate-move-token-group-test + (t/testing "Ignore dropping set group to the same position" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo")) + (ctob/add-set (ctob/make-token-set :name "bar/baz")))) + drop (partial clt/generate-move-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file))] + (t/testing "on top of identical" + (t/is (= (pcb/empty-changes) + (drop {:from-index 1 + :to-index 1 + :position :top})))) + (t/testing "on bottom of identical" + (t/is (= (pcb/empty-changes) + (drop {:from-index 1 + :to-index 1 + :position :bot})))) + (t/testing "on top of next to identical" + (t/is (= (pcb/empty-changes) + (drop {:from-index 1 + :to-index 1 + :position :top})))))) + + (t/testing "Move set groups" + (t/testing "to top" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "bar/bar")) + (ctob/add-set (ctob/make-token-set :name "baz/baz")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 2 + :to-index 0 + :position :top}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["bar/bar" "foo/foo" "baz/baz"] (vec redo-sets))) + + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets))))) + + (t/testing "to bottom" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "bar")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0 + :to-index 2 + :position :bot}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["bar" "foo/foo"] (vec redo-sets))) + + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets))))) + + (t/testing "into set group" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :name "bar/bar")))) + lib (tht/get-tokens-lib file) + changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0 + :to-index 2 + :position :bot}) + redo (thf/apply-changes file changes) + redo-sets (-> (tht/get-tokens-lib redo) + (ctob/get-ordered-set-names)) + undo (thf/apply-undo-changes redo changes) + undo-sets (-> (tht/get-tokens-lib undo) + (ctob/get-ordered-set-names))] + (t/is (= ["bar/foo/foo" "bar/bar"] (vec redo-sets))) + (t/testing "undo" + (t/is (= (ctob/get-ordered-set-names lib) undo-sets)))) + + (t/testing "edge-cases:" + (t/testing "prevent overriding set to identical path" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/identical/foo")) + (ctob/add-set (ctob/make-token-set :name "identical/bar")))) + lib (tht/get-tokens-lib file)] + (t/is (thrown? + #?(:cljs js/Error :clj Exception) + (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 3 + :to-index 1 + :position :top}) + #"move token set error: path exists")))) + + (t/testing "prevent dropping parent to child" + (let [file (setup-file #(-> % + (ctob/add-set (ctob/make-token-set :name "foo/bar/baz")))) + lib (tht/get-tokens-lib file)] + (t/is (thrown? + #?(:cljs js/Error :clj Exception) + (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0 + :to-index 1 + :position :bot}) + #"move token set error: parent-to-child")))))))) diff --git a/common/test/common_tests/runner.cljc b/common/test/common_tests/runner.cljc index 6b30a8886..06a3fc58d 100644 --- a/common/test/common_tests/runner.cljc +++ b/common/test/common_tests/runner.cljc @@ -28,6 +28,7 @@ [common-tests.logic.multiple-nesting-levels-test] [common-tests.logic.swap-and-reset-test] [common-tests.logic.swap-as-override-test] + [common-tests.logic.token-test] [common-tests.pages-helpers-test] [common-tests.record-test] [common-tests.schema-test] @@ -75,6 +76,7 @@ 'common-tests.logic.multiple-nesting-levels-test 'common-tests.logic.swap-and-reset-test 'common-tests.logic.swap-as-override-test + 'common-tests.logic.token-test 'common-tests.pages-helpers-test 'common-tests.record-test 'common-tests.schema-test diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index f392bb79d..66b43e112 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -91,28 +91,75 @@ (apply ctob/make-token-set args))))) (t/testing "move-token-set" - (let [tokens-lib (-> (ctob/make-tokens-lib) - (ctob/add-set (ctob/make-token-set :name "A")) - (ctob/add-set (ctob/make-token-set :name "B")) - (ctob/add-set (ctob/make-token-set :name "Move"))) - original-order (into [] (ctob/get-ordered-set-names tokens-lib)) - move (fn [set-name before-set-name] - (->> (ctob/move-set-before tokens-lib set-name before-set-name) - (ctob/get-ordered-set-names) - (into [])))] - (t/testing "regular moving" - (t/is (= ["A" "Move" "B"] (move "Move" "B"))) - (t/is (= ["B" "A" "Move"] (move "A" "Move")))) + (t/testing "flat" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "A")) + (ctob/add-set (ctob/make-token-set :name "B")) + (ctob/add-set (ctob/make-token-set :name "Move"))) + move (fn [from-path to-path before-path before-group?] + (->> (ctob/move-set tokens-lib from-path to-path before-path before-group?) + (ctob/get-ordered-set-names) + (into [])))] + (t/testing "move to top" + (t/is (= ["Move" "A" "B"] (move ["Move"] ["Move"] ["A"] false)))) - (t/testing "move to bottom" - (t/is (= ["B" "Move" "A"] (move "A" nil)))) + (t/testing "move in-between" + (t/is (= ["A" "Move" "B"] (move ["Move"] ["Move"] ["B"] false)))) - (t/testing "no move expected" - (t/is (= original-order (move "Move" "Move")))) + (t/testing "move to bottom" + (t/is (= ["A" "B" "Move"] (move ["Move"] ["Move"] nil false)))))) - (t/testing "ignore invalid moves" - (t/is (= original-order (move "A" "foo/bar/baz"))) - (t/is (= original-order (move "Missing" "Move")))))) + (t/testing "nested" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "Foo/Baz")) + (ctob/add-set (ctob/make-token-set :name "Foo/Bar")) + (ctob/add-set (ctob/make-token-set :name "Foo"))) + move (fn [from-path to-path before-path before-group?] + (->> (ctob/move-set tokens-lib from-path to-path before-path before-group?) + (ctob/get-ordered-set-names) + (into [])))] + (t/testing "move outside of group" + (t/is (= ["Foo/Baz" "Bar" "Foo"] (move ["Foo" "Bar"] ["Bar"] ["Foo"] false))) + (t/is (= ["Bar" "Foo/Baz" "Foo"] (move ["Foo" "Bar"] ["Bar"] ["Foo" "Baz"] true))) + (t/is (= ["Foo/Baz" "Foo" "Bar"] (move ["Foo" "Bar"] ["Bar"] nil false)))) + + (t/testing "move inside of group" + (t/is (= ["Foo/Foo" "Foo/Baz" "Foo/Bar"] (move ["Foo"] ["Foo" "Foo"] ["Foo" "Baz"] false))) + (t/is (= ["Foo/Baz" "Foo/Bar" "Foo/Foo"] (move ["Foo"] ["Foo" "Foo"] nil false)))))) + + (t/testing "updates theme set names" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "Foo/Bar/Baz")) + (ctob/add-set (ctob/make-token-set :name "Other")) + (ctob/add-theme (ctob/make-token-theme :name "Theme" + :sets #{"Foo/Bar/Baz"})) + (ctob/move-set ["Foo" "Bar" "Baz"] ["Other/Baz"] nil nil))] + (t/is (= #{"Other/Baz"} (:sets (ctob/get-theme tokens-lib "" "Theme"))))))) + + (t/testing "move-token-set-group" + (t/testing "reordering" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "Foo/A")) + (ctob/add-set (ctob/make-token-set :name "Foo/B")) + (ctob/add-set (ctob/make-token-set :name "Bar/Foo")) + (ctob/add-theme (ctob/make-token-theme :name "Theme" + :sets #{"Foo/A" "Bar/Foo"}))) + move (fn [from-path to-path before-path before-group?] + (->> (ctob/move-set-group tokens-lib from-path to-path before-path before-group?) + (ctob/get-ordered-set-names) + (into [])))] + (t/is (= ["Bar/Foo" "Bar/Foo/A" "Bar/Foo/B"] (move ["Foo"] ["Bar" "Foo"] nil nil))) + (t/is (= ["Bar/Foo" "Foo/A" "Foo/B"] (move ["Bar"] ["Bar"] ["Foo"] true))))) + + (t/testing "updates theme set names" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "Foo/A")) + (ctob/add-set (ctob/make-token-set :name "Foo/B")) + (ctob/add-set (ctob/make-token-set :name "Bar/Foo")) + (ctob/add-theme (ctob/make-token-theme :name "Theme" + :sets #{"Foo/A" "Bar/Foo"})) + (ctob/move-set-group ["Foo"] ["Bar" "Foo"] nil nil))] + (t/is (= #{"Bar/Foo/A" "Bar/Foo"} (:sets (ctob/get-theme tokens-lib "" "Theme"))))))) (t/testing "tokens-tree" (let [tokens-lib (-> (ctob/make-tokens-lib) @@ -237,8 +284,8 @@ (ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child-2")) (ctob/add-theme (ctob/make-token-theme :name "theme" :sets #{"foo/bar/baz/baz-child-1"}))) tokens-lib' (-> tokens-lib - (ctob/rename-set-group "foo/bar" "foo/bar-renamed") - (ctob/rename-set-group "foo/bar-renamed/baz" "foo/bar-renamed/baz-renamed")) + (ctob/rename-set-group ["foo" "bar"] "bar-renamed") + (ctob/rename-set-group ["foo" "bar-renamed" "baz"] "baz-renamed")) expected-set-names (ctob/get-ordered-set-names tokens-lib') expected-theme-sets (-> (ctob/get-theme tokens-lib' "" "theme") :sets)] @@ -436,16 +483,16 @@ expected-none (-> tokens-lib (ctob/set-active-themes #{"/none"}) - (ctob/sets-at-path-all-active? "G-foo")) + (ctob/sets-at-path-all-active? ["foo"])) expected-all (-> tokens-lib (ctob/set-active-themes #{"/all"}) - (ctob/sets-at-path-all-active? "G-foo")) + (ctob/sets-at-path-all-active? ["foo"])) expected-partial (-> tokens-lib (ctob/set-active-themes #{"/partial"}) - (ctob/sets-at-path-all-active? "G-foo")) + (ctob/sets-at-path-all-active? ["foo"])) expected-invalid-none (-> tokens-lib (ctob/set-active-themes #{"/invalid"}) - (ctob/sets-at-path-all-active? "G-foo"))] + (ctob/sets-at-path-all-active? ["foo"]))] (t/is (= :none expected-none)) (t/is (= :all expected-all)) (t/is (= :partial expected-partial)) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 70d8a3e65..3373d35a4 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -15,10 +15,12 @@ [app.main.data.changes :as dch] [app.main.data.event :as ev] [app.main.data.helpers :as dsh] + [app.main.data.notifications :as ntf] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.tokens.selected-set :as dwts] [app.main.store :as st] - [app.main.ui.workspace.tokens.token-set :as wtts] [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])) @@ -53,21 +55,6 @@ ;; TOKENS Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn set-selected-token-set-path - [full-path] - (ptk/reify ::set-selected-token-set-path - ptk/UpdateEvent - (update [_ state] - (wtts/assoc-selected-token-set-path state full-path)))) - -(defn set-selected-token-set-path-from-name - [token-set-name] - (ptk/reify ::set-selected-token-set-path-from-name - ptk/UpdateEvent - (update [_ state] - (->> (ctob/set-name-string->prefixed-set-path-string token-set-name) - (wtts/assoc-selected-token-set-path state))))) - (defn create-token-theme [token-theme] (let [new-token-theme token-theme] (ptk/reify ::create-token-theme @@ -131,17 +118,16 @@ (let [changes (-> (pcb/empty-changes it) (pcb/add-token-set new-token-set))] (rx/of - (set-selected-token-set-path-from-name (:name new-token-set)) + (dwts/set-selected-token-set-name (:name new-token-set)) (dch/commit-changes changes))))))) -(defn rename-token-set-group [from-path-str to-path-str] +(defn rename-token-set-group [set-group-path set-group-fname] (ptk/reify ::rename-token-set-group ptk/WatchEvent (watch [it _state _] (let [changes (-> (pcb/empty-changes it) - (pcb/rename-token-set-group from-path-str to-path-str))] + (pcb/rename-token-set-group set-group-path set-group-fname))] (rx/of - (set-selected-token-set-path-from-name to-path-str) (dch/commit-changes changes)))))) (defn update-token-set [set-name token-set] @@ -153,7 +139,7 @@ changes (-> (pcb/empty-changes it) (pcb/update-token-set token-set prev-token-set))] (rx/of - (set-selected-token-set-path-from-name (:name token-set)) + (dwts/set-selected-token-set-name (:name token-set)) (dch/commit-changes changes)))))) (defn toggle-token-set [{:keys [token-set-name]}] @@ -165,11 +151,11 @@ (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) -(defn toggle-token-set-group [{:keys [prefixed-path-str]}] +(defn toggle-token-set-group [group-path] (ptk/reify ::toggle-token-set-group ptk/WatchEvent (watch [_ state _] - (let [changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (get-tokens-lib state) prefixed-path-str)] + (let [changes (clt/generate-toggle-token-set-group (pcb/empty-changes) (get-tokens-lib state) group-path)] (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) @@ -183,7 +169,7 @@ (ctob/get-sets) (first) (:name) - (set-selected-token-set-path-from-name)) + (dwts/set-selected-token-set-name)) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) (pcb/set-tokens-lib lib))] @@ -192,39 +178,66 @@ update-token-set-change (wtu/update-workspace-tokens)))))) -(defn delete-token-set-path [prefixed-full-set-path] +(defn delete-token-set-path [group? path] (ptk/reify ::delete-token-set-path ptk/WatchEvent (watch [it state _] (let [data (dsh/lookup-file-data state) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/delete-token-set-path prefixed-full-set-path))] + (pcb/delete-token-set-path group? path))] (rx/of (dch/commit-changes changes) (wtu/update-workspace-tokens)))))) -(defn move-token-set [source-set-name dest-set-name position] - (ptk/reify ::move-token-set +(defn drop-error [{:keys [error to-path]}] + (ptk/reify ::drop-error + ptk/WatchEvent + (watch [_ _ _] + (let [content (case error + :path-exists (tr "errors.drag-drop.set-exists" to-path) + :parent-to-child (tr "errors.drag-drop.parent-to-child") + nil)] + (when content + (rx/of + (ntf/show {:content content + :type :toast + :level :error + :timeout 9000}))))))) + +(defn drop-token-set-group [drop-opts] + (ptk/reify ::drop-token-set-group ptk/WatchEvent (watch [it state _] - (let [tokens-lib (get-tokens-lib state) - prev-before-set-name (ctob/get-neighbor-set-name tokens-lib source-set-name 1) - [source-set-name' dest-set-name'] (if (= :top position) - [source-set-name dest-set-name] - [source-set-name (ctob/get-neighbor-set-name tokens-lib dest-set-name 1)]) - changes (-> (pcb/empty-changes it) - (pcb/move-token-set-before source-set-name' dest-set-name' prev-before-set-name))] - (rx/of - (dch/commit-changes changes) - (wtu/update-workspace-tokens)))))) + (try + (when-let [changes (clt/generate-move-token-set-group (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 drop-token-set [drop-opts] + (ptk/reify ::drop-token-set + ptk/WatchEvent + (watch [it state _] + (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) + (some-> (get-in changes [:redo-changes 0 :to-path]) (dwts/set-selected-token-set-name)) + (wtu/update-workspace-tokens))) + (catch js/Error e + (rx/of + (drop-error (ex-data e)))))))) (defn update-create-token [{:keys [token prev-token-name]}] (ptk/reify ::update-create-token ptk/WatchEvent (watch [_ state _] - (let [token-set (wtts/get-selected-token-set state) + (let [token-set (dwts/get-selected-token-set state) token-set-name (or (:name token-set) "Global") changes (if (not token-set) ;; No set created add a global set @@ -251,7 +264,7 @@ (st/emit! (ptk/event ::ev/event {::ev/name "create-tokens"})) (pcb/add-token (pcb/empty-changes) (:name token-set) token))))] (rx/of - (set-selected-token-set-path-from-name token-set-name) + (dwts/set-selected-token-set-name token-set-name) (dch/commit-changes changes)))))) (defn delete-token @@ -273,8 +286,7 @@ (ptk/reify ::duplicate-token ptk/WatchEvent (watch [_ state _] - (when-let [token (some-> (wtts/get-selected-token-set state) - (ctob/get-token token-name) + (when-let [token (some-> (dwts/get-selected-token-set-token state token-name) (update :name #(str/concat % "-copy")))] (rx/of (update-create-token {:token token})))))) @@ -317,19 +329,3 @@ ptk/UpdateEvent (update [_ state] (assoc-in state [:workspace-local :token-set-context-menu] nil)))) - -;; === Import Export Context Menu - -(defn show-import-export-context-menu - [{:keys [position] :as params}] - (dm/assert! (gpt/point? position)) - (ptk/reify ::show-import-export-context-menu - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :import-export-context-menu] params)))) - -(def hide-import-export-set-context-menu - (ptk/reify ::hide-import-export-set-context-menu - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace-local :import-export-set-context-menu] nil)))) diff --git a/frontend/src/app/main/data/workspace/tokens/common.cljs b/frontend/src/app/main/data/workspace/tokens/common.cljs new file mode 100644 index 000000000..7cb07aeae --- /dev/null +++ b/frontend/src/app/main/data/workspace/tokens/common.cljs @@ -0,0 +1,8 @@ +(ns app.main.data.workspace.tokens.common + (:require + [app.main.data.helpers :as dsh])) + +(defn get-workspace-tokens-lib + [state] + (-> (dsh/lookup-file-data state) + (get :tokens-lib))) diff --git a/frontend/src/app/main/data/workspace/tokens/selected_set.cljs b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs new file mode 100644 index 000000000..ece47fca0 --- /dev/null +++ b/frontend/src/app/main/data/workspace/tokens/selected_set.cljs @@ -0,0 +1,37 @@ +(ns app.main.data.workspace.tokens.selected-set + "The user selected token set in the ui, stored by the `:name` of the set. + Will default to the first set." + (:require + [app.common.types.tokens-lib :as ctob] + [app.main.data.workspace.tokens.common :as dwtc] + [potok.v2.core :as ptk])) + +(defn assoc-selected-token-set-name [state set-name] + (assoc-in state [:workspace-local :selected-token-set-name] set-name)) + +(defn get-selected-token-set-name [state] + (or (get-in state [:workspace-local :selected-token-set-name]) + (some-> (dwtc/get-workspace-tokens-lib state) + (ctob/get-sets) + (first) + :name))) + +(defn get-selected-token-set [state] + (when-let [set-name (get-selected-token-set-name state)] + (some-> (dwtc/get-workspace-tokens-lib state) + (ctob/get-set set-name)))) + +(defn get-selected-token-set-token [state token-name] + (some-> (get-selected-token-set state) + (ctob/get-token token-name))) + +(defn get-selected-token-set-tokens [state] + (some-> (get-selected-token-set state) + :tokens)) + +(defn set-selected-token-set-name + [set-name] + (ptk/reify ::set-selected-token-set-path-from-name + ptk/UpdateEvent + (update [_ state] + (assoc-selected-token-set-name state set-name)))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index a5007a143..deb64480c 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -15,8 +15,8 @@ [app.common.types.tokens-lib :as ctob] [app.config :as cf] [app.main.data.helpers :as dsh] + [app.main.data.workspace.tokens.selected-set :as dwts] [app.main.store :as st] - [app.main.ui.workspace.tokens.token-set :as wtts] [okulary.core :as l])) ;; ---- Global refs @@ -450,11 +450,8 @@ (def workspace-token-themes-no-hidden (l/derived #(remove ctob/hidden-temporary-theme? %) workspace-token-themes)) -(def workspace-selected-token-set-path - (l/derived wtts/get-selected-token-set-path st/state)) - -(def workspace-token-set-group-selected? - (l/derived wtts/token-group-selected? st/state)) +(def workspace-selected-token-set-name + (l/derived dwts/get-selected-token-set-name st/state)) (def workspace-ordered-token-sets (l/derived #(or (some-> % ctob/get-sets) []) tokens-lib)) @@ -466,11 +463,11 @@ (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) (defn token-sets-at-path-all-active - [prefixed-path] + [group-path] (l/derived (fn [lib] (when lib - (ctob/sets-at-path-all-active? lib prefixed-path))) + (ctob/sets-at-path-all-active? lib group-path))) tokens-lib)) (def workspace-active-theme-paths-no-hidden @@ -485,12 +482,12 @@ (def workspace-selected-token-set-token (fn [token-name] (l/derived - #(some-> (wtts/get-selected-token-set %) - (ctob/get-token token-name)) + #(dwts/get-selected-token-set-token % token-name) st/state))) (def workspace-selected-token-set-tokens - (l/derived #(or (wtts/get-selected-token-set-tokens %) {}) st/state)) + (l/derived #(or (dwts/get-selected-token-set-tokens %) {}) st/state)) + (def plugins-permissions-peek (l/derived (fn [state] 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 4f92afada..7c6719069 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -207,7 +207,7 @@ (generic-attribute-actions #{:x} "X" (assoc context-data :on-update-shape wtch/update-shape-position)) (generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))})) -(defn default-actions [{:keys [token selected-token-set-path]}] +(defn default-actions [{:keys [token selected-token-set-name]}] (let [{:keys [modal]} (wtty/get-token-properties token)] [{:title (tr "workspace.token.edit") :no-selectable true @@ -220,16 +220,16 @@ :position :right :fields fields :action "edit" - :selected-token-set-path selected-token-set-path + :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)))} {:title (tr "workspace.token.delete") :no-selectable true - :action #(st/emit! (-> selected-token-set-path - ctob/prefixed-set-path-string->set-name-string - (dt/delete-token (:name token))))}])) + :action #(st/emit! (dt/delete-token + (ctob/prefixed-set-path-string->set-name-string selected-token-set-name) + (:name token)))}])) (defn selection-actions [{:keys [type token] :as context-data}] (let [with-actions (get shape-attribute-actions-map (or type (:type token))) @@ -350,13 +350,13 @@ 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-path (mf/deref refs/workspace-selected-token-set-path)] + selected-token-set-name (mf/deref refs/workspace-selected-token-set-name)] [:ul {:class (stl/css :context-list)} [:& menu-tree {:submenu-offset width :submenu-direction direction :token token :errors errors - :selected-token-set-path selected-token-set-path + :selected-token-set-name selected-token-set-name :selected-shapes selected-shapes}]])) (mf/defc token-context-menu diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index bf58d92ac..ef22939d4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -214,7 +214,7 @@ (mf/defc form {::mf/wrap-props false} - [{:keys [token token-type action selected-token-set-path]}] + [{:keys [token token-type action selected-token-set-name]}] (let [token (or token {:type token-type}) token-properties (wtty/get-token-properties token) color? (wtt/color-token? token) @@ -376,11 +376,11 @@ (modal/hide!)))))))) on-delete-token (mf/use-fn - (mf/deps selected-token-set-path) + (mf/deps selected-token-set-name) (fn [e] (dom/prevent-default e) (modal/hide!) - (st/emit! (dt/delete-token (ctob/prefixed-set-path-string->set-name-string selected-token-set-path) (:name token))))) + (st/emit! (dt/delete-token (ctob/prefixed-set-path-string->set-name-string selected-token-set-name) (:name token))))) on-cancel (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.cljs b/frontend/src/app/main/ui/workspace/tokens/modals.cljs index 3b2dcccdc..1f12664c8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals.cljs @@ -42,7 +42,7 @@ (mf/defc token-update-create-modal {::mf/wrap-props false} - [{:keys [x y position token token-type action selected-token-set-path] :as _args}] + [{:keys [x y position token token-type action selected-token-set-name] :as _args}] (let [wrapper-style (use-viewport-position-style x y position) close-modal (mf/use-fn (fn [] @@ -57,7 +57,7 @@ :aria-label (tr "labels.close")}] [:& form {:token token :action action - :selected-token-set-path selected-token-set-path + :selected-token-set-name selected-token-set-name :token-type token-type}]])) ;; Modals ---------------------------------------------------------------------- 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 95a3b8ed2..2f54ad9ff 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -8,6 +8,7 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] + [app.common.logic.tokens :as clt] [app.common.types.tokens-lib :as ctob] [app.main.data.event :as ev] [app.main.data.modal :as modal] @@ -308,8 +309,8 @@ token-set-group-active? (mf/use-callback (mf/deps theme-state) - (fn [prefixed-path] - (ctob/sets-at-path-all-active? lib prefixed-path))) + (fn [group-path] + (ctob/sets-at-path-all-active? lib group-path))) token-set-active? (mf/use-callback @@ -323,6 +324,12 @@ (fn [set-name] (swap! theme-state #(ctob/toggle-set % set-name)))) + on-toggle-token-set-group + (mf/use-callback + (mf/deps theme-state) + (fn [group-path] + (swap! theme-state #(clt/toggle-token-set-group group-path lib %)))) + on-click-token-set (mf/use-callback (mf/deps on-toggle-token-set) @@ -358,6 +365,7 @@ :token-set-group-active? token-set-group-active? :on-select on-click-token-set :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}]] @@ -400,4 +408,5 @@ :aria-label (tr "labels.close") :variant "action" :icon "close"}] - [:& themes-modal-body]]])) + [:& sets-context/provider {} + [:& 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 9102a7fb9..59b09ae93 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -7,15 +7,18 @@ (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.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*]] [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]] @@ -27,23 +30,17 @@ (defn on-toggle-token-set-click [token-set-name] (st/emit! (wdt/toggle-token-set {:token-set-name token-set-name}))) -(defn on-toggle-token-set-group-click [prefixed-path-str] - (st/emit! (wdt/toggle-token-set-group {:prefixed-path-str prefixed-path-str}))) +(defn on-toggle-token-set-group-click [group-path] + (st/emit! (wdt/toggle-token-set-group group-path))) -(defn on-select-token-set-click [tree-path] - (st/emit! (wdt/set-selected-token-set-path tree-path))) +(defn on-select-token-set-click [set-name] + (st/emit! (dwts/set-selected-token-set-name set-name))) (defn on-update-token-set [set-name token-set] (st/emit! (wdt/update-token-set set-name token-set))) -(defn on-update-token-set-group [from-prefixed-path-str to-path-str] - (st/emit! - (wdt/rename-token-set-group - (ctob/prefixed-set-path-string->set-name-string from-prefixed-path-str) - (-> (ctob/prefixed-set-path-string->set-path from-prefixed-path-str) - (butlast) - (ctob/join-set-path) - (ctob/join-set-path-str to-path-str))))) +(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-create-token-set [_ token-set] (st/emit! (ptk/event ::ev/event {::ev/name "create-tokens-set"})) @@ -105,58 +102,83 @@ :icon-id (if mixed? ic/remove ic/tick)}])])) (mf/defc sets-tree-set-group - [{:keys [label tree-depth tree-path active? selected? collapsed? editing? on-toggle on-edit on-edit-reset on-edit-submit]}] - (let [editing?' (editing? tree-path) - active?' (active? tree-path) + [{: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)) + on-context-menu (mf/use-fn - (mf/deps editing? tree-path can-edit?) + (mf/deps editing?' editing-id can-edit?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (when (and can-edit? (not editing?')) + (when-not editing?' (st/emit! (wdt/show-token-set-context-menu {:position (dom/get-client-position event) - :prefixed-set-path tree-path}))))) + :group? true + :path tree-path}))))) + on-collapse-click (mf/use-fn (fn [event] (dom/stop-propagation event) - (swap! collapsed? not))) + (on-toggle-collapse tree-path))) on-double-click (mf/use-fn - (mf/deps tree-path can-edit?) - (fn [] - (when can-edit? - (on-edit tree-path)))) + (mf/deps editing-id can-edit?) + #(on-edit editing-id)) on-checkbox-click (mf/use-fn (mf/deps on-toggle tree-path can-edit?) - (fn [] - (when can-edit? - (on-toggle tree-path)))) + #(on-toggle tree-path)) on-edit-submit' (mf/use-fn (mf/deps tree-path on-edit-submit can-edit?) - (fn [e] - (when can-edit? (on-edit-submit tree-path e))))] - [:div {:role "button" + #(on-edit-submit tree-path %)) + + on-drop + (mf/use-fn + (mf/deps tree-index collapsed-paths) + (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)))))) + + [dprops dref] + (h/use-sortable + :data-type "penpot/token-set" + :on-drop on-drop + :data {:index tree-index + :group? true} + :detect-center? true + :draggable? draggable?)] + + [:div {:ref dref + :role "button" :data-testid "tokens-set-group-item" :style {"--tree-depth" tree-depth} :class (stl/css-case :set-item-container true :set-item-group true - :selected-set selected?) + :selected-set selected? + :dnd-over (= (:over dprops) :center) + :dnd-over-top (= (:over dprops) :top) + :dnd-over-bot (= (:over dprops) :bot)) :on-context-menu on-context-menu} [:> icon-button* {: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 collapsed? "arrow-right" "arrow-down") :variant "action"}] (if editing?' [:& editing-label @@ -178,18 +200,20 @@ :arial-label (tr "workspace.token.select-set")}]])])) (mf/defc sets-tree-set - [{:keys [set label tree-depth tree-path selected? on-select active? on-toggle editing? on-edit on-edit-reset on-edit-submit]}] - (let [set-name (.-name set) - editing?' (editing? tree-path) - active?' (some? (active? set-name)) + [{: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)) + on-click (mf/use-fn (mf/deps editing?' tree-path) (fn [event] (dom/stop-propagation event) (when-not editing?' - (on-select tree-path)))) + (on-select set-name)))) + on-context-menu (mf/use-fn (mf/deps editing?' tree-path can-edit?) @@ -200,23 +224,64 @@ (st/emit! (wdt/show-token-set-context-menu {:position (dom/get-client-position event) - :prefixed-set-path tree-path}))))) - on-double-click (mf/use-fn - (mf/deps tree-path) - #(on-edit tree-path)) - on-checkbox-click (mf/use-fn - (mf/deps set-name) - (fn [event] - (dom/stop-propagation event) - (on-toggle set-name))) - on-edit-submit' (mf/use-fn - (mf/deps set on-edit-submit) - #(on-edit-submit set-name (ctob/update-name set %)))] - [:div {:role "button" + :group? false + :path tree-path}))))) + + on-double-click + (mf/use-fn + (mf/deps editing-id) + (fn [] + (on-edit editing-id))) + + on-checkbox-click + (mf/use-fn + (mf/deps set-name) + (fn [event] + (dom/stop-propagation event) + (on-toggle set-name))) + + on-edit-submit' + (mf/use-fn + (mf/deps set on-edit-submit) + #(on-edit-submit set-name (ctob/update-name set %))) + + on-drag + (mf/use-fn + (mf/deps tree-path) + (fn [_] + (when-not selected? + (on-select tree-path)))) + + on-drop + (mf/use-fn + (mf/deps tree-index collapsed-paths) + (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)))))) + + [dprops dref] + (h/use-sortable + :data-type "penpot/token-set" + :on-drag on-drag + :on-drop on-drop + :data {:index tree-index + :group? false} + :draggable? draggable?)] + + [: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?) + :selected-set selected? + :dnd-over (= (:over dprops) :center) + :dnd-over-top (= (:over dprops) :top) + :dnd-over-bot (= (:over dprops) :bot)) :on-click on-click :on-context-menu on-context-menu :aria-checked active?'} @@ -241,82 +306,75 @@ :checked active?'}]])])) (mf/defc sets-tree - [{:keys [active? + [{:keys [draggable? + active? + selected? group-active? editing? - on-edit on-edit-reset on-edit-submit-set on-edit-submit-group on-select on-toggle-set on-toggle-set-group - selected? - set-node - set-path - tree-depth - tree-path] - :or {tree-depth 0} + set-node] :as props}] - (let [[set-path-prefix set-fname] (some-> set-path (ctob/split-set-str-path-prefix)) - set? (instance? ctob/TokenSet set-node) - set-group? (= ctob/set-group-prefix set-path-prefix) - root? (= tree-depth 0) - collapsed? (mf/use-state false) - children? (and - (or root? set-group?) - (not @collapsed?))] - [:* - (cond - root? nil - set? - [:& sets-tree-set - {:set set-node - :active? active? - :selected? (selected? tree-path) - :on-select on-select - :label set-fname - :tree-path (or tree-path set-path) - :tree-depth tree-depth - :editing? editing? - :on-toggle on-toggle-set - :on-edit on-edit - :on-edit-reset on-edit-reset - :on-edit-submit on-edit-submit-set}] - set-group? - [:& sets-tree-set-group - {:selected? (selected? tree-path) - :active? group-active? - :on-select on-select - :label set-fname - :collapsed? collapsed? - :tree-path (or tree-path set-path) - :tree-depth tree-depth - :editing? editing? - :on-toggle on-toggle-set-group - :on-edit on-edit - :on-edit-reset on-edit-reset - :on-edit-submit on-edit-submit-group}]) - (when children? - (for [[set-path set-node] set-node - :let [tree-path' (ctob/join-set-path-str tree-path set-path)]] - [:& sets-tree - {:key tree-path' - :set-path set-path - :set-node set-node - :tree-depth (when-not root? (inc tree-depth)) - :tree-path tree-path' - :on-select on-select - :selected? selected? - :on-toggle-set on-toggle-set - :on-toggle-set-group on-toggle-set-group - :active? active? - :group-active? group-active? - :editing? editing? - :on-edit on-edit - :on-edit-reset on-edit-reset - :on-edit-submit-set on-edit-submit-set - :on-edit-submit-group on-update-token-set-group}]))])) + (let [{:keys [on-edit] :as ctx} (sets-context/use-context) + collapsed-paths (mf/use-state #{}) + collapsed? + (mf/use-fn + #(contains? @collapsed-paths %)) + + on-toggle-collapse + (mf/use-fn + (fn [path] + (swap! collapsed-paths #(if (contains? % path) + (disj % path) + (conj % path)))))] + (for [[index {:keys [group? path parent-path depth] :as node}] + (d/enumerate (ctob/walk-sets-tree-seq set-node :walk-children? #(contains? @collapsed-paths %)))] + (if (not group?) + (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}]) + (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}]))))) (mf/defc controlled-sets-list [{:keys [token-sets @@ -332,19 +390,23 @@ on-select context] :as _props}] - (let [{:keys [editing? new? on-edit on-reset] :as ctx} (or context (sets-context/use-context))] + (let [{:keys [editing? new? on-edit on-reset] :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?)] [:fieldset {:class (stl/css :sets-list)} - (if (and (= origin "theme-modal") + (if (and theme-modal? (empty? token-sets)) [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} (tr "workspace.token.no-sets-create")] - (if (and (= origin "theme-modal") + (if (and theme-modal? (empty? token-sets)) [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} (tr "workspace.token.no-sets-create")] [:* [:& sets-tree - {:set-node token-sets + {:draggable? draggable? + :set-node token-sets :selected? token-set-selected? :on-select on-select :active? token-set-active? @@ -368,22 +430,23 @@ :on-edit-reset on-reset :on-edit-submit on-create-token-set}])]))])) + (mf/defc sets-list [{:keys []}] (let [token-sets (mf/deref refs/workspace-token-sets-tree) - selected-token-set-path (mf/deref refs/workspace-selected-token-set-path) + 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-path) - (fn [tree-path] - (= tree-path selected-token-set-path))) + (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 [prefixed-path] - @(refs/token-sets-at-path-all-active prefixed-path)))] + (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/sets_context.cljs b/frontend/src/app/main/ui/workspace/tokens/sets_context.cljs index d2be84e0b..9f01ae24a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets_context.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets_context.cljs @@ -6,8 +6,15 @@ (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 {:editing-id nil :new? false}) @@ -35,7 +42,8 @@ (mf/deps editing-id) #(= editing-id %)) on-edit (mf/use-fn - #(swap! ctx assoc :editing-id %)) + (fn [editing-id] + (reset! ctx (assoc @ctx :editing-id editing-id)))) on-create (mf/use-fn #(swap! ctx assoc :editing-id (random-uuid) :new? true)) on-reset (mf/use-fn 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 a00570b1a..d0bd660bd 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,7 +7,6 @@ (ns app.main.ui.workspace.tokens.sets-context-menu (:require-macros [app.main.style :as stl]) (:require - [app.common.types.tokens-lib :as ctob] [app.main.data.tokens :as wdt] [app.main.refs :as refs] [app.main.store :as st] @@ -35,13 +34,20 @@ [:span {:class (stl/css :title)} title]]) (mf/defc menu - [{:keys [prefixed-set-path]}] + [{:keys [group? path]}] (let [{:keys [on-edit]} (sets-context/use-context) - edit-name (mf/use-fn #(on-edit prefixed-set-path)) - delete-set (mf/use-fn #(st/emit! (wdt/delete-token-set-path prefixed-set-path)))] + edit-name (mf/use-fn + (mf/deps group?) + (fn [] + (let [path (if 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)))] [:ul {:class (stl/css :context-list)} - (when (ctob/prefixed-set-path-final-group? prefixed-set-path) - [:& menu-entry {:title "Add set to this group" :on-click js/console.log}]) + ;; TODO Implement + ;; (when (ctob/prefixed-set-path-final-group? prefixed-set-path) + ;; [:& menu-entry {:title "Add set to this group" :on-click js/console.log}]) [:& menu-entry {:title (tr "labels.rename") :on-click edit-name}] [:& menu-entry {:title (tr "labels.delete") :on-click delete-set}]])) @@ -63,4 +69,5 @@ :ref dropdown-ref :style {:top top :left left} :on-context-menu prevent-default} - [:& menu {:prefixed-set-path (:prefixed-set-path mdata)}]]])) + [:& menu {:group? (:group? mdata) + :path (:path mdata)}]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index dfd940c80..f212ddaff 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.types.tokens-lib :as ctob] [app.main.data.event :as ev] [app.main.data.modal :as modal] @@ -269,7 +268,7 @@ selected-token-set-tokens (mf/deref refs/workspace-selected-token-set-tokens) - selected-token-set-path (mf/deref refs/workspace-selected-token-set-path) + selected-token-set-name (mf/deref refs/workspace-selected-token-set-name) token-groups (mf/with-memo [tokens selected-token-set-tokens] (-> (select-keys tokens (keys selected-token-set-tokens)) @@ -277,7 +276,7 @@ [:* [:& token-context-menu] [:& title-bar {:all-clickable true - :title (dm/str "TOKENS - " (ctob/set-path->set-name selected-token-set-path))}] + :title (tr "workspace.token.tokens-section-title" selected-token-set-name)}] (for [{:keys [token-key token-type-props tokens]} (concat (:filled token-groups) (:empty token-groups))] diff --git a/frontend/src/app/main/ui/workspace/tokens/token_set.cljs b/frontend/src/app/main/ui/workspace/tokens/token_set.cljs index 22752a848..37717e058 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_set.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_set.cljs @@ -43,38 +43,3 @@ (defn get-active-theme-sets-tokens-names-map [state] (when-let [lib (get-workspace-tokens-lib state)] (ctob/get-active-themes-set-tokens lib))) - -;; === Set selection - -(defn get-selected-token-set-path [state] - (or (get-in state [:workspace-local :selected-token-set-path]) - (some-> (get-workspace-tokens-lib state) - (ctob/get-sets) - (first) - (ctob/get-set-prefixed-path-string)))) - -(defn get-selected-token-set-node [state] - (when-let [path (some-> (get-selected-token-set-path state) - (ctob/split-token-set-path))] - (some-> (get-workspace-tokens-lib state) - (ctob/get-in-set-tree path)))) - -(defn get-selected-token-set [state] - (let [set-node (get-selected-token-set-node state)] - (when (instance? ctob/TokenSet set-node) - set-node))) - -(defn get-selected-token-set-group [state] - (let [set-node (get-selected-token-set-node state)] - (when (and set-node (not (instance? ctob/TokenSet set-node))) - set-node))) - -(defn get-selected-token-set-tokens [state] - (some-> (get-selected-token-set state) - :tokens)) - -(defn token-group-selected? [state] - (some? (get-selected-token-set-group state))) - -(defn assoc-selected-token-set-path [state id] - (assoc-in state [:workspace-local :selected-token-set-path] id)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index b5e6017ab..f59b61fe5 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1200,6 +1200,14 @@ msgstr "An unexpected error occurred." msgid "errors.unexpected-token" msgstr "Unknown token" +#: src/app/main/data/tokens.cljs +msgid "errors.drag-drop.set-exists" +msgstr "Cannot complete drop, a set with same name already exists at path %s." + +#: src/app/main/data/tokens.cljs +msgid "errors.drag-drop.parent-to-child" +msgstr "Cannot drop a parent set to an own child path." + #, unused msgid "errors.validation" msgstr "Validation Error" @@ -6717,6 +6725,10 @@ msgstr "Description" msgid "workspace.token.enter-token-description" msgstr "Add a description (optional)" +#: src/app/main/ui/workspace/tokens/sidebar.cljs +msgid "workspace.token.tokens-section-title" +msgstr "TOKENS - %s" + #: src/app/main/ui/workspace/tokens/sidebar.cljs msgid "workspace.token.original-value" msgstr "Original value: %s"