Compare commits

..
Author SHA1 Message Date
Andrés Moya 67bc748ebd wip 2026-09-08 10:34:22 +02:00
Andrés Moya 86e5b43a5c 🔧 Add more tests for tokens in external libs 2026-09-08 10:34:18 +02:00
Eva Marco fb3cf431c1 🎉 Add changes notification (#11476)
* 🎉 Add changes notification

* ♻️ Change fn names
2026-09-08 10:24:39 +02:00
Eva Marco dcf90f9e28 🐛 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
2026-09-08 10:24:38 +02:00
Andrés Moya b101a16c70 🐛 Propagate tokens after synchronizing tokens in ext library 2026-09-08 10:24:38 +02:00
Andrés Moya b3a946eaeb 🐛 Propagate tokens after creating new instances 2026-09-08 10:24:38 +02:00
Andrés Moya 6a34947586 🐛 Propagate tokens after synchronizing components in ext library 2026-09-08 10:24:38 +02:00
Andrés Moya b0e1e019bb 🎉 Make ui changes optional with config flag 2026-09-08 10:24:38 +02:00
Andrés Moya 46a4a1774d 🔧 Validate and repair missing tokens status 2026-09-08 10:24:38 +02:00
Andrés Moya 6e339ba217 🔧 Add integration tests for tokens in external libs 2026-09-08 10:24:38 +02:00
Andrés Moya 3e6150ab92 🐛 Fix some integration tests 2026-09-08 10:24:38 +02:00
Andrés Moya 8d36565712 🔧 Add harder checks for nil or empty values in everything 2026-09-08 10:24:38 +02:00
Andrés Moya 91dbb873ad 🔧 Refactor tokens source calculations 2026-09-08 10:24:38 +02:00
Eva Marco 4b163f94d4 🎉 Layout updates 2026-09-08 10:24:38 +02:00
Andrés Moya 0d7cf045a9 🐛 Propagate changes of token values in external library 2026-09-08 10:24:38 +02:00
Andrés Moya d56a460b3c 🔧 Fix integration tests 2026-09-08 10:24:38 +02:00
Andrés Moya 12fc08d7f5 🎉 Show source info in sidebar 2026-09-08 10:24:38 +02:00
Andrés Moya d1c3135f8d 🎉 Show tokens in library summaries 2026-09-08 10:24:37 +02:00
Andrés Moya 5301888083 🎉 Make tokens library readonly when it's in an external file 2026-09-08 10:24:37 +02:00
Andrés Moya 105e97bf93 🎉 Allow the user to choose the tokens-source of a file 2026-09-08 10:24:37 +02:00
Andrés Moya 71d144f65b 🔧 Rename :tokens-file to :tokens-source 2026-09-08 10:24:37 +02:00
Andrés Moya 84507a444f 🔧 Normalize status helper functions and access token sets by id 2026-09-08 10:24:37 +02:00
Andrés Moya c358e58e33 🔧 Make all status operations use the new data structure 2026-09-08 10:24:37 +02:00
Andrés Moya ca1b5541c0 🔧 Add separated TokenStatus to store status apart of TokensLib 2026-09-08 10:24:34 +02:00
Andrés Moya 37f27df9d8 🔧 Refactor tokens-lib initialization 2026-09-08 10:22:56 +02:00
Andrés Moya ca619a4c85 Auto link tokens when adding external libraries (provisional) 2026-09-08 10:22:56 +02:00
125 changed files with 7093 additions and 3180 deletions

No files matched your search

+12 -2
View File
@@ -14,6 +14,7 @@
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.files.stats :as cfs]
[app.common.files.tokens :as cfo]
[app.common.logging :as l]
[app.common.schema :as sm]
[app.common.schema.desc-js-like :as-alias smdj]
@@ -21,6 +22,7 @@
[app.common.transit :as t]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.common.uri :as uri]
[app.config :as cf]
[app.db :as db]
@@ -520,12 +522,20 @@
components-sample
(-> (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
:variants {:count (count variant-ids)}
: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
(ct/duration {:days 30}))
@@ -106,7 +106,6 @@
:set-token
:set-token-set
:set-token-theme
:set-active-token-themes
:rename-token-set-group
:move-token-set
:move-token-set-group
+97 -55
View File
@@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.schema :as sm]
@@ -29,6 +30,7 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as ctt]
[app.common.types.variant :as ctv]
@@ -398,10 +400,11 @@
[:id ::sm/uuid]
[:attrs [:maybe ctob/schema:token-theme-attrs]]]]
[:set-active-token-themes
[:map {:title "SetActiveTokenThemes"}
[:type [:= :set-active-token-themes]]
[:theme-paths [:set :string]]]]
[:set-tokens-status
[:map {:title "SetTokensStatus"}
[:type [:= :set-tokens-status]]
[:theme-ids [:set ::sm/uuid]]
[:set-ids [:set ::sm/uuid]]]]
[:rename-token-set-group
[:map {:title "RenameTokenSetGroup"}
@@ -428,7 +431,13 @@
[:set-base-font-size
[:map {:title "ModBaseFontSize"}
[: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
[:sequential {:gen/max 5 :gen/min 1} schema:change])
@@ -992,77 +1001,73 @@
(defmethod process-change :set-token
[data {:keys [set-id token-id attrs]}]
(update data :tokens-lib
(fn [lib]
(let [lib' (ctob/ensure-tokens-lib lib)]
(cond
(not attrs)
(ctob/delete-token lib' set-id token-id)
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-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))
(not (ctob/get-token lib' set-id token-id))
(ctob/add-token lib' set-id (ctob/make-token attrs))
:else
(ctob/update-token lib' set-id token-id
(fn [prev-token]
(ctob/make-token (merge prev-token attrs)))))))))
:else
(ctob/update-token lib' set-id token-id
(fn [prev-token]
(ctob/make-token (merge prev-token attrs)))))))))
(defmethod process-change :set-token-set
[data {:keys [id attrs]}]
(update data :tokens-lib
(fn [lib]
(let [lib' (ctob/ensure-tokens-lib lib)]
(cond
(not attrs)
(ctob/delete-set lib' id)
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-set lib' id)
(not (ctob/get-set lib' id))
(ctob/add-set lib' (ctob/make-token-set attrs))
(not (ctob/get-set lib' id))
(ctob/add-set lib' (ctob/make-token-set attrs))
:else
(ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs))))))))
:else
(ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs))))))))
(defmethod process-change :set-token-theme
[data {:keys [id attrs]}]
(update data :tokens-lib
(fn [lib]
(let [lib' (ctob/ensure-tokens-lib lib)]
(cond
(not attrs)
(ctob/delete-theme lib' id)
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-theme lib' id)
(not (ctob/get-theme lib' id))
(ctob/add-theme lib' (ctob/make-token-theme attrs))
(not (ctob/get-theme lib' id))
(ctob/add-theme lib' (ctob/make-token-theme attrs))
:else
(ctob/update-theme lib'
id
(fn [prev-token-theme]
(ctob/make-token-theme (merge prev-token-theme attrs)))))))))
:else
(ctob/update-theme lib'
id
(fn [prev-token-theme]
(ctob/make-token-theme (merge prev-token-theme attrs)))))))))
(defmethod process-change :set-active-token-themes
[data {:keys [theme-paths]}]
(update data :tokens-lib #(-> % (ctob/ensure-tokens-lib)
(ctob/set-active-themes theme-paths))))
(defmethod process-change :set-tokens-status
[data {:keys [theme-ids set-ids]}]
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-status ctos/set-tokens-status theme-ids set-ids)))
(defmethod process-change :rename-token-set-group
[data {:keys [set-group-path set-group-fname]}]
(update data :tokens-lib (fn [lib]
(-> lib
(ctob/ensure-tokens-lib)
(ctob/rename-set-group set-group-path set-group-fname)))))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/rename-set-group set-group-path set-group-fname)))
(defmethod process-change :move-token-set
[data {:keys [from-path to-path before-path before-group] :as changes}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/move-set from-path to-path before-path before-group))))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/move-set from-path to-path before-path before-group)))
(defmethod process-change :move-token-set-group
[data {:keys [from-path to-path before-path before-group]}]
(update data :tokens-lib #(-> %
(ctob/ensure-tokens-lib)
(ctob/move-set-group from-path to-path before-path before-group))))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/move-set-group from-path to-path before-path before-group)))
;; === Design Tokens configuration
@@ -1070,10 +1075,13 @@
[data {:keys [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
(def decode-shape-attrs
(def decode-shape-attrs
(sm/decoder cts/schema:shape-attrs sm/json-transformer))
(defmethod process-operation :assign
@@ -1242,3 +1250,37 @@
(defmethod frames-changed :default
[_ _]
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))
@@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.files.changes :as cfc]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
@@ -22,6 +23,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]))
@@ -987,7 +989,7 @@
[changes tokens-lib]
(assert-library! 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
(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})
@@ -996,7 +998,7 @@
(defn set-token [changes set-id token-id token]
(assert-library! 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))]
(-> changes
(update :redo-changes conj {:type :set-token
@@ -1013,7 +1015,7 @@
[changes id token-set]
(assert-library! 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))]
(-> changes
(update :redo-changes conj {:type :set-token-set
@@ -1028,7 +1030,7 @@
[changes id new-name]
(assert-library! 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))]
(-> changes
(update :redo-changes conj {:type :set-token-set
@@ -1042,7 +1044,7 @@
(defn set-token-theme [changes id theme]
(assert-library! 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))]
(-> changes
(update :redo-changes conj {:type :set-token-theme
@@ -1053,17 +1055,21 @@
:attrs (datafy prev-theme)})
(apply-changes-local))))
(defn set-active-token-themes
[changes active-theme-paths]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-active-theme-paths (d/nilv (some-> (get library-data :tokens-lib)
(ctob/get-active-theme-paths))
#{})]
(-> changes
(update :redo-changes conj {:type :set-active-token-themes :theme-paths active-theme-paths})
(update :undo-changes conj {:type :set-active-token-themes :theme-paths prev-active-theme-paths})
(apply-changes-local))))
(defn set-tokens-status
([changes tokens-status]
(assert-library! changes)
(assert (or (ctos/tokens-status? tokens-status)
(nil? tokens-status)))
(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) #{})
library-data (::library-data (meta changes))
prev-tokens-status (cfo/get-tokens-status library-data)
prev-theme-ids (if prev-tokens-status (ctos/get-active-theme-ids prev-tokens-status) #{})
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
[changes set-group-path set-group-fname]
@@ -1117,6 +1123,21 @@
:base-font-size previous-font-size})
(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
(defn reorder-children
+10 -1
View File
@@ -13,6 +13,7 @@
[app.common.files.comp-processors :as cfcp]
[app.common.files.defaults :as cfd]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
@@ -1998,6 +1999,13 @@
(update :pages-index 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
(into (d/ordered-set)
["legacy-2"
@@ -2081,4 +2089,5 @@
"0023-repair-token-themes-with-inexistent-sets"
"0024b-fix-stroke-cap-placement"
"0025-repair-empty-text-content"
"0026-fix-svg-raw-shapes-uuids"]))
"0026-fix-svg-raw-shapes-uuids"
"0027-separate-tokens-status"]))
+11
View File
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.logging :as log]
[app.common.path-names :as cpn]
[app.common.types.component :as ctk]
@@ -783,6 +784,16 @@
(pcb/with-file-data file-data)
(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
[_ error file _]
(log/error :hint "Unknown error code, don't know how to repair" :code (:code error))
+324 -1
View File
@@ -9,13 +9,18 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.i18n :refer [tr]]
[app.common.logging :as log]
[app.common.schema :as sm]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.set :as set]
[cuerdas.core :as str]
[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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -165,7 +170,8 @@
(some (fn [[token-name _]]
(not (ctob/token-name-path-exists? token-name tokens-tree)))
new-tokens))))]])
(defn find-refs [value]
(defn- find-refs [value]
(cond
(string? value)
(cto/find-token-value-references value)
@@ -344,6 +350,8 @@
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Token
(def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value."
@@ -415,3 +423,318 @@
;; FIXME: this should be precalculated ?
(defn is-reference? [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)))
(defn set-tokens-status
"Replace the tokens-status inside file-data with a new one."
[file-data tokens-status]
(assert (or (nil? tokens-status) (ctos/tokens-status? tokens-status)) "expected valid tokens-status or nil")
(assoc file-data :tokens-status tokens-status))
;; 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)))
+15 -1
View File
@@ -73,7 +73,8 @@
:variant-main-bad-name
:variant-main-bad-variant-name
:variant-component-bad-name
:variant-component-bad-id})
:variant-component-bad-id
:missing-tokens-status})
(def ^:private schema:error
[:map {:title "ValidationError"}
@@ -816,6 +817,17 @@
(transient [])
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -853,6 +865,8 @@
nil
(:components data))
(check-tokens file)
(-> *errors* deref not-empty))))
(defn validate-shape
+1
View File
@@ -137,6 +137,7 @@
:token-tokenscript
:token-import-from-library
:token-typography-row
:token-lib-sync
;; Only for developtment.
:transit-readable-response
+110 -81
View File
@@ -6,84 +6,31 @@
(ns app.common.logic.tokens
(:require
[app.common.files.changes :as ch]
[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
"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`.
;; Tokens source
Use this for managing sets active state without having to modify a
user created theme (\"no themes selected\" state in the ui)."
[changes tokens-lib update-theme-fn]
(let [active-token-set-names (ctob/get-active-themes-set-names tokens-lib)
hidden-theme (ctob/get-hidden-theme tokens-lib)
hidden-theme' (-> (some-> hidden-theme
(ctob/set-sets active-token-set-names))
(update-theme-fn))]
(defn generate-set-tokens-source
"Create changes for setting the tokens source of a file to `library`,
copying the library's tokens status. If the library is nil, the tokens source
is removed and the tokens status is cleared."
[changes library]
(let [library-id (:id library)
library-tokens-status (if library
(-> library ctf/file-data cfo/get-tokens-status)
(ctos/make-tokens-status))]
(-> changes
(pcb/set-active-token-themes #{(ctob/get-theme-path hidden-theme')})
(pcb/set-token-theme (ctob/get-id hidden-theme)
hidden-theme'))))
(pcb/set-tokens-source library-id)
(pcb/set-tokens-status library-tokens-status))))
(defn generate-set-enabled-token-set
"Enable or disable a token set at `set-name` in `tokens-lib` without modifying a user theme."
[changes tokens-lib set-name enabled?]
(if enabled?
(generate-update-active-sets changes tokens-lib #(ctob/enable-set % set-name))
(generate-update-active-sets changes tokens-lib #(ctob/disable-set % set-name))))
;; Tokens lib
(defn generate-toggle-token-set
"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]
(defn- vec-starts-with? [v1 v2]
(= (subvec v1 0 (min (count v1) (count v2))) v2))
(defn- calculate-move-token-set-or-set-group
@@ -155,27 +102,109 @@
prev-before (assoc :prev-before-path (:path 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
"Create changes for dropping a token set or token set.
Throws for impossible moves."
[changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set changes params)
(if tokens-lib
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set changes params)
changes)
changes))
(defn generate-move-token-set-group
"Create changes for dropping a token set or token set group.
Throws for impossible moves"
[changes tokens-lib params]
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set-group changes params)
(if tokens-lib
(if-let [params (calculate-move-token-set-or-set-group tokens-lib params)]
(pcb/move-token-set-group changes params)
changes)
changes))
(defn generate-delete-token-set-group
"Create changes for deleting a token set group."
[changes tokens-lib path]
(let [sets (ctob/get-sets-at-path tokens-lib path)]
(reduce (fn [changes set]
(pcb/set-token-set changes (ctob/get-id set) nil))
changes
sets)))
(if tokens-lib
(let [sets (ctob/get-sets-at-path tokens-lib path)]
(reduce (fn [changes set]
(pcb/set-token-set changes (ctob/get-id set) nil))
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))
+41 -4
View File
@@ -7,6 +7,7 @@
(ns app.common.test-helpers.tokens
(:require
[app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.shapes :as ths]
[app.common.types.container :as ctn]
@@ -15,19 +16,55 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.text :as ctt]
[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]))
(defn get-tokens-source
[file]
(-> file (ctf/file-data) (cfo/get-tokens-source)))
(defn get-tokens-lib
[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
"Ensure the file has a tokens-lib and a tokens-status in its data, creating empty ones if not"
[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
"Modify the tokens-lib of a file "
[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 set-tokens-source
[file library]
(let [library-id (:id library)
library-tokens-status (if library
(-> library ctf/file-data cfo/get-tokens-status)
(ctos/make-tokens-status))]
(ctf/update-file-data
file
(fn [data]
(-> data
(cfo/set-tokens-source library-id)
(cfo/set-tokens-status library-tokens-status))))))
(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
[file set-id token-id]
+1 -1
View File
@@ -87,7 +87,7 @@
[:components {:optional true} schema:components]
[:typographies {:optional true} schema:typographies]
[: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-status {:optional true} ctos/schema:tokens-status]])
+222 -206
View File
@@ -18,6 +18,7 @@
[app.common.time :as ct]
[app.common.transit :as t]
[app.common.types.token :as cto]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.core.protocols :as cp]
[clojure.datafy :refer [datafy]]
@@ -479,11 +480,6 @@
[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]
(str set-group-prefix group-path))
@@ -547,7 +543,7 @@
"Get an ordered sequence of all sets names in the library")
(get-set-tree [_]
"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"))
(def ^:private schema:token-set-node
@@ -584,8 +580,7 @@
(disable-sets [_ set-names] "disable several sets in 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")
(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"))
(theme-matches-group-name [_ group name] "if a theme matches the given group & name"))
(def hidden-theme-id
uuid/zero)
@@ -659,10 +654,12 @@
(theme-matches-group-name [this group name]
(and (= (:group this) group)
(= (:name this) name)))
(= (:name this) name))))
(hidden-theme? [this]
(theme-matches-group-name this hidden-theme-group hidden-theme-name)))
(defn hidden-theme?
"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
[^TokenTheme obj]
@@ -755,11 +752,15 @@
(def ^:private theme-separator "/")
(defn- join-theme-path [group name]
(cpn/join-path [group name] :separator theme-separator :with-spaces? false))
(defn- join-theme-path [group name with-spaces?]
(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]
(join-theme-path (:group theme) (:name theme)))
(defn get-theme-path
([theme]
(get-theme-path theme false))
([theme with-spaces?]
(join-theme-path (:group theme) (:name theme) with-spaces?)))
(defn split-theme-path [path]
(cpn/split-group-name path
@@ -767,7 +768,7 @@
:with-spaces? false))
(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)
@@ -778,18 +779,13 @@
(delete-theme [_ id] "delete a theme in the library")
(theme-count [_] "get the total number if themes in the library")
(get-theme-tree [_] "get a nested tree of all themes in the library")
(get-theme-tree-no-hidden [_] "get a nested tree of all themes in the library except the hidden theme")
(get-themes [_] "get an ordered sequence of all themes in the library")
(get-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-by-name [_ group name] "get one theme looking for group and name")
(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"))
(get-theme-groups [_] "get a sequence of group names by order"))
(def schema:token-themes
[:and
@@ -930,18 +926,10 @@
(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")
(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-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 read-multi-set-dtcg)
@@ -1116,8 +1104,8 @@ Will return a value that matches this schema:
(->> (tree-seq d/ordered-map? vals sets)
(filter (partial instance? TokenSet))))
(get-sets-at-path [_ path]
(some->> (map add-set-path-group-prefix path)
(get-sets-at-path [_ path-vec]
(some->> (map add-set-path-group-prefix path-vec)
(get-in sets)
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet))))
@@ -1177,7 +1165,7 @@ Will return a value that matches this schema:
(d/dissoc-in [group name])))
(if same-path?
active-themes
(disj active-themes (join-theme-path group name)))))))
(disj active-themes (join-theme-path group name false)))))))
this))
(delete-theme [this id]
@@ -1186,12 +1174,15 @@ Will return a value that matches this schema:
(if theme
(TokensLib. sets
(d/dissoc-in themes [group name])
(disj active-themes (join-theme-path group name)))
(disj active-themes (join-theme-path group name false)))
this)))
(get-theme-tree [_]
themes)
(get-theme-tree-no-hidden [_]
(d/dissoc-in themes [hidden-theme-group hidden-theme-name]))
(get-theme-groups [_]
(into [] (comp
(map key)
@@ -1202,6 +1193,14 @@ Will return a value that matches this schema:
(->> (tree-seq d/ordered-map? vals themes)
(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]
(count (get-themes this)))
@@ -1212,60 +1211,12 @@ Will return a value that matches this schema:
(get-theme-by-name [_ 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
(empty-lib? [this]
(and (empty? sets)
(or (empty? themes)
(and (= (theme-count this) 1)
(get-hidden-theme this)))))
(get-theme this hidden-theme-id)))))
(set-path-exists? [_ 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)
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]
(mapcat #(vals (get-tokens- %))
(get-sets this)))
@@ -1368,6 +1267,9 @@ Will return a value that matches this schema:
(get-set set-id)
(get-tokens-)))
(get-legacy-active-themes [_]
active-themes)
IValidation
(valid? [this]
(valid-tokens-lib-map? (datafy this)))
@@ -1424,6 +1326,12 @@ Will return a value that matches this schema:
[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
"A helper that is responsible to ensure that the hidden theme always
exists on the themes data structure"
@@ -1452,10 +1360,6 @@ Will return a value that matches this schema:
(check-tokens-lib-map)
(map->tokens-lib)))
(defn ensure-tokens-lib
[tokens-lib]
(or tokens-lib (make-tokens-lib)))
(def schema:tokens-lib
(sm/type-schema
{:type ::tokens-lib
@@ -1924,11 +1828,22 @@ Will return a value that matches this schema:
library
(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
(reduce (fn [library theme-path]
(let [[group name] (split-theme-path theme-path)
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
active-theme-names)]
@@ -2033,82 +1948,183 @@ Will return a value that matches this schema:
(defn- dtcg-export-themes
"Extract themes for a dtcg json export."
[tokens-lib]
(let [themes-xform
(comp
(filter #(and (instance? TokenTheme %)
(not (hidden-theme? %))))
(map (fn [token-theme]
;; NOTE: this probaly can be implemented as type method
(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))}))))
([tokens-lib]
;; Backward-compatible version for print/serialization without tokens-status
(let [themes-xform
(comp
(filter #(and (instance? TokenTheme %)
(not (hidden-theme? %))))
(map (fn [token-theme]
;; NOTE: this probaly can be implemented as type method
(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))
themes
(->> (get-theme-tree tokens-lib)
(tree-seq d/ordered-map? vals)
(into [] themes-xform))
;; Active themes without exposing hidden penpot theme
active-themes
(-> (get-active-theme-paths tokens-lib)
(disj hidden-theme-path))]
[themes active-themes]))
;; Active themes without exposing hidden penpot theme
active-themes
(-> (.-active-themes ^TokensLib tokens-lib)
(disj hidden-theme-path))]
[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
"Convert a TokensLib into a plain clojure map, suitable to be encoded as a multi json files each encoded in DTCG format."
[tokens-lib]
(let [[themes active-themes]
(dtcg-export-themes tokens-lib)
([tokens-lib]
;; Backward-compatible version for print/serialization without tokens-status
(let [[themes active-themes]
(dtcg-export-themes tokens-lib)
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 {}))]
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 {}))]
(-> sets
(assoc "$themes.json" themes)
(assoc "$metadata.json"
{"tokenSetOrder" (get-set-names tokens-lib)
"activeThemes" active-themes
"activeSets" (get-active-themes-set-names tokens-lib)}))))
(-> sets
(assoc "$themes.json" themes)
(assoc "$metadata.json"
{"tokenSetOrder" (get-set-names tokens-lib)
"activeThemes" active-themes
;; 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
"Convert a TokensLib into a plain clojure map, suitable to be encoded as a multi sets json string in DTCG format."
[tokens-lib]
(let [[themes active-themes]
(dtcg-export-themes tokens-lib)
([tokens-lib]
;; Backward-compatible version for print/serialization without tokens-status
(let [[themes active-themes]
(dtcg-export-themes tokens-lib)
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)])))
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)
ordered-set-names
(mapv first name-set-tuples)
sets
(into {} name-set-tuples)
sets
(into {} name-set-tuples)
active-set-names
(get-active-themes-set-names tokens-lib)]
;; For backward compat, use internal active-themes field
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)
(-> sets
(assoc "$themes" themes)
(assoc "$metadata" {"tokenSetOrder" ordered-set-names
"activeThemes" active-themes
"activeSets" active-set-names})))))
(when-not (empty-lib? tokens-lib)
(-> sets
(assoc "$themes" themes)
(assoc "$metadata" {"tokenSetOrder" ordered-set-names
"activeThemes" active-themes
"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
"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
@@ -14,6 +14,7 @@
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.test :as t]))
@@ -141,24 +142,27 @@
(t/deftest test-relocate-shape-out-of-layout-with-tokens
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(tht/add-tokens-lib)
(tht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-sizing)
:name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-spacing)
:name "token-spacing"
:type :spacing
:value 30))))
file (-> (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :test-theme)
:name "test-theme"
:sets #{"test-token-set"}))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-sizing)
:name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-spacing)
:name "token-spacing"
:type :spacing
: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
: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
@@ -18,75 +18,78 @@
[app.common.types.text :as txt]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(defn- setup-file
[]
(-> (thf/sample-file :file1)
(tht/add-tokens-lib)
(tht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-radius)
:name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-rotation)
:name "token-rotation"
:type :rotation
:value 30))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-opacity)
:name "token-opacity"
:type :opacity
:value 0.7))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-stroke-width)
:name "token-stroke-width"
:type :stroke-width
:value 2))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-color)
:name "token-color"
:type :color
:value "#00ff00"))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-dimensions)
:name "token-dimensions"
:type :dimensions
:value 100))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-font-size)
:name "token-font-size"
:type :font-size
:value 24))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-letter-spacing)
:name "token-letter-spacing"
:type :letter-spacing
:value 2))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-font-family)
:name "token-font-family"
:type :font-family
:value ["Helvetica" "Arial" "sans-serif"]))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-sizing)
:name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-spacing)
:name "token-spacing"
:type :spacing
:value 30))))
(-> (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :test-theme)
:name "test-theme"
:sets #{"test-token-set"}))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-radius)
:name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-rotation)
:name "token-rotation"
:type :rotation
:value 30))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-opacity)
:name "token-opacity"
:type :opacity
:value 0.7))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-stroke-width)
:name "token-stroke-width"
:type :stroke-width
:value 2))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-color)
:name "token-color"
:type :color
:value "#00ff00"))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-dimensions)
:name "token-dimensions"
:type :dimensions
:value 100))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-font-size)
:name "token-font-size"
:type :font-size
:value 24))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-letter-spacing)
:name "token-letter-spacing"
:type :letter-spacing
:value 2))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-font-family)
:name "token-font-family"
:type :font-family
:value ["Helvetica" "Arial" "sans-serif"]))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-sizing)
:name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :id (thi/new-id! :token-spacing)
:name "token-spacing"
:type :spacing
:value 30)))
:status-fn #(-> %
(ctos/set-tokens-status #{(thi/id :test-theme)}
#{(thi/id :test-token-set)})))
(tho/add-frame :frame1
: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
File diff suppressed because it is too large. Load diff
@@ -11,11 +11,13 @@
#?(:clj [app.common.test-helpers.tokens :as tht])
#?(:clj [clojure.datafy :refer [datafy]])
[app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.ids-map :as thi]
[app.common.time :as ct]
[app.common.transit :as tr]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.test :as t]))
@@ -247,6 +249,16 @@
theme' (ctob/toggle-set theme nil)]
(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
(let [tokens-lib (ctob/make-tokens-lib)]
(t/is (= (ctob/set-count tokens-lib) 0))))
@@ -574,208 +586,19 @@
"token-4"
(ctob/make-token :name "token-4"
:type :border-radius
:value 4000)}))
(ctob/update-theme ctob/hidden-theme-id
#(ctob/enable-sets % #{"set-a" "set-b"})))
:value 4000)})))
set-a-id (ctob/get-id (ctob/get-set-by-name tokens-lib "set-a"))
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 (= (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-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
(let [theme-id (uuid/next)
tokens-lib (ctob/make-tokens-lib)
@@ -1283,12 +1106,12 @@
(let [theme-1-id (uuid/next)
theme-2-id (uuid/next)
theme-3-id (uuid/next)
theme-4-id (uuid/next)
;; theme-4-id (uuid/next)
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-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 :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
(ctob/update-theme theme-2-id
@@ -1612,9 +1435,9 @@
:group "group-1"
:external-id "test-id-01"
:modified-at now
:sets #{"core"}))
(ctob/toggle-theme-active (thi/id :theme-1)))
result (ctob/export-dtcg-json tokens-lib)
:sets #{"core"})))
tokens-status (cfo/toggle-theme-active (ctos/make-tokens-status) tokens-lib (thi/id :theme-1))
result (ctob/export-dtcg-json tokens-lib tokens-status)
expected {"$themes" [{"description" ""
"group" "group-1"
"isSource" false
@@ -1665,9 +1488,9 @@
:group "group-1"
:external-id "test-id-01"
:modified-at now
:sets #{"some/set"}))
(ctob/toggle-theme-active (thi/id :theme-1)))
result (ctob/export-dtcg-multi-file tokens-lib)
:sets #{"some/set"})))
tokens-status (cfo/toggle-theme-active (ctos/make-tokens-status) tokens-lib (thi/id :theme-1))
result (ctob/export-dtcg-multi-file tokens-lib tokens-status)
expected {"$themes.json" [{"description" ""
"group" "group-1"
"isSource" false
@@ -7,9 +7,10 @@
(ns common-tests.types.tokens-migrations-test
(:require
[app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.ids-map :as thi]
[app.common.time :as ct]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
@@ -79,4 +80,55 @@
(t/is (= (ctob/get-description theme1') (ctob/get-description theme1)))
(t/is (= (ctob/get-id theme2') (ctob/get-id 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))))))
-17
View File
@@ -25,7 +25,6 @@
[app.common.uuid :as uuid]
;; Required for side effects: binds the generated enums.
[app.wasm.enums]
[cuerdas.core :as str]
[promesa.core :as p]
[shadow.esm :refer [dynamic-import]]))
@@ -191,22 +190,6 @@
(aget buf 0) (aget buf 1) (aget buf 2) (aget buf 3)
false)))))
(defn store-image-url!
"Registers the public URL an image was loaded from. The SVG export emits
linked `<image href>` from these, and falls back to Skia base64 when missing,
so this should run for every media id the scene references (including
already-cached images).
Does NOT call `mem/free`, for the same reason as `store-font-url!`."
[image-id url]
(when (and (some? url) (not (str/blank? url)))
(let [bytes (js/Buffer.from url "utf-8")
ptr (mem/alloc (.-byteLength bytes))
quart (uuid/get-u32 image-id)]
(mem/write-buffer ptr (mem/get-heap-u8) bytes)
(h/call wasm/internal-module "_store_image_url"
(aget quart 0) (aget quart 1) (aget quart 2) (aget quart 3)))))
(defn store-image!
"Uploads one image's *encoded* bytes (PNG/JPEG — Skia decodes, no WebGL) into
the WASM image store via `_store_image`. Buffer layout matches the Rust reader:
+1 -6
View File
@@ -369,18 +369,13 @@
"Fetches and stores every image the scene references (shape, stroke and
text-span fills, enumerated by `app.common.types.shape.images`). Unlike fonts,
the image store is not reset per request, so already-held images are skipped
and repeated exports of a file reuse them.
Always registers a public media URL for each id so SVG export can emit linked
`<image href>` even when the encoded bytes were already cached."
and repeated exports of a file reuse them."
[scene params]
(let [all-ids (images/scene-image-ids scene)
new-ids (remove wasm/image-cached? all-ids)]
(l/dbg :hint "wasm render: provisioning images"
:total (count all-ids)
:cached (- (count all-ids) (count new-ids)))
(doseq [image-id all-ids]
(wasm/store-image-url! image-id (public-uri (str "assets/by-file-media-id/" image-id))))
(->> new-ids
(map (fn [image-id]
(->> (fetch-file-media-bytes image-id params)
@@ -25,7 +25,7 @@
"~:revn": 11,
"~:modified-at": "~m1778577544627",
"~:vern": 151920609,
"~:id": "~u1bac06a1-a942-80a6-8008-0222e1ec38d5",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false,
"~:migrations": {
"~#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": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -4312,7 +4329,7 @@
}
}
},
"~:id": "~u1bac06a1-a942-80a6-8008-0222e1ec38d5",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
@@ -5771,6 +5771,16 @@
},
"~:id": "~u7b2da435-6186-815a-8007-0daa95d2f26d",
"~: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": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -134,6 +134,18 @@
"~:components-v2": true,
"~:base-font-size": "16px"
},
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~u7c1a66f7-5186-8060-8007-6ce67f8f2592"
]
}
}
},
"~:tokens-lib": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -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"
}
]
@@ -53,6 +53,18 @@
}
}
},
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~uf71719b3-63a1-8157-8008-40346917de8c"
]
}
}
},
"~:tokens-lib": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -373,7 +385,7 @@
}
}
},
"~:id": "~u55608328-aed3-807a-8008-41341f2f7247",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
@@ -29,7 +29,7 @@
"~:revn": 3,
"~:modified-at": "~m1779204621124",
"~:vern": 0,
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
@@ -130,11 +130,23 @@
"~:name": "Page 1"
}
},
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
},
"~:tokens-status": {
"~#penpot/tokens-status": {
"~:active-theme-ids": {
"~#set": []
},
"~:active-set-ids": {
"~#set": [
"~u1dc5cafc-6d57-808d-8008-0b7af8180cbd"
]
}
}
},
"~:tokens-lib": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -42,10 +42,24 @@
]
}
},
"~:id": "~u51e13852-1a8e-8037-8005-9e9413a1f1f6",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": {
"~: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": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -54,6 +54,19 @@
"~:components-v2": true,
"~: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": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -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"
}
}
}
@@ -29,7 +29,7 @@
"~:revn": 36,
"~:modified-at": "~m1776760054954",
"~:vern": 0,
"~:id": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
@@ -423,7 +423,7 @@
"~:key": "wgjr6b27pa",
"~:font-size": "16",
"~:font-weight": "400",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
"~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:modified-at": "~m1776759448186",
"~:font-variant-id": "regular",
"~:text-decoration": "none",
@@ -445,7 +445,7 @@
"~:key": "1dpjnycmsmq",
"~:font-size": "16",
"~:font-weight": "400",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
"~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:text-direction": "ltr",
"~:type": "paragraph",
"~:modified-at": "~m1776759448186",
@@ -1079,7 +1079,7 @@
"~:key": "wgjr6b27pa",
"~:font-size": "18",
"~:font-weight": "700",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
"~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:modified-at": "~m1776759420985",
"~:font-variant-id": "700",
"~:text-decoration": "none",
@@ -1101,7 +1101,7 @@
"~:key": "1dpjnycmsmq",
"~:font-size": "18",
"~:font-weight": "700",
"~:typography-ref-file": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
"~:typography-ref-file": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:text-direction": "ltr",
"~:type": "paragraph",
"~:modified-at": "~m1776759420985",
@@ -1517,7 +1517,7 @@
"~:name": "Page 1"
}
},
"~:id": "~u1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
"~:id": "~uc7ce0794-0992-8105-8004-38f280443849",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
@@ -1554,6 +1554,18 @@
"~: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": {
"~#penpot/tokens-lib": {
"~:sets": {
@@ -554,11 +554,14 @@ export class WorkspacePage extends BaseWebSocketPage {
await expect(this.toolbarOptions).toHaveCSS("opacity", "0");
}
async clickLayers(clickOptions = {}) {
await this.sidebar.getByText("Layers").click(clickOptions);
}
async clickAssets(clickOptions = {}) {
await this.sidebar.getByText("Assets").click(clickOptions);
}
async clickLayers(clickOptions = {}) {
await this.sidebar.getByText("Layers").click(clickOptions);
async clickTokens(clickOptions = {}) {
await this.sidebar.getByText("Tokens").click(clickOptions);
}
async openLibrariesModal(clickOptions = {}) {
@@ -3,6 +3,7 @@ import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
test.beforeEach(async ({ 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 ({
@@ -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
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal();
@@ -40,6 +42,7 @@ test("User adds a library and its automatically selected in the color palette",
// Remove Testing library 1
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "This file" }).click();
await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal();
@@ -152,6 +152,7 @@ test("[Taiga #10630] [INSPECT] Style assets not being displayed on info tab", as
await workspacePage.clickAssets();
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal();
@@ -210,7 +210,7 @@ test("Multiselection of text and typographies", async ({ page }) => {
);
await workspacePage.goToWorkspace({
fileId: "1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
fileId: "c7ce0794-0992-8105-8004-38f280443849",
pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0",
});
@@ -0,0 +1,159 @@
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-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: "Tokens" }).click();
await page.waitForTimeout(500);
// Select the first shape in the file
await page.keyboard.press("v");
await workspacePage.clickAt(200, 150);
// 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.`,
);
});
@@ -89,6 +89,12 @@ test.describe("Tokens Themes", () => {
tokenThemeUpdateCreateModal.getByText("Changed" + "4 active sets"),
).toBeVisible();
await tokenThemeUpdateCreateModal
.getByRole("switch", {
name: "Changed",
})
.click();
await tokenThemeUpdateCreateModal
.getByRole("button")
.getByText("close")
@@ -143,6 +143,10 @@ test("BUG 14214 - Updates tab refreshes after syncing a freshly linked library",
// Open the library modal
await workspace.clickAssets();
await workspace.openLibrariesModal();
// Switch to the Libraries tab
await workspace.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspace.librariesModal
.getByRole("button", { name: "Connect library" })
.click();
@@ -25,7 +25,7 @@ import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
// ---------------------------------------------------------------------------
const FILE_A = {
id: "1062e0a0-8fe0-80ae-8007-e70b4993f5ef",
id: "c7ce0794-0992-8105-8004-38f280443849",
pageId: "1062e0a0-8fe0-80ae-8007-e70b4993f5f0",
// "Text with typography asset one" carries a ref to in-file typography whose
// font-family is "IM Fell French Canon SC" (multiselection-typography.json).
@@ -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
await workspacePage.mockRPC(/get\-file\?/, "workspace/get-file-library.json");
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal.getByRole("tab", { name: "Libraries" }).click();
await workspacePage.clickLibrary("Testing library 1");
await workspacePage.closeLibrariesModal();
+18
View File
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
@@ -37,6 +38,23 @@
([state file-id]
(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
[fdata page-id]
(dm/get-in fdata [:pages-index page-id]))
@@ -16,6 +16,7 @@
[app.common.logging :as log]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.logic.tokens :as clo]
[app.common.logic.variants :as clv]
[app.common.path-names :as cpn]
[app.common.time :as ct]
@@ -46,6 +47,7 @@
[app.main.data.workspace.specialized-panel :as dwsp]
[app.main.data.workspace.thumbnails :as dwt]
[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.undo :as dwu]
[app.main.data.workspace.wasm-text :as dwwt]
@@ -654,6 +656,7 @@
(merge (meta it))))
(dwu/start-undo-transaction undo-id)
(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)]})
(dws/select-shapes (d/ordered-set (:id new-shape)))
(when start-move?
@@ -837,7 +840,11 @@
(rx/merge
(->> (rx/of library-id)
(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))))))
@@ -1096,6 +1103,7 @@
(rx/of
(dwu/start-undo-transaction undo-id)
(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")
(seq new-text-ids))
(dwwt/resize-wasm-text-all new-text-ids))
@@ -1345,6 +1353,9 @@
#(do (apply st/emit! (map (fn [library]
(sync-file file-id (:id library)))
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)))
do-dismiss
@@ -1397,8 +1408,8 @@
(launch-component-sync component-id file-id undo-group)))))
(defn watch-component-changes
"Watch the state for changes that affect to any main instance. If a change is detected will throw
an update-component-sync, so changes are immediately propagated to the component and copies."
"Watch the state for changes that affect to any main instance. If a change is detected will call
component-changed, so changes are immediately propagated to the component and copies."
[]
(ptk/reify ::watch-component-changes
ptk/WatchEvent
@@ -1530,6 +1541,47 @@
(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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1575,6 +1627,7 @@
(map #(assoc % :library-of file-id))
(d/index-by :id))))))
(defn- load-library-file
[file-id library-id]
(ptk/reify ::load-library-file
@@ -1584,8 +1637,9 @@
(rx/merge
(->> (rp/cmd! :get-file {:id library-id :features features})
(rx/merge-map fpmap/resolve-file)
(rx/map (fn [file]
(libraries-fetched file-id [file]))))
(rx/mapcat (fn [file]
(rx/of
(libraries-fetched file-id [file])))))
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
(rx/map (fn [thumbnails]
(fn [state]
@@ -1594,10 +1648,10 @@
(defn link-file-to-library
[file-id library-id]
(ptk/reify ::attach-library
(ptk/reify ::link-file-to-library
ev/Event
(-data [_]
{::ev/name "attach-library"
{::ev/name "link-file-to-library"
:file-id file-id
:library-id library-id})
@@ -1614,23 +1668,22 @@
(map first)
set)]
(rx/concat
(rx/merge
(->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id})
(rx/merge-map (fn [libraries-to-load]
(as-> libraries-to-load $
(remove loaded-libraries $)
(conj $ library-id)
(map #(load-library-file file-id %) $))))
(rx/catch (fn [cause]
(let [error (ex-data cause)]
(if (= (:code error) :circular-library-reference)
(rx/of (ntf/error (tr "errors.circular-library-reference")))
(rx/throw cause)))))))
(rx/of (ptk/reify ::attach-library-finished))
(->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id})
(rx/merge-map (fn [libraries-to-load]
(as-> libraries-to-load $
(remove loaded-libraries $)
(conj $ library-id)
(map #(load-library-file file-id %) $))))
(rx/catch (fn [cause]
(let [error (ex-data cause)]
(if (= (:code error) :circular-library-reference)
(rx/of (ntf/error (tr "errors.circular-library-reference")))
(rx/throw cause))))))
(rx/of (ptk/reify ::link-file-to-library-finished))
(when (pos? variants-count)
(->> (rp/cmd! :get-library-usage {:file-id library-id})
(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
:library-id library-id
:variants-count variants-count
@@ -114,6 +114,7 @@
(rx/of (dwth/watch-state-changes file-id page-id)))
(rx/of (dwl/watch-component-changes))
(rx/of (dwl/watch-token-changes))
(let [profile (:profile state)
props (get profile :props)]
@@ -22,11 +22,9 @@
[app.common.types.text :as txt]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.event :as ev]
[app.main.data.helpers :as dsh]
[app.main.data.workspace :as-alias dw]
[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.pages :as-alias dwpg]
[app.main.data.workspace.reflow :as wrf]
@@ -1168,66 +1166,6 @@
(let [{:keys [name]} (fonts/get-font-data font-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
(defn v2-update-text-editor-styles
@@ -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})))))))))
@@ -25,7 +25,6 @@
[app.main.data.style-dictionary :as sd]
[app.main.data.tinycolor :as tinycolor]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace :as udw]
[app.main.data.workspace.colors :as wdc]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
@@ -95,11 +94,11 @@
(watch [_ _ _]
(when (number? value)
(rx/of
(udw/trigger-bounding-box-cloaking shape-ids)
(udw/increase-rotation shape-ids value nil
{:page-id page-id
:ignore-touched true
:no-wasm? true})))))))
(dwtr/trigger-bounding-box-cloaking shape-ids)
(dwtr/increase-rotation shape-ids value nil
{:page-id page-id
:ignore-touched true
:no-wasm? true})))))))
(defn update-stroke-width
([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-keys (:type token)) (set/union attributes-to-remove ctt/typography-token-keys)
:else attributes-to-remove)]
(when-let [tokens (some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-tokens-in-active-sets))]
(when-let [tokens (let [tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)]
(when (and tokens-lib tokens-status)
(cfo/get-tokens-in-active-sets tokens-status tokens-lib)))]
(->> (if (contains? cf/flags :tokenscript)
(rx/of (ts/resolve-tokens tokens))
(sd/resolve-tokens tokens))
@@ -9,12 +9,14 @@
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[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.test-helpers.ids-map :as cthi]
[app.common.types.shape :as cts]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.event :as ev]
@@ -34,19 +36,12 @@
;; TOKENS Getters
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FIXME: lookup rename
(defn get-tokens-lib
[state]
(-> (dsh/lookup-file-data state)
(get :tokens-lib)))
(defn lookup-token-set
([state]
(when-let [selected (dm/get-in state [:workspace-tokens :selected-token-set-id])]
(lookup-token-set state selected)))
([state id]
(some-> (get-tokens-lib state)
(some-> (dsh/lookup-tokens-lib state)
(ctob/get-set id))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -231,8 +226,7 @@
ptk/WatchEvent
(watch [it 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)))
(rx/of (ntf/show {:content (tr "errors.token-theme-already-exists")
:type :toast
@@ -249,8 +243,8 @@
(ptk/reify ::update-token-theme
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
tokens-lib (get data :tokens-lib)]
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)]
(if (and (not= id (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")
@@ -259,7 +253,7 @@
:timeout 9000}))
(let [changes (-> (pcb/empty-changes it)
(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))))))))
(defn set-token-theme-active
@@ -269,31 +263,35 @@
(ptk/reify ::set-token-theme-active
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)
tokens-lib (get-tokens-lib state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clt/generate-set-active-token-theme tokens-lib id active?))]
(let [data (dsh/lookup-file-data state)
tokens-status (dsh/lookup-tokens-status state)
tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-set-theme-status tokens-status tokens-lib id active?))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(defn toggle-token-theme-active
[id]
(assert (uuid? id) "expected a uuid for `id`")
(ptk/reify ::toggle-token-theme-active
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
tokens-lib (get-tokens-lib state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(clt/generate-toggle-token-theme tokens-lib id))]
(let [data (dsh/lookup-tokens-source-data state)
tokens-status (dsh/lookup-tokens-status state)
tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(clo/generate-toggle-theme tokens-status tokens-lib id))]
(rx/of
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(defn delete-token-theme
[id]
(assert (uuid? id) "expected a uuid for `id`")
(ptk/reify ::delete-token-theme
ptk/WatchEvent
(watch [it state _]
@@ -350,10 +348,11 @@
ptk/WatchEvent
(watch [it 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")]
(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)))
(let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
@@ -362,32 +361,34 @@
(dch/commit-changes changes)))))))))
(defn set-enabled-token-set
[name enabled?]
(assert (string? name) "expected a string for `name`")
[id enabled?]
(assert (uuid? id) "expected a uuid for `id`")
(assert (boolean? enabled?) "expected a boolean for `enabled?`")
(ptk/reify ::set-enabled-token-set
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)
tlib (get-tokens-lib state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clt/generate-set-enabled-token-set tlib name enabled?))]
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-set-enabled-token-set tokens-status tokens-lib id enabled?))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
(defn toggle-token-set
[name]
(assert (string? name) "expected a string for `name`")
[id]
(assert (uuid? id) "expected a uuid for `id`")
(ptk/reify ::toggle-token-set
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)
tlib (get-tokens-lib state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clt/generate-toggle-token-set tlib name))]
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-toggle-token-set tokens-status tokens-lib id))]
(rx/of
(dch/commit-changes changes)
@@ -398,10 +399,12 @@
(ptk/reify ::toggle-token-set-group
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clt/generate-toggle-token-set-group (get-tokens-lib state) group-path))]
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-toggle-token-set-group tokens-status tokens-lib group-path))]
(rx/of
(dch/commit-changes changes)
@@ -413,12 +416,27 @@
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
status (cfo/make-tokens-status-from-lib lib)
changes (-> (pcb/empty-changes it)
(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)
(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
[id]
(ptk/reify ::delete-token-set
@@ -439,7 +457,7 @@
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(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)
(dwtp/propagate-workspace-tokens))))))
@@ -467,7 +485,7 @@
ptk/WatchEvent
(watch [it state _]
(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
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens)))
@@ -483,9 +501,9 @@
ptk/WatchEvent
(watch [it state _]
(try
(let [tokens-lib (get-tokens-lib state)
(let [tokens-lib (dsh/lookup-tokens-lib state)
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)
(dwtp/propagate-workspace-tokens)))
(catch :default cause
@@ -506,11 +524,13 @@
token-set
(ctob/make-token-set :name set-name)
hidden-theme
(ctob/make-hidden-theme)
hidden-theme ;; For legacy compatibility only
(-> (ctob/make-hidden-theme)
(ctob/enable-set set-name))
hidden-theme-with-set
(ctob/enable-set hidden-theme set-name)
token-status
(ctos/make-tokens-status :active-theme-ids #{}
:active-set-ids #{(ctob/get-id token-set)})
changes
(-> (pcb/empty-changes)
@@ -518,8 +538,9 @@
(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-theme (ctob/get-id hidden-theme)
hidden-theme-with-set)
(pcb/set-active-token-themes #{ctob/hidden-theme-path}))]
hidden-theme)
(pcb/set-tokens-status token-status))]
(rx/of (dch/commit-changes changes)
(set-selected-token-set-id (ctob/get-id token-set)))))))
@@ -559,7 +580,7 @@
(let [token-set (lookup-token-set state set-id)
data (dsh/lookup-file-data state)
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))
new-name (->
(cpn/split-path (:name token) :separator ".")
@@ -589,21 +610,22 @@
(lookup-token-set state set-id)
(lookup-token-set state))
data (dsh/lookup-file-data state)
token (-> (get-tokens-lib state)
(ctob/get-token (ctob/get-id token-set) id))
token' (->> (merge token params)
(into {})
(ctob/make-token))
token-type (:type token)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token (ctob/get-id token-set)
id
token'))]
(toggle-token-path (str (name token-type) "." (:name token)))
(rx/of (dch/commit-changes changes)
(ev/event (-> {::ev/name "edit-token" :type token-type}
(merge (meta it))))))))))
tokens-lib (dsh/lookup-tokens-lib state)]
(when (and tokens-lib token-set)
(let [token (ctob/get-token tokens-lib (ctob/get-id token-set) id)
token' (->> (merge token params)
(into {})
(ctob/make-token))
token-type (:type token)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token (ctob/get-id token-set)
id
token'))]
(toggle-token-path (str (name token-type) "." (:name token)))
(rx/of (dch/commit-changes changes)
(ev/event (-> {::ev/name "edit-token" :type token-type}
(merge (meta it))))))))))))
(defn bulk-update-tokens
[set-id token-ids type old-path new-path & {:keys [undo-group]}]
@@ -617,7 +639,7 @@
(lookup-token-set state))
data (dsh/lookup-file-data state)
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))
new-name (str/replace (:name token) old-path new-path)
token' (->> (merge token {:name new-name})
@@ -643,19 +665,20 @@
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
tokens-lib (dsh/lookup-tokens-lib state)
token-set (if set-id
(lookup-token-set state set-id)
(lookup-token-set state))
token (-> (get-tokens-lib state)
(ctob/get-token (ctob/get-id token-set) token-id))
token-type (:type token)
(lookup-token-set state))]
(when (and tokens-lib token-set)
(let [token (ctob/get-token tokens-lib (ctob/get-id token-set) token-id)
token-type (:type token)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token set-id token-id nil))]
(rx/of (dch/commit-changes changes)
(ev/event (-> {::ev/name "delete-token" :type token-type}
(merge (meta it)))))))))
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token set-id token-id nil))]
(rx/of (dch/commit-changes changes)
(ev/event (-> {::ev/name "delete-token" :type token-type}
(merge (meta it)))))))))))
(defn bulk-delete-tokens
[set-id token-ids]
@@ -680,7 +703,7 @@
ptk/WatchEvent
(watch [_ 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
(ctob/get-id token-set)
token-id)]
@@ -8,10 +8,10 @@
(:require
[app.common.data :as d]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.logging :as l]
[app.common.time :as ct]
[app.common.types.token :as ctt]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[app.main.data.helpers :as dsh]
[app.main.data.style-dictionary :as sd]
@@ -188,27 +188,28 @@
(ptk/reify ::propagate-workspace-tokens
ptk/WatchEvent
(watch [_ state _]
(when-let [tokens-tree (-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-tokens-in-active-sets))]
(->> (if (contains? cf/flags :tokenscript)
(rx/of (-> (ts/resolve-tokens tokens-tree)
(d/update-vals #(update % :resolved-value ts/tokenscript-symbols->penpot-unit))))
(sd/resolve-tokens tokens-tree))
(rx/mapcat
(fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
(let [tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)]
(when (and tokens-lib tokens-status)
(when-let [tokens-tree (cfo/get-tokens-in-active-sets tokens-status tokens-lib)]
(->> (if (contains? cf/flags :tokenscript)
(rx/of (-> (ts/resolve-tokens tokens-tree)
(d/update-vals #(update % :resolved-value ts/tokenscript-symbols->penpot-unit))))
(sd/resolve-tokens tokens-tree))
(rx/mapcat
(fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
;; 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
;; should be done with a changes_builder and sending only one `commit-changes` instead
;; of creating lots of `update-shapes`.
(rx/of (dwsh/update-shapes-buffer-start))
(->> (propagate-tokens state sd-tokens)
(rx/catch #(rx/concat
(rx/of (dwsh/update-shapes-buffer-stop))
(rx/throw %))))
(rx/of (dwsh/update-shapes-buffer-stop))
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))
;; 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
;; should be done with a changes_builder and sending only one `commit-changes` instead
;; of creating lots of `update-shapes`.
(rx/of (dwsh/update-shapes-buffer-start))
(->> (propagate-tokens state sd-tokens)
(rx/catch #(rx/concat
(rx/of (dwsh/update-shapes-buffer-stop))
(rx/throw %))))
(rx/of (dwsh/update-shapes-buffer-stop))
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))))
@@ -13,26 +13,22 @@
(defn- get-selected-token-set-id [state]
(or (get-in state [:workspace-tokens :selected-token-set-id])
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(some-> (dsh/lookup-tokens-lib state)
(ctob/get-sets)
(first)
(ctob/get-id))))
(defn get-selected-token-set [state]
(when-let [set-id (get-selected-token-set-id state)]
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(some-> (dsh/lookup-tokens-lib state)
(ctob/get-set set-id))))
(defn get-token-in-selected-set [state token-id]
(when-let [set-id (get-selected-token-set-id state)]
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(some-> (dsh/lookup-tokens-lib state)
(ctob/get-token set-id token-id))))
(defn get-all-tokens-in-selected-set [state]
(when-let [set-id (get-selected-token-set-id state)]
(some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(some-> (dsh/lookup-tokens-lib state)
(ctob/get-tokens set-id))))
+18 -11
View File
@@ -10,9 +10,11 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cph]
[app.common.files.tokens :as cfo]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.config :as cf]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.tokens.selected-set :as dwts]
@@ -464,7 +466,10 @@
;; ---- Token refs
(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
(l/derived (d/nilf ctob/get-theme-groups) tokens-lib))
@@ -501,26 +506,28 @@
(def workspace-token-sets-tree
(l/derived (d/nilf ctob/get-set-tree) tokens-lib))
(def workspace-active-theme-paths
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
(def workspace-active-theme-ids
(l/derived (d/nilf ctos/get-active-theme-ids) tokens-status))
(def workspace-all-tokens-map
(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
[group-path]
(l/derived
(fn [lib]
(when lib
(ctob/sets-at-path-all-active? lib group-path)))
tokens-lib))
(def workspace-active-theme-paths-no-hidden
(l/derived #(disj % ctob/hidden-theme-path) workspace-active-theme-paths))
(fn [status]
(when status
(cfo/sets-at-path-all-active? status @(l/derived identity tokens-lib) group-path)))
tokens-status))
;; FIXME: deprecated, it should not be implemented with ref (still used in form)
(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
(fn [token-id]
@@ -57,6 +57,7 @@
.icon {
color: var(--arrow-icon-color);
flex-shrink: 0;
}
.title-wrapper {
+3
View File
@@ -25,6 +25,8 @@
(def libraries (mf/create-context nil))
(def design-tokens (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-zoom (mf/create-context nil))
@@ -45,6 +47,7 @@
(def permissions (mf/create-context nil))
(def can-edit? (mf/create-context nil))
(def can-edit-tokens? (mf/create-context nil))
(def active-tokens-by-type
"Active tokens by type, used mainly for provide tokens data to the
@@ -313,7 +313,8 @@
"A collection of all 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-s 12)
@@ -322,12 +323,13 @@
[:class {:optional true} [:maybe :string]]
[:icon-id [:and :string [:fn #(contains? icon-list %)]]]
[:size {:optional true}
[:maybe [:enum "s" "m" "l"]]]])
[:maybe [:enum "s" "m" "l" "xl"]]]])
(mf/defc icon*
{::mf/schema schema:icon}
[{: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
:else icon-size-m)
offset (if (or (= size "s") (= size "m"))
@@ -25,6 +25,6 @@
(when icon
[:div {:class (stl/css :icon-wrapper)}
[:> icon* {:icon-id icon
:size "l"
:size "xl"
:class (stl/css :icon)}]])
[:div {:class (stl/css :text)} text]]))
+13 -6
View File
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.component :as ctc]
[app.common.types.components-list :as ctkl]
[app.common.types.shape.layout :as ctl]
@@ -111,11 +112,17 @@
panels (type->panel-group shape-type)
tokens-lib (mf/deref refs/tokens-lib)
active-themes (mf/deref refs/workspace-active-theme-paths-no-hidden)
active-sets (mf/with-memo [tokens-lib]
(some-> tokens-lib (ctob/get-active-themes-set-names)))
active-tokens (mf/with-memo [tokens-lib]
(some-> tokens-lib (ctob/get-tokens-in-active-sets)))
tokens-status (mf/deref refs/tokens-status)
active-theme-ids (mf/deref refs/workspace-active-theme-ids)
active-themes (mf/with-memo [active-theme-ids tokens-lib]
(when tokens-lib
(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)
has-visibility-props? (mf/use-fn
(fn [shape]
@@ -152,7 +159,7 @@
(when (or (seq active-themes) (seq active-sets))
[:li
[:> 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]
[:li {:key (d/name panel)}
(case panel
@@ -13,10 +13,10 @@
[rumext.v2 :as mf]))
(mf/defc tokens-panel*
[{:keys [theme-paths set-names]}]
[{:keys [theme-names set-names]}]
[:div {:class (stl/css :tokens-panel)}
(when (seq theme-paths)
(let [theme-list (str/join ", " theme-paths)]
(when (seq theme-names)
(let [theme-list (str/join ", " theme-names)]
[:> properties-row* {:class (stl/css :token-theme)
:term (tr "inspect.tabs.styles.active-themes")
:detail theme-list}]))
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.types.color :as cc]
@@ -759,10 +760,15 @@
tokens-lib
(mf/deref refs/tokens-lib)
tokens-status
(mf/deref refs/tokens-status)
active-sets-names
(mf/with-memo [tokens-lib]
(some-> tokens-lib
(ctob/get-active-themes-set-names)))
(mf/with-memo [tokens-status tokens-lib]
(when (and tokens-status tokens-lib)
(into #{}
(map ctob/get-name)
(cfo/get-active-sets tokens-status tokens-lib))))
active-tokens (if (delay? active-tokens)
@active-tokens
@@ -782,7 +788,7 @@
(filter-active-sets active-sets-names)
(filter-non-empty-sets)
(group-sets)
(combine-groups-with-resolved color-tokens)))]
(combine-groups-with-resolved color-tokens)))]
(mf/with-effect []
(st/emit! (st/emit! (dsc/push-shortcuts ::colorpicker sc/shortcuts :workspace)))
+522 -49
View File
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.files.variant :as cfv]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
@@ -24,6 +25,7 @@
[app.main.data.team :as dtm]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs]
[app.main.render :refer [component-svg]]
[app.main.store :as st]
@@ -34,7 +36,8 @@
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [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.product.empty-state :refer [empty-state*]]
[app.main.ui.hooks :as h]
@@ -56,16 +59,27 @@
typographies (count (:typographies data))
components (count (->> (ctkl/components-seq 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)
(zero? graphics)
(zero? colors)
(zero? typographies))]
(zero? typographies)
(zero? tokens)
(zero? token-sets)
(zero? token-themes))]
{:is-empty empty?
:colors colors
:graphics graphics
:typographies typographies
:components components}))
:components components
:tokens tokens
:token-sets token-sets
:token-themes token-themes}))
(defn- adapt-backend-summary
[summary]
@@ -73,16 +87,25 @@
graphics (or (-> summary :media :count) 0)
typographies (or (-> summary :typographies :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)
(zero? graphics)
(zero? colors)
(zero? typographies))]
{:is-empty empty?
:components components
:graphics graphics
(zero? typographies)
(zero? tokens)
(zero? token-sets)
(zero? token-themes))]
{:is-empty empty?
:components components
:graphics graphics
:typographies typographies
:colors colors}))
:colors colors
:tokens tokens
:token-sets token-sets
:token-themes token-themes}))
(defn- describe-library
[components-count graphics-count colors-count typography-count]
@@ -108,28 +131,88 @@
(mf/defc library-description*
{::mf/private true}
[{:keys [summary]}]
(let [components-count (get summary :components)
graphics-count (get summary :graphics)
typography-count (get summary :typographies)
colors-count (get summary :colors)]
[{:keys [summary hint-name]}]
(let [components-count (get summary :components)
graphics-count (get summary :graphics)
typography-count (get summary :typographies)
colors-count (get summary :colors)
tokens-count (get summary :tokens)
token-sets-count (get summary :token-sets)
token-themes-count (get summary :token-themes)
[:*
(when (pos? components-count)
[:li {:class (stl/css :element-count)}
(tr "workspace.libraries.components" (c components-count))])
token-lib-sync? (contains? cf/flags :token-lib-sync)
(when (pos? graphics-count)
[:li {:class (stl/css :element-count)}
(tr "workspace.libraries.graphics" (c graphics-count))])
elements-line
(str/join " · "
(cond-> []
(pos? components-count)
(conj (tr "workspace.libraries.components" (c components-count)))
(when (pos? colors-count)
[:li {:class (stl/css :element-count)}
(tr "workspace.libraries.colors" (c colors-count))])
(pos? graphics-count)
(conj (tr "workspace.libraries.graphics" (c graphics-count)))
(when (pos? typography-count)
[:li {:class (stl/css :element-count)}
(tr "workspace.libraries.typography" (c typography-count))])]))
(pos? colors-count)
(conj (tr "workspace.libraries.colors" (c colors-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/private true}
@@ -182,7 +265,7 @@
(when-let [tokens-lib (get data :tokens-lib)]
(not (ctob/empty-lib? tokens-lib))))
(mf/defc libraries-tab*
(mf/defc libraries-tab-legacy*
{::mf/private true}
[{:keys [is-shared linked-libraries shared-libraries]}]
(let [file-id (mf/use-ctx ctx/current-file-id)
@@ -262,7 +345,7 @@
unlink-library
(mf/use-fn
(mf/deps file-id)
(mf/deps file-id local-library)
(fn [event]
(let [library-id (some-> (dom/get-current-target event)
(dom/get-data "library-id")
@@ -270,7 +353,11 @@
(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)))))
(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
@@ -283,6 +370,7 @@
:tokens/import-from-library {:file-id file-id
:library-id library-id})))))
on-delete-accept
(mf/use-fn
(mf/deps file-id)
@@ -324,7 +412,7 @@
:on-cancel on-delete-cancel
:count-libraries 1}))))]
[:div {:class (stl/css :libraries-content)}
[:div {:class (stl/css :libraries-content-legacy)}
[:div {:class (stl/css :lib-section)}
[:> title-bar* {:collapsable false
:title (tr "workspace.libraries.in-this-file")
@@ -334,7 +422,7 @@
[: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)}
[:ul {:class (stl/css :item-contents-legacy)}
[:> library-description* {:summary summary}]]]
(if ^boolean is-shared
@@ -352,21 +440,21 @@
(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 :section-list-item)
[:div {:class (stl/css :section-list-item-legacy)
:key (dm/str id)
:data-testid "library-item"}
[:div {:class (stl/css :item-content)}
[:div {:class (stl/css-case :item-name true
:item-name-short has-tokens?)} name]
[:ul {:class (stl/css :item-contents)}
[:div {:class (stl/css :item-name)} name]
[:ul {:class (stl/css :item-contents-legacy)}
(let [summary (get-library-summary data)]
[:*
[:> library-description* {:summary summary}]
(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 {:class (stl/css :connected-to-values)} (str/join ", " connected-to-names)]
[:span ")"]])])]]
[:div {:class (stl/css :library-actions)}
(when ^boolean has-tokens?
[:> icon-button*
@@ -397,12 +485,12 @@
(if (seq shared-libraries)
[:div {:class (stl/css :section-list-shared)}
(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)
:data-testid "library-item"}
[:div {:class (stl/css :item-content)}
[: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)
(adapt-backend-summary))]
[:> library-description* {:summary summary}])]]
@@ -439,6 +527,372 @@
:else
(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
[file-data library summary?]
(let [exceeded (volatile! {:components false
@@ -534,7 +988,7 @@
[:div {:class (stl/css :section-list-item)
:key (dm/str id)}
[: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
(count components)
0
@@ -655,8 +1109,10 @@
(fn [_]
(modal/hide!)))
token-lib-sync? (contains? cf/flags :token-lib-sync)
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
(deref selected-tab*)
@@ -665,11 +1121,18 @@
(mf/use-fn #(reset! selected-tab* %))
tabs
(mf/with-memo []
[{:label (tr "workspace.libraries.libraries")
:id "libraries"}
{:label (tr "workspace.libraries.updates")
:id "updates"}])]
(mf/with-memo [token-lib-sync?]
(if token-lib-sync?
[{:label "This file"
:id "file"}
{: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 []
(st/emit! (dtm/fetch-shared-files)))
@@ -690,11 +1153,21 @@
:selected selected-tab
:on-change on-change-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-tab*
{:is-shared shared?
:linked-libraries linked-libraries
:shared-libraries shared-libraries}]
(if token-lib-sync?
[:> libraries-tab*
{:linked-libraries linked-libraries
:shared-libraries shared-libraries}]
[:> libraries-tab-legacy*
{:is-shared shared?
:linked-libraries linked-libraries
:shared-libraries shared-libraries}])
"updates"
[:> updates-tab*
+118 -18
View File
@@ -49,7 +49,14 @@
}
// Tabs content
.libraries-content,
.libraries-content {
display: grid;
gap: var(--sp-xxxl);
max-height: $sz-400;
padding-block-start: var(--sp-l);
}
.libraries-content-legacy,
.updates-content {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -75,6 +82,10 @@
margin: 0 0 0 calc(-1 * var(--sp-s));
}
.connected-libraries-title {
@include t.use-typography("headline-small");
}
.section-list,
.section-list-shared {
display: grid;
@@ -86,7 +97,8 @@
%section-list-item-placeholder {
display: grid;
grid-template-columns: 1fr auto;
grid-template-columns: minmax(0, 1fr) auto;
align-items: center;
gap: var(--sp-s);
border: $b-1 solid var(--color-background-quaternary);
padding: var(--sp-s) var(--sp-s) var(--sp-m) var(--sp-m);
@@ -96,21 +108,33 @@
.section-list-publish {
@extend %section-list-item-placeholder;
grid-template-columns: minmax(0, 1fr) auto auto;
border: none;
}
.section-list-item-legacy {
@extend %section-list-item-placeholder;
}
.section-list-item {
@extend %section-list-item-placeholder;
padding: var(--sp-m);
}
.section-list-item-nested {
margin-inline-start: var(--sp-l);
}
.section-list-item-double-icon {
@extend %section-list-item-placeholder;
grid-template-columns: 1fr auto auto;
grid-template-columns: minmax(0, 1fr) auto auto;
}
.item-content {
height: fit-content;
min-width: 0;
}
.close-btn {
@@ -130,6 +154,13 @@
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
.section-list-empty {
display: grid;
@@ -141,6 +172,23 @@
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
.libraries-updates-see-all {
background: unset;
@@ -206,7 +254,6 @@
@include text-ellipsis;
margin: 0;
max-width: px2rem(236);
color: var(--library-name-foreground-color);
}
@@ -214,16 +261,6 @@
@extend %item-name;
}
.item-name-short {
max-width: px2rem(206);
}
.item-name-long {
@extend %item-name;
max-width: px2rem(450);
}
.item-title {
@include t.use-typography("body-large");
@@ -241,7 +278,7 @@
border-radius: $br-8;
}
.item-contents {
.item-contents-legacy {
@include t.use-typography("body-small");
color: var(--library-content-foreground-color);
@@ -250,12 +287,22 @@
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 {
display: flex;
flex-shrink: 0;
gap: var(--sp-xs);
}
.element-count {
.element-count-legacy {
white-space: nowrap;
&:not(:last-child)::after {
@@ -264,14 +311,42 @@
}
}
.connected-to-wrapper {
.element-count-line {
display: flex;
align-items: baseline;
gap: var(--sp-xs);
&::before {
content: "";
}
}
.connected-to-wrapper-legacy {
display: block;
}
.connected-to-values {
.connected-to-values-legacy {
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;
}
// Modal Component v2 update
.modal-v2-info {
width: px2rem(664);
@@ -364,6 +439,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 {
@include t.use-typography("body-small");
@@ -403,3 +491,15 @@
overflow: hidden;
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;
}
+41 -33
View File
@@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[app.main.constants :refer [left-sidebar-default-max-width
@@ -22,7 +23,7 @@
[app.main.features :as features]
[app.main.refs :as refs]
[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.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
@@ -116,7 +117,7 @@
(mf/defc left-sidebar*
{::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)
project (mf/deref refs/project)
file-id (get file :id)
@@ -173,7 +174,7 @@
(mf/with-memo []
(mf/html [:> collapse-button* {}]))]
[:> (mf/provider muc/sidebar) {:value :left}
[:> (mf/provider ctx/sidebar) {:value :left}
[:aside {:ref parent-ref
:id "left-sidebar-aside"
:data-testid "left-sidebar"
@@ -215,9 +216,11 @@
:file-id file-id}]
:tokens
[:> tokens-sidebar-tab* {:tokens-lib tokens-lib
:active-tokens active-tokens
:resolved-active-tokens resolved-active-tokens}]
[:> tokens-sidebar-tab*
{:tokens-lib tokens-lib
:tokens-status tokens-status
:active-tokens active-tokens
:resolved-active-tokens resolved-active-tokens}]
:layers
[:> layers-content* {:layout layout
@@ -313,8 +316,8 @@
(mf/with-memo [active-tokens]
(delay (ctob/group-by-type active-tokens)))]
[:> (mf/provider muc/sidebar) {:value :right}
[:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type}
[:> (mf/provider ctx/sidebar) {:value :right}
[:> (mf/provider ctx/active-tokens-by-type) {:value active-tokens-by-type}
[:aside
{:class (stl/css-case :right-sidebar true
:not-expand (not can-be-expanded?)
@@ -358,19 +361,22 @@
(let [tokens-lib
(mf/deref refs/tokens-lib)
tokens-status
(mf/deref refs/tokens-status)
active-tokens
(mf/with-memo [tokens-lib]
(if tokens-lib
(ctob/get-tokens-in-active-sets tokens-lib)
(mf/with-memo [tokens-status tokens-lib]
(if (and tokens-status tokens-lib)
(cfo/get-tokens-in-active-sets tokens-status tokens-lib)
{}))
selected-token-set-id
(mf/deref refs/selected-token-set-id)
active-tokens-force-set
(mf/with-memo [tokens-lib selected-token-set-id]
(if (and tokens-lib selected-token-set-id)
(ctob/get-tokens-in-active-sets-force tokens-lib selected-token-set-id)
(mf/with-memo [tokens-status tokens-lib selected-token-set-id]
(if (and tokens-status 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)
@@ -389,25 +395,27 @@
resolved-active-tokens-force-set
(sd/use-resolved-tokens* active-tokens-force-set)]
[:*
(if (:collapse-left-sidebar layout)
[:> collapsed-button*]
[:> left-sidebar* {:layout layout
[:> (mf/provider ctx/tokens-lib) {:value tokens-lib}
[:> (mf/provider ctx/tokens-status) {:value tokens-status}
(if (:collapse-left-sidebar 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-id file-id
:page-id page-id
:tokens-lib tokens-lib
: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-id file-id
:page-id page-id
:tokens-lib tokens-lib
:active-tokens (if tokenscript?
tokenscript-resolved-active-tokens
resolved-active-tokens)}]]))
:active-tokens (if tokenscript?
tokenscript-resolved-active-tokens
resolved-active-tokens)}]]]))
@@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.components-list :as ctkl]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
@@ -33,6 +34,12 @@
[{:keys [filters]}]
(let [file-id (mf/use-ctx ctx/current-file-id)
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]
(->> (refs/select-libraries files file-id)
(vals)
@@ -46,6 +53,7 @@
{:key (dm/str (:id file))
:file file
:is-local false
:is-tokens-source (= (:id file) tokens-source)
:is-default-open false
:filters filters}])))
@@ -57,11 +65,16 @@
(mf/defc assets-local-library*
{::mf/private true}
[{: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 file
:is-local true
:is-default-open true
:is-tokens-source is-tokens-source
:filters filters}]))
(defn- toggle-values
@@ -22,7 +22,8 @@
[app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar*]]
[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.common :as cmm]
[app.main.ui.workspace.sidebar.assets.components :refer [components-section*]]
@@ -76,7 +77,7 @@
(mf/defc file-library-title*
{::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)
team-id (mf/use-ctx ctx/current-team-id)
url (rt/resolve router :workspace
@@ -93,7 +94,15 @@
(mf/use-fn
(fn [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
:library-title true
@@ -103,17 +112,24 @@
:on-collapsed toggle-open
:title (if is-local
(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?
(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
[:span {:title (tr "workspace.assets.open-library")}
[:a {:class (stl/css :file-link)
:href (str "#" url)
:target "_blank"
: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
[selected type asset-groups asset-id file-id]
@@ -299,13 +315,15 @@
(not ^boolean show-colors?)
(not ^boolean show-typography?))
[:div {:class (stl/css :asset-title)}
[:span {:class (stl/css :no-found-icon)}
deprecated-icon/search]
[:span {:class (stl/css :no-found-icon-wrapper)}
[:> icon* {:icon-id i/search
:size "l"
:class (stl/css :no-found-icon)}]]
[:span {:class (stl/css :no-found-text)}
(tr "workspace.assets.not-found")]])])]))
(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)
file-name (:name file)
page-id (dm/get-in file [:data :pages 0])
@@ -381,7 +399,8 @@
:page-id page-id
:file-name file-name
:is-open open?
:is-local is-local}]
:is-local is-local
:is-tokens-source is-tokens-source}]
(when ^boolean open?
[:> file-library-content*
@@ -4,95 +4,131 @@
//
// Copyright (c) KALEIDOS SUBSIDIARY SL
@use "ds/typography.scss" as t;
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/typography.scss" as *;
.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;
grid-auto-rows: max-content;
gap: deprecated.$s-4;
height: 100%;
}
.file-name {
@include t.use-typography("body-small");
display: flex;
justify-content: flex-start;
align-items: center;
flex-grow: 100;
height: 100%;
gap: var(--sp-xs);
block-size: 100%;
}
.loading {
@include t.use-typography("body-small");
@include use-typography("body-small");
display: flex;
align-items: center;
justify-content: flex-start;
height: deprecated.$s-32;
padding-left: calc(deprecated.$s-12 + deprecated.$s-2);
block-size: $sz-32;
padding-inline-start: calc(var(--sp-m) + var(--sp-xxs));
color: var(--color-foreground-secondary);
}
.special-title {
@include deprecated.text-ellipsis;
color: var(--title-foreground-color-hover);
margin-left: deprecated.$s-2;
box-sizing: border-box;
inline-size: 100%;
min-inline-size: 0;
display: flex;
align-items: center;
gap: var(--sp-xs);
color: var(--color-foreground-primary);
padding-inline-start: var(--sp-xxs);
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 {
@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;
width: deprecated.$s-28;
border-radius: deprecated.$br-8;
display: grid;
place-content: center;
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 {
@extend %button-icon;
&:hover {
--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);
fill: var(--title-foreground-color-hover);
&:active {
--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 {
width: 100%;
inline-size: 100%;
display: grid;
grid-auto-rows: max-content;
gap: deprecated.$s-4;
gap: var(--sp-xs);
}
.asset-title {
margin-left: deprecated.$s-28;
margin-inline-start: px2rem(28);
display: flex;
flex-direction: column;
align-items: center;
gap: deprecated.$s-8;
gap: var(--sp-s);
}
.no-found-icon {
@include deprecated.flex-center;
background-color: var(--not-found-background-color);
border-radius: deprecated.$br-circle;
height: deprecated.$s-48;
width: deprecated.$s-48;
svg {
@extend %button-icon;
height: deprecated.$s-24;
width: deprecated.$s-24;
stroke: var(--not-found-foreground-color);
}
.no-found-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--color-background-tertiary);
border-radius: $br-circle;
block-size: $sz-48;
inline-size: $sz-48;
color: var(--color-foreground-secondary);
}
.no-found-text {
@include deprecated.body-small-typography;
@include use-typography("body-small");
color: var(--not-found-foreground-color);
color: var(--color-foreground-secondary);
}
@@ -15,6 +15,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.texts-events :as dwte]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -170,7 +171,7 @@
(mf/deps file-id prefix)
(fn [_]
(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)
:on-drag-enter on-drag-enter
@@ -278,7 +279,7 @@
(mf/deps file-id)
(fn [_]
(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
(mf/use-fn
@@ -5,6 +5,7 @@
[app.common.path-names :as cpn]
[app.common.types.shape.layout :as ctsl]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.helpers :as dh]
@@ -49,21 +50,18 @@
(mf/defc selected-set-info*
{::mf/private true}
[{:keys [tokens-lib selected-token-set-id]}]
[{:keys [tokens-lib tokens-status selected-token-set-id]}]
(let [selected-token-set
(mf/with-memo [tokens-lib selected-token-set-id]
(when 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?
(mf/use-fn
(mf/deps active-token-sets-names)
(fn [name]
(contains? active-token-sets-names name)))]
(mf/deps tokens-status)
(fn [id]
(ctos/set-active? tokens-status id)))]
[:div {:class (stl/css :sets-header-container)}
[:> text* {:as "span"
:typography "headline-small"
@@ -71,7 +69,7 @@
:data-testid "active-token-set-title"}
(tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
(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")}
;; NOTE: when no set in tokens-lib, the selected-token-set-id
;; will be `nil`, so for properly hide the inactive message we
@@ -84,7 +82,7 @@
(mf/defc tokens-section*
{::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)
selected (mf/deref refs/selected-shapes)
@@ -305,6 +303,7 @@
:on-delete-node delete-node}]
[:> selected-set-info* {:tokens-lib tokens-lib
:tokens-status tokens-status
:selected-token-set-id selected-token-set-id}]
(for [type filled-group]
@@ -19,6 +19,7 @@
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.hooks :as hooks]
[app.util.clipboard :as clipboard]
@@ -346,31 +347,39 @@
(defn default-actions
[{:keys [token selected-token-set-id on-delete-token errors]}]
(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-duplicate-token #(st/emit! (dwtl/duplicate-token (:id token)))]
[{:title (tr "workspace.tokens.edit")
:no-selectable true
:action (fn [event]
(let [{:keys [key fields]} modal]
(dom/stop-propagation event)
(st/emit! (dwtl/assign-token-context-menu nil)
(modal/show key {:x (.-clientX ^js event)
:y (.-clientY ^js event)
:position :right
:fields fields
:initial-errors errors
:action "edit"
:selected-token-set-id selected-token-set-id
:token token}))))}
{:title (tr "workspace.tokens.duplicate")
:no-selectable true
:action on-duplicate-token}
{:title (tr "workspace.tokens.copy-name")
:no-selectable true
:action on-copy-name}
{:title (tr "workspace.tokens.delete")
:no-selectable true
:action #(on-delete-token token)}]))
(concat
[]
(when can-edit-tokens?
[{:title (tr "workspace.tokens.edit")
:no-selectable true
:action (fn [event]
(let [{:keys [key fields]} modal]
(dom/stop-propagation event)
(st/emit! (dwtl/assign-token-context-menu nil)
(modal/show key {:x (.-clientX ^js event)
:y (.-clientY ^js event)
:position :right
:fields fields
:initial-errors errors
:action "edit"
:selected-token-set-id selected-token-set-id
:token token}))))}
{:title (tr "workspace.tokens.duplicate")
:no-selectable true
:action on-duplicate-token}])
[{:title (tr "workspace.tokens.copy-name")
:no-selectable true
: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
[shapes]
@@ -92,8 +92,8 @@
(not (and (some? edition)
(= :text (:type (get objects edition))))))
can-edit?
(mf/use-ctx ctx/can-edit?)
can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
is-selected-inside-layout (d/nilv is-selected-inside-layout false)
@@ -177,7 +177,7 @@
:aria-controls (dm/str "token-tree-" (name type))
:on-toggle-expand on-toggle-open-click
:icon (token-section-icon type)}
(when can-edit?
(when can-edit-tokens?
[:> icon-button* {:id (str "add-token-button-" title)
:icon "add"
:aria-label (tr "workspace.tokens.add-token" title)
@@ -140,14 +140,17 @@
on-pill-context-menu
on-node-context-menu]}]
(let [separator "."
raw-tree (mf/with-memo [tokens]
(cpn/build-tree-root tokens separator))
permissions (mf/use-ctx ctx/permissions)
can-edit? (:can-edit permissions)
raw-tree
(mf/with-memo [tokens]
(cpn/build-tree-root tokens separator))
can-edit-file?
(mf/use-ctx ctx/can-edit?)
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]
(when can-edit?
(when can-edit-file?
(on-node-context-menu event node))))
ordered-nodes (mf/with-memo [raw-tree]
@@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.sets
(:require
[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.refs :as refs]
[app.main.store :as st]
@@ -19,27 +20,32 @@
(st/emit! (dwtl/clear-tokens-paths)
(dwtl/set-selected-token-set-id id)))
(defn- on-toggle-token-set-click [name]
(st/emit! (dwtl/toggle-token-set name)))
(defn- on-toggle-token-set-click [id]
(st/emit! (dwtl/toggle-token-set id)))
(defn- on-toggle-token-set-group-click [path]
(st/emit! (dwtl/toggle-token-set-group path)))
(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))
can-edit?
(mf/use-ctx ctx/can-edit?)
can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
token-set-active?
(mf/use-fn
(mf/deps tokens-lib)
(fn [name]
(when tokens-lib
(ctob/token-set-active? tokens-lib name))))
(mf/deps tokens-status)
(fn [set-id]
(ctos/set-active? tokens-status set-id)))
token-set-group-active?
(mf/use-fn
@@ -49,17 +55,17 @@
on-reset-edition
(mf/use-fn
(mf/deps can-edit?)
(mf/deps can-edit-tokens?)
(fn [_]
(when can-edit?
(when can-edit-tokens?
(st/emit! (dwtl/clear-token-set-edition)
(dwtl/clear-token-set-creation)))))
on-start-edition
(mf/use-fn
(mf/deps can-edit?)
(mf/deps can-edit-tokens?)
(fn [id]
(when can-edit?
(when can-edit-tokens?
(st/emit! (dwtl/start-token-set-edition id)))))]
[:> controlled-sets-list*
@@ -75,7 +81,6 @@
:edition-id edition-id
:origin "set-panel"
:can-edit can-edit?
:on-start-edition on-start-edition
:on-reset-edition on-reset-edition
@@ -86,8 +86,8 @@
(mf/defc inline-add-button*
[]
(let [can-edit? (mf/use-ctx ctx/can-edit?)]
(if can-edit?
(let [can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?)]
(if can-edit-tokens?
[:div {:class (stl/css :empty-sets-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(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
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?)
label-id
@@ -118,11 +121,11 @@
on-context-menu
(mf/use-fn
(mf/deps is-editing id path can-edit?)
(mf/deps is-editing id path can-edit-tokens?)
(fn [event]
(dom/prevent-default 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
{:position (dom/get-client-position event)
:is-group true
@@ -141,12 +144,12 @@
on-checkbox-click
(mf/use-fn
(mf/deps on-toggle path can-edit?)
(mf/deps on-toggle path can-edit-file?)
#(on-toggle path))
on-edit-submit'
(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-drop
@@ -196,7 +199,7 @@
label]
[:> checkbox*
{:on-click on-checkbox-click
:disabled (not can-edit?)
:disabled (not can-edit-file?)
:checked (case is-active
:all true
:partial "mixed"
@@ -207,7 +210,11 @@
[{: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]}]
(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
(mf/use-fn
@@ -220,11 +227,11 @@
on-context-menu
(mf/use-fn
(mf/deps is-editing id path can-edit?)
(mf/deps is-editing id path can-edit-tokens?)
(fn [event]
(dom/prevent-default 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
{:position (dom/get-client-position event)
:is-group false
@@ -244,7 +251,7 @@
(fn [event]
(dom/stop-propagation event)
(when (fn? on-toggle)
(on-toggle (ctob/get-name set)))))
(on-toggle (ctob/get-id set)))))
on-edit-submit'
(mf/use-fn
@@ -305,15 +312,15 @@
label]
[:> checkbox*
{:on-click on-checkbox-click
:disabled (not can-edit?)
:disabled (not can-edit-file?)
:arial-label (tr "workspace.tokens.select-set")
:checked is-active}]])]))
(mf/defc token-sets-tree*
[{:keys [is-draggable
selected
is-token-set-group-active
is-token-set-active
is-token-set-group-active
on-start-edition
on-reset-edition
on-edit-submit-set
@@ -418,7 +425,7 @@
:set token-set
:label (peek path)
: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-draggable is-draggable
:is-new false
@@ -452,15 +459,15 @@
new-path
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-group-active) "expected a function for `is-token-set-group-active` prop")
(let [theme-modal? (= origin "theme-modal")
can-edit? (mf/use-ctx ctx/can-edit?)
draggable? (and (not theme-modal?) can-edit?)
empty-state? (and theme-modal?
(empty? token-sets)
(not new-path))
(let [theme-modal? (= origin "theme-modal")
can-edit-tokens? (mf/use-ctx ctx/can-edit-tokens?)
draggable? (and (not theme-modal?) can-edit-tokens?)
empty-state? (and theme-modal?
(empty? token-sets)
(not new-path))
;; NOTE: on-reset-edition and on-start-edition function can
;; come as nil, in this case we need to provide a safe
@@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.sidebar
(:require-macros [app.main.style :as stl])
(:require
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob]
[app.config :as cf]
[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.lists :as tsetslist]
[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.i18n :refer [tr]]
[rumext.v2 :as mf]
@@ -34,8 +36,11 @@
(mf/defc token-sets-list*
{::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))
selected-token-set-id
@@ -60,20 +65,25 @@
(mf/defc token-management-section*
{::mf/private true}
[{:keys [resize-height] :as props}]
[{:keys [resize-height current-file-data] :as props}]
(let [can-edit?
(mf/use-ctx ctx/can-edit?)]
(let [can-edit-tokens?
(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*]
[:section {:data-testid "token-management-sidebar"
:class (stl/css :token-management-section-wrapper)
:style {"--resize-height" (str resize-height "px")}}
[:> themes-header*]
(when (not= tokens-source (:id current-file-data))
[:> tokens-source-info* {:tokens-source tokens-source}])
[:> themes-header* {:tokens-source tokens-source}]
[:div {:class (stl/css :sidebar-header)}
[:> title-bar* {:title (tr "labels.sets")}
(when can-edit?
(when can-edit-tokens?
[:> tsetslist/add-button*])]]
[:> token-sets-list* props]]]))
@@ -83,8 +93,8 @@
(let [show-menu* (mf/use-state false)
show-menu? (deref show-menu*)
can-edit?
(mf/use-ctx ctx/can-edit?)
can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
open-menu
(mf/use-fn
@@ -124,7 +134,7 @@
:on-close close-menu
:id "tokens-menu"
:class (stl/css :import-export-menu)}
(when can-edit?
(when can-edit-tokens?
[:> dropdown-menu-item* {:class (stl/css :import-export-menu-item)
:on-click on-modal-show}
[:div {:class (stl/css :import-menu-item)}
@@ -133,31 +143,42 @@
:on-click on-export}
(tr "labels.export")]]
(when (and can-edit? (contains? cf/flags :token-base-font-size))
(when (and can-edit-tokens? (contains? cf/flags :token-base-font-size))
[:> icon-button* {:variant "secondary"
:icon i/settings
:aria-label "Settings"
:on-click open-settings-modal}])]))
(mf/defc tokens-sidebar-tab*
[{:keys [tokens-lib] :as props}]
[{:keys [] :as props}]
(let [{on-pointer-down-pages :on-pointer-down
on-lost-pointer-capture-pages :on-lost-pointer-capture
on-pointer-move-pages :on-pointer-move
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)}
[:> token-management-section*
{:resize-height size-pages-opened
:tokens-lib tokens-lib}]
[: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*]]))
current-file-data
(mf/deref refs/workspace-data)
can-edit-file?
(mf/use-ctx ctx/can-edit?)
can-edit-tokens?
(mf/with-memo [can-edit-file? current-file-data]
(and can-edit-file?
(cfo/editable-tokens? current-file-data)))]
[:> (mf/provider ctx/can-edit-tokens?) {:value can-edit-tokens?}
[: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*]]]))
@@ -26,7 +26,7 @@
overflow-y: auto;
scrollbar-gutter: stable;
position: relative;
padding-block-end: var(--sp-l);
padding-block: var(--sp-m) var(--sp-l);
}
.tokens-section-wrapper {
@@ -19,12 +19,12 @@
(mf/defc themes-header*
{::mf/private true}
[]
[{:keys [tokens-source]}]
(let [ordered-themes
(mf/deref refs/workspace-token-themes-no-hidden)
can-edit?
(mf/use-ctx ctx/can-edit?)
can-edit-tokens?
(mf/use-ctx ctx/can-edit-tokens?)
open-modal
(mf/use-fn
@@ -38,18 +38,18 @@
[:div {:class (stl/css :empty-theme-wrapper)}
[:> text* {:as "span" :typography "body-small" :class (stl/css :empty-state-message)}
(tr "workspace.tokens.no-themes")]
(when can-edit?
(when can-edit-tokens?
[:button {:on-click open-modal
:class (stl/css :create-theme-button)}
(tr "workspace.tokens.create-one")])]
(if can-edit?
(if can-edit-tokens?
[:div {:class (stl/css :theme-selector-wrapper)}
[:> theme-selector*]
[:> theme-selector* {:tokens-source tokens-source}]
[:> button* {:variant "secondary"
:type "button"
:class (stl/css :edit-theme-button)
:on-click open-modal}
(tr "labels.edit")]]
[:div {:title (when-not can-edit?
[:div {:title (when-not can-edit-tokens?
(tr "workspace.tokens.no-permission-themes"))}
[:> theme-selector*]]))]))
[:> theme-selector* {:tokens-source tokens-source}]]))]))
@@ -9,7 +9,7 @@
@use "ds/spacing.scss" as *;
.themes-wrapper {
padding: var(--sp-m) 0 0 var(--sp-m);
padding: 0 0 0 var(--sp-m);
}
.themes-header {
@@ -10,15 +10,16 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.logic.tokens :as clt]
[app.common.schema :as sm]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.constants :refer [max-input-length]]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.combobox :refer [combobox*]]
@@ -66,7 +67,7 @@
(mf/defc themes-overview
[{: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)
create-theme
@@ -95,7 +96,7 @@
[:ul {:class (stl/css :theme-group-rows-wrapper)}
(for [[_ {:keys [id name] :as theme}] themes
: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
(fn [e]
(dom/prevent-default e)
@@ -249,10 +250,15 @@
(defn- make-lib-with-theme
[theme sets]
(let [tlib (-> (ctob/make-tokens-lib)
(ctob/add-theme theme))
tlib (reduce ctob/add-set tlib sets)]
(ctob/activate-theme tlib (ctob/get-id theme))))
(as-> (ctob/make-tokens-lib) $
(ctob/add-theme $ theme)
(reduce ctob/add-set $ sets)))
(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*
[{:keys [change-view theme on-save is-editing has-prev-view]}]
@@ -262,7 +268,8 @@
current-theme* (mf/use-state 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
on-back (mf/use-fn
@@ -306,35 +313,44 @@
;; Sets tree handlers
token-set-group-active?
(mf/use-fn
(mf/deps current-theme)
(mf/deps status-with-theme lib-with-theme)
(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?
(mf/use-fn
(mf/deps current-theme)
(fn [name]
(contains? (:sets current-theme) name)))
(mf/deps status-with-theme)
(fn [id]
(ctos/set-active? status-with-theme id)))
on-toggle-token-set
(mf/use-fn
(mf/deps current-theme)
(fn [set-name]
(swap! current-theme* #(ctob/toggle-set % set-name))))
(mf/deps current-theme tokens-lib)
(fn [set-id]
(let [set (ctob/get-set tokens-lib set-id)]
(swap! current-theme* #(ctob/toggle-set % (ctob/get-name set))))))
on-toggle-token-set-group
(mf/use-fn
(mf/deps current-theme ordered-token-sets)
(fn [group-path]
(swap! current-theme* (fn [theme']
(let [lib' (make-lib-with-theme theme' ordered-token-sets)]
(clt/toggle-token-set-group group-path lib' theme'))))))
; TODOstatus ver si se puede añadir una función en files.tokens o types.tokens
; 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
(mf/use-fn
(mf/deps on-toggle-token-set)
(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)))))]
[:div {:class (stl/css :themes-modal-wrapper)}
@@ -450,12 +466,14 @@
::mf/register modal/components
::mf/register-as :tokens/themes}
[]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)
:data-testid "token-theme-update-create-modal"}
[:> icon-button* {:class (stl/css :close-btn)
:on-click modal/hide!
:aria-label (tr "labels.close")
:variant "action"
:icon i/close}]
[:> themes-modal-body*]]])
(let [tokens-status (mf/deref refs/tokens-status)]
[:> (mf/provider ctx/tokens-status) {:value tokens-status}
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)
:data-testid "token-theme-update-create-modal"}
[:> icon-button* {:class (stl/css :close-btn)
:on-click modal/hide!
:aria-label (tr "labels.close")
:variant "action"
:icon i/close}]
[:> themes-modal-body*]]]]))
@@ -8,13 +8,16 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
[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.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as hooks]
@@ -24,17 +27,16 @@
[rumext.v2 :as mf]))
(mf/defc themes-list*
[{:keys [themes active-theme-paths on-close is-grouped]}]
[{:keys [themes tokens-status on-close is-grouped]}]
(when (seq themes)
[:ul {:class (stl/css :theme-options)}
(for [[_ {:keys [id name] :as theme}] themes
:let [theme-id (ctob/get-theme-path theme)
selected? (get active-theme-paths theme-id)
:let [selected? (ctos/theme-active? tokens-status id)
select-theme (fn [e]
(dom/stop-propagation e)
(st/emit! (dwtl/toggle-token-theme-active id))
(on-close))]]
[:li {:key theme-id
[:li {:key (str "theme-" id)
:role "option"
:aria-selected selected?
:class (stl/css-case
@@ -48,47 +50,68 @@
:class (stl/css-case :check-icon true
:check-icon-visible selected?)}]])]))
(defn- open-tokens-theme-modal
[]
(modal/show! :tokens/themes {}))
(mf/defc theme-options*
[{:keys [active-theme-paths themes on-close]}]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:> themes-list* {:themes themes
:active-theme-paths active-theme-paths
:on-close on-close
:is-grouped true}]])
[:li {:class (stl/css :separator)
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true
:checked-element-button true)
:role "option"
:on-click open-tokens-theme-modal}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")]
[:> icon* {:icon-id i/arrow-right :aria-hidden true}]]])
[{:keys [tokens-lib tokens-status tokens-source can-edit-tokens team-id on-close]}]
(let [themes
(mf/with-memo [tokens-lib]
(ctob/get-theme-tree-no-hidden tokens-lib))
edit-token-themes
(fn []
(if can-edit-tokens
(modal/show! :tokens/themes {})
(st/emit! (rt/nav :workspace
{:team-id team-id
:file-id tokens-source
:layout :tokens}
::rt/new-window true))))]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:> themes-list* {:themes themes
: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*
[{:keys []}]
[{:keys [tokens-source]}]
(let [;; Store
active-theme-paths (mf/deref refs/workspace-active-theme-paths-no-hidden)
active-themes-count (count active-theme-paths)
themes (mf/deref refs/workspace-token-theme-tree-no-hidden)
can-edit? (:can-edit (deref refs/permissions))
tokens-lib (mf/use-ctx ctx/tokens-lib)
tokens-status (mf/use-ctx ctx/tokens-status)
can-edit-file? (mf/use-ctx ctx/can-edit?)
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
current-label (cond
(> active-themes-count 1) (tr "workspace.tokens.active-themes" active-themes-count)
(= active-themes-count 1) (some->> (first active-theme-paths)
(ctob/split-theme-path)
(remove empty?)
(str/join " / "))
(= active-themes-count 1) (-> (first active-themes)
(ctob/get-theme-path true))
:else (tr "workspace.tokens.no-active-theme"))
;; State
@@ -105,9 +128,9 @@
on-open-dropdown
(mf/use-fn
(mf/deps can-edit?)
(mf/deps can-edit-file?)
(fn [event]
(when can-edit?
(when can-edit-file?
(when-let [node (dom/get-current-target event)]
(let [rect (dom/get-bounding-rect node)]
(swap! state* assoc
@@ -117,14 +140,14 @@
container (hooks/use-portal-container :popup)]
[:div {:on-click on-open-dropdown
:disabled (not can-edit?)
:disabled (not can-edit-file?)
:aria-expanded is-open?
:aria-haspopup "listbox"
:tab-index "0"
:role "combobox"
:data-testid "theme-select"
: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)}
current-label]
[:> icon* {:icon-id i/arrow-down :class (stl/css :dropdown-button) :aria-hidden true}]
@@ -140,7 +163,10 @@
[:& dropdown {:show is-open?
:on-close on-close-dropdown}
[:> theme-options* {:active-theme-paths active-theme-paths
:themes themes
[:> theme-options* {:tokens-lib tokens-lib
:tokens-status tokens-status
:tokens-source tokens-source
:can-edit-tokens can-edit-tokens
:team-id team-id
:on-close on-close-dropdown}]]])
container))]))
@@ -0,0 +1,52 @@
;; 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.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.typography.text :refer [text*]]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc tokens-source-info*
{::mf/private true}
[{:keys [tokens-source] :as props}]
(let [files (mf/deref refs/files)
tokens-source-file (get files tokens-source)
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))))]
[:div {:class (stl/css :tokens-source-wrapper)}
[:div {:class (stl/css :tokens-source-header)}
[:> text* {:as "span"
:typography "headline-small"
:class (stl/css :tokens-source-title)}
(:name tokens-source-file)]
[:> text* {:as "span"
:typography "body-medium"
:class (stl/css :tokens-source-description)}
(tr "workspace.tokens.connected-library")]]
[:> icon-button*
{:variant "ghost"
:aria-label (tr "workspace.tokens.open-connected-library")
:on-click open-library-new-window
:icon "open-link"}]]))
@@ -0,0 +1,40 @@
// 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 *;
.tokens-source-wrapper {
display: flex;
padding: 0 0 0 var(--sp-m);
}
.tokens-source-header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--sp-s);
flex-grow: 1;
padding: var(--sp-s);
}
.tokens-source-title {
flex: 1 1 auto;
min-width: 0;
color: var(--color-foreground-primary);
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;
}
+1 -1
View File
@@ -1177,7 +1177,7 @@
(let [file-id (:current-file-id @st/state)
library-id (uuid/parse library-id)]
(->> st/stream
(rx/filter (ptk/type? ::dwl/attach-library-finished))
(rx/filter (ptk/type? ::dwl/link-file-to-library-finished))
(rx/take 1)
(rx/subs! #(resolve (library-proxy plugin-id library-id)) reject))
(st/emit! (-> (dwl/link-file-to-library file-id library-id)
+38 -12
View File
@@ -10,8 +10,10 @@
[app.common.files.tokens :as cfo]
[app.common.json :as json]
[app.common.schema :as sm]
[app.common.types.file :as ctf]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.application :as dwta]
@@ -204,6 +206,7 @@
(ctob/get-tokens set-id)))
:set
(fn [_ value]
(u/check-editable-tokens file-id)
(st/emit! (-> (dwtl/update-token set-id id {:name value})
(se/add-event plugin-id))))}
@@ -230,6 +233,7 @@
base))
:set
(fn [_ value]
(u/check-editable-tokens file-id)
(let [token (u/locate-token file-id set-id id)
value (cond-> value
(= :font-family (:type token))
@@ -243,7 +247,8 @@
(fn [_]
(let [token (u/locate-token file-id set-id 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)))}
:resolvedValueString
@@ -253,7 +258,8 @@
(fn [_]
(let [token (u/locate-token file-id set-id 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))))}
:description
@@ -265,6 +271,7 @@
:schema cfo/schema:token-description
:set
(fn [_ value]
(u/check-editable-tokens file-id)
(st/emit! (-> (dwtl/update-token set-id id {:description value})
(se/add-event :plugin-id))))}
@@ -275,6 +282,7 @@
;; - use this function in dwtl/duplicate-token
;; - return the new token proxy using the locally forced id
;; - do the same with sets and themes
(u/check-editable-tokens file-id)
(let [token (u/locate-token file-id set-id id)
token' (ctob/make-token (-> (datafy token)
(dissoc :id
@@ -285,6 +293,7 @@
:remove
(fn []
(u/check-editable-tokens file-id)
(st/emit! (-> (dwtl/delete-token set-id id)
(se/add-event plugin-id))))
@@ -337,6 +346,7 @@
id)
:set
(fn [_ name]
(u/check-editable-tokens file-id)
(let [set (u/locate-token-set file-id id)]
(st/emit! (dwtl/rename-token-set set name))))}
@@ -345,19 +355,16 @@
:enumerable false
:get
(fn [_]
(let [tokens-lib (u/locate-tokens-lib file-id)
set (u/locate-token-set file-id id)]
(ctob/token-set-active? tokens-lib (ctob/get-name set))))
(let [tokens-status (u/locate-tokens-status file-id)]
(ctos/set-active? tokens-status id)))
:schema ::sm/boolean
:set
(fn [_ value]
(let [set (u/locate-token-set file-id id)]
(st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) value))))}
(st/emit! (dwtl/set-enabled-token-set id value)))} ;; This can be done even with tokens in an external library
:toggleActive
(fn [_]
(let [set (u/locate-token-set file-id id)]
(st/emit! (dwtl/toggle-token-set (ctob/get-name set)))))
(st/emit! (dwtl/toggle-token-set id)))
:tokens
{:this true
@@ -416,6 +423,7 @@
(sm/update-properties assoc :decode/json cfo/convert-dtcg-token))]))
:decode/options {:key-fn identity}
:fn (fn [attrs]
(u/check-editable-tokens file-id)
(let [tokens-lib (u/locate-tokens-lib file-id)
token (ctob/make-token attrs)
;; Resolve against all tokens in the library (including those
@@ -441,6 +449,7 @@
:duplicate
(fn []
(u/check-editable-tokens file-id)
(let [id-ref (atom nil)]
(st/emit! (dwtl/duplicate-token-set id {:id-ref id-ref}))
(when (some? @id-ref)
@@ -448,6 +457,7 @@
:remove
(fn []
(u/check-editable-tokens file-id)
(st/emit! (dwtl/delete-token-set id))))))
(defn token-theme-proxy? [p]
@@ -501,6 +511,7 @@
(:id theme)))
:set
(fn [_ 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)))))}
@@ -517,6 +528,7 @@
(:group theme)))
:set
(fn [_ name]
(u/check-editable-tokens file-id)
(let [theme (u/locate-token-theme file-id id)]
(when name
(st/emit! (dwtl/update-token-theme id (assoc theme :name name))))))}
@@ -526,8 +538,8 @@
:enumerable false
:get
(fn [_]
(let [tokens-lib (u/locate-tokens-lib file-id)]
(ctob/theme-active? tokens-lib id)))
(let [tokens-status (u/locate-tokens-status file-id)]
(ctos/theme-active? tokens-status id)))
:schema ::sm/boolean
:set
(fn [_ value]
@@ -554,6 +566,7 @@
{:enumerable false
:schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]]
:fn (fn [set-arg]
(u/check-editable-tokens file-id)
(let [set-name (token-set-name (resolve-token-set file-id set-arg))
theme (u/locate-token-theme file-id id)]
(when (and set-name theme)
@@ -563,6 +576,7 @@
{:enumerable false
:schema [:tuple [:or [:fn token-set-proxy?] ::sm/uuid]]
:fn (fn [set-arg]
(u/check-editable-tokens file-id)
(let [set-name (token-set-name (resolve-token-set file-id set-arg))
theme (u/locate-token-theme file-id id)]
(when (and set-name theme)
@@ -570,6 +584,7 @@
:duplicate
(fn []
(u/check-editable-tokens file-id)
(let [theme (u/locate-token-theme file-id id)
theme' (ctob/make-token-theme (-> (datafy theme)
(dissoc :id
@@ -579,6 +594,7 @@
:remove
(fn []
(u/check-editable-tokens file-id)
(st/emit! (dwtl/delete-token-theme id)))))
(defn tokens-catalog
@@ -588,6 +604,14 @@
:$plugin {:enumerable false :get (constantly plugin-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
{:this true
:enumerable false
@@ -619,6 +643,7 @@
nil)
(sm/dissoc-key :id))]) ;; We don't allow plugins to set the id
:fn (fn [attrs]
(u/check-editable-tokens file-id)
(let [theme (ctob/make-token-theme attrs)]
(st/emit! (dwtl/create-token-theme theme))
(token-theme-proxy plugin-id file-id (:id theme))))}
@@ -638,6 +663,7 @@
(sm/merge [:map [:active {:optional true} ::sm/boolean]]))]
:fn (fn [attrs]
(u/check-editable-tokens file-id)
(let [active? (boolean (:active attrs))
attrs (-> attrs
(dissoc :active)
@@ -648,7 +674,7 @@
;; requested. Enabling only adds the set name to the hidden theme,
;; so it does not depend on the create event having propagated yet.
(when active?
(st/emit! (dwtl/set-enabled-token-set (ctob/get-name set) true)))
(st/emit! (dwtl/set-enabled-token-set (ctob/get-id set) true)))
;; Pass the set name as `initial-name` so the proxy can resolve
;; it immediately, before the async `st/emit!` above propagates
;; the new set into `@st/state`.
+19 -2
View File
@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.i18n :as i18n :refer [tr]]
[app.common.schema :as sm]
[app.common.schema.messages :as csm]
@@ -67,8 +68,18 @@
(defn locate-tokens-lib
[file-id]
(let [file (locate-file file-id)]
(->> file :data :tokens-lib)))
(let [file (locate-file file-id)
file-data (ctf/file-data file)
tokens-source-id (cfo/get-effective-tokens-source file-data)
tokens-file (locate-file tokens-source-id)
tokens-file-data (ctf/file-data tokens-file)]
(cfo/get-tokens-lib tokens-file-data)))
(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
[file-id id]
@@ -266,6 +277,12 @@
(boolean
(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?
[plugin-id]
(boolean
-24
View File
@@ -880,25 +880,6 @@
(h/call wasm/internal-module "_store_image")
true)))))
(defn- store-image-url!
"Registers the public URL an image was loaded from so SVG export can emit a
linked `<image href>` instead of a Skia base64 embed."
[image-id url]
(when (and (wasm/live?) (some? url) (not (str/blank? url)))
(let [buffer (uuid/get-u32 image-id)
encoder (js/TextEncoder.)
encoded (.encode encoder url)
size (.-byteLength encoded)
offset (mem/alloc size)
heap (mem/get-heap-u8)]
(.set heap encoded offset)
(h/call wasm/internal-module "_store_image_url"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))
true)))
(defn- store-image-texture
"Creates a WebGL texture from a decoded image and passes the texture ID to
WASM. This avoids decoding the image twice (once in browser, once in WASM)."
@@ -941,7 +922,6 @@
so Skia rasterizes them."
[shape-id image-id thumbnail?]
(let [url (cf/resolve-file-media {:id image-id} thumbnail?)]
(store-image-url! image-id url)
{:key url
:thumbnail? thumbnail?
:callback
@@ -979,8 +959,6 @@
(aget buffer 2)
(aget buffer 3)
thumbnail?)]
;; Always register the URL (SVG export needs it even when bytes are cached).
(store-image-url! id (cf/resolve-file-media {:id id} thumbnail?))
(when (zero? cached-image?)
(fetch-image shape-id id thumbnail?)))))
@@ -1015,7 +993,6 @@
(aget buffer 2)
(aget buffer 3)
thumbnail?)]
(store-image-url! id (cf/resolve-file-media {:id id} thumbnail?))
(when (zero? cached-image?)
(fetch-image shape-id id thumbnail?))))
(types.fills/get-image-ids fills))))))
@@ -1044,7 +1021,6 @@
(aget buffer 2)
(aget buffer 3)
thumbnail?)]
(store-image-url! image-id (cf/resolve-file-media {:id image-id} thumbnail?))
(when (zero? cached-image?)
(fetch-image shape-id image-id thumbnail?))))
image-ids))))
@@ -14,6 +14,7 @@
[app.common.types.text :as txt]
[app.common.uuid :as uuid]
[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]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths]))
@@ -312,7 +313,7 @@
store (ths/setup-store file)
events [;; Pre-select the text shape so add-typography can find it
(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id}))
(dwt/add-typography file-id)]]
(dwte/add-typography file-id)]]
(ths/run-store
store done events
@@ -339,7 +340,7 @@
file-id (:id file)
store (ths/setup-store file)
events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id}))
(dwt/add-typography file-id)]]
(dwte/add-typography file-id)]]
(ths/run-store
store done events
@@ -366,7 +367,7 @@
file-id (:id file)
store (ths/setup-store file)
events [(fn [state] (assoc-in state [:workspace-local :selected] #{shape-id}))
(dwt/add-typography file-id)]]
(dwte/add-typography file-id)]]
(ths/run-store
store done events
@@ -6,9 +6,11 @@
(ns frontend-tests.helpers.state
(:require
[app.common.data :as d]
[app.common.pprint :refer [pprint]]
[app.common.schema :as sm]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.tokens :as ctht]
[app.main.data.workspace.layout :as layout]
[app.main.features :as features]
[beicon.v2.core :as rx]
@@ -34,12 +36,21 @@
(defn setup-store
([file] (setup-store file nil))
([file {:keys [renderer] :as _opts}]
(let [state (-> initial-state
([file {:keys [renderer libraries tokens-source-library] :as _opts
:or {libraries [] tokens-source-library nil}}]
(let [file (cond-> file
(some? tokens-source-library)
(ctht/set-tokens-source tokens-source-library))
libraries (map #(assoc %
:library-of (:id file)
:is-shared true)
libraries)
files (d/index-by :id (cons file libraries))
state (-> initial-state
(assoc :current-file-id (:id file)
:current-page-id (cthf/current-page-id file)
:permissions {:can-edit true}
:files {(:id file) file})
:files files)
(cond-> (some? renderer)
(assoc-in [:profile :props :renderer] renderer)))
store (ptk/store {:state state :on-error on-error})]
@@ -9,11 +9,11 @@
[app.common.math :as mth]
[app.common.test-helpers.components :as cthc]
[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.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
@@ -35,29 +35,31 @@
(defn- setup-base-file
[]
(-> (cthf/sample-file :file1)
(ctht/add-tokens-lib)
(ctht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :test-token-1)
:name "test-token-1"
:type :border-radius
:value 25))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :test-token-2)
:name "test-token-2"
:type :border-radius
:value 50))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :test-token-3)
:name "test-token-3"
:type :border-radius
:value 75))))
(-> (ctht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :test-theme)
:name "test-theme"
:sets #{"test-token-set"}))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :test-token-1)
:name "test-token-1"
:type :border-radius
:value 25))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :test-token-2)
:name "test-token-2"
:type :border-radius
:value 50))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :test-token-3)
:name "test-token-3"
:type :border-radius
:value 75)))
:status-fn #(-> %
(ctos/set-tokens-status #{(cthi/id :test-theme)}
#{(cthi/id :test-token-set)})))
(ctho/add-frame :frame1)
(ctht/apply-token-to-shape :frame1 "test-token-1" [:r1 :r2 :r3 :r4] [:r1 :r2 :r3 :r4] 25)))
@@ -323,44 +325,44 @@
(t/async
done
(let [;; ==== Setup
file (-> (cthf/sample-file :file1)
(ctht/add-tokens-lib)
(ctht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-radius)
:name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-rotation)
:name "token-rotation"
:type :rotation
:value 30))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-opacity)
:name "token-opacity"
:type :opacity
:value 0.7))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-stroke-width)
:name "token-stroke-width"
:type :stroke-width
:value 2))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-color)
:name "token-color"
:type :color
:value "#00ff00"))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-dimensions)
:name "token-dimensions"
:type :dimensions
:value 100))))
file (-> (ctht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :id (cthi/new-id! :test-theme)
:name "test-theme"
:sets #{"test-token-set"}))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-radius)
:name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-rotation)
:name "token-rotation"
:type :rotation
:value 30))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-opacity)
:name "token-opacity"
:type :opacity
:value 0.7))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-stroke-width)
:name "token-stroke-width"
:type :stroke-width
:value 2))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-color)
:name "token-color"
:type :color
:value "#00ff00"))
(ctob/add-token (cthi/id :test-token-set)
(ctob/make-token :id (cthi/new-id! :token-dimensions)
:name "token-dimensions"
:type :dimensions
:value 100)))
:status-fn #(ctos/set-tokens-status % #{(cthi/id :test-theme)} #{(cthi/id :test-token-set)}))
(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-rotation" [:rotation] [:rotation] 30)
@@ -6,11 +6,13 @@
(ns frontend-tests.plugins.tokens-test
(:require
[app.common.files.tokens :as cfo]
[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.tokens :as ctht]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.library-edit :as dwtl]
@@ -107,24 +109,25 @@
done
(let [set-id (cthi/new-id! :token-set)
token-id (cthi/new-id! :spacing-token)
file (-> (cthf/sample-file :file1 :page-label :page1)
(ctho/add-frame :frame1 {:layout :flex})
(ctht/add-tokens-lib)
(ctht/update-tokens-lib
#(-> %
(ctob/add-set
(ctob/make-token-set :id set-id
:name "spacing"))
(ctob/add-theme
(ctob/make-token-theme :name "theme"
:sets #{"spacing"}))
(ctob/set-active-themes #{"/theme"})
(ctob/add-token
set-id
(ctob/make-token :id token-id
:name "spacing.medium"
:type :spacing
:value 16)))))
theme-id (cthi/new-id! :theme)
file (-> (ctht/sample-file-with-tokens
:file-id :file1
:lib-fn #(-> %
(ctob/add-set
(ctob/make-token-set :id set-id
:name "spacing"))
(ctob/add-theme
(ctob/make-token-theme :id theme-id
:name "theme"
:sets #{"spacing"}))
(ctob/add-token
set-id
(ctob/make-token :id token-id
:name "spacing.medium"
:type :spacing
:value 16)))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{set-id}))
(ctho/add-frame :frame1 {:layout :flex}))
store (ths/setup-store file)
_ (set! st/state store)
_ (set! st/stream (ptk/input-stream store))
@@ -191,10 +194,11 @@
;; Demonstrates the bug: resolving the new token against active sets
;; only leaves the reference unresolved.
(let [tokens-lib (inactive-set-library)
tokens-status (cfo/make-tokens-status-from-lib tokens-lib)
token (ctob/make-token {:name "color.bg.default"
:value "{color.gray.50}"
: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))
resolved (ts/resolve-tokens tokens-tree)
{:keys [errors resolved-value]} (get resolved (:name token))]
@@ -234,15 +238,17 @@
(t/deftest token-set-duplicate-returns-the-duplicated-set
(let [file-id (cthi/new-id! :file)
set-id (cthi/new-id! :set)
dup-id (cthi/new-id! :dup)
proxy (ptok/token-set-proxy "plugin-id" file-id set-id)]
(with-redefs [dwtl/duplicate-token-set
dup-id (cthi/new-id! :dup)]
(with-redefs [u/locate-tokens-lib (constantly nil)
u/check-editable-tokens (constantly nil)
dwtl/duplicate-token-set
(mock/stub (fn [id {:keys [id-ref]}]
(t/is (= set-id id))
(reset! id-ref dup-id)
:duplicate-token-set))
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 (= (str dup-id) (.-id dup)))))))
@@ -250,10 +256,10 @@
(let [file-id (cthi/new-id! :file)
theme-id (cthi/new-id! :theme)
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 [])]
(with-redefs [u/locate-token-theme
(with-redefs [u/locate-tokens-lib (constantly nil)
u/check-editable-tokens (constantly nil)
u/locate-token-theme
(fn [_file _theme]
(ctob/make-token-theme :id theme-id
:name "Theme"
@@ -263,18 +269,22 @@
(swap! captured conj {:id id :theme theme})
:update-token-theme)
st/emit! mock/noop]
(.addSet theme set)
(.removeSet theme set)
(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"))))))
(let [set (ptok/token-set-proxy "plugin-id" file-id set-id "Primitives")
theme (ptok/token-theme-proxy "plugin-id" file-id theme-id)]
(.addSet theme set)
(.removeSet theme set)
(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
(let [file-id (cthi/new-id! :file)
set-id (cthi/new-id! :set)
token-id (cthi/new-id! :token)
captured (atom nil)]
(with-redefs [u/locate-token (constantly {:id token-id
(with-redefs [u/locate-tokens-lib (constantly nil)
u/check-editable-tokens (constantly nil)
u/locate-token (constantly {:id token-id
:name "font.primary"
:type :font-family
:value ["Inter"]})
@@ -347,12 +357,14 @@
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom [])
invalid (atom [])]
(with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
(with-redefs [u/locate-tokens-lib (constantly nil)
u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
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})
st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))]
st/emit! (fn ([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)]
(.addSet theme-proxy (str set-id))
(t/is (= #{"Core"} (-> @emitted first :theme :sets)))
@@ -367,12 +379,14 @@
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom [])
invalid (atom [])]
(with-redefs [u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
u/not-valid (fn [_ code value] (swap! invalid conj [code value]))
(with-redefs [u/locate-tokens-lib (constantly nil)
u/locate-token-set (fn [_ id] (when (= id set-id) token-set))
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})
st/emit! (fn ([event] (swap! emitted conj event) nil)
([event & _] (swap! emitted conj event) nil))]
st/emit! (fn ([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)
set-proxy (ptok/token-set-proxy plugin-id file-id set-id "Core")]
(.addSet theme-proxy set-proxy)
@@ -385,18 +399,22 @@
theme-id (uuid/next)
theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light")
emitted (atom [])
errors (atom [])]
(with-redefs [u/locate-token-set (constantly nil)
u/locate-token-theme (fn [_ id] (when (= id theme-id) theme))
invalid (atom [])]
(with-redefs [u/locate-tokens-lib (constantly nil)
u/locate-token-set (constantly nil)
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})
st/emit! (fn ([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)]
;; Non-id, non-proxy arguments are rejected by the schema coercer.
(try (.addSet theme-proxy 42) (catch :default e (swap! errors conj e)))
(try (.removeSet theme-proxy nil) (catch :default e (swap! errors conj e)))
(try (.addSet theme-proxy 42) (catch :default e (swap! invalid conj e)))
(try (.removeSet theme-proxy nil) (catch :default e (swap! invalid conj e)))
(t/is (empty? @emitted))
(t/is (= 2 (count @errors)))
(t/is (every? #(instance? js/Error %) @errors))))))
(t/is (= 2 (count @invalid)))
(t/is (every? #(instance? js/Error %) @invalid))))))
+98 -92
View File
@@ -72,7 +72,9 @@
[frontend-tests.tokens.import-export-test]
[frontend-tests.tokens.logic.token-actions-test]
[frontend-tests.tokens.logic.token-data-test]
[frontend-tests.tokens.logic.token-libraries-test]
[frontend-tests.tokens.logic.token-remapping-test]
[frontend-tests.tokens.logic.tokens-status-test]
[frontend-tests.tokens.style-dictionary-test]
[frontend-tests.tokens.token-errors-test]
[frontend-tests.tokens.workspace-tokens-remap-test]
@@ -110,98 +112,102 @@
(.exit js/process 1)))
(def test-namespaces
['frontend-tests.basic-shapes-test
'frontend-tests.code-gen-style-test
'frontend-tests.composable-tests.comp.sync-test
'frontend-tests.copy-as-svg-test
'frontend-tests.data.dashboard-test
'frontend-tests.data.nitrate-test
'frontend-tests.data.profile-test
'frontend-tests.data.repo-test
'frontend-tests.data.store-test
'frontend-tests.data.exports-assets-test
'frontend-tests.data.uploads-test
'frontend-tests.data.viewer-test
'frontend-tests.data.workspace-colors-test
'frontend-tests.data.workspace-comments-test
'frontend-tests.data.workspace-interactions-test
'frontend-tests.data.workspace-mcp-test
'frontend-tests.data.workspace-media-test
'frontend-tests.data.workspace-pages-test
'frontend-tests.data.workspace-path-edition-test
'frontend-tests.data.workspace-reflow-test
'frontend-tests.data.workspace-shortcuts-test
'frontend-tests.data.workspace-texts-test
'frontend-tests.data.workspace-thumbnails-test
'frontend-tests.errors-test
'frontend-tests.fonts-test
'frontend-tests.helpers-shapes-test
'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.components-and-tokens
'frontend-tests.logic.copying-and-duplicating-test
'frontend-tests.logic.frame-guides-test
'frontend-tests.logic.groups-test
'frontend-tests.logic.nudge-selected-shapes-test
'frontend-tests.logic.path-actions-test
'frontend-tests.logic.path-clipboard-test
'frontend-tests.logic.path-helpers-test
'frontend-tests.logic.path-lifecycle-test
'frontend-tests.logic.path-tools-test
'frontend-tests.logic.pasting-in-containers-test
'frontend-tests.main-errors-test
'frontend-tests.logic.sidebar-transform-coalescing-test
'frontend-tests.logic.update-position-test
'frontend-tests.logic.wasm-modifiers-nil-id-test
'frontend-tests.plugins.comments-test
'frontend-tests.plugins.context-shapes-test
'frontend-tests.plugins.file-test
'frontend-tests.plugins.format-test
'frontend-tests.plugins.grid-test
'frontend-tests.plugins.interactions-test
'frontend-tests.plugins.library-test
'frontend-tests.plugins.local-storage-test
'frontend-tests.plugins.page-active-validation-test
'frontend-tests.plugins.page-test
'frontend-tests.plugins.parser-test
'frontend-tests.plugins.shape-bugfixes-test
'frontend-tests.plugins.text-test
'frontend-tests.plugins.tokens-test
'frontend-tests.plugins.utils-test
'frontend-tests.plugins.value-objects-test
'frontend-tests.render-wasm.process-objects-test
'frontend-tests.render-wasm.text-editor-apply-styles-test
'frontend-tests.render-wasm.text-editor-caret-color-test
'frontend-tests.svg-fills-test
'frontend-tests.tokens.copy-paste-props-test
'frontend-tests.tokens.import-export-test
'frontend-tests.tokens.logic.token-actions-test
'frontend-tests.tokens.logic.token-data-test
'frontend-tests.tokens.logic.token-remapping-test
'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-errors-test
'frontend-tests.tokens.workspace-tokens-remap-test
'frontend-tests.ui.check-updates-test
'frontend-tests.ui.colorpicker-token-set-order-test
'frontend-tests.ui.comments-clustering-test
'frontend-tests.ui.comments-position-modifier-test
'frontend-tests.ui.ds-controls-numeric-input-test
'frontend-tests.ui.gradient-handlers-test
'frontend-tests.ui.layout-container-multiple-test
'frontend-tests.ui.measures-menu-props-test
'frontend-tests.ui.routes-test
'frontend-tests.render-dimensions-test
'frontend-tests.text-editor-paste-guard-test
'frontend-tests.ui.settings-password-schema-test
'frontend-tests.ui.settings-shortcuts-test
'frontend-tests.util-clipboard-test
'frontend-tests.util-object-test
'frontend-tests.util-range-tree-test
'frontend-tests.util-simple-math-test
'frontend-tests.util-text-editor-test
'frontend-tests.util-webapi-test
'frontend-tests.util.dom.dnd-test
'frontend-tests.util-zip-test
'frontend-tests.worker-snap-test])
[
;; 'frontend-tests.basic-shapes-test
;; 'frontend-tests.code-gen-style-test
;; 'frontend-tests.composable-tests.comp.sync-test
;; 'frontend-tests.copy-as-svg-test
;; 'frontend-tests.data.dashboard-test
;; 'frontend-tests.data.nitrate-test
;; 'frontend-tests.data.profile-test
;; 'frontend-tests.data.repo-test
;; 'frontend-tests.data.store-test
;; 'frontend-tests.data.exports-assets-test
;; 'frontend-tests.data.uploads-test
;; 'frontend-tests.data.viewer-test
;; 'frontend-tests.data.workspace-colors-test
;; 'frontend-tests.data.workspace-comments-test
;; 'frontend-tests.data.workspace-interactions-test
;; 'frontend-tests.data.workspace-mcp-test
;; 'frontend-tests.data.workspace-media-test
;; 'frontend-tests.data.workspace-pages-test
;; 'frontend-tests.data.workspace-path-edition-test
;; 'frontend-tests.data.workspace-reflow-test
;; 'frontend-tests.data.workspace-shortcuts-test
;; 'frontend-tests.data.workspace-texts-test
;; 'frontend-tests.data.workspace-thumbnails-test
;; 'frontend-tests.errors-test
;; 'frontend-tests.fonts-test
;; 'frontend-tests.helpers-shapes-test
;; 'frontend-tests.logic.comp-remove-swap-slots-test
;; 'frontend-tests.logic.components-and-tokens
;; 'frontend-tests.logic.copying-and-duplicating-test
;; 'frontend-tests.logic.frame-guides-test
;; 'frontend-tests.logic.groups-test
;; 'frontend-tests.logic.nudge-selected-shapes-test
;; 'frontend-tests.logic.path-actions-test
;; 'frontend-tests.logic.path-clipboard-test
;; 'frontend-tests.logic.path-helpers-test
;; 'frontend-tests.logic.path-lifecycle-test
;; 'frontend-tests.logic.path-tools-test
;; 'frontend-tests.logic.pasting-in-containers-test
;; 'frontend-tests.main-errors-test
;; 'frontend-tests.logic.sidebar-transform-coalescing-test
;; 'frontend-tests.logic.update-position-test
;; 'frontend-tests.logic.wasm-modifiers-nil-id-test
;; 'frontend-tests.plugins.comments-test
;; 'frontend-tests.plugins.context-shapes-test
;; 'frontend-tests.plugins.file-test
;; 'frontend-tests.plugins.format-test
;; 'frontend-tests.plugins.grid-test
;; 'frontend-tests.plugins.interactions-test
;; 'frontend-tests.plugins.library-test
;; 'frontend-tests.plugins.local-storage-test
;; 'frontend-tests.plugins.page-active-validation-test
;; 'frontend-tests.plugins.page-test
;; 'frontend-tests.plugins.parser-test
;; 'frontend-tests.plugins.shape-bugfixes-test
;; 'frontend-tests.plugins.text-test
;; 'frontend-tests.plugins.tokens-test
;; 'frontend-tests.plugins.utils-test
;; 'frontend-tests.plugins.value-objects-test
;; 'frontend-tests.render-wasm.process-objects-test
;; 'frontend-tests.render-wasm.text-editor-apply-styles-test
;; 'frontend-tests.render-wasm.text-editor-caret-color-test
;; 'frontend-tests.svg-fills-test
;; 'frontend-tests.tokens.copy-paste-props-test
;; 'frontend-tests.tokens.import-export-test
;; 'frontend-tests.tokens.logic.token-actions-test
;; 'frontend-tests.tokens.logic.token-data-test
'frontend-tests.tokens.logic.token-libraries-test
;; 'frontend-tests.tokens.logic.token-remapping-test
;; 'frontend-tests.tokens.style-dictionary-test
;; 'frontend-tests.tokens.token-errors-test
;; 'frontend-tests.tokens.logic.tokens-status-test
;; 'frontend-tests.tokens.workspace-tokens-remap-test
;; 'frontend-tests.ui.check-updates-test
;; 'frontend-tests.ui.colorpicker-token-set-order-test
;; 'frontend-tests.ui.comments-clustering-test
;; 'frontend-tests.ui.comments-position-modifier-test
;; 'frontend-tests.ui.ds-controls-numeric-input-test
;; 'frontend-tests.ui.gradient-handlers-test
;; 'frontend-tests.ui.layout-container-multiple-test
;; 'frontend-tests.ui.measures-menu-props-test
;; 'frontend-tests.ui.routes-test
;; 'frontend-tests.render-dimensions-test
;; 'frontend-tests.text-editor-paste-guard-test
;; 'frontend-tests.ui.settings-password-schema-test
;; 'frontend-tests.ui.settings-shortcuts-test
;; 'frontend-tests.util-clipboard-test
;; 'frontend-tests.util-object-test
;; 'frontend-tests.util-range-tree-test
;; 'frontend-tests.util-simple-math-test
;; 'frontend-tests.util-text-editor-test
;; 'frontend-tests.util-webapi-test
;; 'frontend-tests.util.dom.dnd-test
;; 'frontend-tests.util-zip-test
;; 'frontend-tests.worker-snap-test
])
(assert (every? find-ns-obj test-namespaces)
"test-namespaces contains a namespace that isn't required in runner.cljs")
@@ -6,49 +6,51 @@
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.ui.workspace.tokens.management.context-menu :as wtcm]
[clojure.test :as t]))
(defn setup-file []
(-> (thf/sample-file :file-1)
(tht/add-tokens-lib)
(tht/update-tokens-lib #(-> %
(ctob/add-set (ctob/make-token-set :name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :name "test-theme"
:sets #{"test-token-set"}))
(ctob/set-active-themes #{"/test-theme"})
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-color"
:type :color
:value "red"))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-spacing"
:type :spacing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-rotation"
:type :rotation
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-opacity"
:type :opacity
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-dimensions"
:type :dimensions
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-number"
:type :number
:value 10))))
(-> (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :test-token-set)
:name "test-token-set"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :test-theme)
:name "test-theme"
:sets #{"test-token-set"}))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-radius"
:type :border-radius
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-color"
:type :color
:value "red"))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-spacing"
:type :spacing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-sizing"
:type :sizing
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-rotation"
:type :rotation
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-opacity"
:type :opacity
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-dimensions"
:type :dimensions
:value 10))
(ctob/add-token (thi/id :test-token-set)
(ctob/make-token :name "token-number"
:type :number
: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
(tho/add-group :group1)
;; app.main.data.workspace.tokens.application/rect-attributes
@@ -6,9 +6,12 @@
(ns frontend-tests.tokens.helpers.state
(:require
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
[app.main.data.style-dictionary :as sd]
[app.main.data.workspace.undo :as dwu]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@@ -29,11 +32,13 @@
(ptk/reify ::end+
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)]
(->> (get data :tokens-lib)
(ctob/get-tokens-in-active-sets)
(sd/resolve-tokens)
(rx/mapcat #(rx/of (end))))))))
(let [tokens-status (dsh/lookup-tokens-status state)
tokens-lib (dsh/lookup-tokens-lib state)]
(if (and tokens-status tokens-lib)
(->> (cfo/get-tokens-in-active-sets tokens-status tokens-lib)
(sd/resolve-tokens)
(rx/mapcat #(rx/of (end))))
(rx/of (end)))))))
(defn stop-on
"Helper function to be used with async version of run-store.
@@ -51,6 +56,31 @@
"Stops on `send-update-indices` function being called, which should be the last function of an event chain."
(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
;; https://chat.kaleidos.net/penpot-partners/pl/tz1yoes3w3fr9qanxqpuhoz3ch
(defn run-store
@@ -11,9 +11,11 @@
[app.common.types.tokens-lib :as ctob]))
(defn get-token [file name]
(some-> (get-in file [:data :tokens-lib])
(ctob/get-tokens-in-active-sets)
(get name)))
(let [data (get-in file [:data])
tokens-status (cfo/get-tokens-status data)
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
[file shape-label token-label attributes]
@@ -10,8 +10,10 @@
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.text :as txt]
[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.library-edit :as dwtl]
[app.main.data.workspace.wasm-text :as dwwt]
@@ -44,21 +46,22 @@
(defn setup-file-with-tokens
[& {:keys [rect-1 rect-2 rect-3]}]
(-> (setup-file)
(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!")
(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"})
(-> (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 border-radius-token))
(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
(tohs/stop-on ::dwwt/resize-wasm-text-debounce-commit))
@@ -83,15 +86,16 @@
done
(let [file (setup-file-with-empty-lib)
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)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
lib (get-in file' [:data :tokens-lib])]
(let [file' (ths/get-file-from-state new-state)
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 (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/testing "create followed by set-enabled (as the plugin addSet does) yields an active set"
@@ -99,16 +103,17 @@
done
(let [file (setup-file-with-empty-lib)
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)
(dwtl/set-enabled-token-set "primitives" true)]]
(dwtl/set-enabled-token-set (cthi/id :set1) true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
lib (get-in file' [:data :tokens-lib])]
(let [file' (ths/get-file-from-state new-state)
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 (true? (ctob/token-set-active? lib "primitives"))))))))))
(t/is (true? (ctos/set-active? status (cthi/id :set1)))))))))))
(t/deftest test-apply-token
(t/testing "applies token to shape and updates shape attributes to resolved value"
@@ -8,9 +8,12 @@
(:require
[app.common.test-helpers.files :as cthf]
[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-status :as ctos]
[app.common.uuid :as uuid]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.undo :as dwu]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
@@ -25,11 +28,40 @@
(defn setup-file-with-token-lib
[]
(-> (setup-file)
(assoc-in [:data :tokens-lib]
(-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set)
:name "Set A"))))))
(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"))
: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/async
@@ -50,6 +82,20 @@
(t/is (= (count sets') 2))
(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/async
done
@@ -71,6 +117,21 @@
(t/is (= (count sets') 1))
(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/async
done
@@ -103,21 +164,734 @@
sets (ctob/get-sets token-lib)]
(t/testing "Token lib contains one set"
(t/is (= (count sets) 1))))))))
(t/is (= (count sets) 1)))))))))
(t/deftest delete-set
(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))]]
(t/deftest duplicate-set-no-tokens-lib
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
events [(dwtl/duplicate-token-set (uuid/next))]]
(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')]
(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/testing "Set has been deleted"
(t/is (= (count sets') 0))))))))))
(t/deftest delete-set
(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"))))))))))
@@ -0,0 +1,100 @@
;; 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 frontend-tests.tokens.logic.token-libraries-test
(:require
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.application :as dwta]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]
[frontend-tests.tokens.helpers.state :as tohs]
[frontend-tests.tokens.helpers.tokens :as toht]))
(t/use-fixtures :each
{:before (fn []
(thp/reset-idmap!)
(thw/setup-wasm-mocks!))
:after thw/teardown-wasm-mocks!})
(defn setup-file []
(-> (cthf/sample-file :file-1 :page-label :page-1)
(cths/add-sample-shape :rect-1
:type :rect
:r1 0 :r2 0 :r3 0 :r4 0)))
(defn setup-file-with-tokens
[]
(-> (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-set (ctob/make-token-set :id (cthi/new-id! :set-b)
:name "Set B"))
(ctob/add-token (cthi/id :set-a)
(ctob/make-token {:name "borderRadius.sm"
:value "12"
:type :border-radius}))
(ctob/add-token (cthi/id :set-b)
(ctob/make-token {:name "borderRadius.sm"
:value "24"
:type :border-radius})))
:status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :set-a)}))))
(t/deftest test-apply-token-from-lib
(t/testing "applies token from library to local shape updates shape attributes to resolved value"
(t/async
done
(let [library (setup-file-with-tokens)
file (setup-file)
store (ths/setup-store file {:libraries [library]
:tokens-source-library library})
rect-1 (cths/get-shape file :rect-1)
token (toht/get-token library "borderRadius.sm")
events [(dwta/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:r1 :r2 :r3 :r4}
:token token
:on-update-shape dwta/update-shape-radius})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:r1 (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:r1 rect-1') 12)))))))))
(t/testing "applies token using active set-b when the file's active token set changes"
(t/async
done
(let [library (setup-file-with-tokens)
file (-> (setup-file)
(ctht/set-tokens-source library)
(ctht/update-tokens-status #(ctos/set-tokens-status % #{} #{(cthi/id :set-b)})))
store (ths/setup-store file {:libraries [library]})
rect-1 (cths/get-shape file :rect-1)
token (toht/get-token library "borderRadius.sm")
events [(dwta/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:r1 :r2 :r3 :r4}
:token token
:on-update-shape dwta/update-shape-radius})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape radius uses the value from the active set-b"
(t/is (= (:r1 rect-1') 24))))))))))
@@ -13,6 +13,7 @@
[app.common.test-helpers.tokens :as ctht]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.remapping :as dwtr]
[cljs.test :as t :include-macros true]))
@@ -27,19 +28,20 @@
:name "color.secondary"
:value "{color.primary}"
:type :color}]
(-> (cthf/sample-file :file-1 :page-label :page-1)
(ctho/add-rect :rect-1)
(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"})
(-> (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-token))
(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
(ctht/apply-token-to-shape :rect-1 "color.primary" [:fill] [:fill] "#FF0000"))))
@@ -132,22 +134,23 @@
:name "color.secondary"
:value "#00FF00"
: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
:main-root
:main-child
:copy-root
: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 :copy-child "color.primary" [:fill] [:fill] "#FF0000"))))
@@ -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")))))))
@@ -10,8 +10,10 @@
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.ids-map :as cthi]
[app.common.test-helpers.shapes :as cths]
[app.common.test-helpers.tokens :as ctht]
[app.common.types.text :as txt]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.remapping :as remap]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.state :as ths]
@@ -31,17 +33,22 @@
(defn- attach-token-set [file tokens]
(let [set-id (cthi/new-id! :token-set)
tokens-lib (reduce
(fn [lib token]
(ctob/add-token lib set-id (ctob/make-token token)))
(-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id set-id
:name token-set-name))
(ctob/add-theme (ctob/make-token-theme :name token-theme-name
:sets #{token-set-name}))
(ctob/set-active-themes #{(str "/" token-theme-name)}))
tokens)]
(assoc-in file [:data :tokens-lib] tokens-lib)))
theme-id (cthi/new-id! :theme)]
(-> file
(ctht/add-tokens-lib)
(ctht/update-tokens-lib
#(reduce
(fn [lib token]
(ctob/add-token lib set-id (ctob/make-token token)))
(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name token-set-name))
(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]
(get-in file [:data :tokens-lib]))
+57 -3
View File
@@ -7394,14 +7394,22 @@ msgid_plural "workspace.libraries.components"
msgstr[0] "1 component"
msgstr[1] "%s components"
#: src/app/main/ui/workspace/libraries.cljs:367
msgid "workspace.libraries.connected-to"
msgstr "Connected to"
#: src/app/main/ui/workspace/libraries.cljs:541
msgid "workspace.libraries.connected-libraries"
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
msgid "workspace.libraries.empty.add-some"
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
msgid "workspace.libraries.empty.no-libraries"
msgstr "There are no Shared Libraries at your team, you can look for"
@@ -7414,6 +7422,14 @@ msgstr "some templates in here"
msgid "workspace.libraries.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
msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics"
@@ -7462,6 +7478,10 @@ msgstr "Search shared libraries"
msgid "workspace.libraries.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
msgid "workspace.libraries.shared-library-btn"
msgstr "Connect library"
@@ -7507,6 +7527,24 @@ msgid_plural "workspace.libraries.typography"
msgstr[0] "1 typography"
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
msgid "workspace.libraries.unlink-library-btn"
msgstr "Disconnect library"
@@ -10109,6 +10147,22 @@ msgstr "Use a reference"
msgid "workspace.tokens.value-not-valid"
msgstr "The value is not valid"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:24
msgid "workspace.tokens.connected-library"
msgstr "connected library"
#: 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/ui/workspace/tokens/tokens_source.cljs:25
msgid "workspace.tokens.open-connected-library"
msgstr "Open connected library"
#: 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/management/forms/generic_form.cljs
#, unused
msgid "workspace.tokens.warning-name-change"
+58
View File
@@ -7223,10 +7223,22 @@ msgid_plural "workspace.libraries.components"
msgstr[0] "1 componente"
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
msgid "workspace.libraries.connected-to"
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
msgid "workspace.libraries.empty.add-some"
msgstr "O añadir algunas de éstas para probar:"
@@ -7243,6 +7255,14 @@ msgstr "algunas plantillas aquí"
msgid "workspace.libraries.file-library"
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
msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics"
@@ -7291,6 +7311,10 @@ msgstr "Buscar bibliotecas compartidas"
msgid "workspace.libraries.shared-libraries"
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
msgid "workspace.libraries.shared-library-btn"
msgstr "Conectar biblioteca"
@@ -7331,6 +7355,24 @@ msgid_plural "workspace.libraries.typography"
msgstr[0] "1 tipografía"
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
msgid "workspace.libraries.unlink-library-btn"
msgstr "Desconectar biblioteca"
@@ -9762,6 +9804,22 @@ msgstr "Usa una referencia"
msgid "workspace.tokens.value-not-valid"
msgstr "El valor no es válido"
#: src/app/main/ui/workspace/tokens/tokens_source.cljs:24
msgid "workspace.tokens.connected-library"
msgstr "librería conectada"
#: 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/ui/workspace/tokens/tokens_source.cljs:25
msgid "workspace.tokens.open-connected-library"
msgstr "Abrir librería conectada"
#: 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/management/forms/generic_form.cljs
#, unused
msgid "workspace.tokens.warning-name-change"
-1
View File
@@ -10,7 +10,6 @@
- **plugins-runtime**: `Library.createComponent()` now rejects invalid input (an empty shape list, or a shape inside a component copy) with a validation error instead of returning a component proxy pointing at nothing.
- **plugins-runtime**: Setting an individual padding/margin side (`leftPadding`, `topMargin`, …) now re-derives the padding/margin type, switching to `multiple` when the four sides stop being symmetric (so the value is actually painted) and back to `simple` once top/bottom and left/right are mirrored again.
- **plugins-runtime**: Removed the premature deep-hardening of the host plugin context, which froze shared host functions (including `Function.prototype`) before SES override taming, causing `TypeError: Cannot assign to read only property 'toString'` on later host-side function extension. Related to #11001.
## 1.5.0 (2026-07-08)
@@ -6,13 +6,15 @@
<div class="panel">
<div class="panel-heading">
<p class="headline-m">THEMES</p>
<button
type="button"
data-appearance="secondary"
(click)="addTheme()"
>
+
</button>
@if (isEditableTokens) {
<button
type="button"
data-appearance="secondary"
(click)="addTheme()"
>
+
</button>
}
</div>
<ul data-handler="themes-list">
@@ -24,20 +26,22 @@
}
{{ theme.name }}
</span>
<button
type="button"
data-appearance="secondary"
(click)="renameTheme(theme.id, theme.name)"
>
🖊️
</button>
<button
type="button"
data-appearance="secondary"
(click)="deleteTheme(theme.id)"
>
</button>
@if (isEditableTokens) {
<button
type="button"
data-appearance="secondary"
(click)="renameTheme(theme.id, theme.name)"
>
🖊️
</button>
<button
type="button"
data-appearance="secondary"
(click)="deleteTheme(theme.id)"
>
</button>
}
<div class="checkbox-container">
<input
class="checkbox-input"
@@ -55,9 +59,15 @@
<div class="panel">
<div class="panel-heading">
<p class="headline-m">SETS</p>
<button type="button" data-appearance="secondary" (click)="addSet()">
+
</button>
@if (isEditableTokens) {
<button
type="button"
data-appearance="secondary"
(click)="addSet()"
>
+
</button>
}
</div>
<ul data-handler="sets-list">
@@ -69,20 +79,22 @@
<span (click)="loadTokens(set.id)">
{{ set.name }}
</span>
<button
type="button"
data-appearance="secondary"
(click)="renameSet(set.id, set.name)"
>
🖊️
</button>
<button
type="button"
data-appearance="secondary"
(click)="deleteSet(set.id)"
>
</button>
@if (isEditableTokens) {
<button
type="button"
data-appearance="secondary"
(click)="renameSet(set.id, set.name)"
>
🖊️
</button>
<button
type="button"
data-appearance="secondary"
(click)="deleteSet(set.id)"
>
</button>
}
<div class="checkbox-container">
<input
class="checkbox-input"
@@ -105,13 +117,15 @@
@for (group of tokenGroups; track group[0]) {
<li class="body-m token-group">
<span>{{ group[0] }}</span>
<button
type="button"
data-appearance="secondary"
(click)="addToken(group[0])"
>
+
</button>
@if (isEditableTokens) {
<button
type="button"
data-appearance="secondary"
(click)="addToken(group[0])"
>
+
</button>
}
</li>
@for (token of group[1]; track token.id) {
<li
@@ -125,20 +139,22 @@
>
{{ token.name }}
</span>
<button
type="button"
data-appearance="secondary"
(click)="renameToken(token.id, token.name)"
>
🖊️
</button>
<button
type="button"
data-appearance="secondary"
(click)="deleteToken(token.id)"
>
</button>
@if (isEditableTokens) {
<button
type="button"
data-appearance="secondary"
(click)="renameToken(token.id, token.name)"
>
🖊️
</button>
<button
type="button"
data-appearance="secondary"
(click)="deleteToken(token.id)"
>
</button>
}
</li>
}
}
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff. Show more