🎉 Add design tokens to plugins API (#7602)

Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
This commit is contained in:
Andrés Moya
2025-11-14 11:14:56 +01:00
committed by GitHub
parent 2233f34a15
commit 3cc54fd988
18 changed files with 665 additions and 111 deletions

View File

@@ -9,7 +9,7 @@
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.types.tokens-lib :as ctob])) [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 "Copy the active sets from the currently active themes and move them
to the hidden token theme and update the theme with to the hidden token theme and update the theme with
`update-theme-fn`. `update-theme-fn`.
@@ -28,12 +28,45 @@
(pcb/set-token-theme (ctob/get-id hidden-theme) (pcb/set-token-theme (ctob/get-id hidden-theme)
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 (defn generate-toggle-token-set
"Toggle a token set at `set-name` in `tokens-lib` without modifying a "Toggle a token set at `set-name` in `tokens-lib` without modifying a user theme."
user theme."
[changes tokens-lib set-name] [changes tokens-lib set-name]
(generate-update-active-sets changes tokens-lib #(ctob/toggle-set % 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 (defn toggle-token-set-group
"Toggle a token set group at `group-path` in `tokens-lib` for a `tokens-lib-theme`." "Toggle a token set group at `group-path` in `tokens-lib` for a `tokens-lib-theme`."
[group-path tokens-lib tokens-lib-theme] [group-path tokens-lib tokens-lib-theme]

View File

@@ -310,6 +310,10 @@
schema:text-decoration schema:text-decoration
schema:dimensions]) schema:dimensions])
(defn token-attr?
[attr]
(contains? all-keys attr))
(defn shape-attr->token-attrs (defn shape-attr->token-attrs
([shape-attr] (shape-attr->token-attrs shape-attr nil)) ([shape-attr] (shape-attr->token-attrs shape-attr nil))
([shape-attr changed-sub-attr] ([shape-attr changed-sub-attr]
@@ -403,15 +407,15 @@
:text text-attributes :text text-attributes
nil)) nil))
(defn appliable-attrs (defn appliable-attrs-for-shape
"Returns intersection of shape `attributes` for `shape-type`." "Returns intersection of shape `attributes` for `shape-type`."
[attributes shape-type is-layout] [attributes shape-type is-layout]
(set/intersection attributes (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`." "Checks if `token-type` supports given shape `attributes`."
[attributes token-type is-layout] [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 ;; Token attrs that are set inside content blocks of text shapes, instead
;; at the shape level. ;; at the shape level.

View File

@@ -758,7 +758,7 @@
(theme-active? [_ id] "predicate if token theme is active") (theme-active? [_ id] "predicate if token theme is active")
(activate-theme [_ id] "adds theme from the active-themes") (activate-theme [_ id] "adds theme from the active-themes")
(deactivate-theme [_ id] "removes 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")) (get-hidden-theme [_] "get the hidden temporary theme"))
(def schema:token-themes (def schema:token-themes
@@ -901,6 +901,7 @@
(delete-token [_ set-id token-id] "delete a token from a set") (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") (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") (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`. (sets-at-path-all-active? [_ group-path] "compute active state for child sets at `group-path`.
Will return a value that matches this schema: Will return a value that matches this schema:
`:none` None of the nested sets are active `: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)] (when-let [theme (get-theme this id)]
(contains? active-themes (get-theme-path theme)))) (contains? active-themes (get-theme-path theme))))
(toggle-theme-active? [this id] (toggle-theme-active [this id]
(if (theme-active? this id) (if (theme-active? this id)
(deactivate-theme this id) (deactivate-theme this id)
(activate-theme this id))) (activate-theme this id)))
@@ -1270,6 +1271,10 @@ Will return a value that matches this schema:
(mapcat :sets) (mapcat :sets)
(get-active-themes this))) (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] (sets-at-path-all-active? [this group-path]
(let [active-set-names (get-active-themes-set-names this) (let [active-set-names (get-active-themes-set-names this)
prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)] prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)]

View File

@@ -1558,7 +1558,7 @@
:external-id "test-id-01" :external-id "test-id-01"
:modified-at now :modified-at now
:sets #{"core"})) :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) result (ctob/export-dtcg-json tokens-lib)
expected {"$themes" [{"description" "" expected {"$themes" [{"description" ""
"group" "group-1" "group" "group-1"
@@ -1612,7 +1612,7 @@
:external-id "test-id-01" :external-id "test-id-01"
:modified-at now :modified-at now
:sets #{"some/set"})) :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) result (ctob/export-dtcg-multi-file tokens-lib)
expected {"$themes.json" [{"description" "" expected {"$themes.json" [{"description" ""
"group" "group-1" "group" "group-1"

View File

@@ -39,6 +39,7 @@
(declare token-properties) (declare token-properties)
(declare update-layout-item-margin) (declare update-layout-item-margin)
(declare all-attrs-appliable-for-token?)
;; Events to update the value of attributes with applied tokens --------------------------------------------------------- ;; Events to update the value of attributes with applied tokens ---------------------------------------------------------
@@ -519,7 +520,8 @@
(or (or
(and (ctsl/any-layout-immediate-child? objects shape) (and (ctsl/any-layout-immediate-child? objects shape)
(some ctt/spacing-margin-keys attributes)) (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) []) shape-ids (d/nilv (keys shapes) [])
any-variant? (->> shapes vals (some ctk/is-variant?) boolean) any-variant? (->> shapes vals (some ctk/is-variant?) boolean)
@@ -596,6 +598,7 @@
(watch [_ state _] (watch [_ state _]
(let [objects (dsh/lookup-page-objects state) (let [objects (dsh/lookup-page-objects state)
shapes (into [] (keep (d/getf objects)) shape-ids) shapes (into [] (keep (d/getf objects)) shape-ids)
shapes shapes
(if expand-with-children (if expand-with-children
(into [] (into []
@@ -605,10 +608,15 @@
[shape]))) [shape])))
shapes) shapes)
shapes) shapes)
{:keys [attributes all-attributes on-update-shape]} {:keys [attributes all-attributes on-update-shape]}
(get token-properties (:type token)) (get token-properties (:type token))
unapply-tokens? 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? (if unapply-tokens?
(rx/of (rx/of
(unapply-token {:attributes (or attrs all-attributes attributes) (unapply-token {:attributes (or attrs all-attributes attributes)
@@ -620,7 +628,7 @@
(apply-spacing-token {:token token (apply-spacing-token {:token token
:attr attrs :attr attrs
:shapes shapes}) :shapes shapes})
(apply-token {:attributes (or attrs attributes) (apply-token {:attributes (if (empty? attrs) attributes attrs)
:token token :token token
:shape-ids shape-ids :shape-ids shape-ids
:on-update-shape on-update-shape})))))))) :on-update-shape on-update-shape}))))))))
@@ -808,3 +816,22 @@
(defn get-token-properties [token] (defn get-token-properties [token]
(get token-properties (:type 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)))

View File

@@ -84,7 +84,8 @@
new-token-theme))] new-token-theme))]
(rx/of (dch/commit-changes changes))))))))) (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/reify ::update-token-theme
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
@@ -101,27 +102,38 @@
(pcb/set-token-theme (ctob/get-id token-theme) token-theme))] (pcb/set-token-theme (ctob/get-id token-theme) token-theme))]
(rx/of (dch/commit-changes changes)))))))) (rx/of (dch/commit-changes changes))))))))
(defn toggle-token-theme-active? [id] (defn set-token-theme-active
(ptk/reify ::toggle-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 ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
tokens-lib (get-tokens-lib 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) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/set-active-token-themes active-token-themes'))] (clt/generate-toggle-token-theme tokens-lib id))]
(rx/of (rx/of
(dch/commit-changes changes) (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn delete-token-theme [id] (defn delete-token-theme
[id]
(ptk/reify ::delete-token-theme (ptk/reify ::delete-token-theme
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
@@ -134,7 +146,7 @@
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn create-token-set (defn create-token-set
[set-name] [token-set]
(ptk/reify ::create-token-set (ptk/reify ::create-token-set
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@@ -145,20 +157,20 @@
(watch [it state _] (watch [it state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
tokens-lib (get data :tokens-lib) tokens-lib (get data :tokens-lib)
set-name (ctob/normalize-set-name 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 set-name)) (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") (rx/of (ntf/show {:content (tr "errors.token-set-already-exists")
:type :toast :type :toast
:level :error :level :error
:timeout 9000})) :timeout 9000}))
(let [token-set (ctob/make-token-set :name set-name) (let [changes (-> (pcb/empty-changes it)
changes (-> (pcb/empty-changes it) (pcb/with-library-data data)
(pcb/with-library-data data) (pcb/set-token-set (ctob/get-id token-set) token-set))]
(pcb/set-token-set (ctob/get-id token-set) token-set))]
(rx/of (set-selected-token-set-id (ctob/get-id token-set)) (rx/of (set-selected-token-set-id (ctob/get-id token-set))
(dch/commit-changes changes)))))))) (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/reify ::rename-token-set-group
ptk/WatchEvent ptk/WatchEvent
(watch [it _state _] (watch [it _state _]
@@ -203,6 +215,22 @@
(rx/of (set-selected-token-set-id (ctob/get-id token-set)) (rx/of (set-selected-token-set-id (ctob/get-id token-set))
(dch/commit-changes changes)))))))) (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 (defn toggle-token-set
[name] [name]
(assert (string? name) "expected a string for `name`") (assert (string? name) "expected a string for `name`")
@@ -218,7 +246,8 @@
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (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/reify ::toggle-token-set-group
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
@@ -230,7 +259,8 @@
(dch/commit-changes changes) (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn import-tokens-lib [lib] (defn import-tokens-lib
[lib]
(ptk/reify ::import-tokens-lib (ptk/reify ::import-tokens-lib
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
@@ -265,7 +295,8 @@
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn drop-error [{:keys [error to-path]}] (defn drop-error
[{:keys [error to-path]}]
(ptk/reify ::drop-error (ptk/reify ::drop-error
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
@@ -282,7 +313,8 @@
;; FIXME: add schema for params ;; 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/reify ::drop-token-set-group
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
@@ -344,47 +376,52 @@
(set-selected-token-set-id (ctob/get-id token-set))))))) (set-selected-token-set-id (ctob/get-id token-set)))))))
(defn create-token (defn create-token
[params] ([token] (create-token nil token))
(let [token (ctob/make-token params)] ([set-id token]
(ptk/reify ::create-token (ptk/reify ::create-token
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(if-let [token-set (lookup-token-set state)] (if-let [token-set (if set-id
(let [data (dsh/lookup-file-data state) (lookup-token-set state set-id)
token-type (:type token) (lookup-token-set state))]
changes (-> (pcb/empty-changes it) (let [data (dsh/lookup-file-data state)
(pcb/with-library-data data) token-type (:type token)
(pcb/set-token (ctob/get-id token-set) changes (-> (pcb/empty-changes it)
(:id token) (pcb/with-library-data data)
token))] (pcb/set-token (ctob/get-id token-set)
(:id token)
token))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "create-token" :type token-type}))) (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 (defn update-token
[id params] ([id params] (update-token nil id params))
(assert (uuid? id) "expected uuid for `id`") ([set-id id params]
(assert (uuid? id) "expected uuid for `id`")
(ptk/reify ::update-token (ptk/reify ::update-token
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [token-set (lookup-token-set state) (let [token-set (if set-id
data (dsh/lookup-file-data state) (lookup-token-set state set-id)
token (-> (get-tokens-lib state) (lookup-token-set state))
(ctob/get-token (ctob/get-id token-set) id)) data (dsh/lookup-file-data state)
token' (->> (merge token params) token (-> (get-tokens-lib state)
(into {}) (ctob/get-token (ctob/get-id token-set) id))
(ctob/make-token)) token' (->> (merge token params)
token-type (:type token) (into {})
changes (-> (pcb/empty-changes it) (ctob/make-token))
(pcb/with-library-data data) token-type (:type token)
(pcb/set-token (ctob/get-id token-set) changes (-> (pcb/empty-changes it)
id (pcb/with-library-data data)
token'))] (pcb/set-token (ctob/get-id token-set)
(rx/of (dch/commit-changes changes) id
(ptk/data-event ::ev/event {::ev/name "edit-token" :type token-type})))))) token'))]
(rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "edit-token" :type token-type})))))))
(defn delete-token (defn delete-token
[set-id token-id] [set-id token-id]
@@ -413,10 +450,11 @@
(let [tokens (vals (ctob/get-tokens tokens-lib (ctob/get-id token-set))) (let [tokens (vals (ctob/get-tokens tokens-lib (ctob/get-id token-set)))
unames (map :name tokens) unames (map :name tokens)
suffix (tr "workspace.tokens.duplicate-suffix") suffix (tr "workspace.tokens.duplicate-suffix")
copy-name (cfh/generate-unique-name (:name token) unames :suffix suffix)] copy-name (cfh/generate-unique-name (:name token) unames :suffix suffix)
(rx/of (create-token (assoc token new-token (-> token
:id (uuid/next) (ctob/reid (uuid/next))
:name copy-name)))))))))) (ctob/rename copy-name))]
(rx/of (create-token new-token)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKEN UI OPS ;; TOKEN UI OPS

View File

@@ -55,10 +55,7 @@
(defn generic-attribute-actions [attributes title {:keys [token selected-shapes on-update-shape hint allowed-shape-attributes]}] (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) (let [allowed-attributes (set/intersection attributes allowed-shape-attributes)
on-update-shape-fn on-update-shape-fn (or on-update-shape (dwta/get-update-shape-fn token))
(or on-update-shape
(-> (dwta/get-token-properties token)
(:on-update-shape)))
{:keys [selected-pred shape-ids]} {:keys [selected-pred shape-ids]}
(attribute-actions token selected-shapes allowed-attributes)] (attribute-actions token selected-shapes allowed-attributes)]

View File

@@ -69,6 +69,12 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))} [:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]) #(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 (def ^:private schema:token-description
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}]) [:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}])
@@ -363,27 +369,20 @@
;; Allow setting editing token to it's own path ;; Allow setting editing token to it's own path
(d/dissoc-in token-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 on-blur-name
(mf/use-fn (mf/use-fn
(mf/deps touched-name? validate-token-name) (mf/deps touched-name?)
(fn [e] (fn [e]
(let [value (dom/get-target-val 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)) (when touched-name? (reset! warning-name-change* true))
(reset! name-errors* errors)))) (reset! name-errors* errors))))
on-update-name-debounced on-update-name-debounced
(mf/with-memo [touched-name? validate-token-name] (mf/with-memo [touched-name?]
(uf/debounce (fn [token-name] (uf/debounce (fn [token-name]
(when touched-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)) 300))
on-update-name on-update-name
@@ -487,7 +486,7 @@
on-submit on-submit
(mf/use-fn (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] (fn [e]
(dom/prevent-default e) (dom/prevent-default e)
;; We have to re-validate the current form values before submitting ;; We have to re-validate the current form values before submitting
@@ -496,7 +495,7 @@
;; and press enter before the next validations could return. ;; and press enter before the next validations could return.
(let [clean-name (clean-name (mf/ref-val token-name-ref)) (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) value (mf/ref-val value-ref)
clean-description (mf/ref-val description-ref) clean-description (mf/ref-val description-ref)
@@ -512,10 +511,10 @@
(fn [valid-token] (fn [valid-token]
(st/emit! (st/emit!
(if is-create (if is-create
(dwtl/create-token {:name clean-name (dwtl/create-token (ctob/make-token {:name clean-name
:type token-type :type token-type
:value (:value valid-token) :value (:value valid-token)
:description clean-description}) :description clean-description}))
(dwtl/update-token (:id token) (dwtl/update-token (:id token)
{:name clean-name {:name clean-name

View File

@@ -166,7 +166,7 @@
;; Edge-case for allowing margin attribute on shapes inside layout parent ;; Edge-case for allowing margin attribute on shapes inside layout parent
(and selected-inside-layout? (set/subset? ctt/spacing-margin-keys attrs)) (and selected-inside-layout? (set/subset? ctt/spacing-margin-keys attrs))
(some (fn [shape] (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))) selected-shapes)))
(def token-types-with-status-icon (def token-types-with-status-icon

View File

@@ -33,15 +33,12 @@
can-edit? can-edit?
(mf/use-ctx ctx/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? token-set-active?
(mf/use-fn (mf/use-fn
(mf/deps active-token-sets-names) (mf/deps tokens-lib)
(fn [name] (fn [name]
(contains? active-token-sets-names name))) (when tokens-lib
(ctob/token-set-active? tokens-lib name))))
token-set-group-active? token-set-group-active?
(mf/use-fn (mf/use-fn

View File

@@ -28,7 +28,8 @@
(if-let [parent-path (ctob/get-set-path parent-set)] (if-let [parent-path (ctob/get-set-path parent-set)]
(->> (concat parent-path (ctob/split-set-name name)) (->> (concat parent-path (ctob/split-set-name name))
(ctob/join-set-path)) (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}) (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))))

View File

@@ -51,7 +51,7 @@
true)) ;; if still no library exists, cannot be duplicate true)) ;; if still no library exists, cannot be duplicate
:type-properties {:error/fn #(tr "workspace.tokens.theme-name-already-exists")}})) :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] [tokens-lib group theme-id name]
(let [schema (theme-name-schema {:tokens-lib tokens-lib :theme-id theme-id :group group}) (let [schema (theme-name-schema {:tokens-lib tokens-lib :theme-id theme-id :group group})
validation (m/explain schema (str/trim name))] validation (m/explain schema (str/trim name))]
@@ -151,7 +151,7 @@
[:div {:on-click (fn [e] [:div {:on-click (fn [e]
(dom/prevent-default e) (dom/prevent-default e)
(dom/stop-propagation 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) [:& switch {:name (tr "workspace.tokens.theme-name" name)
:on-change (constantly nil) :on-change (constantly nil)
:selected? selected?}]]] :selected? selected?}]]]

View File

@@ -31,7 +31,7 @@
selected? (get active-theme-paths theme-id) selected? (get active-theme-paths theme-id)
select-theme (fn [e] select-theme (fn [e]
(dom/stop-propagation e) (dom/stop-propagation e)
(st/emit! (dwtl/toggle-token-theme-active? id)) (st/emit! (dwtl/toggle-token-theme-active id))
(on-close))]] (on-close))]]
[:li {:key theme-id [:li {:key theme-id
:role "option" :role "option"

View File

@@ -28,6 +28,7 @@
[app.plugins.register :as r] [app.plugins.register :as r]
[app.plugins.shape :as shape] [app.plugins.shape :as shape]
[app.plugins.text :as text] [app.plugins.text :as text]
[app.plugins.tokens :as tokens]
[app.plugins.utils :as u] [app.plugins.utils :as u]
[app.util.object :as obj] [app.util.object :as obj]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@@ -959,6 +960,12 @@
(map #(lib-component-proxy plugin-id file-id %)))] (map #(lib-component-proxy plugin-id file-id %)))]
(apply array components)))} (apply array components)))}
:tokens
{:this true
:get
(fn [_]
(tokens/tokens-catalog plugin-id file-id))}
:createColor :createColor
(fn [] (fn []
(cond (cond

View File

@@ -31,6 +31,7 @@
[app.common.types.shape.radius :as ctsr] [app.common.types.shape.radius :as ctsr]
[app.common.types.shape.shadow :as ctss] [app.common.types.shape.shadow :as ctss]
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.types.token :as cto]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.plugins :as dp] [app.main.data.plugins :as dp]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@@ -42,6 +43,7 @@
[app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.variants :as dwv] [app.main.data.workspace.variants :as dwv]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
@@ -765,6 +767,8 @@
;; Interactions ;; Interactions
:interactions :interactions
{:this true {:this true
:get :get
@@ -1224,6 +1228,31 @@
(let [guide (u/proxy->ruler-guide value)] (let [guide (u/proxy->ruler-guide value)]
(st/emit! (dwgu/remove-guide guide))))) (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 :isVariantHead
(fn [] (fn []
(let [shape (u/locate-shape file-id page-id id) (let [shape (u/locate-shape file-id page-id id)

View File

@@ -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)))))))

View File

@@ -11,6 +11,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.store :as st] [app.main.store :as st]
[app.util.object :as obj])) [app.util.object :as obj]))
@@ -52,6 +53,26 @@
(assert (uuid? id) "Component not valid uuid") (assert (uuid? id) "Component not valid uuid")
(dm/get-in (locate-file file-id) [:data :components id])) (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 (defn locate-presence
[session-id] [session-id]
(dm/get-in @st/state [:workspace-presence session-id])) (dm/get-in @st/state [:workspace-presence session-id]))

View File

@@ -36,7 +36,7 @@
done done
(let [file (setup-file-with-token-lib) (let [file (setup-file-with-token-lib)
store (ths/setup-store file) 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 (tohs/run-store-async
store done events store done events