This commit is contained in:
Andrés Moya 2026-06-12 09:33:13 +02:00
parent ae56f4d0a8
commit 0db0fd6dff
11 changed files with 188 additions and 82 deletions

View File

@ -1087,7 +1087,7 @@
;; === Operations
(def decode-shape-attrs
(def decode-shape-attrs
(sm/decoder cts/schema:shape-attrs sm/json-transformer))
(defmethod process-operation :assign

View File

@ -11,6 +11,7 @@
[app.common.i18n :refer [tr]]
[app.common.schema :as sm]
[app.common.types.token :as cto]
[app.common.types.token-status :as ctos]
[app.common.types.tokens-lib :as ctob]
[clojure.set :as set]
[cuerdas.core :as str]
@ -302,6 +303,8 @@
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Token
(def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value."
@ -373,3 +376,15 @@
;; FIXME: this should be precalculated ?
(defn is-reference? [token]
(str/includes? (:value token) "{"))
;; Tokens status
(defn set-theme-status
[token-status tokens-lib theme-id status]
(assert (ctos/token-status? token-status) "expected valid token-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? theme-id) "expected valid theme-id")
(assert (boolean? status) "expected boolean status")
(if (ctob/get-theme tokens-lib theme-id)
(ctos/set-theme-status token-status tokens-lib theme-id status)
token-status))

View File

@ -29,6 +29,7 @@
(ctf/update-file-data file ctf/ensure-tokens-lib))
(defn update-tokens-lib
"Modify the tokens-lib of a file "
[file f]
(ctf/update-file-data file #(ctf/update-tokens-lib % f)))

View File

@ -330,10 +330,14 @@
(:token-status file-data)))
(defn update-tokens-lib
"Update the tokens-lib inside file-data through a callback function.
The function will receive the tokens lib and the rest of args."
[file-data f & args]
(d/update-when file-data :tokens-lib #(apply f % args)))
(defn update-token-status
"Update the token-status inside file-data through a callback function.
The function will receive the tokens status, the tokens lib and the rest of args."
[file-data f & args]
(d/update-when file-data :token-status #(apply f % (get-tokens-lib file-data) args)))

View File

@ -24,7 +24,7 @@
(deactivate-theme [_ tokens-lib theme-id] "Remove a theme uuid from active themes")
(set-theme-status [_ tokens-lib theme-id status] "Add or remove a theme uuid to active themes")
(theme-active? [_ theme-id] "Check if a theme uuid is active")
(active-theme-count [_] "Return the number of active themes")
(active-themes-count [_] "Return the number of active themes")
(activate-set [_ set-id] "Add a set uuid to active sets")
(deactivate-set [_ set-id] "Remove a set uuid from active sets")
(toggle-set-active [_ set-id] "Toggle a set uuid in active sets")
@ -44,39 +44,50 @@
ITokenStatus
(activate-theme [this tokens-lib theme-id]
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? theme-id) "expected valid theme-id")
(if (ctob/get-theme tokens-lib theme-id)
(TokenStatus. (conj active-themes theme-id) active-sets)
this))
(deactivate-theme [this tokens-lib theme-id]
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? theme-id) "expected valid theme-id")
(if (ctob/get-theme tokens-lib theme-id)
(TokenStatus. (disj active-themes theme-id) active-sets)
this))
(set-theme-status [this tokens-lib theme-id status]
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? theme-id) "expected valid theme-id")
(assert (boolean? status) "expected boolean status")
(if status
(activate-theme this tokens-lib theme-id)
(deactivate-theme this tokens-lib theme-id)))
(theme-active? [_ theme-id]
(assert (uuid? theme-id) "expected valid theme-id")
(contains? active-themes theme-id))
(active-theme-count [_]
(prn active-themes)
(active-themes-count [_]
(count active-themes))
(activate-set [_ set-id]
(assert (uuid? set-id) "expected valid set-id")
(TokenStatus. active-themes (conj active-sets set-id)))
(deactivate-set [_ set-id]
(assert (uuid? set-id) "expected valid set-id")
(TokenStatus. active-themes (disj active-sets set-id)))
(toggle-set-active [this set-id]
(assert (uuid? set-id) "expected valid set-id")
(if (contains? active-sets set-id)
(deactivate-set this set-id)
(activate-set this set-id)))
(set-active? [_ set-id]
(assert (uuid? set-id) "expected valid set-id")
(contains? active-sets set-id))
(active-set-count [_]
@ -126,6 +137,7 @@
"Make a TokenStatus from a TokensLib, activating the themes and sets
marked as active in the library (to migrate from legacy files)."
[tokens-lib]
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(let [active-themes (into #{}
(comp (map :id)
(filter #(not= % ctob/hidden-theme-id)))
@ -135,7 +147,7 @@
(map ctob/get-id))
(ctob/get-active-themes-set-names tokens-lib))]
(make-token-status :active-themes active-themes
:active-sets active-sets)))
:active-sets active-sets)))
;; === Pretty-print for debugging ===

