From 3cc54fd98817e483e9e0e13a973afcf79bbdc6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 14 Nov 2025 11:14:56 +0100 Subject: [PATCH] :tada: Add design tokens to plugins API (#7602) Co-authored-by: alonso.torres --- common/src/app/common/logic/tokens.cljc | 39 +- common/src/app/common/types/token.cljc | 10 +- common/src/app/common/types/tokens_lib.cljc | 9 +- .../common_tests/types/tokens_lib_test.cljc | 4 +- .../data/workspace/tokens/application.cljs | 33 +- .../data/workspace/tokens/library_edit.cljs | 166 +++++--- .../tokens/management/context_menu.cljs | 5 +- .../tokens/management/create/form.cljs | 33 +- .../tokens/management/token_pill.cljs | 2 +- .../app/main/ui/workspace/tokens/sets.cljs | 9 +- .../ui/workspace/tokens/sets/helpers.cljs | 5 +- .../workspace/tokens/themes/create_modal.cljs | 4 +- .../tokens/themes/theme_selector.cljs | 2 +- frontend/src/app/plugins/library.cljs | 7 + frontend/src/app/plugins/shape.cljs | 29 ++ frontend/src/app/plugins/tokens.cljs | 396 ++++++++++++++++++ frontend/src/app/plugins/utils.cljs | 21 + .../tokens/logic/token_data_test.cljs | 2 +- 18 files changed, 665 insertions(+), 111 deletions(-) create mode 100644 frontend/src/app/plugins/tokens.cljs diff --git a/common/src/app/common/logic/tokens.cljc b/common/src/app/common/logic/tokens.cljc index e5d6c086c7..a590051e80 100644 --- a/common/src/app/common/logic/tokens.cljc +++ b/common/src/app/common/logic/tokens.cljc @@ -9,7 +9,7 @@ [app.common.files.changes-builder :as pcb] [app.common.types.tokens-lib :as ctob])) -(defn generate-update-active-sets +(defn- generate-update-active-sets "Copy the active sets from the currently active themes and move them to the hidden token theme and update the theme with `update-theme-fn`. @@ -28,12 +28,45 @@ (pcb/set-token-theme (ctob/get-id hidden-theme) hidden-theme')))) +(defn generate-set-enabled-token-set + "Enable or disable a token set at `set-name` in `tokens-lib` without modifying a user theme." + [changes tokens-lib set-name enabled?] + (if enabled? + (generate-update-active-sets changes tokens-lib #(ctob/enable-set % set-name)) + (generate-update-active-sets changes tokens-lib #(ctob/disable-set % set-name)))) + (defn generate-toggle-token-set - "Toggle a token set at `set-name` in `tokens-lib` without modifying a - user theme." + "Toggle a token set at `set-name` in `tokens-lib` without modifying a user theme." [changes tokens-lib set-name] (generate-update-active-sets changes tokens-lib #(ctob/toggle-set % set-name))) +(defn- generate-update-active-token-theme + "Change the active state of a theme in `tokens-lib`. If after the change there is + any active theme other than the hidden one, deactivate the hidden theme." + [changes tokens-lib update-fn] + (let [active-token-themes (some-> tokens-lib + (update-fn) + (ctob/get-active-theme-paths)) + active-token-themes' (if (= active-token-themes #{ctob/hidden-theme-path}) + active-token-themes + (disj active-token-themes ctob/hidden-theme-path))] + (pcb/set-active-token-themes changes active-token-themes'))) + +(defn generate-set-active-token-theme + "Activate or deactivate a token theme in `tokens-lib`." + [changes tokens-lib id active?] + (if active? + (generate-update-active-token-theme changes tokens-lib + #(ctob/activate-theme % id)) + (generate-update-active-token-theme changes tokens-lib + #(ctob/deactivate-theme % id)))) + +(defn generate-toggle-token-theme + "Toggle the active state of a token theme in `tokens-lib`." + [changes tokens-lib id] + (generate-update-active-token-theme changes tokens-lib + #(ctob/toggle-theme-active % id))) + (defn toggle-token-set-group "Toggle a token set group at `group-path` in `tokens-lib` for a `tokens-lib-theme`." [group-path tokens-lib tokens-lib-theme] diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 59cd4f8bca..cacfc5ea48 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -310,6 +310,10 @@ schema:text-decoration schema:dimensions]) +(defn token-attr? + [attr] + (contains? all-keys attr)) + (defn shape-attr->token-attrs ([shape-attr] (shape-attr->token-attrs shape-attr nil)) ([shape-attr changed-sub-attr] @@ -403,15 +407,15 @@ :text text-attributes nil)) -(defn appliable-attrs +(defn appliable-attrs-for-shape "Returns intersection of shape `attributes` for `shape-type`." [attributes shape-type is-layout] (set/intersection attributes (shape-type->attributes shape-type is-layout))) -(defn any-appliable-attr? +(defn any-appliable-attr-for-shape? "Checks if `token-type` supports given shape `attributes`." [attributes token-type is-layout] - (seq (appliable-attrs attributes token-type is-layout))) + (d/not-empty? (appliable-attrs-for-shape attributes token-type is-layout))) ;; Token attrs that are set inside content blocks of text shapes, instead ;; at the shape level. diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 41e8f7b252..d6f273defe 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -758,7 +758,7 @@ (theme-active? [_ id] "predicate if token theme is active") (activate-theme [_ id] "adds theme from the active-themes") (deactivate-theme [_ id] "removes theme from the active-themes") - (toggle-theme-active? [_ id] "toggles theme in the active-themes") + (toggle-theme-active [_ id] "toggles theme in the active-themes") (get-hidden-theme [_] "get the hidden temporary theme")) (def schema:token-themes @@ -901,6 +901,7 @@ (delete-token [_ set-id token-id] "delete a token from a set") (toggle-set-in-theme [_ theme-id 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") + (token-set-active? [_ set-name] "if a set is active in any of the active themes") (sets-at-path-all-active? [_ group-path] "compute active state for child sets at `group-path`. Will return a value that matches this schema: `:none` None of the nested sets are active @@ -1206,7 +1207,7 @@ Will return a value that matches this schema: (when-let [theme (get-theme this id)] (contains? active-themes (get-theme-path theme)))) - (toggle-theme-active? [this id] + (toggle-theme-active [this id] (if (theme-active? this id) (deactivate-theme this id) (activate-theme this id))) @@ -1270,6 +1271,10 @@ Will return a value that matches this schema: (mapcat :sets) (get-active-themes this))) + (token-set-active? [this set-name] + (let [set-names (get-active-themes-set-names this)] + (contains? set-names set-name))) + (sets-at-path-all-active? [this group-path] (let [active-set-names (get-active-themes-set-names this) prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)] diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index 9bce6cb959..7ff924e59d 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -1558,7 +1558,7 @@ :external-id "test-id-01" :modified-at now :sets #{"core"})) - (ctob/toggle-theme-active? (thi/id :theme-1))) + (ctob/toggle-theme-active (thi/id :theme-1))) result (ctob/export-dtcg-json tokens-lib) expected {"$themes" [{"description" "" "group" "group-1" @@ -1612,7 +1612,7 @@ :external-id "test-id-01" :modified-at now :sets #{"some/set"})) - (ctob/toggle-theme-active? (thi/id :theme-1))) + (ctob/toggle-theme-active (thi/id :theme-1))) result (ctob/export-dtcg-multi-file tokens-lib) expected {"$themes.json" [{"description" "" "group" "group-1" diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index af08421052..d0f8fb61b4 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -39,6 +39,7 @@ (declare token-properties) (declare update-layout-item-margin) +(declare all-attrs-appliable-for-token?) ;; Events to update the value of attributes with applied tokens --------------------------------------------------------- @@ -519,7 +520,8 @@ (or (and (ctsl/any-layout-immediate-child? objects shape) (some ctt/spacing-margin-keys attributes)) - (ctt/any-appliable-attr? attributes (:type shape) (:layout shape)))))) + (and (ctt/any-appliable-attr-for-shape? attributes (:type shape) (:layout shape)) + (all-attrs-appliable-for-token? attributes (:type token))))))) shape-ids (d/nilv (keys shapes) []) any-variant? (->> shapes vals (some ctk/is-variant?) boolean) @@ -596,6 +598,7 @@ (watch [_ state _] (let [objects (dsh/lookup-page-objects state) shapes (into [] (keep (d/getf objects)) shape-ids) + shapes (if expand-with-children (into [] @@ -605,10 +608,15 @@ [shape]))) shapes) shapes) + {:keys [attributes all-attributes on-update-shape]} (get token-properties (:type token)) + unapply-tokens? - (cft/shapes-token-applied? token shapes (or attrs all-attributes attributes))] + (cft/shapes-token-applied? token shapes (or attrs all-attributes attributes)) + + shape-ids (map :id shapes)] + (if unapply-tokens? (rx/of (unapply-token {:attributes (or attrs all-attributes attributes) @@ -620,7 +628,7 @@ (apply-spacing-token {:token token :attr attrs :shapes shapes}) - (apply-token {:attributes (or attrs attributes) + (apply-token {:attributes (if (empty? attrs) attributes attrs) :token token :shape-ids shape-ids :on-update-shape on-update-shape})))))))) @@ -808,3 +816,22 @@ (defn get-token-properties [token] (get token-properties (:type token))) + +(defn get-update-shape-fn + "Get the function that updates the attributes of a shape if this token is applied." + [token] + (when token + (-> (get-token-properties token) + :on-update-shape))) + +(defn appliable-attributes-for-token + "Get the attributes to which this token type can be applied." + [token-type] + (let [props (get token-properties token-type)] + (or (:all-attributes props) + (:attributes props)))) + +(defn all-attrs-appliable-for-token? + "Check if any of the given attributes can be applied for the given token type." + [attributes token-type] + (set/subset? attributes (appliable-attributes-for-token token-type))) diff --git a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs index b5e63979c7..63b883a48f 100644 --- a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs +++ b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs @@ -84,7 +84,8 @@ new-token-theme))] (rx/of (dch/commit-changes changes))))))))) -(defn update-token-theme [id token-theme] +(defn update-token-theme + [id token-theme] (ptk/reify ::update-token-theme ptk/WatchEvent (watch [it state _] @@ -101,27 +102,38 @@ (pcb/set-token-theme (ctob/get-id token-theme) token-theme))] (rx/of (dch/commit-changes changes)))))))) -(defn toggle-token-theme-active? [id] - (ptk/reify ::toggle-token-theme-active? +(defn set-token-theme-active + [id active?] + (assert (uuid? id) "expected a uuid for `id`") + (assert (boolean? active?) "expected a boolean for `active?`") + (ptk/reify ::set-token-theme-active + ptk/WatchEvent + (watch [_ state _] + (let [data (dsh/lookup-file-data state) + tokens-lib (get-tokens-lib state) + changes (-> (pcb/empty-changes) + (pcb/with-library-data data) + (clt/generate-set-active-token-theme tokens-lib id active?))] + + (rx/of (dch/commit-changes changes) + (dwtp/propagate-workspace-tokens)))))) + +(defn toggle-token-theme-active + [id] + (ptk/reify ::toggle-token-theme-active ptk/WatchEvent (watch [it state _] (let [data (dsh/lookup-file-data state) - tokens-lib (get-tokens-lib state) - active-token-themes (some-> tokens-lib - (ctob/toggle-theme-active? id) - (ctob/get-active-theme-paths)) - active-token-themes' (if (= active-token-themes #{ctob/hidden-theme-path}) - active-token-themes - (disj active-token-themes ctob/hidden-theme-path)) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/set-active-token-themes active-token-themes'))] + (clt/generate-toggle-token-theme tokens-lib id))] (rx/of (dch/commit-changes changes) (dwtp/propagate-workspace-tokens)))))) -(defn delete-token-theme [id] +(defn delete-token-theme + [id] (ptk/reify ::delete-token-theme ptk/WatchEvent (watch [it state _] @@ -134,7 +146,7 @@ (dwtp/propagate-workspace-tokens)))))) (defn create-token-set - [set-name] + [token-set] (ptk/reify ::create-token-set ptk/UpdateEvent (update [_ state] @@ -145,20 +157,20 @@ (watch [it state _] (let [data (dsh/lookup-file-data state) tokens-lib (get data :tokens-lib) - set-name (ctob/normalize-set-name set-name)] - (if (and tokens-lib (ctob/get-set-by-name tokens-lib set-name)) + token-set (ctob/rename token-set (ctob/normalize-set-name (ctob/get-name token-set)))] + (if (and tokens-lib (ctob/get-set-by-name tokens-lib (ctob/get-name token-set))) (rx/of (ntf/show {:content (tr "errors.token-set-already-exists") :type :toast :level :error :timeout 9000})) - (let [token-set (ctob/make-token-set :name set-name) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/set-token-set (ctob/get-id token-set) token-set))] + (let [changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/set-token-set (ctob/get-id token-set) token-set))] (rx/of (set-selected-token-set-id (ctob/get-id token-set)) (dch/commit-changes changes)))))))) -(defn rename-token-set-group [set-group-path set-group-fname] +(defn rename-token-set-group + [set-group-path set-group-fname] (ptk/reify ::rename-token-set-group ptk/WatchEvent (watch [it _state _] @@ -203,6 +215,22 @@ (rx/of (set-selected-token-set-id (ctob/get-id token-set)) (dch/commit-changes changes)))))))) +(defn set-enabled-token-set + [name enabled?] + (assert (string? name) "expected a string for `name`") + (assert (boolean? enabled?) "expected a boolean for `enabled?`") + (ptk/reify ::set-enabled-token-set + ptk/WatchEvent + (watch [_ state _] + (let [data (dsh/lookup-file-data state) + tlib (get-tokens-lib state) + changes (-> (pcb/empty-changes) + (pcb/with-library-data data) + (clt/generate-set-enabled-token-set tlib name enabled?))] + + (rx/of (dch/commit-changes changes) + (dwtp/propagate-workspace-tokens)))))) + (defn toggle-token-set [name] (assert (string? name) "expected a string for `name`") @@ -218,7 +246,8 @@ (rx/of (dch/commit-changes changes) (dwtp/propagate-workspace-tokens)))))) -(defn toggle-token-set-group [group-path] +(defn toggle-token-set-group + [group-path] (ptk/reify ::toggle-token-set-group ptk/WatchEvent (watch [_ state _] @@ -230,7 +259,8 @@ (dch/commit-changes changes) (dwtp/propagate-workspace-tokens)))))) -(defn import-tokens-lib [lib] +(defn import-tokens-lib + [lib] (ptk/reify ::import-tokens-lib ptk/WatchEvent (watch [it state _] @@ -265,7 +295,8 @@ (rx/of (dch/commit-changes changes) (dwtp/propagate-workspace-tokens)))))) -(defn drop-error [{:keys [error to-path]}] +(defn drop-error + [{:keys [error to-path]}] (ptk/reify ::drop-error ptk/WatchEvent (watch [_ _ _] @@ -282,7 +313,8 @@ ;; FIXME: add schema for params -(defn drop-token-set-group [drop-opts] +(defn drop-token-set-group + [drop-opts] (ptk/reify ::drop-token-set-group ptk/WatchEvent (watch [it state _] @@ -344,47 +376,52 @@ (set-selected-token-set-id (ctob/get-id token-set))))))) (defn create-token - [params] - (let [token (ctob/make-token params)] - (ptk/reify ::create-token - ptk/WatchEvent - (watch [it state _] - (if-let [token-set (lookup-token-set state)] - (let [data (dsh/lookup-file-data state) - token-type (:type token) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/set-token (ctob/get-id token-set) - (:id token) - token))] + ([token] (create-token nil token)) + ([set-id token] + (ptk/reify ::create-token + ptk/WatchEvent + (watch [it state _] + (if-let [token-set (if set-id + (lookup-token-set state set-id) + (lookup-token-set state))] + (let [data (dsh/lookup-file-data state) + token-type (:type token) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/set-token (ctob/get-id token-set) + (:id token) + token))] - (rx/of (dch/commit-changes changes) - (ptk/data-event ::ev/event {::ev/name "create-token" :type token-type}))) + (rx/of (dch/commit-changes changes) + (ptk/data-event ::ev/event {::ev/name "create-token" :type token-type}))) - (rx/of (create-token-with-set token))))))) + (rx/of (create-token-with-set token))))))) (defn update-token - [id params] - (assert (uuid? id) "expected uuid for `id`") + ([id params] (update-token nil id params)) + ([set-id id params] + (assert (uuid? id) "expected uuid for `id`") - (ptk/reify ::update-token - ptk/WatchEvent - (watch [it state _] - (let [token-set (lookup-token-set state) - data (dsh/lookup-file-data state) - token (-> (get-tokens-lib state) - (ctob/get-token (ctob/get-id token-set) id)) - token' (->> (merge token params) - (into {}) - (ctob/make-token)) - token-type (:type token) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/set-token (ctob/get-id token-set) - id - token'))] - (rx/of (dch/commit-changes changes) - (ptk/data-event ::ev/event {::ev/name "edit-token" :type token-type})))))) + (ptk/reify ::update-token + ptk/WatchEvent + (watch [it state _] + (let [token-set (if set-id + (lookup-token-set state set-id) + (lookup-token-set state)) + data (dsh/lookup-file-data state) + token (-> (get-tokens-lib state) + (ctob/get-token (ctob/get-id token-set) id)) + token' (->> (merge token params) + (into {}) + (ctob/make-token)) + token-type (:type token) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/set-token (ctob/get-id token-set) + id + token'))] + (rx/of (dch/commit-changes changes) + (ptk/data-event ::ev/event {::ev/name "edit-token" :type token-type}))))))) (defn delete-token [set-id token-id] @@ -413,10 +450,11 @@ (let [tokens (vals (ctob/get-tokens tokens-lib (ctob/get-id token-set))) unames (map :name tokens) suffix (tr "workspace.tokens.duplicate-suffix") - copy-name (cfh/generate-unique-name (:name token) unames :suffix suffix)] - (rx/of (create-token (assoc token - :id (uuid/next) - :name copy-name)))))))))) + copy-name (cfh/generate-unique-name (:name token) unames :suffix suffix) + new-token (-> token + (ctob/reid (uuid/next)) + (ctob/rename copy-name))] + (rx/of (create-token new-token))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TOKEN UI OPS diff --git a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs index 1d361f2fd3..1b26079247 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs @@ -55,10 +55,7 @@ (defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape hint allowed-shape-attributes]}] (let [allowed-attributes (set/intersection attributes allowed-shape-attributes) - on-update-shape-fn - (or on-update-shape - (-> (dwta/get-token-properties token) - (:on-update-shape))) + on-update-shape-fn (or on-update-shape (dwta/get-update-shape-fn token)) {:keys [selected-pred shape-ids]} (attribute-actions token selected-shapes allowed-attributes)] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs b/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs index 2652c35395..3a0d9720bb 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/create/form.cljs @@ -69,6 +69,12 @@ [:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))} #(not (cft/token-name-path-exists? % tokens-tree))]]) +(defn validate-token-name + [tokens-tree name] + (let [schema (make-token-name-schema tokens-tree) + explainer (sm/explainer schema)] + (-> name explainer sm/simplify not-empty))) + (def ^:private schema:token-description [:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]) @@ -363,27 +369,20 @@ ;; Allow setting editing token to it's own path (d/dissoc-in token-path))) - validate-token-name - (mf/with-memo [tokens-tree-in-selected-set] - (let [schema (make-token-name-schema tokens-tree-in-selected-set) - explainer (sm/explainer schema)] - (fn [name] - (-> name explainer sm/simplify not-empty)))) - on-blur-name (mf/use-fn - (mf/deps touched-name? validate-token-name) + (mf/deps touched-name?) (fn [e] (let [value (dom/get-target-val e) - errors (validate-token-name value)] + errors (validate-token-name tokens-tree-in-selected-set value)] (when touched-name? (reset! warning-name-change* true)) (reset! name-errors* errors)))) on-update-name-debounced - (mf/with-memo [touched-name? validate-token-name] + (mf/with-memo [touched-name?] (uf/debounce (fn [token-name] (when touched-name? - (reset! name-errors* (validate-token-name token-name)))) + (reset! name-errors* (validate-token-name tokens-tree-in-selected-set token-name)))) 300)) on-update-name @@ -487,7 +486,7 @@ on-submit (mf/use-fn - (mf/deps is-create token active-theme-tokens validate-token validate-token-name validate-token-description) + (mf/deps is-create token active-theme-tokens validate-token validate-token-description) (fn [e] (dom/prevent-default e) ;; We have to re-validate the current form values before submitting @@ -496,7 +495,7 @@ ;; and press enter before the next validations could return. (let [clean-name (clean-name (mf/ref-val token-name-ref)) - valid-name? (empty? (validate-token-name clean-name)) + valid-name? (empty? (validate-token-name tokens-tree-in-selected-set clean-name)) value (mf/ref-val value-ref) clean-description (mf/ref-val description-ref) @@ -512,10 +511,10 @@ (fn [valid-token] (st/emit! (if is-create - (dwtl/create-token {:name clean-name - :type token-type - :value (:value valid-token) - :description clean-description}) + (dwtl/create-token (ctob/make-token {:name clean-name + :type token-type + :value (:value valid-token) + :description clean-description})) (dwtl/update-token (:id token) {:name clean-name diff --git a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs index e93a69c2aa..dd001e187c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/token_pill.cljs @@ -166,7 +166,7 @@ ;; Edge-case for allowing margin attribute on shapes inside layout parent (and selected-inside-layout? (set/subset? ctt/spacing-margin-keys attrs)) (some (fn [shape] - (ctt/any-appliable-attr? attrs (:type shape) (:layout shape))) + (ctt/any-appliable-attr-for-shape? attrs (:type shape) (:layout shape))) selected-shapes))) (def token-types-with-status-icon diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs index b46b9f0aba..d530394b99 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs @@ -33,15 +33,12 @@ can-edit? (mf/use-ctx ctx/can-edit?) - active-token-sets-names - (mf/with-memo [tokens-lib] - (some-> tokens-lib (ctob/get-active-themes-set-names))) - token-set-active? (mf/use-fn - (mf/deps active-token-sets-names) + (mf/deps tokens-lib) (fn [name] - (contains? active-token-sets-names name))) + (when tokens-lib + (ctob/token-set-active? tokens-lib name)))) token-set-group-active? (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/tokens/sets/helpers.cljs b/frontend/src/app/main/ui/workspace/tokens/sets/helpers.cljs index b4ad5e568f..ab6331bd47 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets/helpers.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets/helpers.cljs @@ -28,7 +28,8 @@ (if-let [parent-path (ctob/get-set-path parent-set)] (->> (concat parent-path (ctob/split-set-name name)) (ctob/join-set-path)) - (ctob/normalize-set-name name))] + (ctob/normalize-set-name name)) + token-set (ctob/make-token-set :name name)] (st/emit! (ptk/data-event ::ev/event {::ev/name "create-token-set" :name name}) - (dwtl/create-token-set name)))) + (dwtl/create-token-set token-set)))) diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs index 7eb31aa15b..a2c9e1447b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes/create_modal.cljs @@ -51,7 +51,7 @@ true)) ;; if still no library exists, cannot be duplicate :type-properties {:error/fn #(tr "workspace.tokens.theme-name-already-exists")}})) -(defn- validate-theme-name +(defn validate-theme-name [tokens-lib group theme-id name] (let [schema (theme-name-schema {:tokens-lib tokens-lib :theme-id theme-id :group group}) validation (m/explain schema (str/trim name))] @@ -151,7 +151,7 @@ [:div {:on-click (fn [e] (dom/prevent-default e) (dom/stop-propagation e) - (st/emit! (dwtl/toggle-token-theme-active? id)))} + (st/emit! (dwtl/toggle-token-theme-active id)))} [:& switch {:name (tr "workspace.tokens.theme-name" name) :on-change (constantly nil) :selected? selected?}]]] diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs index a4289545b1..3d799e0b59 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs @@ -31,7 +31,7 @@ selected? (get active-theme-paths theme-id) select-theme (fn [e] (dom/stop-propagation e) - (st/emit! (dwtl/toggle-token-theme-active? id)) + (st/emit! (dwtl/toggle-token-theme-active id)) (on-close))]] [:li {:key theme-id :role "option" diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index 9391c0223a..0d3c8c1d07 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -28,6 +28,7 @@ [app.plugins.register :as r] [app.plugins.shape :as shape] [app.plugins.text :as text] + [app.plugins.tokens :as tokens] [app.plugins.utils :as u] [app.util.object :as obj] [beicon.v2.core :as rx] @@ -959,6 +960,12 @@ (map #(lib-component-proxy plugin-id file-id %)))] (apply array components)))} + :tokens + {:this true + :get + (fn [_] + (tokens/tokens-catalog plugin-id file-id))} + :createColor (fn [] (cond diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 2272bfb9c8..6224920783 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -31,6 +31,7 @@ [app.common.types.shape.radius :as ctsr] [app.common.types.shape.shadow :as ctss] [app.common.types.text :as txt] + [app.common.types.token :as cto] [app.common.uuid :as uuid] [app.main.data.plugins :as dp] [app.main.data.workspace :as dw] @@ -42,6 +43,7 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.texts :as dwt] + [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.variants :as dwv] [app.main.repo :as rp] [app.main.store :as st] @@ -765,6 +767,8 @@ ;; Interactions + + :interactions {:this true :get @@ -1224,6 +1228,31 @@ (let [guide (u/proxy->ruler-guide value)] (st/emit! (dwgu/remove-guide guide))))) + :tokens + {:this true + :get + (fn [_] + (let [tokens + (-> (u/locate-shape file-id page-id id) + (get :applied-tokens))] + (reduce + (fn [acc [prop name]] + (obj/set! acc (d/name prop) name)) + #js {} + tokens)))} + + :applyToken + (fn [token attrs] + (let [token (u/locate-token file-id (obj/get token "$set-id") (obj/get token "$id")) + kw-attrs (into #{} (map keyword attrs))] + (if (some #(not (cto/token-attr? %)) kw-attrs) + (u/display-not-valid :applyToken attrs) + (st/emit! + (dwta/toggle-token {:token token + :attrs kw-attrs + :shape-ids [id] + :expand-with-children false}))))) + :isVariantHead (fn [] (let [shape (u/locate-shape file-id page-id id) diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs new file mode 100644 index 0000000000..fc0d0bb11b --- /dev/null +++ b/frontend/src/app/plugins/tokens.cljs @@ -0,0 +1,396 @@ +;; 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.plugins.tokens + (:require + [app.common.data.macros :as dm] + [app.common.types.token :as cto] + [app.common.types.tokens-lib :as ctob] + [app.common.uuid :as uuid] + [app.main.data.workspace.tokens.application :as dwta] + [app.main.data.workspace.tokens.library-edit :as dwtl] + [app.main.store :as st] + [app.main.ui.workspace.tokens.management.create.form :as token-form] + [app.main.ui.workspace.tokens.themes.create-modal :as theme-form] + [app.plugins.utils :as u] + [app.util.object :as obj] + [clojure.datafy :refer [datafy]])) + +(defn- apply-token-to-shapes + [file-id set-id id shape-ids attrs] + (let [token (u/locate-token file-id set-id id) + kw-attrs (into #{} (map keyword attrs))] + (if (some #(not (cto/token-attr? %)) kw-attrs) + (u/display-not-valid :applyToSelected attrs) + (st/emit! + (dwta/toggle-token {:token token + :attrs kw-attrs + :shape-ids shape-ids + :expand-with-children false}))))) + +(defn token-proxy + [plugin-id file-id set-id id] + (obj/reify {:name "TokenSetProxy"} + :$plugin {:enumerable false :get (constantly plugin-id)} + :$file-id {:enumerable false :get (constantly file-id)} + :$set-id {:enumerable false :get (constantly set-id)} + :$id {:enumerable false :get (constantly id)} + + :id + {:get #(dm/str id)} + + :name + {:this true + :get + (fn [_] + (let [token (u/locate-token file-id set-id id)] + (ctob/get-name token))) + :set + (fn [_ value] + (let [tokens-lib (u/locate-tokens-lib file-id) + errors (token-form/validate-token-name + (ctob/get-tokens tokens-lib set-id) + value)] + (cond + (some? errors) + (u/display-not-valid :name (first errors)) + + :else + (st/emit! (dwtl/update-token set-id id {:name value})))))} + + :type + {:this true + :get + (fn [_] + (let [token (u/locate-token file-id set-id id)] + (-> (:type token) (cto/token-type->dtcg-token-type))))} + + :value + {:this true + :get + (fn [_] + (let [token (u/locate-token file-id set-id id)] + (:value token)))} + + :description + {:this true + :get + (fn [_] + (let [token (u/locate-token file-id set-id id)] + (ctob/get-description token)))} + + :duplicate + (fn [] + (let [token (u/locate-token file-id set-id id) + token' (ctob/make-token (-> (datafy token) + (dissoc :id + :modified-at)))] + (st/emit! (dwtl/create-token set-id token')) + (token-proxy plugin-id file-id set-id (:id token')))) + + :remove + (fn [] + (st/emit! (dwtl/delete-token set-id id))) + + :applyToShapes + (fn [shapes attrs] + (apply-token-to-shapes file-id set-id id (map :id shapes) attrs)) + + :applyToSelected + (fn [attrs] + (let [selected (get-in @st/state [:workspace-local :selected])] + (apply-token-to-shapes file-id set-id id selected attrs))))) + +(defn token-set-proxy + [plugin-id file-id id] + (obj/reify {:name "TokenSetProxy"} + :$plugin {:enumerable false :get (constantly plugin-id)} + :$file-id {:enumerable false :get (constantly file-id)} + :$id {:enumerable false :get (constantly id)} + + :id + {:get #(dm/str id)} + + :name + {:this true + :get + (fn [_] + (let [set (u/locate-token-set file-id id)] + (ctob/get-name set))) + :set + (fn [_ value] + (let [set (u/locate-token-set file-id id)] + (cond + (not (string? value)) + (u/display-not-valid :name value) + + :else + (st/emit! (dwtl/update-token-set set value)))))} + + :active + {:this true + :enumerable false + :get + (fn [_] + (let [tokens-lib (u/locate-tokens-lib file-id) + set (u/locate-token-set file-id id)] + (ctob/token-set-active? tokens-lib (ctob/get-name set)))) + :set + (fn [_ value] + (let [set (u/locate-token-set file-id id)] + (st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) value))))} + + :toggleActive + (fn [_] + (let [set (u/locate-token-set file-id id)] + (st/emit! (dwtl/toggle-token-set (ctob/get-name set))))) + + :tokens + {:this true + :enumerable false + :get + (fn [_] + (let [file (u/locate-file file-id) + tokens-lib (->> file :data :tokens-lib)] + (->> (ctob/get-tokens tokens-lib id) + (vals) + (map #(token-proxy plugin-id file-id id (:id %))) + (apply array))))} + + :tokensByType + {:this true + :enumerable false + :get + (fn [_] + (let [file (u/locate-file file-id) + tokens-lib (->> file :data :tokens-lib) + tokens (ctob/get-tokens tokens-lib id)] + (->> tokens + (vals) + (sort-by :name) + (group-by #(cto/token-type->dtcg-token-type (:type %))) + (into []) + (mapv (fn [[type tokens]] + #js [(name type) + (->> tokens + (map #(token-proxy plugin-id file-id id (:id %))) + (apply array))])) + (apply array))))} + + :getTokenById + (fn [token-id] + (cond + (not (string? token-id)) + (u/display-not-valid :getTokenById token-id) + + :else + (let [token-id (uuid/parse token-id) + token (u/locate-token file-id id token-id)] + (when (some? token) + (token-proxy plugin-id file-id id token-id))))) + + :addToken + (fn [type-str name value] + (let [type (cto/dtcg-token-type->token-type type-str)] + (cond + (nil? type) + (u/display-not-valid :addTokenType type-str) + + (not (string? name)) + (u/display-not-valid :addTokenName name) + + :else + (let [token (ctob/make-token {:type type + :name name + :value value})] + (st/emit! (dwtl/create-token id token)) + (token-proxy plugin-id file-id (:id set) (:id token)))))) + + :duplicate + (fn [] + (let [set (u/locate-token-set file-id id) + set' (ctob/make-token-set (-> (datafy set) + (dissoc :id + :modified-at)))] + (st/emit! (dwtl/create-token-set set')) + (token-set-proxy plugin-id file-id (:id set')))) + + :remove + (fn [] + (st/emit! (dwtl/delete-token-set id))))) + +(defn token-theme-proxy + [plugin-id file-id id] + (obj/reify {:name "TokenThemeProxy"} + :$plugin {:enumerable false :get (constantly plugin-id)} + :$file-id {:enumerable false :get (constantly file-id)} + :$id {:enumerable false :get (constantly id)} + + :id + {:get #(dm/str id)} + + :external-id + {:this true + :get + (fn [_] + (let [theme (u/locate-token-theme file-id id)] + (:external-id theme)))} + + :group + {:this true + :get + (fn [_] + (let [theme (u/locate-token-theme file-id id)] + (:group theme))) + :set + (fn [_ value] + (let [theme (u/locate-token-theme file-id id)] + (cond + (not (string? value)) + (u/display-not-valid :group value) + + :else + (st/emit! (dwtl/update-token-theme id (assoc theme :group value))))))} + + :name + {:this true + :get + (fn [_] + (let [theme (u/locate-token-theme file-id id)] + (:name theme))) + :set + (fn [_ value] + (let [theme (u/locate-token-theme file-id id) + errors (theme-form/validate-theme-name + (u/locate-tokens-lib file-id) + (:group theme) + id + value)] + (cond + (some? errors) + (u/display-not-valid :name (first errors)) + + :else + (st/emit! (dwtl/update-token-theme id (assoc theme :name value))))))} + + :active + {:this true + :enumerable false + :get + (fn [_] + (let [tokens-lib (u/locate-tokens-lib file-id)] + (ctob/theme-active? tokens-lib id))) + :set + (fn [_ value] + (st/emit! (dwtl/set-token-theme-active id value)))} + + :toggleActive + (fn [_] + (st/emit! (dwtl/toggle-token-theme-active id))) + + :activeSets + {:this true :get (fn [_])} + + :addSet + (fn [tokenSet] + (let [theme (u/locate-token-theme file-id id)] + (st/emit! (dwtl/update-token-theme id (ctob/enable-set theme (obj/get tokenSet :name)))))) + + :removeSet + (fn [tokenSet] + (let [theme (u/locate-token-theme file-id id)] + (st/emit! (dwtl/update-token-theme id (ctob/disable-set theme (obj/get tokenSet :name)))))) + + :duplicate + (fn [] + (let [theme (u/locate-token-theme file-id id) + theme' (ctob/make-token-theme (-> (datafy theme) + (dissoc :id + :modified-at)))] + (st/emit! (dwtl/create-token-theme theme')) + (token-theme-proxy plugin-id file-id (:id theme')))) + + :remove + (fn [] + (st/emit! (dwtl/delete-token-theme id))))) + +(defn tokens-catalog + [plugin-id file-id] + (obj/reify {:name "TokensCatalog"} + :$plugin {:enumerable false :get (constantly plugin-id)} + :$id {:enumerable false :get (constantly file-id)} + + :themes + {:this true + :enumerable false + :get + (fn [_] + (let [file (u/locate-file file-id) + tokens-lib (->> file :data :tokens-lib) + themes (->> (ctob/get-themes tokens-lib) + (remove #(= (:id %) uuid/zero)))] + (apply array (map #(token-theme-proxy plugin-id file-id (ctob/get-id %)) themes))))} + + :sets + {:this true + :enumerable false + :get + (fn [_] + (let [file (u/locate-file file-id) + tokens-lib (->> file :data :tokens-lib) + sets (ctob/get-sets tokens-lib)] + (apply array (map #(token-set-proxy plugin-id file-id (ctob/get-id %)) sets))))} + + :addTheme + (fn [group name] + (cond + (not (string? group)) + (u/display-not-valid :addThemeGroup group) + + (not (string? name)) + (u/display-not-valid :addThemeName name) + + :else + (let [theme (ctob/make-token-theme {:group group + :name name})] + (st/emit! (dwtl/create-token-theme theme)) + (token-theme-proxy plugin-id file-id (:id theme))))) + + :addSet + (fn [name] + (cond + (not (string? name)) + (u/display-not-valid :addSetName name) + + :else + (let [set (ctob/make-token-set {:name name})] + (st/emit! (dwtl/create-token-set set)) + (token-set-proxy plugin-id file-id (:id set))))) + + :getThemeById + (fn [theme-id] + (cond + (not (string? theme-id)) + (u/display-not-valid :getThemeById theme-id) + + :else + (let [theme-id (uuid/parse theme-id) + theme (u/locate-token-theme file-id theme-id)] + (when (some? theme) + (token-theme-proxy plugin-id file-id theme-id))))) + + :getSetById + (fn [set-id] + (cond + (not (string? set-id)) + (u/display-not-valid :getSetById set-id) + + :else + (let [set-id (uuid/parse set-id) + set (u/locate-token-set file-id set-id)] + (when (some? set) + (token-set-proxy plugin-id file-id set-id))))))) + diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 758a6d648b..907a8dd352 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.tokens-lib :as ctob] [app.main.data.helpers :as dsh] [app.main.store :as st] [app.util.object :as obj])) @@ -52,6 +53,26 @@ (assert (uuid? id) "Component not valid uuid") (dm/get-in (locate-file file-id) [:data :components id])) +(defn locate-tokens-lib + [file-id] + (let [file (locate-file file-id)] + (->> file :data :tokens-lib))) + +(defn locate-token-theme + [file-id id] + (let [tokens-lib (locate-tokens-lib file-id)] + (ctob/get-theme tokens-lib id))) + +(defn locate-token-set + [file-id set-id] + (let [tokens-lib (locate-tokens-lib file-id)] + (ctob/get-set tokens-lib set-id))) + +(defn locate-token + [file-id set-id token-id] + (let [tokens-lib (locate-tokens-lib file-id)] + (ctob/get-token tokens-lib set-id token-id))) + (defn locate-presence [session-id] (dm/get-in @st/state [:workspace-presence session-id])) diff --git a/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs index 3314d88d7c..c7f6a2e59c 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs @@ -36,7 +36,7 @@ done (let [file (setup-file-with-token-lib) store (ths/setup-store file) - events [(dwtl/create-token-set "Set B")]] + events [(dwtl/create-token-set (ctob/make-token-set :name "Set B"))]] (tohs/run-store-async store done events