diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index cc3e3f3a3d..ed8bc02fa2 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -14,6 +14,7 @@ [cuerdas.core :as str] [malli.core :as m] [malli.error :as me] + [promesa.core :as p] [rumext.v2 :as mf])) ;; Schemas --------------------------------------------------------------------- @@ -33,11 +34,6 @@ [:string {:min 1 :max 255}] non-existing-token-schema]))) -(comment - (-> (m/explain (token-name-schema #{"foo"}) nil) - (me/humanize)) - nil) - ;; Helpers --------------------------------------------------------------------- (defn finalize-name [name] @@ -46,6 +42,57 @@ (defn finalize-value [name] (str/trim name)) +;; Component ------------------------------------------------------------------- + +(defn use-debonced-resolve-callback + [name-ref token tokens callback & {:keys [cached timeout] + :or {cached {} + timeout 160}}] + (let [timeout-id-ref (mf/use-ref nil) + cache (mf/use-ref cached) + debounced-resolver-callback + (mf/use-callback + (mf/deps token callback tokens) + (fn [event] + (let [input (dom/get-target-val event) + timeout-id (js/Symbol) + ;; Dont execute callback when the timout-id-ref is outdated because this function got called again + timeout-outdated-cb? #(not= (mf/ref-val timeout-id-ref) timeout-id)] + (mf/set-ref-val! timeout-id-ref timeout-id) + (js/setTimeout + (fn [] + (when (not (timeout-outdated-cb?)) + (if-let [cached (get (mf/ref-val cache) tokens)] + (callback cached) + (let [token-references (sd/find-token-references input) + ;; When creating a new token we dont have a token name yet, + ;; so we use a temporary token name that hopefully doesn't clash with any of the users token names. + token-name (if (empty? @name-ref) "__TOKEN_STUDIO_SYSTEM.TEMP" @name-ref) + direct-self-reference? (get token-references token-name) + empty-input? (empty? (str/trim input))] + (cond + empty-input? (callback nil) + direct-self-reference? (callback :error/token-direct-self-reference) + :else + (let [token-id (or (:id token) (random-uuid)) + new-tokens (update tokens token-id merge {:id token-id + :value input + :name token-name})] + (-> (sd/resolve-tokens+ new-tokens) + (p/finally + (fn [resolved-tokens _err] + (when-not (timeout-outdated-cb?) + (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)] + (cond + resolved-value (do + (mf/set-ref-val! cache (assoc (mf/ref-val cache) input resolved-tokens)) + (callback resolved-token)) + (= #{:style-dictionary/missing-reference} errors) (callback :error/token-missing-reference) + :else (callback :error/unknown-error))))))))))))) + + timeout))))] + debounced-resolver-callback)) + (mf/defc form {::mf/wrap-props false} [{:keys [token] :as _args}] @@ -63,7 +110,6 @@ :description ""} token)) state @state* - _ (js/console.log "render") form-touched (mf/use-state nil) update-form-touched (mf/use-callback @@ -90,11 +136,11 @@ set-resolve-value (mf/use-callback (fn [token-or-err] (let [value (cond - (= token-or-err :error/token-self-reference) :error/token-self-reference + (= token-or-err :error/token-direct-self-reference) :error/token-self-reference (= token-or-err :error/token-missing-reference) :error/token-missing-reference (:resolved-value token-or-err) (:resolved-value token-or-err))] (reset! token-resolve-result value)))) - on-update-value (sd/use-debonced-resolve-callback name token tokens set-resolve-value) + on-update-value (use-debonced-resolve-callback name token tokens set-resolve-value) value-error? (when (keyword? @token-resolve-result) (= (namespace @token-resolve-result) "error")) @@ -136,6 +182,7 @@ (case @token-resolve-result :error/token-self-reference "Token has self reference" :error/token-missing-reference "Token has missing reference" + :error/unknown-error "" nil "Enter token value" [:p @token-resolve-result])] [:& tokens.common/labeled-input {:label "Description" diff --git a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs index 7cdc438d41..34b8ccb645 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -30,10 +30,6 @@ (map second) (into #{}))) -(defn token-self-reference? [token-name value-string] - (let [refs (find-token-references value-string)] - (get refs token-name))) - (defn tokens->style-dictionary+ "Resolves references and math expressions using StyleDictionary. Returns a promise with the resolved dictionary." @@ -115,55 +111,6 @@ ;; Hooks ----------------------------------------------------------------------- -(def new-token-temp-name - "TOKEN_STUDIO_SYSTEM.TEMP") - -(defn use-debonced-resolve-callback - [name-ref token tokens callback & {:keys [cached timeout] - :or {cached {} - timeout 160}}] - (let [timeout-id-ref (mf/use-ref nil) - cache (mf/use-ref cached) - debounced-resolver-callback - (mf/use-callback - (mf/deps token callback tokens) - (fn [event] - (let [input (dom/get-target-val event) - timeout-id (js/Symbol)] - (mf/set-ref-val! timeout-id-ref timeout-id) - (js/setTimeout - (fn [] - (when (= (mf/ref-val timeout-id-ref) timeout-id) - (let [cached (-> (mf/ref-val cache) - (get tokens)) - token-name (if (empty? @name-ref) new-token-temp-name @name-ref)] - (cond - cached (callback cached) - (token-self-reference? token-name input) (callback :error/token-self-reference) - :else (let [token-id (or (:id token) (random-uuid)) - new-tokens (update tokens token-id merge {:id token-id - :value input - :name token-name})] - (-> (resolve-tokens+ new-tokens) - (p/finally - (fn [resolved-tokens _err] - (js/console.log "input" input (empty? (str/trim input))) - (cond - ;; Ignore outdated callbacks because the user input changed since it tried to resolve - (not= (mf/ref-val timeout-id-ref) timeout-id) nil - (empty? (str/trim input)) (callback nil) - :else (let [resolved-token (get resolved-tokens token-id)] - (js/console.log "resolved-token" resolved-token) - (if (:resolved-value resolved-token) - (do - (mf/set-ref-val! cache (assoc (mf/ref-val cache) input resolved-tokens)) - (callback resolved-token)) - (callback :error/token-missing-reference)))))))))))) - - - timeout))))] - debounced-resolver-callback)) - (defonce !tokens-cache (atom nil)) (defn use-resolved-tokens diff --git a/frontend/test/token_tests/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs index 8b77745ccd..ff03ba16c8 100644 --- a/frontend/test/token_tests/style_dictionary_test.cljs +++ b/frontend/test/token_tests/style_dictionary_test.cljs @@ -18,8 +18,3 @@ (t/is (nil? (wtsd/find-token-references "1 + 2"))) ;; Edge-case: Ignore unmatched closing parens (t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo}} + {bar}")))) - -(t/deftest test-token-self-reference? - (t/is (some? (wtsd/token-self-reference? "some.value" "{md} + {some.value}"))) - (t/is (nil? (wtsd/token-self-reference? "some.value" "some.value"))) - (t/is (nil? (wtsd/token-self-reference? "sm" "{md} + {lg}"))))