From 9318c10172293ff7b0465ed451a3fc5dfae20243 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 4 Dec 2024 16:21:09 +0100 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20Add=20function=20to=20compute?= =?UTF-8?q?=20active=20state=20of=20nested=20sets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/src/app/common/types/tokens_lib.cljc | 17 +++++++++++ .../common_tests/types/tokens_lib_test.cljc | 29 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 1df9c698ce..54dea76162 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -625,6 +625,11 @@ When `before-set-name` is nil, move set to bottom") (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`. +Will return a value that matches this schema: +`:none` None of the nested sets are active +`:all` All of the nested sets are active +`:partial` Mixed active state of nested sets") (get-active-themes-set-tokens [_] "set of set names that are active in the the active themes") (encode-dtcg [_] "Encodes library to a dtcg compatible json string") (decode-dtcg-json [_ parsed-json] "Decodes parsed json containing tokens and converts to library") @@ -870,6 +875,18 @@ When `before-set-name` is nil, move set to bottom") (mapcat :sets) (get-active-themes this))) + (sets-at-path-all-active? [this prefix-path] + (let [active-set-names (get-active-themes-set-names this)] + (if (seq active-set-names) + (let [path-active-set-names (->> (get-sets-at-prefix-path this prefix-path) + (map :name) + (into #{})) + difference (set/difference path-active-set-names active-set-names)] + (if (empty? difference) + :all + difference)) + :none))) + (get-active-themes-set-tokens [this] (let [sets-order (get-ordered-set-names this) active-themes (get-active-themes this) diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index 89148a4e53..c9dff8dd7b 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -399,8 +399,35 @@ expected-tokens (ctob/get-active-themes-set-tokens tokens-lib) expected-token-names (mapv key expected-tokens)] (t/is (= '("set-a" "set-b" "inactive-set") expected-order)) - (t/is (= ["set-a-token" "set-b-token"] expected-token-names))))) + (t/is (= ["set-a-token" "set-b-token"] expected-token-names)))) + (t/testing "sets-at-path-active-state" + (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 "foo/bar/bam")) + + (ctob/add-theme (ctob/make-token-theme :name "none")) + (ctob/add-theme (ctob/make-token-theme :name "partial" + :sets #{"foo/bar/baz"})) + (ctob/add-theme (ctob/make-token-theme :name "all" + :sets #{"foo/bar/baz" + "foo/bar/bam"}))) + + expected-none (-> tokens-lib + (ctob/set-active-themes #{"/none"}) + (ctob/sets-at-path-all-active? "G-foo")) + expected-all (-> tokens-lib + (ctob/set-active-themes #{"/all"}) + (ctob/sets-at-path-all-active? "G-foo")) + expected-partial (-> tokens-lib + (ctob/set-active-themes #{"/partial"}) + (ctob/sets-at-path-all-active? "G-foo"))] + (t/is (= :none expected-none)) + (t/is (= :all expected-all)) + (t/is (= #{"foo/bar/baz"} expected-partial)) + expected-partial)) + nil) (t/deftest token-theme-in-a-lib (t/testing "add-token-theme" From 07e3f581d34eba3657cbfcc5a72c7e59810dd067 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 4 Dec 2024 16:58:43 +0100 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8=20Display=20active=20state=20of?= =?UTF-8?q?=20children=20checkmark=20next=20to=20set=20groups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/src/app/common/types/tokens_lib.cljc | 6 +- .../common_tests/types/tokens_lib_test.cljc | 4 +- frontend/src/app/main/refs.cljs | 8 +++ .../app/main/ui/workspace/tokens/sets.cljs | 63 ++++++++++++++----- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 54dea76162..7495ccac25 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -875,16 +875,16 @@ Will return a value that matches this schema: (mapcat :sets) (get-active-themes this))) - (sets-at-path-all-active? [this prefix-path] + (sets-at-path-all-active? [this prefixed-path] (let [active-set-names (get-active-themes-set-names this)] (if (seq active-set-names) - (let [path-active-set-names (->> (get-sets-at-prefix-path this prefix-path) + (let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path) (map :name) (into #{})) difference (set/difference path-active-set-names active-set-names)] (if (empty? difference) :all - difference)) + :partial)) :none))) (get-active-themes-set-tokens [this] diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index c9dff8dd7b..96f1247725 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -425,9 +425,7 @@ (ctob/sets-at-path-all-active? "G-foo"))] (t/is (= :none expected-none)) (t/is (= :all expected-all)) - (t/is (= #{"foo/bar/baz"} expected-partial)) - expected-partial)) - nil) + (t/is (= :partial expected-partial))))) (t/deftest token-theme-in-a-lib (t/testing "add-token-theme" diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 16ebf7a767..dce5e74632 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -502,6 +502,14 @@ (def workspace-active-theme-paths (l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) +(defn token-sets-at-path-all-active? + [prefixed-path] + (l/derived + (fn [lib] + (when lib + (ctob/sets-at-path-all-active? lib prefixed-path))) + tokens-lib)) + (def workspace-active-theme-paths-no-hidden (l/derived #(disj % ctob/hidden-token-theme-path) workspace-active-theme-paths)) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 145cd2f957..aa8e72d0c6 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -59,9 +59,22 @@ :auto-focus true :default-value default-value}])) +(mf/defc checkbox + [{:keys [on-click active? aria-label icon]}] + [:button {:type "button" + :on-click on-click + :class (stl/css-case :checkbox-style true + :checkbox-checked-style active?)} + (when active? + [:> icon* {:aria-label aria-label + :class (stl/css :check-icon) + :size "s" + :id (or icon ic/tick)}])]) + (mf/defc sets-tree-set-group - [{:keys [label tree-depth tree-path selected? collapsed? editing? on-edit on-edit-reset on-edit-submit]}] + [{:keys [active? label tree-depth tree-path selected? collapsed? editing? on-edit on-edit-reset on-edit-submit]}] (let [editing?' (editing? tree-path) + active?' (active? tree-path) on-context-menu (mf/use-fn (mf/deps editing? tree-path) @@ -97,9 +110,14 @@ :on-create on-edit-reset ;; TODO Implement set group renaming :on-submit (constantly nil)}] - [:div {:class (stl/css :set-name) - :on-double-click #(on-edit tree-path)} - label])])) + [:* + [:div {:class (stl/css :set-name) + :on-double-click #(on-edit tree-path)} + label] + [:& checkbox + {:active? (not= :none active?') + :icon (when (= active?' :partial) ic/remove) + :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]}] @@ -150,18 +168,25 @@ [:div {:class (stl/css :set-name) :on-double-click #(on-edit tree-path)} label] - [:button {:type "button" - :on-click on-checkbox-click - :class (stl/css-case :checkbox-style true - :checkbox-checked-style active?')} - (when active?' - [:> icon* {:aria-label (tr "workspace.token.select-set") - :class (stl/css :check-icon) - :size "s" - :id ic/tick}])]])])) + [:& checkbox + {:on-click on-checkbox-click + :arial-label (tr "workspace.token.select-set") + :active? active?'}]])])) (mf/defc sets-tree - [{:keys [set-path set-node tree-depth tree-path on-select selected? on-toggle active? editing? on-edit on-edit-reset on-edit-submit] + [{:keys [active? + group-active? + editing? + on-edit + on-edit-reset + on-edit-submit + on-select + on-toggle + selected? + set-node + set-path + tree-depth + tree-path] :or {tree-depth 0} :as props}] (let [[set-path-prefix set-fname] (some-> set-path (ctob/split-set-str-path-prefix)) @@ -192,6 +217,7 @@ set-group? [:& sets-tree-set-group {:selected? (selected? tree-path) + :active? group-active? :on-select on-select :label set-fname :collapsed? collapsed? @@ -214,6 +240,7 @@ :selected? selected? :on-toggle on-toggle :active? active? + :group-active? group-active? :editing? editing? :on-edit on-edit :on-edit-reset on-edit-reset @@ -224,6 +251,7 @@ on-update-token-set token-set-selected? token-set-active? + token-set-group-active? on-create-token-set on-toggle-token-set origin @@ -247,6 +275,7 @@ :selected? token-set-selected? :on-select on-select :active? token-set-active? + :group-active? token-set-group-active? :on-toggle on-toggle-token-set :editing? editing? :on-edit on-edit @@ -276,11 +305,15 @@ token-set-active? (mf/use-fn (mf/deps active-token-set-names) (fn [set-name] - (get active-token-set-names 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)))] [:& 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? :on-select on-select-token-set-click :origin "set-panel" :on-toggle-token-set on-toggle-token-set-click From 8b569005e1de544d422d80fffceefd345b09e8bf Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 4 Dec 2024 17:16:35 +0100 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8=20Display=20active=20state=20of?= =?UTF-8?q?=20children=20checkmark=20next=20to=20set=20groups=20in=20theme?= =?UTF-8?q?s=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/workspace/tokens/modals/themes.cljs | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) 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 476156b618..f964533ef3 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -254,28 +254,22 @@ [{:keys [state set-state]}] (let [{:keys [theme-path]} @state [_ theme-group theme-name] theme-path + ordered-token-sets (mf/deref refs/workspace-ordered-token-sets) token-sets (mf/deref refs/workspace-token-sets-tree) theme (mf/deref (refs/workspace-token-theme theme-group theme-name)) + theme-state (mf/use-state theme) + lib (-> (ctob/make-tokens-lib) + (ctob/add-theme @theme-state) + (ctob/add-sets ordered-token-sets) + (ctob/activate-theme (:group @theme-state) (:name @theme-state))) + + ;; Form / Modal handlers on-back #(set-state (constantly {:type :themes-overview})) on-submit #(st/emit! (wdt/update-token-theme [(:group theme) (:name theme)] %)) {:keys [dropdown-open? _on-open-dropdown on-close-dropdown on-toggle-dropdown]} (wtco/use-dropdown-open-state) - theme-state (mf/use-state theme) disabled? (-> (:name @theme-state) (str/trim) (str/empty?)) - token-set-active? (mf/use-callback - (mf/deps theme-state) - (fn [set-name] - (get-in @theme-state [:sets set-name]))) - on-toggle-token-set (mf/use-callback - (mf/deps theme-state) - (fn [set-name] - (swap! theme-state #(ctob/toggle-set % set-name)))) - on-click-token-set (mf/use-callback - (mf/deps on-toggle-token-set) - (fn [prefixed-set-path-str] - (let [set-name (ctob/prefixed-set-path-string->set-name-string prefixed-set-path-str)] - (on-toggle-token-set set-name)))) on-change-field (fn [field value] (swap! theme-state #(assoc % field value))) on-save-form (mf/use-callback @@ -294,13 +288,31 @@ (fn [e] (dom/prevent-default e) (st/emit! (modal/hide)))) - on-delete-token (mf/use-fn (mf/deps theme on-back) (fn [] (st/emit! (wdt/delete-token-theme (:group theme) (:name theme))) - (on-back)))] + (on-back))) + + ;; Sets tree handlers + token-set-group-active? (mf/use-callback + (mf/deps theme-state) + (fn [prefixed-path] + (ctob/sets-at-path-all-active? lib prefixed-path))) + token-set-active? (mf/use-callback + (mf/deps theme-state) + (fn [set-name] + (get-in @theme-state [:sets set-name]))) + on-toggle-token-set (mf/use-callback + (mf/deps theme-state) + (fn [set-name] + (swap! theme-state #(ctob/toggle-set % set-name)))) + on-click-token-set (mf/use-callback + (mf/deps on-toggle-token-set) + (fn [prefixed-set-path-str] + (let [set-name (ctob/prefixed-set-path-string->set-name-string prefixed-set-path-str)] + (on-toggle-token-set set-name))))] [:div {:class (stl/css :themes-modal-wrapper)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)} @@ -327,6 +339,7 @@ {:token-sets token-sets :token-set-selected? (constantly false) :token-set-active? token-set-active? + :token-set-group-active? token-set-group-active? :on-select on-click-token-set :on-toggle-token-set on-toggle-token-set :origin "theme-modal" From 09e5d8883595f15cf23d04036df17dd0892a388d Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 4 Dec 2024 17:36:58 +0100 Subject: [PATCH 04/13] =?UTF-8?q?=E2=99=BB=20Cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/workspace/tokens/modals/themes.cljs | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) 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 f964533ef3..a3ff1a8b15 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals/themes.cljs @@ -270,24 +270,31 @@ disabled? (-> (:name @theme-state) (str/trim) (str/empty?)) - on-change-field (fn [field value] - (swap! theme-state #(assoc % field value))) - on-save-form (mf/use-callback - (mf/deps theme-state on-submit) - (fn [e] - (dom/prevent-default e) - (let [theme (-> @theme-state - (update :name str/trim) - (update :group str/trim) - (update :description str/trim))] - (when-not (str/empty? (:name theme)) - (on-submit theme))) - (on-back))) + + on-change-field + (mf/use-fn + (fn [field value] + (swap! theme-state #(assoc % field value)))) + + on-save-form + (mf/use-callback + (mf/deps theme-state on-submit) + (fn [e] + (dom/prevent-default e) + (let [theme (-> @theme-state + (update :name str/trim) + (update :group str/trim) + (update :description str/trim))] + (when-not (str/empty? (:name theme)) + (on-submit theme))) + (on-back))) + close-modal (mf/use-fn (fn [e] (dom/prevent-default e) (st/emit! (modal/hide)))) + on-delete-token (mf/use-fn (mf/deps theme on-back) @@ -296,23 +303,30 @@ (on-back))) ;; Sets tree handlers - token-set-group-active? (mf/use-callback - (mf/deps theme-state) - (fn [prefixed-path] - (ctob/sets-at-path-all-active? lib prefixed-path))) - token-set-active? (mf/use-callback - (mf/deps theme-state) - (fn [set-name] - (get-in @theme-state [:sets set-name]))) - on-toggle-token-set (mf/use-callback - (mf/deps theme-state) - (fn [set-name] - (swap! theme-state #(ctob/toggle-set % set-name)))) - on-click-token-set (mf/use-callback - (mf/deps on-toggle-token-set) - (fn [prefixed-set-path-str] - (let [set-name (ctob/prefixed-set-path-string->set-name-string prefixed-set-path-str)] - (on-toggle-token-set set-name))))] + token-set-group-active? + (mf/use-callback + (mf/deps theme-state) + (fn [prefixed-path] + (ctob/sets-at-path-all-active? lib prefixed-path))) + + token-set-active? + (mf/use-callback + (mf/deps theme-state) + (fn [set-name] + (get-in @theme-state [:sets set-name]))) + + on-toggle-token-set + (mf/use-callback + (mf/deps theme-state) + (fn [set-name] + (swap! theme-state #(ctob/toggle-set % set-name)))) + + on-click-token-set + (mf/use-callback + (mf/deps on-toggle-token-set) + (fn [prefixed-set-path-str] + (let [set-name (ctob/prefixed-set-path-string->set-name-string prefixed-set-path-str)] + (on-toggle-token-set set-name))))] [:div {:class (stl/css :themes-modal-wrapper)} [:> heading* {:level 2 :typography "headline-medium" :class (stl/css :themes-modal-title)} From 5ff3469da70657e245a832fef293949c748b21c8 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 10 Dec 2024 14:04:11 +0100 Subject: [PATCH 05/13] =?UTF-8?q?=E2=99=BB=20Accessible=20checkbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/main/ui/workspace/tokens/sets.cljs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index aa8e72d0c6..379ee4b3b6 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -60,16 +60,22 @@ :default-value default-value}])) (mf/defc checkbox - [{:keys [on-click active? aria-label icon]}] - [:button {:type "button" - :on-click on-click - :class (stl/css-case :checkbox-style true - :checkbox-checked-style active?)} - (when active? - [:> icon* {:aria-label aria-label - :class (stl/css :check-icon) - :size "s" - :id (or icon ic/tick)}])]) + [{:keys [checked aria-label on-click]}] + (let [all? (true? checked) + mixed? (= checked "mixed") + checked? (or all? mixed?)] + [:div {:role "checkbox" + :aria-checked (str checked) + :tabindex 0 + :class (stl/css-case :checkbox-style true + :checkbox-checked-style checked?) + :on-click on-click} + (when checked? + [:> icon* + {:aria-label aria-label + :class (stl/css :check-icon) + :size "s" + :id (if mixed? ic/remove ic/tick)}])])) (mf/defc sets-tree-set-group [{:keys [active? label tree-depth tree-path selected? collapsed? editing? on-edit on-edit-reset on-edit-submit]}] @@ -115,8 +121,10 @@ :on-double-click #(on-edit tree-path)} label] [:& checkbox - {:active? (not= :none active?') - :icon (when (= active?' :partial) ic/remove) + {:checked (case active?' + :all true + :partial "mixed" + :none false) :arial-label (tr "workspace.token.select-set")}]])])) (mf/defc sets-tree-set @@ -171,7 +179,7 @@ [:& checkbox {:on-click on-checkbox-click :arial-label (tr "workspace.token.select-set") - :active? active?'}]])])) + :checked active?'}]])])) (mf/defc sets-tree [{:keys [active? @@ -259,10 +267,9 @@ context] :as _props}] (let [{:keys [editing? new? on-edit on-reset] :as ctx} (or context (sets-context/use-context))] - [:ul {:class (stl/css :sets-list)} - (if (and - (= origin "theme-modal") - (empty? token-sets)) + [:fieldset {:class (stl/css :sets-list)} + (if (and (= origin "theme-modal") + (empty? token-sets)) [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message-sets)} (tr "workspace.token.no-sets-create")] (if (and (= origin "theme-modal") From aa292e4829d77f72cd5c9a04f05b844700b92ff6 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 10 Dec 2024 14:04:32 +0100 Subject: [PATCH 06/13] =?UTF-8?q?=F0=9F=90=9B=20Fix=20missing=20active=20s?= =?UTF-8?q?ets=20in=20set=20groups=20showing=20partial=20selection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/src/app/common/types/tokens_lib.cljc | 7 ++++--- common/test/common_tests/types/tokens_lib_test.cljc | 12 +++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 7495ccac25..484d9c81df 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -882,9 +882,10 @@ Will return a value that matches this schema: (map :name) (into #{})) difference (set/difference path-active-set-names active-set-names)] - (if (empty? difference) - :all - :partial)) + (cond + (empty? difference) :all + (seq (set/intersection path-active-set-names active-set-names)) :partial + :else :none)) :none))) (get-active-themes-set-tokens [this] diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index 96f1247725..f11829d98e 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -412,7 +412,9 @@ :sets #{"foo/bar/baz"})) (ctob/add-theme (ctob/make-token-theme :name "all" :sets #{"foo/bar/baz" - "foo/bar/bam"}))) + "foo/bar/bam"})) + (ctob/add-theme (ctob/make-token-theme :name "invalid" + :sets #{"foo/missing"}))) expected-none (-> tokens-lib (ctob/set-active-themes #{"/none"}) @@ -422,10 +424,14 @@ (ctob/sets-at-path-all-active? "G-foo")) expected-partial (-> tokens-lib (ctob/set-active-themes #{"/partial"}) - (ctob/sets-at-path-all-active? "G-foo"))] + (ctob/sets-at-path-all-active? "G-foo")) + expected-invalid-none (-> tokens-lib + (ctob/set-active-themes #{"/invalid"}) + (ctob/sets-at-path-all-active? "G-foo"))] (t/is (= :none expected-none)) (t/is (= :all expected-all)) - (t/is (= :partial expected-partial))))) + (t/is (= :partial expected-partial)) + (t/is (= :none expected-invalid-none))))) (t/deftest token-theme-in-a-lib (t/testing "add-token-theme" From 6077ba6690819ab8e178c61fd6e113a8569bd3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 19 Nov 2024 16:14:02 +0100 Subject: [PATCH 07/13] :sparkles: Synchronize tokens in components --- common/src/app/common/files/changes.cljc | 2 +- common/src/app/common/logic/libraries.cljc | 94 +++++++++++++++++----- common/src/app/common/types/component.cljc | 8 ++ common/src/app/common/types/token.cljc | 11 +++ 4 files changed, 92 insertions(+), 23 deletions(-) diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 45195c48d3..50411a0144 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -1153,7 +1153,7 @@ ; We need to trigger a sync if the shape has changed any ; attribute that participates in components synchronization. (and (= (:type operation) :set) - (get ctk/sync-attrs (:attr operation)))) + (ctk/component-attr? (:attr operation)))) any-sync? (some need-sync? operations)] (when any-sync? (if page-id diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index bc9ab68b02..98db438160 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -27,6 +27,7 @@ [app.common.types.shape-tree :as ctst] [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctl] + [app.common.types.token :as cto] [app.common.types.typography :as cty] [app.common.uuid :as uuid] [clojure.set :as set] @@ -1479,6 +1480,44 @@ [{:type :set-remote-synced :remote-synced (:remote-synced shape)}]})))))) +(defn- update-tokens + "Token synchronization algorithm. Copy the applied tokens that have changed + in the origin shape to the dest shape (applying or removing as necessary). + + Only the given token attributes are synced." + [changes container dest-shape orig-shape token-attrs] + (let [orig-tokens (get orig-shape :applied-tokens {}) + dest-tokens (get dest-shape :applied-tokens {}) + dest-tokens' (reduce (fn [dest-tokens' token-attr] + (let [orig-token (get orig-tokens token-attr) + dest-token (get dest-tokens token-attr)] + (if (= orig-token dest-token) + dest-tokens' + (if (nil? orig-token) + (dissoc dest-tokens' token-attr) + (assoc dest-tokens' token-attr orig-token))))) + dest-tokens + token-attrs)] + (if (= dest-tokens dest-tokens') + changes + (-> changes + (update :redo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations [{:type :set + :attr :applied-tokens + :val dest-tokens' + :ignore-touched true}]})) + (update :undo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations [{:type :set + :attr :applied-tokens + :val dest-tokens + :ignore-touched true}]})))))) + (defn- update-attrs "The main function that implements the attribute sync algorithm. Copy attributes that have changed in the origin shape to the dest shape. @@ -1511,37 +1550,41 @@ (loop [attrs (->> (seq (keys ctk/sync-attrs)) ;; We don't update the flex-child attrs (remove ctk/swap-keep-attrs) - ;; We don't do automatic update of the `layout-grid-cells` property. (remove #(= :layout-grid-cells %))) + applied-tokens #{} roperations [] uoperations '()] (let [attr (first attrs)] (if (nil? attr) - (if (empty? roperations) + (if (and (empty? roperations) (empty? applied-tokens)) changes (let [all-parents (cfh/get-parent-ids (:objects container) (:id dest-shape))] - (-> changes - (update :redo-changes conj (make-change - container - {:type :mod-obj - :id (:id dest-shape) - :operations roperations})) - (update :redo-changes conj (make-change - container - {:type :reg-objects - :shapes all-parents})) - (update :undo-changes conj (make-change - container - {:type :mod-obj - :id (:id dest-shape) - :operations (vec uoperations)})) - (update :undo-changes concat [(make-change - container - {:type :reg-objects - :shapes all-parents})])))) + (cond-> changes + (seq roperations) + (-> (update :redo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations roperations})) + (update :redo-changes conj (make-change + container + {:type :reg-objects + :shapes all-parents})) + (update :undo-changes conj (make-change + container + {:type :mod-obj + :id (:id dest-shape) + :operations (vec uoperations)})) + (update :undo-changes concat [(make-change + container + {:type :reg-objects + :shapes all-parents})])) + (seq applied-tokens) + (update-tokens container dest-shape origin-shape applied-tokens)))) + (let [;; position-data is a special case because can be affected by :geometry-group and :content-group ;; so, if the position-data changes but the geometry is touched we need to reset the position-data ;; so it's calculated again @@ -1564,14 +1607,21 @@ :val (get dest-shape attr) :ignore-touched true} - attr-group (get ctk/sync-attrs attr)] + attr-group (get ctk/sync-attrs attr) + token-attrs (cto/shape-attr->token-attrs attr) + applied-tokens' (cond-> applied-tokens + (not (and (touched attr-group) + omit-touched?)) + (into token-attrs))] (if (or (= (get origin-shape attr) (get dest-shape attr)) (and (touched attr-group) omit-touched?)) (recur (next attrs) + applied-tokens' roperations uoperations) (recur (next attrs) + applied-tokens' (conj roperations roperation) (conj uoperations uoperation))))))))) diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 2af68c8d89..fc4ea0093d 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -140,6 +140,14 @@ :layout-item-z-index :layout-item-align-self}) +(defn component-attr? + "Check if some attribute is one that is involved in component syncrhonization. + Note that design tokens also are involved, although they go by an alternate + route and thus they are not part of :sync-attrs." + [attr] + (or (get sync-attrs attr) + (= :applied-tokens attr))) + (defn instance-root? "Check if this shape is the head of a top instance." [shape] diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 1075ba461f..d97279fb66 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -163,3 +163,14 @@ ::spacing ::rotation ::dimensions]) + +(defn shape-attr->token-attrs + [shape-attr] + (cond + (= :fills shape-attr) #{:fill} + (= :strokes shape-attr) #{:stroke-color :stroke-width} + (border-radius-keys shape-attr) #{shape-attr} + (sizing-keys shape-attr) #{shape-attr} + (opacity-keys shape-attr) #{shape-attr} + (spacing-keys shape-attr) #{shape-attr} + (rotation-keys shape-attr) #{shape-attr})) From d378937a370f452f6edde4c88d5c87eefe6411bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 21 Nov 2024 16:39:43 +0100 Subject: [PATCH 08/13] :tada: Set touched groups when changing tokens in copies --- common/src/app/common/types/container.cljc | 31 ++++++++++++++++++---- common/src/app/common/types/token.cljc | 17 ++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index ca0181604b..5c5673459e 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -18,6 +18,7 @@ [app.common.types.plugins :as ctpg] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] + [app.common.types.token :as ctt] [app.common.uuid :as uuid])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -540,14 +541,28 @@ ;; --- SHAPE UPDATE +(defn- get-token-groups + [shape new-applied-tokens] + (let [old-applied-tokens (d/nilv (:applied-tokens shape) #{}) + changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %)) + ctt/all-keys) + changed-groups (into #{} + (comp (map ctt/token-attr->shape-attr) + (map #(get ctk/sync-attrs %)) + (filter some?)) + changed-token-attrs)] + changed-groups)) + (defn set-shape-attr "Assign attribute to shape with touched logic. The returned shape will contain a metadata associated with it indicating if shape is touched or not." [shape attr val & {:keys [ignore-touched ignore-geometry]}] - (let [group (get ctk/sync-attrs attr) - shape-val (get shape attr) + (let [group (get ctk/sync-attrs attr) + token-groups (when (= attr :applied-tokens) + (get-token-groups shape val)) + shape-val (get shape attr) ignore? (or ignore-touched @@ -585,9 +600,15 @@ ;; set the "touched" flag for the group the attribute belongs to. ;; In some cases we need to ignore touched only if the attribute is ;; geometric (position, width or transformation). - (and in-copy? group (not ignore?) (not equal?) - (not (and ignore-geometry is-geometry?))) - (-> (update :touched ctk/set-touched-group group) + (and in-copy? + (or (and group (not equal?)) (seq token-groups)) + (not ignore?) (not (and ignore-geometry is-geometry?))) + (-> (update :touched (fn [touched] + (reduce #(ctk/set-touched-group %1 %2) + touched + (if group + (cons group token-groups) + token-groups)))) (dissoc :remote-synced)) (nil? val) diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index d97279fb66..b1e58dfed8 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -150,6 +150,15 @@ (def rotation-keys (schema-keys ::rotation)) +(def all-keys (set/union color-keys + border-radius-keys + stroke-width-keys + sizing-keys + opacity-keys + spacing-keys + dimensions-keys + rotation-keys)) + (sm/register! ^{::sm/type ::tokens} [:map {:title "Applied Tokens"}]) @@ -174,3 +183,11 @@ (opacity-keys shape-attr) #{shape-attr} (spacing-keys shape-attr) #{shape-attr} (rotation-keys shape-attr) #{shape-attr})) + +(defn token-attr->shape-attr + [token-attr] + (case token-attr + :fill :fills + :stroke-color :strokes + :stroke-width :strokes + token-attr)) From ddec03966d96faff102c051a3ac8e68570a2fc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 26 Nov 2024 11:23:02 +0100 Subject: [PATCH 09/13] :wrench: Partial refactor to move things to common.types --- common/src/app/common/types/token.cljc | 38 +++++++++++++++++++ .../common_tests/logic/comp_sync_test.cljc | 6 +-- frontend/src/app/main/data/tokens.cljs | 35 ----------------- .../sidebar/options/menus/measures.cljs | 10 ++--- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index b1e58dfed8..65ef9ba790 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -6,8 +6,10 @@ (ns app.common.types.token (:require + [app.common.data :as d] [app.common.schema :as sm] [app.common.schema.registry :as sr] + [clojure.data :as data] [clojure.set :as set] [malli.util :as mu])) @@ -191,3 +193,39 @@ :stroke-color :strokes :stroke-width :strokes token-attr)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TOKENS IN SHAPES +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- toggle-or-apply-token + "Remove any shape attributes from token if they exists. + Othewise apply token attributes." + [shape token] + (let [[shape-leftover token-leftover _matching] (data/diff (:applied-tokens shape) token)] + (merge {} shape-leftover token-leftover))) + +(defn- token-from-attributes [token attributes] + (->> (map (fn [attr] [attr (:name token)]) attributes) + (into {}))) + +(defn- apply-token-to-attributes [{:keys [shape token attributes]}] + (let [token (token-from-attributes token attributes)] + (toggle-or-apply-token shape token))) + +(defn apply-token-to-shape + [{:keys [shape token attributes] :as _props}] + (let [applied-tokens (apply-token-to-attributes {:shape shape + :token token + :attributes attributes})] + (update shape :applied-tokens #(merge % applied-tokens)))) + +(defn maybe-apply-token-to-shape + "When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape." + [{:keys [shape token _attributes] :as props}] + (if token + (apply-token-to-shape props) + shape)) + +(defn unapply-token-id [shape attributes] + (update shape :applied-tokens d/without-keys attributes)) diff --git a/common/test/common_tests/logic/comp_sync_test.cljc b/common/test/common_tests/logic/comp_sync_test.cljc index 94f093ff38..f970f5fcbf 100644 --- a/common/test/common_tests/logic/comp_sync_test.cljc +++ b/common/test/common_tests/logic/comp_sync_test.cljc @@ -193,7 +193,6 @@ (ths/add-sample-shape :free-shape)) page (thf/current-page file) - main-root (ths/get-shape file :main-root) ;; ==== Action changes1 (cls/generate-relocate (pcb/empty-changes) @@ -203,9 +202,6 @@ 0 ; to-index #{(thi/id :free-shape)}) ; ids - - - updated-file (thf/apply-changes file changes1) changes2 (cll/generate-sync-file-changes (pcb/empty-changes) @@ -491,4 +487,4 @@ (t/is (= (:fill-color fill') "#fabada")) (t/is (= (:fill-opacity fill') 1)) (t/is (= (:touched copy2-root') nil)) - (t/is (= (:touched copy2-child') nil)))) \ No newline at end of file + (t/is (= (:touched copy2-child') nil)))) diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index 0b8aec15d7..cc94f3e1ac 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -6,7 +6,6 @@ (ns app.main.data.tokens (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.changes-builder :as pcb] [app.common.geom.point :as gpt] @@ -15,11 +14,9 @@ [app.main.data.changes :as dch] [app.main.data.workspace.shapes :as dwsh] [app.main.refs :as refs] - [app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token-set :as wtts] [app.main.ui.workspace.tokens.update :as wtu] [beicon.v2.core :as rx] - [clojure.data :as data] [cuerdas.core :as str] [potok.v2.core :as ptk])) @@ -51,38 +48,6 @@ ;; TOKENS Actions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn toggle-or-apply-token - "Remove any shape attributes from token if they exists. - Othewise apply token attributes." - [shape token] - (let [[shape-leftover token-leftover _matching] (data/diff (:applied-tokens shape) token)] - (merge {} shape-leftover token-leftover))) - -(defn token-from-attributes [token attributes] - (->> (map (fn [attr] [attr (wtt/token-identifier token)]) attributes) - (into {}))) - -(defn unapply-token-id [shape attributes] - (update shape :applied-tokens d/without-keys attributes)) - -(defn apply-token-to-attributes [{:keys [shape token attributes]}] - (let [token (token-from-attributes token attributes)] - (toggle-or-apply-token shape token))) - -(defn apply-token-to-shape - [{:keys [shape token attributes] :as _props}] - (let [applied-tokens (apply-token-to-attributes {:shape shape - :token token - :attributes attributes})] - (update shape :applied-tokens #(merge % applied-tokens)))) - -(defn maybe-apply-token-to-shape - "When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape." - [{:keys [shape token _attributes] :as props}] - (if token - (apply-token-to-shape props) - shape)) - (defn get-token-data-from-token-id [id] (let [workspace-data (deref refs/workspace-data)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 3be9489760..b56788ab53 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -13,9 +13,9 @@ [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] [app.common.types.shape.radius :as ctsr] + [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob] [app.main.constants :refer [size-presets]] - [app.main.data.tokens :as dt] [app.main.data.workspace :as udw] [app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.shapes :as dwsh] @@ -342,7 +342,7 @@ (let [token-value (wtc/maybe-resolve-token-value token)] (st/emit! (change-radius (fn [shape] - (-> (dt/unapply-token-id shape (wtty/token-attributes :border-radius)) + (-> (cto/unapply-token-id shape (wtty/token-attributes :border-radius)) (ctsr/set-radius-1 token-value)))))))) on-radius-1-change @@ -352,9 +352,9 @@ (let [token-value (wtc/maybe-resolve-token-value value)] (st/emit! (change-radius (fn [shape] - (-> (dt/maybe-apply-token-to-shape {:token (when token-value value) - :shape shape - :attributes (wtty/token-attributes :border-radius)}) + (-> (cto/maybe-apply-token-to-shape {:token (when token-value value) + :shape shape + :attributes (wtty/token-attributes :border-radius)}) (ctsr/set-radius-1 (or token-value value))))))))) on-radius-multi-change From 99c30dd44f8098f14c01bafbfcd7f8a837c862a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 26 Nov 2024 19:40:21 +0100 Subject: [PATCH 10/13] :tada: Add frontend unit tests --- .../src/app/common/test_helpers/tokens.cljc | 45 +++ .../app/main/data/workspace/libraries.cljs | 1 - .../app/main/ui/workspace/tokens/update.cljs | 8 +- .../test/frontend_tests/helpers/state.cljs | 2 +- .../logic/components_and_tokens.cljs | 286 ++++++++++++++++++ frontend/test/frontend_tests/runner.cljs | 2 + 6 files changed, 338 insertions(+), 6 deletions(-) create mode 100644 common/src/app/common/test_helpers/tokens.cljc create mode 100644 frontend/test/frontend_tests/logic/components_and_tokens.cljs diff --git a/common/src/app/common/test_helpers/tokens.cljc b/common/src/app/common/test_helpers/tokens.cljc new file mode 100644 index 0000000000..35a7d53aad --- /dev/null +++ b/common/src/app/common/test_helpers/tokens.cljc @@ -0,0 +1,45 @@ +;; 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.common.test-helpers.tokens + (:require + [app.common.test-helpers.files :as thf] + [app.common.test-helpers.shapes :as ths] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape-tree :as ctst] + [app.common.types.token :as cto] + [app.common.types.tokens-lib :as ctob])) + +(defn add-tokens-lib + [file] + (ctf/update-file-data file #(update % :tokens-lib ctob/ensure-tokens-lib))) + +(defn update-tokens-lib + [file f] + (ctf/update-file-data file #(update % :tokens-lib f))) + +(defn apply-token-to-shape + [file shape-label token-name token-attrs shape-attrs resolved-value] + (let [page (thf/current-page file) + shape (ths/get-shape file shape-label) + shape' (as-> shape $ + (cto/apply-token-to-shape {:shape $ + :token {:name token-name} + :attributes token-attrs}) + (reduce (fn [shape attr] + (ctn/set-shape-attr shape attr resolved-value {:ignore-touched true})) + $ + shape-attrs))] + + (ctf/update-file-data + file + (fn [file-data] + (ctpl/update-page file-data + (:id page) + #(ctst/set-shape % shape')))))) + diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index e04b4dfb28..f4187ab2ba 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -786,7 +786,6 @@ (rx/map #(reset-component %) (rx/from ids)) (rx/of (dwu/commit-undo-transaction undo-id))))))) - (defn update-component "Modify the component linked to the shape with the given id, in the current page, so that all attributes of its shapes are equal to the diff --git a/frontend/src/app/main/ui/workspace/tokens/update.cljs b/frontend/src/app/main/ui/workspace/tokens/update.cljs index 2d4120b01c..376e887ac1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/update.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/update.cljs @@ -2,8 +2,8 @@ (:require [app.common.types.token :as ctt] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] - [app.main.refs :as refs] [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.style-dictionary :as wtsd] [app.main.ui.workspace.tokens.token-set :as wtts] @@ -112,8 +112,8 @@ update-infos))) shapes-update-info)) -(defn update-tokens [resolved-tokens] - (->> @refs/workspace-page-objects +(defn update-tokens [state resolved-tokens] + (->> (wsh/lookup-page-objects state) (collect-shapes-update-info resolved-tokens) (actionize-shapes-update-info))) @@ -131,5 +131,5 @@ (let [undo-id (js/Symbol)] (rx/concat (rx/of (dwu/start-undo-transaction undo-id)) - (update-tokens sd-tokens) + (update-tokens state sd-tokens) (rx/of (dwu/commit-undo-transaction undo-id)))))))))) diff --git a/frontend/test/frontend_tests/helpers/state.cljs b/frontend/test/frontend_tests/helpers/state.cljs index 068a6cce9f..4027ccf294 100644 --- a/frontend/test/frontend_tests/helpers/state.cljs +++ b/frontend/test/frontend_tests/helpers/state.cljs @@ -57,7 +57,7 @@ (fn [cause] (js/console.log "[error]:" cause)) (fn [_] - (js/console.log "[complete]")))) + #_(js/console.debug "[complete]")))) (doseq [event events] (ptk/emit! store event)) diff --git a/frontend/test/frontend_tests/logic/components_and_tokens.cljs b/frontend/test/frontend_tests/logic/components_and_tokens.cljs new file mode 100644 index 0000000000..4175792f01 --- /dev/null +++ b/frontend/test/frontend_tests/logic/components_and_tokens.cljs @@ -0,0 +1,286 @@ +;; 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 frontend-tests.logic.components-and-tokens + (:require + [app.common.geom.point :as geom] + [app.common.test-helpers.components :as cthc] + [app.common.test-helpers.compositions :as ctho] + [app.common.test-helpers.files :as cthf] + [app.common.test-helpers.ids-map :as cthi] + [app.common.test-helpers.shapes :as cths] + [app.common.test-helpers.tokens :as ctht] + [app.common.types.tokens-lib :as ctob] + [app.main.data.tokens :as dt] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.ui.workspace.tokens.changes :as wtch] + [app.main.ui.workspace.tokens.update :as wtu] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.pages :as thp] + [frontend-tests.helpers.state :as ths] + [frontend-tests.tokens.helpers.state :as tohs] + [frontend-tests.tokens.helpers.tokens :as toht])) + +(t/use-fixtures :each + {:before thp/reset-idmap!}) + +(defn- setup-base-file + [] + (-> (cthf/sample-file :file1) + (ctht/add-tokens-lib) + (ctht/update-tokens-lib #(-> % + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-theme (ctob/make-token-theme :name "test-theme" + :sets #{"test-token-set"})) + (ctob/set-active-themes #{"/test-theme"}) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :border-radius + :value 25)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-2" + :type :border-radius + :value 50)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-3" + :type :border-radius + :value 75)))) + (ctho/add-frame :frame1) + (ctht/apply-token-to-shape :frame1 "test-token-1" [:rx :ry] [:rx :ry] 25))) + +(defn- setup-file-with-main + [] + (-> (setup-base-file) + (cthc/make-component :component1 :frame1))) + +(defn- setup-file-with-copy + [] + (-> (setup-file-with-main) + (cthc/instantiate-component :component1 :c-frame1))) + +(t/deftest create-component-with-token + (t/async + done + (let [;; ==== Setup + file (setup-base-file) + store (ths/setup-store file) + + ;; ==== Action + events + [(dws/select-shape (cthi/id :frame1)) + (dwl/add-component)]] + + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + frame1' (cths/get-shape file' :frame1) + tokens-frame1' (:applied-tokens frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-1")) + (t/is (= (get tokens-frame1' :ry) "test-token-1")) + (t/is (= (get frame1' :rx) 25)) + (t/is (= (get frame1' :ry) 25)))))))) + +(t/deftest create-copy-with-token + (t/async + done + (let [;; ==== Setup + file (setup-file-with-main) + store (ths/setup-store file) + + ;; ==== Action + events + [(dwl/instantiate-component (:id file) + (cthi/id :component1) + (geom/point 0 0))]] + + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + selected (wsh/lookup-selected new-state) + c-frame1' (wsh/lookup-shape new-state (first selected)) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-1")) + (t/is (= (get tokens-frame1' :ry) "test-token-1")) + (t/is (= (get c-frame1' :rx) 25)) + (t/is (= (get c-frame1' :ry) 25)))))))) + +(t/deftest change-token-in-main + (t/async + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) + + ;; ==== Action + events [(wtch/apply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-2") + :on-update-shape wtch/update-shape-radius-all})] + + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-2")) + (t/is (= (get tokens-frame1' :ry) "test-token-2")) + (t/is (= (get c-frame1' :rx) 50)) + (t/is (= (get c-frame1' :ry) 50)))))))] + + (tohs/run-store-async + store step2 events identity)))) + +(t/deftest remove-token-in-main + (t/async + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) + + ;; ==== Action + events [(wtch/unapply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-1")})] + + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 0)) + (t/is (= (get c-frame1' :rx) 25)) + (t/is (= (get c-frame1' :ry) 25)))))))] + + (tohs/run-store-async + store step2 events identity)))) + +(t/deftest modify-token + (t/async + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) + + ;; ==== Action + events [(dt/update-create-token {:token (ctob/make-token :name "test-token-1" + :type :border-radius + :value 66) + :prev-token-name "test-token-1"})] + + step2 (fn [_] + (let [events2 [(wtu/update-workspace-tokens) + (dwl/sync-file (:id file) (:id file))]] + (tohs/run-store-async + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-1")) + (t/is (= (get tokens-frame1' :ry) "test-token-1")) + (t/is (= (get c-frame1' :rx) 66)) + (t/is (= (get c-frame1' :ry) 66)))))))] + + (tohs/run-store-async + store step2 events identity)))) + +(t/deftest change-token-in-copy-then-change-main + (t/async + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) + + ;; ==== Action + events [(wtch/apply-token {:shape-ids [(cthi/id :c-frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-2") + :on-update-shape wtch/update-shape-radius-all}) + (wtch/apply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-3") + :on-update-shape wtch/update-shape-radius-all})] + + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-2")) + (t/is (= (get tokens-frame1' :ry) "test-token-2")) + (t/is (= (get c-frame1' :rx) 50)) + (t/is (= (get c-frame1' :ry) 50)))))))] + + (tohs/run-store-async + store step2 events identity)))) + +(t/deftest remove-token-in-copy-then-change-main + (t/async + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) + + ;; ==== Action + events [(wtch/unapply-token {:shape-ids [(cthi/id :c-frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-1")}) + (wtch/apply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-3") + :on-update-shape wtch/update-shape-radius-all})] + + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 0)) + (t/is (= (get c-frame1' :rx) 25)) + (t/is (= (get c-frame1' :ry) 25)))))))] + + (tohs/run-store-async + store step2 events identity)))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index a42eb72037..bca0112e19 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -4,6 +4,7 @@ [frontend-tests.basic-shapes-test] [frontend-tests.helpers-shapes-test] [frontend-tests.logic.comp-remove-swap-slots-test] + [frontend-tests.logic.components-and-tokens] [frontend-tests.logic.copying-and-duplicating-test] [frontend-tests.logic.frame-guides-test] [frontend-tests.logic.groups-test] @@ -28,6 +29,7 @@ (t/run-tests 'frontend-tests.helpers-shapes-test 'frontend-tests.logic.comp-remove-swap-slots-test + 'frontend-tests.logic.components-and-tokens 'frontend-tests.logic.copying-and-duplicating-test 'frontend-tests.logic.frame-guides-test 'frontend-tests.logic.groups-test From d51a2640bfb30bbae3ea466109c4260fa69297a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 28 Nov 2024 16:36:10 +0100 Subject: [PATCH 11/13] :bug: Avoid marking copies touched when changing token values --- .../src/app/main/data/workspace/colors.cljs | 31 ++++++------- .../app/main/data/workspace/modifiers.cljs | 7 ++- .../app/main/data/workspace/shape_layout.cljs | 12 ++--- .../src/app/main/data/workspace/texts.cljs | 4 +- .../app/main/data/workspace/transforms.cljs | 8 ++-- .../app/main/ui/workspace/tokens/changes.cljs | 45 +++++++++++-------- 6 files changed, 60 insertions(+), 47 deletions(-) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index b116324965..dafd3de4ef 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -82,7 +82,7 @@ (assoc-in [:workspace-global :picked-shift?] shift?))))) (defn transform-fill - [state ids color transform] + [state ids color transform & options] (let [objects (wsh/lookup-page-objects state) is-text? #(= :text (:type (get objects %))) @@ -118,8 +118,8 @@ (rx/concat (rx/of (dwu/start-undo-transaction undo-id)) - (rx/from (map #(dwt/update-text-with-function % transform-attrs) text-ids)) - (rx/of (dwsh/update-shapes shape-ids transform-attrs)) + (rx/from (map #(apply dwt/update-text-with-function % transform-attrs options) text-ids)) + (rx/of (dwsh/update-shapes shape-ids transform-attrs options)) (rx/of (dwu/commit-undo-transaction undo-id))))) (defn swap-attrs [shape attr index new-index] @@ -146,7 +146,7 @@ (rx/of (dwsh/update-shapes shape-ids transform-attrs))))))) (defn change-fill - [ids color position] + [ids color position & options] (ptk/reify ::change-fill ptk/WatchEvent (watch [_ state _] @@ -155,18 +155,18 @@ (cond-> (not (contains? shape :fills)) (assoc :fills [])) (assoc-in [:fills position] (into {} attrs))))] - (transform-fill state ids color change-fn))))) + (apply transform-fill state ids color change-fn options))))) (defn change-fill-and-clear - [ids color] + [ids color & options] (ptk/reify ::change-fill-and-clear ptk/WatchEvent (watch [_ state _] (let [set (fn [shape attrs] (assoc shape :fills [attrs]))] - (transform-fill state ids color set))))) + (apply transform-fill state ids color set options))))) (defn add-fill - [ids color] + [ids color & options] (dm/assert! "expected a valid color struct" @@ -182,10 +182,10 @@ (let [add (fn [shape attrs] (-> shape (update :fills #(into [attrs] %))))] - (transform-fill state ids color add))))) + (apply transform-fill state ids color add options))))) (defn remove-fill - [ids color position] + [ids color position & options] (dm/assert! "expected a valid color struct" @@ -203,10 +203,10 @@ (mapv second))) remove (fn [shape _] (update shape :fills remove-fill-by-index position))] - (transform-fill state ids color remove))))) + (apply transform-fill state ids color remove options))))) (defn remove-all-fills - [ids color] + [ids color & options] (dm/assert! "expected a valid color struct" @@ -220,7 +220,7 @@ ptk/WatchEvent (watch [_ state _] (let [remove-all (fn [shape _] (assoc shape :fills []))] - (transform-fill state ids color remove-all))))) + (apply transform-fill state ids color remove-all options))))) (defn change-hide-fill-on-export [ids hide-fill-on-export] @@ -237,7 +237,7 @@ (d/merge shape attrs) shape)))))))) (defn change-stroke - [ids attrs index] + [ids attrs index & options] (ptk/reify ::change-stroke ptk/WatchEvent (watch [_ _ _] @@ -286,7 +286,8 @@ (assoc :strokes []) :always - (assoc-in [:strokes index] new-attrs)))))))))) + (assoc-in [:strokes index] new-attrs)))) + options)))))) (defn change-shadow [ids attrs index] diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 773b4f146f..ed745892aa 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -465,8 +465,10 @@ ([] (apply-modifiers nil)) - ([{:keys [modifiers undo-transation? stack-undo? ignore-constraints ignore-snap-pixel undo-group] - :or {undo-transation? true stack-undo? false ignore-constraints false ignore-snap-pixel false}}] + ([{:keys [modifiers undo-transation? stack-undo? ignore-constraints + ignore-snap-pixel ignore-touched undo-group] + :or {undo-transation? true stack-undo? false ignore-constraints false + ignore-snap-pixel false ignore-touched false}}] (ptk/reify ::apply-modifiers ptk/WatchEvent (watch [_ state _] @@ -515,6 +517,7 @@ {:reg-objects? true :stack-undo? stack-undo? :ignore-tree ignore-tree + :ignore-touched ignore-touched :undo-group undo-group ;; Attributes that can change in the transform. This way we don't have to check ;; all the attributes diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 3f7440c063..a8e614093a 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -260,13 +260,13 @@ (rx/of (with-meta event (meta it))))))))) (defn update-layout - [ids changes] + [ids changes & options] (ptk/reify ::update-layout ptk/WatchEvent (watch [_ _ _] (let [undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) - (dwsh/update-shapes ids (d/patch-object changes)) + (dwsh/update-shapes ids (d/patch-object changes) options) (ptk/data-event :layout/update {:ids ids}) (dwu/commit-undo-transaction undo-id)))))) @@ -516,7 +516,7 @@ (assoc :layout-item-v-sizing :fix)))) (defn update-layout-child - [ids changes] + [ids changes & options] (ptk/reify ::update-layout-child ptk/WatchEvent (watch [_ state _] @@ -525,8 +525,8 @@ parent-ids (->> ids (map #(cfh/get-parent-id objects %))) undo-id (js/Symbol)] (rx/of (dwu/start-undo-transaction undo-id) - (dwsh/update-shapes ids (d/patch-object changes)) - (dwsh/update-shapes children-ids (partial fix-child-sizing objects changes)) + (dwsh/update-shapes ids (d/patch-object changes) options) + (dwsh/update-shapes children-ids (partial fix-child-sizing objects changes) options) (dwsh/update-shapes parent-ids (fn [parent objects] @@ -534,7 +534,7 @@ (fix-parent-sizing objects (set ids) changes) (cond-> (ctl/grid-layout? parent) (ctl/assign-cells objects)))) - {:with-objects? true}) + (merge options {:with-objects? true})) (ptk/data-event :layout/update {:ids ids}) (dwu/commit-undo-transaction undo-id)))))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 20e24611b8..042fce3a55 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -432,7 +432,7 @@ (txt/transform-nodes (some-fn txt/is-text-node? txt/is-paragraph-node?) migrate-node content)) (defn update-text-with-function - [id update-node-fn] + [id update-node-fn & options] (ptk/reify ::update-text-with-function ptk/UpdateEvent (update [_ state] @@ -464,7 +464,7 @@ (-> shape (dissoc :fills) (d/update-when :content update-content)))] - (rx/of (dwsh/update-shapes shape-ids update-shape))))) + (rx/of (dwsh/update-shapes shape-ids update-shape options))))) ptk/EffectEvent (effect [_ state _] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index c4e2a80642..1f51afc0d4 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -301,7 +301,7 @@ (defn update-dimensions "Change size of shapes, from the sideber options form. Will ignore pixel snap used in the options side panel" - [ids attr value] + [ids attr value & options] (dm/assert! (number? value)) (dm/assert! "expected valid coll of uuids" @@ -324,7 +324,7 @@ ptk/WatchEvent (watch [_ _ _] - (rx/of (dwm/apply-modifiers))))) + (rx/of (dwm/apply-modifiers options))))) (defn change-orientation "Change orientation of shapes, from the sidebar options form. @@ -402,7 +402,7 @@ "Rotate shapes a fixed angle, from a keyboard action." ([ids rotation] (increase-rotation ids rotation nil)) - ([ids rotation params] + ([ids rotation params & options] (ptk/reify ::increase-rotation ptk/WatchEvent (watch [_ state _] @@ -411,7 +411,7 @@ shapes (->> ids (map #(get objects %)))] (rx/concat (rx/of (dwm/set-delta-rotation-modifiers rotation shapes params)) - (rx/of (dwm/apply-modifiers)))))))) + (rx/of (dwm/apply-modifiers options)))))))) ;; -- Move ---------------------------------------------------------- diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index e0c0f09100..8e7bdf3702 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -95,20 +95,9 @@ (when (ctsr/has-radius? shape) (ctsr/set-radius-1 shape value))) {:reg-objects? true + :ignore-touched true :attrs ctt/border-radius-keys})) -(defn update-opacity [value shape-ids] - (when (<= 0 value 1) - (dwsh/update-shapes shape-ids #(assoc % :opacity value)))) - -(defn update-rotation [value shape-ids] - (ptk/reify ::update-shape-rotation - ptk/WatchEvent - (watch [_ _ _] - (rx/of - (udw/trigger-bounding-box-cloaking shape-ids) - (udw/increase-rotation shape-ids value))))) - (defn update-shape-radius-single-corner [value shape-ids attributes] (dwsh/update-shapes shape-ids (fn [shape] @@ -117,8 +106,23 @@ (:rx shape) (ctsr/switch-to-radius-4) :always (ctsr/set-radius-4 (first attributes) value)))) {:reg-objects? true + :ignore-touched true :attrs [:rx :ry :r1 :r2 :r3 :r4]})) +(defn update-opacity [value shape-ids] + (when (<= 0 value 1) + (dwsh/update-shapes shape-ids + #(assoc % :opacity value) + {:ignore-touched true}))) + +(defn update-rotation [value shape-ids] + (ptk/reify ::update-shape-rotation + ptk/WatchEvent + (watch [_ _ _] + (rx/of + (udw/trigger-bounding-box-cloaking shape-ids) + (udw/increase-rotation shape-ids value nil :ignore-touched true))))) + (defn update-stroke-width [value shape-ids] (dwsh/update-shapes shape-ids @@ -126,6 +130,7 @@ (when (seq (:strokes shape)) (assoc-in shape [:strokes 0 :stroke-width] value))) {:reg-objects? true + :ignore-touched true :attrs [:strokes]})) (defn update-color [f value shape-ids] @@ -133,7 +138,7 @@ (tinycolor/valid-color) (tinycolor/->hex) (str "#"))] - (f shape-ids {:color color} 0))) + (apply f shape-ids {:color color} 0 [:ignore-touched true]))) (defn update-fill [value shape-ids] @@ -156,8 +161,8 @@ ptk/WatchEvent (watch [_ _ _] (rx/of - (when (:width attributes) (dwt/update-dimensions shape-ids :width value)) - (when (:height attributes) (dwt/update-dimensions shape-ids :height value)))))) + (when (:width attributes) (dwt/update-dimensions shape-ids :width value :ignore-touched true)) + (when (:height attributes) (dwt/update-dimensions shape-ids :height value :ignore-touched true)))))) (defn- attributes->layout-gap [attributes value] (let [layout-gap (-> (set/intersection attributes #{:column-gap :row-gap}) @@ -165,7 +170,9 @@ {:layout-gap layout-gap})) (defn update-layout-padding [value shape-ids attrs] - (dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat value))})) + (dwsl/update-layout shape-ids + {:layout-padding (zipmap attrs (repeat value))} + :ignore-touched true)) (defn update-layout-spacing [value shape-ids attributes] (ptk/reify ::update-layout-spacing @@ -177,7 +184,9 @@ (map :id))) layout-attributes (attributes->layout-gap attributes value)] (rx/of - (dwsl/update-layout layout-shape-ids layout-attributes)))))) + (dwsl/update-layout layout-shape-ids + layout-attributes + :ignore-touched true)))))) (defn update-shape-position [value shape-ids attributes] (ptk/reify ::update-shape-position @@ -195,4 +204,4 @@ :layout-item-max-w value :layout-item-max-h value} (select-keys attributes))] - (dwsl/update-layout-child shape-ids props))))) + (dwsl/update-layout-child shape-ids props :ignore-touched true))))) From 88fdafa2c6aa075ca3542fbe82561c082d783227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 2 Dec 2024 15:50:20 +0100 Subject: [PATCH 12/13] :tada: Add tests to check all types of tokens --- .../src/app/common/test_helpers/tokens.cljc | 39 +- .../logic/components_and_tokens.cljs | 435 +++++++++++------- 2 files changed, 311 insertions(+), 163 deletions(-) diff --git a/common/src/app/common/test_helpers/tokens.cljc b/common/src/app/common/test_helpers/tokens.cljc index 35a7d53aad..7ddb78a3d7 100644 --- a/common/src/app/common/test_helpers/tokens.cljc +++ b/common/src/app/common/test_helpers/tokens.cljc @@ -23,6 +23,39 @@ [file f] (ctf/update-file-data file #(update % :tokens-lib f))) +(defn- set-stroke-width + [shape stroke-width] + (let [strokes (if (seq (:strokes shape)) + (:strokes shape) + [{:stroke-style :solid + :stroke-alignment :inner + :stroke-width 1 + :stroke-color "#000000" + :stroke-opacity 1}]) + new-strokes (update strokes 0 assoc :stroke-width stroke-width)] + (ctn/set-shape-attr shape :strokes new-strokes {:ignore-touched true}))) + +(defn- set-stroke-color + [shape stroke-color] + (let [strokes (if (seq (:strokes shape)) + (:strokes shape) + [{:stroke-style :solid + :stroke-alignment :inner + :stroke-width 1 + :stroke-color "#000000" + :stroke-opacity 1}]) + new-strokes (update strokes 0 assoc :stroke-color stroke-color)] + (ctn/set-shape-attr shape :strokes new-strokes {:ignore-touched true}))) + +(defn- set-fill-color + [shape fill-color] + (let [fills (if (seq (:fills shape)) + (:fills shape) + [{:fill-color "#000000" + :fill-opacity 1}]) + new-fills (update fills 0 assoc :fill-color fill-color)] + (ctn/set-shape-attr shape :fills new-fills {:ignore-touched true}))) + (defn apply-token-to-shape [file shape-label token-name token-attrs shape-attrs resolved-value] (let [page (thf/current-page file) @@ -32,7 +65,11 @@ :token {:name token-name} :attributes token-attrs}) (reduce (fn [shape attr] - (ctn/set-shape-attr shape attr resolved-value {:ignore-touched true})) + (case attr + :stroke-width (set-stroke-width shape resolved-value) + :stroke-color (set-stroke-color shape resolved-value) + :fill (set-fill-color shape resolved-value) + (ctn/set-shape-attr shape attr resolved-value {:ignore-touched true}))) $ shape-attrs))] diff --git a/frontend/test/frontend_tests/logic/components_and_tokens.cljs b/frontend/test/frontend_tests/logic/components_and_tokens.cljs index 4175792f01..a51985ef8f 100644 --- a/frontend/test/frontend_tests/logic/components_and_tokens.cljs +++ b/frontend/test/frontend_tests/logic/components_and_tokens.cljs @@ -6,6 +6,7 @@ (ns frontend-tests.logic.components-and-tokens (:require [app.common.geom.point :as geom] + [app.common.math :as mth] [app.common.test-helpers.components :as cthc] [app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.files :as cthf] @@ -64,223 +65,333 @@ (t/deftest create-component-with-token (t/async - done - (let [;; ==== Setup - file (setup-base-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-base-file) + store (ths/setup-store file) ;; ==== Action - events - [(dws/select-shape (cthi/id :frame1)) - (dwl/add-component)]] + events + [(dws/select-shape (cthi/id :frame1)) + (dwl/add-component)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-store new-state) - frame1' (cths/get-shape file' :frame1) - tokens-frame1' (:applied-tokens frame1')] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + frame1' (cths/get-shape file' :frame1) + tokens-frame1' (:applied-tokens frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 2)) - (t/is (= (get tokens-frame1' :rx) "test-token-1")) - (t/is (= (get tokens-frame1' :ry) "test-token-1")) - (t/is (= (get frame1' :rx) 25)) - (t/is (= (get frame1' :ry) 25)))))))) + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-1")) + (t/is (= (get tokens-frame1' :ry) "test-token-1")) + (t/is (= (get frame1' :rx) 25)) + (t/is (= (get frame1' :ry) 25)))))))) (t/deftest create-copy-with-token (t/async - done - (let [;; ==== Setup - file (setup-file-with-main) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-with-main) + store (ths/setup-store file) ;; ==== Action - events - [(dwl/instantiate-component (:id file) - (cthi/id :component1) - (geom/point 0 0))]] + events + [(dwl/instantiate-component (:id file) + (cthi/id :component1) + (geom/point 0 0))]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - selected (wsh/lookup-selected new-state) - c-frame1' (wsh/lookup-shape new-state (first selected)) - tokens-frame1' (:applied-tokens c-frame1')] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + selected (wsh/lookup-selected new-state) + c-frame1' (wsh/lookup-shape new-state (first selected)) + tokens-frame1' (:applied-tokens c-frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 2)) - (t/is (= (get tokens-frame1' :rx) "test-token-1")) - (t/is (= (get tokens-frame1' :ry) "test-token-1")) - (t/is (= (get c-frame1' :rx) 25)) - (t/is (= (get c-frame1' :ry) 25)))))))) + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-1")) + (t/is (= (get tokens-frame1' :ry) "test-token-1")) + (t/is (= (get c-frame1' :rx) 25)) + (t/is (= (get c-frame1' :ry) 25)))))))) (t/deftest change-token-in-main (t/async - done - (let [;; ==== Setup - file (setup-file-with-copy) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) ;; ==== Action - events [(wtch/apply-token {:shape-ids [(cthi/id :frame1)] - :attributes #{:rx :ry} - :token (toht/get-token file "test-token-2") - :on-update-shape wtch/update-shape-radius-all})] + events [(wtch/apply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-2") + :on-update-shape wtch/update-shape-radius-all})] - step2 (fn [_] - (let [events2 [(dwl/sync-file (:id file) (:id file))]] - (ths/run-store - store done events2 - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-store new-state) - c-frame1' (cths/get-shape file' :c-frame1) - tokens-frame1' (:applied-tokens c-frame1')] + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 2)) - (t/is (= (get tokens-frame1' :rx) "test-token-2")) - (t/is (= (get tokens-frame1' :ry) "test-token-2")) - (t/is (= (get c-frame1' :rx) 50)) - (t/is (= (get c-frame1' :ry) 50)))))))] + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-2")) + (t/is (= (get tokens-frame1' :ry) "test-token-2")) + (t/is (= (get c-frame1' :rx) 50)) + (t/is (= (get c-frame1' :ry) 50)))))))] - (tohs/run-store-async - store step2 events identity)))) + (tohs/run-store-async + store step2 events identity)))) (t/deftest remove-token-in-main (t/async - done - (let [;; ==== Setup - file (setup-file-with-copy) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) ;; ==== Action - events [(wtch/unapply-token {:shape-ids [(cthi/id :frame1)] - :attributes #{:rx :ry} - :token (toht/get-token file "test-token-1")})] + events [(wtch/unapply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-1")})] - step2 (fn [_] - (let [events2 [(dwl/sync-file (:id file) (:id file))]] - (ths/run-store - store done events2 - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-store new-state) - c-frame1' (cths/get-shape file' :c-frame1) - tokens-frame1' (:applied-tokens c-frame1')] + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 0)) - (t/is (= (get c-frame1' :rx) 25)) - (t/is (= (get c-frame1' :ry) 25)))))))] + (t/is (= (count tokens-frame1') 0)) + (t/is (= (get c-frame1' :rx) 25)) + (t/is (= (get c-frame1' :ry) 25)))))))] - (tohs/run-store-async - store step2 events identity)))) + (tohs/run-store-async + store step2 events identity)))) (t/deftest modify-token (t/async - done - (let [;; ==== Setup - file (setup-file-with-copy) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) ;; ==== Action - events [(dt/update-create-token {:token (ctob/make-token :name "test-token-1" - :type :border-radius - :value 66) - :prev-token-name "test-token-1"})] + events [(dt/update-create-token {:token (ctob/make-token :name "test-token-1" + :type :border-radius + :value 66) + :prev-token-name "test-token-1"})] - step2 (fn [_] - (let [events2 [(wtu/update-workspace-tokens) - (dwl/sync-file (:id file) (:id file))]] - (tohs/run-store-async - store done events2 - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-store new-state) - c-frame1' (cths/get-shape file' :c-frame1) - tokens-frame1' (:applied-tokens c-frame1')] + step2 (fn [_] + (let [events2 [(wtu/update-workspace-tokens) + (dwl/sync-file (:id file) (:id file))]] + (tohs/run-store-async + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 2)) - (t/is (= (get tokens-frame1' :rx) "test-token-1")) - (t/is (= (get tokens-frame1' :ry) "test-token-1")) - (t/is (= (get c-frame1' :rx) 66)) - (t/is (= (get c-frame1' :ry) 66)))))))] + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-1")) + (t/is (= (get tokens-frame1' :ry) "test-token-1")) + (t/is (= (get c-frame1' :rx) 66)) + (t/is (= (get c-frame1' :ry) 66)))))))] - (tohs/run-store-async - store step2 events identity)))) + (tohs/run-store-async + store step2 events identity)))) (t/deftest change-token-in-copy-then-change-main (t/async - done - (let [;; ==== Setup - file (setup-file-with-copy) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) ;; ==== Action - events [(wtch/apply-token {:shape-ids [(cthi/id :c-frame1)] - :attributes #{:rx :ry} - :token (toht/get-token file "test-token-2") - :on-update-shape wtch/update-shape-radius-all}) - (wtch/apply-token {:shape-ids [(cthi/id :frame1)] - :attributes #{:rx :ry} - :token (toht/get-token file "test-token-3") - :on-update-shape wtch/update-shape-radius-all})] + events [(wtch/apply-token {:shape-ids [(cthi/id :c-frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-2") + :on-update-shape wtch/update-shape-radius-all}) + (wtch/apply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-3") + :on-update-shape wtch/update-shape-radius-all})] - step2 (fn [_] - (let [events2 [(dwl/sync-file (:id file) (:id file))]] - (ths/run-store - store done events2 - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-store new-state) - c-frame1' (cths/get-shape file' :c-frame1) - tokens-frame1' (:applied-tokens c-frame1')] + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 2)) - (t/is (= (get tokens-frame1' :rx) "test-token-2")) - (t/is (= (get tokens-frame1' :ry) "test-token-2")) - (t/is (= (get c-frame1' :rx) 50)) - (t/is (= (get c-frame1' :ry) 50)))))))] + (t/is (= (count tokens-frame1') 2)) + (t/is (= (get tokens-frame1' :rx) "test-token-2")) + (t/is (= (get tokens-frame1' :ry) "test-token-2")) + (t/is (= (get c-frame1' :rx) 50)) + (t/is (= (get c-frame1' :ry) 50)))))))] - (tohs/run-store-async - store step2 events identity)))) + (tohs/run-store-async + store step2 events identity)))) (t/deftest remove-token-in-copy-then-change-main (t/async - done - (let [;; ==== Setup - file (setup-file-with-copy) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-with-copy) + store (ths/setup-store file) ;; ==== Action - events [(wtch/unapply-token {:shape-ids [(cthi/id :c-frame1)] - :attributes #{:rx :ry} - :token (toht/get-token file "test-token-1")}) - (wtch/apply-token {:shape-ids [(cthi/id :frame1)] - :attributes #{:rx :ry} - :token (toht/get-token file "test-token-3") - :on-update-shape wtch/update-shape-radius-all})] + events [(wtch/unapply-token {:shape-ids [(cthi/id :c-frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-1")}) + (wtch/apply-token {:shape-ids [(cthi/id :frame1)] + :attributes #{:rx :ry} + :token (toht/get-token file "test-token-3") + :on-update-shape wtch/update-shape-radius-all})] - step2 (fn [_] - (let [events2 [(dwl/sync-file (:id file) (:id file))]] - (ths/run-store - store done events2 - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-store new-state) - c-frame1' (cths/get-shape file' :c-frame1) - tokens-frame1' (:applied-tokens c-frame1')] + step2 (fn [_] + (let [events2 [(dwl/sync-file (:id file) (:id file))]] + (ths/run-store + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] ;; ==== Check - (t/is (= (count tokens-frame1') 0)) - (t/is (= (get c-frame1' :rx) 25)) - (t/is (= (get c-frame1' :ry) 25)))))))] + (t/is (= (count tokens-frame1') 0)) + (t/is (= (get c-frame1' :rx) 25)) + (t/is (= (get c-frame1' :ry) 25)))))))] - (tohs/run-store-async - store step2 events identity)))) + (tohs/run-store-async + store step2 events identity)))) + +(t/deftest modify-token-all-types + (t/async + done + (let [;; ==== Setup + file (-> (cthf/sample-file :file1) + (ctht/add-tokens-lib) + (ctht/update-tokens-lib #(-> % + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-theme (ctob/make-token-theme :name "test-theme" + :sets #{"test-token-set"})) + (ctob/set-active-themes #{"/test-theme"}) + (ctob/add-token-in-set "token-radius" + (ctob/make-token :name "token-radius" + :type :border-radius + :value 10)) + (ctob/add-token-in-set "token-rotation" + (ctob/make-token :name "token-rotation" + :type :rotation + :value 30)) + (ctob/add-token-in-set "token-opacity" + (ctob/make-token :name "token-opacity" + :type :opacity + :value 0.7)) + (ctob/add-token-in-set "token-stroke-width" + (ctob/make-token :name "token-stroke-width" + :type :stroke-width + :value 2)) + (ctob/add-token-in-set "token-color" + (ctob/make-token :name "token-color" + :type :color + :value "#00ff00")) + (ctob/add-token-in-set "token-dimensions" + (ctob/make-token :name "token-dimensions" + :type :dimensions + :value 100)))) + (ctho/add-frame :frame1) + (ctht/apply-token-to-shape :frame1 "token-radius" [:rx :ry] [:rx :ry] 10) + (ctht/apply-token-to-shape :frame1 "token-rotation" [:rotation] [:rotation] 30) + (ctht/apply-token-to-shape :frame1 "token-opacity" [:opacity] [:opacity] 0.7) + (ctht/apply-token-to-shape :frame1 "token-stroke-width" [:stroke-width] [:stroke-width] 2) + (ctht/apply-token-to-shape :frame1 "token-color" [:stroke-color] [:stroke-color] "#00ff00") + (ctht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00") + (ctht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100) + (cthc/make-component :component1 :frame1) + (cthc/instantiate-component :component1 :c-frame1)) + store (ths/setup-store file) + + ;; ==== Action + events [(dt/update-create-token {:token (ctob/make-token :name "token-radius" + :type :border-radius + :value 30) + :prev-token-name "token-radius"}) + (dt/update-create-token {:token (ctob/make-token :name "token-rotation" + :type :rotation + :value 45) + :prev-token-name "token-rotation"}) + (dt/update-create-token {:token (ctob/make-token :name "token-opacity" + :type :opacity + :value 0.9) + :prev-token-name "token-opacity"}) + (dt/update-create-token {:token (ctob/make-token :name "token-stroke-width" + :type :stroke-width + :value 8) + :prev-token-name "token-stroke-width"}) + (dt/update-create-token {:token (ctob/make-token :name "token-color" + :type :color + :value "#ff0000") + :prev-token-name "token-color"}) + (dt/update-create-token {:token (ctob/make-token :name "token-dimensions" + :type :dimensions + :value 200) + :prev-token-name "token-dimensions"})] + + step2 (fn [_] + (let [events2 [(wtu/update-workspace-tokens) + (dwl/sync-file (:id file) (:id file))]] + (tohs/run-store-async + store done events2 + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-store new-state) + c-frame1' (cths/get-shape file' :c-frame1) + tokens-frame1' (:applied-tokens c-frame1')] + + ;; ==== Check + (t/is (= (count tokens-frame1') 9)) + (t/is (= (get tokens-frame1' :rx) "token-radius")) + (t/is (= (get tokens-frame1' :ry) "token-radius")) + (t/is (= (get tokens-frame1' :rotation) "token-rotation")) + (t/is (= (get tokens-frame1' :opacity) "token-opacity")) + (t/is (= (get tokens-frame1' :stroke-width) "token-stroke-width")) + (t/is (= (get tokens-frame1' :stroke-color) "token-color")) + (t/is (= (get tokens-frame1' :fill) "token-color")) + (t/is (= (get tokens-frame1' :width) "token-dimensions")) + (t/is (= (get tokens-frame1' :height) "token-dimensions")) + (t/is (= (get c-frame1' :rx) 30)) + (t/is (= (get c-frame1' :ry) 30)) + (t/is (= (get c-frame1' :rotation) 45)) + (t/is (= (get c-frame1' :opacity) 0.9)) + (t/is (= (get-in c-frame1' [:strokes 0 :stroke-width]) 8)) + (t/is (= (get-in c-frame1' [:strokes 0 :stroke-color]) "#ff0000")) + (t/is (= (get-in c-frame1' [:fills 0 :fill-color]) "#ff0000")) + (t/is (mth/close? (get c-frame1' :width) 200)) + (t/is (mth/close? (get c-frame1' :height) 200)) + + (t/is (empty? (:touched c-frame1'))))))))] + + (tohs/run-store-async + store step2 events identity)))) \ No newline at end of file From 6e7a5e5c7f9d25a28b399cd2e9d172e11a7212da Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 10 Dec 2024 17:15:40 +0100 Subject: [PATCH 13/13] =?UTF-8?q?=E2=99=BB=20Use=20dm/str?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/app/main/ui/workspace/tokens/sets.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index 670cff3feb..1f75103a36 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.sets (:require-macros [app.main.style :as stl]) (:require + [app.common.data.macros :as dm] [app.common.types.tokens-lib :as ctob] [app.main.data.tokens :as wdt] [app.main.refs :as refs] @@ -65,7 +66,7 @@ mixed? (= checked "mixed") checked? (or all? mixed?)] [:div {:role "checkbox" - :aria-checked (str checked) + :aria-checked (dm/str checked) :tabindex 0 :class (stl/css-case :checkbox-style true :checkbox-checked-style checked?)