♻️ Use rx streams for style dictionary interface

This commit is contained in:
Florian Schroedl 2025-05-09 11:01:09 +02:00 committed by Andrés Moya
parent 9f414b6ecd
commit 31f642ed25
6 changed files with 115 additions and 122 deletions

View file

@ -202,20 +202,20 @@
(build-dictionary [_]
(let [platform "json"
config' (clj->js config)]
(-> (sd. config')
(.buildAllPlatforms platform)
(p/then #(.getPlatformTokens ^js % platform))
(p/then #(.-allTokens ^js %))))))
config' (clj->js config)
build+ (-> (sd. config')
(.buildAllPlatforms platform)
(p/then #(.getPlatformTokens ^js % platform))
(p/then #(.-allTokens ^js %)))]
(rx/from build+))))
(defn resolve-tokens-tree+
(defn resolve-tokens-tree
([tokens-tree get-token]
(resolve-tokens-tree+ tokens-tree get-token (StyleDictionary. default-config)))
(resolve-tokens-tree tokens-tree get-token (StyleDictionary. default-config)))
([tokens-tree get-token style-dictionary]
(let [sdict (-> style-dictionary
(add-tokens tokens-tree)
(build-dictionary))]
(p/fmap #(process-sd-tokens % get-token) sdict))))
(->> (add-tokens style-dictionary tokens-tree)
(build-dictionary)
(rx/map #(process-sd-tokens % get-token)))))
(defn sd-token-name [^js sd-token]
(.. sd-token -original -name))
@ -223,12 +223,12 @@
(defn sd-token-uuid [^js sd-token]
(uuid (.-uuid (.-id ^js sd-token))))
(defn resolve-tokens+
(defn resolve-tokens
[tokens]
(let [tokens-tree (ctob/tokens-tree tokens)]
(resolve-tokens-tree+ tokens-tree #(get tokens (sd-token-name %)))))
(resolve-tokens-tree tokens-tree #(get tokens (sd-token-name %)))))
(defn resolve-tokens-interactive+
(defn resolve-tokens-interactive
"Interactive check of resolving tokens.
Uses a ids map to backtrace the original token from the resolved StyleDictionary token.
@ -241,10 +241,10 @@
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]
(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 %)))))
(defn resolve-tokens-with-errors+ [tokens]
(resolve-tokens-tree+
(defn resolve-tokens-with-errors [tokens]
(resolve-tokens-tree
(ctob/tokens-tree tokens)
#(get tokens (sd-token-name %))
(StyleDictionary. (assoc default-config :log {:verbosity "verbose"}))))
@ -346,24 +346,23 @@
(when unknown-tokens
(st/emit! (tokens-of-unknown-type-warning unknown-tokens)))
(try
(-> (ctob/get-all-tokens tokens-lib)
(resolve-tokens-with-errors+)
(p/then (fn [_] tokens-lib))
(p/catch (fn [sd-error]
(let [reference-errors (reference-errors sd-error)]
;; We allow reference errors for the users to resolve in the ui and throw on any other errors
(if reference-errors
(p/resolved tokens-lib)
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error)))))))
(->> (ctob/get-all-tokens tokens-lib)
(resolve-tokens-with-errors)
(rx/map (fn [_]
tokens-lib))
(rx/catch (fn [sd-error]
(let [reference-errors (reference-errors sd-error)]
;; We allow reference errors for the users to resolve in the ui and throw on any other errors
(if reference-errors
(rx/of tokens-lib)
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error)))))))
(catch js/Error e
(p/rejected (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e))))))))))
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e))))))))))
;; === Hooks
(defonce !tokens-cache (atom nil))
(defonce !theme-tokens-cache (atom nil))
(defn use-resolved-tokens
"The StyleDictionary process function is async, so we can't use resolved values directly.
@ -382,19 +381,17 @@
(cond
(nil? tokens) nil
;; The tokens are already processing somewhere
(p/promise? cached) (-> cached
(p/then #(reset! tokens-state %))
#_(p/catch js/console.error))
(rx/observable? cached) (rx/sub! cached #(reset! tokens-state %))
;; Get the cached entry
(some? cached) (reset! tokens-state cached)
;; No cached entry, start processing
:else (let [promise+ (if interactive?
(resolve-tokens-interactive+ tokens)
(resolve-tokens+ tokens))]
(swap! cache-atom assoc tokens promise+)
(p/then promise+ (fn [resolved-tokens]
(swap! cache-atom assoc tokens resolved-tokens)
(reset! tokens-state resolved-tokens)))))))
:else (let [resolved-tokens-s (if interactive?
(resolve-tokens-interactive tokens)
(resolve-tokens tokens))]
(swap! cache-atom assoc tokens resolved-tokens-s)
(rx/sub! resolved-tokens-s (fn [resolved-tokens]
(swap! cache-atom assoc tokens resolved-tokens)
(reset! tokens-state resolved-tokens)))))))
@tokens-state))
(defn use-resolved-tokens*
@ -408,12 +405,12 @@
(mf/with-effect [tokens interactive?]
(if (seq tokens)
(let [tpoint (dt/tpoint-ms)
promise (if interactive?
(resolve-tokens-interactive+ tokens)
(resolve-tokens+ tokens))]
tokens-s (if interactive?
(resolve-tokens-interactive tokens)
(resolve-tokens tokens))]
(->> promise
(p/fmap (fn [resolved-tokens]
(-> tokens-s
(rx/sub! (fn [resolved-tokens]
(let [elapsed (tpoint)]
(l/dbg :hint "use-resolved-tokens*" :elapsed elapsed)
(reset! state* resolved-tokens))))))

View file

@ -45,7 +45,7 @@
(when-let [tokens (some-> (dsh/lookup-file-data state)
(get :tokens-lib)
(ctob/get-active-themes-set-tokens))]
(->> (rx/from (sd/resolve-tokens+ tokens))
(->> (sd/resolve-tokens tokens)
(rx/mapcat
(fn [resolved-tokens]
(let [undo-id (js/Symbol)

View file

@ -185,12 +185,11 @@
(watch [_ state _]
(when-let [tokens-lib (-> (dsh/lookup-file-data state)
(get :tokens-lib))]
(let [tokens (-> (ctob/get-active-themes-set-tokens tokens-lib)
(sd/resolve-tokens+))]
(->> (rx/from tokens)
(rx/mapcat (fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
(propagate-tokens state sd-tokens)
(rx/of (dwu/commit-undo-transaction undo-id))))))))))))
(->> (ctob/get-active-themes-set-tokens tokens-lib)
(sd/resolve-tokens)
(rx/mapcat (fn [sd-tokens]
(let [undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
(propagate-tokens state sd-tokens)
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))

View file

@ -36,10 +36,10 @@
[app.util.functions :as uf]
[app.util.i18n :refer [tr]]
[app.util.keyboard :as k]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[malli.core :as m]
[malli.error :as me]
[promesa.core :as p]
[rumext.v2 :as mf]))
;; Schemas ---------------------------------------------------------------------
@ -94,14 +94,9 @@
(defn valid-value? [value]
(seq (finalize-value value)))
(defn schema-validation->promise [validated]
(if (:errors validated)
(p/rejected validated)
(p/resolved validated)))
;; Component -------------------------------------------------------------------
(defn validate-token-value+
(defn validate-token-value
"Validates token value by resolving the value `input` using `StyleDictionary`.
Returns a promise of either resolved tokens or rejects with an error state."
[{:keys [value name-value token tokens]}]
@ -110,10 +105,10 @@
token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value)]
(cond
(empty? (str/trim value))
(p/rejected {:errors [(wte/get-error-code :error.token/empty-input)]})
(rx/throw {:errors [(wte/get-error-code :error.token/empty-input)]})
(ctob/token-value-self-reference? token-name value)
(p/rejected {:errors [(wte/get-error-code :error.token/direct-self-reference)]})
(rx/throw {:errors [(wte/get-error-code :error.token/direct-self-reference)]})
:else
(let [tokens' (cond-> tokens
@ -122,14 +117,14 @@
:always (update token-name #(ctob/make-token (merge % {:value value
:name token-name
:type (:type token)}))))]
(-> tokens'
(sd/resolve-tokens-interactive+)
(p/then
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)]
(cond
resolved-value (p/resolved resolved-token)
:else (p/rejected {:errors (or errors (wte/get-error-code :error/unknown-error))}))))))))))
(->> tokens'
(sd/resolve-tokens-interactive)
(rx/mapcat
(fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)]
(cond
resolved-value (rx/of resolved-token)
:else (rx/throw {:errors (or errors (wte/get-error-code :error/unknown-error))}))))))))))
(defn use-debonced-resolve-callback
"Resolves a token values using `StyleDictionary`.
@ -148,14 +143,14 @@
(js/setTimeout
(fn []
(when (not (timeout-outdated-cb?))
(-> (validate-token-value+ {:value value
(->> (validate-token-value {:value value
:name-value @name-ref
:token token
:tokens tokens})
(p/finally
(fn [x err]
(when-not (timeout-outdated-cb?)
(callback (or err x))))))))
(rx/filter #(not (timeout-outdated-cb?)))
(rx/subs!
callback
callback))))
timeout))))]
debounced-resolver-callback))
@ -442,33 +437,36 @@
;; and the user might have edited a valid form to make it invalid,
;; and press enter before the next validations could return.
(let [final-name (finalize-name @token-name-ref)
valid-name?+ (-> (validate-name final-name) schema-validation->promise)
valid-name? (try
(not (:errors (validate-name final-name)))
(catch js/Error _ nil))
final-value (finalize-value (mf/ref-val value-ref))
final-description @description-ref
valid-description?+ (some-> final-description validate-descripion schema-validation->promise)]
(-> (p/all [valid-name?+
valid-description?+
(validate-token-value+ {:value final-value
:name-value final-name
:token token
:tokens active-theme-tokens})])
(p/finally (fn [result err]
;; 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
(when (and (seq result) (not err))
(st/emit!
(if (ctob/token? token)
(dwtl/update-token (:name token)
{:name final-name
:value final-value
:description final-description})
valid-description? (if final-description
(try
(not (:errors (validate-descripion final-description)))
(catch js/Error _ nil))
true)]
(when (and valid-name? valid-description?)
(->> (validate-token-value {:value final-value
:name-value final-name
:token token
:tokens active-theme-tokens})
(rx/subs!
(fn []
(st/emit!
(if (ctob/token? token)
(dwtl/update-token (:name token)
{:name final-name
:value final-value
:description final-description})
(dwtl/create-token {:name final-name
:type token-type
:value final-value
:description final-description}))
(dwtp/propagate-workspace-tokens)
(modal/hide)))))))))
(dwtl/create-token {:name final-name
:type token-type
:value final-value
:description final-description}))
(dwtp/propagate-workspace-tokens)
(modal/hide)))))))))
on-delete-token
(mf/use-fn