From 0a73c3aa9512aceb7f01db9eeb28f3cfa50a5e83 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 11:09:01 +0200 Subject: [PATCH 01/51] Validation in modal --- .../app/main/ui/workspace/tokens/modal.cljs | 58 +++++++++++++++++-- .../app/main/ui/workspace/tokens/modal.scss | 14 +++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.cljs b/frontend/src/app/main/ui/workspace/tokens/modal.cljs index 755aa7a54..5d4312fd7 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modal.cljs @@ -7,14 +7,17 @@ (ns app.main.ui.workspace.tokens.modal (:require-macros [app.main.style :as stl]) (:require + ["lodash.debounce" :as debounce] [app.common.data :as d] [app.main.data.modal :as modal] [app.main.data.tokens :as dt] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.workspace.tokens.common :as tokens.common] + [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.util.dom :as dom] [okulary.core :as l] + [promesa.core :as p] [rumext.v2 :as mf])) (defn calculate-position @@ -41,12 +44,47 @@ [key (:value field)]) fields) (into {}))) +(defn fields-to-token + "Converts field to token value that will be stored and processed. + Handles a simple token token type for now." + [token-type fields] + (case token-type + (first fields))) + +;; https://dev.to/haseeb1009/the-useevent-hook-1c8l +(defn use-event-callback + [f] + (let [ref (mf/use-ref)] + (mf/use-layout-effect + (fn [] + (reset! ref f) + js/undefined)) + (mf/use-callback (fn [& args] (some-> @ref (apply args))) []))) + +(defn use-promise-debounce [fn+ on-success on-err] + (let [debounce-promise (mf/use-ref nil) + callback-fn (fn [] + (let [id (random-uuid)] + (mf/set-ref-val! debounce-promise id) + (-> (fn+) + (p/then (fn [result] + (js/console.log "@debounce-promise id" @debounce-promise id) + (when (= @debounce-promise id) + (js/console.log "update" result) + (on-success result)))) + (p/catch on-err)))) + debounced-fn (debounce callback-fn)] + debounced-fn)) + (mf/defc tokens-properties-form {::mf/wrap-props false} - [{:keys [token-type x y position fields token]}] - (let [vport (mf/deref viewport) + [{:keys [token-type x y position fields token] :as args}] + (let [tokens (sd/use-resolved-workspace-tokens {:debug? true}) + vport (mf/deref viewport) style (calculate-position vport position x y) + resolved-value (mf/use-state (get-in tokens [(:id token) :value])) + name (mf/use-var (or (:name token) "")) on-update-name #(reset! name (dom/get-target-val %)) @@ -60,8 +98,15 @@ fields) state (mf/use-state initial-fields) + debounced-update (use-promise-debounce sd/resolve-tokens+ + (fn [tokens] + (let [value (get-in tokens [(:id token) :value])] + (reset! resolved-value value))) + #(reset! resolved-value nil)) + on-update-state-field (fn [idx e] (let [value (dom/get-target-val e)] + (debounced-update) (swap! state assoc-in [idx :value] value))) on-submit (fn [e] @@ -76,7 +121,6 @@ (:id token) (assoc :id (:id token)))] (st/emit! (dt/add-token token)) (modal/hide!)))] - [:form {:class (stl/css :shadow) :style (clj->js style) @@ -86,13 +130,17 @@ :input-props {:default-value @name :auto-focus true :on-change on-update-name}}] - (for [[idx {:keys [type label]}] (d/enumerate @state)] + (for [[idx {:keys [label type]}] (d/enumerate @state)] [:* {:key (str "form-field-" idx)} (case type :box-shadow [:p "TODO BOX SHADOW"] - [:& tokens.common/labeled-input {:label label + [:& tokens.common/labeled-input {:label "Value" :input-props {:default-value @token-value :on-change #(on-update-state-field idx %)}}])]) + (when (and @resolved-value + (not= @resolved-value (:value (first @state)))) + [:div {:class (stl/css :resolved-value)} + [:p @resolved-value]]) [:& tokens.common/labeled-input {:label "Description" :input-props {:default-value @description :on-change #(on-update-description %)}}] diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.scss b/frontend/src/app/main/ui/workspace/tokens/modal.scss index d40a861dd..c191f0bdf 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.scss +++ b/frontend/src/app/main/ui/workspace/tokens/modal.scss @@ -19,6 +19,20 @@ gap: $s-8; } +.resolved-value { + @include bodySmallTypography; + padding: $s-4 $s-6; + font-weight: medium; + + color: var(--color-foreground-primary); + border: 1px solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent); + + p { + font-size: $fs-12; + margin: 0; + } +} + .shadow { @extend .modal-container-base; @include menuShadow; From f169d4939721b22184eaa8c13a8edcf231dac2f5 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 11:18:41 +0200 Subject: [PATCH 02/51] Remove double cljs conversion --- frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs | 1 - 1 file changed, 1 deletion(-) 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 d3c6ceef9..248870baf 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -77,7 +77,6 @@ (defn resolve-tokens+ [tokens & {:keys [debug?] :as config}] (p/let [sd-tokens (-> (tokens-name-map tokens) - (clj->js) (resolve-sd-tokens+ config))] (let [resolved-tokens (reduce (fn [acc ^js cur] From deb9cb11201aec86898443bc60e2620c09f8ba27 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 11:26:29 +0200 Subject: [PATCH 03/51] Remove debugging code --- .../main/ui/workspace/tokens/style_dictionary.cljs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) 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 248870baf..ba32c17eb 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -136,17 +136,9 @@ ;; Testing --------------------------------------------------------------------- -(defn tokens-studio-example [] - (-> (shadow.resource/inline "./data/example-tokens-set.json") - (js/JSON.parse) - .-core)) - (comment - (defonce !output (atom nil)) - @!output - (-> (resolve-workspace-tokens+ {:debug? true}) (p/then #(reset! !output %))) @@ -158,7 +150,9 @@ "b" {:name "b" :value "{a} * 2"}}) (#(resolve-sd-tokens+ % {:debug? true}))) - (-> (tokens-studio-example) - (resolve-sd-tokens+ {:debug? true})) + (let [example (-> (shadow.resource/inline "./data/example-tokens-set.json") + (js/JSON.parse) + .-core)] + (resolve-sd-tokens+ example {:debug? true})) nil) From 1596dbe155b6b2477858e58e85cd926e58cc5d77 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 13:54:09 +0200 Subject: [PATCH 04/51] Add function to verify already used names --- frontend/src/app/main/ui/workspace/tokens/modal.cljs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.cljs b/frontend/src/app/main/ui/workspace/tokens/modal.cljs index 5d4312fd7..d9738f713 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modal.cljs @@ -80,6 +80,12 @@ {::mf/wrap-props false} [{:keys [token-type x y position fields token] :as args}] (let [tokens (sd/use-resolved-workspace-tokens {:debug? true}) + used-token-names (mf/use-memo + (mf/deps tokens) + (fn [] + (-> (into #{} (map (fn [[_ {:keys [name]}]] name) tokens)) + ;; Allow setting token to already used name + (disj (:name token))))) vport (mf/deref viewport) style (calculate-position vport position x y) From 0c45d15fe7fc171e8ff6cd26dec8269a29d442ea Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 16:01:40 +0200 Subject: [PATCH 05/51] Variadic function doesn't work for hooks --- .../app/main/ui/workspace/tokens/style_dictionary.cljs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 ba32c17eb..f1e1c7a7d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -128,11 +128,9 @@ (reset! tokens-state resolved-tokens)))))))) @tokens-state)) -(defn use-resolved-workspace-tokens - ([] (use-resolved-tokens nil)) - ([options] - (-> (mf/deref refs/workspace-tokens) - (use-resolved-tokens options)))) +(defn use-resolved-workspace-tokens [& {:as config}] + (-> (mf/deref refs/workspace-tokens) + (use-resolved-tokens config))) ;; Testing --------------------------------------------------------------------- From 0830a26be972b74db9595b7dcd188cb15b517034 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 17:11:28 +0200 Subject: [PATCH 06/51] Add error state for invalid name --- .../app/main/ui/workspace/tokens/common.cljs | 5 +- .../app/main/ui/workspace/tokens/common.scss | 4 + .../app/main/ui/workspace/tokens/modal.cljs | 177 +++++++++++------- .../app/main/ui/workspace/tokens/modal.scss | 6 + 4 files changed, 124 insertions(+), 68 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/common.cljs b/frontend/src/app/main/ui/workspace/tokens/common.cljs index 03b3d4239..6cb36ef18 100644 --- a/frontend/src/app/main/ui/workspace/tokens/common.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/common.cljs @@ -34,7 +34,7 @@ (mf/defc labeled-input {::mf/wrap-props false} - [{:keys [label input-props auto-complete?]}] + [{:keys [label input-props auto-complete? error?]}] (let [input-props (cond-> input-props :always camel-keys ;; Disable auto-complete on form fields for proprietary password managers @@ -42,6 +42,7 @@ (not auto-complete?) (assoc "data-1p-ignore" true "data-lpignore" true :auto-complete "off"))] - [:label {:class (stl/css :labeled-input)} + [:label {:class (stl/css-case :labeled-input true + :error error?)} [:span {:class (stl/css :label)} label] [:& :input input-props]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/common.scss b/frontend/src/app/main/ui/workspace/tokens/common.scss index 30d611be7..9398a2bb2 100644 --- a/frontend/src/app/main/ui/workspace/tokens/common.scss +++ b/frontend/src/app/main/ui/workspace/tokens/common.scss @@ -18,6 +18,10 @@ } } +.labeled-input-error { + border: 1px solid var(--status-color-error-500) !important; +} + .button { @extend .button-primary; } diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.cljs b/frontend/src/app/main/ui/workspace/tokens/modal.cljs index d9738f713..9f1edc4a4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modal.cljs @@ -16,6 +16,10 @@ [app.main.ui.workspace.tokens.common :as tokens.common] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.util.dom :as dom] + [clojure.set :as set] + [cuerdas.core :as str] + [malli.core :as m] + [malli.error :as me] [okulary.core :as l] [promesa.core :as p] [rumext.v2 :as mf])) @@ -36,8 +40,11 @@ :else {:left (str (+ x 80) "px") :top (str (- y 70 overflow-fix) "px")}))) -(def viewport - (l/derived :vport refs/workspace-local)) +(defn use-viewport-position-style [x y position] + (let [vport (-> (l/derived :vport refs/workspace-local) + (mf/deref))] + (-> (calculate-position vport position x y) + (clj->js)))) (defn fields->map [fields] (->> (map (fn [{:keys [key] :as field}] @@ -76,81 +83,119 @@ debounced-fn (debounce callback-fn)] debounced-fn)) +(defn token-name-schema + "Generate a dynamic schema validation to check if a token name already exists. + `existing-token-names` should be a set of strings." + [existing-token-names] + (let [non-existing-token-schema + (m/-simple-schema + {:type :token/name-exists + :pred #(not (get existing-token-names %)) + :type-properties {:error/fn #(str (:value %) " is an already existing token name") + :existing-token-names existing-token-names}})] + (m/schema + [:and + [:string {:min 1 :max 255}] + non-existing-token-schema]))) + +(comment + (-> (m/explain (token-name-schema #{"foo"}) nil) + (me/humanize)) + nil) + (mf/defc tokens-properties-form {::mf/wrap-props false} - [{:keys [token-type x y position fields token] :as args}] - (let [tokens (sd/use-resolved-workspace-tokens {:debug? true}) - used-token-names (mf/use-memo - (mf/deps tokens) - (fn [] - (-> (into #{} (map (fn [[_ {:keys [name]}]] name) tokens)) - ;; Allow setting token to already used name - (disj (:name token))))) - vport (mf/deref viewport) - style (calculate-position vport position x y) + [{:keys [x y position token] :as _args}] + (let [wrapper-style (use-viewport-position-style x y position) - resolved-value (mf/use-state (get-in tokens [(:id token) :value])) + ;; Tokens + tokens (sd/use-resolved-workspace-tokens) + existing-token-names (mf/use-memo + (mf/deps tokens) + (fn [] + (-> (into #{} (map (fn [[_ {:keys [name]}]] name) tokens)) + ;; Allow setting token to already used name + (disj (:name token))))) - name (mf/use-var (or (:name token) "")) - on-update-name #(reset! name (dom/get-target-val %)) + ;; State + state* (mf/use-state (merge {:name "" + :value "" + :description ""} + token)) + state @state* - token-value (mf/use-var (or (:value token) "")) + ;; Name + finalize-name str/trim + name-schema (mf/use-memo + (mf/deps existing-token-names) + (fn [] + (token-name-schema existing-token-names))) + on-update-name (fn [e] + (let [value (dom/get-target-val e) + errors (->> (finalize-name value) + (m/explain name-schema))] + (swap! state* merge {:name value + :errors/name errors}))) + disabled? (or + (empty? (finalize-name (:name state))) + (:errors/name state))] - description (mf/use-var (or (:description token) "")) - on-update-description #(reset! description (dom/get-target-val %)) + ;; on-update-name (fn [e] + ;; (let [{:keys [errors] :as state} (mf/deref state*) + ;; value (-> (dom/get-target-val e) + ;; (str/trim))] + ;; (cond-> @state* + ;; ;; Remove existing name errors + ;; :always (update :errors set/difference #{:empty}) + ;; (str/empty?) (conj)) + ;; (swap! state* assoc :name (dom/get-target-val e)))) + ;; on-update-description #(swap! state* assoc :description (dom/get-target-val %)) + ;; on-update-field (fn [idx e] + ;; (let [value (dom/get-target-val e)] + ;; (swap! state* assoc-in [idx :value] value))) - initial-fields (mapv (fn [field] - (assoc field :value (or (:value token) ""))) - fields) - state (mf/use-state initial-fields) - debounced-update (use-promise-debounce sd/resolve-tokens+ - (fn [tokens] - (let [value (get-in tokens [(:id token) :value])] - (reset! resolved-value value))) - #(reset! resolved-value nil)) - - on-update-state-field (fn [idx e] - (let [value (dom/get-target-val e)] - (debounced-update) - (swap! state assoc-in [idx :value] value))) - - on-submit (fn [e] - (dom/prevent-default e) - (let [token-value (-> (fields->map @state) - (first) - (val)) - token (cond-> {:name @name - :type (or (:type token) token-type) - :value token-value} - @description (assoc :description @description) - (:id token) (assoc :id (:id token)))] - (st/emit! (dt/add-token token)) - (modal/hide!)))] + ;; on-submit (fn [e] + ;; (dom/prevent-default e) + ;; (let [token-value (-> (fields->map state) + ;; (first) + ;; (val)) + ;; token (cond-> {:name (:name state) + ;; :type (or (:type token) token-type) + ;; :value token-value + ;; :description (:description state)} + ;; (:id token) (assoc :id (:id token)))] + ;; (st/emit! (dt/add-token token)) + ;; (modal/hide!)))] [:form {:class (stl/css :shadow) - :style (clj->js style) - :on-submit on-submit} + :style wrapper-style + #_#_:on-submit on-submit} [:div {:class (stl/css :token-rows)} - [:& tokens.common/labeled-input {:label "Name" - :input-props {:default-value @name - :auto-focus true - :on-change on-update-name}}] - (for [[idx {:keys [label type]}] (d/enumerate @state)] - [:* {:key (str "form-field-" idx)} - (case type - :box-shadow [:p "TODO BOX SHADOW"] - [:& tokens.common/labeled-input {:label "Value" - :input-props {:default-value @token-value - :on-change #(on-update-state-field idx %)}}])]) - (when (and @resolved-value - (not= @resolved-value (:value (first @state)))) - [:div {:class (stl/css :resolved-value)} - [:p @resolved-value]]) - [:& tokens.common/labeled-input {:label "Description" - :input-props {:default-value @description - :on-change #(on-update-description %)}}] + [:div + [:& tokens.common/labeled-input {:label "Name" + :error? (:errors/name state) + :input-props {:default-value (:name state) + :auto-focus true + :on-change on-update-name}}] + (when-let [errors (:errors/name state)] + [:p {:class (stl/css :error)} (me/humanize errors)])] + #_(for [[idx {:keys [label type value]}] (d/enumerate (:fields state))] + [:* {:key (str "form-field-" idx)} + (case type + :box-shadow [:p "TODO BOX SHADOW"] + [:& tokens.common/labeled-input {:label "Value" + :input-props {:default-value value + :on-change #(on-update-field idx %)}}])]) + ;; (when (and @resolved-value + ;; (not= @resolved-value (:value (first @state*)))) + ;; [:div {:class (stl/css :resolved-value)} + ;; [:p @resolved-value]]) + #_[:& tokens.common/labeled-input {:label "Description" + :input-props {:default-value (:description state) + :on-change #(on-update-description %)}}] [:div {:class (stl/css :button-row)} [:button {:class (stl/css :button) - :type "submit"} + :type "submit" + :disabled disabled?} "Save"]]]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.scss b/frontend/src/app/main/ui/workspace/tokens/modal.scss index c191f0bdf..403189783 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.scss +++ b/frontend/src/app/main/ui/workspace/tokens/modal.scss @@ -19,6 +19,12 @@ gap: $s-8; } +.error { + @include bodySmallTypography; + margin-top: $s-6; + color: var(--status-color-error-500); +} + .resolved-value { @include bodySmallTypography; padding: $s-4 $s-6; From 885322d479f8544130e5ec059f3976e473aed44c Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Wed, 19 Jun 2024 17:17:00 +0200 Subject: [PATCH 07/51] Reestore fields --- .../src/app/main/ui/workspace/tokens/common.cljs | 2 +- .../src/app/main/ui/workspace/tokens/modal.cljs | 16 ++++++---------- .../src/app/main/ui/workspace/tokens/modal.scss | 1 + 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/common.cljs b/frontend/src/app/main/ui/workspace/tokens/common.cljs index 6cb36ef18..cd955330c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/common.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/common.cljs @@ -43,6 +43,6 @@ "data-lpignore" true :auto-complete "off"))] [:label {:class (stl/css-case :labeled-input true - :error error?)} + :labeled-input-error error?)} [:span {:class (stl/css :label)} label] [:& :input input-props]])) diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.cljs b/frontend/src/app/main/ui/workspace/tokens/modal.cljs index 9f1edc4a4..56ecdd396 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modal.cljs @@ -180,20 +180,16 @@ :on-change on-update-name}}] (when-let [errors (:errors/name state)] [:p {:class (stl/css :error)} (me/humanize errors)])] - #_(for [[idx {:keys [label type value]}] (d/enumerate (:fields state))] - [:* {:key (str "form-field-" idx)} - (case type - :box-shadow [:p "TODO BOX SHADOW"] - [:& tokens.common/labeled-input {:label "Value" - :input-props {:default-value value - :on-change #(on-update-field idx %)}}])]) + [:& tokens.common/labeled-input {:label "Value" + :input-props {:default-value (:value state) + #_#_:on-change #(on-update-field idx %)}}] ;; (when (and @resolved-value ;; (not= @resolved-value (:value (first @state*)))) ;; [:div {:class (stl/css :resolved-value)} ;; [:p @resolved-value]]) - #_[:& tokens.common/labeled-input {:label "Description" - :input-props {:default-value (:description state) - :on-change #(on-update-description %)}}] + [:& tokens.common/labeled-input {:label "Description" + :input-props {:default-value (:description state) + #_#_:on-change #(on-update-description %)}}] [:div {:class (stl/css :button-row)} [:button {:class (stl/css :button) :type "submit" diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.scss b/frontend/src/app/main/ui/workspace/tokens/modal.scss index 403189783..2c4fae323 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.scss +++ b/frontend/src/app/main/ui/workspace/tokens/modal.scss @@ -22,6 +22,7 @@ .error { @include bodySmallTypography; margin-top: $s-6; + margin-bottom: 0; color: var(--status-color-error-500); } From e394216f00417f0130a3ab02a25e3fee69632e87 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 10:02:21 +0200 Subject: [PATCH 08/51] Move form out of modal specific code --- .../tokens/{modal.cljs => form.cljs} | 83 ++----------------- .../tokens/{modal.scss => form.scss} | 46 ---------- .../app/main/ui/workspace/tokens/modals.cljs | 66 +++++++++++---- .../app/main/ui/workspace/tokens/modals.scss | 16 ++++ 4 files changed, 73 insertions(+), 138 deletions(-) rename frontend/src/app/main/ui/workspace/tokens/{modal.cljs => form.cljs} (66%) rename frontend/src/app/main/ui/workspace/tokens/{modal.scss => form.scss} (51%) create mode 100644 frontend/src/app/main/ui/workspace/tokens/modals.scss diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs similarity index 66% rename from frontend/src/app/main/ui/workspace/tokens/modal.cljs rename to frontend/src/app/main/ui/workspace/tokens/form.cljs index 56ecdd396..5ba500d9b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -4,85 +4,17 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.main.ui.workspace.tokens.modal +(ns app.main.ui.workspace.tokens.form (:require-macros [app.main.style :as stl]) (:require - ["lodash.debounce" :as debounce] - [app.common.data :as d] - [app.main.data.modal :as modal] - [app.main.data.tokens :as dt] - [app.main.refs :as refs] - [app.main.store :as st] [app.main.ui.workspace.tokens.common :as tokens.common] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.util.dom :as dom] - [clojure.set :as set] [cuerdas.core :as str] [malli.core :as m] [malli.error :as me] - [okulary.core :as l] - [promesa.core :as p] [rumext.v2 :as mf])) -(defn calculate-position - "Calculates the style properties for the given coordinates and position" - [{vh :height} position x y] - (let [;; picker height in pixels - h 510 - ;; Checks for overflow outside the viewport height - overflow-fix (max 0 (+ y (- 50) h (- vh))) - - x-pos 325] - (cond - (or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"} - (= position :left) {:left (str (- x x-pos) "px") - :top (str (- y 50 overflow-fix) "px")} - :else {:left (str (+ x 80) "px") - :top (str (- y 70 overflow-fix) "px")}))) - -(defn use-viewport-position-style [x y position] - (let [vport (-> (l/derived :vport refs/workspace-local) - (mf/deref))] - (-> (calculate-position vport position x y) - (clj->js)))) - -(defn fields->map [fields] - (->> (map (fn [{:keys [key] :as field}] - [key (:value field)]) fields) - (into {}))) - -(defn fields-to-token - "Converts field to token value that will be stored and processed. - Handles a simple token token type for now." - [token-type fields] - (case token-type - (first fields))) - -;; https://dev.to/haseeb1009/the-useevent-hook-1c8l -(defn use-event-callback - [f] - (let [ref (mf/use-ref)] - (mf/use-layout-effect - (fn [] - (reset! ref f) - js/undefined)) - (mf/use-callback (fn [& args] (some-> @ref (apply args))) []))) - -(defn use-promise-debounce [fn+ on-success on-err] - (let [debounce-promise (mf/use-ref nil) - callback-fn (fn [] - (let [id (random-uuid)] - (mf/set-ref-val! debounce-promise id) - (-> (fn+) - (p/then (fn [result] - (js/console.log "@debounce-promise id" @debounce-promise id) - (when (= @debounce-promise id) - (js/console.log "update" result) - (on-success result)))) - (p/catch on-err)))) - debounced-fn (debounce callback-fn)] - debounced-fn)) - (defn token-name-schema "Generate a dynamic schema validation to check if a token name already exists. `existing-token-names` should be a set of strings." @@ -103,13 +35,10 @@ (me/humanize)) nil) -(mf/defc tokens-properties-form +(mf/defc form {::mf/wrap-props false} - [{:keys [x y position token] :as _args}] - (let [wrapper-style (use-viewport-position-style x y position) - - ;; Tokens - tokens (sd/use-resolved-workspace-tokens) + [{:keys [token] :as _args}] + (let [tokens (sd/use-resolved-workspace-tokens) existing-token-names (mf/use-memo (mf/deps tokens) (fn [] @@ -168,9 +97,7 @@ ;; (st/emit! (dt/add-token token)) ;; (modal/hide!)))] [:form - {:class (stl/css :shadow) - :style wrapper-style - #_#_:on-submit on-submit} + {#_#_:on-submit on-submit} [:div {:class (stl/css :token-rows)} [:div [:& tokens.common/labeled-input {:label "Name" diff --git a/frontend/src/app/main/ui/workspace/tokens/modal.scss b/frontend/src/app/main/ui/workspace/tokens/form.scss similarity index 51% rename from frontend/src/app/main/ui/workspace/tokens/modal.scss rename to frontend/src/app/main/ui/workspace/tokens/form.scss index 2c4fae323..df3fc5a88 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modal.scss +++ b/frontend/src/app/main/ui/workspace/tokens/form.scss @@ -39,49 +39,3 @@ margin: 0; } } - -.shadow { - @extend .modal-container-base; - @include menuShadow; - position: absolute; - z-index: 11; - overflow-y: auto; - overflow-x: hidden; - - &-select-wrapper { - display: flex; - grid-gap: $s-4; - } - - &-properties { - display: flex; - flex-direction: column; - grid-gap: $s-4; - } - - .inputs-grid { - display: grid; - grid-template-areas: - "x blur blur spread spread" - "y color color color color"; - grid-template-columns: repeat(5, 1fr); - grid-template-rows: repeat(2, 1fr); - grid-gap: $s-4; - - label:nth-child(1) { - grid-area: x; - } - label:nth-child(2) { - grid-area: y; - } - label:nth-child(3) { - grid-area: blur; - } - label:nth-child(4) { - grid-area: spread; - } - label:nth-child(5) { - grid-area: color; - } - } -} diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.cljs b/frontend/src/app/main/ui/workspace/tokens/modals.cljs index 9404c9c07..eba26a68e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals.cljs @@ -5,85 +5,123 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.tokens.modals + (:require-macros [app.main.style :as stl]) (:require [app.main.data.modal :as modal] - [app.main.ui.workspace.tokens.modal :refer [tokens-properties-form]] + [app.main.refs :as refs] + [app.main.ui.workspace.tokens.form :refer [form]] + [okulary.core :as l] [rumext.v2 :as mf])) +;; Component ------------------------------------------------------------------- + +(defn calculate-position + "Calculates the style properties for the given coordinates and position" + [{vh :height} position x y] + (let [;; picker height in pixels + h 510 + ;; Checks for overflow outside the viewport height + overflow-fix (max 0 (+ y (- 50) h (- vh))) + + x-pos 325] + (cond + (or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"} + (= position :left) {:left (str (- x x-pos) "px") + :top (str (- y 50 overflow-fix) "px")} + :else {:left (str (+ x 80) "px") + :top (str (- y 70 overflow-fix) "px")}))) + +(defn use-viewport-position-style [x y position] + (let [vport (-> (l/derived :vport refs/workspace-local) + (mf/deref))] + (-> (calculate-position vport position x y) + (clj->js)))) + +(mf/defc modal + {::mf/wrap-props false} + [{:keys [x y position token] :as _args}] + (let [wrapper-style (use-viewport-position-style x y position)] + [:div + {:class (stl/css :shadow) + :style wrapper-style} + [:& form {:token token}]])) + +;; Modals ---------------------------------------------------------------------- + (mf/defc boolean-modal {::mf/register modal/components ::mf/register-as :tokens/boolean} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc border-radius-modal {::mf/register modal/components ::mf/register-as :tokens/border-radius} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc stroke-width-modal {::mf/register modal/components ::mf/register-as :tokens/stroke-width} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc box-shadow-modal {::mf/register modal/components ::mf/register-as :tokens/box-shadow} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc sizing-modal {::mf/register modal/components ::mf/register-as :tokens/sizing} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc dimensions-modal {::mf/register modal/components ::mf/register-as :tokens/dimensions} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc numeric-modal {::mf/register modal/components ::mf/register-as :tokens/numeric} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc opacity-modal {::mf/register modal/components ::mf/register-as :tokens/opacity} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc other-modal {::mf/register modal/components ::mf/register-as :tokens/other} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc rotation-modal {::mf/register modal/components ::mf/register-as :tokens/rotation} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc spacing-modal {::mf/register modal/components ::mf/register-as :tokens/spacing} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc string-modal {::mf/register modal/components ::mf/register-as :tokens/string} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) (mf/defc typography-modal {::mf/register modal/components ::mf/register-as :tokens/typography} [properties] - [:& tokens-properties-form properties]) + [:& modal properties]) diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.scss b/frontend/src/app/main/ui/workspace/tokens/modals.scss new file mode 100644 index 000000000..c0e045448 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/modals.scss @@ -0,0 +1,16 @@ +// 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 + +@import "refactor/common-refactor.scss"; + +.shadow { + @extend .modal-container-base; + @include menuShadow; + position: absolute; + z-index: 11; + overflow-y: auto; + overflow-x: hidden; +} From f00ac72fbea75f282f2c9791d6b3f7ff49b44ccf Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 10:10:45 +0200 Subject: [PATCH 09/51] Revert to use-var --- .../app/main/ui/workspace/tokens/form.cljs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 5ba500d9b..dcda447f4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -35,6 +35,9 @@ (me/humanize)) nil) +(defn finalize-name [name] + (str/trim name)) + (mf/defc form {::mf/wrap-props false} [{:keys [token] :as _args}] @@ -52,22 +55,23 @@ :description ""} token)) state @state* + _ (js/console.log "render") ;; Name - finalize-name str/trim + name (mf/use-var (or (:name token) "")) + name-errors (mf/use-state nil) name-schema (mf/use-memo (mf/deps existing-token-names) - (fn [] - (token-name-schema existing-token-names))) + #(token-name-schema existing-token-names)) on-update-name (fn [e] (let [value (dom/get-target-val e) errors (->> (finalize-name value) (m/explain name-schema))] - (swap! state* merge {:name value - :errors/name errors}))) + (reset! name value) + (reset! name-errors errors))) disabled? (or - (empty? (finalize-name (:name state))) - (:errors/name state))] + @name-errors + (empty? (finalize-name (:name state))))] ;; on-update-name (fn [e] ;; (let [{:keys [errors] :as state} (mf/deref state*) @@ -101,12 +105,13 @@ [:div {:class (stl/css :token-rows)} [:div [:& tokens.common/labeled-input {:label "Name" - :error? (:errors/name state) + :error? @name-errors :input-props {:default-value (:name state) :auto-focus true :on-change on-update-name}}] - (when-let [errors (:errors/name state)] - [:p {:class (stl/css :error)} (me/humanize errors)])] + (when @name-errors + [:p {:class (stl/css :error)} + (me/humanize @name-errors)])] [:& tokens.common/labeled-input {:label "Value" :input-props {:default-value (:value state) #_#_:on-change #(on-update-field idx %)}}] From 35ee732701d40073901339a0e698a33c7db261f9 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 14:59:08 +0200 Subject: [PATCH 10/51] Debounced update of resolved value --- .../app/main/ui/workspace/tokens/form.cljs | 45 ++++++++++--------- .../ui/workspace/tokens/style_dictionary.cljs | 35 +++++++++++++++ 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index dcda447f4..58f7c722c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -15,6 +15,8 @@ [malli.error :as me] [rumext.v2 :as mf])) +;; Schemas --------------------------------------------------------------------- + (defn token-name-schema "Generate a dynamic schema validation to check if a token name already exists. `existing-token-names` should be a set of strings." @@ -35,9 +37,14 @@ (me/humanize)) nil) +;; Helpers --------------------------------------------------------------------- + (defn finalize-name [name] (str/trim name)) +(defn finalize-value [name] + (str/trim name)) + (mf/defc form {::mf/wrap-props false} [{:keys [token] :as _args}] @@ -71,22 +78,18 @@ (reset! name-errors errors))) disabled? (or @name-errors - (empty? (finalize-name (:name state))))] - - ;; on-update-name (fn [e] - ;; (let [{:keys [errors] :as state} (mf/deref state*) - ;; value (-> (dom/get-target-val e) - ;; (str/trim))] - ;; (cond-> @state* - ;; ;; Remove existing name errors - ;; :always (update :errors set/difference #{:empty}) - ;; (str/empty?) (conj)) - ;; (swap! state* assoc :name (dom/get-target-val e)))) - ;; on-update-description #(swap! state* assoc :description (dom/get-target-val %)) - ;; on-update-field (fn [idx e] - ;; (let [value (dom/get-target-val e)] - ;; (swap! state* assoc-in [idx :value] value))) + (empty? (finalize-name (:name state)))) + ;; Value + value (mf/use-var (or (:value token) "")) + resolved-value (mf/use-state nil) + set-resolve-value (mf/use-callback + (fn [token] + (js/console.log "token" (:resolved-value token)) + (when (:resolved-value token) + (reset! resolved-value (:resolved-value token))))) + value-errors (mf/use-state nil) + on-update-value (sd/use-debonced-resolve-callback tokens set-resolve-value)] ;; on-submit (fn [e] ;; (dom/prevent-default e) @@ -106,19 +109,17 @@ [:div [:& tokens.common/labeled-input {:label "Name" :error? @name-errors - :input-props {:default-value (:name state) + :input-props {:default-value @name :auto-focus true :on-change on-update-name}}] (when @name-errors [:p {:class (stl/css :error)} (me/humanize @name-errors)])] [:& tokens.common/labeled-input {:label "Value" - :input-props {:default-value (:value state) - #_#_:on-change #(on-update-field idx %)}}] - ;; (when (and @resolved-value - ;; (not= @resolved-value (:value (first @state*)))) - ;; [:div {:class (stl/css :resolved-value)} - ;; [:p @resolved-value]]) + :input-props {:default-value @value + :on-change on-update-value}}] + [:div {:class (stl/css :resolved-value)} + [:p @resolved-value]] [:& tokens.common/labeled-input {:label "Description" :input-props {:default-value (:description state) #_#_:on-change #(on-update-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 f1e1c7a7d..c87c9dcc0 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -4,6 +4,7 @@ ["style-dictionary$default" :as sd] [app.common.data :as d] [app.main.refs :as refs] + [app.util.dom :as dom] [cuerdas.core :as str] [promesa.core :as p] [rumext.v2 :as mf] @@ -77,7 +78,9 @@ (defn resolve-tokens+ [tokens & {:keys [debug?] :as config}] (p/let [sd-tokens (-> (tokens-name-map tokens) + (doto js/console.log) (resolve-sd-tokens+ config))] + (js/console.log "sd-tokens" sd-tokens) (let [resolved-tokens (reduce (fn [acc ^js cur] (let [value (.-value cur) @@ -101,6 +104,38 @@ ;; Hooks ----------------------------------------------------------------------- +(defn use-debonced-resolve-callback + [tokens on-success & {:keys [cached timeout] + :or {cached {} + timeout 500}}] + (let [id-ref (mf/use-ref nil) + cache (mf/use-ref cached) + debounced-resolver-callback + (mf/use-callback + (mf/deps on-success tokens) + (fn [event] + (let [input (dom/get-target-val event) + id (js/Symbol)] + (mf/set-ref-val! id-ref id) + (js/setTimeout + (fn [] + (when (= (mf/ref-val id-ref) id) + (if-let [cached (-> (mf/ref-val cache) + (get tokens))] + (on-success cached) + (let [token-id (random-uuid) + new-tokens (assoc tokens token-id {:id token-id + :value input + :name "TEMP"})] + (-> (resolve-tokens+ new-tokens) + (p/catch js/console.error) + (p/then (fn [resolved-tokens] + (mf/set-ref-val! cache (assoc (mf/ref-val cache) tokens resolved-tokens)) + (when (= (mf/ref-val id-ref) id) + (on-success (get resolved-tokens token-id)))))))))) + timeout))))] + debounced-resolver-callback)) + (defonce !tokens-cache (atom nil)) (defn use-resolved-tokens From 53f01ef46c0b63a48f37d11fe2f96db563466571 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 15:04:30 +0200 Subject: [PATCH 11/51] Use input as cache key --- .../main/ui/workspace/tokens/style_dictionary.cljs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 c87c9dcc0..9155b2c0b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -108,18 +108,18 @@ [tokens on-success & {:keys [cached timeout] :or {cached {} timeout 500}}] - (let [id-ref (mf/use-ref nil) + (let [timeout-id-ref (mf/use-ref nil) cache (mf/use-ref cached) debounced-resolver-callback (mf/use-callback (mf/deps on-success tokens) (fn [event] (let [input (dom/get-target-val event) - id (js/Symbol)] - (mf/set-ref-val! id-ref id) + timeout-id (js/Symbol)] + (mf/set-ref-val! timeout-id-ref timeout-id) (js/setTimeout (fn [] - (when (= (mf/ref-val id-ref) id) + (when (= (mf/ref-val timeout-id-ref) timeout-id) (if-let [cached (-> (mf/ref-val cache) (get tokens))] (on-success cached) @@ -130,8 +130,8 @@ (-> (resolve-tokens+ new-tokens) (p/catch js/console.error) (p/then (fn [resolved-tokens] - (mf/set-ref-val! cache (assoc (mf/ref-val cache) tokens resolved-tokens)) - (when (= (mf/ref-val id-ref) id) + (mf/set-ref-val! cache (assoc (mf/ref-val cache) input resolved-tokens)) + (when (= (mf/ref-val timeout-id-ref) timeout-id) (on-success (get resolved-tokens token-id)))))))))) timeout))))] debounced-resolver-callback)) From 8db47b58775fde1fe7fd567fbc26ea10ffc58979 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 15:09:46 +0200 Subject: [PATCH 12/51] Use initial value --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 58f7c722c..ecc26f6c1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -82,10 +82,9 @@ ;; Value value (mf/use-var (or (:value token) "")) - resolved-value (mf/use-state nil) + resolved-value (mf/use-state (get-in tokens [(:id token) :resolved-value])) set-resolve-value (mf/use-callback (fn [token] - (js/console.log "token" (:resolved-value token)) (when (:resolved-value token) (reset! resolved-value (:resolved-value token))))) value-errors (mf/use-state nil) From 941fb041b62f33a6c80e55635eba17b346a49a7d Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 17:00:00 +0200 Subject: [PATCH 13/51] Add form styling --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 11 +++++++++-- frontend/src/app/main/ui/workspace/tokens/form.scss | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index ecc26f6c1..ba0abee6e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -117,8 +117,15 @@ [:& tokens.common/labeled-input {:label "Value" :input-props {:default-value @value :on-change on-update-value}}] - [:div {:class (stl/css :resolved-value)} - [:p @resolved-value]] + [:div {:class (stl/css-case :resolved-value true + :resolved-value-placeholder (nil? @token-resolve-result) + :resolved-value-error (when (keyword? @token-resolve-result) + (= (namespace @token-resolve-result) "error")))} + (case @token-resolve-result + :error/token-self-reference "Token has self reference" + :error/token-missing-reference "Token has missing reference" + nil "Enter token value" + [:p @token-resolve-result])] [:& tokens.common/labeled-input {:label "Description" :input-props {:default-value (:description state) #_#_:on-change #(on-update-description %)}}] diff --git a/frontend/src/app/main/ui/workspace/tokens/form.scss b/frontend/src/app/main/ui/workspace/tokens/form.scss index df3fc5a88..cb67c9001 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.scss +++ b/frontend/src/app/main/ui/workspace/tokens/form.scss @@ -30,6 +30,7 @@ @include bodySmallTypography; padding: $s-4 $s-6; font-weight: medium; + height: $s-24; color: var(--color-foreground-primary); border: 1px solid color-mix(in hsl, var(--color-foreground-secondary) 30%, transparent); @@ -39,3 +40,11 @@ margin: 0; } } + +.resolved-value-placeholder { + color: var(--color-foreground-secondary); +} + +.resolved-value-error { + color: var(--status-color-error-500); +} From 1dcdddb2dbc9d0059b2c6297fd1eb451020c00c8 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 17:00:45 +0200 Subject: [PATCH 14/51] Check for self references --- .../src/app/main/ui/workspace/tokens/form.cljs | 14 ++++++++------ .../ui/workspace/tokens/style_dictionary.cljs | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index ba0abee6e..0e71f6d04 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -82,13 +82,15 @@ ;; Value value (mf/use-var (or (:value token) "")) - resolved-value (mf/use-state (get-in tokens [(:id token) :resolved-value])) + token-resolve-result (mf/use-state (get-in tokens [(:id token) :resolved-value])) set-resolve-value (mf/use-callback - (fn [token] - (when (:resolved-value token) - (reset! resolved-value (:resolved-value token))))) - value-errors (mf/use-state nil) - on-update-value (sd/use-debonced-resolve-callback tokens set-resolve-value)] + (fn [token-or-err] + (let [value (cond + (= token-or-err :error/token-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-submit (fn [e] ;; (dom/prevent-default e) 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 9155b2c0b..bc34925c5 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -23,6 +23,21 @@ ;; Functions ------------------------------------------------------------------- +(defn token-self-reference? [token-name reference-string] + (let [escaped-name (str/replace token-name "." "\\.") + regex (-> (str "{" escaped-name "}") + (re-pattern))] + (re-find regex reference-string))) + +(comment + (token-self-reference? {:name "some.value"} "{md} + {some.value}") + (token-self-reference? {:name "some.value"} "some.value") + (token-self-reference? {:name "some.value"} "{some|value}") + (token-self-reference? {:name "sm"} "{md} + {lg}") + (token-self-reference? {:name "sm"} "1") + (token-self-reference? {:name ""} "121") + nil) + (defn tokens->style-dictionary+ "Resolves references and math expressions using StyleDictionary. Returns a promise with the resolved dictionary." From dd62c7fe18cf0d1e296638bf1403f1819dbd1057 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 17:00:56 +0200 Subject: [PATCH 15/51] Give new tokens without a name a temporary hardcoded string --- .../ui/workspace/tokens/style_dictionary.cljs | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) 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 bc34925c5..a0062bb7c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -119,15 +119,18 @@ ;; Hooks ----------------------------------------------------------------------- +(def new-token-temp-name + "TOKEN_STUDIO_SYSTEM.TEMP") + (defn use-debonced-resolve-callback - [tokens on-success & {:keys [cached timeout] - :or {cached {} - timeout 500}}] + [name-ref token tokens callback & {:keys [cached timeout] + :or {cached {} + timeout 500}}] (let [timeout-id-ref (mf/use-ref nil) cache (mf/use-ref cached) debounced-resolver-callback (mf/use-callback - (mf/deps on-success tokens) + (mf/deps token callback tokens) (fn [event] (let [input (dom/get-target-val event) timeout-id (js/Symbol)] @@ -135,19 +138,33 @@ (js/setTimeout (fn [] (when (= (mf/ref-val timeout-id-ref) timeout-id) - (if-let [cached (-> (mf/ref-val cache) - (get tokens))] - (on-success cached) - (let [token-id (random-uuid) - new-tokens (assoc tokens token-id {:id token-id - :value input - :name "TEMP"})] - (-> (resolve-tokens+ new-tokens) - (p/catch js/console.error) - (p/then (fn [resolved-tokens] - (mf/set-ref-val! cache (assoc (mf/ref-val cache) input resolved-tokens)) - (when (= (mf/ref-val timeout-id-ref) timeout-id) - (on-success (get resolved-tokens token-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)) From 39eb964cb774489955a58b3d1b012577592f4938 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 17:18:56 +0200 Subject: [PATCH 16/51] Reduce debounce timeout --- frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a0062bb7c..3c38675f4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -125,7 +125,7 @@ (defn use-debonced-resolve-callback [name-ref token tokens callback & {:keys [cached timeout] :or {cached {} - timeout 500}}] + timeout 160}}] (let [timeout-id-ref (mf/use-ref nil) cache (mf/use-ref cached) debounced-resolver-callback From 910485008fc603a00cf999b1af3a8e6edd03a83c Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 17:19:13 +0200 Subject: [PATCH 17/51] Fix name not updating button --- .../app/main/ui/workspace/tokens/form.cljs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 0e71f6d04..ffe3dbaa0 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.tokens.form (:require-macros [app.main.style :as stl]) (:require + ["lodash.debounce" :as debounce] [app.main.ui.workspace.tokens.common :as tokens.common] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.util.dom :as dom] @@ -64,21 +65,24 @@ state @state* _ (js/console.log "render") + form-touched (mf/use-state nil) + update-form-touched (mf/use-callback + (debounce #(reset! form-touched (js/Symbol)) 120)) + ;; Name name (mf/use-var (or (:name token) "")) name-errors (mf/use-state nil) name-schema (mf/use-memo (mf/deps existing-token-names) #(token-name-schema existing-token-names)) - on-update-name (fn [e] - (let [value (dom/get-target-val e) - errors (->> (finalize-name value) - (m/explain name-schema))] - (reset! name value) - (reset! name-errors errors))) - disabled? (or - @name-errors - (empty? (finalize-name (:name state)))) + on-update-name (mf/use-callback + (debounce (fn [e] + (let [value (dom/get-target-val e) + errors (->> (finalize-name value) + (m/explain name-schema))] + (mf/set-ref-val! name value) + (reset! name-errors errors) + (update-form-touched))))) ;; Value value (mf/use-var (or (:value token) "")) From 598e4d36fc337235c38e5b0ad0662f15dbebe9a3 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 17:19:59 +0200 Subject: [PATCH 18/51] Disable on value error --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index ffe3dbaa0..cc3e3f3a3 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -94,7 +94,14 @@ (= 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 (sd/use-debonced-resolve-callback name token tokens set-resolve-value) + value-error? (when (keyword? @token-resolve-result) + (= (namespace @token-resolve-result) "error")) + + disabled? (or + @name-errors + value-error? + (empty? (finalize-name (mf/ref-val name))))] ;; on-submit (fn [e] ;; (dom/prevent-default e) @@ -125,8 +132,7 @@ :on-change on-update-value}}] [:div {:class (stl/css-case :resolved-value true :resolved-value-placeholder (nil? @token-resolve-result) - :resolved-value-error (when (keyword? @token-resolve-result) - (= (namespace @token-resolve-result) "error")))} + :resolved-value-error value-error?)} (case @token-resolve-result :error/token-self-reference "Token has self reference" :error/token-missing-reference "Token has missing reference" From 59780a9d4d4362df8e54ec728ebf7c497e156021 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 19:40:03 +0200 Subject: [PATCH 19/51] Add token finding function --- .../main/ui/workspace/tokens/style_dictionary.cljs | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 3c38675f4..0d9560bbe 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -23,6 +23,18 @@ ;; Functions ------------------------------------------------------------------- +(defn find-token-references + "Finds token reference values in `str` and returns a set with all contained namespaces." + [str] + (some->> (re-seq #"\{([^}]*)\}" str) + (map second) + (into #{}))) + +(comment + (find-token-references "{foo} + {bar}") + (find-token-references "nothing") + nil) + (defn token-self-reference? [token-name reference-string] (let [escaped-name (str/replace token-name "." "\\.") regex (-> (str "{" escaped-name "}") From 5f25bd8a7bf46175a97dd3976f129d519f0560a1 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Fri, 21 Jun 2024 19:41:04 +0200 Subject: [PATCH 20/51] Add comments --- .../src/app/main/ui/workspace/tokens/style_dictionary.cljs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 0d9560bbe..b81d43121 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -32,7 +32,11 @@ (comment (find-token-references "{foo} + {bar}") - (find-token-references "nothing") + ;; => #{"foo" "bar"} + (find-token-references "{foo.bar.baz} + something") + ;; => #{"foo.bar.baz"} + (find-token-references "1 + 2") + ;; => nil nil) (defn token-self-reference? [token-name reference-string] From 5c425141704c4e651baa4ad41083722ed6371d9d Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 09:59:22 +0200 Subject: [PATCH 21/51] Add style dictionary find-token-reference test --- .../ui/workspace/tokens/style_dictionary.cljs | 15 +++------------ .../tokens/style_dictionary_test.cljs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 frontend/test/frontend_tests/tokens/style_dictionary_test.cljs 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 b81d43121..104ee9c2d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -24,21 +24,12 @@ ;; Functions ------------------------------------------------------------------- (defn find-token-references - "Finds token reference values in `str` and returns a set with all contained namespaces." - [str] - (some->> (re-seq #"\{([^}]*)\}" str) + "Finds token reference values in `value-string` and returns a set with all contained namespaces." + [value-string] + (some->> (re-seq #"\{([^}]*)\}" value-string) (map second) (into #{}))) -(comment - (find-token-references "{foo} + {bar}") - ;; => #{"foo" "bar"} - (find-token-references "{foo.bar.baz} + something") - ;; => #{"foo.bar.baz"} - (find-token-references "1 + 2") - ;; => nil - nil) - (defn token-self-reference? [token-name reference-string] (let [escaped-name (str/replace token-name "." "\\.") regex (-> (str "{" escaped-name "}") diff --git a/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs b/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs new file mode 100644 index 000000000..45e561c1a --- /dev/null +++ b/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs @@ -0,0 +1,19 @@ +;; 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 frontend-tests.tokens.style-dictionary-test + (:require + [app.main.ui.workspace.tokens.style-dictionary :as wtsd] + [cljs.test :as t :include-macros true])) + +(t/deftest test-find-token-references + ;; Return references + (t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo} + {bar}"))) + ;; Ignore non reference text + (t/is (= #{"foo.bar.baz"} (wtsd/find-token-references "{foo.bar.baz} + something"))) + ;; No references found + (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}")))) From ab51b433653c5c3f402983738bfab92bf7cc1c9d Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 12:44:05 +0200 Subject: [PATCH 22/51] Add type --- frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 104ee9c2d..7b6393648 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -17,7 +17,7 @@ (do (sd-transforms/registerTransforms sd) (.registerFormat sd #js {:name "custom/json" - :format (fn [res] + :format (fn [^js res] (.-tokens (.-dictionary res)))}) sd)) From 69d9c8e88ff2beba907a8463b5d1b52207ca97d8 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 12:44:29 +0200 Subject: [PATCH 23/51] Add esm test --- frontend/shadow-cljs.edn | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 7a5b4c4ff..538908e74 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -106,8 +106,8 @@ :warnings {:fn-deprecated false}}} :lib-penpot - {:target :esm - :output-dir "resources/public/libs" + {:target :esm + :output-dir "resources/public/libs" :modules {:penpot {:exports {:renderPage app.libs.render/render-page-export @@ -158,5 +158,16 @@ :source-map-detail-level :all :warnings {:fn-deprecated false}}} - }} + :test-esm + {:target :esm-files + :output-dir "target/test-esm" + :ns-regexp "^frontend-tests.tokens.*-test$" + :autorun true + :compiler-options + {:output-feature-set :es2020 + :output-wrapper false + :source-map true + :source-map-include-sources-content true + :source-map-detail-level :all + :warnings {:fn-deprecated false}}}}} From ba6a6059c155c11d32c8a38c720592c3376cd6b7 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 14:07:21 +0200 Subject: [PATCH 24/51] Move to custom ns --- frontend/shadow-cljs.edn | 2 +- .../tokens => token_tests}/style_dictionary_test.cljs | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/test/{frontend_tests/tokens => token_tests}/style_dictionary_test.cljs (100%) diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 538908e74..83b46479a 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -161,7 +161,7 @@ :test-esm {:target :esm-files :output-dir "target/test-esm" - :ns-regexp "^frontend-tests.tokens.*-test$" + :ns-regexp "^token-tests.*-test$" :autorun true :compiler-options diff --git a/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs similarity index 100% rename from frontend/test/frontend_tests/tokens/style_dictionary_test.cljs rename to frontend/test/token_tests/style_dictionary_test.cljs From 10033ead913a8f956c0a2547fef464b697740725 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 14:23:03 +0200 Subject: [PATCH 25/51] Add specific esm testing environment for tokens --- frontend/package.json | 4 ++++ frontend/shadow-cljs.edn | 3 ++- frontend/test/token_tests/style_dictionary_test.cljs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index a373539f9..c4b5b3c95 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,10 @@ "test:run": "node target/tests.cjs", "test:watch": "clojure -M:dev:shadow-cljs watch test", "test": "yarn run test:compile && yarn run test:run", + "token-test:compile": "clojure -M:dev:shadow-cljs compile test-esm --config-merge '{:autorun false}'", + "token-test:run": "bun target/tests-esm.cjs", + "token-test:watch": "clojure -M:dev:shadow-cljs watch test-esm", + "token-test": "yarn run token-test:compile && yarn run token-test:run", "translations:validate": "node ./scripts/validate-translations.js", "translations:find-unused": "node ./scripts/find-unused-translations.js", "compile": "node ./scripts/compile.js", diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 83b46479a..7201b1dcb 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -159,7 +159,8 @@ :warnings {:fn-deprecated false}}} :test-esm - {:target :esm-files + {:target :node-test + :output-to "target/tests-esm.cjs" :output-dir "target/test-esm" :ns-regexp "^token-tests.*-test$" :autorun true diff --git a/frontend/test/token_tests/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs index 45e561c1a..5506cb8d7 100644 --- a/frontend/test/token_tests/style_dictionary_test.cljs +++ b/frontend/test/token_tests/style_dictionary_test.cljs @@ -3,7 +3,7 @@ ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC -(ns frontend-tests.tokens.style-dictionary-test +(ns token-tests.style-dictionary-test (:require [app.main.ui.workspace.tokens.style-dictionary :as wtsd] [cljs.test :as t :include-macros true])) From 111900c1220f43f36d4f31965c15a6aea2f61c18 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 14:23:28 +0200 Subject: [PATCH 26/51] Cleanup --- frontend/test/token_tests/style_dictionary_test.cljs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/test/token_tests/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs index 5506cb8d7..ff03ba16c 100644 --- a/frontend/test/token_tests/style_dictionary_test.cljs +++ b/frontend/test/token_tests/style_dictionary_test.cljs @@ -3,6 +3,7 @@ ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC + (ns token-tests.style-dictionary-test (:require [app.main.ui.workspace.tokens.style-dictionary :as wtsd] From 28f25da9e8e54b9509c77c18574e301e8c64c256 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 14:29:29 +0200 Subject: [PATCH 27/51] Move to tests --- .../ui/workspace/tokens/style_dictionary.cljs | 17 +++-------------- .../test/token_tests/style_dictionary_test.cljs | 5 +++++ 2 files changed, 8 insertions(+), 14 deletions(-) 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 7b6393648..7cdc438d4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -30,20 +30,9 @@ (map second) (into #{}))) -(defn token-self-reference? [token-name reference-string] - (let [escaped-name (str/replace token-name "." "\\.") - regex (-> (str "{" escaped-name "}") - (re-pattern))] - (re-find regex reference-string))) - -(comment - (token-self-reference? {:name "some.value"} "{md} + {some.value}") - (token-self-reference? {:name "some.value"} "some.value") - (token-self-reference? {:name "some.value"} "{some|value}") - (token-self-reference? {:name "sm"} "{md} + {lg}") - (token-self-reference? {:name "sm"} "1") - (token-self-reference? {:name ""} "121") - nil) +(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. diff --git a/frontend/test/token_tests/style_dictionary_test.cljs b/frontend/test/token_tests/style_dictionary_test.cljs index ff03ba16c..8b77745cc 100644 --- a/frontend/test/token_tests/style_dictionary_test.cljs +++ b/frontend/test/token_tests/style_dictionary_test.cljs @@ -18,3 +18,8 @@ (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}")))) From c98162d0bf6a2084ae19bf127ace3ecb2b5c8ed9 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 15:24:22 +0200 Subject: [PATCH 28/51] Move callback function to component --- .../app/main/ui/workspace/tokens/form.cljs | 63 ++++++++++++++++--- .../ui/workspace/tokens/style_dictionary.cljs | 53 ---------------- .../token_tests/style_dictionary_test.cljs | 5 -- 3 files changed, 55 insertions(+), 66 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index cc3e3f3a3..ed8bc02fa 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 7cdc438d4..34b8ccb64 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 8b77745cc..ff03ba16c 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}")))) From e0be30bb79f0d01d11e778d2be0d890246c8e729 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Mon, 24 Jun 2024 15:58:19 +0200 Subject: [PATCH 29/51] Dont show error when unfocusing name input field, but keep form disabled --- .../src/app/main/ui/workspace/tokens/form.cljs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index ed8bc02fa..e86a6896a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -39,8 +39,14 @@ (defn finalize-name [name] (str/trim name)) -(defn finalize-value [name] - (str/trim name)) +(defn valid-name? [name] + (seq (finalize-name name))) + +(defn finalize-value [value] + (str/trim value)) + +(defn valid-value? [value] + (seq (finalize-value value))) ;; Component ------------------------------------------------------------------- @@ -117,12 +123,17 @@ ;; Name name (mf/use-var (or (:name token) "")) + name-touched? (mf/use-state (some? (:name token))) + on-name-touch (mf/use-callback + #(when (valid-name? (dom/get-target-val %)) + (reset! name-touched? true))) name-errors (mf/use-state nil) name-schema (mf/use-memo (mf/deps existing-token-names) #(token-name-schema existing-token-names)) on-update-name (mf/use-callback (debounce (fn [e] + (on-name-touch e) (let [value (dom/get-target-val e) errors (->> (finalize-name value) (m/explain name-schema))] @@ -147,7 +158,7 @@ disabled? (or @name-errors value-error? - (empty? (finalize-name (mf/ref-val name))))] + (not @name-touched?))] ;; on-submit (fn [e] ;; (dom/prevent-default e) @@ -169,6 +180,7 @@ :error? @name-errors :input-props {:default-value @name :auto-focus true + :on-blur on-name-touch :on-change on-update-name}}] (when @name-errors [:p {:class (stl/css :error)} From d2bdc6c6249a6e525440caa7e89f94769a38029c Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 10:40:31 +0200 Subject: [PATCH 30/51] Fix ref logic --- .../app/main/ui/workspace/tokens/form.cljs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index e86a6896a..b39067f3d 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -40,13 +40,15 @@ (str/trim name)) (defn valid-name? [name] - (seq (finalize-name name))) + (seq (finalize-name (str name)))) (defn finalize-value [value] (str/trim value)) (defn valid-value? [value] - (seq (finalize-value value))) + (-> (str value) + (finalize-value) + (seq))) ;; Component ------------------------------------------------------------------- @@ -122,43 +124,43 @@ (debounce #(reset! form-touched (js/Symbol)) 120)) ;; Name - name (mf/use-var (or (:name token) "")) - name-touched? (mf/use-state (some? (:name token))) - on-name-touch (mf/use-callback - #(when (valid-name? (dom/get-target-val %)) - (reset! name-touched? true))) + name-ref (mf/use-var (:name token)) name-errors (mf/use-state nil) name-schema (mf/use-memo (mf/deps existing-token-names) #(token-name-schema existing-token-names)) on-update-name (mf/use-callback (debounce (fn [e] - (on-name-touch e) (let [value (dom/get-target-val e) errors (->> (finalize-name value) (m/explain name-schema))] - (mf/set-ref-val! name value) + (reset! name-ref value) (reset! name-errors errors) (update-form-touched))))) + valid-name-field? (and + (not @name-errors) + (valid-name? @name-ref)) ;; Value - value (mf/use-var (or (:value token) "")) + value-ref (mf/use-var (:value token)) token-resolve-result (mf/use-state (get-in tokens [(:id token) :resolved-value])) set-resolve-value (mf/use-callback (fn [token-or-err] - (let [value (cond - (= 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 (use-debonced-resolve-callback name token tokens set-resolve-value) + (let [v (cond + (= 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! value-ref v) + (reset! token-resolve-result v)))) + on-update-value (use-debonced-resolve-callback name-ref token tokens set-resolve-value) value-error? (when (keyword? @token-resolve-result) (= (namespace @token-resolve-result) "error")) + valid-value-field? (and + (not value-error?) + (valid-value? (or @token-resolve-result @value-ref))) - disabled? (or - @name-errors - value-error? - (not @name-touched?))] + disabled? (or (not valid-name-field?) + (not valid-value-field?))] ;; on-submit (fn [e] ;; (dom/prevent-default e) @@ -178,15 +180,16 @@ [:div [:& tokens.common/labeled-input {:label "Name" :error? @name-errors - :input-props {:default-value @name + :input-props {:default-value @name-ref :auto-focus true - :on-blur on-name-touch + :on-blur on-update-name :on-change on-update-name}}] (when @name-errors [:p {:class (stl/css :error)} (me/humanize @name-errors)])] [:& tokens.common/labeled-input {:label "Value" - :input-props {:default-value @value + :input-props {:default-value @value-ref + :on-blur on-update-value :on-change on-update-value}}] [:div {:class (stl/css-case :resolved-value true :resolved-value-placeholder (nil? @token-resolve-result) From ca98747dea38dc198672c4b5eb154ac54a7cfeff Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:02:21 +0200 Subject: [PATCH 31/51] Add description with schema --- .../app/main/ui/workspace/tokens/form.cljs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index b39067f3d..2d72057db 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -34,6 +34,10 @@ [:string {:min 1 :max 255}] non-existing-token-schema]))) +(def token-description-schema + (m/schema + [:string {:max 2048}])) + ;; Helpers --------------------------------------------------------------------- (defn finalize-name [name] @@ -159,8 +163,22 @@ (not value-error?) (valid-value? (or @token-resolve-result @value-ref))) + ;; Description + description-ref (mf/use-var (:description token)) + description-errors (mf/use-state nil) + on-update-description (mf/use-callback + (debounce (fn [e] + (let [value (dom/get-target-val e) + errors (m/explain token-description-schema value)] + (reset! description-ref value) + (reset! description-errors errors) + (update-form-touched))))) + valid-description-field? (not @description-errors) + + ;; Form disabled? (or (not valid-name-field?) - (not valid-value-field?))] + (not valid-value-field?) + (not valid-description-field?))] ;; on-submit (fn [e] ;; (dom/prevent-default e) @@ -200,9 +218,13 @@ :error/unknown-error "" nil "Enter token value" [:p @token-resolve-result])] - [:& tokens.common/labeled-input {:label "Description" - :input-props {:default-value (:description state) - #_#_:on-change #(on-update-description %)}}] + [:div + [:& tokens.common/labeled-input {:label "Description" + :input-props {:default-value @description-ref + :on-change on-update-description}}] + (when @description-errors + [:p {:class (stl/css :error)} + (me/humanize @description-errors)])] [:div {:class (stl/css :button-row)} [:button {:class (stl/css :button) :type "submit" From 33131fa943117a738d2954a213d38c5154341d42 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:15:43 +0200 Subject: [PATCH 32/51] Restore token saving --- .../app/main/ui/workspace/tokens/form.cljs | 40 ++++++++++--------- .../app/main/ui/workspace/tokens/modals.cljs | 5 ++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 2d72057db..205d5c98b 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -8,6 +8,9 @@ (:require-macros [app.main.style :as stl]) (:require ["lodash.debounce" :as debounce] + [app.main.data.modal :as modal] + [app.main.data.tokens :as dt] + [app.main.store :as st] [app.main.ui.workspace.tokens.common :as tokens.common] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.util.dom :as dom] @@ -47,12 +50,11 @@ (seq (finalize-name (str name)))) (defn finalize-value [value] - (str/trim value)) + (-> (str value) + (str/trim))) (defn valid-value? [value] - (-> (str value) - (finalize-value) - (seq))) + (seq (finalize-value value))) ;; Component ------------------------------------------------------------------- @@ -107,7 +109,7 @@ (mf/defc form {::mf/wrap-props false} - [{:keys [token] :as _args}] + [{:keys [token token-type] :as _args}] (let [tokens (sd/use-resolved-workspace-tokens) existing-token-names (mf/use-memo (mf/deps tokens) @@ -178,22 +180,22 @@ ;; Form disabled? (or (not valid-name-field?) (not valid-value-field?) - (not valid-description-field?))] + (not valid-description-field?)) - ;; on-submit (fn [e] - ;; (dom/prevent-default e) - ;; (let [token-value (-> (fields->map state) - ;; (first) - ;; (val)) - ;; token (cond-> {:name (:name state) - ;; :type (or (:type token) token-type) - ;; :value token-value - ;; :description (:description state)} - ;; (:id token) (assoc :id (:id token)))] - ;; (st/emit! (dt/add-token token)) - ;; (modal/hide!)))] + on-submit (mf/use-callback + (fn [e] + (js/console.log "@value-ref" @value-ref (finalize-value @value-ref)) + (dom/prevent-default e) + (let [token (cond-> {:name (finalize-name @name-ref) + :type (or (:type token) token-type) + :value (finalize-value @value-ref)} + @description-ref (assoc :description @description-ref) + (:id token) (assoc :id (:id token)))] + (js/console.log "token" token) + (st/emit! (dt/add-token token)) + (modal/hide!))))] [:form - {#_#_:on-submit on-submit} + {:on-submit on-submit} [:div {:class (stl/css :token-rows)} [:div [:& tokens.common/labeled-input {:label "Name" diff --git a/frontend/src/app/main/ui/workspace/tokens/modals.cljs b/frontend/src/app/main/ui/workspace/tokens/modals.cljs index eba26a68e..23edf75ed 100644 --- a/frontend/src/app/main/ui/workspace/tokens/modals.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/modals.cljs @@ -39,12 +39,13 @@ (mf/defc modal {::mf/wrap-props false} - [{:keys [x y position token] :as _args}] + [{:keys [x y position token token-type] :as _args}] (let [wrapper-style (use-viewport-position-style x y position)] [:div {:class (stl/css :shadow) :style wrapper-style} - [:& form {:token token}]])) + [:& form {:token token + :token-type token-type}]])) ;; Modals ---------------------------------------------------------------------- From 05f6cfc4b0b4dff49fe9fac0d8353db8b4cd9566 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:30:45 +0200 Subject: [PATCH 33/51] Remove unneeded state --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 205d5c98b..e1a20dc7a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -118,13 +118,6 @@ ;; Allow setting token to already used name (disj (:name token))))) - ;; State - state* (mf/use-state (merge {:name "" - :value "" - :description ""} - token)) - state @state* - form-touched (mf/use-state nil) update-form-touched (mf/use-callback (debounce #(reset! form-touched (js/Symbol)) 120)) From eac7d9288bf5aa63fd725f0f026b46fc02d525b5 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:30:53 +0200 Subject: [PATCH 34/51] Fix on-submit taking old ref-values when user submits before errors have been validated --- .../app/main/ui/workspace/tokens/form.cljs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index e1a20dc7a..dfe3e419e 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -128,14 +128,18 @@ name-schema (mf/use-memo (mf/deps existing-token-names) #(token-name-schema existing-token-names)) + on-update-name-debounced (mf/use-callback + (debounce (fn [e] + (let [value (dom/get-target-val e) + errors (->> (finalize-name value) + (m/explain name-schema))] + (reset! name-errors errors) + (update-form-touched))))) on-update-name (mf/use-callback - (debounce (fn [e] - (let [value (dom/get-target-val e) - errors (->> (finalize-name value) - (m/explain name-schema))] - (reset! name-ref value) - (reset! name-errors errors) - (update-form-touched))))) + (mf/deps on-update-name-debounced) + (fn [e] + (reset! name-ref (dom/get-target-val e)) + (on-update-name-debounced e))) valid-name-field? (and (not @name-errors) (valid-name? @name-ref)) @@ -149,25 +153,33 @@ (= 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! value-ref v) (reset! token-resolve-result v)))) - on-update-value (use-debonced-resolve-callback name-ref token tokens set-resolve-value) + on-update-value-debounced (use-debonced-resolve-callback name-ref token tokens set-resolve-value) + on-update-value (mf/use-callback + (mf/deps on-update-value-debounced) + (fn [e] + (reset! value-ref (dom/get-target-val e)) + (on-update-value-debounced e))) value-error? (when (keyword? @token-resolve-result) (= (namespace @token-resolve-result) "error")) valid-value-field? (and (not value-error?) - (valid-value? (or @token-resolve-result @value-ref))) + (valid-value? @token-resolve-result)) ;; Description description-ref (mf/use-var (:description token)) description-errors (mf/use-state nil) + on-update-description-debounced (mf/use-callback + (debounce (fn [e] + (let [value (dom/get-target-val e) + errors (m/explain token-description-schema value)] + (reset! description-errors errors) + (update-form-touched))))) on-update-description (mf/use-callback - (debounce (fn [e] - (let [value (dom/get-target-val e) - errors (m/explain token-description-schema value)] - (reset! description-ref value) - (reset! description-errors errors) - (update-form-touched))))) + (mf/deps on-update-description-debounced) + (fn [e] + (reset! description-ref (dom/get-target-val e)) + (on-update-description-debounced e))) valid-description-field? (not @description-errors) ;; Form From 5df0cf545e404923bf1912e30d7d37d24adc936b Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:33:18 +0200 Subject: [PATCH 35/51] Remove form-touched work-around --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index dfe3e419e..93134d50a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -118,10 +118,6 @@ ;; Allow setting token to already used name (disj (:name token))))) - form-touched (mf/use-state nil) - update-form-touched (mf/use-callback - (debounce #(reset! form-touched (js/Symbol)) 120)) - ;; Name name-ref (mf/use-var (:name token)) name-errors (mf/use-state nil) @@ -133,8 +129,7 @@ (let [value (dom/get-target-val e) errors (->> (finalize-name value) (m/explain name-schema))] - (reset! name-errors errors) - (update-form-touched))))) + (reset! name-errors errors))))) on-update-name (mf/use-callback (mf/deps on-update-name-debounced) (fn [e] @@ -173,8 +168,7 @@ (debounce (fn [e] (let [value (dom/get-target-val e) errors (m/explain token-description-schema value)] - (reset! description-errors errors) - (update-form-touched))))) + (reset! description-errors errors))))) on-update-description (mf/use-callback (mf/deps on-update-description-debounced) (fn [e] @@ -189,14 +183,12 @@ on-submit (mf/use-callback (fn [e] - (js/console.log "@value-ref" @value-ref (finalize-value @value-ref)) (dom/prevent-default e) (let [token (cond-> {:name (finalize-name @name-ref) :type (or (:type token) token-type) :value (finalize-value @value-ref)} @description-ref (assoc :description @description-ref) (:id token) (assoc :id (:id token)))] - (js/console.log "token" token) (st/emit! (dt/add-token token)) (modal/hide!))))] [:form From d0f8e9612a9d9c8e004b336030e9b51b57bab669 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:52:39 +0200 Subject: [PATCH 36/51] Validate name before submitting --- .../app/main/ui/workspace/tokens/form.cljs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 93134d50a..4f1a41320 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -121,14 +121,15 @@ ;; Name name-ref (mf/use-var (:name token)) name-errors (mf/use-state nil) - name-schema (mf/use-memo - (mf/deps existing-token-names) - #(token-name-schema existing-token-names)) + validate-name (mf/use-callback + (mf/deps existing-token-names) + (fn [value] + (let [schema (token-name-schema existing-token-names)] + (m/explain schema (finalize-name value))))) on-update-name-debounced (mf/use-callback (debounce (fn [e] (let [value (dom/get-target-val e) - errors (->> (finalize-name value) - (m/explain name-schema))] + errors (validate-name value)] (reset! name-errors errors))))) on-update-name (mf/use-callback (mf/deps on-update-name-debounced) @@ -184,13 +185,18 @@ on-submit (mf/use-callback (fn [e] (dom/prevent-default e) - (let [token (cond-> {:name (finalize-name @name-ref) - :type (or (:type token) token-type) - :value (finalize-value @value-ref)} - @description-ref (assoc :description @description-ref) - (:id token) (assoc :id (:id token)))] - (st/emit! (dt/add-token token)) - (modal/hide!))))] + (let [name (finalize-name @name-ref) + ;; Validate form before submitting + ;; As the form might still be evaluating due to debounce and async form state + invalid-form? (or (:errors (validate-name name)))] + (when-not invalid-form? + (let [token (cond-> {:name (finalize-name @name-ref) + :type (or (:type token) token-type) + :value (finalize-value @value-ref)} + @description-ref (assoc :description @description-ref) + (:id token) (assoc :id (:id token)))] + (st/emit! (dt/add-token token)) + (modal/hide!))))))] [:form {:on-submit on-submit} [:div {:class (stl/css :token-rows)} From 6e9623153c1eb9408bd2f433c4872c5c6d016567 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 11:55:57 +0200 Subject: [PATCH 37/51] Remove caching layer for now --- .../app/main/ui/workspace/tokens/form.cljs | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 4f1a41320..1d045e7a9 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -59,11 +59,8 @@ ;; Component ------------------------------------------------------------------- (defn use-debonced-resolve-callback - [name-ref token tokens callback & {:keys [cached timeout] - :or {cached {} - timeout 160}}] + [name-ref token tokens callback & {:keys [timeout] :or {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) @@ -76,33 +73,29 @@ (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))))))))))))) + (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 (callback resolved-token) + (= #{:style-dictionary/missing-reference} errors) (callback :error/token-missing-reference) + :else (callback :error/unknown-error)))))))))))) timeout))))] debounced-resolver-callback)) From eb123bf8efe31af1e479f572cef703ffb74d2c6e Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 12:15:14 +0200 Subject: [PATCH 38/51] Extract token validation --- .../app/main/ui/workspace/tokens/form.cljs | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 1d045e7a9..f4d203ea4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -58,6 +58,29 @@ ;; Component ------------------------------------------------------------------- +(defn validate-token-value+ [{:keys [input name-value token tokens]}] + (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 (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value) + direct-self-reference? (get token-references token-name) + empty-input? (empty? (str/trim input))] + (cond + empty-input? (p/rejected nil) + direct-self-reference? (p/rejected :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/then + (fn [resolved-tokens] + (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)] + (cond + resolved-value (p/resolved resolved-token) + (= #{:style-dictionary/missing-reference} errors) (p/rejected :error/token-missing-reference) + :else (p/rejected :error/unknown-error)))))))))) + (defn use-debonced-resolve-callback [name-ref token tokens callback & {:keys [timeout] :or {timeout 160}}] (let [timeout-id-ref (mf/use-ref nil) @@ -73,30 +96,13 @@ (js/setTimeout (fn [] (when (not (timeout-outdated-cb?)) - (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 (callback resolved-token) - (= #{:style-dictionary/missing-reference} errors) (callback :error/token-missing-reference) - :else (callback :error/unknown-error)))))))))))) - + (-> (validate-token-value+ {:input input + :name-value @name-ref + :token token + :tokens tokens}) + (p/finally (fn [x err] + (when-not (timeout-outdated-cb?) + (callback (or err x)))))))) timeout))))] debounced-resolver-callback)) From b905ff7d2c020bcf9875e85bff0eb8385f656c84 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 14:18:07 +0200 Subject: [PATCH 39/51] Validate forms again on submit --- .../app/main/ui/workspace/tokens/form.cljs | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index f4d203ea4..27f025829 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -56,6 +56,11 @@ (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+ [{:keys [input name-value token tokens]}] @@ -164,10 +169,11 @@ ;; Description description-ref (mf/use-var (:description token)) description-errors (mf/use-state nil) + validate-descripion (mf/use-callback #(m/explain token-description-schema %)) on-update-description-debounced (mf/use-callback (debounce (fn [e] (let [value (dom/get-target-val e) - errors (m/explain token-description-schema value)] + errors (validate-descripion value)] (reset! description-errors errors))))) on-update-description (mf/use-callback (mf/deps on-update-description-debounced) @@ -184,18 +190,26 @@ on-submit (mf/use-callback (fn [e] (dom/prevent-default e) - (let [name (finalize-name @name-ref) - ;; Validate form before submitting - ;; As the form might still be evaluating due to debounce and async form state - invalid-form? (or (:errors (validate-name name)))] - (when-not invalid-form? - (let [token (cond-> {:name (finalize-name @name-ref) - :type (or (:type token) token-type) - :value (finalize-value @value-ref)} - @description-ref (assoc :description @description-ref) - (:id token) (assoc :id (:id token)))] - (st/emit! (dt/add-token token)) - (modal/hide!))))))] + (let [final-name (finalize-name @name-ref) + valid-name+ (-> (validate-name final-name) schema-validation->promise) + final-value (finalize-value @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+ {:input final-value + :name-value final-name + :token token + :tokens tokens})]) + (p/finally (fn [xs err] + (when (and (seq xs) (not err)) + (let [token (cond-> {:name final-name + :type (or (:type token) token-type) + :value final-value} + final-description (assoc :description final-description) + (:id token) (assoc :id (:id token)))] + (st/emit! (dt/add-token token)) + (modal/hide!)))))))))] [:form {:on-submit on-submit} [:div {:class (stl/css :token-rows)} From 2c42ca5a4b0d3198d6ac7c4723ddf65138b76899 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 14:24:20 +0200 Subject: [PATCH 40/51] Cleanup --- frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs | 1 - 1 file changed, 1 deletion(-) 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 34b8ccb64..0b9071a70 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -87,7 +87,6 @@ (p/let [sd-tokens (-> (tokens-name-map tokens) (doto js/console.log) (resolve-sd-tokens+ config))] - (js/console.log "sd-tokens" sd-tokens) (let [resolved-tokens (reduce (fn [acc ^js cur] (let [value (.-value cur) From af374276e40c8e371cbcbf5d4ded27252f41473f Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:30:57 +0200 Subject: [PATCH 41/51] Extract missing reference error check --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 2 +- .../src/app/main/ui/workspace/tokens/style_dictionary.cljs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 27f025829..e58c3b6ea 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -83,7 +83,7 @@ (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)] (cond resolved-value (p/resolved resolved-token) - (= #{:style-dictionary/missing-reference} errors) (p/rejected :error/token-missing-reference) + (sd/missing-reference-error? errors) (p/rejected :error/token-missing-reference) :else (p/rejected :error/unknown-error)))))))))) (defn use-debonced-resolve-callback 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 0b9071a70..9a5a38ee4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -77,6 +77,11 @@ errors) (str/join "\n"))) +(defn missing-reference-error? + [errors] + (and (set? errors) + (get errors :style-dictionary/missing-reference))) + (defn tokens-name-map [tokens] (->> tokens (map (fn [[_ x]] [(:name x) x])) From 135ecf0e3a3688a74efd17c9151edf5867f09718 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:33:07 +0200 Subject: [PATCH 42/51] Cleanup --- .../src/app/main/ui/workspace/tokens/form.cljs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index e58c3b6ea..e51674125 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -63,13 +63,17 @@ ;; Component ------------------------------------------------------------------- -(defn validate-token-value+ [{:keys [input name-value token tokens]}] - (let [token-references (sd/find-token-references input) +(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 [input name-value token tokens]}] + (let [empty-input? (empty? (str/trim input)) + ;; Check if the given value contains a reference that is the current token-name ;; 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 (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value) - direct-self-reference? (get token-references token-name) - empty-input? (empty? (str/trim input))] + token-references (sd/find-token-references input) + direct-self-reference? (get token-references token-name)] (cond empty-input? (p/rejected nil) direct-self-reference? (p/rejected :error/token-direct-self-reference) @@ -87,6 +91,9 @@ :else (p/rejected :error/unknown-error)))))))))) (defn use-debonced-resolve-callback + "Resolves a token values using `StyleDictionary`. + This function is debounced as the resolving might be an expensive calculation. + Uses a custom debouncing logic, as the resolve function is async." [name-ref token tokens callback & {:keys [timeout] :or {timeout 160}}] (let [timeout-id-ref (mf/use-ref nil) debounced-resolver-callback From b89dc759be1541ad7226f37eb6f350f5877d7ccd Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:36:21 +0200 Subject: [PATCH 43/51] Cleanup --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index e51674125..2dba0f70c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -126,7 +126,8 @@ (mf/deps tokens) (fn [] (-> (into #{} (map (fn [[_ {:keys [name]}]] name) tokens)) - ;; Allow setting token to already used name + ;; Remove the currently editing token name, + ;; as we don't want it to show when checking for duplicate names. (disj (:name token))))) ;; Name From 656cc009238e7020835a6c27b015af5e925e78cb Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:38:28 +0200 Subject: [PATCH 44/51] Add missing deps --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 2dba0f70c..001fa70dc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -196,6 +196,7 @@ (not valid-description-field?)) on-submit (mf/use-callback + (mf/deps validate-name validate-descripion token tokens) (fn [e] (dom/prevent-default e) (let [final-name (finalize-name @name-ref) From b924bbc9c70f93697651e60c1a716280b2bc970b Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:39:43 +0200 Subject: [PATCH 45/51] Cleanup --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 001fa70dc..08e78a7fc 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -200,18 +200,18 @@ (fn [e] (dom/prevent-default e) (let [final-name (finalize-name @name-ref) - valid-name+ (-> (validate-name final-name) schema-validation->promise) + valid-name?+ (-> (validate-name final-name) schema-validation->promise) final-value (finalize-value @value-ref) final-description @description-ref - valid-description+ (some-> final-description validate-descripion schema-validation->promise)] - (-> (p/all [valid-name+ - valid-description+ + valid-description?+ (some-> final-description validate-descripion schema-validation->promise)] + (-> (p/all [valid-name?+ + valid-description?+ (validate-token-value+ {:input final-value :name-value final-name :token token :tokens tokens})]) - (p/finally (fn [xs err] - (when (and (seq xs) (not err)) + (p/finally (fn [result err] + (when (and (seq result) (not err)) (let [token (cond-> {:name final-name :type (or (:type token) token-type) :value final-value} From b4d1ef3fc7bb329637f603580f6c5f282a96923b Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:40:48 +0200 Subject: [PATCH 46/51] Cleanup --- frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs | 1 - 1 file changed, 1 deletion(-) 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 9a5a38ee4..d9d3c4a0f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -90,7 +90,6 @@ (defn resolve-tokens+ [tokens & {:keys [debug?] :as config}] (p/let [sd-tokens (-> (tokens-name-map tokens) - (doto js/console.log) (resolve-sd-tokens+ config))] (let [resolved-tokens (reduce (fn [acc ^js cur] From e1b683f670610afbee718938a350fe3257c812d2 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:43:52 +0200 Subject: [PATCH 47/51] Cleanup --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 08e78a7fc..41ee8ee43 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -199,6 +199,10 @@ (mf/deps validate-name validate-descripion token tokens) (fn [e] (dom/prevent-default e) + ;; We have to re-validate the current form values before submitting + ;; because the validation is asynchronous/debounced + ;; 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 @name-ref) valid-name?+ (-> (validate-name final-name) schema-validation->promise) final-value (finalize-value @value-ref) From 9f3e1743a175f77b4597f47722c0d5e3600d97e7 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:45:01 +0200 Subject: [PATCH 48/51] Cleanup --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 41ee8ee43..62a50332c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -215,6 +215,8 @@ :token token :tokens 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)) (let [token (cond-> {:name final-name :type (or (:type token) token-type) From 9f6c587c95947d9e201fd6ffa44b130bd50af9c0 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 16:46:15 +0200 Subject: [PATCH 49/51] Remove duplicate similar errors --- frontend/src/app/main/ui/workspace/tokens/form.cljs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 62a50332c..9dd1930a4 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -158,8 +158,8 @@ set-resolve-value (mf/use-callback (fn [token-or-err] (let [v (cond - (= token-or-err :error/token-direct-self-reference) :error/token-self-reference - (= token-or-err :error/token-missing-reference) :error/token-missing-reference + (= token-or-err :error/token-direct-self-reference) token-or-err + (= token-or-err :error/token-missing-reference) token-or-err (:resolved-value token-or-err) (:resolved-value token-or-err))] (reset! token-resolve-result v)))) on-update-value-debounced (use-debonced-resolve-callback name-ref token tokens set-resolve-value) @@ -246,7 +246,7 @@ :resolved-value-placeholder (nil? @token-resolve-result) :resolved-value-error value-error?)} (case @token-resolve-result - :error/token-self-reference "Token has self reference" + :error/token-direct-self-reference "Token has self reference" :error/token-missing-reference "Token has missing reference" :error/unknown-error "" nil "Enter token value" From 56976e24996aa7207ade26db7786942a664929d8 Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 17:04:13 +0200 Subject: [PATCH 50/51] Update CHANGELOG --- .../src/app/main/ui/workspace/tokens/CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md b/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md index 26f32ec3e..2816acee6 100644 --- a/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md +++ b/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md @@ -14,6 +14,18 @@ If possible add video here from PR as well ## Changes +### 2024-06-25 - Token Insert/Edit Validation + Value Preview + +[Video](https://private-user-images.githubusercontent.com/1898374/342781533-06054a7e-3efb-4f48-a063-8b03f4b8fe5c.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTkzMjgwNzYsIm5iZiI6MTcxOTMyNzc3NiwicGF0aCI6Ii8xODk4Mzc0LzM0Mjc4MTUzMy0wNjA1NGE3ZS0zZWZiLTRmNDgtYTA2My04YjAzZjRiOGZlNWMubXA0P1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDYyNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA2MjVUMTUwMjU2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDliZmUwMzU1MWY3NWQ2NWZkYzA0ODYxYzYzMTYzMjMyOGZjZGMzZDNhMWJmZGI4ZmM3NmU2NzNjYjY2MTdmMCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.44rKA1h3Cvw-vDWevnx7xVUeuZ1ezV4pqEtekVXgVds) + +https://github.com/tokens-studio/tokens-studio-for-penpot/pull/194 + +Adds validation to the token create/edit field + + - Name duplication is not allowed and takes a min/max length + - Value has to be a resolvable value + - Description has max value + ### 2024-06-19 - Added CHANGELOG.md Added template for changelog From 86fd667a1197c80d49e4047a10655274906761af Mon Sep 17 00:00:00 2001 From: Florian Schroedl Date: Tue, 25 Jun 2024 17:06:52 +0200 Subject: [PATCH 51/51] Hide template section in preview document --- frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md b/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md index 2816acee6..95291feb5 100644 --- a/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md +++ b/frontend/src/app/main/ui/workspace/tokens/CHANGELOG.md @@ -2,6 +2,9 @@ Add changes that are meaningful to the user here after each PR so they can be updated in feature base. +
+Template + ## Template ### - @@ -11,6 +14,7 @@ Add changes that are meaningful to the user here after each PR so they can be up If possible add video here from PR as well - Outline of changes +
## Changes