0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-21 22:22:43 -05:00

♻ Make process-sd-tokens more readable

This commit is contained in:
Florian Schroedl 2024-10-25 14:40:14 +02:00
parent f5596b2b3f
commit 0923dcc43f
2 changed files with 88 additions and 50 deletions

View file

@ -41,33 +41,73 @@
:warnings "silent" :warnings "silent"
:errors {:brokenReferences "console"}}}) :errors {:brokenReferences "console"}}})
(defn process-sd-tokens [sd-tokens get-origin-token] (defn parse-sd-token-color-value
(reduce "Parses `value` of a color `sd-token` into a map like `{:value 1 :unit \"px\"}`.
(fn [acc ^js sd-token] If the value is not parseable and/or has missing references returns a map with `:errors`."
(let [{:keys [type] :as origin-token} (get-origin-token sd-token) [value]
value (.-value sd-token) (if-let [tc (tinycolor/valid-color value)]
token-or-err (case type
:color (if-let [tc (tinycolor/valid-color value)]
{:value value :unit (tinycolor/color-format tc)} {:value value :unit (tinycolor/color-format tc)}
{:errors [(wte/error-with-value :error.token/invalid-color value)]}) {:errors [(wte/error-with-value :error.token/invalid-color value)]}))
(or (wtt/parse-token-value value)
(if-let [references (-> (ctob/find-token-value-references value) (defn parse-sd-token-dimensions-value
(seq))] "Parses `value` of a dimensions `sd-token` into a map like `{:value 1 :unit \"px\"}`.
If the `value` is not parseable and/or has missing references returns a map with `:errors`."
[value]
(or
(wtt/parse-token-value value)
(if-let [references (seq (ctob/find-token-value-references value))]
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)] {:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)]
:references references} :references references}
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]}))) {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]})))
output-token (if (:errors token-or-err)
(merge origin-token token-or-err) (defn process-sd-tokens
"Converts a StyleDictionary dictionary with resolved tokens (aka `sd-tokens`) back to clojure.
The `get-origin-token` argument should be a function that takes an `sd-token` and returns the original penpot token, so we can merge the resolved attributes back in.
The `sd-token` will have references in `value` replaced with the computed value as a string.
Here's an example for a `sd-token`:
```js
{
name: 'token.with.reference',
value: '12px',
type: 'border-radius',
path: ['token', 'with', 'reference'],
// The penpot origin token converted to a js object
original: {
name: 'token.with.reference',
value: '{referenced.token}',
type: 'border-radius'
},
}
```
We also convert `sd-token` value string into a unit that can be used as penpot shape attributes.
- Dimensions like '12px' will be converted into numbers
- Colors will be validated & converted to hex
Lastly we check for errors in each token
`sd-token` will keep the missing references in the `value` (E.g \"{missing} + {existing}\" -> \"{missing} + 12px\")
So we parse out the missing references and add them to `:errors` in the final token."
[sd-tokens get-origin-token]
(reduce
(fn [acc ^js sd-token]
(let [origin-token (get-origin-token sd-token)
value (.-value sd-token)
parsed-token-value (case (:type origin-token)
:color (parse-sd-token-color-value value)
(parse-sd-token-dimensions-value value))
output-token (if (:errors parsed-token-value)
(merge origin-token parsed-token-value)
(assoc origin-token (assoc origin-token
:resolved-value (:value token-or-err) :resolved-value (:value parsed-token-value)
:unit (:unit token-or-err)))] :unit (:unit parsed-token-value)))]
(assoc acc (wtt/token-identifier output-token) output-token))) (assoc acc (:name output-token) output-token)))
{} sd-tokens)) {} sd-tokens))
(defprotocol IStyleDictionary (defprotocol IStyleDictionary
(add-tokens [_ tokens]) (add-tokens [_ tokens])
(enable-debug [_]) (enable-debug [_])
(set-config [_])
(get-config [_]) (get-config [_])
(build-dictionary [_])) (build-dictionary [_]))
@ -79,9 +119,6 @@
(enable-debug [_] (enable-debug [_]
(StyleDictionary. (update config :log merge {:verbosity "verbose"}))) (StyleDictionary. (update config :log merge {:verbosity "verbose"})))
(set-config [_]
(StyleDictionary. config))
(get-config [_] (get-config [_]
config) config)
@ -111,7 +148,14 @@
(defn resolve-tokens-interactive+ (defn resolve-tokens-interactive+
"Interactive check of resolving tokens. "Interactive check of resolving tokens.
Uses a ids map to backtrace the original token from the resolved StyleDictionary token. Uses a ids map to backtrace the original token from the resolved StyleDictionary token.
This is necessary as the user might have removed/changed the token name but we still want to validate the value interactively."
We have to pass in all tokens from all sets in the entire library to style dictionary
so we know if references are missing / to resolve them and possibly show interactive previews (in the tokens form) to the user.
Since we're using the :name path as the identifier we might be throwing away or overriding tokens in the tree that we pass to StyleDictionary.
So to get back the original token from the resolved sd-token (see my updates for what an sd-token is) we include a temporary :id for the token that we pass to StyleDictionary,
this way after the resolving computation we can restore any token, even clashing ones with the same :name path by just looking up that :id in the ids map."
[tokens] [tokens]
(let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)] (let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)]
(resolve-tokens-tree+ tokens-tree #(get ids (sd-token-uuid %))))) (resolve-tokens-tree+ tokens-tree #(get ids (sd-token-uuid %)))))

View file

@ -1,31 +1,25 @@
(ns token-tests.style-dictionary-test (ns token-tests.style-dictionary-test
(:require (:require
[app.common.data :as d] [app.common.transit :as tr]
[app.common.types.tokens-lib :as ctob]
[app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.style-dictionary :as sd]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[app.common.transit :as tr]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[promesa.core :as p] [promesa.core :as p]))
[app.common.types.tokens-lib :as ctob]))
(def border-radius-token
{:value "12px"
:name "borderRadius.sm"
:type :border-radius})
(def reference-border-radius-token
{:value "{borderRadius.sm} * 2"
:name "borderRadius.md-with-dashes"
:type :border-radius})
(def tokens (d/ordered-map
(:name border-radius-token) border-radius-token
(:name reference-border-radius-token) reference-border-radius-token))
(t/deftest resolve-tokens-test (t/deftest resolve-tokens-test
(t/async (t/async
done done
(t/testing "resolves tokens using style-dictionary from a ids map" (t/testing "resolves tokens using style-dictionary from a ids map"
(let [tokens (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"))
(ctob/add-token-in-set "core" (ctob/make-token {:name "borderRadius.sm"
:value "12px"
:type :border-radius}))
(ctob/add-token-in-set "core" (ctob/make-token {:value "{borderRadius.sm} * 2"
:name "borderRadius.md-with-dashes"
:type :border-radius}))
(ctob/get-all-tokens))]
(-> (sd/resolve-tokens+ tokens) (-> (sd/resolve-tokens+ tokens)
(p/finally (p/finally
(fn [resolved-tokens] (fn [resolved-tokens]
@ -33,7 +27,7 @@
(t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit]))) (t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit])))
(t/is (= 24 (get-in resolved-tokens ["borderRadius.md-with-dashes" :resolved-value]))) (t/is (= 24 (get-in resolved-tokens ["borderRadius.md-with-dashes" :resolved-value])))
(t/is (= "px" (get-in resolved-tokens ["borderRadius.md-with-dashes" :unit]))) (t/is (= "px" (get-in resolved-tokens ["borderRadius.md-with-dashes" :unit])))
(done))))))) (done))))))))
(t/deftest process-json-stream-test (t/deftest process-json-stream-test
(t/async (t/async