Merge pull request #5840 from penpot/niwinz-tokens-changes-3

♻️ Token changes (part 3)
This commit is contained in:
Eva Marco 2025-02-14 12:55:12 +01:00 committed by GitHub
commit 2ffb77cb4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 316 additions and 291 deletions

View file

@ -44,17 +44,13 @@
(defn group-item (defn group-item
"Add a group to the item name, in the form group.name." "Add a group to the item name, in the form group.name."
[item group-name separator] [item group-name separator]
(dm/assert! (assert (valid-groupable-item? item) "expected groupable item")
"expected groupable item" (update item :name #(dm/str group-name separator %)))
(valid-groupable-item? item))
(update item :name #(str group-name separator %)))
(defn ungroup-item (defn ungroup-item
"Remove the first group from the item name." "Remove the first group from the item name."
[item separator] [item separator]
(dm/assert! (assert (valid-groupable-item? item) "expected groupable item")
"expected groupable item"
(valid-groupable-item? item))
(update item :name #(-> % (update item :name #(-> %
(split-path separator) (split-path separator)
(rest) (rest)
@ -63,9 +59,7 @@
(defn get-path (defn get-path
"Get the groups part of the name as a vector. E.g. group.subgroup.name -> ['group' 'subgroup']" "Get the groups part of the name as a vector. E.g. group.subgroup.name -> ['group' 'subgroup']"
[item separator] [item separator]
(dm/assert! (assert (valid-groupable-item? item) "expected groupable item")
"expected groupable item"
(valid-groupable-item? item))
(split-path (:name item) separator)) (split-path (:name item) separator))
(defn get-groups-str (defn get-groups-str
@ -78,9 +72,7 @@
(defn get-final-name (defn get-final-name
"Get the final part of the name. E.g. group.subgroup.name -> name" "Get the final part of the name. E.g. group.subgroup.name -> name"
[item separator] [item separator]
(dm/assert! (assert (valid-groupable-item? item) "expected groupable item")
"expected groupable item"
(valid-groupable-item? item))
(-> (:name item) (-> (:name item)
(split-path separator) (split-path separator)
(last))) (last)))
@ -94,9 +86,7 @@
"Get all children of a group of a grouping tree. Each child is "Get all children of a group of a grouping tree. Each child is
a tuple [name item], where item " a tuple [name item], where item "
[group] [group]
(dm/assert! (assert (group? group) "expected group node")
"expected group node"
(group? group))
(seq group)) (seq group))
;; === Token ;; === Token
@ -114,36 +104,37 @@
(defrecord Token [name type value description modified-at]) (defrecord Token [name type value description modified-at])
(defn token?
[o]
(instance? Token o))
(def schema:token-attrs
[:map {:title "Token"}
[:name cto/token-name-ref]
[:type [::sm/one-of cto/token-types]]
[:value :any]
[:description [:maybe :string]]
[:modified-at ::sm/inst]])
(def schema:token (def schema:token
[:and [:and
[:map {:title "Token"} schema:token-attrs
[:name cto/token-name-ref] [:fn token?]])
[:type [::sm/one-of cto/token-types]]
[:value :any]
[:description [:maybe :string]]
[:modified-at ::sm/inst]]
[:fn (partial instance? Token)]])
(sm/register! ::token schema:token) (def check-token
(sm/check-fn schema:token :hint "expected valid token"))
(def valid-token? (def ^:private check-token-attrs
(sm/validator schema:token)) (sm/check-fn schema:token-attrs :hint "expected valid params for token"))
(def check-token!
(sm/check-fn ::token))
(defn make-token (defn make-token
[& {:keys [] :as params}] [& {:as attrs}]
(let [params (-> params (-> attrs
(dissoc :id) ;; we will remove this when old data structures are removed (dissoc :id) ;; we will remove this when old data structures are removed
(update :modified-at #(or % (dt/now)))) (update :modified-at #(or % (dt/now)))
token (map->Token params)] (update :description d/nilv "")
(check-token-attrs)
(dm/assert! (map->Token)))
"expected valid token"
(check-token! token))
token))
(defn find-token-value-references (defn find-token-value-references
"Returns set of token references found in `token-value`. "Returns set of token references found in `token-value`.
@ -336,17 +327,16 @@
tokens)) tokens))
(add-token [_ token] (add-token [_ token]
(dm/assert! "expected valid token" (check-token! token)) (let [token (check-token token)]
(TokenSet. name (TokenSet. name
description description
(dt/now) (dt/now)
(assoc tokens (:name token) token))) (assoc tokens (:name token) token))))
(update-token [this token-name f] (update-token [this token-name f]
(if-let [token (get tokens token-name)] (if-let [token (get tokens token-name)]
(let [token' (-> (make-token (f token)) (let [token' (-> (make-token (f token))
(assoc :modified-at (dt/now)))] (assoc :modified-at (dt/now)))]
(check-token! token')
(TokenSet. name (TokenSet. name
description description
(dt/now) (dt/now)
@ -382,36 +372,44 @@
"$type" (cto/token-type->dtcg-token-type (:type token))} "$type" (cto/token-type->dtcg-token-type (:type token))}
(:description token) (assoc "$description" (:description token))))))) (:description token) (assoc "$description" (:description token)))))))
(defn token-set?
[o]
(instance? TokenSet o))
(def schema:token-set-attrs
[:map {:title "TokenSet"}
[:name :string]
[:description [:maybe :string]]
[:modified-at ::sm/inst]
[:tokens [:and
[:map-of {:gen/max 5} :string schema:token]
[:fn d/ordered-map?]]]])
(def schema:token-set (def schema:token-set
[:and [:map {:title "TokenSet"} [:and
[:name :string] schema:token-set-attrs
[:description [:maybe :string]] [:fn token-set?]])
[:modified-at ::sm/inst]
[:tokens [:and [:map-of {:gen/max 5} :string ::token]
[:fn d/ordered-map?]]]]
[:fn (partial instance? TokenSet)]])
(sm/register! ::token-set schema:token-set) (sm/register! ::token-set schema:token-set)
(def valid-token-set? (def valid-token-set?
(sm/validator schema:token-set)) (sm/validator schema:token-set))
(def check-token-set! (def check-token-set
(sm/check-fn ::token-set)) (sm/check-fn schema:token-set :hint "expected valid token set"))
(def ^:private check-token-set-attrs
(sm/check-fn schema:token-set-attrs :hint "expected valid params for token-set"))
(defn make-token-set (defn make-token-set
[& {:keys [] :as params}] [& {:as attrs}]
(let [params (-> params (-> attrs
(dissoc :id) (dissoc :id)
(update :modified-at #(or % (dt/now))) (update :modified-at #(or % (dt/now)))
(update :tokens #(into (d/ordered-map) %))) (update :tokens #(into (d/ordered-map) %))
token-set (map->TokenSet params)] (update :description d/nilv "")
(check-token-set-attrs)
(dm/assert! (map->TokenSet)))
"expected valid token set"
(check-token-set! token-set))
token-set))
;; === TokenSets (collection) ;; === TokenSets (collection)
@ -455,22 +453,16 @@
[:fn d/ordered-map?]]]}} [:fn d/ordered-map?]]]}}
[:ref ::node]]) [:ref ::node]])
(sm/register! ::token-set-node schema:token-set-node)
(def schema:token-sets (def schema:token-sets
[:and [:and
[:map-of {:title "TokenSets"} [:map-of {:title "TokenSets"}
:string ::token-set-node] :string
schema:token-set-node]
[:fn d/ordered-map?]]) [:fn d/ordered-map?]])
(sm/register! ::token-sets schema:token-sets)
(def valid-token-sets? (def valid-token-sets?
(sm/validator schema:token-sets)) (sm/validator schema:token-sets))
(def check-token-sets!
(sm/check-fn ::token-sets))
;; === TokenTheme ;; === TokenTheme
(def theme-separator "/") (def theme-separator "/")
@ -549,23 +541,34 @@
(hidden-temporary-theme? [this] (hidden-temporary-theme? [this]
(theme-matches-group-name this hidden-token-theme-group hidden-token-theme-name))) (theme-matches-group-name this hidden-token-theme-group hidden-token-theme-name)))
(defn token-theme?
[o]
(instance? TokenTheme o))
(def schema:token-theme-attrs
[:map {:title "TokenTheme"}
[:name :string]
[:group :string]
[:description [:maybe :string]]
[:is-source [:maybe :boolean]]
[:modified-at ::sm/inst]
[:sets [:set {:gen/max 5} :string]]])
(def schema:token-theme (def schema:token-theme
[:and [:map {:title "TokenTheme"} [:and
[:name :string] schema:token-theme-attrs
[:group :string] [:fn token-theme?]])
[:description [:maybe :string]]
[:is-source [:maybe :boolean]]
[:modified-at ::sm/inst]
[:sets [:set {:gen/max 5} :string]]]
[:fn (partial instance? TokenTheme)]])
(sm/register! ::token-theme schema:token-theme) (sm/register! ::token-theme schema:token-theme)
(def valid-token-theme? (def valid-token-theme?
(sm/validator schema:token-theme)) (sm/validator schema:token-theme))
(def check-token-theme! (def check-token-theme
(sm/check-fn ::token-theme)) (sm/check-fn schema:token-theme :hint "expected a valid token-theme"))
(def ^:private check-token-theme-attrs
(sm/check-fn schema:token-theme-attrs :hint "expected valid params for token-theme"))
(def top-level-theme-group-name (def top-level-theme-group-name
"Top level theme groups have an empty string as the theme group." "Top level theme groups have an empty string as the theme group."
@ -575,26 +578,23 @@
(= group top-level-theme-group-name)) (= group top-level-theme-group-name))
(defn make-token-theme (defn make-token-theme
[& {:keys [] :as params}] [& {:as attrs}]
(let [params (-> params (-> attrs
(dissoc :id) (dissoc :id)
(update :group #(or % top-level-theme-group-name)) (update :group d/nilv top-level-theme-group-name)
(update :is-source #(or % false)) (update :is-source d/nilv false)
(update :modified-at #(or % (dt/now))) (update :modified-at #(or % (dt/now)))
(update :sets #(into #{} %))) (update :sets set)
token-theme (map->TokenTheme params)] (update :description d/nilv "")
(check-token-theme-attrs)
(dm/assert! (map->TokenTheme)))
"expected valid token theme"
(check-token-theme! token-theme))
token-theme))
(defn make-hidden-token-theme (defn make-hidden-token-theme
[& {:keys [] :as params}] [& {:as attrs}]
(make-token-theme (assoc params (-> attrs
:group hidden-token-theme-group (assoc :group hidden-token-theme-group)
:name hidden-token-theme-name))) (assoc :name hidden-token-theme-name)
(make-token-theme)))
;; === TokenThemes (collection) ;; === TokenThemes (collection)
@ -606,8 +606,7 @@
(get-theme-tree [_] "get a nested tree of all themes in the library") (get-theme-tree [_] "get a nested tree of all themes in the library")
(get-themes [_] "get an ordered sequence of all themes in the library") (get-themes [_] "get an ordered sequence of all themes in the library")
(get-theme [_ group name] "get one theme looking for name") (get-theme [_ group name] "get one theme looking for name")
(get-hidden-theme [_] "get the theme hidden from the user , (get-hidden-theme [_] "get the theme hidden from the user, used for managing active sets without a user created theme.")
used for managing active sets without a user created theme.")
(get-theme-groups [_] "get a sequence of group names by order") (get-theme-groups [_] "get a sequence of group names by order")
(get-active-theme-paths [_] "get the active theme paths") (get-active-theme-paths [_] "get the active theme paths")
(get-active-themes [_] "get an ordered sequence of active themes in the library") (get-active-themes [_] "get an ordered sequence of active themes in the library")
@ -620,23 +619,18 @@ used for managing active sets without a user created theme.")
(def schema:token-themes (def schema:token-themes
[:and [:and
[:map-of {:title "TokenThemes"} [:map-of {:title "TokenThemes"}
:string [:and [:map-of :string ::token-theme] :string [:and [:map-of :string schema:token-theme]
[:fn d/ordered-map?]]] [:fn d/ordered-map?]]]
[:fn d/ordered-map?]]) [:fn d/ordered-map?]])
(sm/register! ::token-themes schema:token-themes)
(def valid-token-themes? (def valid-token-themes?
(sm/validator schema:token-themes)) (sm/validator schema:token-themes))
(def check-token-themes! (def ^:private schema:active-themes
(sm/check-fn ::token-themes)) [:set :string])
(def schema:active-token-themes
[:set string?])
(def valid-active-token-themes? (def valid-active-token-themes?
(sm/validator schema:active-token-themes)) (sm/validator schema:active-themes))
;; === Import / Export from DTCG format ;; === Import / Export from DTCG format
@ -869,17 +863,14 @@ Will return a value that matches this schema:
ITokenSets ITokenSets
(add-set [_ token-set] (add-set [_ token-set]
(dm/assert! "expected valid token set" (check-token-set! token-set)) (let [path (get-token-set-prefixed-path token-set)
(let [path (get-token-set-prefixed-path token-set)] token-set (check-token-set token-set)]
(TokensLib. (d/oassoc-in sets path token-set) (TokensLib. (d/oassoc-in sets path token-set)
themes themes
active-themes))) active-themes)))
(add-sets [this token-sets] (add-sets [this token-sets]
(reduce (reduce add-set this token-sets))
(fn [lib set]
(add-set lib set))
this token-sets))
(update-set [this set-name f] (update-set [this set-name f]
(let [prefixed-full-path (set-name->prefixed-full-path set-name) (let [prefixed-full-path (set-name->prefixed-full-path set-name)
@ -889,7 +880,6 @@ Will return a value that matches this schema:
(assoc :modified-at (dt/now))) (assoc :modified-at (dt/now)))
prefixed-full-path' (get-token-set-prefixed-path set') prefixed-full-path' (get-token-set-prefixed-path set')
name-changed? (not= (:name set) (:name set'))] name-changed? (not= (:name set) (:name set'))]
(check-token-set! set')
(if name-changed? (if name-changed?
(TokensLib. (-> sets (TokensLib. (-> sets
(d/oassoc-in-before prefixed-full-path prefixed-full-path' set') (d/oassoc-in-before prefixed-full-path prefixed-full-path' set')
@ -1061,10 +1051,10 @@ Will return a value that matches this schema:
ITokenThemes ITokenThemes
(add-theme [_ token-theme] (add-theme [_ token-theme]
(dm/assert! "expected valid token theme" (check-token-theme! token-theme)) (let [token-theme (check-token-theme token-theme)]
(TokensLib. sets (TokensLib. sets
(update themes (:group token-theme) d/oassoc (:name token-theme) token-theme) (update themes (:group token-theme) d/oassoc (:name token-theme) token-theme)
active-themes)) active-themes)))
(update-theme [this group name f] (update-theme [this group name f]
(let [theme (dm/get-in themes [group name])] (let [theme (dm/get-in themes [group name])]
@ -1076,7 +1066,6 @@ Will return a value that matches this schema:
same-group? (= group group') same-group? (= group group')
same-name? (= name name') same-name? (= name name')
same-path? (and same-group? same-name?)] same-path? (and same-group? same-name?)]
(check-token-theme! theme')
(TokensLib. sets (TokensLib. sets
(if same-path? (if same-path?
(update themes group' assoc name' theme') (update themes group' assoc name' theme')
@ -1164,7 +1153,6 @@ Will return a value that matches this schema:
(some? (get-in sets (set-group-path->set-group-prefixed-path set-path)))) (some? (get-in sets (set-group-path->set-group-prefixed-path set-path))))
(add-token-in-set [this set-name token] (add-token-in-set [this set-name token]
(dm/assert! "expected valid token instance" (check-token! token))
(update-set this set-name #(add-token % token))) (update-set this set-name #(add-token % token)))
(get-token-in-set [this set-name token-name] (get-token-in-set [this set-name token-name]
@ -1304,6 +1292,7 @@ Will return a value that matches this schema:
(into tokens' (map (fn [x] [(:name x) x]) (get-tokens set)))) (into tokens' (map (fn [x] [(:name x) x]) (get-tokens set))))
{} (get-sets this))) {} (get-sets this)))
;; FIXME: revisit if this still necessary
(validate [_] (validate [_]
(and (valid-token-sets? sets) (and (valid-token-sets? sets)
(valid-token-themes? themes) (valid-token-themes? themes)
@ -1314,11 +1303,14 @@ Will return a value that matches this schema:
(and (instance? TokensLib o) (and (instance? TokensLib o)
(validate o))) (validate o)))
(defn check-tokens-lib! (def ^:private check-token-sets
[lib] (sm/check-fn schema:token-sets :hint "expected valid token sets"))
(dm/assert!
"expected valid tokens lib" (def ^:private check-token-themes
(valid-tokens-lib? lib))) (sm/check-fn schema:token-themes :hint "expected valid token themes"))
(def ^:private check-active-themes
(sm/check-fn schema:active-themes :hint "expected valid active themes"))
(defn make-tokens-lib (defn make-tokens-lib
"Create an empty or prepopulated tokens library." "Create an empty or prepopulated tokens library."
@ -1333,13 +1325,11 @@ Will return a value that matches this schema:
:active-themes #{})) :active-themes #{}))
([& {:keys [sets themes active-themes]}] ([& {:keys [sets themes active-themes]}]
(let [tokens-lib (TokensLib. sets themes (or active-themes #{}))] (let [active-themes (d/nilv active-themes #{})]
(TokensLib.
(dm/assert! (check-token-sets sets)
"expected valid tokens lib" (check-token-themes themes)
(valid-tokens-lib? tokens-lib)) (check-active-themes active-themes)))))
tokens-lib)))
(defn ensure-tokens-lib (defn ensure-tokens-lib
[tokens-lib] [tokens-lib]
@ -1353,10 +1343,11 @@ Will return a value that matches this schema:
(def type:tokens-lib (def type:tokens-lib
{:type ::tokens-lib {:type ::tokens-lib
:pred valid-tokens-lib? :pred valid-tokens-lib?
:type-properties {:encode/json encode-dtcg :type-properties
:decode/json decode-dtcg}}) {:encode/json encode-dtcg
:decode/json decode-dtcg}})
(sm/register! ::tokens-lib type:tokens-lib) (sm/register! type:tokens-lib)
;; === Serialization handlers for RPC API (transit) and database (fressian) ;; === Serialization handlers for RPC API (transit) and database (fressian)
@ -1369,17 +1360,17 @@ Will return a value that matches this schema:
{:id "penpot/token-set" {:id "penpot/token-set"
:class TokenSet :class TokenSet
:wfn #(into {} %) :wfn #(into {} %)
:rfn #(make-token-set %)} :rfn #(map->TokenSet %)}
{:id "penpot/token-theme" {:id "penpot/token-theme"
:class TokenTheme :class TokenTheme
:wfn #(into {} %) :wfn #(into {} %)
:rfn #(make-token-theme %)} :rfn #(map->TokenTheme %)}
{:id "penpot/token" {:id "penpot/token"
:class Token :class Token
:wfn #(into {} %) :wfn #(into {} %)
:rfn #(make-token %)}) :rfn #(map->Token %)})
#?(:clj #?(:clj
(fres/add-handlers! (fres/add-handlers!

View file

@ -37,23 +37,21 @@
(t/is (= (:name token1) "test-token-1")) (t/is (= (:name token1) "test-token-1"))
(t/is (= (:type token1) :boolean)) (t/is (= (:type token1) :boolean))
(t/is (= (:value token1) true)) (t/is (= (:value token1) true))
(t/is (nil? (:description token1))) (t/is (= (:description token1) ""))
(t/is (some? (:modified-at token1))) (t/is (some? (:modified-at token1)))
(t/is (ctob/valid-token? token1)) (t/is (ctob/check-token token1))
(t/is (= (:name token2) "test-token-2")) (t/is (= (:name token2) "test-token-2"))
(t/is (= (:type token2) :numeric)) (t/is (= (:type token2) :numeric))
(t/is (= (:value token2) 66)) (t/is (= (:value token2) 66))
(t/is (= (:description token2) "test description")) (t/is (= (:description token2) "test description"))
(t/is (= (:modified-at token2) now)) (t/is (= (:modified-at token2) now))
(t/is (ctob/valid-token? token2)))) (t/is (ctob/check-token token2))))
(t/testing "invalid-tokens" (t/testing "invalid-tokens"
(let [args {:name 777 (let [params {:name 777 :type :invalid}]
:type :invalid}] (t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid params for token"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token" (ctob/make-token params)))))
(apply ctob/make-token args)))
(t/is (false? (ctob/valid-token? {})))))
(t/testing "find-token-value-references" (t/testing "find-token-value-references"
(t/testing "finds references inside curly braces in a string" (t/testing "finds references inside curly braces in a string"
@ -75,7 +73,7 @@
:tokens [])] :tokens [])]
(t/is (= (:name token-set1) "test-token-set-1")) (t/is (= (:name token-set1) "test-token-set-1"))
(t/is (nil? (:description token-set1))) (t/is (= (:description token-set1) ""))
(t/is (some? (:modified-at token-set1))) (t/is (some? (:modified-at token-set1)))
(t/is (empty? (:tokens token-set1))) (t/is (empty? (:tokens token-set1)))
@ -85,10 +83,9 @@
(t/is (empty? (:tokens token-set2))))) (t/is (empty? (:tokens token-set2)))))
(t/testing "invalid-token-set" (t/testing "invalid-token-set"
(let [args {:name 777 (let [params {:name 777 :description 999}]
:description 999}] (t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid params for token-set"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token set" (ctob/make-token-set params)))))
(apply ctob/make-token-set args)))))
(t/testing "move-token-set" (t/testing "move-token-set"
(t/testing "flat" (t/testing "flat"
@ -192,7 +189,7 @@
(t/is (= (:name token-theme1) "test-token-theme-1")) (t/is (= (:name token-theme1) "test-token-theme-1"))
(t/is (= (:group token-theme1) "")) (t/is (= (:group token-theme1) ""))
(t/is (nil? (:description token-theme1))) (t/is (= (:description token-theme1) ""))
(t/is (false? (:is-source token-theme1))) (t/is (false? (:is-source token-theme1)))
(t/is (some? (:modified-at token-theme1))) (t/is (some? (:modified-at token-theme1)))
(t/is (empty? (:sets token-theme1))) (t/is (empty? (:sets token-theme1)))
@ -205,12 +202,12 @@
(t/is (empty? (:sets token-theme2))))) (t/is (empty? (:sets token-theme2)))))
(t/testing "invalid-token-theme" (t/testing "invalid-token-theme"
(let [args {:name 777 (let [params {:name 777
:group nil :group nil
:description 999 :description 999
:is-source 42}] :is-source 42}]
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token theme" (t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid params for token-theme"
(apply ctob/make-token-theme args)))))) (ctob/make-token-theme params))))))
(t/deftest tokens-lib (t/deftest tokens-lib
@ -219,10 +216,9 @@
(t/is (= (ctob/set-count tokens-lib) 0)))) (t/is (= (ctob/set-count tokens-lib) 0))))
(t/testing "invalid-tokens-lib" (t/testing "invalid-tokens-lib"
(let [args {:sets nil (let [params {:sets nil :themes nil}]
:themes nil}] (t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid token sets"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception) #"expected valid tokens lib" (ctob/make-tokens-lib params))))))
(apply ctob/make-tokens-lib args))))))
(t/testing "token-set in a lib" (t/testing "token-set in a lib"
@ -413,7 +409,7 @@
(t/is (= (count (:tokens token-set')) 2)) (t/is (= (count (:tokens token-set')) 2))
(t/is (= (d/index-of (keys (:tokens token-set')) "updated-name") 0)) (t/is (= (d/index-of (keys (:tokens token-set')) "updated-name") 0))
(t/is (= (:name token') "updated-name")) (t/is (= (:name token') "updated-name"))
(t/is (= (:description token') nil)) (t/is (= (:description token') ""))
(t/is (= (:value token') true)) (t/is (= (:value token') true))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token))))) (t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
@ -757,7 +753,7 @@
(t/is (= (ctob/set-count tokens-lib') 1)) (t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token') "group1.updated-name")) (t/is (= (:name token') "group1.updated-name"))
(t/is (= (:description token') nil)) (t/is (= (:description token') ""))
(t/is (= (:value token') true)) (t/is (= (:value token') true))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token))))) (t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
@ -792,7 +788,7 @@
(t/is (= (ctob/set-count tokens-lib') 1)) (t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (d/index-of (keys (:tokens token-set')) "group2.updated-name") 1)) (t/is (= (d/index-of (keys (:tokens token-set')) "group2.updated-name") 1))
(t/is (= (:name token') "group2.updated-name")) (t/is (= (:name token') "group2.updated-name"))
(t/is (= (:description token') nil)) (t/is (= (:description token') ""))
(t/is (= (:value token') true)) (t/is (= (:value token') true))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token))))) (t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
@ -930,7 +926,7 @@
(t/is (= (count group1') 3)) (t/is (= (count group1') 3))
(t/is (= (d/index-of (keys group1') "S-updated-name") 0)) (t/is (= (d/index-of (keys group1') "S-updated-name") 0))
(t/is (= (:name token-set') "group1/updated-name")) (t/is (= (:name token-set') "group1/updated-name"))
(t/is (= (:description token-set') nil)) (t/is (= (:description token-set') ""))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
sets-tree')) sets-tree'))
@ -961,7 +957,7 @@
(t/is (= (count group2') 1)) (t/is (= (count group2') 1))
(t/is (nil? (get group1' "S-updated-name"))) (t/is (nil? (get group1' "S-updated-name")))
(t/is (= (:name token-set') "group2/updated-name")) (t/is (= (:name token-set') "group2/updated-name"))
(t/is (= (:description token-set') nil)) (t/is (= (:description token-set') ""))
(t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
(t/testing "delete-set-in-group" (t/testing "delete-set-in-group"
@ -1097,7 +1093,7 @@
(t/is (= (d/index-of (keys group1') "updated-name") 0)) (t/is (= (d/index-of (keys group1') "updated-name") 0))
(t/is (= (:name token-theme') "updated-name")) (t/is (= (:name token-theme') "updated-name"))
(t/is (= (:group token-theme') "group1")) (t/is (= (:group token-theme') "group1"))
(t/is (= (:description token-theme') nil)) (t/is (= (:description token-theme') ""))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))) (t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/testing "move-theme-of-group" (t/testing "move-theme-of-group"
@ -1127,7 +1123,7 @@
(t/is (= (d/index-of (keys group2') "updated-name") 0)) (t/is (= (d/index-of (keys group2') "updated-name") 0))
(t/is (= (:name token-theme') "updated-name")) (t/is (= (:name token-theme') "updated-name"))
(t/is (= (:group token-theme') "group2")) (t/is (= (:group token-theme') "group2"))
(t/is (= (:description token-theme') nil)) (t/is (= (:description token-theme') ""))
(t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))) (t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme)))))
(t/testing "delete-theme-in-group" (t/testing "delete-theme-in-group"
@ -1166,7 +1162,7 @@
{:name "colors.red.600" {:name "colors.red.600"
:type :color :type :color
:value "#e53e3e" :value "#e53e3e"
:description nil})) :description ""}))
(t/is (= (get-set-token "core" "spacing.multi-value") (t/is (= (get-set-token "core" "spacing.multi-value")
{:name "spacing.multi-value" {:name "spacing.multi-value"
:type :spacing :type :spacing
@ -1176,7 +1172,7 @@
{:name "button.primary.background" {:name "button.primary.background"
:type :color :type :color
:value "{accent.default}" :value "{accent.default}"
:description nil}))) :description ""})))
(t/testing "invalid tokens got discarded" (t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold")))))))) (t/is (nil? (get-set-token "typography" "H1.Bold"))))))))
@ -1200,7 +1196,7 @@
{:name "colors.red.600" {:name "colors.red.600"
:type :color :type :color
:value "#e53e3e" :value "#e53e3e"
:description nil})) :description ""}))
(t/is (tht/token-data-eq? (get-set-token "core" "spacing.multi-value") (t/is (tht/token-data-eq? (get-set-token "core" "spacing.multi-value")
{:name "spacing.multi-value" {:name "spacing.multi-value"
:type :spacing :type :spacing
@ -1210,7 +1206,7 @@
{:name "button.primary.background" {:name "button.primary.background"
:type :color :type :color
:value "{accent.default}" :value "{accent.default}"
:description nil}))) :description ""})))
(t/testing "invalid tokens got discarded" (t/testing "invalid tokens got discarded"
(t/is (nil? (get-set-token "typography" "H1.Bold")))))) (t/is (nil? (get-set-token "typography" "H1.Bold"))))))
@ -1238,26 +1234,29 @@
:group "group-1" :group "group-1"
:modified-at now :modified-at now
:sets #{"core"}))) :sets #{"core"})))
expected (ctob/encode-dtcg tokens-lib)] result (ctob/encode-dtcg tokens-lib)
(t/is (= {"$themes" [{"description" nil expected {"$themes" [{"description" ""
"group" "group-1" "group" "group-1"
"is-source" false "is-source" false
"modified-at" now "modified-at" now
"name" "theme-1" "name" "theme-1"
"selectedTokenSets" {"core" "enabled"}}] "selectedTokenSets" {"core" "enabled"}}]
"$metadata" {"tokenSetOrder" ["core"]} "$metadata" {"tokenSetOrder" ["core"]}
"core" "core"
{"colors" {"red" {"600" {"$value" "#e53e3e" {"colors" {"red" {"600" {"$value" "#e53e3e"
"$type" "color"}}} "$type" "color"
"spacing" "$description" ""}}}
{"multi-value" "spacing"
{"$value" "{dimension.sm} {dimension.xl}" {"multi-value"
"$type" "spacing" {"$value" "{dimension.sm} {dimension.xl}"
"$description" "You can have multiple values in a single spacing token"}} "$type" "spacing"
"button" "$description" "You can have multiple values in a single spacing token"}}
{"primary" {"background" {"$value" "{accent.default}" "button"
"$type" "color"}}}}} {"primary" {"background" {"$value" "{accent.default}"
expected)))) "$type" "color"
"$description" ""}}}}}]
(t/is (= expected result))))
(t/testing "encode-decode-dtcg-json" (t/testing "encode-decode-dtcg-json"
(with-redefs [dt/now (constantly #inst "2024-10-16T12:01:20.257840055-00:00")] (with-redefs [dt/now (constantly #inst "2024-10-16T12:01:20.257840055-00:00")]

View file

@ -18,7 +18,6 @@
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.tokens.selected-set :as dwts]
[app.main.ui.workspace.tokens.update :as wtu] [app.main.ui.workspace.tokens.update :as wtu]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@ -38,6 +37,14 @@
(-> (dsh/lookup-file-data state) (-> (dsh/lookup-file-data state)
(get :tokens-lib))) (get :tokens-lib)))
(defn lookup-token-set
([state]
(when-let [selected (dm/get-in state [:workspace-tokens :selected-token-set-name])]
(lookup-token-set state selected)))
([state name]
(some-> (get-tokens-lib state)
(ctob/get-set name))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helpers ;; Helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -233,49 +240,72 @@
(rx/of (rx/of
(drop-error (ex-data e)))))))) (drop-error (ex-data e))))))))
;; FIXME: the the name is very confusing (defn- create-token-with-set
(defn update-create-token "A special case when a first token is created and no set exists"
[{:keys [token prev-token-name]}] [token]
(ptk/reify ::update-create-token (ptk/reify ::create-token-and-set
ptk/WatchEvent
(watch [_ _ _]
(let [set-name "Global"
token-set
(-> (ctob/make-token-set :name set-name)
(ctob/add-token token))
hidden-theme
(ctob/make-hidden-token-theme :sets [set-name])
changes
(pcb/add-token-set (pcb/empty-changes) token-set)
changes
(-> changes
(pcb/add-token-theme hidden-theme)
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{}))]
(rx/of (set-selected-token-set-name set-name)
(dch/commit-changes changes))))))
(defn create-token
[params]
(let [token (ctob/make-token params)]
(ptk/reify ::create-token
ptk/WatchEvent
(watch [it state _]
(if-let [token-set (lookup-token-set state)]
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token (:name token-set)
(:name token)
token))]
(rx/of (dch/commit-changes changes)
(ptk/data-event ::ev/event {::ev/name "create-token"})))
(rx/of (create-token-with-set token)))))))
(defn update-token
[name params]
(assert (string? name) "expected string for `name`")
(ptk/reify ::update-token
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it state _]
(let [data (dsh/lookup-file-data state) (let [token-set (lookup-token-set state)
selected (dm/get-in state [:workspace-tokens :selected-token-set-name]) data (dsh/lookup-file-data state)
token (ctob/get-token token-set name)
token' (->> (merge token params)
(into {})
(ctob/make-token))
tokens-lib (get-tokens-lib state) changes (-> (pcb/empty-changes it)
token-set (if selected (pcb/with-library-data data)
(some-> tokens-lib (ctob/get-set selected)) (pcb/set-token (:name token-set)
(some-> tokens-lib (ctob/get-sets) (first))) (:name token)
token'))]
set-name (or (:name token-set) "Global") (rx/of (dch/commit-changes changes))))))
changes (if (not token-set)
;; No set created add a global set
(let [token-set (ctob/make-token-set :name set-name :tokens {(:name token) token})
hidden-theme (ctob/make-hidden-token-theme :sets [set-name])
active-theme-paths (some-> tokens-lib ctob/get-active-theme-paths)
add-to-hidden-theme? (= active-theme-paths #{ctob/hidden-token-theme-path})
base-changes (pcb/add-token-set (pcb/empty-changes) token-set)]
(cond
(not tokens-lib)
(-> base-changes
(pcb/add-token-theme hidden-theme)
(pcb/update-active-token-themes #{ctob/hidden-token-theme-path} #{}))
add-to-hidden-theme?
(let [prev-hidden-theme (ctob/get-theme tokens-lib ctob/hidden-token-theme-group ctob/hidden-token-theme-name)]
(-> base-changes
(pcb/update-token-theme (ctob/toggle-set prev-hidden-theme ctob/hidden-token-theme-path) prev-hidden-theme)))
:else base-changes))
(-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/set-token set-name (or prev-token-name (:name token)) token)))]
(rx/of
(set-selected-token-set-name set-name)
(when-not prev-token-name
(ptk/event ::ev/event {::ev/name "create-tokens"}))
(dch/commit-changes changes))))))
(defn delete-token (defn delete-token
[set-name token-name] [set-name token-name]
@ -296,21 +326,23 @@
(ptk/reify ::duplicate-token (ptk/reify ::duplicate-token
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [token-set (dwts/get-selected-token-set state) (when-let [token-set (lookup-token-set state)]
token (some-> token-set (ctob/get-token token-name)) (when-let [token (ctob/get-token token-set token-name)]
tokens (some-> token-set (ctob/get-tokens)) (let [tokens (ctob/get-tokens token-set)
suffix-fn (fn [copy-count] unames (map :name tokens)
(let [suffix (tr "workspace.token.duplicate-suffix")]
(str/concat "-" suffix-fn
suffix (fn [copy-count]
(when (> copy-count 1) (let [suffix (tr "workspace.token.duplicate-suffix")]
(str "-" copy-count))))) (str/concat "-"
unames (map :name tokens) suffix
copy-name (cfh/generate-unique-name token-name unames :suffix-fn suffix-fn)] (when (> copy-count 1)
(when token (str "-" copy-count)))))
(rx/of
(update-create-token copy-name
{:token (assoc token :name copy-name)}))))))) (cfh/generate-unique-name token-name unames :suffix-fn suffix-fn)]
(rx/of (create-token (assoc token :name copy-name)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKEN UI OPS ;; TOKEN UI OPS

View file

@ -397,13 +397,20 @@
;; The result should be a vector of all resolved validations ;; The result should be a vector of all resolved validations
;; We do not handle the error case as it will be handled by the components validations ;; We do not handle the error case as it will be handled by the components validations
(when (and (seq result) (not err)) (when (and (seq result) (not err))
(st/emit! (dt/update-create-token {:token (ctob/make-token :name final-name (st/emit!
:type (or (:type token) token-type) (if (ctob/token? token)
:value final-value (dt/update-token (:name token)
:description final-description) {:name final-name
:prev-token-name (:name token)})) :value final-value
(st/emit! (wtu/update-workspace-tokens)) :description final-description})
(modal/hide!))))))))
(dt/create-token {:name final-name
:type token-type
:value final-value
:description final-description}))
(wtu/update-workspace-tokens)
(modal/hide)))))))))
on-delete-token on-delete-token
(mf/use-fn (mf/use-fn
(mf/deps selected-token-set-name) (mf/deps selected-token-set-name)

View file

@ -203,10 +203,11 @@
store (ths/setup-store file) store (ths/setup-store file)
;; ==== Action ;; ==== Action
events [(dt/update-create-token {:token (ctob/make-token :name "test-token-1" events [(dt/set-selected-token-set-name "test-token-set")
:type :border-radius (dt/update-token "test-token-1"
:value 66) {:name "test-token-1"
:prev-token-name "test-token-1"})] :type :border-radius
:value 66})]
step2 (fn [_] step2 (fn [_]
(let [events2 [(wtu/update-workspace-tokens) (let [events2 [(wtu/update-workspace-tokens)
@ -358,30 +359,25 @@
store (ths/setup-store file) store (ths/setup-store file)
;; ==== Action ;; ==== Action
events [(dt/update-create-token {:token (ctob/make-token :name "token-radius" events [(dt/set-selected-token-set-name "test-token-set")
:type :border-radius (dt/update-token "token-radius"
:value 30) {:name "token-radius"
:prev-token-name "token-radius"}) :value 30})
(dt/update-create-token {:token (ctob/make-token :name "token-rotation" (dt/update-token "token-rotation"
:type :rotation {:name "token-rotation"
:value 45) :value 45})
:prev-token-name "token-rotation"}) (dt/update-token "token-opacity"
(dt/update-create-token {:token (ctob/make-token :name "token-opacity" {:name "token-opacity"
:type :opacity :value 0.9})
:value 0.9) (dt/update-token "token-stroke-width"
:prev-token-name "token-opacity"}) {:name "token-stroke-width"
(dt/update-create-token {:token (ctob/make-token :name "token-stroke-width" :value 8})
:type :stroke-width (dt/update-token "token-color"
:value 8) {:name "token-color"
:prev-token-name "token-stroke-width"}) :value "#ff0000"})
(dt/update-create-token {:token (ctob/make-token :name "token-color" (dt/update-token "token-dimensions"
:type :color {:name "token-dimensions"
:value "#ff0000") :value 200})]
:prev-token-name "token-color"})
(dt/update-create-token {:token (ctob/make-token :name "token-dimensions"
:type :dimensions
:value 200)
:prev-token-name "token-dimensions"})]
step2 (fn [_] step2 (fn [_]
(let [events2 [(wtu/update-workspace-tokens) (let [events2 [(wtu/update-workspace-tokens)
@ -391,7 +387,7 @@
(fn [new-state] (fn [new-state]
(let [;; ==== Get (let [;; ==== Get
file' (ths/get-file-from-state new-state) file' (ths/get-file-from-state new-state)
frame1' (cths/get-shape file' :frame1) frame1' (cths/get-shape file' :frame1)
c-frame1' (cths/get-shape file' :c-frame1) c-frame1' (cths/get-shape file' :c-frame1)
tokens-frame1' (:applied-tokens c-frame1')] tokens-frame1' (:applied-tokens c-frame1')]