Add sync with design tokens in external libraries (#10293)

*  Auto link tokens when adding external libraries (provisional)

* 🔧 Refactor tokens-lib initialization

* 🔧 Add separated TokenStatus to store status apart of TokensLib

* 🔧 Make all status operations use the new data structure

* 🔧 Normalize status helper functions and access token sets by id

* 🔧 Rename :tokens-file to :tokens-source

* 🎉 Allow the user to choose the tokens-source of a file

* 🎉 Make tokens library readonly when it's in an external file

* 🎉 Show tokens in library summaries

* 🎉 Show source info in sidebar

* 🔧 Fix integration tests

* 🐛 Propagate changes of token values in external library

* 🎉 Layout updates

* 🔧 Refactor tokens source calculations

* 🔧 Add harder checks for nil or empty values in everything

* 🐛 Fix some integration tests

* 🔧 Add integration tests for tokens in external libs

* 🔧 Validate and repair missing tokens status

* 🎉 Make ui changes optional with config flag

* 🐛 Propagate tokens after synchronizing components in ext library

* 🐛 Propagate tokens after creating new instances

* 🐛 Propagate tokens after synchronizing tokens in ext library

* 🐛 Add a tokens source icon to libraries section (#11439)

* 🐛 Add a tokens source icon to libraries section

* 🐛 Fix ellipsis on library names

* ♻️ Remove code under flag on legacy component

* 🐛 Fix token theme name on inspect tab

* 🎉 Add changes notification (#11476)

* 🎉 Add changes notification

* ♻️ Change fn names

* 🐛 Fix tokens source label truncation and missing translations (#11533)

* 🐛 Fix tokens source label truncation and missing translations

The tokens source file name always showed, even for the current file,
and long names wrapped onto a second line instead of truncating
because the header used flex-wrap and overflow-wrap: break-word
instead of single-line ellipsis.

Show the source row unconditionally (it now displays "This file" when
the source is the current file, matching the connected-library case),
truncate the file name to one line with an ellipsis, and only attach a
tooltip with the full name when the text is actually truncated.

Replace the hardcoded UI strings with translated ones and add their
English and Spanish entries.

AI-assisted-by: claude-sonnet-5

* 🐛 Remove redundant effect dependency in tokens source

file-name-truncated? was listed as a dependency of the with-effect
that checks and observes label truncation, even though it isn't
read inside the effect body. Since the effect itself flips that
state via check-file-name-truncated, including it as a dependency
caused the ResizeObserver to be needlessly disconnected and
reconnected on every truncation change.

AI-assisted-by: claude-sonnet-5

* 🐛 Fix small visual error

* 🐛 Fix problem with plugins

* 🐛 Fix playwright tests

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
Co-authored-by: Eva Marco <eva.marco@kaleidos.net>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
This commit is contained in:
Andrés Moya 2026-09-21 15:37:45 +02:00 committed by GitHub
parent 56bf0e3ebb
commit 3cd9bfa9de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
98 changed files with 7013 additions and 1995 deletions

View File

@ -14,6 +14,7 @@
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg] [app.common.files.migrations :as fmg]
[app.common.files.stats :as cfs] [app.common.files.stats :as cfs]
[app.common.files.tokens :as cfo]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.desc-js-like :as-alias smdj] [app.common.schema.desc-js-like :as-alias smdj]
@ -21,6 +22,7 @@
[app.common.transit :as t] [app.common.transit :as t]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.common.uri :as uri] [app.common.uri :as uri]
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
@ -520,12 +522,20 @@
components-sample components-sample
(-> (sample-assets components 4) (-> (sample-assets components 4)
(update :sample load-objects))] (update :sample load-objects))
tokens-lib (cfo/get-tokens-lib data)
tokens-count (if (some? tokens-lib) (count (ctob/get-all-tokens tokens-lib)) 0)
token-sets-count (if (some? tokens-lib) (count (ctob/get-sets tokens-lib)) 0)
token-themes-count (if (some? tokens-lib) (count (ctob/get-themes-no-hidden tokens-lib)) 0)]
{:components components-sample {:components components-sample
:variants {:count (count variant-ids)} :variants {:count (count variant-ids)}
:colors (sample-assets (:colors data) 3) :colors (sample-assets (:colors data) 3)
:typographies (sample-assets (:typographies data) 3)})) :typographies (sample-assets (:typographies data) 3)
:tokens-count tokens-count
:token-sets-count token-sets-count
:token-themes-count token-themes-count}))
(def ^:private file-summary-cache-key-ttl (def ^:private file-summary-cache-key-ttl
(ct/duration {:days 30})) (ct/duration {:days 30}))

View File

@ -106,7 +106,6 @@
:set-token :set-token
:set-token-set :set-token-set
:set-token-theme :set-token-theme
:set-active-token-themes
:rename-token-set-group :rename-token-set-group
:move-token-set :move-token-set
:move-token-set-group :move-token-set-group

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.schema :as sm] [app.common.schema :as sm]
@ -29,6 +30,7 @@
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.types.typographies-list :as ctyl] [app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as ctt] [app.common.types.typography :as ctt]
[app.common.types.variant :as ctv] [app.common.types.variant :as ctv]
@ -398,10 +400,11 @@
[:id ::sm/uuid] [:id ::sm/uuid]
[:attrs [:maybe ctob/schema:token-theme-attrs]]]] [:attrs [:maybe ctob/schema:token-theme-attrs]]]]
[:set-active-token-themes [:set-tokens-status
[:map {:title "SetActiveTokenThemes"} [:map {:title "SetTokensStatus"}
[:type [:= :set-active-token-themes]] [:type [:= :set-tokens-status]]
[:theme-paths [:set :string]]]] [:theme-ids [:set ::sm/uuid]]
[:set-ids [:set ::sm/uuid]]]]
[:rename-token-set-group [:rename-token-set-group
[:map {:title "RenameTokenSetGroup"} [:map {:title "RenameTokenSetGroup"}
@ -428,7 +431,13 @@
[:set-base-font-size [:set-base-font-size
[:map {:title "ModBaseFontSize"} [:map {:title "ModBaseFontSize"}
[:type [:= :set-base-font-size]] [:type [:= :set-base-font-size]]
[:base-font-size :string]]]]) [:base-font-size :string]]]
[:set-tokens-source
[:map {:title "SetTokensSource"}
[:type [:= :set-tokens-source]]
[:file-id ::sm/uuid]
[:library-id [:maybe ::sm/uuid]]]]])
(def schema:changes (def schema:changes
[:sequential {:gen/max 5 :gen/min 1} schema:change]) [:sequential {:gen/max 5 :gen/min 1} schema:change])
@ -992,77 +1001,73 @@
(defmethod process-change :set-token (defmethod process-change :set-token
[data {:keys [set-id token-id attrs]}] [data {:keys [set-id token-id attrs]}]
(update data :tokens-lib (-> (cfo/ensure-tokens-lib data)
(fn [lib] (cfo/update-tokens-lib
(let [lib' (ctob/ensure-tokens-lib lib)] (fn [lib']
(cond (cond
(not attrs) (not attrs)
(ctob/delete-token lib' set-id token-id) (ctob/delete-token lib' set-id token-id)
(not (ctob/get-token lib' set-id token-id)) (not (ctob/get-token lib' set-id token-id))
(ctob/add-token lib' set-id (ctob/make-token attrs)) (ctob/add-token lib' set-id (ctob/make-token attrs))
:else :else
(ctob/update-token lib' set-id token-id (ctob/update-token lib' set-id token-id
(fn [prev-token] (fn [prev-token]
(ctob/make-token (merge prev-token attrs))))))))) (ctob/make-token (merge prev-token attrs)))))))))
(defmethod process-change :set-token-set (defmethod process-change :set-token-set
[data {:keys [id attrs]}] [data {:keys [id attrs]}]
(update data :tokens-lib (-> (cfo/ensure-tokens-lib data)
(fn [lib] (cfo/update-tokens-lib
(let [lib' (ctob/ensure-tokens-lib lib)] (fn [lib']
(cond (cond
(not attrs) (not attrs)
(ctob/delete-set lib' id) (ctob/delete-set lib' id)
(not (ctob/get-set lib' id)) (not (ctob/get-set lib' id))
(ctob/add-set lib' (ctob/make-token-set attrs)) (ctob/add-set lib' (ctob/make-token-set attrs))
:else :else
(ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs)))))))) (ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs))))))))
(defmethod process-change :set-token-theme (defmethod process-change :set-token-theme
[data {:keys [id attrs]}] [data {:keys [id attrs]}]
(update data :tokens-lib (-> (cfo/ensure-tokens-lib data)
(fn [lib] (cfo/update-tokens-lib
(let [lib' (ctob/ensure-tokens-lib lib)] (fn [lib']
(cond (cond
(not attrs) (not attrs)
(ctob/delete-theme lib' id) (ctob/delete-theme lib' id)
(not (ctob/get-theme lib' id)) (not (ctob/get-theme lib' id))
(ctob/add-theme lib' (ctob/make-token-theme attrs)) (ctob/add-theme lib' (ctob/make-token-theme attrs))
:else :else
(ctob/update-theme lib' (ctob/update-theme lib'
id id
(fn [prev-token-theme] (fn [prev-token-theme]
(ctob/make-token-theme (merge prev-token-theme attrs))))))))) (ctob/make-token-theme (merge prev-token-theme attrs)))))))))
(defmethod process-change :set-active-token-themes (defmethod process-change :set-tokens-status
[data {:keys [theme-paths]}] [data {:keys [theme-ids set-ids]}]
(update data :tokens-lib #(-> % (ctob/ensure-tokens-lib) (-> (cfo/ensure-tokens-lib data)
(ctob/set-active-themes theme-paths)))) (cfo/update-tokens-status ctos/set-tokens-status theme-ids set-ids)))
(defmethod process-change :rename-token-set-group (defmethod process-change :rename-token-set-group
[data {:keys [set-group-path set-group-fname]}] [data {:keys [set-group-path set-group-fname]}]
(update data :tokens-lib (fn [lib] (-> (cfo/ensure-tokens-lib data)
(-> lib (cfo/update-tokens-lib ctob/rename-set-group set-group-path set-group-fname)))
(ctob/ensure-tokens-lib)
(ctob/rename-set-group set-group-path set-group-fname)))))
(defmethod process-change :move-token-set (defmethod process-change :move-token-set
[data {:keys [from-path to-path before-path before-group] :as changes}] [data {:keys [from-path to-path before-path before-group] :as changes}]
(update data :tokens-lib #(-> % (-> (cfo/ensure-tokens-lib data)
(ctob/ensure-tokens-lib) (cfo/update-tokens-lib ctob/move-set from-path to-path before-path before-group)))
(ctob/move-set from-path to-path before-path before-group))))
(defmethod process-change :move-token-set-group (defmethod process-change :move-token-set-group
[data {:keys [from-path to-path before-path before-group]}] [data {:keys [from-path to-path before-path before-group]}]
(update data :tokens-lib #(-> % (-> (cfo/ensure-tokens-lib data)
(ctob/ensure-tokens-lib) (cfo/update-tokens-lib ctob/move-set-group from-path to-path before-path before-group)))
(ctob/move-set-group from-path to-path before-path before-group))))
;; === Design Tokens configuration ;; === Design Tokens configuration
@ -1070,10 +1075,13 @@
[data {:keys [base-font-size]}] [data {:keys [base-font-size]}]
(ctf/set-base-font-size data base-font-size)) (ctf/set-base-font-size data base-font-size))
(defmethod process-change :set-tokens-source
[data {:keys [library-id]}]
(cfo/set-tokens-source data library-id))
;; === Operations ;; === Operations
(def decode-shape-attrs (def decode-shape-attrs
(sm/decoder cts/schema:shape-attrs sm/json-transformer)) (sm/decoder cts/schema:shape-attrs sm/json-transformer))
(defmethod process-operation :assign (defmethod process-operation :assign
@ -1242,3 +1250,37 @@
(defmethod frames-changed :default (defmethod frames-changed :default
[_ _] [_ _]
nil) nil)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Design Tokens changes detection
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private tokens-lib-change-types
"Set of change types that modify the tokens library."
#{:set-tokens-lib
:set-token
:set-token-set
:set-token-theme
:rename-token-set-group
:move-token-set
:move-token-set-group})
(defn tokens-lib-changed?
"Check if a commit contains changes that modify the tokens library."
[changes]
(some #(tokens-lib-change-types (:type %)) changes))
(def ^:private notifiable-token-change-types
"Set of change types that modify the structure of the tokens library sets
or themes (as opposed to `:set-token`, which only edits a token's value)."
#{:set-token-set
:set-token-theme
:rename-token-set-group
:move-token-set
:move-token-set-group})
(defn notifiable-token-change-occured?
"Check if a commit contains changes that modify the tokens library sets or
themes structure (renamed/added/removed sets, theme set-membership, etc.)."
[changes]
(some #(notifiable-token-change-types (:type %)) changes))

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.changes :as cfc] [app.common.files.changes :as cfc]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
@ -22,6 +23,7 @@
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]])) [clojure.datafy :refer [datafy]]))
@ -987,7 +989,7 @@
[changes tokens-lib] [changes tokens-lib]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-tokens-lib (get library-data :tokens-lib)] prev-tokens-lib (cfo/get-tokens-lib library-data)]
(-> changes (-> changes
(update :redo-changes conj {:type :set-tokens-lib :tokens-lib tokens-lib}) (update :redo-changes conj {:type :set-tokens-lib :tokens-lib tokens-lib})
(update :undo-changes conj {:type :set-tokens-lib :tokens-lib prev-tokens-lib}) (update :undo-changes conj {:type :set-tokens-lib :tokens-lib prev-tokens-lib})
@ -996,7 +998,7 @@
(defn set-token [changes set-id token-id token] (defn set-token [changes set-id token-id token]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-token (some-> (get library-data :tokens-lib) prev-token (some-> (cfo/get-tokens-lib library-data)
(ctob/get-token set-id token-id))] (ctob/get-token set-id token-id))]
(-> changes (-> changes
(update :redo-changes conj {:type :set-token (update :redo-changes conj {:type :set-token
@ -1013,7 +1015,7 @@
[changes id token-set] [changes id token-set]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-token-set (some-> (get library-data :tokens-lib) prev-token-set (some-> (cfo/get-tokens-lib library-data)
(ctob/get-set id))] (ctob/get-set id))]
(-> changes (-> changes
(update :redo-changes conj {:type :set-token-set (update :redo-changes conj {:type :set-token-set
@ -1028,7 +1030,7 @@
[changes id new-name] [changes id new-name]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-token-set (some-> (get library-data :tokens-lib) prev-token-set (some-> (cfo/get-tokens-lib library-data)
(ctob/get-set id))] (ctob/get-set id))]
(-> changes (-> changes
(update :redo-changes conj {:type :set-token-set (update :redo-changes conj {:type :set-token-set
@ -1042,7 +1044,7 @@
(defn set-token-theme [changes id theme] (defn set-token-theme [changes id theme]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (let [library-data (::library-data (meta changes))
prev-theme (some-> (get library-data :tokens-lib) prev-theme (some-> (cfo/get-tokens-lib library-data)
(ctob/get-theme id))] (ctob/get-theme id))]
(-> changes (-> changes
(update :redo-changes conj {:type :set-token-theme (update :redo-changes conj {:type :set-token-theme
@ -1053,17 +1055,21 @@
:attrs (datafy prev-theme)}) :attrs (datafy prev-theme)})
(apply-changes-local)))) (apply-changes-local))))
(defn set-active-token-themes (defn set-tokens-status
[changes active-theme-paths] ([changes tokens-status]
(assert-library! changes) (assert-library! changes)
(let [library-data (::library-data (meta changes)) (assert (or (ctos/tokens-status? tokens-status)
prev-active-theme-paths (d/nilv (some-> (get library-data :tokens-lib) (nil? tokens-status)))
(ctob/get-active-theme-paths)) (let [theme-ids (if tokens-status (ctos/get-active-theme-ids tokens-status) #{})
#{})] set-ids (if tokens-status (ctos/get-active-set-ids tokens-status) #{})
(-> changes library-data (::library-data (meta changes))
(update :redo-changes conj {:type :set-active-token-themes :theme-paths active-theme-paths}) prev-tokens-status (cfo/get-tokens-status library-data)
(update :undo-changes conj {:type :set-active-token-themes :theme-paths prev-active-theme-paths}) prev-theme-ids (if prev-tokens-status (ctos/get-active-theme-ids prev-tokens-status) #{})
(apply-changes-local)))) prev-set-ids (if prev-tokens-status (ctos/get-active-set-ids prev-tokens-status) #{})]
(-> changes
(update :redo-changes conj {:type :set-tokens-status :theme-ids theme-ids :set-ids set-ids})
(update :undo-changes conj {:type :set-tokens-status :theme-ids prev-theme-ids :set-ids prev-set-ids})
(apply-changes-local)))))
(defn rename-token-set-group (defn rename-token-set-group
[changes set-group-path set-group-fname] [changes set-group-path set-group-fname]
@ -1117,6 +1123,21 @@
:base-font-size previous-font-size}) :base-font-size previous-font-size})
(apply-changes-local)))) (apply-changes-local))))
(defn set-tokens-source
[changes library-id]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
file-id (:id library-data)
prev-val (cfo/get-tokens-source library-data)]
(-> changes
(update :redo-changes conj {:type :set-tokens-source
:file-id file-id
:library-id library-id})
(update :undo-changes conj {:type :set-tokens-source
:file-id file-id
:library-id prev-val})
(apply-changes-local))))
;; Misc changes ;; Misc changes
(defn reorder-children (defn reorder-children

View File

@ -13,6 +13,7 @@
[app.common.files.comp-processors :as cfcp] [app.common.files.comp-processors :as cfcp]
[app.common.files.defaults :as cfd] [app.common.files.defaults :as cfd]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
@ -1998,6 +1999,13 @@
(update :pages-index d/update-vals update-container) (update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)))) (d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0027-separate-tokens-status"
[data _]
(if-let [tokens-lib (:tokens-lib data)]
(assoc data :tokens-status
(cfo/make-tokens-status-from-lib tokens-lib))
data))
(def available-migrations (def available-migrations
(into (d/ordered-set) (into (d/ordered-set)
["legacy-2" ["legacy-2"
@ -2081,4 +2089,5 @@
"0023-repair-token-themes-with-inexistent-sets" "0023-repair-token-themes-with-inexistent-sets"
"0024b-fix-stroke-cap-placement" "0024b-fix-stroke-cap-placement"
"0025-repair-empty-text-content" "0025-repair-empty-text-content"
"0026-fix-svg-raw-shapes-uuids"])) "0026-fix-svg-raw-shapes-uuids"
"0027-separate-tokens-status"]))

View File

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.path-names :as cpn] [app.common.path-names :as cpn]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
@ -783,6 +784,16 @@
(pcb/with-file-data file-data) (pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape)))) (pcb/update-shapes [(:id shape)] repair-shape))))
(defmethod repair-error :missing-tokens-status
[_ _ file-data _]
(let [tokens-lib (cfo/get-tokens-lib file-data)
tokens-status (cfo/make-tokens-status-from-lib tokens-lib)]
(log/debug :hint "repairing shape :tokens-status-missing")
(log/debug :hint " -> add a tokens-status generated from the library")
(-> (pcb/empty-changes nil)
(pcb/with-library-data file-data)
(pcb/set-tokens-status tokens-status))))
(defmethod repair-error :default (defmethod repair-error :default
[_ error file _] [_ error file _]
(log/error :hint "Unknown error code, don't know how to repair" :code (:code error)) (log/error :hint "Unknown error code, don't know how to repair" :code (:code error))

View File

@ -9,13 +9,18 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.i18n :refer [tr]] [app.common.i18n :refer [tr]]
[app.common.logging :as log]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.set :as set] [clojure.set :as set]
[cuerdas.core :as str] [cuerdas.core :as str]
[malli.core :as m])) [malli.core :as m]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HIGH LEVEL SCHEMAS ;; HIGH LEVEL SCHEMAS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -165,7 +170,8 @@
(some (fn [[token-name _]] (some (fn [[token-name _]]
(not (ctob/token-name-path-exists? token-name tokens-tree))) (not (ctob/token-name-path-exists? token-name tokens-tree)))
new-tokens))))]]) new-tokens))))]])
(defn find-refs [value]
(defn- find-refs [value]
(cond (cond
(string? value) (string? value)
(cto/find-token-value-references value) (cto/find-token-value-references value)
@ -344,6 +350,8 @@
;; HELPERS ;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Token
(def parseable-token-value-regexp (def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value. "Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value." This regexp also trims whitespace around the value."
@ -415,3 +423,312 @@
;; FIXME: this should be precalculated ? ;; FIXME: this should be precalculated ?
(defn is-reference? [token] (defn is-reference? [token]
(str/includes? (:value token) "{")) (str/includes? (:value token) "{"))
;; Tokens lib and status in file data
(defn make-tokens-status-from-lib
"Make a TokensStatus 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-theme-paths (ctob/get-legacy-active-themes tokens-lib)
active-theme-ids (into #{}
(comp (map #(ctob/get-theme-by-path tokens-lib %))
(remove nil?)
(map ctob/get-id)
(filter #(not= % ctob/hidden-theme-id)))
active-theme-paths)
;; Get active set names from the active themes
active-set-names (into #{}
(comp (filter #(and (ctob/token-theme? %)
(contains? active-theme-paths (ctob/get-theme-path %))))
(mapcat :sets))
(ctob/get-themes tokens-lib))
active-set-ids (into #{}
(comp (map #(ctob/get-set-by-name tokens-lib %))
(remove nil?)
(map ctob/get-id))
active-set-names)]
(ctos/make-tokens-status :active-theme-ids active-theme-ids
:active-set-ids active-set-ids)))
(defn ensure-tokens-lib
"Ensure file-data has a :tokens-lib or :tokens-source, and also a :tokens-status, creating them if necessary."
[file-data]
(cond-> file-data
(nil? (:tokens-source file-data))
(update :tokens-lib #(or % (ctob/make-tokens-lib)))
:always
(update :tokens-status #(or % (ctos/make-tokens-status)))))
(defn ensure-tokens-status
"Ensure file-data has a :tokens-status, creating it empty if necessary."
[file-data]
(cond-> file-data
(nil? (:tokens-status file-data))
(update :tokens-status #(or % (ctos/make-tokens-status)))))
(defn get-tokens-source
"Return the current value of :tokens-source attribute."
[file-data]
(:tokens-source file-data))
(defn get-effective-tokens-source
"Return the current tokens source of the file. When there is no explicit
:tokens-source attribute, the source is the file itself."
[file-data]
(or (:tokens-source file-data) (:id file-data)))
(defn set-tokens-source
[file-data tokens-source]
(assert (or (nil? tokens-source) (uuid? tokens-source)) "expected nil or valid uuid")
(if (nil? tokens-source)
(dissoc file-data :tokens-source)
(assoc file-data :tokens-source tokens-source)))
(defn effective-tokens-source?
"Returns true if the given id is the current tokens source of the file-data."
[file-data id]
(assert (uuid? id) "expected valid uuid")
(= (get-effective-tokens-source file-data) id))
(defn has-own-tokens?
"Returns true if the file-data contains a tokens-library itself and the library
contains some tokens. Note that it still may be true even if the tokens source
is external (in this case the own library is inactive, but still exists)."
[file-data]
(let [tokens-lib (:tokens-lib file-data)]
(and (some? tokens-lib)
(not (ctob/empty-lib? tokens-lib)))))
(defn tokens-provider?
"Returns true if the file MAY become a tokens source. This is if the file has tokens and has
not configured another tokens source."
[file-data]
(and (has-own-tokens? file-data)
(effective-tokens-source? file-data (:id file-data))))
(defn editable-tokens?
"Returns true if the file-data is its own tokens source."
[file-data]
(effective-tokens-source? file-data (:id file-data)))
(defn get-tokens-lib
[file-data]
(:tokens-lib file-data))
(defn get-tokens-status
[file-data]
(:tokens-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-tokens-status
"Update the tokens-status inside file-data through a callback function.
The function will receive the tokens status and the rest of args."
[file-data f & args]
(d/update-when file-data :tokens-status #(apply f % args)))
;; Tokens status with tokens lib
(defn- calculate-active-sets
"Obtain the set-ids that needs to be active for a particular set of theme ids"
[active-theme-ids tokens-lib]
(let [active-themes (map #(ctob/get-theme tokens-lib %) active-theme-ids)
active-set-names (reduce set/union #{} (map :sets active-themes))
active-sets (map #(ctob/get-set-by-name tokens-lib %) active-set-names)
active-set-ids (into #{} (map ctob/get-id) active-sets)]
active-set-ids))
(defn get-active-themes
"Return an ordered sequence of active themes"
[tokens-status tokens-lib]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(->> (ctob/get-themes tokens-lib)
(filter #(ctos/theme-active? tokens-status (ctob/get-id %)))))
(defn activate-theme
"Activate a theme and all its sets. Deactivate any other theme in the same group."
[tokens-status tokens-lib id]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? id) "expected valid theme id")
(if-not (ctos/theme-active? tokens-status id)
(if-let [theme (ctob/get-theme tokens-lib id)]
(let [group-themes (into #{} (ctob/get-themes-in-group tokens-lib (:group theme)))
active-theme-ids (ctos/get-active-theme-ids tokens-status)
active-theme-ids' (-> (set/difference active-theme-ids group-themes)
(conj id))
active-set-ids' (calculate-active-sets active-theme-ids' tokens-lib)]
(ctos/set-tokens-status tokens-status active-theme-ids' active-set-ids'))
tokens-status)
tokens-status))
(defn deactivate-theme
"Deactivate a theme and all its sets that are not in other active themes"
[tokens-status tokens-lib id]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? id) "expected valid theme id")
(if (ctos/theme-active? tokens-status id)
(let [active-theme-ids' (disj (ctos/get-active-theme-ids tokens-status) id)
active-set-ids' (calculate-active-sets active-theme-ids' tokens-lib)]
(ctos/set-tokens-status tokens-status active-theme-ids' active-set-ids'))
tokens-status))
(defn toggle-theme-active
"Toggle a theme's active state and update active sets accordingly."
[tokens-status tokens-lib id]
(if (ctos/theme-active? tokens-status id)
(deactivate-theme tokens-status tokens-lib id)
(activate-theme tokens-status tokens-lib id)))
(defn set-theme-active
"Set a theme's active state and update active sets accordingly."
[tokens-status tokens-lib id active?]
(if active?
(activate-theme tokens-status tokens-lib id)
(deactivate-theme tokens-status tokens-lib id)))
(defn get-active-sets
"Return a clojure set of the active sets, resolved from TokensStatus + TokensLib."
[tokens-status tokens-lib]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(let [active-set-ids (ctos/get-active-set-ids tokens-status)]
(into #{}
(comp (map #(ctob/get-set tokens-lib %))
(filter some?))
active-set-ids)))
(defn set-set-active
"Set directly a set's active state and deactivate all themes."
[tokens-status tokens-lib id active?]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? id) "expected valid set id")
(if (and (ctob/get-set tokens-lib id)
(not= active? (ctos/set-active? tokens-status id)))
(let [active-set-ids (ctos/get-active-set-ids tokens-status)
active-set-ids' (if active?
(conj active-set-ids id)
(disj active-set-ids id))]
(ctos/set-tokens-status tokens-status #{} active-set-ids'))
tokens-status))
(defn activate-set
"Activate directly a set, and deactivate all themes."
[tokens-status tokens-lib id]
(set-set-active tokens-status tokens-lib id true))
(defn deactivate-set
"Deactivate directly a set, and deactivate all themes."
[tokens-status tokens-lib id]
(set-set-active tokens-status tokens-lib id false))
(defn toggle-set-active
"Toggle a set's active state and deactivate all themes."
[tokens-status tokens-lib id]
(set-set-active tokens-status tokens-lib id (not (ctos/set-active? tokens-status id))))
(defn sets-at-path-all-active?
"Check active state of sets at a group path.
Returns :none, :all, or :partial."
[tokens-status tokens-lib group-path]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (vector? group-path) "expected valid group path")
(let [active-set-ids (ctos/get-active-set-ids tokens-status)
path-set-ids (into #{}
(map ctob/get-id)
(ctob/get-sets-at-path tokens-lib group-path))]
(if (seq active-set-ids)
(let [difference (set/difference path-set-ids active-set-ids)]
(cond
(empty? difference) :all
(seq (set/intersection path-set-ids active-set-ids)) :partial
:else :none))
:none)))
(defn toggle-set-group-active
"Toggle the active state of all sets at a group path, and deactivate all themes.
If all sets are active, deactivate all. If none or some are active, activate all."
[tokens-status tokens-lib group-path]
(let [all-active? (sets-at-path-all-active? tokens-status tokens-lib group-path)
sets-at-path (ctob/get-sets-at-path tokens-lib group-path)
set-ids-at-path (into #{} (map ctob/get-id) sets-at-path)
active-set-ids (ctos/get-active-set-ids tokens-status)
active-set-ids' (if (contains? #{:all :partial} all-active?)
(set/difference active-set-ids set-ids-at-path)
(set/union active-set-ids set-ids-at-path))]
(if (not= active-set-ids active-set-ids')
(ctos/set-tokens-status tokens-status #{} active-set-ids')
tokens-status)))
(defn get-tokens-in-active-sets
"Get merged tokens from all active sets, in set order."
[tokens-status tokens-lib]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(let [active-set-ids (ctos/get-active-set-ids tokens-status)
all-set-ids (map ctob/get-id (ctob/get-sets tokens-lib))
ordered-active (filter active-set-ids all-set-ids)]
(reduce (fn [tokens set-id]
(merge tokens (ctob/get-tokens tokens-lib set-id)))
(d/ordered-map)
ordered-active)))
(defn get-tokens-in-active-sets-force
"Same as get-tokens-in-active-sets but force-including a set by id."
[tokens-status tokens-lib force-set-id]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? force-set-id) "expected valid set id")
(let [active-set-ids (ctos/get-active-set-ids tokens-status)
all-set-ids (map ctob/get-id (ctob/get-sets tokens-lib))
ordered-active (cond-> (filter active-set-ids all-set-ids)
(some? force-set-id)
(conj force-set-id))]
(reduce (fn [tokens set-id]
(let [set (ctob/get-set tokens-lib set-id)]
(if set
(merge tokens (ctob/get-tokens- set))
tokens)))
(d/ordered-map)
ordered-active)))
(defn sync-tokens-status-with-lib
"Synchronizes tokens status with the current tokens lib:
- Delete any theme or set that no longer exists in the lib.
- Recalculate the list of active sets from the new themes."
[tokens-status tokens-lib]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(let [active-theme-ids (ctos/get-active-theme-ids tokens-status)
valid-theme-ids (into #{}
(filter #(some? (ctob/get-theme tokens-lib %)))
active-theme-ids)
active-set-ids (ctos/get-active-set-ids tokens-status)
valid-set-ids (if (empty? valid-theme-ids)
(into #{}
(filter #(some? (ctob/get-set tokens-lib %)))
active-set-ids)
(calculate-active-sets valid-theme-ids tokens-lib))]
(if (or (not= active-theme-ids valid-theme-ids)
(not= active-set-ids valid-set-ids))
(do
(log/info :hint "syncing token status"
:removed-themes (count (set/difference active-theme-ids valid-theme-ids))
:removed-sets (count (set/difference active-set-ids valid-set-ids)))
(ctos/set-tokens-status tokens-status valid-theme-ids valid-set-ids))
tokens-status)))

View File

@ -73,7 +73,8 @@
:variant-main-bad-name :variant-main-bad-name
:variant-main-bad-variant-name :variant-main-bad-variant-name
:variant-component-bad-name :variant-component-bad-name
:variant-component-bad-id}) :variant-component-bad-id
:missing-tokens-status})
(def ^:private schema:error (def ^:private schema:error
[:map {:title "ValidationError"} [:map {:title "ValidationError"}
@ -816,6 +817,17 @@
(transient []) (transient [])
objects))) objects)))
(defn check-tokens
"Check tokens in the file. The internal structure should be correct because
of the schemas and protocol functions. But we need to check for the case that
the tokens-status is missing due to a migration or import bug."
[{:keys [data] :as file}]
(when (and (some? (:tokens-lib data))
(nil? (:tokens-status data)))
(report-error :missing-tokens-status
"Tokens library is present but tokens-status is missing"
nil file nil)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; PUBLIC API: VALIDATION FUNCTIONS ;; PUBLIC API: VALIDATION FUNCTIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -853,6 +865,8 @@
nil nil
(:components data)) (:components data))
(check-tokens file)
(-> *errors* deref not-empty)))) (-> *errors* deref not-empty))))
(defn validate-shape (defn validate-shape

View File

@ -137,6 +137,7 @@
:token-tokenscript :token-tokenscript
:token-import-from-library :token-import-from-library
:token-typography-row :token-typography-row
:token-lib-sync
;; Only for developtment. ;; Only for developtment.
:transit-readable-response :transit-readable-response

View File

@ -6,84 +6,31 @@
(ns app.common.logic.tokens (ns app.common.logic.tokens
(:require (:require
[app.common.files.changes :as ch]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.types.tokens-lib :as ctob])) [app.common.files.tokens :as cfo]
[app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]))
(defn- generate-update-active-sets ;; Tokens source
"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`.
Use this for managing sets active state without having to modify a (defn generate-set-tokens-source
user created theme (\"no themes selected\" state in the ui)." "Create changes for setting the tokens source of a file to `library`,
[changes tokens-lib update-theme-fn] copying the library's tokens status. If the library is nil, the tokens source
(let [active-token-set-names (ctob/get-active-themes-set-names tokens-lib) is removed and the tokens status is cleared."
[changes library]
hidden-theme (ctob/get-hidden-theme tokens-lib) (let [library-id (:id library)
hidden-theme' (-> (some-> hidden-theme library-tokens-status (if library
(ctob/set-sets active-token-set-names)) (-> library ctf/file-data cfo/get-tokens-status)
(update-theme-fn))] (ctos/make-tokens-status))]
(-> changes (-> changes
(pcb/set-active-token-themes #{(ctob/get-theme-path hidden-theme')}) (pcb/set-tokens-source library-id)
(pcb/set-token-theme (ctob/get-id hidden-theme) (pcb/set-tokens-status library-tokens-status))))
hidden-theme'))))
(defn generate-set-enabled-token-set ;; Tokens lib
"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- vec-starts-with? [v1 v2]
"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]
(let [deactivate? (contains? #{:all :partial} (ctob/sets-at-path-all-active? tokens-lib group-path))
sets-names (->> (ctob/get-sets-at-path tokens-lib group-path)
(map ctob/get-name)
(into #{}))]
(if deactivate?
(ctob/disable-sets tokens-lib-theme sets-names)
(ctob/enable-sets tokens-lib-theme sets-names))))
(defn generate-toggle-token-set-group
"Toggle a token set group at `group-path` in `tokens-lib` without modifying a user theme."
[changes tokens-lib group-path]
(generate-update-active-sets changes tokens-lib #(toggle-token-set-group group-path tokens-lib %)))
(defn vec-starts-with? [v1 v2]
(= (subvec v1 0 (min (count v1) (count v2))) v2)) (= (subvec v1 0 (min (count v1) (count v2))) v2))
(defn- calculate-move-token-set-or-set-group (defn- calculate-move-token-set-or-set-group
@ -155,27 +102,109 @@
prev-before (assoc :prev-before-path (:path prev-before) prev-before (assoc :prev-before-path (:path prev-before)
:prev-before-group? (:group? prev-before)))))) :prev-before-group? (:group? prev-before))))))
(declare generate-sync-tokens-status-with-lib)
(defn generate-update-token-theme
"Create changes for updating a token theme and regenerating the activation status
of the sets inside it."
[changes token-theme]
(let [changes' (pcb/set-token-theme changes (ctob/get-id token-theme) token-theme)
data (pcb/get-library-data changes')
tokens-status (cfo/get-tokens-status data)
tokens-lib' (-> data
(ch/process-changes (:redo-changes changes'))
(cfo/get-tokens-lib))]
(generate-sync-tokens-status-with-lib changes'
tokens-status
tokens-lib')))
(defn generate-move-token-set (defn generate-move-token-set
"Create changes for dropping a token set or token set. "Create changes for dropping a token set or token set.
Throws for impossible moves." Throws for impossible moves."
[changes tokens-lib params] [changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)] (if tokens-lib
(pcb/move-token-set changes params) (if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set changes params)
changes)
changes)) changes))
(defn generate-move-token-set-group (defn generate-move-token-set-group
"Create changes for dropping a token set or token set group. "Create changes for dropping a token set or token set group.
Throws for impossible moves" Throws for impossible moves"
[changes tokens-lib params] [changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)] (if tokens-lib
(pcb/move-token-set-group changes params) (if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set-group changes params)
changes)
changes)) changes))
(defn generate-delete-token-set-group (defn generate-delete-token-set-group
"Create changes for deleting a token set group." "Create changes for deleting a token set group."
[changes tokens-lib path] [changes tokens-lib path]
(let [sets (ctob/get-sets-at-path tokens-lib path)] (if tokens-lib
(reduce (fn [changes set] (let [sets (ctob/get-sets-at-path tokens-lib path)]
(pcb/set-token-set changes (ctob/get-id set) nil)) (reduce (fn [changes set]
changes (pcb/set-token-set changes (ctob/get-id set) nil))
sets))) changes
sets))
changes))
;; Tokens Status
(defn- update-tokens-status
[changes tokens-status update-fn & args]
(if tokens-status
(let [tokens-status' (apply update-fn tokens-status args)]
(if (not= tokens-status tokens-status')
(pcb/set-tokens-status changes tokens-status')
changes))
changes))
(defn generate-activate-theme
[changes tokens-status tokens-lib id]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/activate-theme tokens-lib id)
changes))
(defn generate-deactivate-theme
[changes tokens-status tokens-lib id]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/deactivate-theme tokens-lib id)
changes))
(defn generate-set-theme-status
[changes tokens-status tokens-lib id active?]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/set-theme-active tokens-lib id active?)
changes))
(defn generate-toggle-theme
[changes tokens-status tokens-lib id]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/toggle-theme-active tokens-lib id)
changes))
(defn generate-set-enabled-token-set
[changes tokens-status tokens-lib id enabled?]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/set-set-active tokens-lib id enabled?)
changes))
(defn generate-toggle-token-set
[changes tokens-status tokens-lib id]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/toggle-set-active tokens-lib id)
changes))
(defn generate-toggle-token-set-group
[changes tokens-status tokens-lib group-path]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/toggle-set-group-active tokens-lib group-path)
changes))
(defn generate-sync-tokens-status-with-lib
[changes tokens-status tokens-lib]
(if tokens-lib
(update-tokens-status changes tokens-status cfo/sync-tokens-status-with-lib tokens-lib)
changes))

View File

@ -7,6 +7,7 @@
(ns app.common.test-helpers.tokens (ns app.common.test-helpers.tokens
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.files :as thf] [app.common.test-helpers.files :as thf]
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
@ -17,17 +18,39 @@
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob])) [app.common.types.tokens-lib :as ctob]))
(defn get-tokens-source
[file]
(-> file (ctf/file-data) (cfo/get-tokens-source)))
(defn get-tokens-lib (defn get-tokens-lib
[file] [file]
(:tokens-lib (ctf/file-data file))) (-> file (ctf/file-data) (cfo/get-tokens-lib)))
(defn get-tokens-status
[file]
(-> file (ctf/file-data) (cfo/get-tokens-status)))
(defn add-tokens-lib (defn add-tokens-lib
"Ensure the file has a tokens-lib and a tokens-status in its data, creating empty ones if not"
[file] [file]
(ctf/update-file-data file #(update % :tokens-lib ctob/ensure-tokens-lib))) (ctf/update-file-data file cfo/ensure-tokens-lib))
(defn update-tokens-lib (defn update-tokens-lib
"Modify the tokens-lib of a file "
[file f] [file f]
(ctf/update-file-data file #(update % :tokens-lib f))) (ctf/update-file-data file #(cfo/update-tokens-lib % f)))
(defn update-tokens-status
[file f]
(ctf/update-file-data file #(cfo/update-tokens-status % f)))
(defn sample-file-with-tokens
[& {:keys [lib-fn status-fn file-id] :as params
:or {lib-fn identity status-fn identity file-id :file1}}]
(-> (thf/sample-file file-id (dissoc params :lib-fn :status-fn))
(add-tokens-lib)
(update-tokens-lib lib-fn)
(update-tokens-status status-fn)))
(defn get-token (defn get-token
[file set-id token-id] [file set-id token-id]

View File

@ -87,7 +87,7 @@
[:components {:optional true} schema:components] [:components {:optional true} schema:components]
[:typographies {:optional true} schema:typographies] [:typographies {:optional true} schema:typographies]
[:plugin-data {:optional true} schema:plugin-data] [:plugin-data {:optional true} schema:plugin-data]
[:tokens-source {:optional true} ::sm/uuid] ;; Forward-compat: UUID of external library containing tokens-lib (full support in follow-up PR) [:tokens-source {:optional true} ::sm/uuid] ;; The tokens-lib may be in this file or in an external library
[:tokens-lib {:optional true} ctob/schema:tokens-lib] [:tokens-lib {:optional true} ctob/schema:tokens-lib]
[:tokens-status {:optional true} ctos/schema:tokens-status]]) [:tokens-status {:optional true} ctos/schema:tokens-status]])

View File

@ -18,6 +18,7 @@
[app.common.time :as ct] [app.common.time :as ct]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.core.protocols :as cp] [clojure.core.protocols :as cp]
[clojure.datafy :refer [datafy]] [clojure.datafy :refer [datafy]]
@ -479,11 +480,6 @@
[path] [path]
(mapv add-set-path-group-prefix path)) (mapv add-set-path-group-prefix path))
(defn- set-group-path->set-group-prefixed-path-str
[path]
(-> (set-group-path->set-group-prefixed-path path)
(join-set-path)))
(defn add-set-group-prefix [group-path] (defn add-set-group-prefix [group-path]
(str set-group-prefix group-path)) (str set-group-prefix group-path))
@ -547,7 +543,7 @@
"Get an ordered sequence of all sets names in the library") "Get an ordered sequence of all sets names in the library")
(get-set-tree [_] (get-set-tree [_]
"Get a nested tree of all sets in the library") "Get a nested tree of all sets in the library")
(get-sets-at-path [_ path-str] (get-sets-at-path [_ path-vec]
"Get an ordered sequence of sets under `path` in the library")) "Get an ordered sequence of sets under `path` in the library"))
(def ^:private schema:token-set-node (def ^:private schema:token-set-node
@ -584,8 +580,7 @@
(disable-sets [_ set-names] "disable several sets in theme") (disable-sets [_ set-names] "disable several sets in theme")
(toggle-set [_ set-name] "toggle a set enabled / disabled in the theme") (toggle-set [_ set-name] "toggle a set enabled / disabled in the theme")
(update-set-name [_ prev-set-name set-name] "update set-name when it exists") (update-set-name [_ prev-set-name set-name] "update set-name when it exists")
(theme-matches-group-name [_ group name] "if a theme matches the given group & name") (theme-matches-group-name [_ group name] "if a theme matches the given group & name"))
(hidden-theme? [_] "if a theme is the (from the user ui) hidden temporary theme"))
(def hidden-theme-id (def hidden-theme-id
uuid/zero) uuid/zero)
@ -659,10 +654,12 @@
(theme-matches-group-name [this group name] (theme-matches-group-name [this group name]
(and (= (:group this) group) (and (= (:group this) group)
(= (:name this) name))) (= (:name this) name))))
(hidden-theme? [this] (defn hidden-theme?
(theme-matches-group-name this hidden-theme-group hidden-theme-name))) "Check if a theme is the hidden temporary theme."
[theme]
(theme-matches-group-name theme hidden-theme-group hidden-theme-name))
(defmethod pp/simple-dispatch TokenTheme (defmethod pp/simple-dispatch TokenTheme
[^TokenTheme obj] [^TokenTheme obj]
@ -755,11 +752,15 @@
(def ^:private theme-separator "/") (def ^:private theme-separator "/")
(defn- join-theme-path [group name] (defn- join-theme-path [group name with-spaces?]
(cpn/join-path [group name] :separator theme-separator :with-spaces? false)) (let [path (if (and (str/empty? group) with-spaces?) [name] [group name])]
(cpn/join-path path :separator theme-separator :with-spaces? with-spaces?)))
(defn get-theme-path [theme] (defn get-theme-path
(join-theme-path (:group theme) (:name theme))) ([theme]
(get-theme-path theme false))
([theme with-spaces?]
(join-theme-path (:group theme) (:name theme) with-spaces?)))
(defn split-theme-path [path] (defn split-theme-path [path]
(cpn/split-group-name path (cpn/split-group-name path
@ -767,7 +768,7 @@
:with-spaces? false)) :with-spaces? false))
(def hidden-theme-path (def hidden-theme-path
(join-theme-path hidden-theme-group hidden-theme-name)) (join-theme-path hidden-theme-group hidden-theme-name false))
;; === TokenThemes (collection) ;; === TokenThemes (collection)
@ -778,18 +779,13 @@
(delete-theme [_ id] "delete a theme in the library") (delete-theme [_ id] "delete a theme in the library")
(theme-count [_] "get the total number if themes 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 [_] "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-themes [_] "get an ordered sequence of all themes in the library")
(get-themes-no-hidden [_] "get an ordered sequence of all themes in the library except the hidden theme")
(get-themes-in-group [_ group] "get an ordered sequence of the themes in the group")
(get-theme [_ id] "get one theme looking for id") (get-theme [_ id] "get one theme looking for id")
(get-theme-by-name [_ group name] "get one theme looking for group and name") (get-theme-by-name [_ group name] "get one theme looking for group and name")
(get-theme-groups [_] "get a sequence of group names by order") (get-theme-groups [_] "get a sequence of group names by order"))
(get-active-theme-paths [_] "get the active theme paths")
(get-active-themes [_] "get an ordered sequence of active themes in the library")
(set-active-themes [_ active-themes] "set active themes in library")
(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")
(get-hidden-theme [_] "get the hidden temporary theme"))
(def schema:token-themes (def schema:token-themes
[:and [:and
@ -930,18 +926,10 @@
(update-token [_ set-id token-id f] "update a token in a set") (update-token [_ set-id token-id f] "update a token in a set")
(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")
(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
`:all` All of the nested sets are active
`:partial` Mixed active state of nested sets")
(get-tokens-in-active-sets [_] "set of set names that are active in the the active themes")
(get-tokens-in-active-sets-force [_ force-set-id] "same as above but forcing a set to be active, even if it's not in the active themes")
(get-all-tokens [_] "all tokens in the lib, as a sequence") (get-all-tokens [_] "all tokens in the lib, as a sequence")
(get-all-tokens-map [_] "all tokens in the lib, as a map name -> token") (get-all-tokens-map [_] "all tokens in the lib, as a map name -> token")
(get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name")) (get-tokens [_ set-id] "return a map of tokens in the set, indexed by token-name")
(get-legacy-active-themes [_] "only for legacy compatibility; return active themes inside library"))
(declare parse-multi-set-dtcg-json) (declare parse-multi-set-dtcg-json)
(declare read-multi-set-dtcg) (declare read-multi-set-dtcg)
@ -1116,8 +1104,8 @@ Will return a value that matches this schema:
(->> (tree-seq d/ordered-map? vals sets) (->> (tree-seq d/ordered-map? vals sets)
(filter (partial instance? TokenSet)))) (filter (partial instance? TokenSet))))
(get-sets-at-path [_ path] (get-sets-at-path [_ path-vec]
(some->> (map add-set-path-group-prefix path) (some->> (map add-set-path-group-prefix path-vec)
(get-in sets) (get-in sets)
(tree-seq d/ordered-map? vals) (tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet)))) (filter (partial instance? TokenSet))))
@ -1177,7 +1165,7 @@ Will return a value that matches this schema:
(d/dissoc-in [group name]))) (d/dissoc-in [group name])))
(if same-path? (if same-path?
active-themes active-themes
(disj active-themes (join-theme-path group name))))))) (disj active-themes (join-theme-path group name false)))))))
this)) this))
(delete-theme [this id] (delete-theme [this id]
@ -1186,12 +1174,15 @@ Will return a value that matches this schema:
(if theme (if theme
(TokensLib. sets (TokensLib. sets
(d/dissoc-in themes [group name]) (d/dissoc-in themes [group name])
(disj active-themes (join-theme-path group name))) (disj active-themes (join-theme-path group name false)))
this))) this)))
(get-theme-tree [_] (get-theme-tree [_]
themes) themes)
(get-theme-tree-no-hidden [_]
(d/dissoc-in themes [hidden-theme-group hidden-theme-name]))
(get-theme-groups [_] (get-theme-groups [_]
(into [] (comp (into [] (comp
(map key) (map key)
@ -1202,6 +1193,14 @@ Will return a value that matches this schema:
(->> (tree-seq d/ordered-map? vals themes) (->> (tree-seq d/ordered-map? vals themes)
(filter (partial instance? TokenTheme)))) (filter (partial instance? TokenTheme))))
(get-themes-no-hidden [this]
(->> (get-themes this)
(remove #(hidden-theme? %))))
(get-themes-in-group [_ group]
(->> (get themes group)
(map (comp get-id val))))
(theme-count [this] (theme-count [this]
(count (get-themes this))) (count (get-themes this)))
@ -1212,60 +1211,12 @@ Will return a value that matches this schema:
(get-theme-by-name [_ group name] (get-theme-by-name [_ group name]
(dm/get-in themes [group name])) (dm/get-in themes [group name]))
(set-active-themes [_ active-themes]
(TokensLib. sets
themes
active-themes))
(activate-theme [this id]
(if-let [theme (get-theme this id)]
(let [group (:group theme)
group-themes (->> (get themes group)
(map (comp get-theme-path val))
(into #{}))
active-themes' (-> (set/difference active-themes group-themes)
(conj (get-theme-path theme)))]
(TokensLib. sets
themes
active-themes'))
this))
(deactivate-theme [this id]
(if-let [theme (get-theme this id)]
(TokensLib. sets
themes
(disj active-themes (get-theme-path theme)))
this))
(theme-active? [this id]
(when-let [theme (get-theme this id)]
(contains? active-themes (get-theme-path theme))))
(toggle-theme-active [this id]
(if (theme-active? this id)
(deactivate-theme this id)
(activate-theme this id)))
(get-active-theme-paths [_]
active-themes)
(get-active-themes [this]
(into
(list)
(comp
(filter (partial instance? TokenTheme))
(filter #(theme-active? this (get-id %))))
(tree-seq d/ordered-map? vals themes)))
(get-hidden-theme [this]
(get-theme this hidden-theme-id))
ITokensLib ITokensLib
(empty-lib? [this] (empty-lib? [this]
(and (empty? sets) (and (empty? sets)
(or (empty? themes) (or (empty? themes)
(and (= (theme-count this) 1) (and (= (theme-count this) 1)
(get-hidden-theme this))))) (get-theme this hidden-theme-id)))))
(set-path-exists? [_ set-path] (set-path-exists? [_ set-path]
(some? (get-in sets (set-full-path->set-prefixed-full-path set-path)))) (some? (get-in sets (set-full-path->set-prefixed-full-path set-path))))
@ -1300,58 +1251,6 @@ Will return a value that matches this schema:
active-themes) active-themes)
this)) this))
(get-active-themes-set-names [this]
(into #{}
(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)]
(if (seq active-set-names)
(let [path-active-set-names (some->> (get-in sets (split-set-name prefixed-path-str))
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet))
(map get-name)
(into #{}))
difference (set/difference path-active-set-names active-set-names)]
(cond
(empty? difference) :all
(seq (set/intersection path-active-set-names active-set-names)) :partial
:else :none))
:none)))
(get-tokens-in-active-sets [this]
(let [theme-set-names (get-active-themes-set-names this)
all-set-names (get-set-names this)
active-set-names (filter theme-set-names all-set-names)
tokens (reduce (fn [tokens set-name]
(let [set (get-set-by-name this set-name)]
(merge tokens (get-tokens- set))))
(d/ordered-map)
active-set-names)]
tokens))
(get-tokens-in-active-sets-force [this force-set-id]
(let [theme-set-names (get-active-themes-set-names this)
all-set-names (get-set-names this)
force-set (get-set this force-set-id)
active-set-names (cond-> (filter theme-set-names all-set-names)
(some? force-set)
(conj (get-name force-set)))
tokens (reduce (fn [tokens set-name]
(let [set (get-set-by-name this set-name)]
(merge tokens (get-tokens- set))))
(d/ordered-map)
active-set-names)]
tokens))
(get-all-tokens [this] (get-all-tokens [this]
(mapcat #(vals (get-tokens- %)) (mapcat #(vals (get-tokens- %))
(get-sets this))) (get-sets this)))
@ -1368,6 +1267,9 @@ Will return a value that matches this schema:
(get-set set-id) (get-set set-id)
(get-tokens-))) (get-tokens-)))
(get-legacy-active-themes [_]
active-themes)
IValidation IValidation
(valid? [this] (valid? [this]
(valid-tokens-lib-map? (datafy this))) (valid-tokens-lib-map? (datafy this)))
@ -1424,6 +1326,12 @@ Will return a value that matches this schema:
[o] [o]
(and (tokens-lib? o) (valid? o))) (and (tokens-lib? o) (valid? o)))
(defn get-theme-by-path
"Get a theme by its path string (e.g. \"/Theme Name\" or \"group/Theme Name\")."
[tokens-lib path]
(let [[group name] (split-theme-path path)]
(get-theme-by-name tokens-lib group name)))
(defn- ensure-hidden-theme (defn- ensure-hidden-theme
"A helper that is responsible to ensure that the hidden theme always "A helper that is responsible to ensure that the hidden theme always
exists on the themes data structure" exists on the themes data structure"
@ -1452,10 +1360,6 @@ Will return a value that matches this schema:
(check-tokens-lib-map) (check-tokens-lib-map)
(map->tokens-lib))) (map->tokens-lib)))
(defn ensure-tokens-lib
[tokens-lib]
(or tokens-lib (make-tokens-lib)))
(def schema:tokens-lib (def schema:tokens-lib
(sm/type-schema (sm/type-schema
{:type ::tokens-lib {:type ::tokens-lib
@ -1924,11 +1828,22 @@ Will return a value that matches this schema:
library library
(reduce add-theme library themes) (reduce add-theme library themes)
;; Activate themes by directly manipulating the internal active-themes field
;; (since activate-theme is being removed from the protocol)
library library
(reduce (fn [library theme-path] (reduce (fn [library theme-path]
(let [[group name] (split-theme-path theme-path) (let [[group name] (split-theme-path theme-path)
theme (get-theme-by-name library group name)] theme (get-theme-by-name library group name)]
(activate-theme library (get-id theme)))) (if theme
(let [group-themes (->> (get themes group)
(map (comp get-theme-path val))
(into #{}))
active-themes' (-> (set/difference (.-active-themes ^TokensLib library) group-themes)
(conj (get-theme-path theme)))]
(TokensLib. (.-sets ^TokensLib library)
(.-themes ^TokensLib library)
active-themes'))
library)))
library library
active-theme-names)] active-theme-names)]
@ -2033,82 +1948,183 @@ Will return a value that matches this schema:
(defn- dtcg-export-themes (defn- dtcg-export-themes
"Extract themes for a dtcg json export." "Extract themes for a dtcg json export."
[tokens-lib] ([tokens-lib]
(let [themes-xform ;; Backward-compatible version for print/serialization without tokens-status
(comp (let [themes-xform
(filter #(and (instance? TokenTheme %) (comp
(not (hidden-theme? %)))) (filter #(and (instance? TokenTheme %)
(map (fn [token-theme] (not (hidden-theme? %))))
;; NOTE: this probaly can be implemented as type method (map (fn [token-theme]
(d/without-nils ;; NOTE: this probaly can be implemented as type method
{"id" (:external-id token-theme) (d/without-nils
"name" (:name token-theme) {"id" (:external-id token-theme)
"group" (:group token-theme) "name" (:name token-theme)
"description" (:description token-theme) "group" (:group token-theme)
"isSource" (:is-source token-theme) "description" (:description token-theme)
"selectedTokenSets" (reduce #(assoc %1 %2 "enabled") {} (:sets token-theme))})))) "isSource" (:is-source token-theme)
"selectedTokenSets" (reduce #(assoc %1 %2 "enabled") {} (:sets token-theme))}))))
themes themes
(->> (get-theme-tree tokens-lib) (->> (get-theme-tree tokens-lib)
(tree-seq d/ordered-map? vals) (tree-seq d/ordered-map? vals)
(into [] themes-xform)) (into [] themes-xform))
;; Active themes without exposing hidden penpot theme ;; Active themes without exposing hidden penpot theme
active-themes active-themes
(-> (get-active-theme-paths tokens-lib) (-> (.-active-themes ^TokensLib tokens-lib)
(disj hidden-theme-path))] (disj hidden-theme-path))]
[themes active-themes])) [themes active-themes]))
([tokens-lib active-theme-ids]
;; Version with explicit active-theme-ids from TokensStatus
(let [themes-xform
(comp
(filter #(and (instance? TokenTheme %)
(not (hidden-theme? %))))
(map (fn [token-theme]
(d/without-nils
{"id" (:external-id token-theme)
"name" (:name token-theme)
"group" (:group token-theme)
"description" (:description token-theme)
"isSource" (:is-source token-theme)
"selectedTokenSets" (reduce #(assoc %1 %2 "enabled") {} (:sets token-theme))}))))
themes
(->> (get-theme-tree tokens-lib)
(tree-seq d/ordered-map? vals)
(into [] themes-xform))
;; Convert theme ids to theme paths, excluding hidden theme
active-themes
(into #{}
(comp (map #(get-theme tokens-lib %))
(filter some?)
(map get-theme-path)
(filter #(not= % hidden-theme-path)))
active-theme-ids)]
[themes active-themes])))
(defn export-dtcg-multi-file (defn export-dtcg-multi-file
"Convert a TokensLib into a plain clojure map, suitable to be encoded as a multi json files each encoded in DTCG format." "Convert a TokensLib into a plain clojure map, suitable to be encoded as a multi json files each encoded in DTCG format."
[tokens-lib] ([tokens-lib]
(let [[themes active-themes] ;; Backward-compatible version for print/serialization without tokens-status
(dtcg-export-themes tokens-lib) (let [[themes active-themes]
(dtcg-export-themes tokens-lib)
sets sets
(->> (get-sets tokens-lib) (->> (get-sets tokens-lib)
(map (fn [token-set] (map (fn [token-set]
(let [name (get-name token-set) (let [name (get-name token-set)
tokens (get-tokens- token-set)] tokens (get-tokens- token-set)]
[(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)]))) [(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)])))
(into {}))] (into {}))]
(-> sets (-> sets
(assoc "$themes.json" themes) (assoc "$themes.json" themes)
(assoc "$metadata.json" (assoc "$metadata.json"
{"tokenSetOrder" (get-set-names tokens-lib) {"tokenSetOrder" (get-set-names tokens-lib)
"activeThemes" active-themes "activeThemes" active-themes
"activeSets" (get-active-themes-set-names tokens-lib)})))) ;; For backward compat, use internal active-themes field
"activeSets" (into #{}
(comp (filter #(and (instance? TokenTheme %)
(contains? (.-active-themes ^TokensLib tokens-lib)
(get-theme-path %))))
(mapcat :sets))
(get-themes tokens-lib))}))))
([tokens-lib tokens-status]
;; Version with tokens-status for real exports
(let [active-theme-ids (ctos/get-active-theme-ids tokens-status)
[themes active-themes]
(dtcg-export-themes tokens-lib active-theme-ids)
sets
(->> (get-sets tokens-lib)
(map (fn [token-set]
(let [name (get-name token-set)
tokens (get-tokens- token-set)]
[(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)])))
(into {}))
active-set-ids (ctos/get-active-set-ids tokens-status)
active-set-names (into #{}
(comp (filter #(contains? active-set-ids (get-id %)))
(map get-name))
(get-sets tokens-lib))]
(-> sets
(assoc "$themes.json" themes)
(assoc "$metadata.json"
{"tokenSetOrder" (get-set-names tokens-lib)
"activeThemes" active-themes
"activeSets" active-set-names})))))
(defn export-dtcg-json (defn export-dtcg-json
"Convert a TokensLib into a plain clojure map, suitable to be encoded as a multi sets json string in DTCG format." "Convert a TokensLib into a plain clojure map, suitable to be encoded as a multi sets json string in DTCG format."
[tokens-lib] ([tokens-lib]
(let [[themes active-themes] ;; Backward-compatible version for print/serialization without tokens-status
(dtcg-export-themes tokens-lib) (let [[themes active-themes]
(dtcg-export-themes tokens-lib)
name-set-tuples name-set-tuples
(->> (get-set-tree tokens-lib) (->> (get-set-tree tokens-lib)
(tree-seq d/ordered-map? vals) (tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet)) (filter (partial instance? TokenSet))
(map (fn [set] (map (fn [set]
[(get-name set) [(get-name set)
(tokens-tree (get-tokens- set) :update-token-fn token->dtcg-token)]))) (tokens-tree (get-tokens- set) :update-token-fn token->dtcg-token)])))
ordered-set-names ordered-set-names
(mapv first name-set-tuples) (mapv first name-set-tuples)
sets sets
(into {} name-set-tuples) (into {} name-set-tuples)
active-set-names ;; For backward compat, use internal active-themes field
(get-active-themes-set-names tokens-lib)] active-set-names (into #{}
(comp (filter #(and (instance? TokenTheme %)
(contains? (.-active-themes ^TokensLib tokens-lib)
(get-theme-path %))))
(mapcat :sets))
(get-themes tokens-lib))]
(when-not (empty-lib? tokens-lib) (when-not (empty-lib? tokens-lib)
(-> sets (-> sets
(assoc "$themes" themes) (assoc "$themes" themes)
(assoc "$metadata" {"tokenSetOrder" ordered-set-names (assoc "$metadata" {"tokenSetOrder" ordered-set-names
"activeThemes" active-themes "activeThemes" active-themes
"activeSets" active-set-names}))))) "activeSets" active-set-names})))))
([tokens-lib tokens-status]
;; Version with tokens-status for real exports
(let [active-theme-ids (ctos/get-active-theme-ids tokens-status)
[themes active-themes]
(dtcg-export-themes tokens-lib active-theme-ids)
name-set-tuples
(->> (get-set-tree tokens-lib)
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet))
(map (fn [set]
[(get-name set)
(tokens-tree (get-tokens- set) :update-token-fn token->dtcg-token)])))
ordered-set-names
(mapv first name-set-tuples)
sets
(into {} name-set-tuples)
active-set-ids (ctos/get-active-set-ids tokens-status)
active-set-names (into #{}
(comp (filter #(contains? active-set-ids (get-id %)))
(map get-name))
(get-sets tokens-lib))]
(when-not (empty-lib? tokens-lib)
(-> sets
(assoc "$themes" themes)
(assoc "$metadata" {"tokenSetOrder" ordered-set-names
"activeThemes" active-themes
"activeSets" active-set-names}))))))
(defn get-tokens-of-unknown-type (defn get-tokens-of-unknown-type
"Search for all tokens in the decoded json file that have a type that is not currently "Search for all tokens in the decoded json file that have a type that is not currently

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht] [app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.test :as t])) [clojure.test :as t]))
@ -141,24 +142,27 @@
(t/deftest test-relocate-shape-out-of-layout-with-tokens (t/deftest test-relocate-shape-out-of-layout-with-tokens
(let [;; ==== Setup (let [;; ==== Setup
file (-> (thf/sample-file :file1) file (-> (tht/sample-file-with-tokens
(tht/add-tokens-lib) :lib-fn #(-> %
(tht/update-tokens-lib #(-> % (ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set) :name "test-token-set"))
:name "test-token-set")) (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :test-theme)
(ctob/add-theme (ctob/make-token-theme :name "test-theme" :name "test-theme"
:sets #{"test-token-set"})) :sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"}) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-sizing)
(ctob/make-token :id (thi/new-id! :token-sizing) :name "token-sizing"
:name "token-sizing" :type :sizing
:type :sizing :value 10))
:value 10)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-spacing)
(ctob/make-token :id (thi/new-id! :token-spacing) :name "token-spacing"
:name "token-spacing" :type :spacing
:type :spacing :value 30)))
:value 30)))) :status-fn #(-> %
(ctos/set-tokens-status #{(thi/id :test-theme)}
#{(thi/id :test-token-set)})))
;; Sync tokens-status from lib's internal active-themes
(tho/add-frame :frame-1 (tho/add-frame :frame-1
:layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params :layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params
:layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic :layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic

View File

@ -18,75 +18,78 @@
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.test :as t])) [clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture) (t/use-fixtures :each thi/test-fixture)
(defn- setup-file (defn- setup-file
[] []
(-> (thf/sample-file :file1) (-> (tht/sample-file-with-tokens
(tht/add-tokens-lib) :lib-fn #(-> %
(tht/update-tokens-lib #(-> % (ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set) :name "test-token-set"))
:name "test-token-set")) (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :test-theme)
(ctob/add-theme (ctob/make-token-theme :name "test-theme" :name "test-theme"
:sets #{"test-token-set"})) :sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"}) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-radius)
(ctob/make-token :id (thi/new-id! :token-radius) :name "token-radius"
:name "token-radius" :type :border-radius
:type :border-radius :value 10))
:value 10)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-rotation)
(ctob/make-token :id (thi/new-id! :token-rotation) :name "token-rotation"
:name "token-rotation" :type :rotation
:type :rotation :value 30))
:value 30)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-opacity)
(ctob/make-token :id (thi/new-id! :token-opacity) :name "token-opacity"
:name "token-opacity" :type :opacity
:type :opacity :value 0.7))
:value 0.7)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-stroke-width)
(ctob/make-token :id (thi/new-id! :token-stroke-width) :name "token-stroke-width"
:name "token-stroke-width" :type :stroke-width
:type :stroke-width :value 2))
:value 2)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-color)
(ctob/make-token :id (thi/new-id! :token-color) :name "token-color"
:name "token-color" :type :color
:type :color :value "#00ff00"))
:value "#00ff00")) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-dimensions)
(ctob/make-token :id (thi/new-id! :token-dimensions) :name "token-dimensions"
:name "token-dimensions" :type :dimensions
:type :dimensions :value 100))
:value 100)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-font-size)
(ctob/make-token :id (thi/new-id! :token-font-size) :name "token-font-size"
:name "token-font-size" :type :font-size
:type :font-size :value 24))
:value 24)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-letter-spacing)
(ctob/make-token :id (thi/new-id! :token-letter-spacing) :name "token-letter-spacing"
:name "token-letter-spacing" :type :letter-spacing
:type :letter-spacing :value 2))
:value 2)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-font-family)
(ctob/make-token :id (thi/new-id! :token-font-family) :name "token-font-family"
:name "token-font-family" :type :font-family
:type :font-family :value ["Helvetica" "Arial" "sans-serif"]))
:value ["Helvetica" "Arial" "sans-serif"])) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-sizing)
(ctob/make-token :id (thi/new-id! :token-sizing) :name "token-sizing"
:name "token-sizing" :type :sizing
:type :sizing :value 10))
:value 10)) (ctob/add-token (thi/id :test-token-set)
(ctob/add-token (thi/id :test-token-set) (ctob/make-token :id (thi/new-id! :token-spacing)
(ctob/make-token :id (thi/new-id! :token-spacing) :name "token-spacing"
:name "token-spacing" :type :spacing
:type :spacing :value 30)))
:value 30)))) :status-fn #(-> %
(ctos/set-tokens-status #{(thi/id :test-theme)}
#{(thi/id :test-token-set)})))
(tho/add-frame :frame1 (tho/add-frame :frame1
:layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params :layout :flex ;; TODO: those values come from main.data.workspace.shape_layout/default-layout-params
:layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic :layout-flex-dir :row ;; it should be good to use it directly, but first it should be moved to common.logic

File diff suppressed because it is too large Load Diff

View File

@ -11,11 +11,13 @@
#?(:clj [app.common.test-helpers.tokens :as tht]) #?(:clj [app.common.test-helpers.tokens :as tht])
#?(:clj [clojure.datafy :refer [datafy]]) #?(:clj [clojure.datafy :refer [datafy]])
[app.common.data :as d] [app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.time :as ct] [app.common.time :as ct]
[app.common.transit :as tr] [app.common.transit :as tr]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.test :as t])) [clojure.test :as t]))
@ -247,6 +249,16 @@
theme' (ctob/toggle-set theme nil)] theme' (ctob/toggle-set theme nil)]
(t/is (= (:sets theme') #{}))))) (t/is (= (:sets theme') #{})))))
(t/deftest get-theme-path
(let [theme1 (ctob/make-token-theme :name "theme1")
theme2 (ctob/make-token-theme :group "group1" :name "theme2")]
(t/is (= "/theme1" (ctob/get-theme-path theme1)))
(t/is (= "/theme1" (ctob/get-theme-path theme1 false)))
(t/is (= "theme1" (ctob/get-theme-path theme1 true)))
(t/is (= "group1/theme2" (ctob/get-theme-path theme2)))
(t/is (= "group1/theme2" (ctob/get-theme-path theme2 false)))
(t/is (= "group1 / theme2" (ctob/get-theme-path theme2 true)))))
(t/deftest make-tokens-lib (t/deftest make-tokens-lib
(let [tokens-lib (ctob/make-tokens-lib)] (let [tokens-lib (ctob/make-tokens-lib)]
(t/is (= (ctob/set-count tokens-lib) 0)))) (t/is (= (ctob/set-count tokens-lib) 0))))
@ -574,208 +586,19 @@
"token-4" "token-4"
(ctob/make-token :name "token-4" (ctob/make-token :name "token-4"
:type :border-radius :type :border-radius
:value 4000)})) :value 4000)})))
(ctob/update-theme ctob/hidden-theme-id set-a-id (ctob/get-id (ctob/get-set-by-name tokens-lib "set-a"))
#(ctob/enable-sets % #{"set-a" "set-b"}))) set-b-id (ctob/get-id (ctob/get-set-by-name tokens-lib "set-b"))
tokens-status (ctos/make-tokens-status :active-theme-ids #{}
:active-set-ids #{set-a-id set-b-id})
tokens (ctob/get-tokens-in-active-sets tokens-lib)] tokens (cfo/get-tokens-in-active-sets tokens-status tokens-lib)]
(t/is (= (mapv key tokens) ["token-1" "token-2" "token-3"])) (t/is (= (mapv key tokens) ["token-1" "token-2" "token-3"]))
(t/is (= (get-in tokens ["token-1" :value]) 100)) (t/is (= (get-in tokens ["token-1" :value]) 100))
(t/is (= (get-in tokens ["token-2" :value]) 20)) (t/is (= (get-in tokens ["token-2" :value]) 20))
(t/is (= (get-in tokens ["token-3" :value]) 300)))) (t/is (= (get-in tokens ["token-3" :value]) 300))))
(t/deftest list-active-themes-tokens-one-theme
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "set-a"
:tokens {"token-1"
(ctob/make-token :name "token-1"
:type :border-radius
:value 10)
"token-2"
(ctob/make-token :name "token-2"
:type :border-radius
:value 20)}))
(ctob/add-set (ctob/make-token-set :name "set-b"
:tokens {"token-1"
(ctob/make-token :name "token-1"
:type :border-radius
:value 100)
"token-3"
(ctob/make-token :name "token-3"
:type :border-radius
:value 300)}))
(ctob/add-set (ctob/make-token-set :name "set-c"
:tokens {"token-1"
(ctob/make-token :name "token-1"
:type :border-radius
:value 1000)
"token-2"
(ctob/make-token :name "token-2"
:type :border-radius
:value 2000)
"token-3"
(ctob/make-token :name "token-3"
:type :border-radius
:value 3000)
"token-4"
(ctob/make-token :name "token-4"
:type :border-radius
:value 4000)}))
(ctob/add-theme (ctob/make-token-theme :name "single-theme"
:sets #{"set-b" "set-c" "set-a"}))
(ctob/set-active-themes #{"/single-theme"}))
tokens (ctob/get-tokens-in-active-sets tokens-lib)]
;; Note that sets order inside the theme is undefined. What matters is order in that the
;; sets have been added to the library.
(t/is (= (mapv key tokens) ["token-1" "token-2" "token-3" "token-4"]))
(t/is (= (get-in tokens ["token-1" :value]) 1000))
(t/is (= (get-in tokens ["token-2" :value]) 2000))
(t/is (= (get-in tokens ["token-3" :value]) 3000))
(t/is (= (get-in tokens ["token-4" :value]) 4000))))
(t/deftest list-active-themes-tokens-two-themes
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "set-a"
:tokens {"token-1"
(ctob/make-token :name "token-1"
:type :border-radius
:value 10)
"token-2"
(ctob/make-token :name "token-2"
:type :border-radius
:value 20)}))
(ctob/add-set (ctob/make-token-set :name "set-b"
:tokens {"token-1"
(ctob/make-token :name "token-1"
:type :border-radius
:value 100)
"token-3"
(ctob/make-token :name "token-3"
:type :border-radius
:value 300)}))
(ctob/add-set (ctob/make-token-set :name "set-c"
:tokens {"token-1"
(ctob/make-token :name "token-1"
:type :border-radius
:value 1000)
"token-2"
(ctob/make-token :name "token-2"
:type :border-radius
:value 2000)
"token-3"
(ctob/make-token :name "token-3"
:type :border-radius
:value 3000)
"token-4"
(ctob/make-token :name "token-4"
:type :border-radius
:value 4000)}))
(ctob/add-theme (ctob/make-token-theme :name "theme-1"
:sets #{"set-b"}))
(ctob/add-theme (ctob/make-token-theme :name "theme-2"
:sets #{"set-b" "set-a"}))
(ctob/set-active-themes #{"/theme-1" "/theme-2"}))
tokens (ctob/get-tokens-in-active-sets tokens-lib)]
;; Note that themes order is irrelevant. What matters is the union of the active sets
;; and the order of the sets in the library.
(t/is (= (mapv key tokens) ["token-1" "token-2" "token-3"]))
(t/is (= (get-in tokens ["token-1" :value]) 100))
(t/is (= (get-in tokens ["token-2" :value]) 20))
(t/is (= (get-in tokens ["token-3" :value]) 300))))
(t/deftest list-active-themes-tokens-bug-taiga-10617
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "Mode/Dark"
:tokens {"red"
(ctob/make-token :name "red"
:type :color
:value "#700000")}))
(ctob/add-set (ctob/make-token-set :name "Mode/Light"
:tokens {"red"
(ctob/make-token :name "red"
:type :color
:value "#ff0000")}))
(ctob/add-set (ctob/make-token-set :name "Device/Desktop"
:tokens {"border1"
(ctob/make-token :name "border1"
:type :border-radius
:value 30)}))
(ctob/add-set (ctob/make-token-set :name "Device/Mobile"
:tokens {"border1"
(ctob/make-token :name "border1"
:type :border-radius
:value 50)}))
(ctob/add-theme (ctob/make-token-theme :group "App"
:name "Mobile"
:sets #{"Mode/Dark" "Device/Mobile"}))
(ctob/add-theme (ctob/make-token-theme :group "App"
:name "Web"
:sets #{"Mode/Dark" "Mode/Light" "Device/Desktop"}))
(ctob/add-theme (ctob/make-token-theme :group "Brand"
:name "Brand A"
:sets #{"Mode/Dark" "Mode/Light" "Device/Desktop" "Device/Mobile"}))
(ctob/add-theme (ctob/make-token-theme :group "Brand"
:name "Brand B"
:sets #{}))
(ctob/set-active-themes #{"App/Web" "Brand/Brand A"}))
tokens (ctob/get-tokens-in-active-sets tokens-lib)]
(t/is (= (mapv key tokens) ["red" "border1"]))
(t/is (= (get-in tokens ["red" :value]) "#ff0000"))
(t/is (= (get-in tokens ["border1" :value]) 50))))
(t/deftest list-active-themes-tokens-no-tokens
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "set-a")))
tokens (ctob/get-tokens-in-active-sets tokens-lib)]
(t/is (empty? tokens))))
(t/deftest list-active-themes-tokens-no-sets
(let [tokens-lib (ctob/make-tokens-lib)
tokens (ctob/get-tokens-in-active-sets tokens-lib)]
(t/is (empty? tokens))))
(t/deftest sets-at-path-active-state
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/bam"))
(ctob/add-theme (ctob/make-token-theme :name "none"))
(ctob/add-theme (ctob/make-token-theme :name "partial"
:sets #{"foo/bar/baz"}))
(ctob/add-theme (ctob/make-token-theme :name "all"
:sets #{"foo/bar/baz"
"foo/bar/bam"}))
(ctob/add-theme (ctob/make-token-theme :name "invalid"
:sets #{"foo/missing"})))
expected-none (-> tokens-lib
(ctob/set-active-themes #{"/none"})
(ctob/sets-at-path-all-active? ["foo"]))
expected-all (-> tokens-lib
(ctob/set-active-themes #{"/all"})
(ctob/sets-at-path-all-active? ["foo"]))
expected-partial (-> tokens-lib
(ctob/set-active-themes #{"/partial"})
(ctob/sets-at-path-all-active? ["foo"]))
expected-invalid-none (-> tokens-lib
(ctob/set-active-themes #{"/invalid"})
(ctob/sets-at-path-all-active? ["foo"]))]
(t/is (= :none expected-none))
(t/is (= :all expected-all))
(t/is (= :partial expected-partial))
(t/is (= :none expected-invalid-none))))
(t/deftest add-token-theme (t/deftest add-token-theme
(let [theme-id (uuid/next) (let [theme-id (uuid/next)
tokens-lib (ctob/make-tokens-lib) tokens-lib (ctob/make-tokens-lib)
@ -1283,12 +1106,12 @@
(let [theme-1-id (uuid/next) (let [theme-1-id (uuid/next)
theme-2-id (uuid/next) theme-2-id (uuid/next)
theme-3-id (uuid/next) theme-3-id (uuid/next)
theme-4-id (uuid/next) ;; theme-4-id (uuid/next)
tokens-lib (-> (ctob/make-tokens-lib) tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id theme-1-id :group "" :name "token-theme-1")) (ctob/add-theme (ctob/make-token-theme :id theme-1-id :group "" :name "token-theme-1"))
(ctob/add-theme (ctob/make-token-theme :id theme-2-id :group "group1" :name "token-theme-2")) (ctob/add-theme (ctob/make-token-theme :id theme-2-id :group "group1" :name "token-theme-2"))
(ctob/add-theme (ctob/make-token-theme :id theme-3-id :group "group1" :name "token-theme-3")) (ctob/add-theme (ctob/make-token-theme :id theme-3-id :group "group1" :name "token-theme-3"))
#_(ctob/add-theme (ctob/make-token-theme :in-theme-4-id :group "group2" :name "token-theme-4"))) #_(ctob/add-theme (ctob/make-token-theme :id theme-4-id :group "group2" :name "token-theme-4")))
tokens-lib' (-> tokens-lib tokens-lib' (-> tokens-lib
(ctob/update-theme theme-2-id (ctob/update-theme theme-2-id
@ -1612,9 +1435,9 @@
:group "group-1" :group "group-1"
: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))) tokens-status (cfo/toggle-theme-active (ctos/make-tokens-status) tokens-lib (thi/id :theme-1))
result (ctob/export-dtcg-json tokens-lib) result (ctob/export-dtcg-json tokens-lib tokens-status)
expected {"$themes" [{"description" "" expected {"$themes" [{"description" ""
"group" "group-1" "group" "group-1"
"isSource" false "isSource" false
@ -1665,9 +1488,9 @@
:group "group-1" :group "group-1"
: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))) tokens-status (cfo/toggle-theme-active (ctos/make-tokens-status) tokens-lib (thi/id :theme-1))
result (ctob/export-dtcg-multi-file tokens-lib) result (ctob/export-dtcg-multi-file tokens-lib tokens-status)
expected {"$themes.json" [{"description" "" expected {"$themes.json" [{"description" ""
"group" "group-1" "group" "group-1"
"isSource" false "isSource" false

View File

@ -7,9 +7,10 @@
(ns common-tests.types.tokens-migrations-test (ns common-tests.types.tokens-migrations-test
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.ids-map :as thi]
[app.common.time :as ct]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.datafy :refer [datafy]] [clojure.datafy :refer [datafy]]
[clojure.test :as t])) [clojure.test :as t]))
@ -80,3 +81,54 @@
(t/is (= (ctob/get-id theme2') (ctob/get-id theme2))) (t/is (= (ctob/get-id theme2') (ctob/get-id theme2)))
(t/is (= (ctob/get-name theme2') (ctob/get-name theme2))) (t/is (= (ctob/get-name theme2') (ctob/get-name theme2)))
(t/is (= (ctob/get-description theme2') (ctob/get-description theme2)))))) (t/is (= (ctob/get-description theme2') (ctob/get-description theme2))))))
(t/deftest make-tokens-status-from-tokens-lib
(t/testing "active themes and sets from a legacy tokens-lib should be added to the status"
(let [base-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a)
:name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b)
:name "set-b"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-c)
:name "set-c"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-d)
:name "set-d"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:sets #{"set-a" "set-b"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:sets #{"set-b"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-3)
:name "theme-3"
:sets #{"set-c" "set-d"})))
;; Build a legacy tokens lib to test migration
tokens-lib (ctob/map->tokens-lib {:sets (.-sets base-lib)
:themes (.-themes base-lib)
:active-themes #{"/theme-1" "/theme-2"}})
tokens-status (cfo/make-tokens-status-from-lib tokens-lib)]
(t/is (ctos/tokens-status? tokens-status))
(t/is (ctos/check-tokens-status tokens-status))
(t/is (= (count (ctos/get-active-theme-ids tokens-status)) 2))
(t/is (ctos/theme-active? tokens-status (thi/id :theme-1)))
(t/is (ctos/theme-active? tokens-status (thi/id :theme-2)))
(t/is (= 2 (count (ctos/get-active-set-ids tokens-status))))
(t/is (ctos/set-active? tokens-status (thi/id :set-a)))
(t/is (ctos/set-active? tokens-status (thi/id :set-b)))))
(t/testing "hidden theme should be excluded from active theme ids"
(let [tokens-lib (ctob/make-tokens-lib)
status (cfo/make-tokens-status-from-lib tokens-lib)]
(t/is (not (contains? (ctos/get-active-set-ids status) ctob/hidden-theme-id)))))
(t/testing "returns empty sets when no active themes"
(let [base-lib (ctob/make-tokens-lib)
tokens-lib (ctob/map->tokens-lib
{:sets (:sets (.sets base-lib))
:themes (:themes (.themes base-lib))
:active-themes #{}})
status (cfo/make-tokens-status-from-lib tokens-lib)]
(t/is (= #{} (ctos/get-active-theme-ids status)))
(t/is (= #{} (ctos/get-active-set-ids status))))))

View File

@ -25,7 +25,7 @@
"~:revn": 11, "~:revn": 11,
"~:modified-at": "~m1778577544627", "~:modified-at": "~m1778577544627",
"~:vern": 151920609, "~:vern": 151920609,
"~:id": "~u1bac06a1-a942-80a6-8008-0222e1ec38d5", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false, "~:is-shared": false,
"~:migrations": { "~:migrations": {
"~#ordered-set": [ "~#ordered-set": [
@ -128,6 +128,23 @@
} }
} }
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": ["~uc00d9d2e-76f9-815b-8005-e1260332abac", "~uc00d9d2e-76f9-815b-8005-e1260332abb0"]
},
"~:active-set-ids": {
"~#set": [
"~u4cdd76d8-0e6d-8168-8008-01189f0ece1d",
"~u4cdd76d8-0e6d-8168-8008-01189f161e35",
"~u4cdd76d8-0e6d-8168-8008-01189f101a3f",
"~u4cdd76d8-0e6d-8168-8008-01189f0e2151",
"~u4cdd76d8-0e6d-8168-8008-01189f0cf6d3",
"~u4cdd76d8-0e6d-8168-8008-01189f0e7da8"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {
@ -4312,7 +4329,7 @@
} }
} }
}, },
"~:id": "~u1bac06a1-a942-80a6-8008-0222e1ec38d5", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": { "~:options": {
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"

View File

@ -5771,6 +5771,16 @@
}, },
"~:id": "~u7b2da435-6186-815a-8007-0daa95d2f26d", "~:id": "~u7b2da435-6186-815a-8007-0daa95d2f26d",
"~:options": { "~:components-v2": true, "~:base-font-size": "16px" }, "~:options": { "~:components-v2": true, "~:base-font-size": "16px" },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": ["~u5403f14e-eb02-80be-8007-0494f09cefca"]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {

View File

@ -134,6 +134,18 @@
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~u7c1a66f7-5186-8060-8007-6ce67f8f2592"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {

View File

@ -0,0 +1,23 @@
[
{
"~:features": {
"~#set": [
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:is-indirect": false,
"~:name": "published component",
"~:revn": 3,
"~:modified-at": "~m1730103592325",
"~:vern": 0,
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:project-id": "~u3622460c-3408-81e2-8005-2fc9059741e0",
"~:synced-at": "~m1730103575856",
"~:created-at": "~m1730101410834"
}
]

View File

@ -53,6 +53,18 @@
} }
} }
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~uf71719b3-63a1-8157-8008-40346917de8c"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {
@ -373,7 +385,7 @@
} }
} }
}, },
"~:id": "~u55608328-aed3-807a-8008-41341f2f7247", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": { "~:options": {
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"

View File

@ -29,7 +29,7 @@
"~:revn": 3, "~:revn": 3,
"~:modified-at": "~m1779204621124", "~:modified-at": "~m1779204621124",
"~:vern": 0, "~:vern": 0,
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false, "~:is-shared": false,
"~:migrations": { "~:migrations": {
"~#ordered-set": [ "~#ordered-set": [
@ -130,11 +130,23 @@
"~:name": "Page 1" "~:name": "Page 1"
} }
}, },
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": { "~:options": {
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~u1dc5cafc-6d57-808d-8008-0b7af8180cbd"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {

View File

@ -42,10 +42,24 @@
] ]
} }
}, },
"~:id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f6", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": { "~:options": {
"~:components-v2": true "~:components-v2": true
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": ["~u66697432-c33d-8055-8006-2c62de27d738"]
},
"~:active-set-ids": {
"~#set": [
"~u66697432-c33d-8055-8006-2c62de27d709",
"~u51e13852-1a8e-8037-8005-9e9413a1f1f6",
"~u66697432-c33d-8055-8006-2c62de27d731"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {

View File

@ -54,6 +54,19 @@
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~u7239ff52-c1d9-8014-8006-ae7ad49690e3",
"~u97915939-ccca-808f-8006-aeb3c391e0f4"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {

View File

@ -0,0 +1,60 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"plugins/runtime",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/objects-map",
"tokens/numeric-input",
"render-wasm/v1",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~u5519794a-7293-802b-8008-16c227930902",
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "File with attached lib",
"~:revn": 3,
"~:modified-at": "~m1787664703168",
"~:vern": 0,
"~:id": "~ua604eafd-4265-803c-8008-898ba4b3c8d2",
"~:is-shared": false,
"~:version": 67,
"~:project-id": "~ua604eafd-4265-803c-8008-898b8b680ab4",
"~:created-at": "~m1787664674511",
"~:backend": "db",
"~:data": {
"~:pages": [
"~ua604eafd-4265-803c-8008-898ba4b3c8d3"
],
"~:pages-index": {
"~ua604eafd-4265-803c-8008-898ba4b3c8d3": {
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ud9574b23-9bcc-80d2-8008-898baf40c2e8\"]]]",
"~ud9574b23-9bcc-80d2-8008-898baf40c2e8": "[\"~#shape\",[\"^ \",\"~:y\",100,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",199.9999966621399,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",100,\"~:y\",100]],[\"^<\",[\"^ \",\"~:x\",299.9999966621399,\"~:y\",100]],[\"^<\",[\"^ \",\"~:x\",299.9999966621399,\"~:y\",199.99999511241913]],[\"^<\",[\"^ \",\"~:x\",100,\"~:y\",199.99999511241913]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~ud9574b23-9bcc-80d2-8008-898baf40c2e8\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",100,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",100,\"~:y\",100,\"^8\",199.9999966621399,\"~:height\",99.99999511241913,\"~:x1\",100,\"~:y1\",100,\"~:x2\",299.9999966621399,\"~:y2\",199.99999511241913]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",99.99999511241913,\"~:flip-y\",null]]"
}
},
"~:id": "~ua604eafd-4265-803c-8008-898ba4b3c8d3",
"~:name": "Page 1"
}
},
"~:id": "~ua604eafd-4265-803c-8008-898ba4b3c8d2",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@ -29,7 +29,7 @@
"~:revn": 36, "~:revn": 36,
"~:modified-at": "~m1776760054954", "~:modified-at": "~m1776760054954",
"~:vern": 0, "~:vern": 0,
"~:id": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false, "~:is-shared": false,
"~:migrations": { "~:migrations": {
"~#ordered-set": [ "~#ordered-set": [
@ -423,7 +423,7 @@
"~:key": "wgjr6b27pa", "~:key": "wgjr6b27pa",
"~:font-size": "16", "~:font-size": "16",
"~:font-weight": "400", "~:font-weight": "400",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef", "~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:modified-at": "~m1776759448186", "~:modified-at": "~m1776759448186",
"~:font-variant-id": "regular", "~:font-variant-id": "regular",
"~:text-decoration": "none", "~:text-decoration": "none",
@ -445,7 +445,7 @@
"~:key": "1dpjnycmsmq", "~:key": "1dpjnycmsmq",
"~:font-size": "16", "~:font-size": "16",
"~:font-weight": "400", "~:font-weight": "400",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef", "~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:text-direction": "ltr", "~:text-direction": "ltr",
"~:type": "paragraph", "~:type": "paragraph",
"~:modified-at": "~m1776759448186", "~:modified-at": "~m1776759448186",
@ -1079,7 +1079,7 @@
"~:key": "wgjr6b27pa", "~:key": "wgjr6b27pa",
"~:font-size": "18", "~:font-size": "18",
"~:font-weight": "700", "~:font-weight": "700",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef", "~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:modified-at": "~m1776759420985", "~:modified-at": "~m1776759420985",
"~:font-variant-id": "700", "~:font-variant-id": "700",
"~:text-decoration": "none", "~:text-decoration": "none",
@ -1101,7 +1101,7 @@
"~:key": "1dpjnycmsmq", "~:key": "1dpjnycmsmq",
"~:font-size": "18", "~:font-size": "18",
"~:font-weight": "700", "~:font-weight": "700",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef", "~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:text-direction": "ltr", "~:text-direction": "ltr",
"~:type": "paragraph", "~:type": "paragraph",
"~:modified-at": "~m1776759420985", "~:modified-at": "~m1776759420985",
@ -1517,7 +1517,7 @@
"~:name": "Page 1" "~:name": "Page 1"
} }
}, },
"~:id": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef", "~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": { "~:options": {
"~:components-v2": true, "~:components-v2": true,
"~:base-font-size": "16px" "~:base-font-size": "16px"
@ -1554,6 +1554,18 @@
"~:font-family": "IM Fell French Canon SC" "~:font-family": "IM Fell French Canon SC"
} }
}, },
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~u12f7a4ff-ddae-80ff-8007-e70bba7e2db2"
]
}
}
},
"~:tokens-lib": { "~:tokens-lib": {
"~#penpot/tokens-lib": { "~#penpot/tokens-lib": {
"~:sets": { "~:sets": {

View File

@ -583,11 +583,14 @@ export class WorkspacePage extends BaseWebSocketPage {
await expect(this.toolbarOptions).toHaveCSS("opacity", "0"); await expect(this.toolbarOptions).toHaveCSS("opacity", "0");
} }
async clickLayers(clickOptions = {}) {
await this.sidebar.getByText("Layers").click(clickOptions);
}
async clickAssets(clickOptions = {}) { async clickAssets(clickOptions = {}) {
await this.sidebar.getByText("Assets").click(clickOptions); await this.sidebar.getByText("Assets").click(clickOptions);
} }
async clickLayers(clickOptions = {}) { async clickTokens(clickOptions = {}) {
await this.sidebar.getByText("Layers").click(clickOptions); await this.sidebar.getByText("Tokens").click(clickOptions);
} }
async openLibrariesModal(clickOptions = {}) { async openLibrariesModal(clickOptions = {}) {

View File

@ -3,6 +3,7 @@ import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await WasmWorkspacePage.init(page); await WasmWorkspacePage.init(page);
await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]);
}); });
test("User adds a library and its automatically selected in the color palette", async ({ test("User adds a library and its automatically selected in the color palette", async ({
@ -31,6 +32,7 @@ test("User adds a library and its automatically selected in the color palette",
// Now the get-file call should return a library // Now the get-file call should return a library
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json"); await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal(); await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspacePage.clickLibrary("Testing library 1"); await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal(); await workspacePage.closeLibrariesModal();
@ -40,6 +42,7 @@ test("User adds a library and its automatically selected in the color palette",
// Remove Testing library 1 // Remove Testing library 1
await workspacePage.openLibrariesModal(); await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "This file" }).click();
await workspacePage.clickLibrary("Testing library 1"); await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal(); await workspacePage.closeLibrariesModal();

View File

@ -152,6 +152,7 @@ test("[Taiga #10630] [INSPECT] Style assets not being displayed on info tab", as
await workspacePage.clickAssets(); await workspacePage.clickAssets();
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json"); await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal(); await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspacePage.clickLibrary("Testing library 1"); await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal(); await workspacePage.closeLibrariesModal();

View File

@ -210,7 +210,7 @@ test("Multiselection of text and typographies", async ({ page }) => {
); );
await workspacePage.goToWorkspace({ await workspacePage.goToWorkspace({
fileId: "1062e0a0-8fe0-80ae-8007-e70b4993f5ef", fileId: "c7ce0794-0992-8105-8004-38f280443849",
pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0", pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0",
}); });

View File

@ -84,7 +84,7 @@ test.describe("typography asset applied", () => {
// gfont-agdasima, a built-in Google font that resolves with no extra // gfont-agdasima, a built-in Google font that resolves with no extra
// mocking), and is not multi-selected or token-applied. // mocking), and is not multi-selected or token-applied.
const FILE = { const FILE = {
id: "1062e0a0-8fe0-80ae-8007-e70b4993f5ef", id: "c7ce0794-0992-8105-8004-38f280443849",
pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0", pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0",
}; };

View File

@ -0,0 +1,163 @@
import { test, expect } from "@playwright/test";
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
import {
setupEmptyTokensFileRender,
createToken,
unfoldTokenType,
} from "./helpers";
test.beforeEach(async ({ page }) => {
await WasmWorkspacePage.init(page);
});
test("File with tokens is its own tokens source", async ({ page }) => {
await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]);
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await workspacePage.mockRPC(
"get-team-shared-files?team-id=*",
"workspace/get-team-shared-libraries-non-empty.json",
);
await workspacePage.goToWorkspace();
// A file without tokens is not tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await expect(
workspacePage.librariesModal.getByText("Tokens source"),
).not.toBeVisible();
await workspacePage.closeLibrariesModal();
// Create a token in the file
await workspacePage.clickTokens();
await createToken(
page,
"Color",
"color.primary",
"Value",
"textbox",
"#ff0000",
);
// Now it is a tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await expect(
workspacePage.librariesModal.getByText("Tokens source"),
).toBeVisible();
});
test("User sets a library as tokens source and then they can use the tokens in it", async ({
page,
}) => {
await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]);
// Set up a file linked to a library with tokens
const { workspacePage, tokenThemesSetsSidebar, tokenContextMenuForSet } =
await setupEmptyTokensFileRender(page);
await workspacePage.mockGetFile("workspace/get-file-with-lib.json");
await workspacePage.mockRPC(
"get-file-libraries?file-id=*",
"workspace/get-file-libraries-tokens.json",
);
await workspacePage.mockRPC(
/get\-file\?id=c7ce0794-0992-8105-8004-38f280443849/,
"workspace/get-file-tokens.json",
);
await workspacePage.mockRPC(
/get\-file\-fragment\?/,
"workspace/get-file-fragment-tokens.json",
);
await workspacePage.mockRPC(
"get-team-shared-files?team-id=*",
"workspace/get-team-shared-libraries-non-empty.json",
);
await workspacePage.goToWorkspace();
// Set the external library as the tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await workspacePage.librariesModal
.getByRole("button", { name: "Set as tokens source" })
.click();
await workspacePage.closeLibrariesModal();
// Check that we can apply a token from the library to a shape
await workspacePage.sidebar.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Rectangle" })
.click();
// Apply a color token from the library as the shape fill
await workspacePage.sidebar.getByRole("tab", { name: "Tokens" }).click();
await unfoldTokenType(workspacePage.tokensSidebar, "Color");
const colorTokenName = "colors.red.600";
await workspacePage.tokensSidebar
.getByRole("button", { name: colorTokenName })
.click({ button: "right" });
await workspacePage.tokenContextMenuForToken.getByText("Fill").click();
// Check that the token has been correctly applied in the right sidebar
const fillSection = workspacePage.rightSidebar.getByRole("region", {
name: "Fill section",
});
await expect(fillSection).toBeVisible();
const fillTokenPill = fillSection.getByLabel(colorTokenName, {
exact: true,
});
await expect(fillTokenPill).toBeVisible();
// Go back to the libraries modal and set the current file as tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await workspacePage.librariesModal
.getByRole("button", { name: "Set as tokens source" })
.click();
await workspacePage.closeLibrariesModal();
// Check that the applied token now looks broken
await expect(fillSection).toBeVisible();
const brokenTokenPill = fillSection.getByLabel(colorTokenName, {
exact: true,
});
await expect(brokenTokenPill).toBeVisible();
await brokenTokenPill.hover();
const brokenTokenTooltip = page.getByRole("tooltip", {
name: colorTokenName,
});
await expect(brokenTokenTooltip).toBeVisible();
await expect(brokenTokenTooltip).toHaveText(
`{${colorTokenName}} token does not exist or has been deleted.`,
);
});

View File

@ -89,6 +89,12 @@ test.describe("Tokens Themes", () => {
tokenThemeUpdateCreateModal.getByText("Changed" + "4 active sets"), tokenThemeUpdateCreateModal.getByText("Changed" + "4 active sets"),
).toBeVisible(); ).toBeVisible();
await tokenThemeUpdateCreateModal
.getByRole("switch", {
name: "Changed",
})
.click();
await tokenThemeUpdateCreateModal await tokenThemeUpdateCreateModal
.getByRole("button") .getByRole("button")
.getByText("close") .getByText("close")

View File

@ -143,6 +143,10 @@ test("BUG 14214 - Updates tab refreshes after syncing a freshly linked library",
// Open the library modal // Open the library modal
await workspace.clickAssets(); await workspace.clickAssets();
await workspace.openLibrariesModal(); await workspace.openLibrariesModal();
// Switch to the Libraries tab
await workspace.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspace.librariesModal await workspace.librariesModal
.getByRole("button", { name: "Connect library" }) .getByRole("button", { name: "Connect library" })
.click(); .click();

View File

@ -25,7 +25,7 @@ import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const FILE_A = { const FILE_A = {
id: "1062e0a0-8fe0-80ae-8007-e70b4993f5ef", id: "c7ce0794-0992-8105-8004-38f280443849",
pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0", pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0",
// "Text with typography asset one" carries a ref to in-file typography whose // "Text with typography asset one" carries a ref to in-file typography whose
// font-family is "IM Fell French Canon SC" (multiselection-typography.json). // font-family is "IM Fell French Canon SC" (multiselection-typography.json).

View File

@ -315,6 +315,7 @@ test("User adds a library and its automatically selected in the color palette",
// Now the get-file call should return a library // Now the get-file call should return a library
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json"); await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal(); await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspacePage.clickLibrary("Testing library 1"); await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal(); await workspacePage.closeLibrariesModal();

View File

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
@ -37,6 +38,23 @@
([state file-id] ([state file-id]
(dm/get-in state [:files file-id :data]))) (dm/get-in state [:files file-id :data])))
;; TODOstatus perhaps this is not necessary, lookup-tokens-lib should be enough
(defn lookup-tokens-source-data
[state]
(let [current-file-data (lookup-file-data state)
tokens-source-id (cfo/get-effective-tokens-source current-file-data)]
(lookup-file-data state tokens-source-id)))
(defn lookup-tokens-lib
[state]
(let [tokens-source-data (lookup-tokens-source-data state)]
(cfo/get-tokens-lib tokens-source-data)))
(defn lookup-tokens-status
[state]
(let [current-file-data (lookup-file-data state)] ;; Tokens status is always in the current file
(cfo/get-tokens-status current-file-data)))
(defn get-page (defn get-page
[fdata page-id] [fdata page-id]
(dm/get-in fdata [:pages-index page-id])) (dm/get-in fdata [:pages-index page-id]))

View File

@ -16,6 +16,7 @@
[app.common.logging :as log] [app.common.logging :as log]
[app.common.logic.libraries :as cll] [app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls] [app.common.logic.shapes :as cls]
[app.common.logic.tokens :as clo]
[app.common.logic.variants :as clv] [app.common.logic.variants :as clv]
[app.common.path-names :as cpn] [app.common.path-names :as cpn]
[app.common.time :as ct] [app.common.time :as ct]
@ -46,6 +47,7 @@
[app.main.data.workspace.specialized-panel :as dwsp] [app.main.data.workspace.specialized-panel :as dwsp]
[app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.thumbnails :as dwt]
[app.main.data.workspace.thumbnails-wasm :as dwt.wasm] [app.main.data.workspace.thumbnails-wasm :as dwt.wasm]
[app.main.data.workspace.tokens.propagation :as dwtp]
[app.main.data.workspace.transforms :as dwtr] [app.main.data.workspace.transforms :as dwtr]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.wasm-text :as dwwt] [app.main.data.workspace.wasm-text :as dwwt]
@ -654,6 +656,7 @@
(merge (meta it)))) (merge (meta it))))
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes) (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens) ;; Make the new instance get the token values from the current file, not from the component's library
(ptk/data-event :layout/update {:ids [(:id new-shape)]}) (ptk/data-event :layout/update {:ids [(:id new-shape)]})
(dws/select-shapes (d/ordered-set (:id new-shape))) (dws/select-shapes (d/ordered-set (:id new-shape)))
(when start-move? (when start-move?
@ -837,7 +840,11 @@
(rx/merge (rx/merge
(->> (rx/of library-id) (->> (rx/of library-id)
(rx/delay 5000) (rx/delay 5000)
(rx/map fetch-library-thumbnails))) (rx/map fetch-library-thumbnails))
(when (ch/tokens-lib-changed? changes)
(rx/of (dwtp/propagate-workspace-tokens)))
(when (ch/notifiable-token-change-occured? changes)
(rx/of (ntf/info (tr "workspace.tokens.notifications.source-sets-or-themes-updated")))))
(rx/take-until stopper-s)))))) (rx/take-until stopper-s))))))
@ -1096,6 +1103,7 @@
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes) (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens) ;; Make the new instance get the token values from the current file, not from the component's library
(when (and (features/active-feature? state "render-wasm/v1") (when (and (features/active-feature? state "render-wasm/v1")
(seq new-text-ids)) (seq new-text-ids))
(dwwt/resize-wasm-text-all new-text-ids)) (dwwt/resize-wasm-text-all new-text-ids))
@ -1345,6 +1353,9 @@
#(do (apply st/emit! (map (fn [library] #(do (apply st/emit! (map (fn [library]
(sync-file file-id (:id library))) (sync-file file-id (:id library)))
libraries-with-changes)) libraries-with-changes))
;; Launch a local tokens propagation, so that the copies have the token values
;; with the local tokens status, not the one coming from the external library.
(st/emit! (dwtp/propagate-workspace-tokens))
(st/emit! (ntf/hide))) (st/emit! (ntf/hide)))
do-dismiss do-dismiss
@ -1397,8 +1408,8 @@
(launch-component-sync component-id file-id undo-group))))) (launch-component-sync component-id file-id undo-group)))))
(defn watch-component-changes (defn watch-component-changes
"Watch the state for changes that affect to any main instance. If a change is detected will throw "Watch the state for changes that affect to any main instance. If a change is detected will call
an update-component-sync, so changes are immediately propagated to the component and copies." component-changed, so changes are immediately propagated to the component and copies."
[] []
(ptk/reify ::watch-component-changes (ptk/reify ::watch-component-changes
ptk/WatchEvent ptk/WatchEvent
@ -1530,6 +1541,47 @@
(rx/take-until stopper-s))))))) (rx/take-until stopper-s)))))))
(defn sync-tokens-status-with-lib
[]
(ptk/reify ::sync-tokens-status-with-lib
ptk/WatchEvent
(watch [_ state _]
(let [tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)]
(when (and tokens-lib tokens-status)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-sync-tokens-status-with-lib tokens-status tokens-lib))]
(rx/of (dch/commit-changes changes))))))))
(defn watch-token-changes
"Watch the state for changes that affect the tokens library. If a change is detected,
launches a sync-tokens-status event so the tokens-status is kept in sync with the library."
[]
(ptk/reify ::watch-token-changes
ptk/WatchEvent
(watch [_ _ stream]
(let [stopper-s
(->> stream
(rx/map ptk/type)
(rx/filter (fn [event-type]
(or (= ::dwpg/finalize-page event-type)
(= ::watch-token-changes event-type)))))
changes-s
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(= :local (:source %)))
(rx/observe-on :async))]
(->> changes-s
(rx/filter (comp ch/tokens-lib-changed? :changes))
(rx/debounce 5000)
(rx/map (fn [_] (sync-tokens-status-with-lib)))
(rx/take-until stopper-s))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Backend interactions ;; Backend interactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1575,6 +1627,7 @@
(map #(assoc % :library-of file-id)) (map #(assoc % :library-of file-id))
(d/index-by :id)))))) (d/index-by :id))))))
(defn- load-library-file (defn- load-library-file
[file-id library-id] [file-id library-id]
(ptk/reify ::load-library-file (ptk/reify ::load-library-file
@ -1584,8 +1637,9 @@
(rx/merge (rx/merge
(->> (rp/cmd! :get-file {:id library-id :features features}) (->> (rp/cmd! :get-file {:id library-id :features features})
(rx/merge-map fpmap/resolve-file) (rx/merge-map fpmap/resolve-file)
(rx/map (fn [file] (rx/mapcat (fn [file]
(libraries-fetched file-id [file])))) (rx/of
(libraries-fetched file-id [file])))))
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
(rx/map (fn [thumbnails] (rx/map (fn [thumbnails]
(fn [state] (fn [state]
@ -1594,10 +1648,10 @@
(defn link-file-to-library (defn link-file-to-library
[file-id library-id] [file-id library-id]
(ptk/reify ::attach-library (ptk/reify ::link-file-to-library
ev/Event ev/Event
(-data [_] (-data [_]
{::ev/name "attach-library" {::ev/name "link-file-to-library"
:file-id file-id :file-id file-id
:library-id library-id}) :library-id library-id})
@ -1614,23 +1668,22 @@
(map first) (map first)
set)] set)]
(rx/concat (rx/concat
(rx/merge (->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id})
(->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id}) (rx/merge-map (fn [libraries-to-load]
(rx/merge-map (fn [libraries-to-load] (as-> libraries-to-load $
(as-> libraries-to-load $ (remove loaded-libraries $)
(remove loaded-libraries $) (conj $ library-id)
(conj $ library-id) (map #(load-library-file file-id %) $))))
(map #(load-library-file file-id %) $)))) (rx/catch (fn [cause]
(rx/catch (fn [cause] (let [error (ex-data cause)]
(let [error (ex-data cause)] (if (= (:code error) :circular-library-reference)
(if (= (:code error) :circular-library-reference) (rx/of (ntf/error (tr "errors.circular-library-reference")))
(rx/of (ntf/error (tr "errors.circular-library-reference"))) (rx/throw cause))))))
(rx/throw cause))))))) (rx/of (ptk/reify ::link-file-to-library-finished))
(rx/of (ptk/reify ::attach-library-finished))
(when (pos? variants-count) (when (pos? variants-count)
(->> (rp/cmd! :get-library-usage {:file-id library-id}) (->> (rp/cmd! :get-library-usage {:file-id library-id})
(rx/map (fn [library-usage] (rx/map (fn [library-usage]
(ev/event {::ev/name "attach-library-variants" (ev/event {::ev/name "link-file-to-library-variants"
:file-id file-id :file-id file-id
:library-id library-id :library-id library-id
:variants-count variants-count :variants-count variants-count

View File

@ -114,6 +114,7 @@
(rx/of (dwth/watch-state-changes file-id page-id))) (rx/of (dwth/watch-state-changes file-id page-id)))
(rx/of (dwl/watch-component-changes)) (rx/of (dwl/watch-component-changes))
(rx/of (dwl/watch-token-changes))
(let [profile (:profile state) (let [profile (:profile state)
props (get profile :props)] props (get profile :props)]

View File

@ -22,12 +22,10 @@
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
[app.main.data.event :as ev]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.workspace :as-alias dw] [app.main.data.workspace :as-alias dw]
[app.main.data.workspace.common :as dwc] [app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.pages :as-alias dwpg] [app.main.data.workspace.pages :as-alias dwpg]
[app.main.data.workspace.reflow :as wrf] [app.main.data.workspace.reflow :as wrf]
@ -1171,66 +1169,6 @@
(let [{:keys [name]} (fonts/get-font-data font-id)] (let [{:keys [name]} (fonts/get-font-data font-id)]
(assoc typography :name (str name " " (str/title font-variant-id))))) (assoc typography :name (str name " " (str/title font-variant-id)))))
(defn add-typography
"A higher level version of dwl/add-typography, and has mainly two
responsabilities: add the typography to the library and apply it to
the currently selected text shapes (being aware of the open text
editors.
Optionally accepts a group-path to place the new typography inside
a specific group."
([file-id] (add-typography file-id nil))
([file-id group-path]
(ptk/reify ::add-typography
ptk/WatchEvent
(watch [_ state _]
(let [selected (dsh/lookup-selected state)
objects (dsh/lookup-page-objects state)
xform (comp (keep (d/getf objects))
(filter cfh/text-shape?))
shapes (into [] xform selected)
shape (first shapes)
values (current-text-values
{:editor-state (dm/get-in state [:workspace-editor-state (:id shape)])
:shape shape
:attrs txt/text-node-attrs})
multiple? (or (> 1 (count shapes))
(d/seek (partial = :multiple)
(vals values)))
values (-> (d/without-nils values)
(select-keys
(d/concat-vec txt/text-font-attrs
txt/text-spacing-attrs
txt/text-transform-attrs)))
values (cond-> values
(number? (:line-height values))
(update :line-height #(str (mth/precision % 2)))
(number? (:letter-spacing values))
(update :letter-spacing #(str (mth/precision % 2))))
typ-id (uuid/next)
typ (-> (if multiple?
txt/default-typography
(merge txt/default-typography values))
(generate-typography-name)
(assoc :id typ-id)
(cond-> (string? group-path)
(update :name #(str group-path " / " %))))]
(rx/concat
(rx/of (dwl/add-typography typ)
(ev/event {::ev/name "add-asset-to-library"
:asset-type "typography"}))
(when (not multiple?)
(rx/of (update-attrs (:id shape)
{:typography-ref-id typ-id
:typography-ref-file file-id})))))))))
;; -- Text Editor v2 ;; -- Text Editor v2
(defn v2-update-text-editor-styles (defn v2-update-text-editor-styles

View File

@ -0,0 +1,85 @@
;; 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 SUBSIDIARY SL
(ns app.main.data.workspace.texts-events
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.math :as mth]
[app.common.types.text :as txt]
[app.common.uuid :as uuid]
[app.main.data.event :as ev]
[app.main.data.helpers :as dsh]
[app.main.data.workspace :as-alias dw]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.pages :as-alias dwpg]
[app.main.data.workspace.texts :as dwt]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
;; This function must be separated from app.main.data.workspace.texts to avoid a circular
;; dependency due main.data.workspace.libraries eventually calling app.main.data.workspace.texts.
(defn add-typography
"A higher level version of dwl/add-typography, and has mainly two
responsabilities: add the typography to the library and apply it to
the currently selected text shapes (being aware of the open text
editors.
Optionally accepts a group-path to place the new typography inside
a specific group."
([file-id] (add-typography file-id nil))
([file-id group-path]
(ptk/reify ::add-typography
ptk/WatchEvent
(watch [_ state _]
(let [selected (dsh/lookup-selected state)
objects (dsh/lookup-page-objects state)
xform (comp (keep (d/getf objects))
(filter cfh/text-shape?))
shapes (into [] xform selected)
shape (first shapes)
values (dwt/current-text-values
{:editor-state (dm/get-in state [:workspace-editor-state (:id shape)])
:shape shape
:attrs txt/text-node-attrs})
multiple? (or (> 1 (count shapes))
(d/seek (partial = :multiple)
(vals values)))
values (-> (d/without-nils values)
(select-keys
(d/concat-vec txt/text-font-attrs
txt/text-spacing-attrs
txt/text-transform-attrs)))
values (cond-> values
(number? (:line-height values))
(update :line-height #(str (mth/precision % 2)))
(number? (:letter-spacing values))
(update :letter-spacing #(str (mth/precision % 2))))
typ-id (uuid/next)
typ (-> (if multiple?
txt/default-typography
(merge txt/default-typography values))
(dwt/generate-typography-name)
(assoc :id typ-id)
(cond-> (string? group-path)
(update :name #(str group-path " / " %))))]
(rx/concat
(rx/of (dwl/add-typography typ)
(ev/event {::ev/name "add-asset-to-library"
:asset-type "typography"}))
(when (not multiple?)
(rx/of (dwt/update-attrs (:id shape)
{:typography-ref-id typ-id
:typography-ref-file file-id})))))))))

View File

@ -25,7 +25,6 @@
[app.main.data.style-dictionary :as sd] [app.main.data.style-dictionary :as sd]
[app.main.data.tinycolor :as tinycolor] [app.main.data.tinycolor :as tinycolor]
[app.main.data.tokenscript :as ts] [app.main.data.tokenscript :as ts]
[app.main.data.workspace :as udw]
[app.main.data.workspace.colors :as wdc] [app.main.data.workspace.colors :as wdc]
[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]
@ -95,11 +94,11 @@
(watch [_ _ _] (watch [_ _ _]
(when (number? value) (when (number? value)
(rx/of (rx/of
(udw/trigger-bounding-box-cloaking shape-ids) (dwtr/trigger-bounding-box-cloaking shape-ids)
(udw/increase-rotation shape-ids value nil (dwtr/increase-rotation shape-ids value nil
{:page-id page-id {:page-id page-id
:ignore-touched true :ignore-touched true
:no-wasm? true}))))))) :no-wasm? true})))))))
(defn update-stroke-width (defn update-stroke-width
([value shape-ids attributes] (update-stroke-width value shape-ids attributes nil)) ([value shape-ids attributes] (update-stroke-width value shape-ids attributes nil))
@ -675,9 +674,10 @@
(ctt/typography-token-keys (:type token)) (set/union attributes-to-remove ctt/typography-keys) (ctt/typography-token-keys (:type token)) (set/union attributes-to-remove ctt/typography-keys)
(ctt/typography-keys (:type token)) (set/union attributes-to-remove ctt/typography-token-keys) (ctt/typography-keys (:type token)) (set/union attributes-to-remove ctt/typography-token-keys)
:else attributes-to-remove)] :else attributes-to-remove)]
(when-let [tokens (some-> (dsh/lookup-file-data state) (when-let [tokens (let [tokens-lib (dsh/lookup-tokens-lib state)
(get :tokens-lib) tokens-status (dsh/lookup-tokens-status state)]
(ctob/get-tokens-in-active-sets))] (when (and tokens-lib tokens-status)
(cfo/get-tokens-in-active-sets tokens-status tokens-lib)))]
(->> (if (contains? cf/flags :tokenscript) (->> (if (contains? cf/flags :tokenscript)
(rx/of (ts/resolve-tokens tokens)) (rx/of (ts/resolve-tokens tokens))
(sd/resolve-tokens tokens)) (sd/resolve-tokens tokens))

View File

@ -9,12 +9,14 @@
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.logic.tokens :as clt] [app.common.logic.tokens :as clo]
[app.common.path-names :as cpn] [app.common.path-names :as cpn]
[app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.ids-map :as cthi]
[app.common.types.shape :as cts] [app.common.types.shape :as cts]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.changes :as dch] [app.main.data.changes :as dch]
[app.main.data.event :as ev] [app.main.data.event :as ev]
@ -34,19 +36,12 @@
;; TOKENS Getters ;; TOKENS Getters
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FIXME: lookup rename
(defn get-tokens-lib
[state]
(-> (dsh/lookup-file-data state)
(get :tokens-lib)))
(defn lookup-token-set (defn lookup-token-set
([state] ([state]
(when-let [selected (dm/get-in state [:workspace-tokens :selected-token-set-id])] (when-let [selected (dm/get-in state [:workspace-tokens :selected-token-set-id])]
(lookup-token-set state selected))) (lookup-token-set state selected)))
([state id] ([state id]
(some-> (get-tokens-lib state) (some-> (dsh/lookup-tokens-lib state)
(ctob/get-set id)))) (ctob/get-set id))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -231,8 +226,7 @@
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 data :tokens-lib)] tokens-lib (dsh/lookup-tokens-lib state)]
(if (and tokens-lib (ctob/get-theme tokens-lib (ctob/get-id token-theme))) (if (and tokens-lib (ctob/get-theme tokens-lib (ctob/get-id token-theme)))
(rx/of (ntf/show {:content (tr "errors.token-theme-already-exists") (rx/of (ntf/show {:content (tr "errors.token-theme-already-exists")
:type :toast :type :toast
@ -249,8 +243,8 @@
(ptk/reify ::update-token-theme (ptk/reify ::update-token-theme
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 data :tokens-lib)] tokens-lib (dsh/lookup-tokens-lib state)]
(if (and (not= id (ctob/get-id token-theme)) (if (and (not= id (ctob/get-id token-theme))
(ctob/get-theme tokens-lib (ctob/get-id token-theme))) (ctob/get-theme tokens-lib (ctob/get-id token-theme)))
(rx/of (ntf/show {:content (tr "errors.token-theme-already-exists") (rx/of (ntf/show {:content (tr "errors.token-theme-already-exists")
@ -259,7 +253,7 @@
:timeout 9000})) :timeout 9000}))
(let [changes (-> (pcb/empty-changes it) (let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/set-token-theme (ctob/get-id token-theme) token-theme))] (clo/generate-update-token-theme token-theme))]
(rx/of (dch/commit-changes changes)))))))) (rx/of (dch/commit-changes changes))))))))
(defn set-token-theme-active (defn set-token-theme-active
@ -269,31 +263,35 @@
(ptk/reify ::set-token-theme-active (ptk/reify ::set-token-theme-active
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
tokens-lib (get-tokens-lib state) tokens-status (dsh/lookup-tokens-status state)
changes (-> (pcb/empty-changes) tokens-lib (dsh/lookup-tokens-lib state)
(pcb/with-library-data data) changes (-> (pcb/empty-changes)
(clt/generate-set-active-token-theme tokens-lib id active?))] (pcb/with-library-data data)
(clo/generate-set-theme-status tokens-status tokens-lib id active?))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn toggle-token-theme-active (defn toggle-token-theme-active
[id] [id]
(assert (uuid? id) "expected a uuid for `id`")
(ptk/reify ::toggle-token-theme-active (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-tokens-source-data state)
tokens-lib (get-tokens-lib state) tokens-status (dsh/lookup-tokens-status state)
changes (-> (pcb/empty-changes it) tokens-lib (dsh/lookup-tokens-lib state)
(pcb/with-library-data data) changes (-> (pcb/empty-changes it)
(clt/generate-toggle-token-theme tokens-lib id))] (pcb/with-library-data data)
(clo/generate-toggle-theme tokens-status 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 (defn delete-token-theme
[id] [id]
(assert (uuid? id) "expected a uuid for `id`")
(ptk/reify ::delete-token-theme (ptk/reify ::delete-token-theme
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
@ -350,10 +348,11 @@
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 data :tokens-lib) tokens-lib (dsh/lookup-tokens-lib state)
suffix (tr "workspace.tokens.duplicate-suffix")] suffix (tr "workspace.tokens.duplicate-suffix")]
(when-let [token-set (ctob/duplicate-set id tokens-lib {:suffix suffix})] (when-let [token-set (when tokens-lib
(ctob/duplicate-set id tokens-lib {:suffix suffix}))]
(when id-ref (reset! id-ref (ctob/get-id token-set))) (when id-ref (reset! id-ref (ctob/get-id token-set)))
(let [changes (-> (pcb/empty-changes it) (let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
@ -362,32 +361,34 @@
(dch/commit-changes changes))))))))) (dch/commit-changes changes)))))))))
(defn set-enabled-token-set (defn set-enabled-token-set
[name enabled?] [id enabled?]
(assert (string? name) "expected a string for `name`") (assert (uuid? id) "expected a uuid for `id`")
(assert (boolean? enabled?) "expected a boolean for `enabled?`") (assert (boolean? enabled?) "expected a boolean for `enabled?`")
(ptk/reify ::set-enabled-token-set (ptk/reify ::set-enabled-token-set
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
tlib (get-tokens-lib state) tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes) tokens-status (dsh/lookup-tokens-status state)
(pcb/with-library-data data) changes (-> (pcb/empty-changes)
(clt/generate-set-enabled-token-set tlib name enabled?))] (pcb/with-library-data data)
(clo/generate-set-enabled-token-set tokens-status tokens-lib id enabled?))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn toggle-token-set (defn toggle-token-set
[name] [id]
(assert (string? name) "expected a string for `name`") (assert (uuid? id) "expected a uuid for `id`")
(ptk/reify ::toggle-token-set (ptk/reify ::toggle-token-set
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
tlib (get-tokens-lib state) tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes) tokens-status (dsh/lookup-tokens-status state)
(pcb/with-library-data data) changes (-> (pcb/empty-changes)
(clt/generate-toggle-token-set tlib name))] (pcb/with-library-data data)
(clo/generate-toggle-token-set tokens-status tokens-lib id))]
(rx/of (rx/of
(dch/commit-changes changes) (dch/commit-changes changes)
@ -398,10 +399,12 @@
(ptk/reify ::toggle-token-set-group (ptk/reify ::toggle-token-set-group
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes) tokens-lib (dsh/lookup-tokens-lib state)
(pcb/with-library-data data) tokens-status (dsh/lookup-tokens-status state)
(clt/generate-toggle-token-set-group (get-tokens-lib state) group-path))] changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-toggle-token-set-group tokens-status tokens-lib group-path))]
(rx/of (rx/of
(dch/commit-changes changes) (dch/commit-changes changes)
@ -413,12 +416,27 @@
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
status (cfo/make-tokens-status-from-lib lib)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/set-tokens-lib lib))] (pcb/set-tokens-lib lib)
(pcb/set-tokens-status status))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
(defn set-tokens-source
[library-id]
(ptk/reify ::set-tokens-source
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
library (dsh/lookup-file state library-id)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(clo/generate-set-tokens-source library)
(pcb/set-tokens-source library-id))]
(rx/of (dch/commit-changes changes))))))
(defn delete-token-set (defn delete-token-set
[id] [id]
(ptk/reify ::delete-token-set (ptk/reify ::delete-token-set
@ -439,7 +457,7 @@
(let [data (dsh/lookup-file-data state) (let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(clt/generate-delete-token-set-group (get-tokens-lib state) path))] (clo/generate-delete-token-set-group (dsh/lookup-tokens-lib state) path))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))))) (dwtp/propagate-workspace-tokens))))))
@ -467,7 +485,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(try (try
(when-let [changes (clt/generate-move-token-set-group (pcb/empty-changes it) (get-tokens-lib state) drop-opts)] (when-let [changes (clo/generate-move-token-set-group (pcb/empty-changes it) (dsh/lookup-tokens-lib state) drop-opts)]
(rx/of (rx/of
(dch/commit-changes changes) (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))) (dwtp/propagate-workspace-tokens)))
@ -483,9 +501,9 @@
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(try (try
(let [tokens-lib (get-tokens-lib state) (let [tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(clt/generate-move-token-set tokens-lib params))] (clo/generate-move-token-set tokens-lib params))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))) (dwtp/propagate-workspace-tokens)))
(catch :default cause (catch :default cause
@ -506,11 +524,13 @@
token-set token-set
(ctob/make-token-set :name set-name) (ctob/make-token-set :name set-name)
hidden-theme hidden-theme ;; For legacy compatibility only
(ctob/make-hidden-theme) (-> (ctob/make-hidden-theme)
(ctob/enable-set set-name))
hidden-theme-with-set token-status
(ctob/enable-set hidden-theme set-name) (ctos/make-tokens-status :active-theme-ids #{}
:active-set-ids #{(ctob/get-id token-set)})
changes changes
(-> (pcb/empty-changes) (-> (pcb/empty-changes)
@ -518,8 +538,9 @@
(pcb/set-token-set (ctob/get-id token-set) token-set) (pcb/set-token-set (ctob/get-id token-set) token-set)
(pcb/set-token (ctob/get-id token-set) (:id token) token) (pcb/set-token (ctob/get-id token-set) (:id token) token)
(pcb/set-token-theme (ctob/get-id hidden-theme) (pcb/set-token-theme (ctob/get-id hidden-theme)
hidden-theme-with-set) hidden-theme)
(pcb/set-active-token-themes #{ctob/hidden-theme-path}))] (pcb/set-tokens-status token-status))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(set-selected-token-set-id (ctob/get-id token-set))))))) (set-selected-token-set-id (ctob/get-id token-set)))))))
@ -559,7 +580,7 @@
(let [token-set (lookup-token-set state set-id) (let [token-set (lookup-token-set state set-id)
data (dsh/lookup-file-data state) data (dsh/lookup-file-data state)
changes (reduce (fn [changes token-id] changes (reduce (fn [changes token-id]
(let [token (-> (get-tokens-lib state) (let [token (-> (dsh/lookup-tokens-lib state)
(ctob/get-token (ctob/get-id token-set) token-id)) (ctob/get-token (ctob/get-id token-set) token-id))
new-name (-> new-name (->
(cpn/split-path (:name token) :separator ".") (cpn/split-path (:name token) :separator ".")
@ -589,21 +610,22 @@
(lookup-token-set state set-id) (lookup-token-set state set-id)
(lookup-token-set state)) (lookup-token-set state))
data (dsh/lookup-file-data state) data (dsh/lookup-file-data state)
token (-> (get-tokens-lib state) tokens-lib (dsh/lookup-tokens-lib state)]
(ctob/get-token (ctob/get-id token-set) id)) (when (and tokens-lib token-set)
token' (->> (merge token params) (let [token (ctob/get-token tokens-lib (ctob/get-id token-set) id)
(into {}) token' (->> (merge token params)
(ctob/make-token)) (into {})
token-type (:type token) (ctob/make-token))
changes (-> (pcb/empty-changes it) token-type (:type token)
(pcb/with-library-data data) changes (-> (pcb/empty-changes it)
(pcb/set-token (ctob/get-id token-set) (pcb/with-library-data data)
id (pcb/set-token (ctob/get-id token-set)
token'))] id
(toggle-token-path (str (name token-type) "." (:name token))) token'))]
(rx/of (dch/commit-changes changes) (toggle-token-path (str (name token-type) "." (:name token)))
(ev/event (-> {::ev/name "edit-token" :type token-type} (rx/of (dch/commit-changes changes)
(merge (meta it)))))))))) (ev/event (-> {::ev/name "edit-token" :type token-type}
(merge (meta it))))))))))))
(defn bulk-update-tokens (defn bulk-update-tokens
[set-id token-ids type old-path new-path & {:keys [undo-group]}] [set-id token-ids type old-path new-path & {:keys [undo-group]}]
@ -617,7 +639,7 @@
(lookup-token-set state)) (lookup-token-set state))
data (dsh/lookup-file-data state) data (dsh/lookup-file-data state)
changes (reduce (fn [changes token-id] changes (reduce (fn [changes token-id]
(let [token (-> (get-tokens-lib state) (let [token (-> (dsh/lookup-tokens-lib state)
(ctob/get-token (ctob/get-id token-set) token-id)) (ctob/get-token (ctob/get-id token-set) token-id))
new-name (str/replace (:name token) old-path new-path) new-name (str/replace (:name token) old-path new-path)
token' (->> (merge token {:name new-name}) token' (->> (merge token {:name new-name})
@ -643,19 +665,20 @@
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 (dsh/lookup-tokens-lib state)
token-set (if set-id token-set (if set-id
(lookup-token-set state set-id) (lookup-token-set state set-id)
(lookup-token-set state)) (lookup-token-set state))]
token (-> (get-tokens-lib state) (when (and tokens-lib token-set)
(ctob/get-token (ctob/get-id token-set) token-id)) (let [token (ctob/get-token tokens-lib (ctob/get-id token-set) token-id)
token-type (:type token) token-type (:type token)
changes (-> (pcb/empty-changes it) changes (-> (pcb/empty-changes it)
(pcb/with-library-data data) (pcb/with-library-data data)
(pcb/set-token set-id token-id nil))] (pcb/set-token set-id token-id nil))]
(rx/of (dch/commit-changes changes) (rx/of (dch/commit-changes changes)
(ev/event (-> {::ev/name "delete-token" :type token-type} (ev/event (-> {::ev/name "delete-token" :type token-type}
(merge (meta it))))))))) (merge (meta it)))))))))))
(defn bulk-delete-tokens (defn bulk-delete-tokens
[set-id token-ids] [set-id token-ids]
@ -680,7 +703,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(when-let [token-set (lookup-token-set state)] (when-let [token-set (lookup-token-set state)]
(when-let [tokens-lib (get-tokens-lib state)] (when-let [tokens-lib (dsh/lookup-tokens-lib state)]
(when-let [token (ctob/get-token tokens-lib (when-let [token (ctob/get-token tokens-lib
(ctob/get-id token-set) (ctob/get-id token-set)
token-id)] token-id)]

View File

@ -8,10 +8,10 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.time :as ct] [app.common.time :as ct]
[app.common.types.token :as ctt] [app.common.types.token :as ctt]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf] [app.config :as cf]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.style-dictionary :as sd] [app.main.data.style-dictionary :as sd]
@ -188,27 +188,28 @@
(ptk/reify ::propagate-workspace-tokens (ptk/reify ::propagate-workspace-tokens
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(when-let [tokens-tree (-> (dsh/lookup-file-data state) (let [tokens-lib (dsh/lookup-tokens-lib state)
(get :tokens-lib) tokens-status (dsh/lookup-tokens-status state)]
(ctob/get-tokens-in-active-sets))] (when (and tokens-lib tokens-status)
(->> (if (contains? cf/flags :tokenscript) (when-let [tokens-tree (cfo/get-tokens-in-active-sets tokens-status tokens-lib)]
(rx/of (-> (ts/resolve-tokens tokens-tree) (->> (if (contains? cf/flags :tokenscript)
(d/update-vals #(update % :resolved-value ts/tokenscript-symbols->penpot-unit)))) (rx/of (-> (ts/resolve-tokens tokens-tree)
(sd/resolve-tokens tokens-tree)) (d/update-vals #(update % :resolved-value ts/tokenscript-symbols->penpot-unit))))
(rx/mapcat (sd/resolve-tokens tokens-tree))
(fn [sd-tokens] (rx/mapcat
(let [undo-id (js/Symbol)] (fn [sd-tokens]
(rx/concat (let [undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id :timeout false)) (rx/concat
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
;; FIXME: now the tokens propagations is done by accumulating the update-shapes ;; FIXME: now the tokens propagations is done by accumulating the update-shapes
;; into a single commit-changes. This is not really the best way, the token application ;; into a single commit-changes. This is not really the best way, the token application
;; should be done with a changes_builder and sending only one `commit-changes` instead ;; should be done with a changes_builder and sending only one `commit-changes` instead
;; of creating lots of `update-shapes`. ;; of creating lots of `update-shapes`.
(rx/of (dwsh/update-shapes-buffer-start)) (rx/of (dwsh/update-shapes-buffer-start))
(->> (propagate-tokens state sd-tokens) (->> (propagate-tokens state sd-tokens)
(rx/catch #(rx/concat (rx/catch #(rx/concat
(rx/of (dwsh/update-shapes-buffer-stop)) (rx/of (dwsh/update-shapes-buffer-stop))
(rx/throw %)))) (rx/throw %))))
(rx/of (dwsh/update-shapes-buffer-stop)) (rx/of (dwsh/update-shapes-buffer-stop))
(rx/of (dwu/commit-undo-transaction undo-id))))))))))) (rx/of (dwu/commit-undo-transaction undo-id)))))))))))))

View File

@ -13,26 +13,22 @@
(defn- get-selected-token-set-id [state] (defn- get-selected-token-set-id [state]
(or (get-in state [:workspace-tokens :selected-token-set-id]) (or (get-in state [:workspace-tokens :selected-token-set-id])
(some-> (dsh/lookup-file-data state) (some-> (dsh/lookup-tokens-lib state)
(get :tokens-lib)
(ctob/get-sets) (ctob/get-sets)
(first) (first)
(ctob/get-id)))) (ctob/get-id))))
(defn get-selected-token-set [state] (defn get-selected-token-set [state]
(when-let [set-id (get-selected-token-set-id state)] (when-let [set-id (get-selected-token-set-id state)]
(some-> (dsh/lookup-file-data state) (some-> (dsh/lookup-tokens-lib state)
(get :tokens-lib)
(ctob/get-set set-id)))) (ctob/get-set set-id))))
(defn get-token-in-selected-set [state token-id] (defn get-token-in-selected-set [state token-id]
(when-let [set-id (get-selected-token-set-id state)] (when-let [set-id (get-selected-token-set-id state)]
(some-> (dsh/lookup-file-data state) (some-> (dsh/lookup-tokens-lib state)
(get :tokens-lib)
(ctob/get-token set-id token-id)))) (ctob/get-token set-id token-id))))
(defn get-all-tokens-in-selected-set [state] (defn get-all-tokens-in-selected-set [state]
(when-let [set-id (get-selected-token-set-id state)] (when-let [set-id (get-selected-token-set-id state)]
(some-> (dsh/lookup-file-data state) (some-> (dsh/lookup-tokens-lib state)
(get :tokens-lib)
(ctob/get-tokens set-id)))) (ctob/get-tokens set-id))))

View File

@ -10,9 +10,11 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cph] [app.common.files.helpers :as cph]
[app.common.files.tokens :as cfo]
[app.common.types.shape-tree :as ctt] [app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.config :as cf] [app.config :as cf]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.workspace.tokens.selected-set :as dwts] [app.main.data.workspace.tokens.selected-set :as dwts]
@ -465,7 +467,10 @@
;; ---- Token refs ;; ---- Token refs
(def tokens-lib (def tokens-lib
(l/derived :tokens-lib workspace-data)) (l/derived dsh/lookup-tokens-lib st/state))
(def tokens-status
(l/derived dsh/lookup-tokens-status st/state))
(def workspace-token-theme-groups (def workspace-token-theme-groups
(l/derived (d/nilf ctob/get-theme-groups) tokens-lib)) (l/derived (d/nilf ctob/get-theme-groups) tokens-lib))
@ -502,26 +507,28 @@
(def workspace-token-sets-tree (def workspace-token-sets-tree
(l/derived (d/nilf ctob/get-set-tree) tokens-lib)) (l/derived (d/nilf ctob/get-set-tree) tokens-lib))
(def workspace-active-theme-paths (def workspace-active-theme-ids
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib)) (l/derived (d/nilf ctos/get-active-theme-ids) tokens-status))
(def workspace-all-tokens-map (def workspace-all-tokens-map
(l/derived (d/nilf ctob/get-all-tokens-map) tokens-lib)) (l/derived (d/nilf ctob/get-all-tokens-map) tokens-lib))
;; TODOstatus ver una forma eficiente de hacer un derived de lib y status a la vez
(defn token-sets-at-path-all-active (defn token-sets-at-path-all-active
[group-path] [group-path]
(l/derived (l/derived
(fn [lib] (fn [status]
(when lib (when status
(ctob/sets-at-path-all-active? lib group-path))) (cfo/sets-at-path-all-active? status @(l/derived identity tokens-lib) group-path)))
tokens-lib)) tokens-status))
(def workspace-active-theme-paths-no-hidden
(l/derived #(disj % ctob/hidden-theme-path) workspace-active-theme-paths))
;; FIXME: deprecated, it should not be implemented with ref (still used in form) ;; FIXME: deprecated, it should not be implemented with ref (still used in form)
(def workspace-active-theme-sets-tokens (def workspace-active-theme-sets-tokens
(l/derived #(or (some-> % ctob/get-tokens-in-active-sets) {}) tokens-lib)) (l/derived (fn [[status lib]]
(if (and status lib)
(cfo/get-tokens-in-active-sets status lib)
{}))
[tokens-status tokens-lib]))
(def workspace-token-in-selected-set (def workspace-token-in-selected-set
(fn [token-id] (fn [token-id]

View File

@ -57,6 +57,7 @@
.icon { .icon {
color: var(--arrow-icon-color); color: var(--arrow-icon-color);
flex-shrink: 0;
} }
.title-wrapper { .title-wrapper {

View File

@ -25,6 +25,8 @@
(def libraries (mf/create-context nil)) (def libraries (mf/create-context nil))
(def design-tokens (mf/create-context nil)) (def design-tokens (mf/create-context nil))
(def token-inputs (mf/create-context nil)) (def token-inputs (mf/create-context nil))
(def tokens-lib (mf/create-context nil))
(def tokens-status (mf/create-context nil))
(def current-scroll (mf/create-context nil)) (def current-scroll (mf/create-context nil))
(def current-zoom (mf/create-context nil)) (def current-zoom (mf/create-context nil))
@ -45,6 +47,7 @@
(def permissions (mf/create-context nil)) (def permissions (mf/create-context nil))
(def can-edit? (mf/create-context nil)) (def can-edit? (mf/create-context nil))
(def can-edit-tokens? (mf/create-context nil))
(def active-tokens-by-type (def active-tokens-by-type
"Active tokens by type, used mainly for provide tokens data to the "Active tokens by type, used mainly for provide tokens data to the

View File

@ -314,7 +314,8 @@
"A collection of all icons" "A collection of all icons"
(collect-icons)) (collect-icons))
(def ^:private ^:const icon-size-l 32) (def ^:private ^:const icon-size-xl 32)
(def ^:private ^:const icon-size-l 24)
(def ^:private ^:const icon-size-m 16) (def ^:private ^:const icon-size-m 16)
(def ^:private ^:const icon-size-s 12) (def ^:private ^:const icon-size-s 12)
@ -323,12 +324,13 @@
[:class {:optional true} [:maybe :string]] [:class {:optional true} [:maybe :string]]
[:icon-id [:and :string [:fn #(contains? icon-list %)]]] [:icon-id [:and :string [:fn #(contains? icon-list %)]]]
[:size {:optional true} [:size {:optional true}
[:maybe [:enum "s" "m" "l"]]]]) [:maybe [:enum "s" "m" "l" "xl"]]]])
(mf/defc icon* (mf/defc icon*
{::mf/schema schema:icon} {::mf/schema schema:icon}
[{:keys [icon-id size class] :rest props}] [{:keys [icon-id size class] :rest props}]
(let [size-px (cond (= size "l") icon-size-l (let [size-px (cond (= size "xl") icon-size-xl
(= size "l") icon-size-l
(= size "s") icon-size-s (= size "s") icon-size-s
:else icon-size-m) :else icon-size-m)
offset (if (or (= size "s") (= size "m")) offset (if (or (= size "s") (= size "m"))

View File

@ -25,6 +25,6 @@
(when icon (when icon
[:div {:class (stl/css :icon-wrapper)} [:div {:class (stl/css :icon-wrapper)}
[:> icon* {:icon-id icon [:> icon* {:icon-id icon
:size "l" :size "xl"
:class (stl/css :icon)}]]) :class (stl/css :icon)}]])
[:div {:class (stl/css :text)} text]])) [:div {:class (stl/css :text)} text]]))

View File

@ -9,6 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.component :as ctc] [app.common.types.component :as ctc]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
@ -111,11 +112,17 @@
panels (type->panel-group shape-type) panels (type->panel-group shape-type)
tokens-lib (mf/deref refs/tokens-lib) tokens-lib (mf/deref refs/tokens-lib)
active-themes (mf/deref refs/workspace-active-theme-paths-no-hidden) tokens-status (mf/deref refs/tokens-status)
active-sets (mf/with-memo [tokens-lib] active-theme-ids (mf/deref refs/workspace-active-theme-ids)
(some-> tokens-lib (ctob/get-active-themes-set-names))) active-themes (mf/with-memo [active-theme-ids tokens-lib]
active-tokens (mf/with-memo [tokens-lib] (when tokens-lib
(some-> tokens-lib (ctob/get-tokens-in-active-sets))) (keep #(some-> (ctob/get-theme tokens-lib %) ctob/get-name) active-theme-ids)))
active-sets (mf/with-memo [tokens-status tokens-lib]
(when (and tokens-status tokens-lib)
(cfo/get-active-sets tokens-status tokens-lib)))
active-tokens (mf/with-memo [tokens-status tokens-lib]
(when (and tokens-status tokens-lib)
(cfo/get-tokens-in-active-sets tokens-status tokens-lib)))
resolved-active-tokens (sd/use-resolved-tokens* active-tokens) resolved-active-tokens (sd/use-resolved-tokens* active-tokens)
has-visibility-props? (mf/use-fn has-visibility-props? (mf/use-fn
(fn [shape] (fn [shape]
@ -152,7 +159,7 @@
(when (or (seq active-themes) (seq active-sets)) (when (or (seq active-themes) (seq active-sets))
[:li [:li
[:> style-box* {:panel :token} [:> style-box* {:panel :token}
[:> tokens-panel* {:theme-paths active-themes :set-names active-sets}]]]) [:> tokens-panel* {:theme-names active-themes :set-names (map ctob/get-name active-sets)}]]])
(for [panel panels] (for [panel panels]
[:li {:key (d/name panel)} [:li {:key (d/name panel)}
(case panel (case panel

View File

@ -13,10 +13,10 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc tokens-panel* (mf/defc tokens-panel*
[{:keys [theme-paths set-names]}] [{:keys [theme-names set-names]}]
[:div {:class (stl/css :tokens-panel)} [:div {:class (stl/css :tokens-panel)}
(when (seq theme-paths) (when (seq theme-names)
(let [theme-list (str/join ", " theme-paths)] (let [theme-list (str/join ", " theme-names)]
[:> properties-row* {:class (stl/css :token-theme) [:> properties-row* {:class (stl/css :token-theme)
:term (tr "inspect.tabs.styles.active-themes") :term (tr "inspect.tabs.styles.active-themes")
:detail theme-list}])) :detail theme-list}]))

View File

@ -9,6 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.types.color :as cc] [app.common.types.color :as cc]
@ -759,10 +760,15 @@
tokens-lib tokens-lib
(mf/deref refs/tokens-lib) (mf/deref refs/tokens-lib)
tokens-status
(mf/deref refs/tokens-status)
active-sets-names active-sets-names
(mf/with-memo [tokens-lib] (mf/with-memo [tokens-status tokens-lib]
(some-> tokens-lib (when (and tokens-status tokens-lib)
(ctob/get-active-themes-set-names))) (into #{}
(map ctob/get-name)
(cfo/get-active-sets tokens-status tokens-lib))))
active-tokens (if (delay? active-tokens) active-tokens (if (delay? active-tokens)
@active-tokens @active-tokens
@ -782,7 +788,7 @@
(filter-active-sets active-sets-names) (filter-active-sets active-sets-names)
(filter-non-empty-sets) (filter-non-empty-sets)
(group-sets) (group-sets)
(combine-groups-with-resolved color-tokens)))] (combine-groups-with-resolved color-tokens)))]
(mf/with-effect [] (mf/with-effect []
(st/emit! (st/emit! (dsc/push-shortcuts ::colorpicker sc/shortcuts :workspace))) (st/emit! (st/emit! (dsc/push-shortcuts ::colorpicker sc/shortcuts :workspace)))

View File

@ -9,6 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.files.variant :as cfv] [app.common.files.variant :as cfv]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
@ -24,6 +25,7 @@
[app.main.data.team :as dtm] [app.main.data.team :as dtm]
[app.main.data.workspace.colors :as mdc] [app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.render :refer [component-svg]] [app.main.render :refer [component-svg]]
[app.main.store :as st] [app.main.store :as st]
@ -34,7 +36,8 @@
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.ds.product.empty-state :refer [empty-state*]] [app.main.ui.ds.product.empty-state :refer [empty-state*]]
[app.main.ui.hooks :as h] [app.main.ui.hooks :as h]
@ -56,16 +59,27 @@
typographies (count (:typographies data)) typographies (count (:typographies data))
components (count (->> (ctkl/components-seq data) components (count (->> (ctkl/components-seq data)
(remove #(cfv/is-secondary-variant? % data)))) (remove #(cfv/is-secondary-variant? % data))))
tokens-lib (cfo/get-tokens-lib data)
tokens (if (some? tokens-lib) (count (ctob/get-all-tokens tokens-lib)) 0)
token-sets (if (some? tokens-lib) (count (ctob/get-sets tokens-lib)) 0)
token-themes (if (some? tokens-lib) (count (ctob/get-themes-no-hidden tokens-lib)) 0)
empty? (and (zero? components) empty? (and (zero? components)
(zero? graphics) (zero? graphics)
(zero? colors) (zero? colors)
(zero? typographies))] (zero? typographies)
(zero? tokens)
(zero? token-sets)
(zero? token-themes))]
{:is-empty empty? {:is-empty empty?
:colors colors :colors colors
:graphics graphics :graphics graphics
:typographies typographies :typographies typographies
:components components})) :components components
:tokens tokens
:token-sets token-sets
:token-themes token-themes}))
(defn- adapt-backend-summary (defn- adapt-backend-summary
[summary] [summary]
@ -73,16 +87,25 @@
graphics (or (-> summary :media :count) 0) graphics (or (-> summary :media :count) 0)
typographies (or (-> summary :typographies :count) 0) typographies (or (-> summary :typographies :count) 0)
colors (or (-> summary :colors :count) 0) colors (or (-> summary :colors :count) 0)
tokens (or (-> summary :tokens-count) 0)
token-sets (or (-> summary :token-sets-count) 0)
token-themes (or (-> summary :token-themes-count) 0)
empty? (and (zero? components) empty? (and (zero? components)
(zero? graphics) (zero? graphics)
(zero? colors) (zero? colors)
(zero? typographies))] (zero? typographies)
{:is-empty empty? (zero? tokens)
:components components (zero? token-sets)
:graphics graphics (zero? token-themes))]
{:is-empty empty?
:components components
:graphics graphics
:typographies typographies :typographies typographies
:colors colors})) :colors colors
:tokens tokens
:token-sets token-sets
:token-themes token-themes}))
(defn- describe-library (defn- describe-library
[components-count graphics-count colors-count typography-count] [components-count graphics-count colors-count typography-count]
@ -108,28 +131,88 @@
(mf/defc library-description* (mf/defc library-description*
{::mf/private true} {::mf/private true}
[{:keys [summary]}] [{:keys [summary hint-name]}]
(let [components-count (get summary :components) (let [components-count (get summary :components)
graphics-count (get summary :graphics) graphics-count (get summary :graphics)
typography-count (get summary :typographies) typography-count (get summary :typographies)
colors-count (get summary :colors)] colors-count (get summary :colors)
tokens-count (get summary :tokens)
token-sets-count (get summary :token-sets)
token-themes-count (get summary :token-themes)
[:* token-lib-sync? (contains? cf/flags :token-lib-sync)
(when (pos? components-count)
[:li {:class (stl/css :element-count)}
(tr "workspace.libraries.components" (c components-count))])
(when (pos? graphics-count) elements-line
[:li {:class (stl/css :element-count)} (str/join " · "
(tr "workspace.libraries.graphics" (c graphics-count))]) (cond-> []
(pos? components-count)
(conj (tr "workspace.libraries.components" (c components-count)))
(when (pos? colors-count) (pos? graphics-count)
[:li {:class (stl/css :element-count)} (conj (tr "workspace.libraries.graphics" (c graphics-count)))
(tr "workspace.libraries.colors" (c colors-count))])
(when (pos? typography-count) (pos? colors-count)
[:li {:class (stl/css :element-count)} (conj (tr "workspace.libraries.colors" (c colors-count)))
(tr "workspace.libraries.typography" (c typography-count))])]))
(pos? typography-count)
(conj (tr "workspace.libraries.typography" (c typography-count)))))
tokens-line
(str/join " · "
(cond-> []
(pos? tokens-count)
(conj (tr "workspace.libraries.tokens" (c tokens-count)))
(pos? token-sets-count)
(conj (tr "workspace.libraries.token-sets" (c token-sets-count)))
(pos? token-themes-count)
(conj (tr "workspace.libraries.token-themes" (c token-themes-count)))))]
(if token-lib-sync?
;; New format: show elements and tokens in separate lines
[:*
(when-not (str/empty? elements-line)
[:li {:class (stl/css :element-count-line)} elements-line])
(when-not (str/empty? tokens-line)
[:li {:class (stl/css :element-count-line)} tokens-line])
(when hint-name
[:li {:class (stl/css :hint-line)}
"(" (tr "workspace.libraries.connected-through") " "
[:span {:class (stl/css :hint-library-name)} hint-name]
")"])]
;; Old format: show each item individually
[:*
(when (pos? components-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.components" (c components-count))])
(when (pos? graphics-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.graphics" (c graphics-count))])
(when (pos? colors-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.colors" (c colors-count))])
(when (pos? typography-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.typography" (c typography-count))])
(when (pos? tokens-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.tokens" (c tokens-count))])
(when (pos? token-sets-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.token-sets" (c token-sets-count))])
(when (pos? token-themes-count)
[:li {:class (stl/css :element-count-legacy)}
(tr "workspace.libraries.token-themes" (c token-themes-count))])])))
(mf/defc sample-library-entry* (mf/defc sample-library-entry*
{::mf/private true} {::mf/private true}
@ -182,7 +265,7 @@
(when-let [tokens-lib (get data :tokens-lib)] (when-let [tokens-lib (get data :tokens-lib)]
(not (ctob/empty-lib? tokens-lib)))) (not (ctob/empty-lib? tokens-lib))))
(mf/defc libraries-tab* (mf/defc libraries-tab-legacy*
{::mf/private true} {::mf/private true}
[{:keys [is-shared linked-libraries shared-libraries]}] [{:keys [is-shared linked-libraries shared-libraries]}]
(let [file-id (mf/use-ctx ctx/current-file-id) (let [file-id (mf/use-ctx ctx/current-file-id)
@ -262,7 +345,7 @@
unlink-library unlink-library
(mf/use-fn (mf/use-fn
(mf/deps file-id) (mf/deps file-id local-library)
(fn [event] (fn [event]
(let [library-id (some-> (dom/get-current-target event) (let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id") (dom/get-data "library-id")
@ -270,7 +353,11 @@
(when (= library-id @selected) (when (= library-id @selected)
(reset! selected :file)) (reset! selected :file))
(st/emit! (dwl/unlink-file-from-library file-id library-id) (st/emit! (dwl/unlink-file-from-library file-id library-id)
(dwl/sync-file file-id library-id))))) (dwl/sync-file file-id library-id))
;; When the unlinked library is the current tokens source,
;; we must reset it to the local library.
(when (cfo/effective-tokens-source? local-library library-id)
(st/emit! (dwtl/set-tokens-source (:id local-library)))))))
import-tokens import-tokens
(mf/use-fn (mf/use-fn
@ -283,6 +370,7 @@
:tokens/import-from-library {:file-id file-id :tokens/import-from-library {:file-id file-id
:library-id library-id}))))) :library-id library-id})))))
on-delete-accept on-delete-accept
(mf/use-fn (mf/use-fn
(mf/deps file-id) (mf/deps file-id)
@ -324,7 +412,7 @@
:on-cancel on-delete-cancel :on-cancel on-delete-cancel
:count-libraries 1}))))] :count-libraries 1}))))]
[:div {:class (stl/css :libraries-content)} [:div {:class (stl/css :libraries-content-legacy)}
[:div {:class (stl/css :lib-section)} [:div {:class (stl/css :lib-section)}
[:> title-bar* {:collapsable false [:> title-bar* {:collapsable false
:title (tr "workspace.libraries.in-this-file") :title (tr "workspace.libraries.in-this-file")
@ -334,7 +422,7 @@
[:div {:class (stl/css :section-list-publish)} [:div {:class (stl/css :section-list-publish)}
[:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-title)} (tr "workspace.libraries.file-library")] [:div {:class (stl/css :item-title)} (tr "workspace.libraries.file-library")]
[:ul {:class (stl/css :item-contents)} [:ul {:class (stl/css :item-contents-legacy)}
[:> library-description* {:summary summary}]]] [:> library-description* {:summary summary}]]]
(if ^boolean is-shared (if ^boolean is-shared
@ -352,21 +440,21 @@
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to) (let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
has-tokens? (and (has-tokens? library) has-tokens? (and (has-tokens? library)
(contains? cf/flags :token-import-from-library))] (contains? cf/flags :token-import-from-library))]
[:div {:class (stl/css :section-list-item) [:div {:class (stl/css :section-list-item-legacy)
:key (dm/str id) :key (dm/str id)
:data-testid "library-item"} :data-testid "library-item"}
[:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-content)}
[:div {:class (stl/css-case :item-name true [:div {:class (stl/css :item-name)} name]
:item-name-short has-tokens?)} name] [:ul {:class (stl/css :item-contents-legacy)}
[:ul {:class (stl/css :item-contents)}
(let [summary (get-library-summary data)] (let [summary (get-library-summary data)]
[:* [:*
[:> library-description* {:summary summary}] [:> library-description* {:summary summary}]
(when (seq connected-to) (when (seq connected-to)
[:div {:class (stl/css :connected-to-wrapper)} [:div {:class (stl/css :connected-to-wrapper-legacy)}
[:span "(" (tr "workspace.libraries.connected-to") " "] [:span "(" (tr "workspace.libraries.connected-to") " "]
[:span {:class (stl/css :connected-to-values)} (str/join ", " connected-to-names)] [:span {:class (stl/css :connected-to-values)} (str/join ", " connected-to-names)]
[:span ")"]])])]] [:span ")"]])])]]
[:div {:class (stl/css :library-actions)} [:div {:class (stl/css :library-actions)}
(when ^boolean has-tokens? (when ^boolean has-tokens?
[:> icon-button* [:> icon-button*
@ -397,12 +485,12 @@
(if (seq shared-libraries) (if (seq shared-libraries)
[:div {:class (stl/css :section-list-shared)} [:div {:class (stl/css :section-list-shared)}
(for [{:keys [id name] :as library} shared-libraries] (for [{:keys [id name] :as library} shared-libraries]
[:div {:class (stl/css :section-list-item) [:div {:class (stl/css :section-list-item-legacy)
:key (dm/str id) :key (dm/str id)
:data-testid "library-item"} :data-testid "library-item"}
[:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-name)} name] [:div {:class (stl/css :item-name)} name]
[:ul {:class (stl/css :item-contents)} [:ul {:class (stl/css :item-contents-legacy)}
(let [summary (-> (:library-summary library) (let [summary (-> (:library-summary library)
(adapt-backend-summary))] (adapt-backend-summary))]
[:> library-description* {:summary summary}])]] [:> library-description* {:summary summary}])]]
@ -439,6 +527,372 @@
:else :else
(tr "workspace.libraries.no-matches-for" search-term))]))]])) (tr "workspace.libraries.no-matches-for" search-term))]))]]))
(mf/defc libraries-tab*
{::mf/private true}
[{:keys [linked-libraries shared-libraries]}]
(let [file-id (mf/use-ctx ctx/current-file-id)
search-term* (mf/use-state "")
search-term (deref search-term*)
selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent)
dependencies (mf/with-memo [shared-libraries]
(into {} (map (juxt :id :library-file-ids) (vals shared-libraries))))
library-names (mf/with-memo [shared-libraries]
(into {} (map (fn [{:keys [id name]}]
[id name])
(vals shared-libraries))))
find-connected-to
(mf/use-fn
(mf/deps dependencies)
(fn [library-id]
(->> dependencies
(keep (fn [[k v]] (when (contains? v library-id) k))))))
shared-libraries
(mf/with-memo [shared-libraries linked-libraries file-id search-term]
(when shared-libraries
(->> (vals shared-libraries)
(remove #(= (:id %) file-id))
(filter #(matches-search (:name %) search-term))
(map #(assoc % :connected-to (find-connected-to (:id %))))
(map #(assoc % :connected-to-names (->> (:connected-to %)
(keep library-names))))
(map #(assoc % :linked? (contains? linked-libraries (:id %))))
(sort-by (comp str/lower :name)))))
importing*
(mf/use-state nil)
sample-libraries
(mf/with-memo []
[{:id "penpot-design-system", :name "Design system example"}
{:id "wireframing-kit", :name "Wireframe library"}
{:id "whiteboarding-kit", :name "Whiteboarding Kit"}])
change-search-term
(mf/use-fn
(fn [event]
(reset! search-term* event)))
link-library
(mf/use-fn
(mf/deps file-id)
(fn [event]
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
(uuid/parse))]
(reset! selected library-id)
(st/emit! (dwl/link-file-to-library file-id library-id)))))]
[:div {:class (stl/css :libraries-content)}
[:div {:class (stl/css :shared-section)}
[:> title-bar* {:collapsable false
:title (tr "workspace.libraries.shared-libraries")
:class (stl/css :title-spacing-lib)}]
[:> search-bar* {:on-change change-search-term
:value search-term
:placeholder (tr "workspace.libraries.search-shared-libraries")
:icon-id i/search}]
(if (seq shared-libraries)
[:div {:class (stl/css :section-list-shared)}
(for [{:keys [id name linked?] :as library} shared-libraries]
[:div {:class (stl/css :section-list-item)
:key (dm/str id)
:data-testid "library-item"}
[:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-name)} name]
[:ul {:class (stl/css :item-contents)}
(let [summary (-> (:library-summary library)
(adapt-backend-summary))]
[:> library-description* {:summary summary}])]]
(if ^boolean linked?
[:> text* {:class (stl/css :shared-library-added)
:as "span"
:typography "body-medium"}
[:> icon* {:icon-id i/tick
:class (stl/css :shared-library-added-icon)}]
(tr "workspace.libraries.shared-library-added")]
[:> icon-button* {:class (stl/css :item-button-shared)
:variant "secondary"
:data-library-id (dm/str id)
:icon "add"
:aria-label (tr "workspace.libraries.shared-library-btn")
:on-click link-library}])])]
(when (empty? shared-libraries)
[:div {:class (stl/css :section-list-empty)}
(cond
(nil? shared-libraries)
(tr "workspace.libraries.loading")
(str/empty? search-term)
[:*
[:div {:class (stl/css :sample-libraries-info)}
(tr "workspace.libraries.empty.no-libraries")
[:a {:target "_blank"
:class (stl/css :sample-libraries-link)
:href "https://penpot.app/libraries-templates"}
(tr "workspace.libraries.empty.some-templates")]]
[:div {:class (stl/css :sample-libraries-container)}
(tr "workspace.libraries.empty.add-some")
(for [library sample-libraries]
[:> sample-library-entry*
{:library library
:key (dm/str (:id library))
:importing importing*}])]]
:else
(tr "workspace.libraries.no-matches-for" search-term))]))]]))
(mf/defc file-tab*
{::mf/private true}
[{:keys [is-shared linked-libraries shared-libraries on-change-tab]}]
(let [file-id (mf/use-ctx ctx/current-file-id)
;; The summary of the current/local library
;; NOTE: we only need a snapshot of current library
local-library (deref refs/workspace-data)
summary (get-library-summary local-library)
empty-library? (empty-library? summary)
selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent)
dependencies (mf/with-memo [shared-libraries]
(into {} (map (juxt :id :library-file-ids) (vals shared-libraries))))
library-names (mf/with-memo [shared-libraries]
(into {} (map (fn [{:keys [id name]}]
[id name])
(vals shared-libraries))))
find-connected-to
(mf/use-fn
(mf/deps dependencies)
(fn [library-id]
(->> dependencies
(keep (fn [[k v]] (when (contains? v library-id) k))))))
linked-libraries
(mf/with-memo [linked-libraries find-connected-to library-names]
(let [libs
(->> (vals linked-libraries)
(map #(assoc % :connected-to (find-connected-to (:id %)))))
linked-ids
(into #{} (map :id) libs)
sort-by-name
(partial sort-by (comp str/lower :name))
parent-id
(fn [{:keys [connected-to]}]
(first (filter linked-ids connected-to)))
nested-by-parent
(->> libs
(filter parent-id)
(map #(assoc %
:nested? true
:parent-name (library-names (parent-id %))))
(group-by parent-id)
(d/mapm (fn [_ v] (sort-by-name v))))]
(->> libs
(remove parent-id)
(map #(assoc % :nested? false))
(sort-by-name)
(mapcat (fn [{:keys [id] :as library}]
(cons library (get nested-by-parent id [])))))))
linked-libraries-ids
(mf/with-memo [linked-libraries]
(into #{} d/xf:map-id linked-libraries))
unlink-library
(mf/use-fn
(mf/deps file-id local-library)
(fn [event]
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
(uuid/parse))]
(when (= library-id @selected)
(reset! selected :file))
(st/emit! (dwl/unlink-file-from-library file-id library-id)
(dwl/sync-file file-id library-id))
;; When the unlinked library is the current tokens source,
;; we must reset it to the local library.
(when (cfo/effective-tokens-source? local-library library-id)
(st/emit! (dwtl/set-tokens-source (:id local-library)))))))
import-tokens
(mf/use-fn
(mf/deps file-id)
(fn [event]
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
(uuid/parse))]
(st/emit! (modal/show
:tokens/import-from-library {:file-id file-id
:library-id library-id})))))
set-as-tokens-source
(mf/use-fn
(fn [event]
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
(uuid/parse))]
(st/emit! (dwtl/set-tokens-source library-id)))))
on-delete-accept
(mf/use-fn
(mf/deps file-id)
#(st/emit! (dwl/set-file-shared file-id false)
(modal/show :libraries-dialog {:file-id file-id})))
on-delete-cancel
(mf/use-fn
(mf/deps file-id)
#(st/emit! (modal/show :libraries-dialog {:file-id file-id})))
publish
(mf/use-fn
(mf/deps file-id)
(fn [event]
(let [input-node (dom/get-target event)
publish-library #(st/emit! (dwl/set-file-shared file-id true))
cancel-publish #(st/emit! (modal/show :libraries-dialog {:file-id file-id}))]
(if empty-library?
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.publish-empty-library.title")
:message (tr "modals.publish-empty-library.message")
:accept-label (tr "modals.publish-empty-library.accept")
:on-accept publish-library
:on-cancel cancel-publish}))
(publish-library))
(dom/blur! input-node))))
unpublish
(mf/use-fn
(mf/deps file-id)
(fn [_]
(st/emit! (modal/show
{:type :delete-shared-libraries
:ids #{file-id}
:origin :unpublish
:on-accept on-delete-accept
:on-cancel on-delete-cancel
:count-libraries 1}))))
go-to-shared
(mf/use-fn
(mf/deps on-change-tab)
(fn [_]
(on-change-tab "libraries")))]
[:div {:class (stl/css :lib-section)}
[:> title-bar* {:collapsable false
:title "ASSETS IN THIS FILE"
:class (stl/css :title-spacing-lib)}]
[:div {:class (stl/css :section-list)}
[:div {:class (stl/css :section-list-publish)}
[:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-title)} (tr "workspace.libraries.file-library")]
[:ul {:class (stl/css :item-contents)}
[:> library-description* {:summary summary}]]]
(when (and (contains? cf/flags :token-lib-sync)
(cfo/effective-tokens-source? local-library (:id local-library))
(cfo/has-own-tokens? local-library))
[:> text* {:class (stl/css :tokens-source-label)
:as "span"
:typography "body-medium"}
(tr "workspace.libraries.tokens-source")])
(when (and (contains? cf/flags :token-lib-sync)
(not (cfo/effective-tokens-source? local-library (:id local-library))))
[:> button* {:variant "secondary"
:type "button"
:data-library-id (dm/str (:id local-library))
:on-click set-as-tokens-source}
(tr "workspace.libraries.set-as-tokens-source")])
(if ^boolean is-shared
[:> button* {:variant "secondary"
:type "button"
:on-click unpublish}
(tr "common.unpublish")]
[:> button* {:variant "primary"
:type "button"
:on-click publish}
(tr "common.publish")])]
[:div {:class (stl/css :section-list-linked)}
[:> title-bar* {:collapsable false
:title (tr "workspace.libraries.connected-libraries")
:class (stl/css :title-spacing-lib)
:title-class (stl/css :connected-libraries-title)}]
(if (seq linked-libraries)
(for [{:keys [id name data connected-to nested? parent-name] :as library} linked-libraries]
(let [disabled? (some #(contains? linked-libraries-ids %) connected-to)
has-tokens? (and (has-tokens? library)
(contains? cf/flags :token-import-from-library))]
[:div {:class (stl/css-case :section-list-item true
:section-list-item-nested nested?)
:key (dm/str id)
:data-testid "library-item"}
[:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-name)} name]
[:ul {:class (stl/css :item-contents)}
(let [summary (get-library-summary data)]
[:> library-description* {:summary summary :hint-name parent-name}])]]
[:div {:class (stl/css :library-actions)}
(when (and (contains? cf/flags :token-lib-sync)
(cfo/effective-tokens-source? local-library id))
[:> text* {:class (stl/css :tokens-source-label)
:as "span"
:typography "body-medium"}
(tr "workspace.libraries.tokens-source")])
(if (contains? cf/flags :token-lib-sync)
(when (and (cfo/tokens-provider? (:data library))
(not (cfo/effective-tokens-source? local-library id)))
[:> button* {:variant "secondary"
:type "button"
:data-library-id (dm/str id)
:on-click set-as-tokens-source}
(tr "workspace.libraries.set-as-tokens-source")])
(when ^boolean has-tokens?
[:> icon-button*
{:type "button"
:aria-label (tr "workspace.tokens.import-tokens")
:icon i/import-export
:data-library-id (dm/str id)
:variant "secondary"
:on-click import-tokens}]))
[:> icon-button* {:type "button"
:aria-label (tr "workspace.libraries.unlink-library-btn")
:icon i/detach
:data-library-id (dm/str id)
:variant "secondary"
:disabled disabled?
:on-click unlink-library}]]]))
[:div {:class (stl/css :shared-libraries-empty)}
[:> text* {:class (stl/css :empty-libraries-text)
:as "span"
:typography "body-small"}
(tr "workspace.libraries.empty.no-connected-libraries")]
[:button {:class (stl/css :go-to-shared-button)
:on-click go-to-shared}
"Add a shared library"]])]]]))
(defn- extract-assets (defn- extract-assets
[file-data library summary?] [file-data library summary?]
(let [exceeded (volatile! {:components false (let [exceeded (volatile! {:components false
@ -483,7 +937,7 @@
(mf/defc updates-tab* (mf/defc updates-tab*
{::mf/private true} {::mf/private true}
[{:keys [file-id libraries]}] [{:keys [file-id libraries is-legacy]}]
;; FIXME: naming ;; FIXME: naming
(let [summary?* (mf/use-state true) (let [summary?* (mf/use-state true)
summary? (deref summary?*) summary? (deref summary?*)
@ -518,14 +972,17 @@
(dwl/set-updating-library true) (dwl/set-updating-library true)
(dwl/sync-file file-id library-id)))))))] (dwl/sync-file file-id library-id)))))))]
[:div {:class (stl/css :updates-content)} [:div {:class (stl/css-case :updates-content (not is-legacy)
:updates-content-legacy is-legacy)}
[:div {:class (stl/css :update-section)} [:div {:class (stl/css :update-section)}
(if (empty? libs-assets) (if (empty? libs-assets)
[:div {:class (stl/css :section-list-empty)} [:div {:class (stl/css :section-list-empty)}
[:> empty-state* {:icon i/library [:> empty-state* {:icon i/library
:text (tr "workspace.libraries.no-libraries-need-sync")}]] :text (tr "workspace.libraries.no-libraries-need-sync")}]]
[:* [:*
[:div {:class (stl/css :section-title)} (tr "workspace.libraries.library-updates")] [:> title-bar* {:collapsable false
:title (tr "workspace.libraries.library-updates")
:class (stl/css :title-spacing-lib)}]
[:div {:class (stl/css :section-list)} [:div {:class (stl/css :section-list)}
(for [[{:keys [id name] :as library} (for [[{:keys [id name] :as library}
@ -534,7 +991,7 @@
[:div {:class (stl/css :section-list-item) [:div {:class (stl/css :section-list-item)
:key (dm/str id)} :key (dm/str id)}
[:div {:class (stl/css :item-content)} [:div {:class (stl/css :item-content)}
[:div {:class (stl/css :item-name-long)} name] [:div {:class (stl/css :item-name)} name]
[:ul {:class (stl/css :item-contents)} (describe-library [:ul {:class (stl/css :item-contents)} (describe-library
(count components) (count components)
0 0
@ -655,8 +1112,10 @@
(fn [_] (fn [_]
(modal/hide!))) (modal/hide!)))
token-lib-sync? (contains? cf/flags :token-lib-sync)
selected-tab* selected-tab*
(mf/use-state #(d/nilv starting-tab "libraries")) (mf/use-state #(d/nilv starting-tab (if token-lib-sync? "file" "libraries")))
selected-tab selected-tab
(deref selected-tab*) (deref selected-tab*)
@ -665,11 +1124,18 @@
(mf/use-fn #(reset! selected-tab* %)) (mf/use-fn #(reset! selected-tab* %))
tabs tabs
(mf/with-memo [] (mf/with-memo [token-lib-sync?]
[{:label (tr "workspace.libraries.libraries") (if token-lib-sync?
:id "libraries"} [{:label "This file"
{:label (tr "workspace.libraries.updates") :id "file"}
:id "updates"}])] {:label (tr "workspace.libraries.libraries")
:id "libraries"}
{:label (tr "workspace.libraries.updates")
:id "updates"}]
[{:label (tr "workspace.libraries.libraries")
:id "libraries"}
{:label (tr "workspace.libraries.updates")
:id "updates"}]))]
(mf/with-effect [] (mf/with-effect []
(st/emit! (dtm/fetch-shared-files))) (st/emit! (dtm/fetch-shared-files)))
@ -690,15 +1156,26 @@
:selected selected-tab :selected selected-tab
:on-change on-change-tab} :on-change on-change-tab}
(case selected-tab (case selected-tab
"file"
(when token-lib-sync?
[:> file-tab* {:is-shared shared?
:on-change-tab on-change-tab
:linked-libraries linked-libraries
:shared-libraries shared-libraries}])
"libraries" "libraries"
[:> libraries-tab* (if token-lib-sync?
{:is-shared shared? [:> libraries-tab*
:linked-libraries linked-libraries {:linked-libraries linked-libraries
:shared-libraries shared-libraries}] :shared-libraries shared-libraries}]
[:> libraries-tab-legacy*
{:is-shared shared?
:linked-libraries linked-libraries
:shared-libraries shared-libraries}])
"updates" "updates"
[:> updates-tab* [:> updates-tab*
{:file-id file-id {:file-id file-id
:is-legacy (not token-lib-sync?)
:libraries linked-libraries}])]]])) :libraries linked-libraries}])]]]))
(mf/defc v2-info-dialog (mf/defc v2-info-dialog

View File

@ -51,6 +51,13 @@
// Tabs content // Tabs content
.libraries-content, .libraries-content,
.updates-content { .updates-content {
display: grid;
gap: var(--sp-xxxl);
max-height: $sz-400;
}
.libraries-content-legacy,
.updates-content-legacy {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: var(--sp-xxxl); gap: var(--sp-xxxl);
@ -64,6 +71,7 @@
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
gap: var(--sp-s); gap: var(--sp-s);
padding-block-start: var(--sp-xs);
} }
.shared-section { .shared-section {
@ -75,6 +83,10 @@
margin: 0 0 0 calc(-1 * var(--sp-s)); margin: 0 0 0 calc(-1 * var(--sp-s));
} }
.connected-libraries-title {
@include t.use-typography("headline-small");
}
.section-list, .section-list,
.section-list-shared { .section-list-shared {
display: grid; display: grid;
@ -86,7 +98,8 @@
%section-list-item-placeholder { %section-list-item-placeholder {
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: var(--sp-s); gap: var(--sp-s);
border: $b-1 solid var(--color-background-quaternary); border: $b-1 solid var(--color-background-quaternary);
padding: var(--sp-s) var(--sp-s) var(--sp-m) var(--sp-m); padding: var(--sp-s) var(--sp-s) var(--sp-m) var(--sp-m);
@ -96,21 +109,33 @@
.section-list-publish { .section-list-publish {
@extend %section-list-item-placeholder; @extend %section-list-item-placeholder;
grid-template-columns: minmax(0, 1fr) auto auto;
border: none; border: none;
} }
.section-list-item-legacy {
@extend %section-list-item-placeholder;
}
.section-list-item { .section-list-item {
@extend %section-list-item-placeholder; @extend %section-list-item-placeholder;
padding: var(--sp-m);
}
.section-list-item-nested {
margin-inline-start: var(--sp-l);
} }
.section-list-item-double-icon { .section-list-item-double-icon {
@extend %section-list-item-placeholder; @extend %section-list-item-placeholder;
grid-template-columns: 1fr auto auto; grid-template-columns: minmax(0, 1fr) auto auto;
} }
.item-content { .item-content {
height: fit-content; height: fit-content;
min-width: 0;
} }
.close-btn { .close-btn {
@ -130,6 +155,13 @@
color: var(--title-foreground-color); color: var(--title-foreground-color);
} }
.section-list-linked {
display: grid;
grid-auto-rows: min-content;
gap: var(--sp-s);
max-height: px2rem(320);
}
// empty state // empty state
.section-list-empty { .section-list-empty {
display: grid; display: grid;
@ -141,6 +173,23 @@
margin-block: var(--sp-l); margin-block: var(--sp-l);
} }
.shared-libraries-empty {
border: $b-1 solid var(--color-background-quaternary);
padding: px2rem(40) var(--sp-s);
border-radius: $br-8;
display: grid;
grid-template-rows: auto 1fr;
justify-items: center;
gap: var(--sp-m);
text-align: center;
height: fit-content;
margin-block: var(--sp-l);
}
.empty-libraries-text {
color: var(--color-foreground-secondary);
}
// Update library tab // Update library tab
.libraries-updates-see-all { .libraries-updates-see-all {
background: unset; background: unset;
@ -155,6 +204,7 @@
margin: 0; margin: 0;
} }
.updates-content-legacy,
.updates-content { .updates-content {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@ -206,7 +256,6 @@
@include text-ellipsis; @include text-ellipsis;
margin: 0; margin: 0;
max-width: px2rem(236);
color: var(--library-name-foreground-color); color: var(--library-name-foreground-color);
} }
@ -214,16 +263,6 @@
@extend %item-name; @extend %item-name;
} }
.item-name-short {
max-width: px2rem(206);
}
.item-name-long {
@extend %item-name;
max-width: px2rem(450);
}
.item-title { .item-title {
@include t.use-typography("body-large"); @include t.use-typography("body-large");
@ -241,7 +280,7 @@
border-radius: $br-8; border-radius: $br-8;
} }
.item-contents { .item-contents-legacy {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
color: var(--library-content-foreground-color); color: var(--library-content-foreground-color);
@ -250,12 +289,22 @@
margin: 0; margin: 0;
} }
.item-contents {
@include t.use-typography("body-small");
color: var(--library-content-foreground-color);
display: flex;
flex-direction: column;
margin: 0;
}
.library-actions { .library-actions {
display: flex; display: flex;
flex-shrink: 0;
gap: var(--sp-xs); gap: var(--sp-xs);
} }
.element-count { .element-count-legacy {
white-space: nowrap; white-space: nowrap;
&:not(:last-child)::after { &:not(:last-child)::after {
@ -264,14 +313,43 @@
} }
} }
.connected-to-wrapper { .element-count-line {
display: flex;
align-items: baseline;
gap: var(--sp-xs);
&::before {
content: "";
}
}
.connected-to-wrapper-legacy {
display: block; display: block;
} }
.connected-to-values { .connected-to-values-legacy {
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.hint-line {
color: var(--color-foreground-secondary);
}
.hint-library-name {
color: var(--color-foreground-primary);
}
.shared-library-added {
display: flex;
justify-content: center;
align-items: center;
color: var(--color-accent-tertiary);
block-size: fit-content;
border: $b-1 solid var(--color-accent-select);
border-radius: $br-8;
padding-inline: var(--sp-xxs) px2rem(6);
}
// Modal Component v2 update // Modal Component v2 update
.modal-v2-info { .modal-v2-info {
width: px2rem(664); width: px2rem(664);
@ -364,6 +442,19 @@
} }
} }
.go-to-shared-button {
@include t.use-typography("body-small");
color: var(--color-accent-primary);
border: none;
background: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.sample-libraries-container { .sample-libraries-container {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
@ -403,3 +494,15 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.tokens-source-label {
block-size: $sz-32;
display: flex;
justify-content: center;
align-items: center;
padding: var(--sp-s) var(--sp-xl);
background: var(--color-background-primary);
border: $b-1 solid var(--color-accent-select);
color: var(--color-accent-tertiary);
border-radius: $br-8;
}

View File

@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.config :as cf] [app.config :as cf]
[app.main.constants :refer [left-sidebar-default-max-width [app.main.constants :refer [left-sidebar-default-max-width
@ -22,7 +23,7 @@
[app.main.features :as features] [app.main.features :as features]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as muc] [app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]] [app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
@ -116,7 +117,7 @@
(mf/defc left-sidebar* (mf/defc left-sidebar*
{::mf/memo true} {::mf/memo true}
[{:keys [layout file tokens-lib active-tokens resolved-active-tokens]}] [{:keys [layout file tokens-lib tokens-status active-tokens resolved-active-tokens]}]
(let [options-mode (mf/deref refs/options-mode-global) (let [options-mode (mf/deref refs/options-mode-global)
project (mf/deref refs/project) project (mf/deref refs/project)
file-id (get file :id) file-id (get file :id)
@ -173,7 +174,7 @@
(mf/with-memo [] (mf/with-memo []
(mf/html [:> collapse-button* {}]))] (mf/html [:> collapse-button* {}]))]
[:> (mf/provider muc/sidebar) {:value :left} [:> (mf/provider ctx/sidebar) {:value :left}
[:aside {:ref parent-ref [:aside {:ref parent-ref
:id "left-sidebar-aside" :id "left-sidebar-aside"
:data-testid "left-sidebar" :data-testid "left-sidebar"
@ -215,9 +216,11 @@
:file-id file-id}] :file-id file-id}]
:tokens :tokens
[:> tokens-sidebar-tab* {:tokens-lib tokens-lib [:> tokens-sidebar-tab*
:active-tokens active-tokens {:tokens-lib tokens-lib
:resolved-active-tokens resolved-active-tokens}] :tokens-status tokens-status
:active-tokens active-tokens
:resolved-active-tokens resolved-active-tokens}]
:layers :layers
[:> layers-content* {:layout layout [:> layers-content* {:layout layout
@ -313,8 +316,8 @@
(mf/with-memo [active-tokens] (mf/with-memo [active-tokens]
(delay (ctob/group-by-type active-tokens)))] (delay (ctob/group-by-type active-tokens)))]
[:> (mf/provider muc/sidebar) {:value :right} [:> (mf/provider ctx/sidebar) {:value :right}
[:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type} [:> (mf/provider ctx/active-tokens-by-type) {:value active-tokens-by-type}
[:aside [:aside
{:class (stl/css-case :right-sidebar true {:class (stl/css-case :right-sidebar true
:not-expand (not can-be-expanded?) :not-expand (not can-be-expanded?)
@ -358,19 +361,22 @@
(let [tokens-lib (let [tokens-lib
(mf/deref refs/tokens-lib) (mf/deref refs/tokens-lib)
tokens-status
(mf/deref refs/tokens-status)
active-tokens active-tokens
(mf/with-memo [tokens-lib] (mf/with-memo [tokens-status tokens-lib]
(if tokens-lib (if (and tokens-status tokens-lib)
(ctob/get-tokens-in-active-sets tokens-lib) (cfo/get-tokens-in-active-sets tokens-status tokens-lib)
{})) {}))
selected-token-set-id selected-token-set-id
(mf/deref refs/selected-token-set-id) (mf/deref refs/selected-token-set-id)
active-tokens-force-set active-tokens-force-set
(mf/with-memo [tokens-lib selected-token-set-id] (mf/with-memo [tokens-status tokens-lib selected-token-set-id]
(if (and tokens-lib selected-token-set-id) (if (and tokens-status tokens-lib selected-token-set-id)
(ctob/get-tokens-in-active-sets-force tokens-lib selected-token-set-id) (cfo/get-tokens-in-active-sets-force tokens-status tokens-lib selected-token-set-id)
{})) {}))
tokenscript? (contains? cf/flags :tokenscript) tokenscript? (contains? cf/flags :tokenscript)
@ -389,25 +395,27 @@
resolved-active-tokens-force-set resolved-active-tokens-force-set
(sd/use-resolved-tokens* active-tokens-force-set)] (sd/use-resolved-tokens* active-tokens-force-set)]
[:* [:> (mf/provider ctx/tokens-lib) {:value tokens-lib}
(if (:collapse-left-sidebar layout) [:> (mf/provider ctx/tokens-status) {:value tokens-status}
[:> collapsed-button*] (if (:collapse-left-sidebar layout)
[:> left-sidebar* {:layout layout [:> collapsed-button*]
[:> left-sidebar* {:layout layout
:file file
:page-id page-id
:tokens-lib tokens-lib
:tokens-status tokens-status
:active-tokens active-tokens-force-set
:resolved-active-tokens (if tokenscript?
tokenscript-resolved-active-tokens-force-set
resolved-active-tokens-force-set)}])
[:> right-sidebar* {:section section
:selected selected
:drawing-tool drawing-tool
:layout layout
:file file :file file
:file-id file-id
:page-id page-id :page-id page-id
:tokens-lib tokens-lib :tokens-lib tokens-lib
:active-tokens active-tokens-force-set :active-tokens (if tokenscript?
:resolved-active-tokens (if tokenscript? tokenscript-resolved-active-tokens
tokenscript-resolved-active-tokens-force-set resolved-active-tokens)}]]]))
resolved-active-tokens-force-set)}])
[:> right-sidebar* {:section section
:selected selected
:drawing-tool drawing-tool
:layout layout
:file file
:file-id file-id
:page-id page-id
:tokens-lib tokens-lib
:active-tokens (if tokenscript?
tokenscript-resolved-active-tokens
resolved-active-tokens)}]]))

View File

@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
@ -33,6 +34,12 @@
[{:keys [filters]}] [{:keys [filters]}]
(let [file-id (mf/use-ctx ctx/current-file-id) (let [file-id (mf/use-ctx ctx/current-file-id)
files (mf/deref refs/files) files (mf/deref refs/files)
current-file-data
(mf/deref refs/workspace-data)
tokens-source
(mf/with-memo [current-file-data]
(cfo/get-effective-tokens-source current-file-data))
libraries (mf/with-memo [files file-id] libraries (mf/with-memo [files file-id]
(->> (refs/select-libraries files file-id) (->> (refs/select-libraries files file-id)
(vals) (vals)
@ -46,6 +53,7 @@
{:key (dm/str (:id file)) {:key (dm/str (:id file))
:file file :file file
:is-local false :is-local false
:is-tokens-source (= (:id file) tokens-source)
:is-default-open false :is-default-open false
:filters filters}]))) :filters filters}])))
@ -57,11 +65,16 @@
(mf/defc assets-local-library* (mf/defc assets-local-library*
{::mf/private true} {::mf/private true}
[{:keys [filters]}] [{:keys [filters]}]
(let [file (mf/deref ref:local-library)] (let [file (mf/deref ref:local-library)
is-tokens-source
(mf/with-memo [file]
(cfo/effective-tokens-source? (:data file) (:id file)))]
[:> file-library* [:> file-library*
{:file file {:file file
:is-local true :is-local true
:is-default-open true :is-default-open true
:is-tokens-source is-tokens-source
:filters filters}])) :filters filters}]))
(defn- toggle-values (defn- toggle-values

View File

@ -22,7 +22,8 @@
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar*]] [app.main.ui.components.title-bar :refer [title-bar*]]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.icons :as deprecated-icon] [app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[app.main.ui.ds.tooltip :refer [tooltip*]]
[app.main.ui.workspace.sidebar.assets.colors :refer [colors-section*]] [app.main.ui.workspace.sidebar.assets.colors :refer [colors-section*]]
[app.main.ui.workspace.sidebar.assets.common :as cmm] [app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.sidebar.assets.components :refer [components-section*]] [app.main.ui.workspace.sidebar.assets.components :refer [components-section*]]
@ -76,7 +77,7 @@
(mf/defc file-library-title* (mf/defc file-library-title*
{::mf/private true} {::mf/private true}
[{:keys [is-open is-local file-id page-id file-name]}] [{:keys [is-open is-local file-id page-id file-name is-tokens-source]}]
(let [router (mf/deref refs/router) (let [router (mf/deref refs/router)
team-id (mf/use-ctx ctx/current-team-id) team-id (mf/use-ctx ctx/current-team-id)
url (rt/resolve-uri router :workspace url (rt/resolve-uri router :workspace
@ -93,7 +94,15 @@
(mf/use-fn (mf/use-fn
(fn [ev] (fn [ev]
(dom/stop-propagation ev) (dom/stop-propagation ev)
(st/emit! (ev/event {::ev/name "navigate-to-library-file"}))))] (st/emit! (ev/event {::ev/name "navigate-to-library-file"}))))
tokens-source-icon
(when is-tokens-source
(mf/html [:> tooltip* {:content (tr "workspace.tokens.current-tokens-source-tooltip")}
[:span {:class (stl/css :tokens-source-icon-wrapper)}
[:> icon* {:icon-id i/tokens
:size "m"
:class (stl/css :tokens-source-icon)}]]]))]
[:div {:class (stl/css-case [:div {:class (stl/css-case
:library-title true :library-title true
@ -103,17 +112,24 @@
:on-collapsed toggle-open :on-collapsed toggle-open
:title (if is-local :title (if is-local
(mf/html [:div {:class (stl/css :special-title)} (mf/html [:div {:class (stl/css :special-title)}
(tr "workspace.assets.local-library")]) [:span {:class (stl/css :special-title-text)}
(tr "workspace.assets.local-library")]
tokens-source-icon])
;; Do we need to add shared info here? ;; Do we need to add shared info here?
(mf/html [:div {:class (stl/css :special-title)} (mf/html [:div {:class (stl/css :special-title)}
file-name]))} [:span {:class (stl/css :special-title-text)}
file-name]
tokens-source-icon]))}
(when-not ^boolean is-local (when-not ^boolean is-local
[:span {:title (tr "workspace.assets.open-library")} [:span {:title (tr "workspace.assets.open-library")}
[:a {:class (stl/css :file-link) [:a {:class (stl/css :file-link)
:href url :href url
:target "_blank" :target "_blank"
:on-click on-click} :on-click on-click}
deprecated-icon/open-link]])]])) [:> icon* {:icon-id i/open-link
:size "m"
:class (stl/css :file-link-icon)}]]])]]))
(defn- extend-selected (defn- extend-selected
[selected type asset-groups asset-id file-id] [selected type asset-groups asset-id file-id]
@ -299,13 +315,15 @@
(not ^boolean show-colors?) (not ^boolean show-colors?)
(not ^boolean show-typography?)) (not ^boolean show-typography?))
[:div {:class (stl/css :asset-title)} [:div {:class (stl/css :asset-title)}
[:span {:class (stl/css :no-found-icon)} [:span {:class (stl/css :no-found-icon-wrapper)}
deprecated-icon/search] [:> icon* {:icon-id i/search
:size "l"
:class (stl/css :no-found-icon)}]]
[:span {:class (stl/css :no-found-text)} [:span {:class (stl/css :no-found-text)}
(tr "workspace.assets.not-found")]])])])) (tr "workspace.assets.not-found")]])])]))
(mf/defc file-library* (mf/defc file-library*
[{:keys [file is-local is-default-open filters]}] [{:keys [file is-local is-default-open filters is-tokens-source]}]
(let [file-id (:id file) (let [file-id (:id file)
file-name (:name file) file-name (:name file)
page-id (dm/get-in file [:data :pages 0]) page-id (dm/get-in file [:data :pages 0])
@ -381,7 +399,8 @@
:page-id page-id :page-id page-id
:file-name file-name :file-name file-name
:is-open open? :is-open open?
:is-local is-local}] :is-local is-local
:is-tokens-source is-tokens-source}]
(when ^boolean open? (when ^boolean open?
[:> file-library-content* [:> file-library-content*

View File

@ -4,95 +4,131 @@
// //
// Copyright (c) KALEIDOS SUBSIDIARY SL // Copyright (c) KALEIDOS SUBSIDIARY SL
@use "ds/typography.scss" as t; @use "ds/_borders.scss" as *;
@use "refactor/common-refactor.scss" as deprecated; @use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/typography.scss" as *;
.tool-window { .tool-window {
padding: 0 0 deprecated.$s-24 deprecated.$s-12; padding-inline-start: var(--sp-m);
padding-block-end: var(--sp-xxl);
display: grid; display: grid;
grid-auto-rows: max-content; grid-auto-rows: max-content;
gap: deprecated.$s-4; gap: var(--sp-xs);
height: 100%; block-size: 100%;
}
.file-name {
@include t.use-typography("body-small");
display: flex;
justify-content: flex-start;
align-items: center;
flex-grow: 100;
height: 100%;
} }
.loading { .loading {
@include t.use-typography("body-small"); @include use-typography("body-small");
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
height: deprecated.$s-32; block-size: $sz-32;
padding-left: calc(deprecated.$s-12 + deprecated.$s-2); padding-inline-start: calc(var(--sp-m) + var(--sp-xxs));
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
} }
.special-title { .special-title {
@include deprecated.text-ellipsis; box-sizing: border-box;
inline-size: 100%;
color: var(--title-foreground-color-hover); min-inline-size: 0;
margin-left: deprecated.$s-2; display: flex;
align-items: center;
gap: var(--sp-xs);
color: var(--color-foreground-primary);
padding-inline-start: var(--sp-xxs);
text-align: left; text-align: left;
} }
.special-title-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tokens-source-icon-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: $b-1 solid var(--color-accent-select);
border-radius: $br-4;
padding: var(--sp-xxs);
}
.tokens-source-icon {
flex-shrink: 0;
color: var(--color-accent-tertiary);
}
.file-link { .file-link {
@extend %button-tertiary; --file-link-background-color: transparent;
--file-link-border-color: transparent;
--file-link-foreground-color: var(--color-foreground-secondary);
height: deprecated.$s-32; display: grid;
width: deprecated.$s-28; place-content: center;
border-radius: deprecated.$br-8; block-size: $sz-32;
inline-size: $sz-28;
border: $b-2 solid var(--file-link-border-color);
border-radius: $br-8;
background-color: var(--file-link-background-color);
color: var(--file-link-foreground-color);
cursor: pointer;
svg { &:hover {
@extend %button-icon; --file-link-background-color: var(--color-background-quaternary);
--file-link-border-color: var(--color-background-quaternary);
--file-link-foreground-color: var(--color-accent-primary);
}
stroke: var(--icon-foreground); &:active {
fill: var(--title-foreground-color-hover); --file-link-background-color: var(--color-background-secondary);
--file-link-border-color: transparent;
--file-link-foreground-color: var(--color-accent-primary);
outline: none;
}
&:focus-visible {
--file-link-background-color: var(--color-background-tertiary);
--file-link-foreground-color: var(--color-foreground-primary);
outline: none;
border: $b-1 solid var(--color-accent-primary);
} }
} }
.library-content { .library-content {
width: 100%; inline-size: 100%;
display: grid; display: grid;
grid-auto-rows: max-content; grid-auto-rows: max-content;
gap: deprecated.$s-4; gap: var(--sp-xs);
} }
.asset-title { .asset-title {
margin-left: deprecated.$s-28; margin-inline-start: px2rem(28);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: deprecated.$s-8; gap: var(--sp-s);
} }
.no-found-icon { .no-found-icon-wrapper {
@include deprecated.flex-center; display: flex;
align-items: center;
background-color: var(--not-found-background-color); justify-content: center;
border-radius: deprecated.$br-circle; background-color: var(--color-background-tertiary);
height: deprecated.$s-48; border-radius: $br-circle;
width: deprecated.$s-48; block-size: $sz-48;
inline-size: $sz-48;
svg { color: var(--color-foreground-secondary);
@extend %button-icon;
height: deprecated.$s-24;
width: deprecated.$s-24;
stroke: var(--not-found-foreground-color);
}
} }
.no-found-text { .no-found-text {
@include deprecated.body-small-typography; @include use-typography("body-small");
color: var(--not-found-foreground-color); color: var(--color-foreground-secondary);
} }

View File

@ -15,6 +15,7 @@
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.texts-events :as dwte]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -170,7 +171,7 @@
(mf/deps file-id prefix) (mf/deps file-id prefix)
(fn [_] (fn [_]
(st/emit! (dw/set-assets-section-open file-id :typographies true) (st/emit! (dw/set-assets-section-open file-id :typographies true)
(dwt/add-typography file-id prefix))))] (dwte/add-typography file-id prefix))))]
[:div {:class (stl/css :typographies-group) [:div {:class (stl/css :typographies-group)
:on-drag-enter on-drag-enter :on-drag-enter on-drag-enter
@ -278,7 +279,7 @@
(mf/deps file-id) (mf/deps file-id)
(fn [_] (fn [_]
(st/emit! (dw/set-assets-section-open file-id :typographies true)) (st/emit! (dw/set-assets-section-open file-id :typographies true))
(st/emit! (dwt/add-typography file-id)))) (st/emit! (dwte/add-typography file-id))))
handle-change handle-change
(mf/use-fn (mf/use-fn

View File

@ -5,6 +5,7 @@
[app.common.path-names :as cpn] [app.common.path-names :as cpn]
[app.common.types.shape.layout :as ctsl] [app.common.types.shape.layout :as ctsl]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.main.data.helpers :as dh] [app.main.data.helpers :as dh]
@ -49,21 +50,18 @@
(mf/defc selected-set-info* (mf/defc selected-set-info*
{::mf/private true} {::mf/private true}
[{:keys [tokens-lib selected-token-set-id]}] [{:keys [tokens-lib tokens-status selected-token-set-id]}]
(let [selected-token-set (let [selected-token-set
(mf/with-memo [tokens-lib selected-token-set-id] (mf/with-memo [tokens-lib selected-token-set-id]
(when selected-token-set-id (when selected-token-set-id
(some-> tokens-lib (ctob/get-set selected-token-set-id)))) (some-> tokens-lib (ctob/get-set selected-token-set-id))))
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-status)
(fn [name] (fn [id]
(contains? active-token-sets-names name)))] (ctos/set-active? tokens-status id)))]
[:div {:class (stl/css :sets-header-container)} [:div {:class (stl/css :sets-header-container)}
[:> text* {:as "span" [:> text* {:as "span"
:typography "headline-small" :typography "headline-small"
@ -71,7 +69,7 @@
:data-testid "active-token-set-title"} :data-testid "active-token-set-title"}
(tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))] (tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
(when (and (some? selected-token-set-id) (when (and (some? selected-token-set-id)
(not (token-set-active? (ctob/get-name selected-token-set)))) (not (token-set-active? selected-token-set-id)))
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")} [:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
;; NOTE: when no set in tokens-lib, the selected-token-set-id ;; NOTE: when no set in tokens-lib, the selected-token-set-id
;; will be `nil`, so for properly hide the inactive message we ;; will be `nil`, so for properly hide the inactive message we
@ -84,7 +82,7 @@
(mf/defc tokens-section* (mf/defc tokens-section*
{::mf/private true} {::mf/private true}
[{:keys [tokens-lib active-tokens resolved-active-tokens]}] [{:keys [tokens-lib tokens-status active-tokens resolved-active-tokens]}]
(let [objects (mf/deref refs/workspace-page-objects) (let [objects (mf/deref refs/workspace-page-objects)
selected (mf/deref refs/selected-shapes) selected (mf/deref refs/selected-shapes)
@ -305,6 +303,7 @@
:on-delete-node delete-node}] :on-delete-node delete-node}]
[:> selected-set-info* {:tokens-lib tokens-lib [:> selected-set-info* {:tokens-lib tokens-lib
:tokens-status tokens-status
:selected-token-set-id selected-token-set-id}] :selected-token-set-id selected-token-set-id}]
(for [type filled-group] (for [type filled-group]

View File

@ -19,6 +19,7 @@
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [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.assets.icon :refer [icon*] :as i]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.util.clipboard :as clipboard] [app.util.clipboard :as clipboard]
@ -346,31 +347,39 @@
(defn default-actions (defn default-actions
[{:keys [token selected-token-set-id on-delete-token errors]}] [{:keys [token selected-token-set-id on-delete-token errors]}]
(let [{:keys [modal]} (dwta/get-token-properties token) (let [{:keys [modal]} (dwta/get-token-properties token)
can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
on-copy-name #(clipboard/to-clipboard (:name token)) on-copy-name #(clipboard/to-clipboard (:name token))
on-duplicate-token #(st/emit! (dwtl/duplicate-token (:id token)))] on-duplicate-token #(st/emit! (dwtl/duplicate-token (:id token)))]
[{:title (tr "workspace.tokens.edit") (concat
:no-selectable true []
:action (fn [event] (when can-edit-tokens?
(let [{:keys [key fields]} modal] [{:title (tr "workspace.tokens.edit")
(dom/stop-propagation event) :no-selectable true
(st/emit! (dwtl/assign-token-context-menu nil) :action (fn [event]
(modal/show key {:x (.-clientX ^js event) (let [{:keys [key fields]} modal]
:y (.-clientY ^js event) (dom/stop-propagation event)
:position :right (st/emit! (dwtl/assign-token-context-menu nil)
:fields fields (modal/show key {:x (.-clientX ^js event)
:initial-errors errors :y (.-clientY ^js event)
:action "edit" :position :right
:selected-token-set-id selected-token-set-id :fields fields
:token token}))))} :initial-errors errors
{:title (tr "workspace.tokens.duplicate") :action "edit"
:no-selectable true :selected-token-set-id selected-token-set-id
:action on-duplicate-token} :token token}))))}
{:title (tr "workspace.tokens.copy-name") {:title (tr "workspace.tokens.duplicate")
:no-selectable true :no-selectable true
:action on-copy-name} :action on-duplicate-token}])
{:title (tr "workspace.tokens.delete") [{:title (tr "workspace.tokens.copy-name")
:no-selectable true :no-selectable true
:action #(on-delete-token token)}])) :action on-copy-name}]
(when can-edit-tokens?
[{:title (tr "workspace.tokens.delete")
:no-selectable true
:action #(on-delete-token token)}]))))
(defn- allowed-shape-attributes (defn- allowed-shape-attributes
[shapes] [shapes]

View File

@ -92,8 +92,8 @@
(not (and (some? edition) (not (and (some? edition)
(= :text (:type (get objects edition)))))) (= :text (:type (get objects edition))))))
can-edit? can-edit-tokens?
(mf/use-ctx ctx/can-edit?) (mf/use-ctx ctx/can-edit-tokens?)
is-selected-inside-layout (d/nilv is-selected-inside-layout false) is-selected-inside-layout (d/nilv is-selected-inside-layout false)
@ -177,7 +177,7 @@
:aria-controls (dm/str "token-tree-" (name type)) :aria-controls (dm/str "token-tree-" (name type))
:on-toggle-expand on-toggle-open-click :on-toggle-expand on-toggle-open-click
:icon (token-section-icon type)} :icon (token-section-icon type)}
(when can-edit? (when can-edit-tokens?
[:> icon-button* {:id (str "add-token-button-" title) [:> icon-button* {:id (str "add-token-button-" title)
:icon "add" :icon "add"
:aria-label (tr "workspace.tokens.add-token" title) :aria-label (tr "workspace.tokens.add-token" title)

View File

@ -140,14 +140,17 @@
on-pill-context-menu on-pill-context-menu
on-node-context-menu]}] on-node-context-menu]}]
(let [separator "." (let [separator "."
raw-tree (mf/with-memo [tokens] raw-tree
(cpn/build-tree-root tokens separator)) (mf/with-memo [tokens]
permissions (mf/use-ctx ctx/permissions) (cpn/build-tree-root tokens separator))
can-edit? (:can-edit permissions)
can-edit-file?
(mf/use-ctx ctx/can-edit?)
on-node-context-menu (mf/use-fn on-node-context-menu (mf/use-fn
(mf/deps can-edit? on-node-context-menu) (mf/deps can-edit-file? on-node-context-menu)
(fn [event node] (fn [event node]
(when can-edit? (when can-edit-file?
(on-node-context-menu event node)))) (on-node-context-menu event node))))
ordered-nodes (mf/with-memo [raw-tree] ordered-nodes (mf/with-memo [raw-tree]

View File

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.sets (ns app.main.ui.workspace.tokens.sets
(:require (:require
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
@ -19,27 +20,32 @@
(st/emit! (dwtl/clear-tokens-paths) (st/emit! (dwtl/clear-tokens-paths)
(dwtl/set-selected-token-set-id id))) (dwtl/set-selected-token-set-id id)))
(defn- on-toggle-token-set-click [name] (defn- on-toggle-token-set-click [id]
(st/emit! (dwtl/toggle-token-set name))) (st/emit! (dwtl/toggle-token-set id)))
(defn- on-toggle-token-set-group-click [path] (defn- on-toggle-token-set-group-click [path]
(st/emit! (dwtl/toggle-token-set-group path))) (st/emit! (dwtl/toggle-token-set-group path)))
(mf/defc sets-list* (mf/defc sets-list*
[{:keys [tokens-lib selected new-path edition-id]}] [{:keys [selected new-path edition-id]}]
(let [token-sets (let [tokens-lib
(mf/use-ctx ctx/tokens-lib)
tokens-status
(mf/use-ctx ctx/tokens-status)
token-sets
(some-> tokens-lib (ctob/get-set-tree)) (some-> tokens-lib (ctob/get-set-tree))
can-edit? can-edit-tokens?
(mf/use-ctx ctx/can-edit?) (mf/use-ctx ctx/can-edit-tokens?)
token-set-active? token-set-active?
(mf/use-fn (mf/use-fn
(mf/deps tokens-lib) (mf/deps tokens-status)
(fn [name] (fn [set-id]
(when tokens-lib (ctos/set-active? tokens-status set-id)))
(ctob/token-set-active? tokens-lib name))))
token-set-group-active? token-set-group-active?
(mf/use-fn (mf/use-fn
@ -49,17 +55,17 @@
on-reset-edition on-reset-edition
(mf/use-fn (mf/use-fn
(mf/deps can-edit?) (mf/deps can-edit-tokens?)
(fn [_] (fn [_]
(when can-edit? (when can-edit-tokens?
(st/emit! (dwtl/clear-token-set-edition) (st/emit! (dwtl/clear-token-set-edition)
(dwtl/clear-token-set-creation))))) (dwtl/clear-token-set-creation)))))
on-start-edition on-start-edition
(mf/use-fn (mf/use-fn
(mf/deps can-edit?) (mf/deps can-edit-tokens?)
(fn [id] (fn [id]
(when can-edit? (when can-edit-tokens?
(st/emit! (dwtl/start-token-set-edition id)))))] (st/emit! (dwtl/start-token-set-edition id)))))]
[:> controlled-sets-list* [:> controlled-sets-list*
@ -75,7 +81,6 @@
:edition-id edition-id :edition-id edition-id
:origin "set-panel" :origin "set-panel"
:can-edit can-edit?
:on-start-edition on-start-edition :on-start-edition on-start-edition
:on-reset-edition on-reset-edition :on-reset-edition on-reset-edition

View File

@ -86,8 +86,8 @@
(mf/defc inline-add-button* (mf/defc inline-add-button*
[] []
(let [can-edit? (mf/use-ctx ctx/can-edit?)] (let [can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?)]
(if can-edit? (if can-edit-tokens?
[:div {:class (stl/css :empty-sets-wrapper)} [:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.tokens.no-sets-yet")] (tr "workspace.tokens.no-sets-yet")]
@ -110,7 +110,10 @@
[{:keys [id label is-editing is-active is-selected is-draggable is-collapsed path depth index [{:keys [id label is-editing is-active is-selected is-draggable is-collapsed path depth index
on-toggle on-drop on-start-edition on-reset-edition on-edit-submit on-toggle-collapse]}] on-toggle on-drop on-start-edition on-reset-edition on-edit-submit on-toggle-collapse]}]
(let [can-edit? (let [can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
can-edit-file?
(mf/use-ctx ctx/can-edit?) (mf/use-ctx ctx/can-edit?)
label-id label-id
@ -118,11 +121,11 @@
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps is-editing id path can-edit?) (mf/deps is-editing id path can-edit-tokens?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when (and can-edit? (not is-editing)) (when (and can-edit-tokens? (not is-editing))
(st/emit! (dwtl/assign-token-set-context-menu (st/emit! (dwtl/assign-token-set-context-menu
{:position (dom/get-client-position event) {:position (dom/get-client-position event)
:is-group true :is-group true
@ -141,12 +144,12 @@
on-checkbox-click on-checkbox-click
(mf/use-fn (mf/use-fn
(mf/deps on-toggle path can-edit?) (mf/deps on-toggle path can-edit-file?)
#(on-toggle path)) #(on-toggle path))
on-edit-submit' on-edit-submit'
(mf/use-fn (mf/use-fn
(mf/deps path on-edit-submit can-edit?) (mf/deps path on-edit-submit can-edit-tokens?)
#(on-edit-submit path %)) #(on-edit-submit path %))
on-drop on-drop
@ -196,7 +199,7 @@
label] label]
[:> checkbox* [:> checkbox*
{:on-click on-checkbox-click {:on-click on-checkbox-click
:disabled (not can-edit?) :disabled (not can-edit-file?)
:checked (case is-active :checked (case is-active
:all true :all true
:partial "mixed" :partial "mixed"
@ -207,7 +210,11 @@
[{:keys [id set label is-editing is-active is-selected is-draggable is-new path depth index [{:keys [id set label is-editing is-active is-selected is-draggable is-new path depth index
on-select on-toggle on-drop on-start-edition on-reset-edition on-edit-submit]}] on-select on-toggle on-drop on-start-edition on-reset-edition on-edit-submit]}]
(let [can-edit? (mf/use-ctx ctx/can-edit?) (let [can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
can-edit-file?
(mf/use-ctx ctx/can-edit?)
on-click on-click
(mf/use-fn (mf/use-fn
@ -220,11 +227,11 @@
on-context-menu on-context-menu
(mf/use-fn (mf/use-fn
(mf/deps is-editing id path can-edit?) (mf/deps is-editing id path can-edit-tokens?)
(fn [event] (fn [event]
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(when (and can-edit? (not is-editing)) (when (and can-edit-tokens? (not is-editing))
(st/emit! (dwtl/assign-token-set-context-menu (st/emit! (dwtl/assign-token-set-context-menu
{:position (dom/get-client-position event) {:position (dom/get-client-position event)
:is-group false :is-group false
@ -244,7 +251,7 @@
(fn [event] (fn [event]
(dom/stop-propagation event) (dom/stop-propagation event)
(when (fn? on-toggle) (when (fn? on-toggle)
(on-toggle (ctob/get-name set))))) (on-toggle (ctob/get-id set)))))
on-edit-submit' on-edit-submit'
(mf/use-fn (mf/use-fn
@ -305,15 +312,15 @@
label] label]
[:> checkbox* [:> checkbox*
{:on-click on-checkbox-click {:on-click on-checkbox-click
:disabled (not can-edit?) :disabled (not can-edit-file?)
:arial-label (tr "workspace.tokens.select-set") :arial-label (tr "workspace.tokens.select-set")
:checked is-active}]])])) :checked is-active}]])]))
(mf/defc token-sets-tree* (mf/defc token-sets-tree*
[{:keys [is-draggable [{:keys [is-draggable
selected selected
is-token-set-group-active
is-token-set-active is-token-set-active
is-token-set-group-active
on-start-edition on-start-edition
on-reset-edition on-reset-edition
on-edit-submit-set on-edit-submit-set
@ -418,7 +425,7 @@
:set token-set :set token-set
:label (peek path) :label (peek path)
:is-editing (= edition-id id) :is-editing (= edition-id id)
:is-active (is-token-set-active (ctob/get-name token-set)) :is-active (is-token-set-active id)
:is-selected (= selected id) :is-selected (= selected id)
:is-draggable is-draggable :is-draggable is-draggable
:is-new false :is-new false
@ -452,15 +459,15 @@
new-path new-path
edition-id]}] edition-id]}]
(assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop")
(assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop") (assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop")
(assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop")
(let [theme-modal? (= origin "theme-modal") (let [theme-modal? (= origin "theme-modal")
can-edit? (mf/use-ctx ctx/can-edit?) can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?)
draggable? (and (not theme-modal?) can-edit?) draggable? (and (not theme-modal?) can-edit-tokens?)
empty-state? (and theme-modal? empty-state? (and theme-modal?
(empty? token-sets) (empty? token-sets)
(not new-path)) (not new-path))
;; NOTE: on-reset-edition and on-start-edition function can ;; NOTE: on-reset-edition and on-start-edition function can
;; come as nil, in this case we need to provide a safe ;; come as nil, in this case we need to provide a safe

View File

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.sidebar (ns app.main.ui.workspace.tokens.sidebar
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.config :as cf] [app.config :as cf]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
@ -25,6 +26,7 @@
[app.main.ui.workspace.tokens.sets.context-menu :refer [token-set-context-menu*]] [app.main.ui.workspace.tokens.sets.context-menu :refer [token-set-context-menu*]]
[app.main.ui.workspace.tokens.sets.lists :as tsetslist] [app.main.ui.workspace.tokens.sets.lists :as tsetslist]
[app.main.ui.workspace.tokens.themes :refer [themes-header*]] [app.main.ui.workspace.tokens.themes :refer [themes-header*]]
[app.main.ui.workspace.tokens.tokens-source :refer [tokens-source-info*]]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[rumext.v2 :as mf] [rumext.v2 :as mf]
@ -34,8 +36,11 @@
(mf/defc token-sets-list* (mf/defc token-sets-list*
{::mf/private true} {::mf/private true}
[{:keys [tokens-lib]}] []
(let [token-sets (let [tokens-lib
(mf/use-ctx ctx/tokens-lib)
token-sets
(some-> tokens-lib (ctob/get-set-tree)) (some-> tokens-lib (ctob/get-set-tree))
selected-token-set-id selected-token-set-id
@ -60,20 +65,25 @@
(mf/defc token-management-section* (mf/defc token-management-section*
{::mf/private true} {::mf/private true}
[{:keys [resize-height] :as props}] [{:keys [resize-height current-file-data] :as props}]
(let [can-edit? (let [can-edit-tokens?
(mf/use-ctx ctx/can-edit?)] (mf/use-ctx ctx/can-edit-tokens?)
tokens-source
(mf/with-memo [current-file-data]
(cfo/get-effective-tokens-source current-file-data))]
[:* [:*
[:> token-set-context-menu*] [:> token-set-context-menu*]
[:section {:data-testid "token-management-sidebar" [:section {:data-testid "token-management-sidebar"
:class (stl/css :token-management-section-wrapper) :class (stl/css :token-management-section-wrapper)
:style {"--resize-height" (str resize-height "px")}} :style {"--resize-height" (str resize-height "px")}}
[:> themes-header*] [:> tokens-source-info* {:tokens-source tokens-source
:file-id (:id current-file-data)}]
[:> themes-header* {:tokens-source tokens-source}]
[:div {:class (stl/css :sidebar-header)} [:div {:class (stl/css :sidebar-header)}
[:> title-bar* {:title (tr "labels.sets")} [:> title-bar* {:title (tr "labels.sets")}
(when can-edit? (when can-edit-tokens?
[:> tsetslist/add-button*])]] [:> tsetslist/add-button*])]]
[:> token-sets-list* props]]])) [:> token-sets-list* props]]]))
@ -83,8 +93,8 @@
(let [show-menu* (mf/use-state false) (let [show-menu* (mf/use-state false)
show-menu? (deref show-menu*) show-menu? (deref show-menu*)
can-edit? can-edit-tokens?
(mf/use-ctx ctx/can-edit?) (mf/use-ctx ctx/can-edit-tokens?)
open-menu open-menu
(mf/use-fn (mf/use-fn
@ -124,7 +134,7 @@
:on-close close-menu :on-close close-menu
:id "tokens-menu" :id "tokens-menu"
:class (stl/css :import-export-menu)} :class (stl/css :import-export-menu)}
(when can-edit? (when can-edit-tokens?
[:> dropdown-menu-item* {:class (stl/css :import-export-menu-item) [:> dropdown-menu-item* {:class (stl/css :import-export-menu-item)
:on-click on-modal-show} :on-click on-modal-show}
[:div {:class (stl/css :import-menu-item)} [:div {:class (stl/css :import-menu-item)}
@ -133,31 +143,42 @@
:on-click on-export} :on-click on-export}
(tr "labels.export")]] (tr "labels.export")]]
(when (and can-edit-tokens? (contains? cf/flags :token-base-font-size))
(when (and can-edit? (contains? cf/flags :token-base-font-size))
[:> icon-button* {:variant "secondary" [:> icon-button* {:variant "secondary"
:icon i/settings :icon i/settings
:aria-label "Settings" :aria-label "Settings"
:on-click open-settings-modal}])])) :on-click open-settings-modal}])]))
(mf/defc tokens-sidebar-tab* (mf/defc tokens-sidebar-tab*
[{:keys [tokens-lib] :as props}] [{:keys [] :as props}]
(let [{on-pointer-down-pages :on-pointer-down (let [{on-pointer-down-pages :on-pointer-down
on-lost-pointer-capture-pages :on-lost-pointer-capture on-lost-pointer-capture-pages :on-lost-pointer-capture
on-pointer-move-pages :on-pointer-move on-pointer-move-pages :on-pointer-move
size-pages-opened :size} size-pages-opened :size}
(use-resize-hook :tokens 200 38 "0.6" :y false nil)] (use-resize-hook :tokens 200 38 "0.6" :y false nil)
[:div {:class (stl/css :sidebar-wrapper)} current-file-data
[:> token-management-section* (mf/deref refs/workspace-data)
{:resize-height size-pages-opened
:tokens-lib tokens-lib}] can-edit-file?
[:article {:class (stl/css :tokens-section-wrapper) (mf/use-ctx ctx/can-edit?)
:data-testid "tokens-sidebar"}
[:div {:class (stl/css :resize-area-horiz) can-edit-tokens?
:on-pointer-down on-pointer-down-pages (mf/with-memo [can-edit-file? current-file-data]
:on-lost-pointer-capture on-lost-pointer-capture-pages (and can-edit-file?
:on-pointer-move on-pointer-move-pages} (cfo/editable-tokens? current-file-data)))]
[:div {:class (stl/css :resize-handle-horiz)}]]
[:> tokens-section* props]] [:> (mf/provider ctx/can-edit-tokens?) {:value can-edit-tokens?}
[:> import-export-button*]])) [:div {:class (stl/css :sidebar-wrapper)}
[:> token-management-section*
{:resize-height size-pages-opened
:current-file-data current-file-data}]
[:article {:class (stl/css :tokens-section-wrapper)
:data-testid "tokens-sidebar"}
[:div {:class (stl/css :resize-area-horiz)
:on-pointer-down on-pointer-down-pages
:on-lost-pointer-capture on-lost-pointer-capture-pages
:on-pointer-move on-pointer-move-pages}
[:div {:class (stl/css :resize-handle-horiz)}]]
[:> tokens-section* props]]
[:> import-export-button*]]]))

View File

@ -26,7 +26,7 @@
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
position: relative; position: relative;
padding-block-end: var(--sp-l); padding-block: var(--sp-m) var(--sp-l);
} }
.tokens-section-wrapper { .tokens-section-wrapper {

View File

@ -19,12 +19,12 @@
(mf/defc themes-header* (mf/defc themes-header*
{::mf/private true} {::mf/private true}
[] [{:keys [tokens-source]}]
(let [ordered-themes (let [ordered-themes
(mf/deref refs/workspace-token-themes-no-hidden) (mf/deref refs/workspace-token-themes-no-hidden)
can-edit? can-edit-tokens?
(mf/use-ctx ctx/can-edit?) (mf/use-ctx ctx/can-edit-tokens?)
open-modal open-modal
(mf/use-fn (mf/use-fn
@ -38,18 +38,18 @@
[:div {:class (stl/css :empty-theme-wrapper)} [:div {:class (stl/css :empty-theme-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)} [:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.tokens.no-themes")] (tr "workspace.tokens.no-themes")]
(when can-edit? (when can-edit-tokens?
[:button {:on-click open-modal [:button {:on-click open-modal
:class (stl/css :create-theme-button)} :class (stl/css :create-theme-button)}
(tr "workspace.tokens.create-one")])] (tr "workspace.tokens.create-one")])]
(if can-edit? (if can-edit-tokens?
[:div {:class (stl/css :theme-selector-wrapper)} [:div {:class (stl/css :theme-selector-wrapper)}
[:> theme-selector*] [:> theme-selector* {:tokens-source tokens-source}]
[:> button* {:variant "secondary" [:> button* {:variant "secondary"
:type "button" :type "button"
:class (stl/css :edit-theme-button) :class (stl/css :edit-theme-button)
:on-click open-modal} :on-click open-modal}
(tr "labels.edit")]] (tr "labels.edit")]]
[:div {:title (when-not can-edit? [:div {:title (when-not can-edit-tokens?
(tr "workspace.tokens.no-permission-themes"))} (tr "workspace.tokens.no-permission-themes"))}
[:> theme-selector*]]))])) [:> theme-selector* {:tokens-source tokens-source}]]))]))

View File

@ -9,7 +9,7 @@
@use "ds/spacing.scss" as *; @use "ds/spacing.scss" as *;
.themes-wrapper { .themes-wrapper {
padding: var(--sp-m) 0 0 var(--sp-m); padding: 0 0 0 var(--sp-m);
} }
.themes-header { .themes-header {

View File

@ -10,15 +10,16 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo] [app.common.files.tokens :as cfo]
[app.common.logic.tokens :as clt]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.constants :refer [max-input-length]] [app.main.constants :refer [max-input-length]]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [button*]] [app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.combobox :refer [combobox*]] [app.main.ui.ds.controls.combobox :refer [combobox*]]
@ -66,7 +67,7 @@
(mf/defc themes-overview (mf/defc themes-overview
[{:keys [change-view]}] [{:keys [change-view]}]
(let [active-theme-paths (mf/deref refs/workspace-active-theme-paths) (let [active-theme-ids (mf/deref refs/workspace-active-theme-ids)
themes-groups (mf/deref refs/workspace-token-theme-tree-no-hidden) themes-groups (mf/deref refs/workspace-token-theme-tree-no-hidden)
create-theme create-theme
@ -95,7 +96,7 @@
[:ul {:class (stl/css :theme-group-rows-wrapper)} [:ul {:class (stl/css :theme-group-rows-wrapper)}
(for [[_ {:keys [id name] :as theme}] themes (for [[_ {:keys [id name] :as theme}] themes
:let [theme-path (ctob/get-theme-path theme) :let [theme-path (ctob/get-theme-path theme)
selected? (some? (get active-theme-paths theme-path)) selected? (some? (get active-theme-ids (ctob/get-id theme)))
delete-theme delete-theme
(fn [e] (fn [e]
(dom/prevent-default e) (dom/prevent-default e)
@ -249,10 +250,15 @@
(defn- make-lib-with-theme (defn- make-lib-with-theme
[theme sets] [theme sets]
(let [tlib (-> (ctob/make-tokens-lib) (as-> (ctob/make-tokens-lib) $
(ctob/add-theme theme)) (ctob/add-theme $ theme)
tlib (reduce ctob/add-set tlib sets)] (reduce ctob/add-set $ sets)))
(ctob/activate-theme tlib (ctob/get-id theme))))
(defn- make-status-with-theme
[tokens-lib theme]
(let [sets (map #(ctob/get-set-by-name tokens-lib %) (:sets theme))]
(ctos/make-tokens-status :active-theme-ids #{(:id theme)}
:active-set-ids (into #{} (map ctob/get-id sets)))))
(mf/defc edit-create-theme* (mf/defc edit-create-theme*
[{:keys [change-view theme on-save is-editing has-prev-view]}] [{:keys [change-view theme on-save is-editing has-prev-view]}]
@ -262,7 +268,8 @@
current-theme* (mf/use-state theme) current-theme* (mf/use-state theme)
current-theme (deref current-theme*) current-theme (deref current-theme*)
lib (make-lib-with-theme current-theme ordered-token-sets) lib-with-theme (make-lib-with-theme current-theme ordered-token-sets)
status-with-theme (make-status-with-theme lib-with-theme current-theme)
;; Form / Modal handlers ;; Form / Modal handlers
on-back (mf/use-fn on-back (mf/use-fn
@ -306,35 +313,44 @@
;; Sets tree handlers ;; Sets tree handlers
token-set-group-active? token-set-group-active?
(mf/use-fn (mf/use-fn
(mf/deps current-theme) (mf/deps status-with-theme lib-with-theme)
(fn [group-path] (fn [group-path]
(ctob/sets-at-path-all-active? lib group-path))) (cfo/sets-at-path-all-active? status-with-theme lib-with-theme group-path)))
token-set-active? token-set-active?
(mf/use-fn (mf/use-fn
(mf/deps current-theme) (mf/deps status-with-theme)
(fn [name] (fn [id]
(contains? (:sets current-theme) name))) (ctos/set-active? status-with-theme id)))
on-toggle-token-set on-toggle-token-set
(mf/use-fn (mf/use-fn
(mf/deps current-theme) (mf/deps current-theme tokens-lib)
(fn [set-name] (fn [set-id]
(swap! current-theme* #(ctob/toggle-set % set-name)))) (let [set (ctob/get-set tokens-lib set-id)]
(swap! current-theme* #(ctob/toggle-set % (ctob/get-name set))))))
on-toggle-token-set-group on-toggle-token-set-group
(mf/use-fn (mf/use-fn
(mf/deps current-theme ordered-token-sets) (mf/deps current-theme ordered-token-sets)
(fn [group-path] (fn [group-path]
(swap! current-theme* (fn [theme'] (swap! current-theme* (fn [theme']
(let [lib' (make-lib-with-theme theme' ordered-token-sets)] ; TODOstatus ver si se puede añadir una función en files.tokens o types.tokens
(clt/toggle-token-set-group group-path lib' theme')))))) ; según si necesitamos al final el status o no (que creo que no)
(let [lib' (make-lib-with-theme theme' ordered-token-sets)
status' (make-status-with-theme lib' theme')
active? (cfo/sets-at-path-all-active? status' lib' group-path)
sets-at-path (ctob/get-sets-at-path lib' group-path)
set-names (into #{} (map ctob/get-name) sets-at-path)]
(if (contains? #{:all :partial} active?)
(ctob/disable-sets theme' set-names)
(ctob/enable-sets theme' set-names)))))))
on-click-token-set on-click-token-set
(mf/use-fn (mf/use-fn
(mf/deps on-toggle-token-set) (mf/deps on-toggle-token-set)
(fn [set-id] (fn [set-id]
(let [set (ctob/get-set lib set-id)] (let [set (ctob/get-set lib-with-theme set-id)]
(on-toggle-token-set (ctob/get-name set)))))] (on-toggle-token-set (ctob/get-name set)))))]
[:div {:class (stl/css :themes-modal-wrapper)} [:div {:class (stl/css :themes-modal-wrapper)}
@ -450,12 +466,14 @@
::mf/register modal/components ::mf/register modal/components
::mf/register-as :tokens/themes} ::mf/register-as :tokens/themes}
[] []
[:div {:class (stl/css :modal-overlay)} (let [tokens-status (mf/deref refs/tokens-status)]
[:div {:class (stl/css :modal-dialog) [:> (mf/provider ctx/tokens-status) {:value tokens-status}
:data-testid "token-theme-update-create-modal"} [:div {:class (stl/css :modal-overlay)}
[:> icon-button* {:class (stl/css :close-btn) [:div {:class (stl/css :modal-dialog)
:on-click modal/hide! :data-testid "token-theme-update-create-modal"}
:aria-label (tr "labels.close") [:> icon-button* {:class (stl/css :close-btn)
:variant "action" :on-click modal/hide!
:icon i/close}] :aria-label (tr "labels.close")
[:> themes-modal-body*]]]) :variant "action"
:icon i/close}]
[:> themes-modal-body*]]]]))

View File

@ -8,13 +8,16 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs] [app.main.router :as rt]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]] [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.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]] [app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
@ -24,17 +27,16 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc themes-list* (mf/defc themes-list*
[{:keys [themes active-theme-paths on-close is-grouped]}] [{:keys [themes tokens-status on-close is-grouped]}]
(when (seq themes) (when (seq themes)
[:ul {:class (stl/css :theme-options)} [:ul {:class (stl/css :theme-options)}
(for [[_ {:keys [id name] :as theme}] themes (for [[_ {:keys [id name] :as theme}] themes
:let [theme-id (ctob/get-theme-path theme) :let [selected? (ctos/theme-active? tokens-status 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 (str "theme-" id)
:role "option" :role "option"
:aria-selected selected? :aria-selected selected?
:class (stl/css-case :class (stl/css-case
@ -48,47 +50,68 @@
:class (stl/css-case :check-icon true :class (stl/css-case :check-icon true
:check-icon-visible selected?)}]])])) :check-icon-visible selected?)}]])]))
(defn- open-tokens-theme-modal
[]
(modal/show! :tokens/themes {}))
(mf/defc theme-options* (mf/defc theme-options*
[{:keys [active-theme-paths themes on-close]}] [{:keys [tokens-lib tokens-status tokens-source can-edit-tokens team-id on-close]}]
[:ul {:class (stl/css :theme-options :custom-select-dropdown) (let [themes
:role "listbox"} (mf/with-memo [tokens-lib]
(for [[group themes] themes] (ctob/get-theme-tree-no-hidden tokens-lib))
[:li {:key group
:aria-labelledby (dm/str group "-label") edit-token-themes
:role "group"} (fn []
(when (seq group) (if can-edit-tokens
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group]) (modal/show! :tokens/themes {})
[:> themes-list* {:themes themes (st/emit! (rt/nav :workspace
:active-theme-paths active-theme-paths {:team-id team-id
:on-close on-close :file-id tokens-source
:is-grouped true}]]) :layout :tokens}
[:li {:class (stl/css :separator) ::rt/new-window true))))]
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true [:ul {:class (stl/css :theme-options :custom-select-dropdown)
:checked-element-button true) :role "listbox"}
:role "option" (for [[group themes] themes]
:on-click open-tokens-theme-modal} [:li {:key group
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")] :aria-labelledby (dm/str group "-label")
[:> icon* {:icon-id i/arrow-right :aria-hidden true}]]]) :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
:tokens-status tokens-status
: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 edit-token-themes}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")]
[:> icon* {:icon-id (if can-edit-tokens
i/arrow-right
i/open-link)
:aria-hidden true}]]]))
(mf/defc theme-selector* (mf/defc theme-selector*
[{:keys []}] [{:keys [tokens-source]}]
(let [;; Store (let [;; Store
active-theme-paths (mf/deref refs/workspace-active-theme-paths-no-hidden) tokens-lib (mf/use-ctx ctx/tokens-lib)
active-themes-count (count active-theme-paths) tokens-status (mf/use-ctx ctx/tokens-status)
themes (mf/deref refs/workspace-token-theme-tree-no-hidden) can-edit-file? (mf/use-ctx ctx/can-edit?)
can-edit? (:can-edit (deref refs/permissions)) can-edit-tokens (mf/use-ctx ctx/can-edit-tokens?)
team-id (mf/use-ctx ctx/current-team-id)
active-themes
(mf/with-memo [tokens-lib tokens-status]
(cfo/get-active-themes tokens-status tokens-lib))
active-themes-count
(mf/with-memo [active-themes]
(count active-themes))
;; Data ;; Data
current-label (cond current-label (cond
(> active-themes-count 1) (tr "workspace.tokens.active-themes" active-themes-count) (> active-themes-count 1) (tr "workspace.tokens.active-themes" active-themes-count)
(= active-themes-count 1) (some->> (first active-theme-paths) (= active-themes-count 1) (-> (first active-themes)
(ctob/split-theme-path) (ctob/get-theme-path true))
(remove empty?)
(str/join " / "))
:else (tr "workspace.tokens.no-active-theme")) :else (tr "workspace.tokens.no-active-theme"))
;; State ;; State
@ -105,9 +128,9 @@
on-open-dropdown on-open-dropdown
(mf/use-fn (mf/use-fn
(mf/deps can-edit?) (mf/deps can-edit-file?)
(fn [event] (fn [event]
(when can-edit? (when can-edit-file?
(when-let [node (dom/get-current-target event)] (when-let [node (dom/get-current-target event)]
(let [rect (dom/get-bounding-rect node)] (let [rect (dom/get-bounding-rect node)]
(swap! state* assoc (swap! state* assoc
@ -117,14 +140,14 @@
container (hooks/use-portal-container :popup)] container (hooks/use-portal-container :popup)]
[:div {:on-click on-open-dropdown [:div {:on-click on-open-dropdown
:disabled (not can-edit?) :disabled (not can-edit-file?)
:aria-expanded is-open? :aria-expanded is-open?
:aria-haspopup "listbox" :aria-haspopup "listbox"
:tab-index "0" :tab-index "0"
:role "combobox" :role "combobox"
:data-testid "theme-select" :data-testid "theme-select"
:class (stl/css-case :custom-select true :class (stl/css-case :custom-select true
:disabled-select (not can-edit?))} :disabled-select (not can-edit-file?))}
[:> text* {:as "span" :typography "body-small" :class (stl/css :current-label)} [:> text* {:as "span" :typography "body-small" :class (stl/css :current-label)}
current-label] current-label]
[:> icon* {:icon-id i/arrow-down :class (stl/css :dropdown-button) :aria-hidden true}] [:> icon* {:icon-id i/arrow-down :class (stl/css :dropdown-button) :aria-hidden true}]
@ -140,7 +163,10 @@
[:& dropdown {:show is-open? [:& dropdown {:show is-open?
:on-close on-close-dropdown} :on-close on-close-dropdown}
[:> theme-options* {:active-theme-paths active-theme-paths [:> theme-options* {:tokens-lib tokens-lib
:themes themes :tokens-status tokens-status
:tokens-source tokens-source
:can-edit-tokens can-edit-tokens
:team-id team-id
:on-close on-close-dropdown}]]]) :on-close on-close-dropdown}]]])
container))])) container))]))

View File

@ -0,0 +1,104 @@
;; 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 Sucursal en España SL
(ns app.main.ui.workspace.tokens.tokens-source
(:require-macros [app.main.style :as stl])
(:require
[app.main.data.modal :as modal]
[app.main.refs :as refs]
[app.main.router :as rt]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc tokens-source-info*
{::mf/private true}
[{:keys [tokens-source file-id] :as props}]
(let [files (mf/deref refs/files)
tokens-source-file (get files tokens-source)
source-file-id (:id tokens-source-file)
file-name (:name tokens-source-file)
file-name-ref (mf/use-ref nil)
file-name-truncated?* (mf/use-state false)
file-name-truncated? (deref file-name-truncated?*)
check-file-name-truncated
(mf/use-fn
(fn []
(when-let [node (mf/ref-val file-name-ref)]
(reset! file-name-truncated?*
(> (.-scrollWidth node) (.-clientWidth node))))))
team-id
(mf/use-ctx ctx/current-team-id)
open-library-new-window
(mf/use-fn
(fn []
(st/emit! (rt/nav :workspace
{:team-id team-id
:file-id tokens-source
:layout :tokens}
::rt/new-window true))))
show-libraries-dialog
(mf/use-fn
(mf/deps file-id)
(fn []
(modal/show! :libraries-dialog {:file-id file-id})))]
(mf/with-effect [file-name]
(check-file-name-truncated)
(when-let [node (mf/ref-val file-name-ref)]
(let [ro (js/ResizeObserver. check-file-name-truncated)]
(.observe ro node)
#(.disconnect ro))))
[:div {:class (stl/css :tokens-source-wrapper)}
[:div {:class (stl/css :tokens-source-header)}
[:> icon* {:icon-id "tokens"
:size "m"
:class (stl/css :tokens-source-icon)}]
[:> text* {:as "span"
:typography "body-small"
:class (stl/css :tokens-source-text)}
(tr "workspace.tokens.source")]
(if (= source-file-id file-id)
[:> text* {:as "span"
:typography "body-small"
:class (stl/css :this-file)}
(tr "workspace.tokens.this-file")]
(if file-name-truncated?
[:> tooltip* {:content file-name
:trigger-ref file-name-ref
:class (stl/css :file-name-tooltip)}
[:> text* {:as "span"
:typography "body-small"
:class (stl/css :file-name)
:ref file-name-ref}
file-name]]
[:> text* {:as "span"
:typography "body-small"
:class (stl/css :file-name)
:ref file-name-ref}
file-name]))]
[:> icon-button*
{:variant "ghost"
:aria-label (tr "workspace.tokens.change-token-source")
:on-click show-libraries-dialog
:icon "switch"}]
(when-not (= source-file-id file-id)
[:> icon-button*
{:variant "ghost"
:aria-label (tr "workspace.tokens.open-source-new-tab")
:on-click open-library-new-window
:icon "open-link"}])]))

View File

@ -0,0 +1,77 @@
// 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 Sucursal en España SL
@use "ds/typography.scss" as *;
@use "ds/_sizes" as *;
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/mixins.scss" as *;
// TODO: this must be a custom property in the design system
:global(.light) {
--low-emphasis-background: #fafafa;
}
:global(.default) {
--low-emphasis-background: #121214;
}
.tokens-source-wrapper {
display: flex;
padding: 0 0 0 var(--sp-m);
background-color: var(--low-emphasis-background);
}
.tokens-source-header {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: var(--sp-s);
flex-grow: 1;
min-width: 0;
padding: var(--sp-xs);
}
.tokens-source-icon {
flex-shrink: 0;
color: var(--color-foreground-secondary);
}
.tokens-source-text {
flex-shrink: 0;
color: var(--color-foreground-secondary);
}
.file-name,
.this-file {
flex: 1 1 auto;
min-width: 0;
color: var(--color-foreground-primary);
}
.file-name {
@include text-ellipsis;
}
.file-name-tooltip {
flex: 1 1 auto;
min-width: 0;
}
.this-file {
font-style: italic;
color: var(--color-foreground-secondary);
overflow-wrap: break-word;
}
.tokens-source-description {
flex-shrink: 0;
color: var(--color-foreground-secondary);
border: $b-1 solid var(--color-foreground-secondary);
padding: var(--sp-xxs) px2rem(6);
border-radius: $br-6;
}

View File

@ -1213,7 +1213,7 @@
(let [file-id (:current-file-id @st/state) (let [file-id (:current-file-id @st/state)
library-id (uuid/parse library-id)] library-id (uuid/parse library-id)]
(->> st/stream (->> st/stream
(rx/filter (ptk/type? ::dwl/attach-library-finished)) (rx/filter (ptk/type? ::dwl/link-file-to-library-finished))
(rx/take 1) (rx/take 1)
(rx/subs! #(resolve (library-proxy plugin-id library-id)) reject)) (rx/subs! #(resolve (library-proxy plugin-id library-id)) reject))
(st/emit! (-> (dwl/link-file-to-library file-id library-id) (st/emit! (-> (dwl/link-file-to-library file-id library-id)

View File

@ -10,8 +10,10 @@
[app.common.files.tokens :as cfo] [app.common.files.tokens :as cfo]
[app.common.json :as json] [app.common.json :as json]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.types.file :as ctf]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.tokenscript :as ts] [app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.application :as dwta]
@ -216,8 +218,10 @@
(u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission")
:else :else
(st/emit! (-> (dwtl/update-token set-id id {:name value}) (do
(se/add-event plugin-id)))))} (u/check-editable-tokens file-id)
(st/emit! (-> (dwtl/update-token set-id id {:name value})
(se/add-event plugin-id))))))}
:type :type
{:this true {:this true
@ -247,11 +251,13 @@
(u/not-valid plugin-id :value "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :value "Plugin doesn't have 'content:write' permission")
:else :else
(let [token (u/locate-token file-id set-id id) (do
value (cond-> value (u/check-editable-tokens file-id)
(= :font-family (:type token)) (let [token (u/locate-token file-id set-id id)
(ctob/convert-dtcg-font-family))] value (cond-> value
(st/emit! (dwtl/update-token set-id id {:value value})))))} (= :font-family (:type token))
(ctob/convert-dtcg-font-family))]
(st/emit! (dwtl/update-token set-id id {:value value}))))))}
:resolvedValue :resolvedValue
{:this true {:this true
@ -260,7 +266,8 @@
(fn [_] (fn [_]
(let [token (u/locate-token file-id set-id id) (let [token (u/locate-token file-id set-id id)
tokens-lib (u/locate-tokens-lib file-id) tokens-lib (u/locate-tokens-lib file-id)
tokens-tree (ctob/get-tokens-in-active-sets tokens-lib)] tokens-status (u/locate-tokens-status file-id)
tokens-tree (cfo/get-tokens-in-active-sets tokens-status tokens-lib)]
(get-resolved-value token tokens-tree)))} (get-resolved-value token tokens-tree)))}
:resolvedValueString :resolvedValueString
@ -270,7 +277,8 @@
(fn [_] (fn [_]
(let [token (u/locate-token file-id set-id id) (let [token (u/locate-token file-id set-id id)
tokens-lib (u/locate-tokens-lib file-id) tokens-lib (u/locate-tokens-lib file-id)
tokens-tree (ctob/get-tokens-in-active-sets tokens-lib)] tokens-status (u/locate-tokens-status file-id)
tokens-tree (cfo/get-tokens-in-active-sets tokens-status tokens-lib)]
(str (get-resolved-value token tokens-tree))))} (str (get-resolved-value token tokens-tree))))}
:description :description
@ -287,8 +295,10 @@
(u/not-valid plugin-id :description "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :description "Plugin doesn't have 'content:write' permission")
:else :else
(st/emit! (-> (dwtl/update-token set-id id {:description value}) (do
(se/add-event plugin-id)))))} (u/check-editable-tokens file-id)
(st/emit! (-> (dwtl/update-token set-id id {:description value})
(se/add-event plugin-id))))))}
:duplicate :duplicate
(fn [] (fn []
@ -302,13 +312,15 @@
;; - use this function in dwtl/duplicate-token ;; - use this function in dwtl/duplicate-token
;; - return the new token proxy using the locally forced id ;; - return the new token proxy using the locally forced id
;; - do the same with sets and themes ;; - do the same with sets and themes
(let [token (u/locate-token file-id set-id id) (do
token' (ctob/make-token (-> (datafy token) (u/check-editable-tokens file-id)
(dissoc :id (let [token (u/locate-token file-id set-id id)
:modified-at)))] token' (ctob/make-token (-> (datafy token)
(st/emit! (-> (dwtl/create-token set-id token') (dissoc :id
(se/add-event plugin-id))) :modified-at)))]
(token-proxy plugin-id file-id set-id (:id token'))))) (st/emit! (-> (dwtl/create-token set-id token')
(se/add-event plugin-id)))
(token-proxy plugin-id file-id set-id (:id token'))))))
:remove :remove
(fn [] (fn []
@ -317,8 +329,10 @@
(u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission")
:else :else
(st/emit! (-> (dwtl/delete-token set-id id) (do
(se/add-event plugin-id))))) (u/check-editable-tokens file-id)
(st/emit! (-> (dwtl/delete-token set-id id)
(se/add-event plugin-id))))))
:applyToShapes :applyToShapes
{:enumerable false {:enumerable false
@ -374,17 +388,18 @@
(u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission")
:else :else
(let [set (u/locate-token-set file-id id)] (do
(st/emit! (dwtl/rename-token-set set name)))))} (u/check-editable-tokens file-id)
(let [set (u/locate-token-set file-id id)]
(st/emit! (dwtl/rename-token-set set name))))))}
:active :active
{:this true {:this true
:enumerable false :enumerable false
:get :get
(fn [_] (fn [_]
(let [tokens-lib (u/locate-tokens-lib file-id) (let [tokens-status (u/locate-tokens-status file-id)]
set (u/locate-token-set file-id id)] (ctos/set-active? tokens-status id)))
(ctob/token-set-active? tokens-lib (ctob/get-name set))))
:schema ::sm/boolean :schema ::sm/boolean
:set :set
(fn [_ value] (fn [_ value]
@ -393,8 +408,7 @@
(u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :active "Plugin doesn't have 'content:write' permission")
:else :else
(let [set (u/locate-token-set file-id id)] (st/emit! (dwtl/set-enabled-token-set id value))))} ;; This can be done even with tokens in an external library
(st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) value)))))}
:toggleActive :toggleActive
(fn [] (fn []
@ -403,8 +417,7 @@
(u/not-valid plugin-id :toggleActive "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :toggleActive "Plugin doesn't have 'content:write' permission")
:else :else
(let [set (u/locate-token-set file-id id)] (st/emit! (dwtl/toggle-token-set id))))
(st/emit! (dwtl/toggle-token-set (ctob/get-name set))))))
:tokens :tokens
{:this true {:this true
@ -468,28 +481,30 @@
(u/not-valid plugin-id :addToken "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :addToken "Plugin doesn't have 'content:write' permission")
:else :else
(let [tokens-lib (u/locate-tokens-lib file-id) (do
token (ctob/make-token attrs) (u/check-editable-tokens file-id)
;; Resolve against all tokens in the library (including those (let [tokens-lib (u/locate-tokens-lib file-id)
;; in inactive sets) so that references to structurally token (ctob/make-token attrs)
;; existing tokens resolve even if their set is not active. ;; Resolve against all tokens in the library (including those
;; The target set's tokens take precedence over equally named ;; in inactive sets) so that references to structurally
;; tokens in other sets, and the new token takes precedence ;; existing tokens resolve even if their set is not active.
;; over all. ;; The target set's tokens take precedence over equally named
tokens-tree (-> (merge (ctob/get-all-tokens-map tokens-lib) ;; tokens in other sets, and the new token takes precedence
(ctob/get-tokens tokens-lib id)) ;; over all.
(assoc (:name token) token)) tokens-tree (-> (merge (ctob/get-all-tokens-map tokens-lib)
resolved-tokens (ts/resolve-tokens tokens-tree) (ctob/get-tokens tokens-lib id))
(assoc (:name token) token))
resolved-tokens (ts/resolve-tokens tokens-tree)
{:keys [errors resolved-value] :as resolved-token} {:keys [errors resolved-value] :as resolved-token}
(get resolved-tokens (:name token))] (get resolved-tokens (:name token))]
(if resolved-value (if resolved-value
(do (st/emit! (-> (dwtl/create-token id token) (do (st/emit! (-> (dwtl/create-token id token)
(se/add-event plugin-id))) (se/add-event plugin-id)))
(token-proxy plugin-id file-id id (:id token))) (token-proxy plugin-id file-id id (:id token)))
(do (u/not-valid plugin-id :addToken (str errors)) (do (u/not-valid plugin-id :addToken (str errors))
nil)))))} nil))))))}
:duplicate :duplicate
(fn [] (fn []
@ -498,10 +513,12 @@
(u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission")
:else :else
(let [id-ref (atom nil)] (do
(st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref})) (u/check-editable-tokens file-id)
(when (some? @id-ref) (let [id-ref (atom nil)]
(token-set-proxy plugin-id file-id @id-ref))))) (st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref}))
(when (some? @id-ref)
(token-set-proxy plugin-id file-id @id-ref))))))
:remove :remove
(fn [] (fn []
@ -510,7 +527,9 @@
(u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission")
:else :else
(st/emit! (dwtl/delete-token-set id))))))) (do
(u/check-editable-tokens file-id)
(st/emit! (dwtl/delete-token-set id))))))))
(defn token-theme-proxy? [p] (defn token-theme-proxy? [p]
(obj/type-of? p "TokenThemeProxy")) (obj/type-of? p "TokenThemeProxy"))
@ -568,8 +587,10 @@
(u/not-valid plugin-id :group "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :group "Plugin doesn't have 'content:write' permission")
:else :else
(let [theme (u/locate-token-theme file-id id)] (do
(st/emit! (dwtl/update-token-theme id (assoc theme :group group))))))} (u/check-editable-tokens file-id)
(let [theme (u/locate-token-theme file-id id)]
(st/emit! (dwtl/update-token-theme id (assoc theme :group group)))))))}
:name :name
{:this true {:this true
@ -589,17 +610,19 @@
(u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission")
:else :else
(let [theme (u/locate-token-theme file-id id)] (do
(when name (u/check-editable-tokens file-id)
(st/emit! (dwtl/update-token-theme id (assoc theme :name name)))))))} (let [theme (u/locate-token-theme file-id id)]
(when name
(st/emit! (dwtl/update-token-theme id (assoc theme :name name))))))))}
:active :active
{:this true {:this true
:enumerable false :enumerable false
:get :get
(fn [_] (fn [_]
(let [tokens-lib (u/locate-tokens-lib file-id)] (let [tokens-status (u/locate-tokens-status file-id)]
(ctob/theme-active? tokens-lib id))) (ctos/theme-active? tokens-status id)))
:schema ::sm/boolean :schema ::sm/boolean
:set :set
(fn [_ value] (fn [_ value]
@ -641,10 +664,12 @@
(u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission")
:else :else
(let [set-name (token-set-name (resolve-token-set file-id set-arg)) (do
theme (u/locate-token-theme file-id id)] (u/check-editable-tokens file-id)
(when (and set-name theme) (let [set-name (token-set-name (resolve-token-set file-id set-arg))
(st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name)))))))} theme (u/locate-token-theme file-id id)]
(when (and set-name theme)
(st/emit! (dwtl/update-token-theme id (ctob/enable-set theme set-name))))))))}
:removeSet :removeSet
{:enumerable false {:enumerable false
@ -655,10 +680,12 @@
(u/not-valid plugin-id :removeSet "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :removeSet "Plugin doesn't have 'content:write' permission")
:else :else
(let [set-name (token-set-name (resolve-token-set file-id set-arg)) (do
theme (u/locate-token-theme file-id id)] (u/check-editable-tokens file-id)
(when (and set-name theme) (let [set-name (token-set-name (resolve-token-set file-id set-arg))
(st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name)))))))} theme (u/locate-token-theme file-id id)]
(when (and set-name theme)
(st/emit! (dwtl/update-token-theme id (ctob/disable-set theme set-name))))))))}
:duplicate :duplicate
(fn [] (fn []
@ -667,12 +694,14 @@
(u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :duplicate "Plugin doesn't have 'content:write' permission")
:else :else
(let [theme (u/locate-token-theme file-id id) (do
theme' (ctob/make-token-theme (-> (datafy theme) (u/check-editable-tokens file-id)
(dissoc :id (let [theme (u/locate-token-theme file-id id)
:modified-at)))] theme' (ctob/make-token-theme (-> (datafy theme)
(st/emit! (dwtl/create-token-theme theme')) (dissoc :id
(token-theme-proxy plugin-id file-id (:id theme'))))) :modified-at)))]
(st/emit! (dwtl/create-token-theme theme'))
(token-theme-proxy plugin-id file-id (:id theme'))))))
:remove :remove
(fn [] (fn []
@ -681,7 +710,9 @@
(u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :remove "Plugin doesn't have 'content:write' permission")
:else :else
(st/emit! (dwtl/delete-token-theme id)))))) (do
(u/check-editable-tokens file-id)
(st/emit! (dwtl/delete-token-theme id)))))))
(defn tokens-catalog (defn tokens-catalog
[plugin-id file-id] [plugin-id file-id]
@ -690,6 +721,14 @@
:$plugin {:enumerable false :get (constantly plugin-id)} :$plugin {:enumerable false :get (constantly plugin-id)}
:$id {:enumerable false :get (constantly file-id)} :$id {:enumerable false :get (constantly file-id)}
:isEditableTokens
{:this false
:enumerable false
:get
(fn []
(let [file (u/locate-file file-id)]
(cfo/editable-tokens? (ctf/file-data file))))}
:themes :themes
{:this true {:this true
:enumerable false :enumerable false
@ -726,9 +765,11 @@
(u/not-valid plugin-id :addTheme "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :addTheme "Plugin doesn't have 'content:write' permission")
:else :else
(let [theme (ctob/make-token-theme attrs)] (do
(st/emit! (dwtl/create-token-theme theme)) (u/check-editable-tokens file-id)
(token-theme-proxy plugin-id file-id (:id theme)))))} (let [theme (ctob/make-token-theme attrs)]
(st/emit! (dwtl/create-token-theme theme))
(token-theme-proxy plugin-id file-id (:id theme))))))}
:addSet :addSet
{:enumerable false {:enumerable false
@ -750,21 +791,23 @@
(u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission") (u/not-valid plugin-id :addSet "Plugin doesn't have 'content:write' permission")
:else :else
(let [active? (boolean (:active attrs)) (do
attrs (-> attrs (u/check-editable-tokens file-id)
(dissoc :active) (let [active? (boolean (:active attrs))
(update :name ctob/normalize-set-name)) attrs (-> attrs
set (ctob/make-token-set attrs)] (dissoc :active)
(st/emit! (dwtl/create-token-set set)) (update :name ctob/normalize-set-name))
;; Newly created sets are inactive by default; activate it when set (ctob/make-token-set attrs)]
;; requested. Enabling only adds the set name to the hidden theme, (st/emit! (dwtl/create-token-set set))
;; so it does not depend on the create event having propagated yet. ;; Newly created sets are inactive by default; activate it when
(when active? ;; requested. Enabling only adds the set name to the hidden theme,
(st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) true))) ;; so it does not depend on the create event having propagated yet.
;; Pass the set name as `initial-name` so the proxy can resolve (when active?
;; it immediately, before the async `st/emit!` above propagates (st/emit! (dwtl/set-enabled-token-set (ctob/get-id set) true)))
;; the new set into `@st/state`. ;; Pass the set name as `initial-name` so the proxy can resolve
(token-set-proxy plugin-id file-id (ctob/get-id set) (ctob/get-name set)))))} ;; it immediately, before the async `st/emit!` above propagates
;; the new set into `@st/state`.
(token-set-proxy plugin-id file-id (ctob/get-id set) (ctob/get-name set))))))}
:getThemeById :getThemeById
{:enumerable false {:enumerable false

View File

@ -9,6 +9,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.i18n :as i18n :refer [tr]] [app.common.i18n :as i18n :refer [tr]]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.messages :as csm] [app.common.schema.messages :as csm]
@ -67,8 +68,18 @@
(defn locate-tokens-lib (defn locate-tokens-lib
[file-id] [file-id]
(let [file (locate-file file-id)] (let [file-data (-> (locate-file file-id) (ctf/file-data))
(->> file :data :tokens-lib))) tokens-source-id (cfo/get-effective-tokens-source file-data)]
(some-> tokens-source-id
(locate-file)
(ctf/file-data)
(cfo/get-tokens-lib))))
(defn locate-tokens-status
[file-id]
(let [file (locate-file file-id)
file-data (ctf/file-data file)]
(cfo/get-tokens-status file-data)))
(defn locate-token-theme (defn locate-token-theme
[file-id id] [file-id id]
@ -275,6 +286,12 @@
(boolean (boolean
(dm/get-in @st/state [:plugins :flags plugin-id :natural-child-ordering]))) (dm/get-in @st/state [:plugins :flags plugin-id :natural-child-ordering])))
(defn check-editable-tokens
[file-id]
(let [file (locate-file file-id)]
(when-not (cfo/editable-tokens? (ctf/file-data file))
(throw (js/Error. (dm/str "[PENPOT PLUGIN] Cannot modify tokens in an external library"))))))
(defn throw-validation-errors? (defn throw-validation-errors?
[plugin-id] [plugin-id]
(boolean (boolean

View File

@ -14,6 +14,7 @@
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.texts-events :as dwte]
[app.main.ui.workspace.shapes.text.viewport-texts-html :as vth] [app.main.ui.workspace.shapes.text.viewport-texts-html :as vth]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths])) [frontend-tests.helpers.state :as ths]))
@ -312,7 +313,7 @@
store (ths/setup-store file) store (ths/setup-store file)
events [;; Pre-select the text shape so add-typography can find it events [;; Pre-select the text shape so add-typography can find it
(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id})) (fn [state] (assoc-in state [:workspace-local :selected] #{shape-id}))
(dwt/add-typography file-id)]] (dwte/add-typography file-id)]]
(ths/run-store (ths/run-store
store done events store done events
@ -339,7 +340,7 @@
file-id (:id file) file-id (:id file)
store (ths/setup-store file) store (ths/setup-store file)
events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id})) events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id}))
(dwt/add-typography file-id)]] (dwte/add-typography file-id)]]
(ths/run-store (ths/run-store
store done events store done events
@ -366,7 +367,7 @@
file-id (:id file) file-id (:id file)
store (ths/setup-store file) store (ths/setup-store file)
events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id})) events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id}))
(dwt/add-typography file-id)]] (dwte/add-typography file-id)]]
(ths/run-store (ths/run-store
store done events store done events

View File

@ -9,11 +9,11 @@
[app.common.math :as mth] [app.common.math :as mth]
[app.common.test-helpers.components :as cthc] [app.common.test-helpers.components :as cthc]
[app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths] [app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht] [app.common.test-helpers.tokens :as ctht]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
@ -35,29 +35,31 @@
(defn- setup-base-file (defn- setup-base-file
[] []
(-> (cthf/sample-file :file1) (-> (ctht/sample-file-with-tokens
(ctht/add-tokens-lib) :lib-fn #(-> %
(ctht/update-tokens-lib #(-> % (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set) :name "test-token-set"))
:name "test-token-set")) (ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :test-theme)
(ctob/add-theme (ctob/make-token-theme :name "test-theme" :name "test-theme"
:sets #{"test-token-set"})) :sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"}) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :test-token-1)
(ctob/make-token :id (cthi/new-id! :test-token-1) :name "test-token-1"
:name "test-token-1" :type :border-radius
:type :border-radius :value 25))
:value 25)) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :test-token-2)
(ctob/make-token :id (cthi/new-id! :test-token-2) :name "test-token-2"
:name "test-token-2" :type :border-radius
:type :border-radius :value 50))
:value 50)) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :test-token-3)
(ctob/make-token :id (cthi/new-id! :test-token-3) :name "test-token-3"
:name "test-token-3" :type :border-radius
:type :border-radius :value 75)))
:value 75)))) :status-fn #(-> %
(ctos/set-tokens-status #{(cthi/id :test-theme)}
#{(cthi/id :test-token-set)})))
(ctho/add-frame :frame1) (ctho/add-frame :frame1)
(ctht/apply-token-to-shape :frame1 "test-token-1" [:r1 :r2 :r3 :r4] [:r1 :r2 :r3 :r4] 25))) (ctht/apply-token-to-shape :frame1 "test-token-1" [:r1 :r2 :r3 :r4] [:r1 :r2 :r3 :r4] 25)))
@ -323,44 +325,44 @@
(t/async (t/async
done done
(let [;; ==== Setup (let [;; ==== Setup
file (-> (cthf/sample-file :file1) file (-> (ctht/sample-file-with-tokens
(ctht/add-tokens-lib) :lib-fn #(-> %
(ctht/update-tokens-lib #(-> % (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set) :name "test-token-set"))
:name "test-token-set")) (ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :test-theme)
(ctob/add-theme (ctob/make-token-theme :name "test-theme" :name "test-theme"
:sets #{"test-token-set"})) :sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"}) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :token-radius)
(ctob/make-token :id (cthi/new-id! :token-radius) :name "token-radius"
:name "token-radius" :type :border-radius
:type :border-radius :value 10))
:value 10)) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :token-rotation)
(ctob/make-token :id (cthi/new-id! :token-rotation) :name "token-rotation"
:name "token-rotation" :type :rotation
:type :rotation :value 30))
:value 30)) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :token-opacity)
(ctob/make-token :id (cthi/new-id! :token-opacity) :name "token-opacity"
:name "token-opacity" :type :opacity
:type :opacity :value 0.7))
:value 0.7)) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :token-stroke-width)
(ctob/make-token :id (cthi/new-id! :token-stroke-width) :name "token-stroke-width"
:name "token-stroke-width" :type :stroke-width
:type :stroke-width :value 2))
:value 2)) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :token-color)
(ctob/make-token :id (cthi/new-id! :token-color) :name "token-color"
:name "token-color" :type :color
:type :color :value "#00ff00"))
:value "#00ff00")) (ctob/add-token (cthi/id :test-token-set)
(ctob/add-token (cthi/id :test-token-set) (ctob/make-token :id (cthi/new-id! :token-dimensions)
(ctob/make-token :id (cthi/new-id! :token-dimensions) :name "token-dimensions"
:name "token-dimensions" :type :dimensions
:type :dimensions :value 100)))
:value 100)))) :status-fn #(ctos/set-tokens-status % #{(cthi/id :test-theme)} #{(cthi/id :test-token-set)}))
(ctho/add-frame :frame1) (ctho/add-frame :frame1)
(ctht/apply-token-to-shape :frame1 "token-radius" [:r1 :r2 :r3 :r4] [:r1 :r2 :r3 :r4] 10) (ctht/apply-token-to-shape :frame1 "token-radius" [:r1 :r2 :r3 :r4] [:r1 :r2 :r3 :r4] 10)
(ctht/apply-token-to-shape :frame1 "token-rotation" [:rotation] [:rotation] 30) (ctht/apply-token-to-shape :frame1 "token-rotation" [:rotation] [:rotation] 30)

View File

@ -6,11 +6,13 @@
(ns frontend-tests.plugins.tokens-test (ns frontend-tests.plugins.tokens-test
(:require (:require
[app.common.files.tokens :as cfo]
[app.common.test-helpers.compositions :as ctho] [app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf] [app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.tokens :as ctht] [app.common.test-helpers.tokens :as ctht]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.tokenscript :as ts] [app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
@ -125,24 +127,25 @@
done done
(let [set-id (cthi/new-id! :token-set) (let [set-id (cthi/new-id! :token-set)
token-id (cthi/new-id! :spacing-token) token-id (cthi/new-id! :spacing-token)
file (-> (cthf/sample-file :file1 :page-label :page1) theme-id (cthi/new-id! :theme)
(ctho/add-frame :frame1 {:layout :flex}) file (-> (ctht/sample-file-with-tokens
(ctht/add-tokens-lib) :file-id :file1
(ctht/update-tokens-lib :lib-fn #(-> %
#(-> % (ctob/add-set
(ctob/add-set (ctob/make-token-set :id set-id
(ctob/make-token-set :id set-id :name "spacing"))
:name "spacing")) (ctob/add-theme
(ctob/add-theme (ctob/make-token-theme :id theme-id
(ctob/make-token-theme :name "theme" :name "theme"
:sets #{"spacing"})) :sets #{"spacing"}))
(ctob/set-active-themes #{"/theme"}) (ctob/add-token
(ctob/add-token set-id
set-id (ctob/make-token :id token-id
(ctob/make-token :id token-id :name "spacing.medium"
:name "spacing.medium" :type :spacing
:type :spacing :value 16)))
:value 16))))) :status-fn #(ctos/set-tokens-status % #{theme-id} #{set-id}))
(ctho/add-frame :frame1 {:layout :flex}))
store (ths/setup-store file) store (ths/setup-store file)
_ (set! st/state store) _ (set! st/state store)
_ (set! st/stream (ptk/input-stream store)) _ (set! st/stream (ptk/input-stream store))
@ -170,7 +173,8 @@
(t/deftest shape-apply-token-accepts-font-families (t/deftest shape-apply-token-accepts-font-families
(t/async (t/async
done done
(let [set-id (cthi/new-id! :token-set) (let [theme-id (cthi/new-id! :token-theme)
set-id (cthi/new-id! :token-set)
token-id (cthi/new-id! :font-family-token) token-id (cthi/new-id! :font-family-token)
file (-> (cthf/sample-file :file1 :page-label :page1) file (-> (cthf/sample-file :file1 :page-label :page1)
(ctho/add-text :text1 "Hello World!") (ctho/add-text :text1 "Hello World!")
@ -183,13 +187,14 @@
(ctob/add-theme (ctob/add-theme
(ctob/make-token-theme :name "theme" (ctob/make-token-theme :name "theme"
:sets #{"fonts"})) :sets #{"fonts"}))
(ctob/set-active-themes #{"/theme"})
(ctob/add-token (ctob/add-token
set-id set-id
(ctob/make-token :id token-id (ctob/make-token :id token-id
:name "font.primary" :name "font.primary"
:type :font-family :type :font-family
:value ["Inter"]))))) :value ["Inter"]))))
(ctht/update-tokens-status
#(ctos/set-tokens-status % #{theme-id} #{set-id})))
store (ths/setup-store file) store (ths/setup-store file)
_ (set! st/state store) _ (set! st/state store)
_ (set! st/stream (ptk/input-stream store)) _ (set! st/stream (ptk/input-stream store))
@ -260,10 +265,11 @@
;; Demonstrates the bug: resolving the new token against active sets ;; Demonstrates the bug: resolving the new token against active sets
;; only leaves the reference unresolved. ;; only leaves the reference unresolved.
(let [tokens-lib (inactive-set-library) (let [tokens-lib (inactive-set-library)
tokens-status (cfo/make-tokens-status-from-lib tokens-lib)
token (ctob/make-token {:name "color.bg.default" token (ctob/make-token {:name "color.bg.default"
:value "{color.gray.50}" :value "{color.gray.50}"
:type :color}) :type :color})
tokens-tree (-> (ctob/get-tokens-in-active-sets tokens-lib) tokens-tree (-> (cfo/get-tokens-in-active-sets tokens-status tokens-lib)
(assoc (:name token) token)) (assoc (:name token) token))
resolved (ts/resolve-tokens tokens-tree) resolved (ts/resolve-tokens tokens-tree)
{:keys [errors resolved-value]} (get resolved (:name token))] {:keys [errors resolved-value]} (get resolved (:name token))]
@ -303,16 +309,18 @@
(t/deftest token-set-duplicate-returns-the-duplicated-set (t/deftest token-set-duplicate-returns-the-duplicated-set
(let [file-id (cthi/new-id! :file) (let [file-id (cthi/new-id! :file)
set-id (cthi/new-id! :set) set-id (cthi/new-id! :set)
dup-id (cthi/new-id! :dup) dup-id (cthi/new-id! :dup)]
proxy (ptok/token-set-proxy "plugin-id" file-id set-id)] (with-redefs [u/locate-tokens-lib (constantly nil)
(with-redefs [r/check-permission (constantly true) r/check-permission (constantly true)
u/check-editable-tokens (constantly nil)
dwtl/duplicate-token-set dwtl/duplicate-token-set
(mock/stub (fn [id {:keys [id-ref]}] (mock/stub (fn [id {:keys [id-ref]}]
(t/is (= set-id id)) (t/is (= set-id id))
(reset! id-ref dup-id) (reset! id-ref dup-id)
:duplicate-token-set)) :duplicate-token-set))
st/emit! mock/noop] st/emit! mock/noop]
(let [dup (.duplicate proxy)] (let [proxy (ptok/token-set-proxy "plugin-id" file-id set-id)
dup (.duplicate proxy)]
(t/is (ptok/token-set-proxy? dup)) (t/is (ptok/token-set-proxy? dup))
(t/is (= (str dup-id) (.-id dup))))))) (t/is (= (str dup-id) (.-id dup)))))))
@ -320,10 +328,10 @@
(let [file-id (cthi/new-id! :file) (let [file-id (cthi/new-id! :file)
theme-id (cthi/new-id! :theme) theme-id (cthi/new-id! :theme)
set-id (cthi/new-id! :set) set-id (cthi/new-id! :set)
set (ptok/token-set-proxy "plugin-id" file-id set-id "Primitives")
theme (ptok/token-theme-proxy "plugin-id" file-id theme-id)
captured (atom [])] captured (atom [])]
(with-redefs [r/check-permission (constantly true) (with-redefs [u/locate-tokens-lib (constantly nil)
r/check-permission (constantly true)
u/check-editable-tokens (constantly nil)
u/locate-token-theme u/locate-token-theme
(fn [_file _theme] (fn [_file _theme]
(ctob/make-token-theme :id theme-id (ctob/make-token-theme :id theme-id
@ -334,18 +342,22 @@
(swap! captured conj {:id id :theme theme}) (swap! captured conj {:id id :theme theme})
:update-token-theme) :update-token-theme)
st/emit! mock/noop] st/emit! mock/noop]
(.addSet theme set) (let [set (ptok/token-set-proxy "plugin-id" file-id set-id "Primitives")
(.removeSet theme set) theme (ptok/token-theme-proxy "plugin-id" file-id theme-id)]
(t/is (= [theme-id theme-id] (mapv :id @captured))) (.addSet theme set)
(t/is (contains? (-> @captured first :theme :sets) "Primitives")) (.removeSet theme set)
(t/is (not (contains? (-> @captured second :theme :sets) "Primitives")))))) (t/is (= [theme-id theme-id] (mapv :id @captured)))
(t/is (contains? (-> @captured first :theme :sets) "Primitives"))
(t/is (not (contains? (-> @captured second :theme :sets) "Primitives")))))))
(t/deftest font-family-token-value-accepts-a-string (t/deftest font-family-token-value-accepts-a-string
(let [file-id (cthi/new-id! :file) (let [file-id (cthi/new-id! :file)
set-id (cthi/new-id! :set) set-id (cthi/new-id! :set)
token-id (cthi/new-id! :token) token-id (cthi/new-id! :token)
captured (atom nil)] captured (atom nil)]
(with-redefs [r/check-permission (constantly true) (with-redefs [u/locate-tokens-lib (constantly nil)
r/check-permission (constantly true)
u/check-editable-tokens (constantly nil)
u/locate-token (constantly {:id token-id u/locate-token (constantly {:id token-id
:name "font.primary" :name "font.primary"
:type :font-family :type :font-family
@ -419,13 +431,15 @@
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom []) emitted (atom [])
invalid (atom [])] invalid (atom [])]
(with-redefs [r/check-permission (constantly true) (with-redefs [r/check-permission (constantly true)
u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) u/locate-tokens-lib (constantly nil)
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
u/not-valid (fn [_ code value] (swap! invalid conj [code value])) u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/check-editable-tokens (constantly nil)
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
st/emit! (fn ([event] (swap! emitted conj event) nil) st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))] ([event & _] (swap! emitted conj event) nil))]
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)]
(.addSet theme-proxy (str set-id)) (.addSet theme-proxy (str set-id))
(t/is (= #{"Core"} (-> @emitted first :theme :sets))) (t/is (= #{"Core"} (-> @emitted first :theme :sets)))
@ -440,13 +454,15 @@
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom []) emitted (atom [])
invalid (atom [])] invalid (atom [])]
(with-redefs [r/check-permission (constantly true) (with-redefs [r/check-permission (constantly true)
u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) u/locate-tokens-lib (constantly nil)
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
u/not-valid (fn [_ code value] (swap! invalid conj [code value])) u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/check-editable-tokens (constantly nil)
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
st/emit! (fn ([event] (swap! emitted conj event) nil) st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))] ([event & _] (swap! emitted conj event) nil))]
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id) (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)
set-proxy (ptok/token-set-proxy plugin-id file-id set-id "Core")] set-proxy (ptok/token-set-proxy plugin-id file-id set-id "Core")]
(.addSet theme-proxy set-proxy) (.addSet theme-proxy set-proxy)
@ -459,20 +475,24 @@
theme-id (uuid/next) theme-id (uuid/next)
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom []) emitted (atom [])
errors (atom [])] invalid (atom [])]
(with-redefs [u/locate-token-set (constantly nil) (with-redefs [u/locate-tokens-lib (constantly nil)
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) u/locate-token-set (constantly nil)
u/throw-validation-errors? (constantly true) u/throw-validation-errors? (constantly true)
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/check-editable-tokens (constantly nil)
u/handle-error (fn [plugin-id]
(fn [cause]
(u/not-valid plugin-id :error (str cause))))
dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) dwtl/update-token-theme (fn [id theme] {:id id :theme theme})
st/emit! (fn ([event] (swap! emitted conj event) nil) st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))] ([event & _] (swap! emitted conj event) nil))]
(let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)]
;; Non-id, non-proxy arguments are rejected by the schema coercer. (try (.addSet theme-proxy 42) (catch :default e (swap! invalid conj e)))
(try (.addSet theme-proxy 42) (catch :default e (swap! errors conj e))) (try (.removeSet theme-proxy nil) (catch :default e (swap! invalid conj e)))
(try (.removeSet theme-proxy nil) (catch :default e (swap! errors conj e)))
(t/is (empty? @emitted)) (t/is (empty? @emitted))
(t/is (= 2 (count @errors))) (t/is (= 2 (count @invalid)))
(t/is (every? #(instance? js/Error %) @errors)))))) (t/is (every? #(instance? js/Error %) @invalid))))))
;; ═══════════════════════════════════════════════════════════════ ;; ═══════════════════════════════════════════════════════════════
;; Permission check tests (T9-F-01) ;; Permission check tests (T9-F-01)
@ -582,7 +602,7 @@
u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg]))) u/not-valid (mock/stub (fn [pid prop msg] (swap! errors conj [pid prop msg])))
r/check-permission (constantly false) r/check-permission (constantly false)
st/emit! mock/noop] st/emit! mock/noop]
(let [proxy (ptok/token-set-proxy plugin-id file-id set-id)] (let [proxy (ptok/token-set-proxy plugin-id file-id set-id "test-plugin")]
(.toggleActive proxy) (.toggleActive proxy)
(t/is (= 1 (count @errors))) (t/is (= 1 (count @errors)))
(t/is (= [plugin-id :toggleActive "Plugin doesn't have 'content:write' permission"] (first @errors))))))) (t/is (= [plugin-id :toggleActive "Plugin doesn't have 'content:write' permission"] (first @errors)))))))

View File

@ -82,6 +82,7 @@
[frontend-tests.tokens.logic.token-actions-test] [frontend-tests.tokens.logic.token-actions-test]
[frontend-tests.tokens.logic.token-data-test] [frontend-tests.tokens.logic.token-data-test]
[frontend-tests.tokens.logic.token-remapping-test] [frontend-tests.tokens.logic.token-remapping-test]
[frontend-tests.tokens.logic.tokens-status-test]
[frontend-tests.tokens.style-dictionary-test] [frontend-tests.tokens.style-dictionary-test]
[frontend-tests.tokens.token-errors-test] [frontend-tests.tokens.token-errors-test]
[frontend-tests.tokens.workspace-tokens-remap-test] [frontend-tests.tokens.workspace-tokens-remap-test]
@ -198,6 +199,7 @@
'frontend-tests.tokens.logic.token-remapping-test 'frontend-tests.tokens.logic.token-remapping-test
'frontend-tests.tokens.style-dictionary-test 'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-errors-test 'frontend-tests.tokens.token-errors-test
'frontend-tests.tokens.logic.tokens-status-test
'frontend-tests.tokens.workspace-tokens-remap-test 'frontend-tests.tokens.workspace-tokens-remap-test
'frontend-tests.ui.check-updates-test 'frontend-tests.ui.check-updates-test
'frontend-tests.ui.colorpicker-token-set-order-test 'frontend-tests.ui.colorpicker-token-set-order-test

View File

@ -6,49 +6,51 @@
[app.common.test-helpers.shapes :as ths] [app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht] [app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.ui.workspace.tokens.management.context-menu :as wtcm] [app.main.ui.workspace.tokens.management.context-menu :as wtcm]
[clojure.test :as t])) [clojure.test :as t]))
(defn setup-file [] (defn setup-file []
(-> (thf/sample-file :file-1) (-> (tht/sample-file-with-tokens
(tht/add-tokens-lib) :lib-fn #(-> %
(tht/update-tokens-lib #(-> % (ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
(ctob/add-set (ctob/make-token-set :name "test-token-set")) :name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme" (ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :test-theme)
:sets #{"test-token-set"})) :name "test-theme"
(ctob/set-active-themes #{"/test-theme"}) :sets #{"test-token-set"}))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-radius" (ctob/make-token :name "token-radius"
:type :border-radius :type :border-radius
:value 10)) :value 10))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-color" (ctob/make-token :name "token-color"
:type :color :type :color
:value "red")) :value "red"))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-spacing" (ctob/make-token :name "token-spacing"
:type :spacing :type :spacing
:value 10)) :value 10))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-sizing" (ctob/make-token :name "token-sizing"
:type :sizing :type :sizing
:value 10)) :value 10))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-rotation" (ctob/make-token :name "token-rotation"
:type :rotation :type :rotation
:value 10)) :value 10))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-opacity" (ctob/make-token :name "token-opacity"
:type :opacity :type :opacity
:value 10)) :value 10))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-dimensions" (ctob/make-token :name "token-dimensions"
:type :dimensions :type :dimensions
:value 10)) :value 10))
(ctob/add-token (thi/id :test-token-set) (ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-number" (ctob/make-token :name "token-number"
:type :number :type :number
:value 10)))) :value 10)))
:status-fn #(ctos/set-tokens-status % #{(thi/id :test-theme)} #{(thi/id :test-token-set)}))
;; app.main.data.workspace.tokens.application/generic-attributes ;; app.main.data.workspace.tokens.application/generic-attributes
(tho/add-group :group1) (tho/add-group :group1)
;; app.main.data.workspace.tokens.application/rect-attributes ;; app.main.data.workspace.tokens.application/rect-attributes

View File

@ -6,9 +6,12 @@
(ns frontend-tests.tokens.helpers.state (ns frontend-tests.tokens.helpers.state
(:require (:require
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.style-dictionary :as sd] [app.main.data.style-dictionary :as sd]
[app.main.data.workspace.undo :as dwu]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -29,11 +32,14 @@
(ptk/reify ::end+ (ptk/reify ::end+
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [data (dsh/lookup-file-data state)] (let [data (dsh/lookup-file-data state)
(->> (get data :tokens-lib) tokens-status (cfo/get-tokens-status data)
(ctob/get-tokens-in-active-sets) tokens-lib (cfo/get-tokens-lib data)]
(sd/resolve-tokens) (if (and tokens-status tokens-lib)
(rx/mapcat #(rx/of (end)))))))) (->> (cfo/get-tokens-in-active-sets tokens-status tokens-lib)
(sd/resolve-tokens)
(rx/mapcat #(rx/of (end))))
(rx/of (end)))))))
(defn stop-on (defn stop-on
"Helper function to be used with async version of run-store. "Helper function to be used with async version of run-store.
@ -51,6 +57,31 @@
"Stops on `send-update-indices` function being called, which should be the last function of an event chain." "Stops on `send-update-indices` function being called, which should be the last function of an event chain."
(stop-on ::end)) (stop-on ::end))
(defn watch-undo-stack
"Maintain the workspace undo stack by watching for commit events.
Must be emitted before the events under test so undo entries are populated."
[]
(ptk/reify ::watch-undo-stack
ptk/WatchEvent
(watch [_ _ stream]
(let [stopper-s (->> stream (rx/filter (ptk/type? ::watch-undo-stack)))]
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(= :local (:source %)))
(rx/mapcat
(fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo? selected-before]}]
(if (and save-undo? (seq undo-changes))
(rx/of (dwu/append-undo
{:undo-changes undo-changes
:redo-changes redo-changes
:undo-group undo-group
:tags tags
:selected-before selected-before}
stack-undo?))
(rx/empty))))
(rx/take-until stopper-s))))))
;; Support for async events in tests ;; Support for async events in tests
;; https://chat.kaleidos.net/penpot-partners/pl/tz1yoes3w3fr9qanxqpuhoz3ch ;; https://chat.kaleidos.net/penpot-partners/pl/tz1yoes3w3fr9qanxqpuhoz3ch
(defn run-store (defn run-store

View File

@ -11,9 +11,11 @@
[app.common.types.tokens-lib :as ctob])) [app.common.types.tokens-lib :as ctob]))
(defn get-token [file name] (defn get-token [file name]
(some-> (get-in file [:data :tokens-lib]) (let [data (get-in file [:data])
(ctob/get-tokens-in-active-sets) tokens-status (cfo/get-tokens-status data)
(get name))) tokens-lib (cfo/get-tokens-lib data)]
(some-> (cfo/get-tokens-in-active-sets tokens-status tokens-lib)
(get name))))
(defn apply-token-to-shape (defn apply-token-to-shape
[file shape-label token-label attributes] [file shape-label token-label attributes]

View File

@ -10,8 +10,10 @@
[app.common.test-helpers.files :as cthf] [app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths] [app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.wasm-text :as dwwt] [app.main.data.workspace.wasm-text :as dwwt]
@ -44,21 +46,22 @@
(defn setup-file-with-tokens (defn setup-file-with-tokens
[& {:keys [rect-1 rect-2 rect-3]}] [& {:keys [rect-1 rect-2 rect-3]}]
(-> (setup-file) (-> (ctht/sample-file-with-tokens
(ctho/add-rect :rect-1 rect-1) :lib-fn #(-> %
(ctho/add-rect :rect-2 rect-2)
(ctho/add-rect :rect-3 rect-3)
(ctho/add-text :text-1 "Hello World!")
(assoc-in [:data :tokens-lib]
(-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
(ctob/set-active-themes #{"/Theme A"})
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "Set A")) :name "Set A"))
(ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :theme-a)
:name "Theme A"
:sets #{"Set A"}))
(ctob/add-token (cthi/id :set-a) (ctob/add-token (cthi/id :set-a)
(ctob/make-token border-radius-token)) (ctob/make-token border-radius-token))
(ctob/add-token (cthi/id :set-a) (ctob/add-token (cthi/id :set-a)
(ctob/make-token reference-border-radius-token)))))) (ctob/make-token reference-border-radius-token)))
:status-fn #(ctos/set-tokens-status % #{(cthi/id :theme-a)} #{(cthi/id :set-a)}))
(ctho/add-rect :rect-1 rect-1)
(ctho/add-rect :rect-2 rect-2)
(ctho/add-rect :rect-3 rect-3)
(ctho/add-text :text-1 "Hello World!")))
(def debounce-text-stop (def debounce-text-stop
(tohs/stop-on ::dwwt/resize-wasm-text-debounce-commit)) (tohs/stop-on ::dwwt/resize-wasm-text-debounce-commit))
@ -83,15 +86,16 @@
done done
(let [file (setup-file-with-empty-lib) (let [file (setup-file-with-empty-lib)
store (ths/setup-store file) store (ths/setup-store file)
set (ctob/make-token-set :name "primitives") set (ctob/make-token-set :id (cthi/new-id! :set1) :name "primitives")
events [(dwtl/create-token-set set)]] events [(dwtl/create-token-set set)]]
(tohs/run-store-async (tohs/run-store-async
store done events store done events
(fn [new-state] (fn [new-state]
(let [file' (ths/get-file-from-state new-state) (let [file' (ths/get-file-from-state new-state)
lib (get-in file' [:data :tokens-lib])] lib (ctht/get-tokens-lib file')
status (ctht/get-tokens-status file')]
(t/is (some? (ctob/get-set lib (ctob/get-id set)))) (t/is (some? (ctob/get-set lib (ctob/get-id set))))
(t/is (false? (ctob/token-set-active? lib "primitives")))))))))) (t/is (false? (ctos/set-active? status (cthi/id :set1)))))))))))
(t/deftest test-create-then-enable-token-set (t/deftest test-create-then-enable-token-set
(t/testing "create followed by set-enabled (as the plugin addSet does) yields an active set" (t/testing "create followed by set-enabled (as the plugin addSet does) yields an active set"
@ -99,16 +103,17 @@
done done
(let [file (setup-file-with-empty-lib) (let [file (setup-file-with-empty-lib)
store (ths/setup-store file) store (ths/setup-store file)
set (ctob/make-token-set :name "primitives") set (ctob/make-token-set :id (cthi/new-id! :set1) :name "primitives")
events [(dwtl/create-token-set set) events [(dwtl/create-token-set set)
(dwtl/set-enabled-token-set "primitives" true)]] (dwtl/set-enabled-token-set (cthi/id :set1) true)]]
(tohs/run-store-async (tohs/run-store-async
store done events store done events
(fn [new-state] (fn [new-state]
(let [file' (ths/get-file-from-state new-state) (let [file' (ths/get-file-from-state new-state)
lib (get-in file' [:data :tokens-lib])] lib (ctht/get-tokens-lib file')
status (ctht/get-tokens-status file')]
(t/is (some? (ctob/get-set lib (ctob/get-id set)))) (t/is (some? (ctob/get-set lib (ctob/get-id set))))
(t/is (true? (ctob/token-set-active? lib "primitives")))))))))) (t/is (true? (ctos/set-active? status (cthi/id :set1)))))))))))
(t/deftest test-apply-token (t/deftest test-apply-token
(t/testing "applies token to shape and updates shape attributes to resolved value" (t/testing "applies token to shape and updates shape attributes to resolved value"

View File

@ -8,9 +8,12 @@
(:require (:require
[app.common.test-helpers.files :as cthf] [app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.tokens :as ctho]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.undo :as dwu]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp] [frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths] [frontend-tests.helpers.state :as ths]
@ -25,11 +28,40 @@
(defn setup-file-with-token-lib (defn setup-file-with-token-lib
[] []
(-> (setup-file) (ctho/sample-file-with-tokens
(assoc-in [:data :tokens-lib] :file-id :file-1
(-> (ctob/make-tokens-lib) :page-label :page-1
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set) :lib-fn #(ctob/add-set % (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "Set A")))))) :name "Set A"))
:status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :test-token-set)})))
(defn setup-file-with-token-lib-and-theme
[theme-id]
(ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "Set A"))
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "Theme A"
:group "default")))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{(cthi/id :test-token-set)})))
(defn setup-file-with-token-lib-and-token
[]
(ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "Set A"))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :color.primary)
:name "color.primary"
:type :color
:value "#000000")))
:status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :test-token-set)})))
(t/deftest add-set (t/deftest add-set
(t/async (t/async
@ -50,6 +82,20 @@
(t/is (= (count sets') 2)) (t/is (= (count sets') 2))
(t/is (some? set-b'))))))))) (t/is (some? set-b')))))))))
(t/deftest add-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/create-token-set (ctob/make-token-set :name "Set A"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest rename-set (t/deftest rename-set
(t/async (t/async
done done
@ -71,6 +117,21 @@
(t/is (= (count sets') 1)) (t/is (= (count sets') 1))
(t/is (some? set-a'))))))))) (t/is (some? set-a')))))))))
(t/deftest rename-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
set-a (ctob/make-token-set :name "Set A")
events [(dwtl/rename-token-set set-a "Set A updated")]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest duplicate-set (t/deftest duplicate-set
(t/async (t/async
done done
@ -103,21 +164,734 @@
sets (ctob/get-sets token-lib)] sets (ctob/get-sets token-lib)]
(t/testing "Token lib contains one set" (t/testing "Token lib contains one set"
(t/is (= (count sets) 1)))))))) (t/is (= (count sets) 1)))))))))
(t/deftest delete-set (t/deftest duplicate-set-no-tokens-lib
(t/async (t/async
done done
(let [file (setup-file-with-token-lib) (let [file (setup-file)
store (ths/setup-store file) store (ths/setup-store file)
events [(dwtl/delete-token-set (cthi/id :test-token-set))]] events [(dwtl/duplicate-token-set (uuid/next))]]
(tohs/run-store-async (tohs/run-store-async
store done events store done events
(fn [new-state] (fn [new-state]
(let [file' (ths/get-file-from-state new-state) (let [file-data (-> (ths/get-file-from-state new-state) :data)]
tokens-lib' (toht/get-tokens-lib file') (t/testing "No crash when file has no tokens-lib"
sets' (ctob/get-sets tokens-lib')] (t/is (some? file-data)))))))))
(t/testing "Set has been deleted" (t/deftest delete-set
(t/is (= (count sets') 0)))))))))) (t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
events [(dwtl/delete-token-set (cthi/id :test-token-set))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
sets' (ctob/get-sets tokens-lib')]
(t/testing "Set has been deleted"
(t/is (= (count sets') 0)))))))))
(t/deftest delete-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/delete-token-set (uuid/next))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest set-tokens-source
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
library-id (uuid/next)]
;; Phase 1: set tokens-source with undo watcher active
(tohs/run-store
store identity
[(tohs/watch-undo-stack)
(dwtl/set-tokens-source library-id)]
(fn [new-state]
(let [file-data' (-> (ths/get-file-from-state new-state) :data)]
(t/testing "tokens-source is set to the library id"
(t/is (= library-id (:tokens-source file-data'))))))
(tohs/stop-on ::dwtl/set-tokens-source))
;; Phase 2: undo and verify restoration
(tohs/run-store
store done
[dwu/undo]
(fn [undone-state]
(let [file-data'' (-> (ths/get-file-from-state undone-state) :data)]
(t/testing "tokens-source is restored to nil"
(t/is (nil? (:tokens-source file-data''))))))
(tohs/stop-on ::dwu/undo)))))
(t/deftest set-tokens-source-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
library-id (uuid/next)]
(tohs/run-store-async
store done
[(dwtl/set-tokens-source library-id)]
(fn [new-state]
(let [file-data' (-> (ths/get-file-from-state new-state) :data)]
(t/testing "tokens-source is set even without tokens-lib"
(t/is (= library-id (:tokens-source file-data'))))))))))
;; ==========================================================================
;; Token Themes
;; ==========================================================================
(t/deftest create-token-theme
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/create-token-theme (ctob/make-token-theme :id theme-id
:name "Theme B"
:group "default"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
theme' (ctob/get-theme tokens-lib' theme-id)]
(t/testing "Theme has been created"
(t/is (some? theme'))
(t/is (= "Theme B" (:name theme'))))))))))
(t/deftest create-token-theme-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/create-token-theme (ctob/make-token-theme :id theme-id
:name "Theme A"
:group "default"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest update-token-theme
(t/async
done
(let [theme-id (uuid/next)
file (setup-file-with-token-lib-and-theme theme-id)
store (ths/setup-store file)
events [(dwtl/update-token-theme theme-id
(ctob/make-token-theme :id theme-id
:name "Theme A renamed"
:group "default"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
theme' (ctob/get-theme tokens-lib' theme-id)]
(t/testing "Theme has been renamed"
(t/is (= "Theme A renamed" (:name theme'))))))))))
(t/deftest update-token-theme-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/update-token-theme theme-id
(ctob/make-token-theme :id theme-id
:name "Theme A"
:group "default"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest set-token-theme-active
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/create-token-theme (ctob/make-token-theme :id theme-id
:name "Theme B"
:group "default"))
(dwtl/set-token-theme-active theme-id true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-status (ctho/get-tokens-status file')]
(t/testing "Theme has been activated"
(t/is (ctos/theme-active? tokens-status theme-id)))))))))
(t/deftest set-token-theme-active-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/set-token-theme-active theme-id true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest toggle-token-theme-active
(t/async
done
(let [theme-id (uuid/next)
file (setup-file-with-token-lib-and-theme theme-id)
store (ths/setup-store file)
events [(dwtl/toggle-token-theme-active theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-status (ctho/get-tokens-status file')]
(t/testing "Theme has been deactivated"
(t/is (not (ctos/theme-active? tokens-status theme-id))))))))))
(t/deftest toggle-token-theme-active-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/toggle-token-theme-active theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest delete-token-theme
(t/async
done
(let [theme-id (uuid/next)
file (setup-file-with-token-lib-and-theme theme-id)
store (ths/setup-store file)
events [(dwtl/delete-token-theme theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
theme' (ctob/get-theme tokens-lib' theme-id)]
(t/testing "Theme has been deleted"
(t/is (nil? theme')))))))))
(t/deftest delete-token-theme-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (uuid/next)
events [(dwtl/delete-token-theme theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
;; ==========================================================================
;; Token Set Operations
;; ==========================================================================
(t/deftest set-enabled-token-set
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
events [(dwtl/set-enabled-token-set (cthi/id :test-token-set) true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-status (ctho/get-tokens-status file')]
(t/testing "Set has been enabled"
(t/is (ctos/set-active? tokens-status (cthi/id :test-token-set))))))))))
(t/deftest set-enabled-token-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/set-enabled-token-set (uuid/next) true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest toggle-token-set
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
events [(dwtl/toggle-token-set (cthi/id :test-token-set))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-status (ctho/get-tokens-status file')]
(t/testing "Set has been toggled (deactivated)"
(t/is (not (ctos/set-active? tokens-status (cthi/id :test-token-set)))))))))))
(t/deftest toggle-token-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/toggle-token-set (uuid/next))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest toggle-token-set-group
(t/async
done
(let [file (ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "group/set-a"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b)
:name "group/set-b")))
:status-fn #(ctos/set-tokens-status % #{} #{}))
store (ths/setup-store file)
events [(dwtl/toggle-token-set-group ["group"])]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-status (ctho/get-tokens-status file')]
(t/testing "Set group has been toggled (activated)"
(t/is (ctos/set-active? tokens-status (cthi/id :set-a)))
(t/is (ctos/set-active? tokens-status (cthi/id :set-b))))))))))
(t/deftest toggle-token-set-group-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/toggle-token-set-group ["group"])]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest delete-token-set-group
(t/async
done
(let [file (ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "group/set-a"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b)
:name "group/set-b"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-c)
:name "other/set-c"))))
store (ths/setup-store file)
events [(dwtl/delete-token-set-group ["group"])]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')]
(t/testing "Group sets deleted, other set remains"
(t/is (nil? (ctob/get-set-by-name tokens-lib' "group/set-a")))
(t/is (nil? (ctob/get-set-by-name tokens-lib' "group/set-b")))
(t/is (some? (ctob/get-set-by-name tokens-lib' "other/set-c"))))))))))
(t/deftest delete-token-set-group-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/delete-token-set-group ["group"])]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest rename-token-set-group
(t/async
done
(let [file (ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(ctob/add-set % (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "old-group/set-a")))
store (ths/setup-store file)
events [(dwtl/rename-token-set-group ["old-group"] "new-group")]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')]
(t/testing "Set group has been renamed"
(t/is (some? (ctob/get-set-by-name tokens-lib' "new-group/set-a")))
(t/is (nil? (ctob/get-set-by-name tokens-lib' "old-group/set-a"))))))))))
(t/deftest rename-token-set-group-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/rename-token-set-group ["old-group"] "new-group")]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
;; ==========================================================================
;; Drop Operations
;; ==========================================================================
(t/deftest drop-token-set-group
(t/async
done
(let [file (ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "foo/foo"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b)
:name "bar/bar"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-c)
:name "baz/baz"))))
store (ths/setup-store file)
events [(dwtl/drop-token-set-group {:from-index 2
:to-index 0
:position :top})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
sets' (ctob/get-set-names tokens-lib')]
(t/testing "Set groups have been reordered"
(t/is (= ["bar/bar" "foo/foo" "baz/baz"] (vec sets'))))))))))
(t/deftest drop-token-set-group-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/drop-token-set-group {:from-index 0
:to-index 1
:position :top})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest drop-token-set
(t/async
done
(let [file (ctho/sample-file-with-tokens
:file-id :file-1
:page-label :page-1
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "foo"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b)
:name "bar"))
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-c)
:name "baz"))))
store (ths/setup-store file)
events [(dwtl/drop-token-set {:from-index 0
:to-index 2
:position :bot})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
sets' (ctob/get-set-names tokens-lib')]
(t/testing "Token sets have been reordered"
(t/is (= ["bar" "baz" "foo"] (vec sets'))))))))))
(t/deftest drop-token-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/drop-token-set {:from-index 0
:to-index 1
:position :top})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
;; ==========================================================================
;; Token CRUD
;; ==========================================================================
(t/deftest create-token-in-set
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
token-id (cthi/new-id! :color.primary)
events [(dwtl/create-token (cthi/id :test-token-set)
(ctob/make-token :id token-id
:name "color.primary"
:type :color
:value "#000000"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
token (ctob/get-token tokens-lib' (cthi/id :test-token-set)
(cthi/id :color.primary))]
(t/testing "Token has been created in set"
(t/is (some? token)))))))))
(t/deftest create-token-no-set
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
token-id (cthi/new-id! :color.primary)
events [(dwtl/create-token (ctob/make-token :id token-id
:name "color.primary"
:type :color
:value "#000000"))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')]
(t/testing "Global set has been created with the token"
(t/is (some? tokens-lib'))
(t/is (some? (ctob/get-set-by-name tokens-lib' "Global"))))))))))
(t/deftest update-token
(t/async
done
(let [file (setup-file-with-token-lib-and-token)
store (ths/setup-store file)
events [(dwtl/update-token (cthi/id :test-token-set)
(cthi/id :color.primary)
{:value "#ffffff"
:name "color.primary.updated"})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
token' (ctob/get-token tokens-lib' (cthi/id :test-token-set)
(cthi/id :color.primary))]
(t/testing "Token has been updated"
(t/is (= "color.primary.updated" (:name token')))
(t/is (= "#ffffff" (:value token'))))))))))
(t/deftest update-token-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/update-token (uuid/next) (uuid/next) {:value "#ffffff"})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest delete-token
(t/async
done
(let [file (setup-file-with-token-lib-and-token)
store (ths/setup-store file)
events [(dwtl/delete-token (cthi/id :test-token-set)
(cthi/id :color.primary))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
token' (ctob/get-token tokens-lib' (cthi/id :test-token-set)
(cthi/id :color.primary))]
(t/testing "Token has been deleted"
(t/is (nil? token')))))))))
(t/deftest delete-token-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/delete-token (uuid/next) (uuid/next))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
(t/deftest duplicate-token
(t/async
done
(let [file (setup-file-with-token-lib-and-token)
store (ths/setup-store file)
events [(dwtl/set-selected-token-set-id (cthi/id :test-token-set))
(dwtl/duplicate-token (cthi/id :color.primary))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
tokens (ctob/get-tokens tokens-lib' (cthi/id :test-token-set))]
(t/testing "Token has been duplicated"
(t/is (= 2 (count tokens))))))))))
(t/deftest duplicate-token-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/duplicate-token (uuid/next))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file-data (-> (ths/get-file-from-state new-state) :data)]
(t/testing "No crash when file has no tokens-lib"
(t/is (some? file-data)))))))))
;; ==========================================================================
;; Import Tokens Lib
;; ==========================================================================
(t/deftest import-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
new-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "Imported Set")))
events [(dwtl/import-tokens-lib new-lib)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')]
(t/testing "Tokens lib has been imported"
(t/is (some? tokens-lib'))
(t/is (some? (ctob/get-set-by-name tokens-lib' "Imported Set"))))))))))
(t/deftest import-tokens-lib-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
new-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "Imported Set")))
events [(dwtl/import-tokens-lib new-lib)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')]
(t/testing "Tokens lib has been imported into file without existing lib"
(t/is (some? tokens-lib'))
(t/is (some? (ctob/get-set-by-name tokens-lib' "Imported Set"))))))))))

View File

@ -13,6 +13,7 @@
[app.common.test-helpers.tokens :as ctht] [app.common.test-helpers.tokens :as ctht]
[app.common.types.token :as cto] [app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.remapping :as dwtr] [app.main.data.workspace.tokens.remapping :as dwtr]
[cljs.test :as t :include-macros true])) [cljs.test :as t :include-macros true]))
@ -27,19 +28,20 @@
:name "color.secondary" :name "color.secondary"
:value "{color.primary}" :value "{color.primary}"
:type :color}] :type :color}]
(-> (cthf/sample-file :file-1 :page-label :page-1) (-> (ctht/sample-file-with-tokens
(ctho/add-rect :rect-1) :lib-fn #(-> %
(ctho/add-rect :rect-2)
(assoc-in [:data :tokens-lib]
(-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
(ctob/set-active-themes #{"/Theme A"})
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "Set A")) :name "Set A"))
(ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :theme-a)
:name "Theme A"
:sets #{"Set A"}))
(ctob/add-token (cthi/id :set-a) (ctob/add-token (cthi/id :set-a)
(ctob/make-token color-token)) (ctob/make-token color-token))
(ctob/add-token (cthi/id :set-a) (ctob/add-token (cthi/id :set-a)
(ctob/make-token alias-token)))) (ctob/make-token alias-token)))
:status-fn #(ctos/set-tokens-status % #{(cthi/id :theme-a)} #{(cthi/id :set-a)}))
(ctho/add-rect :rect-1)
(ctho/add-rect :rect-2)
;; Apply the token to rect-1 ;; Apply the token to rect-1
(ctht/apply-token-to-shape :rect-1 "color.primary" [:fill] [:fill] "#FF0000")))) (ctht/apply-token-to-shape :rect-1 "color.primary" [:fill] [:fill] "#FF0000"))))
@ -132,22 +134,23 @@
:name "color.secondary" :name "color.secondary"
:value "#00FF00" :value "#00FF00"
:type :color}] :type :color}]
(-> (cthf/sample-file :file-1 :page-label :page-1) (-> (ctht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "Set A"))
(ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :theme-a)
:name "Theme A"
:sets #{"Set A"}))
(ctob/add-token (cthi/id :set-a)
(ctob/make-token color-primary))
(ctob/add-token (cthi/id :set-a)
(ctob/make-token color-secondary)))
:status-fn #(ctos/set-tokens-status % #{(cthi/id :theme-a)} #{(cthi/id :set-a)}))
(ctho/add-simple-component-with-copy :component1 (ctho/add-simple-component-with-copy :component1
:main-root :main-root
:main-child :main-child
:copy-root :copy-root
:copy-root-params {:children-labels [:copy-child]}) :copy-root-params {:children-labels [:copy-child]})
(assoc-in [:data :tokens-lib]
(-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
(ctob/set-active-themes #{"/Theme A"})
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
:name "Set A"))
(ctob/add-token (cthi/id :set-a)
(ctob/make-token color-primary))
(ctob/add-token (cthi/id :set-a)
(ctob/make-token color-secondary))))
(ctht/apply-token-to-shape :main-child "color.primary" [:fill] [:fill] "#FF0000") (ctht/apply-token-to-shape :main-child "color.primary" [:fill] [:fill] "#FF0000")
(ctht/apply-token-to-shape :copy-child "color.primary" [:fill] [:fill] "#FF0000")))) (ctht/apply-token-to-shape :copy-child "color.primary" [:fill] [:fill] "#FF0000"))))

View File

@ -0,0 +1,118 @@
(ns frontend-tests.tokens.logic.tokens-status-test
(:require
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.tokens :as tho]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.tokens.helpers.state :as tohs]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
(defn- setup-file
"Create a file with several token themes and token sets, some active and
some inactive. Returns the file map with :tokens-lib and :tokens-status
in its :data."
[]
(-> (tho/sample-file-with-tokens
:lib-fn #(-> %
;; Add token sets
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-active)
:name "set-active"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-inactive)
:name "set-inactive"))
;; Add themes
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-active)
:name "theme-active"
:group "group-1"
:sets #{"set-active"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-inactive)
:name "theme-inactive"
:group "group-1"
:sets #{"set-inactive"})))
:status-fn #(-> %
(ctos/set-tokens-status #{(thi/id :theme-active)}
#{(thi/id :set-active)})))))
(defn- get-tokens-status [file]
(get-in file [:data :tokens-status]))
(t/deftest test-set-token-theme-active-deactivate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-active)
set-id (thi/id :set-active)
events [(dwtl/set-token-theme-active theme-id false)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (not (ctos/theme-active? status theme-id))
"theme should be inactive after deactivation")
(t/is (not (ctos/set-active? status set-id))
"set should be inactive when its theme is deactivated")))))))
(t/deftest test-set-token-theme-active-activate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-inactive)
set-id (thi/id :set-inactive)
events [(dwtl/set-token-theme-active theme-id true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (ctos/theme-active? status theme-id)
"theme should be active after activation")
(t/is (ctos/set-active? status set-id)
"set should be active when its theme is activated")))))))
(t/deftest test-toggle-token-theme-active-deactivate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-active)
set-id (thi/id :set-active)
events [(dwtl/toggle-token-theme-active theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (not (ctos/theme-active? status theme-id))
"active theme should become inactive after toggle")
(t/is (not (ctos/set-active? status set-id))
"set should be inactive when its theme is toggled off")))))))
(t/deftest test-toggle-token-theme-active-activate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-inactive)
set-id (thi/id :set-inactive)
events [(dwtl/toggle-token-theme-active theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (ctos/theme-active? status theme-id)
"inactive theme should become active after toggle")
(t/is (ctos/set-active? status set-id)
"set should be active when its theme is toggled on")))))))

View File

@ -10,8 +10,10 @@
[app.common.test-helpers.files :as cthf] [app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths] [app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.text :as txt] [app.common.types.text :as txt]
[app.common.types.tokens-lib :as ctob] [app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.remapping :as remap] [app.main.data.workspace.tokens.remapping :as remap]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths] [frontend-tests.helpers.state :as ths]
@ -31,17 +33,22 @@
(defn- attach-token-set [file tokens] (defn- attach-token-set [file tokens]
(let [set-id (cthi/new-id! :token-set) (let [set-id (cthi/new-id! :token-set)
tokens-lib (reduce theme-id (cthi/new-id! :theme)]
(fn [lib token] (-> file
(ctob/add-token lib set-id (ctob/make-token token))) (ctht/add-tokens-lib)
(-> (ctob/make-tokens-lib) (ctht/update-tokens-lib
(ctob/add-set (ctob/make-token-set :id set-id #(reduce
:name token-set-name)) (fn [lib token]
(ctob/add-theme (ctob/make-token-theme :name token-theme-name (ctob/add-token lib set-id (ctob/make-token token)))
:sets #{token-set-name})) (-> %
(ctob/set-active-themes #{(str "/" token-theme-name)})) (ctob/add-set (ctob/make-token-set :id set-id
tokens)] :name token-set-name))
(assoc-in file [:data :tokens-lib] tokens-lib))) (ctob/add-theme (ctob/make-token-theme :id theme-id
:name token-theme-name
:sets #{token-set-name})))
tokens))
(ctht/update-tokens-status
#(ctos/set-tokens-status % #{theme-id} #{set-id})))))
(defn- tokens-lib [file] (defn- tokens-lib [file]
(get-in file [:data :tokens-lib])) (get-in file [:data :tokens-lib]))

View File

@ -7712,14 +7712,22 @@ msgid_plural "workspace.libraries.components"
msgstr[0] "1 component" msgstr[0] "1 component"
msgstr[1] "%s components" msgstr[1] "%s components"
#: src/app/main/ui/workspace/libraries.cljs:367 #: src/app/main/ui/workspace/libraries.cljs:541
msgid "workspace.libraries.connected-to" msgid "workspace.libraries.connected-libraries"
msgstr "Connected to" msgstr "Connected library"
#: src/app/main/ui/workspace/libraries.cljs:550
msgid "workspace.libraries.connected-through"
msgstr "Connected through"
#: src/app/main/ui/workspace/libraries.cljs:432 #: src/app/main/ui/workspace/libraries.cljs:432
msgid "workspace.libraries.empty.add-some" msgid "workspace.libraries.empty.add-some"
msgstr "Or add some of these to try:" msgstr "Or add some of these to try:"
#: src/app/main/ui/workspace/libraries.cljs:600
msgid "workspace.libraries.empty.no-connected-libraries"
msgstr "No connected libraries yet."
#: src/app/main/ui/workspace/libraries.cljs:426 #: src/app/main/ui/workspace/libraries.cljs:426
msgid "workspace.libraries.empty.no-libraries" msgid "workspace.libraries.empty.no-libraries"
msgstr "There are no Shared Libraries at your team, you can look for" msgstr "There are no Shared Libraries at your team, you can look for"
@ -7732,6 +7740,14 @@ msgstr "some templates in here"
msgid "workspace.libraries.file-library" msgid "workspace.libraries.file-library"
msgstr "File library" msgstr "File library"
#: src/app/main/ui/workspace/libraries.cljs:336
msgid "workspace.libraries.tokens-source"
msgstr "Tokens source"
#: src/app/main/ui/workspace/libraries.cljs:336
msgid "workspace.libraries.set-as-tokens-source"
msgstr "Set as tokens source"
#: src/app/main/ui/workspace/libraries.cljs:100, src/app/main/ui/workspace/libraries.cljs:124 #: src/app/main/ui/workspace/libraries.cljs:100, src/app/main/ui/workspace/libraries.cljs:124
msgid "workspace.libraries.graphics" msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics" msgid_plural "workspace.libraries.graphics"
@ -7780,6 +7796,10 @@ msgstr "Search shared libraries"
msgid "workspace.libraries.shared-libraries" msgid "workspace.libraries.shared-libraries"
msgstr "SHARED LIBRARIES" msgstr "SHARED LIBRARIES"
#: src/app/main/ui/workspace/libraries.cljs:311
msgid "workspace.libraries.shared-library-added"
msgstr "Added"
#: src/app/main/ui/workspace/libraries.cljs:414 #: src/app/main/ui/workspace/libraries.cljs:414
msgid "workspace.libraries.shared-library-btn" msgid "workspace.libraries.shared-library-btn"
msgstr "Connect library" msgstr "Connect library"
@ -7825,6 +7845,24 @@ msgid_plural "workspace.libraries.typography"
msgstr[0] "1 typography" msgstr[0] "1 typography"
msgstr[1] "%s typographies" msgstr[1] "%s typographies"
#: src/app/main/ui/workspace/libraries.cljs:106, src/app/main/ui/workspace/libraries.cljs:150
msgid "workspace.libraries.tokens"
msgid_plural "workspace.libraries.tokens"
msgstr[0] "1 token"
msgstr[1] "%s tokens"
#: src/app/main/ui/workspace/libraries.cljs:106, src/app/main/ui/workspace/libraries.cljs:150
msgid "workspace.libraries.token-sets"
msgid_plural "workspace.libraries.token-sets"
msgstr[0] "1 set"
msgstr[1] "%s sets"
#: src/app/main/ui/workspace/libraries.cljs:106, src/app/main/ui/workspace/libraries.cljs:150
msgid "workspace.libraries.token-themes"
msgid_plural "workspace.libraries.token-themes"
msgstr[0] "1 theme"
msgstr[1] "%s themes"
#: src/app/main/ui/workspace/libraries.cljs:381 #: src/app/main/ui/workspace/libraries.cljs:381
msgid "workspace.libraries.unlink-library-btn" msgid "workspace.libraries.unlink-library-btn"
msgstr "Disconnect library" msgstr "Disconnect library"
@ -10437,6 +10475,32 @@ msgstr "Use a reference"
msgid "workspace.tokens.value-not-valid" msgid "workspace.tokens.value-not-valid"
msgstr "The value is not valid" msgstr "The value is not valid"
#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:102
msgid "workspace.tokens.current-tokens-source-tooltip"
msgstr "This is the current Tokens source"
#: src/app/main/data/workspace/libraries.cljs:847
msgid "workspace.tokens.notifications.source-sets-or-themes-updated"
msgstr "Tokens source sets or themes have been modified"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:74
msgid "workspace.tokens.source"
msgstr "Source:"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:79
msgid "workspace.tokens.this-file"
msgstr "This file"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:96
msgid "workspace.tokens.change-token-source"
msgstr "Change token source"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:102
msgid "workspace.tokens.open-source-new-tab"
msgstr "Open tokens' source in a new tab"
#: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs #: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs
#, unused #, unused
msgid "workspace.tokens.warning-name-change" msgid "workspace.tokens.warning-name-change"

View File

@ -7546,10 +7546,22 @@ msgid_plural "workspace.libraries.components"
msgstr[0] "1 componente" msgstr[0] "1 componente"
msgstr[1] "%s componentes" msgstr[1] "%s componentes"
#: src/app/main/ui/workspace/libraries.cljs:541
msgid "workspace.libraries.connected-libraries"
msgstr "Biblioteca conectada"
#: src/app/main/ui/workspace/libraries.cljs:550
msgid "workspace.libraries.connected-through"
msgstr "Conectada mediante"
#: src/app/main/ui/workspace/libraries.cljs:367 #: src/app/main/ui/workspace/libraries.cljs:367
msgid "workspace.libraries.connected-to" msgid "workspace.libraries.connected-to"
msgstr "Conectada con" msgstr "Conectada con"
#: src/app/main/ui/workspace/libraries.cljs:600
msgid "workspace.libraries.empty.no-connected-libraries"
msgstr "Aun no hay bibliotecas conectadas."
#: src/app/main/ui/workspace/libraries.cljs:432 #: src/app/main/ui/workspace/libraries.cljs:432
msgid "workspace.libraries.empty.add-some" msgid "workspace.libraries.empty.add-some"
msgstr "O añadir algunas de éstas para probar:" msgstr "O añadir algunas de éstas para probar:"
@ -7566,6 +7578,14 @@ msgstr "algunas plantillas aquí"
msgid "workspace.libraries.file-library" msgid "workspace.libraries.file-library"
msgstr "Biblioteca del archivo" msgstr "Biblioteca del archivo"
#: src/app/main/ui/workspace/libraries.cljs:336
msgid "workspace.libraries.tokens-source"
msgstr "Fuente de tokens"
#: src/app/main/ui/workspace/libraries.cljs:336
msgid "workspace.libraries.set-as-tokens-source"
msgstr "Activar fuente de tokens"
#: src/app/main/ui/workspace/libraries.cljs:100, src/app/main/ui/workspace/libraries.cljs:124 #: src/app/main/ui/workspace/libraries.cljs:100, src/app/main/ui/workspace/libraries.cljs:124
msgid "workspace.libraries.graphics" msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics" msgid_plural "workspace.libraries.graphics"
@ -7614,6 +7634,10 @@ msgstr "Buscar bibliotecas compartidas"
msgid "workspace.libraries.shared-libraries" msgid "workspace.libraries.shared-libraries"
msgstr "BIBLIOTECAS COMPARTIDAS" msgstr "BIBLIOTECAS COMPARTIDAS"
#: src/app/main/ui/workspace/libraries.cljs:311
msgid "workspace.libraries.shared-library-added"
msgstr "Añadido"
#: src/app/main/ui/workspace/libraries.cljs:414 #: src/app/main/ui/workspace/libraries.cljs:414
msgid "workspace.libraries.shared-library-btn" msgid "workspace.libraries.shared-library-btn"
msgstr "Conectar biblioteca" msgstr "Conectar biblioteca"
@ -7654,6 +7678,24 @@ msgid_plural "workspace.libraries.typography"
msgstr[0] "1 tipografía" msgstr[0] "1 tipografía"
msgstr[1] "%s tipografías" msgstr[1] "%s tipografías"
#: src/app/main/ui/workspace/libraries.cljs:106, src/app/main/ui/workspace/libraries.cljs:150
msgid "workspace.libraries.tokens"
msgid_plural "workspace.libraries.tokens"
msgstr[0] "1 token"
msgstr[1] "%s tokens"
#: src/app/main/ui/workspace/libraries.cljs:106, src/app/main/ui/workspace/libraries.cljs:150
msgid "workspace.libraries.token-sets"
msgid_plural "workspace.libraries.token-sets"
msgstr[0] "1 token set"
msgstr[1] "%s token sets"
#: src/app/main/ui/workspace/libraries.cljs:106, src/app/main/ui/workspace/libraries.cljs:150
msgid "workspace.libraries.token-themes"
msgid_plural "workspace.libraries.token-themes"
msgstr[0] "1 token theme"
msgstr[1] "%s token themes"
#: src/app/main/ui/workspace/libraries.cljs:381 #: src/app/main/ui/workspace/libraries.cljs:381
msgid "workspace.libraries.unlink-library-btn" msgid "workspace.libraries.unlink-library-btn"
msgstr "Desconectar biblioteca" msgstr "Desconectar biblioteca"
@ -10106,6 +10148,32 @@ msgstr "Usa una referencia"
msgid "workspace.tokens.value-not-valid" msgid "workspace.tokens.value-not-valid"
msgstr "El valor no es válido" msgstr "El valor no es válido"
#: src/app/main/ui/workspace/sidebar/assets/file_library.cljs:102
msgid "workspace.tokens.current-tokens-source-tooltip"
msgstr "Esta es la actual fuente de Tokens"
#: src/app/main/data/workspace/libraries.cljs:847
msgid "workspace.tokens.notifications.source-sets-or-themes-updated"
msgstr "Los sets o temas de la fuente de tokens se han modificado"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:74
msgid "workspace.tokens.source"
msgstr "Fuente:"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:79
msgid "workspace.tokens.this-file"
msgstr "Este archivo"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:96
msgid "workspace.tokens.change-token-source"
msgstr "Cambiar fuente de tokens"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:102
msgid "workspace.tokens.open-source-new-tab"
msgstr "Abrir la fuente de tokens en una pestaña nueva"
#: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs #: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs
#, unused #, unused
msgid "workspace.tokens.warning-name-change" msgid "workspace.tokens.warning-name-change"

View File

@ -6,13 +6,15 @@
<div class="panel"> <div class="panel">
<div class="panel-heading"> <div class="panel-heading">
<p class="headline-m">THEMES</p> <p class="headline-m">THEMES</p>
<button @if (isEditableTokens) {
type="button" <button
data-appearance="secondary" type="button"
(click)="addTheme()" data-appearance="secondary"
> (click)="addTheme()"
+ >
</button> +
</button>
}
</div> </div>
<ul data-handler="themes-list"> <ul data-handler="themes-list">
@ -24,20 +26,22 @@
} }
{{ theme.name }} {{ theme.name }}
</span> </span>
<button @if (isEditableTokens) {
type="button" <button
data-appearance="secondary" type="button"
(click)="renameTheme(theme.id, theme.name)" data-appearance="secondary"
> (click)="renameTheme(theme.id, theme.name)"
🖊️ >
</button> 🖊️
<button </button>
type="button" <button
data-appearance="secondary" type="button"
(click)="deleteTheme(theme.id)" data-appearance="secondary"
> (click)="deleteTheme(theme.id)"
>
</button>
</button>
}
<div class="checkbox-container"> <div class="checkbox-container">
<input <input
class="checkbox-input" class="checkbox-input"
@ -55,9 +59,15 @@
<div class="panel"> <div class="panel">
<div class="panel-heading"> <div class="panel-heading">
<p class="headline-m">SETS</p> <p class="headline-m">SETS</p>
<button type="button" data-appearance="secondary" (click)="addSet()"> @if (isEditableTokens) {
+ <button
</button> type="button"
data-appearance="secondary"
(click)="addSet()"
>
+
</button>
}
</div> </div>
<ul data-handler="sets-list"> <ul data-handler="sets-list">
@ -69,20 +79,22 @@
<span (click)="loadTokens(set.id)"> <span (click)="loadTokens(set.id)">
{{ set.name }} {{ set.name }}
</span> </span>
<button @if (isEditableTokens) {
type="button" <button
data-appearance="secondary" type="button"
(click)="renameSet(set.id, set.name)" data-appearance="secondary"
> (click)="renameSet(set.id, set.name)"
🖊️ >
</button> 🖊️
<button </button>
type="button" <button
data-appearance="secondary" type="button"
(click)="deleteSet(set.id)" data-appearance="secondary"
> (click)="deleteSet(set.id)"
>
</button>
</button>
}
<div class="checkbox-container"> <div class="checkbox-container">
<input <input
class="checkbox-input" class="checkbox-input"
@ -105,13 +117,15 @@
@for (group of tokenGroups; track group[0]) { @for (group of tokenGroups; track group[0]) {
<li class="body-m token-group"> <li class="body-m token-group">
<span>{{ group[0] }}</span> <span>{{ group[0] }}</span>
<button @if (isEditableTokens) {
type="button" <button
data-appearance="secondary" type="button"
(click)="addToken(group[0])" data-appearance="secondary"
> (click)="addToken(group[0])"
+ >
</button> +
</button>
}
</li> </li>
@for (token of group[1]; track token.id) { @for (token of group[1]; track token.id) {
<li <li
@ -125,20 +139,22 @@
> >
{{ token.name }} {{ token.name }}
</span> </span>
<button @if (isEditableTokens) {
type="button" <button
data-appearance="secondary" type="button"
(click)="renameToken(token.id, token.name)" data-appearance="secondary"
> (click)="renameToken(token.id, token.name)"
🖊️ >
</button> 🖊️
<button </button>
type="button" <button
data-appearance="secondary" type="button"
(click)="deleteToken(token.id)" data-appearance="secondary"
> (click)="deleteToken(token.id)"
>
</button>
</button>
}
</li> </li>
} }
} }

View File

@ -59,6 +59,7 @@ export class AppComponent {
), ),
); );
public isEditableTokens: boolean = false;
public themes: TokenTheme[] = []; public themes: TokenTheme[] = [];
public sets: TokenSet[] = []; public sets: TokenSet[] = [];
public tokenGroups: TokensGroup[] = []; public tokenGroups: TokensGroup[] = [];
@ -72,6 +73,8 @@ export class AppComponent {
this.#setSets(event.data.setsData); this.#setSets(event.data.setsData);
} else if (event.data.type === 'set-tokens') { } else if (event.data.type === 'set-tokens') {
this.#setTokens(event.data.tokenGroupsData); this.#setTokens(event.data.tokenGroupsData);
} else if (event.data.type === 'set-is-editable-tokens') {
this.#setIsEditableTokens(event.data.isEditableTokens);
} }
}); });
} }
@ -268,6 +271,10 @@ export class AppComponent {
parent.postMessage(message, '*'); parent.postMessage(message, '*');
} }
#setIsEditableTokens(isEditableTokens: boolean) {
this.isEditableTokens = isEditableTokens;
}
#setThemes(themes: TokenTheme[]) { #setThemes(themes: TokenTheme[]) {
this.themes = themes; this.themes = themes;
} }

View File

@ -58,6 +58,14 @@ function sendMessage(message: PluginMessageEvent) {
function loadLibrary() { function loadLibrary() {
const tokensCatalog = penpot.library.local.tokens; const tokensCatalog = penpot.library.local.tokens;
const isEditableTokens = tokensCatalog.isEditableTokens;
penpot.ui.sendMessage({
source: 'penpot',
type: 'set-is-editable-tokens',
isEditableTokens,
});
const themes = tokensCatalog.themes; const themes = tokensCatalog.themes;
const themesData = themes.map((theme) => { const themesData = themes.map((theme) => {

View File

@ -5220,6 +5220,14 @@ export type Token =
* Themes. * Themes.
*/ */
export interface TokenCatalog { export interface TokenCatalog {
/**
* Returns true if the tokens can be edited (it can't
* if the tokens of the file are in an external library).
* Note that even in this case, Themes and Sets status still
* can be changed and tokens can be applied to shapes.
*/
readonly isEditableTokens: boolean;
/** /**
* The list of themes in this catalog, in creation order. * The list of themes in this catalog, in creation order.
*/ */