View File

@ -778,6 +778,7 @@
(delete-theme [_ id] "delete a theme in the library")
(theme-count [_] "get the total number if themes in the library")
(get-theme-tree [_] "get a nested tree of all themes in the library")
(get-theme-tree-no-hidden [_] "get a nested tree of all themes in the library except the hidden theme")
(get-themes [_] "get an ordered sequence of all themes in the library")
(get-theme [_ id] "get one theme looking for id")
(get-theme-by-name [_ group name] "get one theme looking for group and name")
@ -1192,6 +1193,9 @@ Will return a value that matches this schema:
(get-theme-tree [_]
themes)
(get-theme-tree-no-hidden [_]
(dissoc themes hidden-theme-group))
(get-theme-groups [_]
(into [] (comp
(map key)

View File

@ -7,6 +7,11 @@
(ns common-tests.files.tokens-test
(:require
[app.common.files.tokens :as cfo]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.tokens :as tht]
[app.common.types.token-status :as ctos]
[app.common.types.tokens-lib :as ctob]
[clojure.test :as t]))
(t/deftest test-parse-token-value
@ -80,3 +85,11 @@
(t/is (nil? (cfo/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "a"}}]
#{:y})))))
(t/deftest set-theme-status
(t/testing "setting the status of a theme gets it activated or deactivated"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme1) :name "theme")))
token-status (ctos/make-token-status)
token-status' (cfo/set-theme-status token-status tokens-lib (thi/id :theme1) true)]
(t/is (ctos/theme-active? token-status' (thi/id :theme1))))))

View File

@ -13,7 +13,6 @@
[app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)

View File

@ -8,9 +8,10 @@
(:require
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.tokens :as tht]
[app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.common.types.token-status :as ctos]
[app.common.types.tokens-lib :as ctob]
[clojure.test :as t]))
(t/deftest test-ensure-tokens-lib
@ -23,36 +24,77 @@
(t/is (contains? file-data' :token-status))
(t/is (ctos/token-status? (:token-status file-data')))))
(t/testing "ensure-tokens-lib should create a token-status from an existing tokens-lib")
(let [file (thf/sample-file :file1)
file-data (-> (ctf/file-data file)
(assoc :tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set
:id (thi/new-id! :set1)
:name "set1"))
(ctob/add-set (ctob/make-token-set
:id (thi/new-id! :set2)
:name "set2"))
(ctob/add-theme (ctob/make-token-theme
:id (thi/new-id! :theme1)
:name "theme1"
:sets #{"set1"}))
(ctob/add-theme (ctob/make-token-theme
:id ctob/hidden-theme-id
:name "HIDDEN_THEME"
:sets #{"set2"}))
(ctob/activate-theme ctob/hidden-theme-id)
(ctob/activate-theme (thi/id :theme1)))))
file-data' (ctf/ensure-tokens-lib file-data)
token-status' (:token-status file-data')]
(t/testing "ensure-tokens-lib should create a token-status from an existing tokens-lib"
(let [file (thf/sample-file :file1)
file-data (-> (ctf/file-data file)
(assoc :tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set
:id (thi/new-id! :set1)
:name "set1"))
(ctob/add-set (ctob/make-token-set
:id (thi/new-id! :set2)
:name "set2"))
(ctob/add-theme (ctob/make-token-theme
:id (thi/new-id! :theme1)
:name "theme1"
:sets #{"set1"}))
(ctob/add-theme (ctob/make-token-theme
:id ctob/hidden-theme-id
:name "HIDDEN_THEME"
:sets #{"set2"}))
(ctob/activate-theme ctob/hidden-theme-id)
(ctob/activate-theme (thi/id :theme1)))))
file-data' (ctf/ensure-tokens-lib file-data)
token-status' (:token-status file-data')]
(t/is (contains? file-data' :tokens-lib))
(t/is (ctob/tokens-lib? (:tokens-lib file-data')))
(t/is (contains? file-data' :token-status))
(t/is (ctos/token-status? token-status'))
(t/is (ctos/check-token-status token-status'))
(t/is (= 1 (ctos/active-theme-count token-status')))
(t/is (true? (ctos/theme-active? token-status' (thi/id :theme1))))
(t/is (= 1 (ctos/active-set-count token-status') 1))
(t/is (ctos/set-active? token-status' (thi/id :set1)))))
(t/is (contains? file-data' :tokens-lib))
(t/is (ctob/tokens-lib? (:tokens-lib file-data')))
(t/is (contains? file-data' :token-status))
(t/is (ctos/token-status? token-status'))
(t/is (ctos/check-token-status token-status'))
(t/is (= 1 (ctos/active-themes-count token-status')))
(t/is (true? (ctos/theme-active? token-status' (thi/id :theme1))))
(t/is (= 1 (ctos/active-set-count token-status') 1))
(t/is (ctos/set-active? token-status' (thi/id :set1))))))
(t/deftest test-update-tokens-lib
(t/testing "update when there is no tokens-lib has no effect"
(let [file (thf/sample-file :file1)
file' (ctf/update-tokens-lib file #(t/is false "This should not be called"))]
(t/is (= file file'))))
(t/testing "update a tokens-lib applies the changes correctly"
(let [file (-> (thf/sample-file :file1)
(tht/add-tokens-lib))
file' (ctf/update-file-data file
#(ctf/update-tokens-lib
%
ctob/add-theme
(ctob/make-token-theme :id (thi/new-id! :theme1)
:name "theme 1")))
tokens-lib' (tht/get-tokens-lib file')]
(t/is (= 2 (ctob/theme-count tokens-lib'))) ;; Count the hidden theme
(t/is (ctob/token-theme? (ctob/get-theme tokens-lib' (thi/id :theme1)))))))
(t/deftest test-update-token-status
(t/testing "update when there is no token-status has no effect"
(let [file (thf/sample-file :file1)
file' (ctf/update-token-status file #(t/is false "This should not be called"))]
(t/is (= file file'))))
(t/testing "update a token-status applies the changes correctly"
(let [file (-> (thf/sample-file :file1)
(tht/add-tokens-lib)
(tht/update-tokens-lib
(fn [tokens-lib]
(-> tokens-lib
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme1) :name "theme"))))))
file' (ctf/update-file-data file
#(ctf/update-token-status
%
ctos/activate-theme
(thi/id :theme1)))
token-status' (tht/get-token-status file')]
(t/is (= 1 (ctos/active-themes-count token-status')))
(t/is (ctos/theme-active? token-status' (thi/id :theme1))))))

View File

@ -10,8 +10,8 @@
#?(:clj [clojure.data.json :as json])
[app.common.test-helpers.ids-map :as thi]
[app.common.transit :as tr]
[app.common.types.tokens-lib :as ctob]
[app.common.types.token-status :as ctos]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
@ -20,10 +20,10 @@
(let [theme-id (uuid/next)
set-id (uuid/next)
status (ctos/make-token-status :active-themes #{theme-id}
:active-sets #{set-id})]
:active-sets #{set-id})]
(t/is (ctos/token-status? status))
(t/is (ctos/check-token-status status))
(t/is (= (ctos/active-theme-count status) 1))
(t/is (= (ctos/active-themes-count status) 1))
(t/is (ctos/theme-active? status theme-id))
(t/is (= (ctos/active-set-count status) 1))
(t/is (ctos/set-active? status set-id))))
@ -32,7 +32,7 @@
(let [status (ctos/make-token-status)]
(t/is (ctos/token-status? status))
(t/is (ctos/check-token-status status))
(t/is (= (ctos/active-theme-count status) 0))
(t/is (= (ctos/active-themes-count status) 0))
(t/is (= (ctos/active-set-count status) 0))))
(t/deftest make-invalid-token-status
@ -47,29 +47,35 @@
(t/deftest activate-theme
(let [theme-id (uuid/next)
tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id theme-id :name "theme")))
status (ctos/make-token-status)
status' (ctos/activate-theme status theme-id)]
status' (ctos/activate-theme status tokens-lib theme-id)]
(t/is (not (ctos/theme-active? status theme-id)))
(t/is (ctos/theme-active? status' theme-id))
(t/is (= (ctos/active-theme-count status') 1))))
(t/is (= (ctos/active-themes-count status') 1))))
(t/deftest deactivate-theme
(let [theme-id (uuid/next)
tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id theme-id :name "theme")))
status (ctos/make-token-status :active-themes #{theme-id})
status' (ctos/deactivate-theme status theme-id)]
status' (ctos/deactivate-theme status tokens-lib theme-id)]
(t/is (ctos/theme-active? status theme-id))
(t/is (not (ctos/theme-active? status' theme-id)))
(t/is (= (ctos/active-theme-count status') 0))))
(t/is (= (ctos/active-themes-count status') 0))))
(t/deftest toggle-theme-active
(t/deftest set-theme-status
(let [theme-id (uuid/next)
tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id theme-id :name "theme")))
status (ctos/make-token-status)
status' (ctos/toggle-theme-active status theme-id)
status'' (ctos/toggle-theme-active status' theme-id)]
status' (ctos/set-theme-status status tokens-lib theme-id true)
status'' (ctos/set-theme-status status' tokens-lib theme-id false)]
(t/is (ctos/theme-active? status' theme-id))
(t/is (not (ctos/theme-active? status'' theme-id)))
(t/is (= (ctos/active-theme-count status') 1))
(t/is (= (ctos/active-theme-count status'') 0))))
(t/is (= (ctos/active-themes-count status') 1))
(t/is (= (ctos/active-themes-count status'') 0))))
(t/deftest activate-set
(let [set-id (uuid/next)
@ -165,7 +171,7 @@
token-status (ctos/make-token-status-from-lib tokens-lib)]
(t/is (ctos/token-status? token-status))
(t/is (ctos/check-token-status token-status))
(t/is (= (ctos/active-theme-count token-status) 2))
(t/is (= (ctos/active-themes-count token-status) 2))
(t/is (ctos/theme-active? token-status (thi/id :theme-1)))
(t/is (ctos/theme-active? token-status (thi/id :theme-2)))
(t/is (= (ctos/active-set-count token-status) 2))

View File

@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.types.token-status :as ctos]
[app.common.types.tokens-lib :as ctob]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
@ -15,6 +16,7 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as hooks]
@ -24,17 +26,16 @@
[rumext.v2 :as mf]))
(mf/defc themes-list*
[{:keys [themes active-theme-paths on-close is-grouped]}]
[{:keys [themes token-status on-close is-grouped]}]
(when (seq themes)
[:ul {:class (stl/css :theme-options)}
(for [[_ {:keys [id name] :as theme}] themes
:let [theme-id (ctob/get-theme-path theme)
selected? (get active-theme-paths theme-id)
:let [selected? (ctos/theme-active? token-status id)
select-theme (fn [e]
(dom/stop-propagation e)
(st/emit! (dwtl/toggle-token-theme-active id))
(on-close))]]
[:li {:key theme-id
[:li {:key (str "theme-" id)
:role "option"
:aria-selected selected?
:class (stl/css-case
@ -53,35 +54,42 @@
(modal/show! :tokens/themes {}))
(mf/defc theme-options*
[{:keys [active-theme-paths themes on-close]}]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:> themes-list* {:themes themes
:active-theme-paths active-theme-paths
:on-close on-close
:is-grouped true}]])
[:li {:class (stl/css :separator)
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true
:checked-element-button true)
:role "option"
:on-click open-tokens-theme-modal}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")]
[:> icon* {:icon-id i/arrow-right :aria-hidden true}]]])
[{:keys [tokens-lib token-status on-close]}]
(let [themes (ctob/get-theme-tree-no-hidden tokens-lib)]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:> themes-list* {:themes themes
:token-status token-status
;; :active-theme-paths active-theme-paths
:on-close on-close
:is-grouped true}]])
[:li {:class (stl/css :separator)
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true
:checked-element-button true)
:role "option"
:on-click open-tokens-theme-modal}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")]
[:> icon* {:icon-id i/arrow-right :aria-hidden true}]]]))
(mf/defc theme-selector*
[{:keys []}]
(let [;; Store
active-theme-paths (mf/deref refs/workspace-active-theme-paths-no-hidden)
active-themes-count (count active-theme-paths)
themes (mf/deref refs/workspace-token-theme-tree-no-hidden)
tokens-lib (mf/use-ctx ctx/tokens-lib)
token-status (mf/use-ctx ctx/token-status)
active-themes-count (ctos/active-themes-count token-status)
active-theme-paths (-> (ctob/get-active-theme-paths tokens-lib) ;; TODO replace by a call to ctos
(disj ctob/hidden-theme-path))
can-edit? (:can-edit (deref refs/permissions))
;; Data
current-label (cond
(> active-themes-count 1) (tr "workspace.tokens.active-themes" active-themes-count)
@ -140,7 +148,9 @@
[:& dropdown {:show is-open?
:on-close on-close-dropdown}
[:> theme-options* {:active-theme-paths active-theme-paths
:themes themes
[:> theme-options* {;;:active-theme-paths active-theme-paths
;;:themes themes
:tokens-lib tokens-lib
:token-status token-status
:on-close on-close-dropdown}]]])
container))]))