From 3907a1783aa3c0d8c7f524a5656866b1d7844db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 25 Feb 2025 14:05:07 +0100 Subject: [PATCH] :sparkles: Include active sets and themes in exported tokens json file (#5806) * :sparkles: Add active sets to json decode * :paperclip: Fix tests * :paperclip: Add encode tests * :paperclip: Add decode test --------- Co-authored-by: Eva Marco --- common/src/app/common/types/tokens_lib.cljc | 70 +++++++---- .../types/data/tokens-default-team-only.json | 11 ++ .../types/data/tokens-multi-set-example.json | 4 +- .../common_tests/types/tokens_lib_test.cljc | 115 +++++++++++++++++- .../ui/workspace/tokens/theme_select.cljs | 1 + 5 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 common/test/common_tests/types/data/tokens-default-team-only.json diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 011aae089..8249b9a8a 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -26,15 +26,18 @@ (def valid-groupable-item? (sm/validator schema:groupable-item)) -(def ^:private xf:clean-string - (comp (map str/trim) - (remove str/empty?))) - (defn split-path "Decompose a string in the form 'one.two.three' into a vector of strings, removing spaces." + [path separator] - (->> (str/split path separator) - (into [] xf:clean-string))) + (let [xf (comp (map str/trim) + (remove str/empty?))] + (->> (str/split path separator) + (into [] xf)))) + +(defn split-path-name [s separator] + (let [[path name] (str/split s (re-pattern (str "\\" separator)) 2)] + [(or path "") name])) (defn join-path "Regenerate a path as a string, from a vector." @@ -471,7 +474,7 @@ (join-path [group name] theme-separator)) (defn split-token-theme-path [path] - (split-path path theme-separator)) + (split-path-name path theme-separator)) (def hidden-token-theme-group "") @@ -1206,7 +1209,7 @@ Will return a value that matches this schema: tokens (order-theme-set theme))) (d/ordered-map) active-themes))) - (encode-dtcg [_] + (encode-dtcg [this] (let [themes (into [] (comp (filter #(and (instance? TokenTheme %) @@ -1222,16 +1225,21 @@ Will return a value that matches this schema: [s "enabled"]) (into {}))))))))) (tree-seq d/ordered-map? vals themes)) + ;; Active themes without exposing hidden penpot theme + active-themes-clear (disj active-themes hidden-token-theme-path) name-set-tuples (->> sets (tree-seq d/ordered-map? vals) (filter (partial instance? TokenSet)) (map (fn [token-set] [(:name token-set) (get-dtcg-tokens-tree token-set)]))) ordered-set-names (map first name-set-tuples) - sets (into {} name-set-tuples)] + sets (into {} name-set-tuples) + active-sets (get-active-themes-set-names this)] (-> sets (assoc "$themes" themes) - (assoc-in ["$metadata" "tokenSetOrder"] ordered-set-names)))) + (assoc-in ["$metadata" "tokenSetOrder"] ordered-set-names) + (assoc-in ["$metadata" "activeThemes"] active-themes-clear) + (assoc-in ["$metadata" "activeSets"] active-sets)))) (decode-dtcg-json [_ parsed-json] (let [metadata (get parsed-json "$metadata") @@ -1241,6 +1249,20 @@ Will return a value that matches this schema: (-> theme (set/rename-keys {"selectedTokenSets" "sets"}) (update "sets" keys))))) + active-sets (get metadata "activeSets") + active-themes (get metadata "activeThemes") + themes-data (if (and (seq active-sets) + (empty? active-themes)) + (conj themes-data {"name" hidden-token-theme-name + "group" "" + "description" nil + "is-source" true + "modified-at" (dt/now) + "sets" active-sets}) + themes-data) + active-themes (if (empty? active-themes) + #{hidden-token-theme-path} + active-themes) set-order (get metadata "tokenSetOrder") name->pos (into {} (map-indexed (fn [idx itm] [itm idx]) set-order)) sets-data' (sort-by (comp name->pos first) sets-data) @@ -1252,17 +1274,23 @@ Will return a value that matches this schema: :tokens (flatten-nested-tokens-json tokens "")))) lib sets-data')] (if-let [themes-data (seq themes-data)] - (reduce - (fn [lib {:strs [name group description is-source modified-at sets]}] - (add-theme lib (TokenTheme. name - (or group "") - description - (some? is-source) - (or (some-> modified-at - (dt/parse-instant)) - (dt/now)) - (set sets)))) - lib' themes-data) + (as-> lib' $ + (reduce + (fn [lib {:strs [name group description is-source modified-at sets]}] + (add-theme lib (TokenTheme. name + (or group "") + description + (some? is-source) + (or (some-> modified-at + (dt/parse-instant)) + (dt/now)) + (set sets)))) + $ themes-data) + (reduce + (fn [lib active-theme] + (let [[group name] (split-token-theme-path active-theme)] + (activate-theme lib group name))) + $ active-themes)) lib'))) (decode-legacy-json [this parsed-legacy-json] diff --git a/common/test/common_tests/types/data/tokens-default-team-only.json b/common/test/common_tests/types/data/tokens-default-team-only.json new file mode 100644 index 000000000..3e128c151 --- /dev/null +++ b/common/test/common_tests/types/data/tokens-default-team-only.json @@ -0,0 +1,11 @@ +{ + "dark": + {"small": + {"$value": "8", + "$type":"borderRadius", + "$description":""}}, + "$themes": [], + "$metadata": + {"tokenSetOrder": ["dark"], + "activeThemes": [], + "activeSets": ["dark"]}} \ No newline at end of file diff --git a/common/test/common_tests/types/data/tokens-multi-set-example.json b/common/test/common_tests/types/data/tokens-multi-set-example.json index 547f8f901..cf94c1f2d 100644 --- a/common/test/common_tests/types/data/tokens-multi-set-example.json +++ b/common/test/common_tests/types/data/tokens-multi-set-example.json @@ -805,6 +805,8 @@ "selectedTokenSets": {"light": "enabled"} } ], "$metadata": { - "tokenSetOrder": ["core", "light", "dark", "theme"] + "tokenSetOrder": ["core", "light", "dark", "theme"], + "activeSets": {}, + "activeThemes": {} } } diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index e6b60cfbb..e9fbf032c 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -1210,6 +1210,27 @@ (t/testing "invalid tokens got discarded" (t/is (nil? (get-set-token "typography" "H1.Bold")))))) + (t/testing "decode-dtcg-json-default-team" + (let [json (-> (slurp "test/common_tests/types/data/tokens-default-team-only.json") + (tr/decode-str)) + lib (ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) json) + get-set-token (fn [set-name token-name] + (some-> (ctob/get-set lib set-name) + (ctob/get-token token-name))) + themes (ctob/get-themes lib) + first-theme (first themes)] + (t/is (= '("dark") (ctob/get-ordered-set-names lib))) + (t/is (= 1 (count themes))) + (t/testing "existing theme is default theme" + (t/is (= (:group first-theme) "")) + (t/is (= (:name first-theme) ctob/hidden-token-theme-name))) + (t/testing "token exist in dark set" + (t/is (tht/token-data-eq? (get-set-token "dark" "small") + {:name "small" + :value "8" + :type :border-radius + :description ""}))))) + (t/testing "encode-dtcg-json" (let [now (dt/now) tokens-lib (-> (ctob/make-tokens-lib) @@ -1241,7 +1262,8 @@ "modified-at" now "name" "theme-1" "selectedTokenSets" {"core" "enabled"}}] - "$metadata" {"tokenSetOrder" ["core"]} + "$metadata" {"tokenSetOrder" ["core"] + "activeSets" #{}, "activeThemes" #{}} "core" {"colors" {"red" {"600" {"$value" "#e53e3e" "$type" "color" @@ -1285,4 +1307,93 @@ (t/is (not= with-prev-tokens-lib tokens-lib)) (t/is (= @with-prev-tokens-lib @tokens-lib))) (t/testing "fresh tokens library is also equal" - (= @with-empty-tokens-lib @tokens-lib))))))) + (= @with-empty-tokens-lib @tokens-lib))))) + + (t/testing "encode-default-theme-json" + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "core" + :tokens {"colors.red.600" + (ctob/make-token + {:name "colors.red.600" + :type :color + :value "#e53e3e"}) + "spacing.multi-value" + (ctob/make-token + {:name "spacing.multi-value" + :type :spacing + :value "{dimension.sm} {dimension.xl}" + :description "You can have multiple values in a single spacing token"}) + "button.primary.background" + (ctob/make-token + {:name "button.primary.background" + :type :color + :value "{accent.default}"})}))) + result (ctob/encode-dtcg tokens-lib) + expected {"$themes" [] + "$metadata" {"tokenSetOrder" ["core"] + "activeSets" #{}, "activeThemes" #{}} + "core" + {"colors" {"red" {"600" {"$value" "#e53e3e" + "$type" "color" + "$description" ""}}} + "spacing" + {"multi-value" + {"$value" "{dimension.sm} {dimension.xl}" + "$type" "spacing" + "$description" "You can have multiple values in a single spacing token"}} + "button" + {"primary" {"background" {"$value" "{accent.default}" + "$type" "color" + "$description" ""}}}}}] + + (t/is (= expected result)))) + + (t/testing "encode-dtcg-json-with-active-theme-and-set" + (let [now (dt/now) + tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "core" + :tokens {"colors.red.600" + (ctob/make-token + {:name "colors.red.600" + :type :color + :value "#e53e3e"}) + "spacing.multi-value" + (ctob/make-token + {:name "spacing.multi-value" + :type :spacing + :value "{dimension.sm} {dimension.xl}" + :description "You can have multiple values in a single spacing token"}) + "button.primary.background" + (ctob/make-token + {:name "button.primary.background" + :type :color + :value "{accent.default}"})})) + (ctob/add-theme (ctob/make-token-theme :name "theme-1" + :group "group-1" + :modified-at now + :sets #{"core"})) + (ctob/toggle-theme-active? "group-1" "theme-1")) + result (ctob/encode-dtcg tokens-lib) + expected {"$themes" [{"description" "" + "group" "group-1" + "is-source" false + "modified-at" now + "name" "theme-1" + "selectedTokenSets" {"core" "enabled"}}] + "$metadata" {"tokenSetOrder" ["core"] + "activeSets" #{"core"}, + "activeThemes" #{"group-1/theme-1"}} + "core" + {"colors" {"red" {"600" {"$value" "#e53e3e" + "$type" "color" + "$description" ""}}} + "spacing" + {"multi-value" + {"$value" "{dimension.sm} {dimension.xl}" + "$type" "spacing" + "$description" "You can have multiple values in a single spacing token"}} + "button" + {"primary" {"background" {"$value" "{accent.default}" + "$type" "color" + "$description" ""}}}}}] + (t/is (= expected result)))))) diff --git a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs index 2c0bd57f6..df81cf6f0 100644 --- a/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/theme_select.cljs @@ -84,6 +84,7 @@ (> active-themes-count 1) (tr "workspace.token.active-themes" active-themes-count) (= active-themes-count 1) (some->> (first active-theme-paths) (ctob/split-token-theme-path) + (remove empty?) (str/join " / ")) :else (tr "workspace.token.no-active-theme"))