mirror of
https://github.com/penpot/penpot.git
synced 2026-09-07 12:40:12 +00:00
🐛 Detect duplicated token names in the whole library (#9034)
* 🐛 Detect duplicated token names in the whole library * 🔧 Review comments * 🐛 Prevent and repair token themes with inexistent sets * 🔧 Convert tokens lib migration into file migration
This commit is contained in:
parent
7c5fa038c1
commit
61cd757355
@ -34,7 +34,7 @@
|
|||||||
[app.common.types.shape.shadow :as ctss]
|
[app.common.types.shape.shadow :as ctss]
|
||||||
[app.common.types.shape.text :as ctst]
|
[app.common.types.shape.text :as ctst]
|
||||||
[app.common.types.text :as types.text]
|
[app.common.types.text :as types.text]
|
||||||
[app.common.types.tokens-lib :as types.tokens-lib]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
@ -1599,7 +1599,7 @@
|
|||||||
|
|
||||||
(defmethod migrate-data "0014-fix-tokens-lib-duplicate-ids"
|
(defmethod migrate-data "0014-fix-tokens-lib-duplicate-ids"
|
||||||
[data _]
|
[data _]
|
||||||
(d/update-when data :tokens-lib types.tokens-lib/fix-duplicate-token-set-ids))
|
(d/update-when data :tokens-lib ctob/fix-duplicate-token-set-ids))
|
||||||
|
|
||||||
(defmethod migrate-data "0014-clear-components-nil-objects"
|
(defmethod migrate-data "0014-clear-components-nil-objects"
|
||||||
[data _]
|
[data _]
|
||||||
@ -1805,6 +1805,13 @@
|
|||||||
{})]
|
{})]
|
||||||
(cfcp/sync-component-id-with-ref-shape data libraries)))
|
(cfcp/sync-component-id-with-ref-shape data libraries)))
|
||||||
|
|
||||||
|
(defmethod migrate-data "0021-repair-bad-tokens"
|
||||||
|
[data _]
|
||||||
|
(d/update-when data :tokens-lib
|
||||||
|
#(-> %
|
||||||
|
(ctob/fix-conflicting-token-names)
|
||||||
|
(ctob/fix-missing-sets-in-themes))))
|
||||||
|
|
||||||
(def available-migrations
|
(def available-migrations
|
||||||
(into (d/ordered-set)
|
(into (d/ordered-set)
|
||||||
["legacy-2"
|
["legacy-2"
|
||||||
@ -1882,4 +1889,5 @@
|
|||||||
"0017-fix-layout-flex-dir"
|
"0017-fix-layout-flex-dir"
|
||||||
"0018-remove-unneeded-objects-from-components"
|
"0018-remove-unneeded-objects-from-components"
|
||||||
"0019-fix-missing-swap-slots"
|
"0019-fix-missing-swap-slots"
|
||||||
"0020-sync-component-id-with-near-main"]))
|
"0020-sync-component-id-with-near-main"
|
||||||
|
"0021-repair-bad-tokens"]))
|
||||||
|
|||||||
@ -134,26 +134,26 @@
|
|||||||
|
|
||||||
(defn make-token-name-schema
|
(defn make-token-name-schema
|
||||||
"Dynamically generates a schema to check a token name, adding translated error messages
|
"Dynamically generates a schema to check a token name, adding translated error messages
|
||||||
and two additional validations:
|
and additional validations:
|
||||||
- Min and max length.
|
- Min and max length.
|
||||||
- Checks if other token with a path derived from the name already exists at `tokens-tree`.
|
- Checks if other token with a path derived from the name already exists in the library.
|
||||||
e.g. it's not allowed to create a token `foo.bar` if a token `foo` already exists."
|
e.g. it's not allowed to create a token `foo.bar` if a token `foo` already exists.
|
||||||
[tokens-tree]
|
- Also checks if there is a token with the exact same name in the current set, but different
|
||||||
|
from the current token."
|
||||||
|
[tokens-lib set-id token-id]
|
||||||
[:and
|
[:and
|
||||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||||
(-> cto/schema:token-name
|
(-> cto/schema:token-name
|
||||||
(sm/update-properties assoc :error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error"))))
|
(sm/update-properties assoc :error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error"))))
|
||||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||||
#(and (some? tokens-tree)
|
#(or (nil? tokens-lib)
|
||||||
(not (ctob/token-name-path-exists? % tokens-tree)))]])
|
(not (ctob/token-name-path-exists? % tokens-lib set-id token-id)))]])
|
||||||
|
|
||||||
(defn make-node-token-name-schema
|
(defn make-node-token-name-schema
|
||||||
"Dynamically generates a schema to check a token node name, adding translated error messages
|
"Dynamically generates a schema to check the name of a token node, that may be a final token or a group.
|
||||||
and two additional validations:
|
This runs same checks as make-token-name-schema, but for all tokens that will be renamed by this change,
|
||||||
- Min and max length.
|
if the group already contains tokens."
|
||||||
- Checks if other token with a path derived from the name already exists at `tokens-tree`.
|
[active-tokens tokens-lib node set-id]
|
||||||
e.g. it's not allowed to create a token `foo.bar` if a token `foo` already exists."
|
|
||||||
[active-tokens tokens-tree node]
|
|
||||||
[:and
|
[:and
|
||||||
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
[:string {:min 1 :max 255 :error/fn #(str (:value %) (tr "workspace.tokens.token-name-length-validation-error"))}]
|
||||||
(-> cto/schema:token-node-name
|
(-> cto/schema:token-node-name
|
||||||
@ -164,20 +164,20 @@
|
|||||||
current-name (:name node)
|
current-name (:name node)
|
||||||
new-tokens (ctob/update-tokens-group active-tokens current-path current-name name)]
|
new-tokens (ctob/update-tokens-group active-tokens current-path current-name name)]
|
||||||
(and (some? new-tokens)
|
(and (some? new-tokens)
|
||||||
(some (fn [[token-name _]]
|
(some (fn [[token-name token]]
|
||||||
(not (ctob/token-name-path-exists? token-name tokens-tree)))
|
(not (ctob/token-name-path-exists? token-name tokens-lib set-id (ctob/get-id token))))
|
||||||
new-tokens))))]])
|
new-tokens))))]])
|
||||||
|
|
||||||
(def schema:token-description
|
(def schema:token-description
|
||||||
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}])
|
[:string {:max 2048 :error/fn #(tr "errors.field-max-length" 2048)}])
|
||||||
|
|
||||||
(defn make-token-schema
|
(defn make-token-schema
|
||||||
[tokens-tree token-type]
|
[tokens-lib token-type set-id token-id]
|
||||||
[:and
|
[:and
|
||||||
(sm/merge
|
(sm/merge
|
||||||
cto/schema:token-attrs
|
cto/schema:token-attrs
|
||||||
[:map
|
[:map
|
||||||
[:name (make-token-name-schema tokens-tree)]
|
[:name (make-token-name-schema tokens-lib set-id token-id)]
|
||||||
[:value (make-token-value-schema token-type)]
|
[:value (make-token-value-schema token-type)]
|
||||||
[:description {:optional true} schema:token-description]])
|
[:description {:optional true} schema:token-description]])
|
||||||
[:fn {:error/field :value
|
[:fn {:error/field :value
|
||||||
@ -187,9 +187,9 @@
|
|||||||
(not (cto/token-value-self-reference? name value))))]])
|
(not (cto/token-value-self-reference? name value))))]])
|
||||||
|
|
||||||
(defn make-node-token-schema
|
(defn make-node-token-schema
|
||||||
[active-tokens tokens-tree node]
|
[active-tokens tokens-lib node set-id]
|
||||||
[:map
|
[:map
|
||||||
[:name (make-node-token-name-schema active-tokens tokens-tree node)]])
|
[:name (make-node-token-name-schema active-tokens tokens-lib node set-id)]])
|
||||||
|
|
||||||
(defn convert-dtcg-token
|
(defn convert-dtcg-token
|
||||||
"Convert token attributes as they come from a decoded json, with DTCG types, to internal types.
|
"Convert token attributes as they come from a decoded json, with DTCG types, to internal types.
|
||||||
@ -287,12 +287,18 @@
|
|||||||
|
|
||||||
(defn make-token-theme-schema
|
(defn make-token-theme-schema
|
||||||
[tokens-lib group name theme-id]
|
[tokens-lib group name theme-id]
|
||||||
(sm/merge
|
[:and
|
||||||
ctob/schema:token-theme-attrs
|
(sm/merge
|
||||||
[:map
|
ctob/schema:token-theme-attrs
|
||||||
[:group (make-token-theme-group-schema tokens-lib name theme-id)] ;; TODO how to keep error-fn from here?
|
[:map
|
||||||
[:name (make-token-theme-name-schema tokens-lib group theme-id)]
|
[:group (make-token-theme-group-schema tokens-lib name theme-id)] ;; TODO how to keep error-fn from here?
|
||||||
[:description {:optional true} schema:token-theme-description]]))
|
[:name (make-token-theme-name-schema tokens-lib group theme-id)]
|
||||||
|
[:description {:optional true} schema:token-theme-description]])
|
||||||
|
[:fn {:error/field :sets
|
||||||
|
:error/fn #(tr "errors.token-theme-not-existing-sets" (str/join ", " (:sets (:value %))))}
|
||||||
|
(fn [{:keys [sets]}]
|
||||||
|
(or (nil? tokens-lib)
|
||||||
|
(every? #(ctob/get-set-by-name tokens-lib %) sets)))]])
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; HELPERS
|
;; HELPERS
|
||||||
|
|||||||
@ -202,6 +202,24 @@
|
|||||||
(zero? result) false
|
(zero? result) false
|
||||||
:else false)))
|
:else false)))
|
||||||
|
|
||||||
|
(defn is-after-or-equal?
|
||||||
|
"Analgous to: da >= db"
|
||||||
|
[da db]
|
||||||
|
(let [result (compare da db)]
|
||||||
|
(cond
|
||||||
|
(neg? result) false
|
||||||
|
(zero? result) true
|
||||||
|
:else true)))
|
||||||
|
|
||||||
|
(defn is-before-or-equal?
|
||||||
|
"Analgous to: da <= db"
|
||||||
|
[da db]
|
||||||
|
(let [result (compare da db)]
|
||||||
|
(cond
|
||||||
|
(neg? result) true
|
||||||
|
(zero? result) true
|
||||||
|
:else false)))
|
||||||
|
|
||||||
(defn inst?
|
(defn inst?
|
||||||
[o]
|
[o]
|
||||||
#?(:clj (instance? Instant o)
|
#?(:clj (instance? Instant o)
|
||||||
|
|||||||
@ -74,13 +74,19 @@
|
|||||||
modified-at)
|
modified-at)
|
||||||
|
|
||||||
(rename [this new-name]
|
(rename [this new-name]
|
||||||
(assoc this :name new-name))
|
(assoc this
|
||||||
|
:name new-name
|
||||||
|
:modified-at (ct/now)))
|
||||||
|
|
||||||
(reid [this new-id]
|
(reid [this new-id]
|
||||||
(assoc this :id new-id))
|
(assoc this
|
||||||
|
:id new-id
|
||||||
|
:modified-at (ct/now)))
|
||||||
|
|
||||||
(set-description [this new-description]
|
(set-description [this new-description]
|
||||||
(assoc this :description new-description)))
|
(assoc this
|
||||||
|
:description new-description
|
||||||
|
:modified-at (ct/now))))
|
||||||
|
|
||||||
(defmethod pp/simple-dispatch Token
|
(defmethod pp/simple-dispatch Token
|
||||||
[^Token obj]
|
[^Token obj]
|
||||||
@ -1154,25 +1160,26 @@ Will return a value that matches this schema:
|
|||||||
(if-let [theme (get-theme this id)]
|
(if-let [theme (get-theme this id)]
|
||||||
(let [group (:group theme)
|
(let [group (:group theme)
|
||||||
name (:name theme)
|
name (:name theme)
|
||||||
theme' (-> (make-token-theme (f theme))
|
theme' (make-token-theme (f theme))]
|
||||||
(assoc :modified-at (ct/now)))
|
(if (= theme theme')
|
||||||
group' (:group theme')
|
this
|
||||||
name' (:name theme')
|
(let [theme' (assoc theme' :modified-at (ct/now))
|
||||||
same-group? (= group group')
|
group' (:group theme')
|
||||||
same-name? (= name name')
|
name' (:name theme')
|
||||||
same-path? (and same-group? same-name?)]
|
same-group? (= group group')
|
||||||
(TokensLib. sets
|
same-name? (= name name')
|
||||||
(if same-path?
|
same-path? (and same-group? same-name?)]
|
||||||
(update themes group' assoc name' theme')
|
(TokensLib. sets
|
||||||
(-> themes
|
(if same-path?
|
||||||
(d/oassoc-in-before [group name] [group' name'] theme')
|
(update themes group' assoc name' theme')
|
||||||
(d/dissoc-in [group name])))
|
(-> themes
|
||||||
(if same-path?
|
(d/oassoc-in-before [group name] [group' name'] theme')
|
||||||
active-themes
|
(d/dissoc-in [group name])))
|
||||||
(disj active-themes (join-theme-path group name)))))
|
(if same-path?
|
||||||
|
active-themes
|
||||||
|
(disj active-themes (join-theme-path group name)))))))
|
||||||
this))
|
this))
|
||||||
|
|
||||||
|
|
||||||
(delete-theme [this id]
|
(delete-theme [this id]
|
||||||
(let [theme (get-theme this id)
|
(let [theme (get-theme this id)
|
||||||
[group name] [(:group theme) (:name theme)]]
|
[group name] [(:group theme) (:name theme)]]
|
||||||
@ -1477,49 +1484,63 @@ Will return a value that matches this schema:
|
|||||||
(rename copy-name)
|
(rename copy-name)
|
||||||
(reid (uuid/next))))))
|
(reid (uuid/next))))))
|
||||||
|
|
||||||
(defn- token-name->path-selector
|
|
||||||
"Splits token-name into map with `:path` and `:selector` using `token-name->path`.
|
|
||||||
|
|
||||||
`:selector` is the last item of the names path
|
|
||||||
`:path` is everything leading up the the `:selector`."
|
|
||||||
[token-name]
|
|
||||||
(let [path-segments (get-token-path {:name token-name})
|
|
||||||
last-idx (dec (count path-segments))
|
|
||||||
[path [selector]] (split-at last-idx path-segments)]
|
|
||||||
{:path (seq path)
|
|
||||||
:selector selector}))
|
|
||||||
|
|
||||||
(defn token-name-path-exists?
|
(defn token-name-path-exists?
|
||||||
"Traverses the path from `token-name` down a `tokens-tree` and checks if a token at that path exists.
|
"Check if a token name or fragment exists in any part of the library, to prevent creating
|
||||||
|
duplicated names that may clash when merging sets and resolving tokens.
|
||||||
|
|
||||||
It's not allowed to create a token inside a token. E.g.:
|
Matches any combination of of names completely included inside group or token names.
|
||||||
Creating a token with
|
For example:
|
||||||
|
- Matches the name \"foo.bar\" with an existing token named \"foo.bar.baz\" or \"foo\".
|
||||||
|
- Does not match the name \"foo.bar\" with an existing token named \"foo.baz\".
|
||||||
|
|
||||||
{:name \"foo.bar\"}
|
You can give a current set id, and it will check if there is a token with the exact same
|
||||||
|
name in this set (there may be tokens with same name in different sets for overriding
|
||||||
|
values, but not in the same set). You can also give a token id to ignore, to search for
|
||||||
|
a token that is a different one.
|
||||||
|
|
||||||
in the tokens tree:
|
If the function finds a match, it returns the part of the name that is duplicated;
|
||||||
|
if not, it returns null."
|
||||||
|
[token-name tokens-lib current-set-id token-id-to-ignore]
|
||||||
|
(letfn [(exists-in-set?
|
||||||
|
[set]
|
||||||
|
(let [tokens-tree (-> set (get-tokens-) (tokens-tree))
|
||||||
|
token-name-path (get-token-path {:name token-name})]
|
||||||
|
(loop [path-segment token-name-path
|
||||||
|
subtree tokens-tree]
|
||||||
|
(if (empty? path-segment)
|
||||||
|
;; All path segments found -> return full name
|
||||||
|
token-name
|
||||||
|
(let [node (get subtree (first path-segment))]
|
||||||
|
(cond
|
||||||
|
;; Path segment doesn't exist
|
||||||
|
(nil? node) nil
|
||||||
|
;; A token exists at this path
|
||||||
|
(token? node)
|
||||||
|
(if (and (some? token-id-to-ignore)
|
||||||
|
(= (get-id node) token-id-to-ignore))
|
||||||
|
;; This is the token to ignore
|
||||||
|
nil
|
||||||
|
(if (and (not= (get-id set) current-set-id)
|
||||||
|
(= (get-name node) token-name))
|
||||||
|
;; A token with the same name in a different set is allowed
|
||||||
|
nil
|
||||||
|
;; If we are in the same set or the name of the token is a subpath of the
|
||||||
|
;; current name: this is a conflict
|
||||||
|
;; -> return the part of the name until this point
|
||||||
|
(str/join "." (take (- (count token-name-path) (count (rest path-segment)))
|
||||||
|
token-name-path))))
|
||||||
|
;; Continue traversing the tree
|
||||||
|
:else (recur (rest path-segment) node)))))))]
|
||||||
|
|
||||||
{\"foo\" {:name \"other\"}}"
|
(if (or (nil? tokens-lib) (empty? (get-sets tokens-lib))
|
||||||
[token-name tokens-tree]
|
(nil? token-name) (str/empty? token-name))
|
||||||
(let [{:keys [path selector]} (token-name->path-selector token-name)
|
nil
|
||||||
path-target (reduce
|
(do
|
||||||
(fn [acc cur]
|
(assert (or (nil? current-set-id)
|
||||||
(let [target (get acc cur)]
|
(some? (get-set tokens-lib current-set-id)))
|
||||||
(cond
|
(str "Set '" current-set-id "' does not exist in the library"))
|
||||||
;; Path segment doesn't exist yet
|
(assert (or (nil? token-id-to-ignore) (uuid? token-id-to-ignore)))
|
||||||
(nil? target) (reduced false)
|
(some exists-in-set? (get-sets tokens-lib))))))
|
||||||
;; A token exists at this path
|
|
||||||
(:name target) (reduced true)
|
|
||||||
;; Continue traversing the true
|
|
||||||
:else target)))
|
|
||||||
tokens-tree
|
|
||||||
path)]
|
|
||||||
(cond
|
|
||||||
(boolean? path-target) path-target
|
|
||||||
(get path-target :name) true
|
|
||||||
:else (-> (get path-target selector)
|
|
||||||
(seq)
|
|
||||||
(boolean)))))
|
|
||||||
|
|
||||||
(defn update-tokens-group
|
(defn update-tokens-group
|
||||||
"Updates the active tokens path when renaming a group node.
|
"Updates the active tokens path when renaming a group node.
|
||||||
@ -1530,7 +1551,9 @@ Will return a value that matches this schema:
|
|||||||
active-tokens: map of token-name to token-object for all active tokens in the set
|
active-tokens: map of token-name to token-object for all active tokens in the set
|
||||||
current-path: the path of the group being renamed, e.g. \"foo.bar\"
|
current-path: the path of the group being renamed, e.g. \"foo.bar\"
|
||||||
current-name: the current name of the group being renamed, e.g. \"bar\"
|
current-name: the current name of the group being renamed, e.g. \"bar\"
|
||||||
new-name: the new name for the group being renamed, e.g. \"baz\""
|
new-name: the new name for the group being renamed, e.g. \"baz\"
|
||||||
|
|
||||||
|
Returns a sequence of [name token] for each renamed token."
|
||||||
|
|
||||||
[active-tokens current-path current-name new-name]
|
[active-tokens current-path current-name new-name]
|
||||||
(let [path-prefix (str/replace current-path current-name "")]
|
(let [path-prefix (str/replace current-path current-name "")]
|
||||||
@ -1879,7 +1902,11 @@ Will return a value that matches this schema:
|
|||||||
library
|
library
|
||||||
(reduce (fn [library name]
|
(reduce (fn [library name]
|
||||||
(if-let [tokens (get sets name)]
|
(if-let [tokens (get sets name)]
|
||||||
(add-set library (make-token-set :name name :tokens tokens))
|
(do (doseq [token (vals tokens)]
|
||||||
|
(when (token-name-path-exists? (get-name token) library nil (get-id token))
|
||||||
|
(throw (ex-info (get-name token)
|
||||||
|
{:error/code :error.import/duplicated-token-name}))))
|
||||||
|
(add-set library (make-token-set :name name :tokens tokens)))
|
||||||
library))
|
library))
|
||||||
library
|
library
|
||||||
ordered-set-names)
|
ordered-set-names)
|
||||||
@ -2136,6 +2163,39 @@ Will return a value that matches this schema:
|
|||||||
;; MIGRATIONS HELPERS
|
;; MIGRATIONS HELPERS
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn update-all-tokens
|
||||||
|
"Walk through all tokens in the library and apply the given function to them.
|
||||||
|
The function receives the library, the set and the token as arguments,
|
||||||
|
and should return the updated token."
|
||||||
|
[tokens-lib update-fn]
|
||||||
|
(let [update-one-set
|
||||||
|
(fn [lib set]
|
||||||
|
(reduce (fn [lib' token]
|
||||||
|
(update-token lib'
|
||||||
|
(get-id set)
|
||||||
|
(get-id token)
|
||||||
|
#(update-fn lib'
|
||||||
|
(get-set lib' (get-id set))
|
||||||
|
%)))
|
||||||
|
lib
|
||||||
|
(vals (get-tokens lib (get-id set)))))]
|
||||||
|
(reduce (fn [lib set]
|
||||||
|
(update-one-set lib set))
|
||||||
|
tokens-lib
|
||||||
|
(get-sets tokens-lib))))
|
||||||
|
|
||||||
|
(defn update-all-themes
|
||||||
|
"Walk through all themes in the library and apply the given function to them.
|
||||||
|
The function receives the library and the theme as arguments,
|
||||||
|
and should return the updated theme."
|
||||||
|
[tokens-lib update-fn]
|
||||||
|
(reduce (fn [lib theme]
|
||||||
|
(update-theme lib
|
||||||
|
(get-id theme)
|
||||||
|
#(update-fn lib %)))
|
||||||
|
tokens-lib
|
||||||
|
(get-themes tokens-lib)))
|
||||||
|
|
||||||
(defn fix-duplicate-token-set-ids
|
(defn fix-duplicate-token-set-ids
|
||||||
"Given an instance of TokensLib fixes it internal sets data sturcture
|
"Given an instance of TokensLib fixes it internal sets data sturcture
|
||||||
for ensure each set has unique id;
|
for ensure each set has unique id;
|
||||||
@ -2163,6 +2223,42 @@ Will return a value that matches this schema:
|
|||||||
(map->tokens-lib)
|
(map->tokens-lib)
|
||||||
(check)))))
|
(check)))))
|
||||||
|
|
||||||
|
(defn fix-conflicting-token-names
|
||||||
|
[tokens-lib]
|
||||||
|
(let [counter (atom 0)
|
||||||
|
match-suffixes (atom {})
|
||||||
|
|
||||||
|
generate-name
|
||||||
|
(fn [name match]
|
||||||
|
(let [matches (if (contains? @match-suffixes match)
|
||||||
|
@match-suffixes
|
||||||
|
(swap! match-suffixes assoc match (swap! counter inc)))
|
||||||
|
suffix (get matches match)]
|
||||||
|
(str (str/slice name 0 (count match))
|
||||||
|
"-" suffix
|
||||||
|
(str/slice name (count match)))))]
|
||||||
|
|
||||||
|
(update-all-tokens
|
||||||
|
tokens-lib
|
||||||
|
(fn [lib set token]
|
||||||
|
(let [name (get-name token)]
|
||||||
|
(if-let [match (token-name-path-exists? name lib (:id set) (get-id token))]
|
||||||
|
(rename token (generate-name name match))
|
||||||
|
token))))))
|
||||||
|
|
||||||
|
(defn fix-missing-sets-in-themes
|
||||||
|
[tokens-lib]
|
||||||
|
(let [existing-set-names (into #{} (map get-name) (get-sets tokens-lib))
|
||||||
|
fix-theme-sets
|
||||||
|
(fn [_ theme]
|
||||||
|
(let [current-sets (:sets theme)
|
||||||
|
valid-sets (clojure.set/intersection current-sets existing-set-names)]
|
||||||
|
(if-not (= valid-sets current-sets)
|
||||||
|
(assoc theme :sets valid-sets)
|
||||||
|
theme)))]
|
||||||
|
|
||||||
|
(update-all-themes tokens-lib fix-theme-sets)))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; SERIALIZATION (FRESIAN)
|
;; SERIALIZATION (FRESIAN)
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@ -2200,7 +2296,7 @@ Will return a value that matches this schema:
|
|||||||
#?(:clj
|
#?(:clj
|
||||||
(defn- migrate-to-v1-3
|
(defn- migrate-to-v1-3
|
||||||
"Migrate the TokensLib data structure internals to v1.3 version; it
|
"Migrate the TokensLib data structure internals to v1.3 version; it
|
||||||
expects input from v1.2 version"
|
expects input from v1.2 version"
|
||||||
[{:keys [sets themes] :as params}]
|
[{:keys [sets themes] :as params}]
|
||||||
(let [migrate-token
|
(let [migrate-token
|
||||||
(fn [token]
|
(fn [token]
|
||||||
@ -2248,7 +2344,7 @@ Will return a value that matches this schema:
|
|||||||
#?(:clj
|
#?(:clj
|
||||||
(defn- migrate-to-v1-4
|
(defn- migrate-to-v1-4
|
||||||
"Migrate the TokensLib data structure internals to v1.4 version; it
|
"Migrate the TokensLib data structure internals to v1.4 version; it
|
||||||
expects input from v1.3 version"
|
expects input from v1.3 version"
|
||||||
[params]
|
[params]
|
||||||
(let [migrate-set-node
|
(let [migrate-set-node
|
||||||
(fn recurse [node]
|
(fn recurse [node]
|
||||||
|
|||||||
@ -2027,12 +2027,235 @@
|
|||||||
(t/is (= (:value imported-ref) (:value original-ref))))))))
|
(t/is (= (:value imported-ref) (:value original-ref))))))))
|
||||||
|
|
||||||
(t/deftest token-name-path-exists?-test
|
(t/deftest token-name-path-exists?-test
|
||||||
(t/is (true? (ctob/token-name-path-exists? "border-radius" {"border-radius" {"sm" {:name "sm"}}})))
|
(let [tokens-lib (ctob/make-tokens-lib)
|
||||||
(t/is (true? (ctob/token-name-path-exists? "border-radius" {"border-radius" {:name "sm"}})))
|
add-set (fn [lib set-label set-name token-names]
|
||||||
(t/is (true? (ctob/token-name-path-exists? "border-radius.sm" {"border-radius" {:name "sm"}})))
|
(ctob/add-set lib (ctob/make-token-set
|
||||||
(t/is (true? (ctob/token-name-path-exists? "border-radius.sm.x" {"border-radius" {:name "sm"}})))
|
:id (thi/new-id! set-label)
|
||||||
(t/is (false? (ctob/token-name-path-exists? "other" {"border-radius" {:name "sm"}})))
|
:name set-name
|
||||||
(t/is (false? (ctob/token-name-path-exists? "dark.border-radius.md" {"dark" {"border-radius" {"sm" {:name "sm"}}}}))))
|
:tokens (into {}
|
||||||
|
(map (fn [token-name]
|
||||||
|
[token-name (ctob/make-token
|
||||||
|
{:name token-name
|
||||||
|
:type :border-radius
|
||||||
|
:value "1"})]))
|
||||||
|
token-names))))]
|
||||||
|
|
||||||
|
;; Empty cases
|
||||||
|
|
||||||
|
(t/testing "returns match for no library or empty library or empty name"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? nil nil nil nil)))
|
||||||
|
(t/is (not (ctob/token-name-path-exists? nil tokens-lib nil nil)))
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "" tokens-lib nil nil)))
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "bad-name" tokens-lib nil nil)))
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "bad-name"
|
||||||
|
(ctob/add-theme tokens-lib
|
||||||
|
(ctob/make-token-theme {:name "theme1"}))
|
||||||
|
nil
|
||||||
|
nil))))
|
||||||
|
|
||||||
|
(t/testing "throws error when giving a bad set id"
|
||||||
|
(t/is (thrown-with-msg? #?(:clj AssertionError :cljs js/Error)
|
||||||
|
#"Set '[0-9a-f-]+' does not exist in the library"
|
||||||
|
(ctob/token-name-path-exists? "some-name"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :empty-set "empty-set" []))
|
||||||
|
(thi/new-id! :non-existent-set) nil))))
|
||||||
|
|
||||||
|
(t/testing "does not throw error when giving a nil set id"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "some-name"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :empty-set "empty-set" []))
|
||||||
|
nil nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match for empty set"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "some-name"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :empty-set "empty-set" []))
|
||||||
|
(thi/id :empty-set) nil))))
|
||||||
|
|
||||||
|
;; Search in the current set
|
||||||
|
|
||||||
|
(t/testing "returns match when name matches exactly a token in the set without groups"
|
||||||
|
(t/is (= "token1"
|
||||||
|
(ctob/token-name-path-exists? "token1"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["token1" "token2" "token3"]))
|
||||||
|
(thi/id :set1) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns match when name matches exactly a token in the set with groups"
|
||||||
|
(t/is (= "group1.subgroup1.token2"
|
||||||
|
(ctob/token-name-path-exists? "group1.subgroup1.token2"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"]))
|
||||||
|
(thi/id :set1) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns match when name is a subpath of a token in the set"
|
||||||
|
(t/is (= "group1"
|
||||||
|
(ctob/token-name-path-exists? "group1"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"]))
|
||||||
|
(thi/id :set1) nil)))
|
||||||
|
(t/is (= "group1.subgroup1"
|
||||||
|
(ctob/token-name-path-exists? "group1.subgroup1"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"]))
|
||||||
|
(thi/id :set1) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns match when one of the token names in the set is a subpath of the name"
|
||||||
|
(t/is (= "group2.subgroup2.token3"
|
||||||
|
(ctob/token-name-path-exists? "group2.subgroup2.token3.subtoken"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"]))
|
||||||
|
(thi/id :set1) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when name matches part of the path but not the full token name"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "group1.subgroup1.token4"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"]))
|
||||||
|
(thi/id :set1) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when name does not match any part of the token names in the set"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "token4"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"]))
|
||||||
|
(thi/id :set1) nil))))
|
||||||
|
|
||||||
|
;; Search in other set
|
||||||
|
|
||||||
|
(t/testing "returns not match when name matches exactly a token in other set without groups"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "token1"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["token1" "token2" "token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when name matches exactly a token in other set with groups"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "group1.subgroup1.token2"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns match when name is a subpath of a token in other set"
|
||||||
|
(t/is (= "group1"
|
||||||
|
(ctob/token-name-path-exists? "group1"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil)))
|
||||||
|
(t/is (= "group1.subgroup1"
|
||||||
|
(ctob/token-name-path-exists? "group1.subgroup1"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns match when one of the token names in other set is a subpath of the name"
|
||||||
|
(t/is (= "group2.subgroup2.token3"
|
||||||
|
(ctob/token-name-path-exists? "group2.subgroup2.token3.subtoken"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when name matches part of the path but not the full token name"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "group1.subgroup1.token4"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when name does not match any part of the token names in the set"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "token4"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" []))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
;; Additional cases
|
||||||
|
|
||||||
|
(t/testing "returns match when matches an exact token with several sets"
|
||||||
|
(t/is (= "group3.subgroup3.token4"
|
||||||
|
(ctob/token-name-path-exists? "group3.subgroup3.token4"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" ["group3.subgroup3.token4"]))
|
||||||
|
(thi/id :set2) nil))))
|
||||||
|
|
||||||
|
(t/testing "returns match when matches in one of the sets, even if the set is disabled"
|
||||||
|
(let [tokens-lib (-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" ["group3.subgroup3.token4"]))
|
||||||
|
hidden-theme (ctob/get-hidden-theme tokens-lib)
|
||||||
|
tokens-lib (ctob/toggle-set-in-theme tokens-lib (:id hidden-theme) "set2")]
|
||||||
|
(t/is (= "group3.subgroup3.token4"
|
||||||
|
(ctob/token-name-path-exists? "group3.subgroup3.token4"
|
||||||
|
tokens-lib
|
||||||
|
(thi/id :set2)
|
||||||
|
nil)))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when does not match in any of the sets"
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "group3.subgroup3.token5"
|
||||||
|
(-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" ["group3.subgroup3.token4"]))
|
||||||
|
(thi/id :set1)
|
||||||
|
nil))))
|
||||||
|
|
||||||
|
(t/testing "returns not match when the token exists but is the one we have told it to ignore"
|
||||||
|
(let [tokens-lib (-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" ["group3.subgroup3.token4"]))
|
||||||
|
token4 (ctob/get-token-by-name tokens-lib "set2" "group3.subgroup3.token4")]
|
||||||
|
(t/is (not (ctob/token-name-path-exists? "group3.subgroup3.token4"
|
||||||
|
tokens-lib
|
||||||
|
(thi/id :set2)
|
||||||
|
(:id token4))))))
|
||||||
|
|
||||||
|
(t/testing "returns match when we give an id to ignore but is not the token that matches"
|
||||||
|
(let [tokens-lib (-> tokens-lib
|
||||||
|
(add-set :set1 "set1" ["group1.subgroup1.token1"
|
||||||
|
"group1.subgroup1.token2"
|
||||||
|
"group2.subgroup2.token3"])
|
||||||
|
(add-set :set2 "set2" ["group3.subgroup3.token4"]))
|
||||||
|
token4 (ctob/get-token-by-name tokens-lib "set2" "group3.subgroup3.token4")]
|
||||||
|
(t/is (= "group1.subgroup1.token1"
|
||||||
|
(ctob/token-name-path-exists? "group1.subgroup1.token1"
|
||||||
|
tokens-lib
|
||||||
|
(thi/id :set1)
|
||||||
|
(:id token4))))))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(t/deftest token-set-encode-decode-roundtrip-with-invalid-set-name
|
(t/deftest token-set-encode-decode-roundtrip-with-invalid-set-name
|
||||||
|
|||||||
280
common/test/common_tests/types/tokens_migrations_test.cljc
Normal file
280
common/test/common_tests/types/tokens_migrations_test.cljc
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns common-tests.types.tokens-migrations-test
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.test-helpers.ids-map :as thi]
|
||||||
|
[app.common.time :as ct]
|
||||||
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[clojure.datafy :refer [datafy]]
|
||||||
|
[clojure.test :as t]))
|
||||||
|
|
||||||
|
(t/deftest test-v1-5-fix-token-names
|
||||||
|
|
||||||
|
(t/testing "empty tokens-lib should not need any action"
|
||||||
|
(let [tokens-lib (ctob/make-tokens-lib)
|
||||||
|
tokens-lib' (ctob/fix-conflicting-token-names tokens-lib)]
|
||||||
|
(t/is (empty? (d/map-diff (datafy tokens-lib) (datafy tokens-lib'))))))
|
||||||
|
|
||||||
|
(t/testing "tokens with valid names should not need any action"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"
|
||||||
|
:tokens {"name1" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token1)
|
||||||
|
:name "name1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "1"})}))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"
|
||||||
|
:tokens {"name1" (ctob/make-token ;; Same name in different
|
||||||
|
{:id (thi/new-id! :token2) ;; sets is ok
|
||||||
|
:name "name1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "2"})})))
|
||||||
|
|
||||||
|
tokens-lib' (ctob/fix-conflicting-token-names tokens-lib)]
|
||||||
|
|
||||||
|
(t/is (empty? (d/map-diff (datafy tokens-lib) (datafy tokens-lib'))))))
|
||||||
|
|
||||||
|
(t/testing "tokens with conflicting names should be renamed, and the rest of the library should be unchanged"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"
|
||||||
|
:tokens {"name1" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token1)
|
||||||
|
:name "name1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "1"})}))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"
|
||||||
|
:tokens {"name1.name2" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token2)
|
||||||
|
:name "name1.name2"
|
||||||
|
:type :border-radius
|
||||||
|
:value "2"})})))
|
||||||
|
|
||||||
|
tokens-lib' (ctob/fix-conflicting-token-names tokens-lib)
|
||||||
|
|
||||||
|
token-sets (ctob/get-set-tree tokens-lib)
|
||||||
|
set1 (ctob/get-set tokens-lib (thi/id :set1))
|
||||||
|
set2 (ctob/get-set tokens-lib (thi/id :set2))
|
||||||
|
tokens1 (ctob/get-tokens tokens-lib (thi/id :set1))
|
||||||
|
tokens2 (ctob/get-tokens tokens-lib (thi/id :set2))
|
||||||
|
token1 (ctob/get-token tokens-lib (thi/id :set1) (thi/id :token1))
|
||||||
|
token2 (ctob/get-token tokens-lib (thi/id :set2) (thi/id :token2))
|
||||||
|
|
||||||
|
token-sets' (ctob/get-set-tree tokens-lib')
|
||||||
|
set1' (ctob/get-set tokens-lib' (thi/id :set1))
|
||||||
|
set2' (ctob/get-set tokens-lib' (thi/id :set2))
|
||||||
|
tokens1' (ctob/get-tokens tokens-lib' (thi/id :set1))
|
||||||
|
tokens2' (ctob/get-tokens tokens-lib' (thi/id :set2))
|
||||||
|
token1' (ctob/get-token tokens-lib' (thi/id :set1) (thi/id :token1))
|
||||||
|
token2' (ctob/get-token tokens-lib' (thi/id :set2) (thi/id :token2))]
|
||||||
|
|
||||||
|
(t/is (= (count token-sets') (count token-sets)))
|
||||||
|
|
||||||
|
(t/is (= (ctob/get-id set1') (ctob/get-id set1)))
|
||||||
|
(t/is (= (ctob/get-name set1') (ctob/get-name set1)))
|
||||||
|
(t/is (= (ctob/get-description set1') (ctob/get-description set1)))
|
||||||
|
(t/is (ct/is-after-or-equal? (ctob/get-modified-at set1') (ctob/get-modified-at set1))) ;; <-- MODIFIED
|
||||||
|
|
||||||
|
(t/is (= (ctob/get-id set2') (ctob/get-id set2)))
|
||||||
|
(t/is (= (ctob/get-name set2') (ctob/get-name set2)))
|
||||||
|
(t/is (= (ctob/get-description set2') (ctob/get-description set2)))
|
||||||
|
(t/is (= (ctob/get-modified-at set2') (ctob/get-modified-at set2)))
|
||||||
|
|
||||||
|
(t/is (= (count tokens1') (count tokens1)))
|
||||||
|
(t/is (= (count tokens2') (count tokens2)))
|
||||||
|
|
||||||
|
(t/is (= (ctob/get-id token1') (ctob/get-id token1)))
|
||||||
|
(t/is (= (ctob/get-name token1') "name1-1")) ;; <-- RENAMED
|
||||||
|
(t/is (= (ctob/get-description token1') (ctob/get-description token1)))
|
||||||
|
(t/is (ct/is-after-or-equal? (ctob/get-modified-at set1') (ctob/get-modified-at set1))) ;; <-- MODIFIED
|
||||||
|
(t/is (= (:type token1') (:type token1)))
|
||||||
|
(t/is (= (:value token1') (:value token1)))
|
||||||
|
|
||||||
|
(t/is (= (ctob/get-id token2') (ctob/get-id token2)))
|
||||||
|
(t/is (= (ctob/get-name token2') (ctob/get-name token2)))
|
||||||
|
(t/is (= (ctob/get-description token2') (ctob/get-description token2)))
|
||||||
|
(t/is (= (ctob/get-modified-at token2') (ctob/get-modified-at token2)))
|
||||||
|
(t/is (= (:type token2') (:type token2)))
|
||||||
|
(t/is (= (:value token2') (:value token2)))))
|
||||||
|
|
||||||
|
(t/testing "the renamed token is always the first one found with a conflicting name"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"
|
||||||
|
:tokens {"name1.name2" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token1)
|
||||||
|
:name "name1.name2"
|
||||||
|
:type :border-radius
|
||||||
|
:value "1"})}))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"
|
||||||
|
:tokens {"name1" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token2)
|
||||||
|
:name "name1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "2"})})))
|
||||||
|
|
||||||
|
tokens-lib' (ctob/fix-conflicting-token-names tokens-lib)
|
||||||
|
token1' (ctob/get-token tokens-lib' (thi/id :set1) (thi/id :token1))
|
||||||
|
token2' (ctob/get-token tokens-lib' (thi/id :set2) (thi/id :token2))]
|
||||||
|
|
||||||
|
(t/is (= "name1-1.name2" (ctob/get-name token1')))
|
||||||
|
(t/is (= "name1" (ctob/get-name token2')))))
|
||||||
|
|
||||||
|
(t/testing "several tokens with the same conflicting prefix should be assigned the same number as suffixes"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"
|
||||||
|
:tokens {"name1.name2" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token1)
|
||||||
|
:name "name1.name2"
|
||||||
|
:type :border-radius
|
||||||
|
:value "1"})
|
||||||
|
"name1.name3" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token2)
|
||||||
|
:name "name1.name3"
|
||||||
|
:type :border-radius
|
||||||
|
:value "2"})}))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"
|
||||||
|
:tokens {"name1" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token3)
|
||||||
|
:name "name1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "3"})})))
|
||||||
|
|
||||||
|
tokens-lib' (ctob/fix-conflicting-token-names tokens-lib)
|
||||||
|
token1' (ctob/get-token tokens-lib' (thi/id :set1) (thi/id :token1))
|
||||||
|
token2' (ctob/get-token tokens-lib' (thi/id :set1) (thi/id :token2))
|
||||||
|
token3' (ctob/get-token tokens-lib' (thi/id :set2) (thi/id :token3))]
|
||||||
|
|
||||||
|
(t/is (= "name1-1.name2" (ctob/get-name token1')))
|
||||||
|
(t/is (= "name1-1.name3" (ctob/get-name token2')))
|
||||||
|
(t/is (= "name1" (ctob/get-name token3')))))
|
||||||
|
|
||||||
|
(t/testing "tokens with diferent conflicting prefixes should be assigned consecutive numbers as suffixes"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"
|
||||||
|
:tokens {"name1" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token1)
|
||||||
|
:name "name1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "1"})
|
||||||
|
"name2" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token2)
|
||||||
|
:name "name2"
|
||||||
|
:type :border-radius
|
||||||
|
:value "2"})}))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"
|
||||||
|
:tokens {"name1.subname1" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token3)
|
||||||
|
:name "name1.subname1"
|
||||||
|
:type :border-radius
|
||||||
|
:value "3"})}))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set3)
|
||||||
|
:name "set3"
|
||||||
|
:tokens {"name2.subname2" (ctob/make-token
|
||||||
|
{:id (thi/new-id! :token4)
|
||||||
|
:name "name2.subname2"
|
||||||
|
:type :border-radius
|
||||||
|
:value "3"})})))
|
||||||
|
|
||||||
|
tokens-lib' (ctob/fix-conflicting-token-names tokens-lib)
|
||||||
|
token1' (ctob/get-token tokens-lib' (thi/id :set1) (thi/id :token1))
|
||||||
|
token2' (ctob/get-token tokens-lib' (thi/id :set1) (thi/id :token2))
|
||||||
|
token3' (ctob/get-token tokens-lib' (thi/id :set2) (thi/id :token3))
|
||||||
|
token4' (ctob/get-token tokens-lib' (thi/id :set3) (thi/id :token4))]
|
||||||
|
|
||||||
|
(t/is (= "name1-1" (ctob/get-name token1')))
|
||||||
|
(t/is (= "name2-2" (ctob/get-name token2')))
|
||||||
|
(t/is (= "name1.subname1" (ctob/get-name token3')))
|
||||||
|
(t/is (= "name2.subname2" (ctob/get-name token4'))))))
|
||||||
|
|
||||||
|
(t/deftest test-v1-6-fix-token-names
|
||||||
|
|
||||||
|
(t/testing "empty tokens-lib should not need any action"
|
||||||
|
(let [tokens-lib (ctob/make-tokens-lib)
|
||||||
|
tokens-lib' (ctob/fix-missing-sets-in-themes tokens-lib)]
|
||||||
|
(t/is (empty? (d/map-diff (datafy tokens-lib) (datafy tokens-lib'))))))
|
||||||
|
|
||||||
|
(t/testing "library with a valid theme should not need any action"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"))
|
||||||
|
(ctob/add-theme (ctob/make-token-theme
|
||||||
|
:id (thi/new-id! :theme1)
|
||||||
|
:name "theme1"
|
||||||
|
:sets #{"set1"})))
|
||||||
|
tokens-lib' (ctob/fix-missing-sets-in-themes tokens-lib)]
|
||||||
|
(t/is (empty? (d/map-diff (datafy tokens-lib) (datafy tokens-lib'))))))
|
||||||
|
|
||||||
|
(t/testing "library with a theme containing a non-existent set should have it removed, and the rest of the library should be unchanged"
|
||||||
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set1)
|
||||||
|
:name "set1"))
|
||||||
|
(ctob/add-set (ctob/make-token-set
|
||||||
|
:id (thi/new-id! :set2)
|
||||||
|
:name "set2"))
|
||||||
|
(ctob/add-theme (ctob/make-token-theme
|
||||||
|
:id (thi/new-id! :theme1)
|
||||||
|
:name "theme1"
|
||||||
|
:sets #{"set1" "set3"})) ;; "set3" does not exist
|
||||||
|
(ctob/add-theme (ctob/make-token-theme
|
||||||
|
:id (thi/new-id! :theme2)
|
||||||
|
:name "theme2"
|
||||||
|
:sets #{"set1" "set2"})))
|
||||||
|
tokens-lib' (ctob/fix-missing-sets-in-themes tokens-lib)
|
||||||
|
|
||||||
|
set1 (ctob/get-set tokens-lib (thi/id :set1))
|
||||||
|
set2 (ctob/get-set tokens-lib (thi/id :set2))
|
||||||
|
theme1 (ctob/get-theme tokens-lib (thi/id :theme1))
|
||||||
|
theme2 (ctob/get-theme tokens-lib (thi/id :theme2))
|
||||||
|
set1' (ctob/get-set tokens-lib' (thi/id :set1))
|
||||||
|
set2' (ctob/get-set tokens-lib' (thi/id :set2))
|
||||||
|
theme1' (ctob/get-theme tokens-lib' (thi/id :theme1))
|
||||||
|
theme2' (ctob/get-theme tokens-lib' (thi/id :theme2))]
|
||||||
|
|
||||||
|
(t/is (= (:sets theme1') #{"set1"}))
|
||||||
|
(t/is (= (:sets theme2') #{"set1" "set2"}))
|
||||||
|
|
||||||
|
(t/is (= (ctob/get-id set1') (ctob/get-id set1)))
|
||||||
|
(t/is (= (ctob/get-name set1') (ctob/get-name set1)))
|
||||||
|
(t/is (= (ctob/get-description set1') (ctob/get-description set1)))
|
||||||
|
(t/is (= (ctob/get-modified-at set1') (ctob/get-modified-at set1)))
|
||||||
|
(t/is (= (ctob/get-id set2') (ctob/get-id set2)))
|
||||||
|
(t/is (= (ctob/get-name set2') (ctob/get-name set2)))
|
||||||
|
(t/is (= (ctob/get-description set2') (ctob/get-description set2)))
|
||||||
|
(t/is (= (ctob/get-modified-at set2') (ctob/get-modified-at set2)))
|
||||||
|
|
||||||
|
(t/is (= (ctob/get-id theme1') (ctob/get-id theme1)))
|
||||||
|
(t/is (= (ctob/get-name theme1') (ctob/get-name theme1)))
|
||||||
|
(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))))))
|
||||||
@ -1687,29 +1687,58 @@ test.describe("Tokens - creation", () => {
|
|||||||
// Submit button should remain disabled when value is empty
|
// Submit button should remain disabled when value is empty
|
||||||
await expect(submitButton).toBeDisabled();
|
await expect(submitButton).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("User duplicate color token", async ({ page }) => {
|
test("User cannot create token with a conflicting name in other set", async ({ page }) => {
|
||||||
const { tokensSidebar, tokenContextMenuForToken } =
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
|
||||||
await setupTokensFileRender(page);
|
await setupTokensFileRender(page);
|
||||||
|
|
||||||
await expect(tokensSidebar).toBeVisible();
|
await expect(tokensSidebar).toBeVisible();
|
||||||
|
|
||||||
await unfoldTokenType(tokensSidebar, "color");
|
await tokenThemesSetsSidebar.getByRole('button', { name: 'light', exact: true }).click();
|
||||||
|
|
||||||
const colorToken = tokensSidebar.getByRole("button", {
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||||
name: "colors.blue.100",
|
await tokensTabPanel
|
||||||
});
|
.getByRole("button", { name: "Add Token: Color" })
|
||||||
|
.click();
|
||||||
|
|
||||||
await colorToken.click({ button: "right" });
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||||
await expect(tokenContextMenuForToken).toBeVisible();
|
|
||||||
|
|
||||||
await tokenContextMenuForToken.getByText("Duplicate token").click();
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||||
await expect(tokenContextMenuForToken).not.toBeVisible();
|
const valueField = tokensUpdateCreateModal.getByLabel("Value");
|
||||||
|
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||||
await expect(
|
name: "Save",
|
||||||
tokensSidebar.getByRole("button", { name: "colors.blue.100-copy" }),
|
|
||||||
).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initially submit button should be disabled
|
||||||
|
await expect(submitButton).toBeDisabled();
|
||||||
|
|
||||||
|
await nameField.click();
|
||||||
|
|
||||||
|
// Fill in the name of an existing token in the current set
|
||||||
|
await nameField.fill("accent.default");
|
||||||
|
|
||||||
|
// An error message should appear and submit button should be disabled
|
||||||
|
await expect(tokensUpdateCreateModal.getByText('A token already exists at the path: accent.default'))
|
||||||
|
.toBeVisible()
|
||||||
|
|
||||||
|
await expect(submitButton).toBeDisabled();
|
||||||
|
|
||||||
|
// Fill in a name that clashes with tokens like colors.red.600 in set core
|
||||||
|
await nameField.fill("colors.red");
|
||||||
|
|
||||||
|
// An error message should appear and submit button should be disabled
|
||||||
|
await expect(tokensUpdateCreateModal.getByText('A token already exists at the path: colors.red'))
|
||||||
|
.toBeVisible()
|
||||||
|
|
||||||
|
await expect(submitButton).toBeDisabled();
|
||||||
|
|
||||||
|
// Fill in a name that matches exactly a token in another set
|
||||||
|
await nameField.fill("colors.red.600");
|
||||||
|
await valueField.fill("#6000000");
|
||||||
|
|
||||||
|
// Submit button should be enabled now
|
||||||
|
await expect(submitButton).toBeEnabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("User creates grouped color token", async ({ page }) => {
|
test("User creates grouped color token", async ({ page }) => {
|
||||||
|
|||||||
@ -27,6 +27,11 @@
|
|||||||
:error/fn #(tr "errors.tokens.invalid-json-token-name")
|
:error/fn #(tr "errors.tokens.invalid-json-token-name")
|
||||||
:error/detail #(tr "errors.tokens.invalid-json-token-name-detail" %)}
|
:error/detail #(tr "errors.tokens.invalid-json-token-name-detail" %)}
|
||||||
|
|
||||||
|
:error.import/duplicated-token-name
|
||||||
|
{:error/code :error.import/duplicated-token-name
|
||||||
|
:error/fn #(tr "workspace.tokens.duplicated-json-token-name")
|
||||||
|
:error/detail #(tr "workspace.tokens.duplicated-json-token-name-detail" %)}
|
||||||
|
|
||||||
:error.import/style-dictionary-reference-errors
|
:error.import/style-dictionary-reference-errors
|
||||||
{:error/code :error.import/style-dictionary-reference-errors
|
{:error/code :error.import/style-dictionary-reference-errors
|
||||||
:error/fn #(str (tr "errors.tokens.import-error") "\n\n" (first %))
|
:error/fn #(str (tr "errors.tokens.import-error") "\n\n" (first %))
|
||||||
|
|||||||
@ -20,6 +20,12 @@
|
|||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(defn- extract-error-with-code
|
||||||
|
"Return the error if it has an error code generated from Penpot code"
|
||||||
|
[err]
|
||||||
|
(when (contains? (ex-data err) :error/code)
|
||||||
|
(wte/error-ex-info (:error/code (ex-data err)) (ex-message err) err)))
|
||||||
|
|
||||||
(defn- extract-reference-errors
|
(defn- extract-reference-errors
|
||||||
"Extracts reference errors from errors produced by StyleDictionary."
|
"Extracts reference errors from errors produced by StyleDictionary."
|
||||||
[err]
|
[err]
|
||||||
@ -75,7 +81,8 @@
|
|||||||
{:tokens-lib (ctob/parse-decoded-json decoded-json file-name)
|
{:tokens-lib (ctob/parse-decoded-json decoded-json file-name)
|
||||||
:unknown-tokens (ctob/get-tokens-of-unknown-type decoded-json {})}
|
:unknown-tokens (ctob/get-tokens-of-unknown-type decoded-json {})}
|
||||||
(catch js/Error e
|
(catch js/Error e
|
||||||
(let [err (or (extract-name-error e)
|
(let [err (or (extract-error-with-code e)
|
||||||
|
(extract-name-error e)
|
||||||
(wte/error-ex-info :error.import/invalid-json-data decoded-json e))]
|
(wte/error-ex-info :error.import/invalid-json-data decoded-json e))]
|
||||||
(throw err)))))
|
(throw err)))))
|
||||||
|
|
||||||
|
|||||||
@ -327,4 +327,5 @@
|
|||||||
:type type
|
:type type
|
||||||
:selected-shapes selected-shapes
|
:selected-shapes selected-shapes
|
||||||
:is-selected-inside-layout is-selected-inside-layout
|
:is-selected-inside-layout is-selected-inside-layout
|
||||||
:active-theme-tokens resolved-active-tokens}])]))
|
:active-theme-tokens resolved-active-tokens
|
||||||
|
:selected-token-set-id selected-token-set-id}])]))
|
||||||
|
|||||||
@ -8,12 +8,13 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.files.tokens :as cfo]
|
[app.common.files.tokens :as cfo]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
||||||
[app.main.ui.workspace.tokens.management.forms.generic-form :as generic]
|
[app.main.ui.workspace.tokens.management.forms.generic-form :as generic]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc form*
|
(mf/defc form*
|
||||||
[{:keys [token token-type] :as props}]
|
[{:keys [token token-type selected-token-set-id] :as props}]
|
||||||
(let [initial
|
(let [initial
|
||||||
(mf/with-memo [token-type token]
|
(mf/with-memo [token-type token]
|
||||||
{:type token-type
|
{:type token-type
|
||||||
@ -22,7 +23,11 @@
|
|||||||
:description (:description token "")
|
:description (:description token "")
|
||||||
:color-result ""})
|
:color-result ""})
|
||||||
|
|
||||||
props (mf/spread-props props {:make-schema #(-> (cfo/make-token-schema %1 token-type)
|
props (mf/spread-props props {:make-schema #(-> (cfo/make-token-schema %1
|
||||||
|
token-type
|
||||||
|
selected-token-set-id
|
||||||
|
(when (ctob/token? token)
|
||||||
|
(ctob/get-id token)))
|
||||||
(sm/dissoc-key :id)
|
(sm/dissoc-key :id)
|
||||||
(sm/assoc-key :color-result :string))
|
(sm/assoc-key :color-result :string))
|
||||||
:initial initial
|
:initial initial
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
[app.common.files.tokens :as cfo]
|
[app.common.files.tokens :as cfo]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.types.token :as cto]
|
[app.common.types.token :as cto]
|
||||||
|
[app.common.types.tokens-lib :as ctob]
|
||||||
[app.main.data.workspace.tokens.errors :as wte]
|
[app.main.data.workspace.tokens.errors :as wte]
|
||||||
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
[app.main.ui.workspace.tokens.management.forms.controls :as token.controls]
|
||||||
[app.main.ui.workspace.tokens.management.forms.generic-form :as generic]
|
[app.main.ui.workspace.tokens.management.forms.generic-form :as generic]
|
||||||
@ -29,7 +30,7 @@
|
|||||||
(default-validate-token)))
|
(default-validate-token)))
|
||||||
|
|
||||||
(mf/defc form*
|
(mf/defc form*
|
||||||
[{:keys [token token-type] :rest props}]
|
[{:keys [token token-type selected-token-set-id] :rest props}]
|
||||||
(let [token
|
(let [token
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(if token
|
(if token
|
||||||
@ -37,7 +38,11 @@
|
|||||||
{:type token-type}))
|
{:type token-type}))
|
||||||
props (mf/spread-props props {:token token
|
props (mf/spread-props props {:token token
|
||||||
:token-type token-type
|
:token-type token-type
|
||||||
:make-schema #(-> (cfo/make-token-schema %1 token-type)
|
:make-schema #(-> (cfo/make-token-schema %1
|
||||||
|
token-type
|
||||||
|
selected-token-set-id
|
||||||
|
(when (ctob/token? token)
|
||||||
|
(ctob/get-id token)))
|
||||||
(sm/dissoc-key :id)
|
(sm/dissoc-key :id)
|
||||||
;; The value as edited in the form is a simple stirng.
|
;; The value as edited in the form is a simple stirng.
|
||||||
;; It's converted to vector in the validator.
|
;; It's converted to vector in the validator.
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
(ns app.main.ui.workspace.tokens.management.forms.form-container
|
(ns app.main.ui.workspace.tokens.management.forms.form-container
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.types.tokens-lib :as ctob]
|
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.ui.workspace.tokens.management.forms.color :as color]
|
[app.main.ui.workspace.tokens.management.forms.color :as color]
|
||||||
@ -24,27 +22,13 @@
|
|||||||
(let [token-type
|
(let [token-type
|
||||||
(or (:type token) token-type)
|
(or (:type token) token-type)
|
||||||
|
|
||||||
tokens-in-selected-set
|
|
||||||
(mf/deref refs/workspace-all-tokens-in-selected-set)
|
|
||||||
|
|
||||||
token-path
|
|
||||||
(mf/with-memo [token]
|
|
||||||
(ctob/get-token-path token))
|
|
||||||
|
|
||||||
tokens-tree-in-selected-set
|
|
||||||
(mf/with-memo [token-path tokens-in-selected-set]
|
|
||||||
(-> (ctob/tokens-tree tokens-in-selected-set)
|
|
||||||
(d/dissoc-in token-path)))
|
|
||||||
|
|
||||||
props
|
props
|
||||||
(if (contains? cf/flags :token-combobox)
|
(if (contains? cf/flags :token-combobox)
|
||||||
(mf/spread-props props {:token-type token-type
|
(mf/spread-props props {:token-type token-type
|
||||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
|
||||||
:selected-token-set-id (mf/deref refs/selected-token-set-id)
|
:selected-token-set-id (mf/deref refs/selected-token-set-id)
|
||||||
:token token
|
:token token
|
||||||
:input-component token.controls/value-combobox*})
|
:input-component token.controls/value-combobox*})
|
||||||
(mf/spread-props props {:token-type token-type
|
(mf/spread-props props {:token-type token-type
|
||||||
:tokens-tree-in-selected-set tokens-tree-in-selected-set
|
|
||||||
:selected-token-set-id (mf/deref refs/selected-token-set-id)
|
:selected-token-set-id (mf/deref refs/selected-token-set-id)
|
||||||
:token token}))
|
:token token}))
|
||||||
text-case-props (if (contains? cf/flags :token-combobox)
|
text-case-props (if (contains? cf/flags :token-combobox)
|
||||||
|
|||||||
@ -60,7 +60,6 @@
|
|||||||
action
|
action
|
||||||
is-create
|
is-create
|
||||||
selected-token-set-id
|
selected-token-set-id
|
||||||
tokens-tree-in-selected-set
|
|
||||||
token-type
|
token-type
|
||||||
make-schema
|
make-schema
|
||||||
input-component
|
input-component
|
||||||
@ -69,7 +68,11 @@
|
|||||||
value-subfield
|
value-subfield
|
||||||
input-value-placeholder] :as props}]
|
input-value-placeholder] :as props}]
|
||||||
|
|
||||||
(let [make-schema (or make-schema #(-> (cfo/make-token-schema % token-type)
|
(let [make-schema (or make-schema #(-> (cfo/make-token-schema %
|
||||||
|
token-type
|
||||||
|
selected-token-set-id
|
||||||
|
(when (ctob/token? token)
|
||||||
|
(ctob/get-id token)))
|
||||||
(sm/dissoc-key :id)))
|
(sm/dissoc-key :id)))
|
||||||
input-component (or input-component token.controls/input*)
|
input-component (or input-component token.controls/input*)
|
||||||
validate-token (or validator default-validate-token)
|
validate-token (or validator default-validate-token)
|
||||||
@ -86,6 +89,8 @@
|
|||||||
|
|
||||||
token-title (str/lower (:title token-properties))
|
token-title (str/lower (:title token-properties))
|
||||||
|
|
||||||
|
tokens-lib (mf/deref refs/tokens-lib)
|
||||||
|
|
||||||
;; All tokens in the lib, as a map name -> token, flattened
|
;; All tokens in the lib, as a map name -> token, flattened
|
||||||
;; including tokens in inactive sets.
|
;; including tokens in inactive sets.
|
||||||
tokens-tree (mf/deref refs/workspace-all-tokens-map)
|
tokens-tree (mf/deref refs/workspace-all-tokens-map)
|
||||||
@ -123,8 +128,8 @@
|
|||||||
resolved-active-tokens))))
|
resolved-active-tokens))))
|
||||||
|
|
||||||
schema
|
schema
|
||||||
(mf/with-memo [tokens-tree-in-selected-set active-tab]
|
(mf/with-memo [tokens-lib active-tab]
|
||||||
(make-schema tokens-tree-in-selected-set active-tab))
|
(make-schema tokens-lib active-tab))
|
||||||
|
|
||||||
initial
|
initial
|
||||||
(mf/with-memo [token initial]
|
(mf/with-memo [token initial]
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.files.tokens :as cfo]
|
[app.common.files.tokens :as cfo]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||||
@ -18,8 +18,8 @@
|
|||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(mf/defc rename-node-form*
|
(mf/defc rename-node-form*
|
||||||
[{:keys [new-node-name node active-tokens tokens-tree variant on-close on-submit]}]
|
[{:keys [new-node-name node active-tokens tokens-lib selected-token-set-id variant on-close on-submit]}]
|
||||||
(let [make-schema #(cfo/make-node-token-schema active-tokens tokens-tree node)
|
(let [make-schema #(cfo/make-node-token-schema active-tokens tokens-lib node selected-token-set-id)
|
||||||
|
|
||||||
schema
|
schema
|
||||||
(mf/with-memo [active-tokens]
|
(mf/with-memo [active-tokens]
|
||||||
@ -82,10 +82,9 @@
|
|||||||
|
|
||||||
(let [variant (d/nilv variant "rename") ;; "rename" or "duplicate"
|
(let [variant (d/nilv variant "rename") ;; "rename" or "duplicate"
|
||||||
|
|
||||||
tokens-tree-in-selected-set
|
selected-token-set-id (mf/deref refs/selected-token-set-id)
|
||||||
(mf/with-memo [tokens-in-active-set node]
|
|
||||||
(-> (ctob/tokens-tree tokens-in-active-set)
|
tokens-lib (mf/deref refs/tokens-lib)
|
||||||
(d/dissoc-in (:name node))))
|
|
||||||
|
|
||||||
close-modal
|
close-modal
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -118,6 +117,7 @@
|
|||||||
:node node
|
:node node
|
||||||
:variant variant
|
:variant variant
|
||||||
:active-tokens tokens-in-active-set
|
:active-tokens tokens-in-active-set
|
||||||
:tokens-tree tokens-tree-in-selected-set
|
:tokens-lib tokens-lib
|
||||||
|
:selected-token-set-id selected-token-set-id
|
||||||
:on-close close-modal
|
:on-close close-modal
|
||||||
:on-submit rename}]]]))
|
:on-submit rename}]]]))
|
||||||
|
|||||||
@ -255,7 +255,7 @@
|
|||||||
|
|
||||||
;; TODO: use cfo/make-schema:token-value and extend it with shadow and reference fields
|
;; TODO: use cfo/make-schema:token-value and extend it with shadow and reference fields
|
||||||
(defn- make-schema
|
(defn- make-schema
|
||||||
[tokens-tree active-tab]
|
[set-id token-id tokens-lib active-tab]
|
||||||
(sm/schema
|
(sm/schema
|
||||||
[:and
|
[:and
|
||||||
[:map
|
[:map
|
||||||
@ -266,7 +266,7 @@
|
|||||||
(sm/update-properties cto/schema:token-name assoc
|
(sm/update-properties cto/schema:token-name assoc
|
||||||
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||||
#(not (ctob/token-name-path-exists? % tokens-tree))]]]
|
#(not (ctob/token-name-path-exists? % tokens-lib set-id token-id))]]]
|
||||||
|
|
||||||
[:value
|
[:value
|
||||||
[:map
|
[:map
|
||||||
@ -340,8 +340,7 @@
|
|||||||
:shadow [default-token-shadow]}))
|
:shadow [default-token-shadow]}))
|
||||||
|
|
||||||
(mf/defc form*
|
(mf/defc form*
|
||||||
[{:keys [token
|
[{:keys [token token-type selected-token-set-id] :as props}]
|
||||||
token-type] :as props}]
|
|
||||||
(let [token
|
(let [token
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(or token
|
(or token
|
||||||
@ -352,6 +351,12 @@
|
|||||||
{:type token-type
|
{:type token-type
|
||||||
:value {:reference nil
|
:value {:reference nil
|
||||||
:shadow [default-token-shadow]}})))
|
:shadow [default-token-shadow]}})))
|
||||||
|
|
||||||
|
make-schema
|
||||||
|
(mf/with-memo [selected-token-set-id token]
|
||||||
|
(partial make-schema selected-token-set-id (when (ctob/token? token)
|
||||||
|
(ctob/get-id token))))
|
||||||
|
|
||||||
initial
|
initial
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(let [raw-value (:value token)
|
(let [raw-value (:value token)
|
||||||
|
|||||||
@ -209,7 +209,7 @@
|
|||||||
|
|
||||||
;; TODO: use cfo/make-schema:token-value and extend it with typography and reference fields
|
;; TODO: use cfo/make-schema:token-value and extend it with typography and reference fields
|
||||||
(defn- make-schema
|
(defn- make-schema
|
||||||
[tokens-tree active-tab]
|
[set-id token-id tokens-lib active-tab]
|
||||||
(sm/schema
|
(sm/schema
|
||||||
[:and
|
[:and
|
||||||
[:map
|
[:map
|
||||||
@ -220,7 +220,7 @@
|
|||||||
(sm/update-properties cto/schema:token-name assoc
|
(sm/update-properties cto/schema:token-name assoc
|
||||||
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
:error/fn #(str (:value %) (tr "workspace.tokens.token-name-validation-error")))
|
||||||
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
|
||||||
#(not (ctob/token-name-path-exists? % tokens-tree))]]]
|
#(not (ctob/token-name-path-exists? % tokens-lib set-id token-id))]]]
|
||||||
|
|
||||||
[:value
|
[:value
|
||||||
[:map
|
[:map
|
||||||
@ -269,7 +269,7 @@
|
|||||||
result))]]))
|
result))]]))
|
||||||
|
|
||||||
(mf/defc form*
|
(mf/defc form*
|
||||||
[{:keys [token] :as props}]
|
[{:keys [token selected-token-set-id] :as props}]
|
||||||
(let [initial
|
(let [initial
|
||||||
(mf/with-memo [token]
|
(mf/with-memo [token]
|
||||||
(let [value (:value token)
|
(let [value (:value token)
|
||||||
@ -296,6 +296,12 @@
|
|||||||
{:name (:name token "")
|
{:name (:name token "")
|
||||||
:value processed-value
|
:value processed-value
|
||||||
:description (:description token "")}))
|
:description (:description token "")}))
|
||||||
|
|
||||||
|
make-schema
|
||||||
|
(mf/with-memo [selected-token-set-id token]
|
||||||
|
(partial make-schema selected-token-set-id (when (ctob/token? token)
|
||||||
|
(ctob/get-id token))))
|
||||||
|
|
||||||
props (mf/spread-props props {:initial initial
|
props (mf/spread-props props {:initial initial
|
||||||
:make-schema make-schema
|
:make-schema make-schema
|
||||||
:token token
|
:token token
|
||||||
|
|||||||
@ -131,7 +131,7 @@
|
|||||||
|
|
||||||
on-popover-open-click
|
on-popover-open-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps type title modal)
|
(mf/deps type title modal selected-token-set-id)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(st/emit!
|
(st/emit!
|
||||||
@ -143,7 +143,8 @@
|
|||||||
:fields (:fields modal)
|
:fields (:fields modal)
|
||||||
:title title
|
:title title
|
||||||
:action "create"
|
:action "create"
|
||||||
:token-type type})))))
|
:token-type type
|
||||||
|
:selected-token-set-id selected-token-set-id})))))
|
||||||
|
|
||||||
on-token-pill-click
|
on-token-pill-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
|||||||
@ -120,7 +120,9 @@
|
|||||||
(ctob/get-name token)))
|
(ctob/get-name token)))
|
||||||
:schema (cfo/make-token-name-schema
|
:schema (cfo/make-token-name-schema
|
||||||
(some-> (u/locate-tokens-lib file-id)
|
(some-> (u/locate-tokens-lib file-id)
|
||||||
(ctob/get-tokens set-id)))
|
(ctob/get-tokens set-id))
|
||||||
|
set-id
|
||||||
|
id)
|
||||||
:set
|
:set
|
||||||
(fn [_ value]
|
(fn [_ value]
|
||||||
(st/emit! (dwtl/update-token set-id id {:name value})))}
|
(st/emit! (dwtl/update-token set-id id {:name value})))}
|
||||||
@ -305,19 +307,17 @@
|
|||||||
:addToken
|
:addToken
|
||||||
{:enumerable false
|
{:enumerable false
|
||||||
:schema (fn [args]
|
:schema (fn [args]
|
||||||
(let [tokens-tree (-> (u/locate-tokens-lib file-id)
|
[:tuple (-> (cfo/make-token-schema
|
||||||
(ctob/get-tokens id)
|
(u/locate-tokens-lib file-id)
|
||||||
;; Convert to the adecuate format for schema
|
(cto/dtcg-token-type->token-type (-> args (first) (get "type")))
|
||||||
(ctob/tokens-tree))]
|
id
|
||||||
[:tuple (-> (cfo/make-token-schema
|
(-> args (first) (get "id")))
|
||||||
tokens-tree
|
;; Don't allow plugins to set the id
|
||||||
(cto/dtcg-token-type->token-type (-> args (first) (get "type"))))
|
(sm/dissoc-key :id)
|
||||||
;; Don't allow plugins to set the id
|
;; Instruct the json decoder in obj/reify not to process map keys (:key-fn below)
|
||||||
(sm/dissoc-key :id)
|
;; and set a converter that changes DTCG types to internal types (:decode/json).
|
||||||
;; Instruct the json decoder in obj/reify not to process map keys (:key-fn below)
|
;; E.g. "FontFamilies" -> :font-family or "BorderWidth" -> :stroke-width
|
||||||
;; and set a converter that changes DTCG types to internal types (:decode/json).
|
(sm/update-properties assoc :decode/json cfo/convert-dtcg-token))])
|
||||||
;; E.g. "FontFamilies" -> :font-family or "BorderWidth" -> :stroke-width
|
|
||||||
(sm/update-properties assoc :decode/json cfo/convert-dtcg-token))]))
|
|
||||||
:decode/options {:key-fn identity}
|
:decode/options {:key-fn identity}
|
||||||
:fn (fn [attrs]
|
:fn (fn [attrs]
|
||||||
(let [tokens-lib (u/locate-tokens-lib file-id)
|
(let [tokens-lib (u/locate-tokens-lib file-id)
|
||||||
@ -494,12 +494,12 @@
|
|||||||
|
|
||||||
:addTheme
|
:addTheme
|
||||||
{:enumerable false
|
{:enumerable false
|
||||||
:schema (fn [attrs]
|
:schema (fn [args]
|
||||||
[:tuple (-> (sm/schema (cfo/make-token-theme-schema
|
[:tuple (-> (cfo/make-token-theme-schema
|
||||||
(u/locate-tokens-lib file-id)
|
(u/locate-tokens-lib file-id)
|
||||||
(or (obj/get attrs "group") "")
|
(get (first args) :group "")
|
||||||
(or (obj/get attrs "name") "")
|
(get (first args) :name "")
|
||||||
nil))
|
nil)
|
||||||
(sm/dissoc-key :id))]) ;; We don't allow plugins to set the id
|
(sm/dissoc-key :id))]) ;; We don't allow plugins to set the id
|
||||||
:fn (fn [attrs]
|
:fn (fn [attrs]
|
||||||
(let [theme (ctob/make-token-theme attrs)]
|
(let [theme (ctob/make-token-theme attrs)]
|
||||||
|
|||||||
@ -280,7 +280,7 @@
|
|||||||
[explain]
|
[explain]
|
||||||
(->> (:errors explain)
|
(->> (:errors explain)
|
||||||
(reduce csm/interpret-schema-problem {})
|
(reduce csm/interpret-schema-problem {})
|
||||||
(mapcat (comp seq val))
|
#_(mapcat (comp seq val)) ;; FIXME: why is this for? it breaks the message
|
||||||
(map (fn [[field {:keys [message]}]]
|
(map (fn [[field {:keys [message]}]]
|
||||||
(tr "plugins.validation.message" (name field) message)))
|
(tr "plugins.validation.message" (name field) message)))
|
||||||
(str/join ". ")))
|
(str/join ". ")))
|
||||||
|
|||||||
@ -1679,6 +1679,10 @@ msgstr "Cannot complete drop, a set with same name already exists at path."
|
|||||||
msgid "errors.token-theme-already-exists"
|
msgid "errors.token-theme-already-exists"
|
||||||
msgstr "Theme Option with the same name exists"
|
msgstr "Theme Option with the same name exists"
|
||||||
|
|
||||||
|
#: src/app/common/files/tokens.cljc:298
|
||||||
|
msgid "errors.token-theme-not-existing-sets"
|
||||||
|
msgstr "The theme refers to some not existing sets: %s"
|
||||||
|
|
||||||
#: src/app/main/data/media.cljs:73
|
#: src/app/main/data/media.cljs:73
|
||||||
msgid "errors.unexpected-error"
|
msgid "errors.unexpected-error"
|
||||||
msgstr "An unexpected error occurred."
|
msgstr "An unexpected error occurred."
|
||||||
@ -8457,6 +8461,10 @@ msgstr "Import Error: Invalid token data in JSON."
|
|||||||
msgid "errors.tokens.invalid-json-token-name"
|
msgid "errors.tokens.invalid-json-token-name"
|
||||||
msgstr "Import Error: Invalid token name in JSON."
|
msgstr "Import Error: Invalid token name in JSON."
|
||||||
|
|
||||||
|
#: src/app/main/data/workspace/tokens/errors.cljs:32
|
||||||
|
msgid "workspace.tokens.duplicated-json-token-name"
|
||||||
|
msgstr "Import Error: Duplicated token name in JSON."
|
||||||
|
|
||||||
#: src/app/main/data/workspace/tokens/errors.cljs:28
|
#: src/app/main/data/workspace/tokens/errors.cljs:28
|
||||||
msgid "errors.tokens.invalid-json-token-name-detail"
|
msgid "errors.tokens.invalid-json-token-name-detail"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -8464,6 +8472,10 @@ msgstr ""
|
|||||||
"Token names should only contain letters and digits separated by . "
|
"Token names should only contain letters and digits separated by . "
|
||||||
"characters and must not start with a $ sign."
|
"characters and must not start with a $ sign."
|
||||||
|
|
||||||
|
#: src/app/main/data/workspace/tokens/errors.cljs:33
|
||||||
|
msgid "workspace.tokens.duplicated-json-token-name-detail"
|
||||||
|
msgstr "A token already exists at the path '%s' or at a prefix thereof, in another set."
|
||||||
|
|
||||||
#: src/app/main/data/workspace/tokens/errors.cljs:105
|
#: src/app/main/data/workspace/tokens/errors.cljs:105
|
||||||
msgid "errors.tokens.invalid-shadow-type-token-value"
|
msgid "errors.tokens.invalid-shadow-type-token-value"
|
||||||
msgstr "Invalid shadow type: only 'innerShadow' or 'dropShadow' are accepted"
|
msgstr "Invalid shadow type: only 'innerShadow' or 'dropShadow' are accepted"
|
||||||
|
|||||||
@ -1644,6 +1644,10 @@ msgstr ""
|
|||||||
msgid "errors.token-theme-already-exists"
|
msgid "errors.token-theme-already-exists"
|
||||||
msgstr "Ya existe un theme con este nombre"
|
msgstr "Ya existe un theme con este nombre"
|
||||||
|
|
||||||
|
#: src/app/common/files/tokens.cljc:298
|
||||||
|
msgid "errors.token-theme-not-existing-sets"
|
||||||
|
msgstr "El tema referencia sets que no existen: %s"
|
||||||
|
|
||||||
#: src/app/main/data/media.cljs:73
|
#: src/app/main/data/media.cljs:73
|
||||||
msgid "errors.unexpected-error"
|
msgid "errors.unexpected-error"
|
||||||
msgstr "Ha ocurrido un error inesperado."
|
msgstr "Ha ocurrido un error inesperado."
|
||||||
@ -8244,6 +8248,10 @@ msgstr "Error al importar: Datos de token no válidos en JSON."
|
|||||||
msgid "errors.tokens.invalid-json-token-name"
|
msgid "errors.tokens.invalid-json-token-name"
|
||||||
msgstr "Error al importar: Nombre de token no válido en JSON."
|
msgstr "Error al importar: Nombre de token no válido en JSON."
|
||||||
|
|
||||||
|
#: src/app/main/data/workspace/tokens/errors.cljs:32
|
||||||
|
msgid "workspace.tokens.duplicated-json-token-name"
|
||||||
|
msgstr "Error al importar: Nombre de token duplicado en JSON."
|
||||||
|
|
||||||
#: src/app/main/data/workspace/tokens/errors.cljs:28
|
#: src/app/main/data/workspace/tokens/errors.cljs:28
|
||||||
msgid "errors.tokens.invalid-json-token-name-detail"
|
msgid "errors.tokens.invalid-json-token-name-detail"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -8251,6 +8259,10 @@ msgstr ""
|
|||||||
"Los nombres de token solo pueden contener letras y dígitos separados por "
|
"Los nombres de token solo pueden contener letras y dígitos separados por "
|
||||||
"caracteres . y no pueden empezar con un signo $."
|
"caracteres . y no pueden empezar con un signo $."
|
||||||
|
|
||||||
|
#: src/app/main/data/workspace/tokens/errors.cljs:33
|
||||||
|
msgid "workspace.tokens.duplicated-json-token-name-detail"
|
||||||
|
msgstr "Existe un token en la ruta '%s' o en un prefijo del mismo, en otro set."
|
||||||
|
|
||||||
#: src/app/main/data/workspace/tokens/errors.cljs:105
|
#: src/app/main/data/workspace/tokens/errors.cljs:105
|
||||||
msgid "errors.tokens.invalid-shadow-type-token-value"
|
msgid "errors.tokens.invalid-shadow-type-token-value"
|
||||||
msgstr "Tipo de sombra no válida: solo se aceptan 'innerShadow' o 'dropShadow'"
|
msgstr "Tipo de sombra no válida: solo se aceptan 'innerShadow' o 'dropShadow'"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user