diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index c8b6b6f9f5..4ee9b523e9 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -468,3 +468,25 @@ "Predicate if a typography composite token is a reference value - a string pointing to another reference token." [token-value] (string? token-value)) + +(def tokens-by-input + "A map from input name to applicable token for that input." + {:width #{:sizing :dimensions} + :height #{:sizing :dimensions} + :max-width #{:sizing :dimensions} + :max-height #{:sizing :dimensions} + :x #{:spacing :dimensions} + :y #{:spacing :dimensions} + :rotation #{:number} + :border-radius #{:border-radius :dimensions} + :row-gap #{:spacing :dimensions} + :column-gap #{:spacing :dimensions} + :horizontal-padding #{:spacing :dimensions} + :vertical-padding #{:spacing :dimensions} + :sided-paddings #{:spacing :dimensions} + :horizontal-margin #{:spacing :dimensions} + :vertical-margin #{:spacing :dimensions} + :sided-margins #{:spacing :dimensions} + :line-height #{:line-height :number} + :font-size #{:font-size} + :letter-spacing #{:letter-spacing}}) diff --git a/frontend/src/app/main/data/style_dictionary.cljs b/frontend/src/app/main/data/style_dictionary.cljs index b341d35a96..19df3a7207 100644 --- a/frontend/src/app/main/data/style_dictionary.cljs +++ b/frontend/src/app/main/data/style_dictionary.cljs @@ -417,7 +417,6 @@ ;; FIXME: this with effect with trigger all the time because ;; `config` will be always a different instance - (mf/with-effect [tokens config] (let [cached (get @cache-atom tokens)] (cond diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 37b457a65b..9a53b971a5 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -38,6 +38,138 @@ [potok.v2.core :as ptk])) (declare token-properties) +(declare update-layout-item-margin) + +;; Events to apply / unapply tokens to shapes ------------------------------------------------------------ + +(defn apply-token + "Apply `attributes` that match `token` for `shape-ids`. + + Optionally remove attributes from `attributes-to-remove`, + this is useful for applying a single attribute from an attributes set + while removing other applied tokens from this set." + [{:keys [attributes attributes-to-remove token shape-ids on-update-shape]}] + (ptk/reify ::apply-token + ptk/WatchEvent + (watch [_ state _] + ;; We do not allow to apply tokens while text editor is open. + (when (empty? (get state :workspace-editor-state)) + (let [attributes-to-remove + ;; Remove atomic typography tokens when applying composite and vice-verca + (cond + (ctt/typography-token-keys (:type token)) (set/union attributes-to-remove ctt/typography-keys) + (ctt/typography-keys (:type token)) (set/union attributes-to-remove ctt/typography-token-keys) + :else attributes-to-remove)] + (when-let [tokens (some-> (dsh/lookup-file-data state) + (get :tokens-lib) + (ctob/get-tokens-in-active-sets))] + (->> (sd/resolve-tokens tokens) + (rx/mapcat + (fn [resolved-tokens] + (let [undo-id (js/Symbol) + objects (dsh/lookup-page-objects state) + selected-shapes (select-keys objects shape-ids) + + shape-ids (or (->> selected-shapes + (filter (fn [[_ shape]] + (or + (and (ctsl/any-layout-immediate-child? objects shape) + (some ctt/spacing-margin-keys attributes)) + (ctt/any-appliable-attr? attributes (:type shape))))) + (keys)) + []) + + resolved-value (get-in resolved-tokens [(cft/token-identifier token) :resolved-value]) + tokenized-attributes (cft/attributes-map attributes token) + type (:type token)] + (rx/concat + (rx/of + (st/emit! (ev/event {::ev/name "apply-tokens" + :type type + :applyed-to attributes})) + (dwu/start-undo-transaction undo-id) + (dwsh/update-shapes shape-ids (fn [shape] + (cond-> shape + attributes-to-remove + (update :applied-tokens #(apply (partial dissoc %) attributes-to-remove)) + :always + (update :applied-tokens merge tokenized-attributes))))) + (when on-update-shape + (let [res (on-update-shape resolved-value shape-ids attributes)] + ;; Composed updates return observables and need to be executed differently + (if (rx/observable? res) + res + (rx/of res)))) + (rx/of (dwu/commit-undo-transaction undo-id))))))))))))) + +(defn apply-spacing-token + "Handles edge-case for spacing token when applying token via toggle button. + Splits out `shape-ids` into seperate default actions: + - Layouts take the `default` update function + - Shapes inside layout will only take margin" + [{:keys [token shapes]}] + (ptk/reify ::apply-spacing-token + ptk/WatchEvent + (watch [_ state _] + (let [objects (dsh/lookup-page-objects state) + + {:keys [attributes on-update-shape]} + (get token-properties (:type token)) + + {:keys [other frame-children]} + (group-by #(if (ctsl/any-layout-immediate-child? objects %) :frame-children :other) shapes)] + + (rx/of + (apply-token {:attributes attributes + :token token + :shape-ids (map :id other) + :on-update-shape on-update-shape}) + (apply-token {:attributes ctt/spacing-margin-keys + :token token + :shape-ids (map :id frame-children) + :on-update-shape update-layout-item-margin})))))) + +(defn unapply-token + "Removes `attributes` that match `token` for `shape-ids`. + + Doesn't update shape attributes." + [{:keys [attributes token shape-ids] :as _props}] + (ptk/reify ::unapply-token + ptk/WatchEvent + (watch [_ _ _] + (rx/of + (let [remove-token #(when % (cft/remove-attributes-for-token attributes token %))] + (dwsh/update-shapes + shape-ids + (fn [shape] + (update shape :applied-tokens remove-token)))))))) + +(defn toggle-token + [{:keys [token shapes attrs]}] + (ptk/reify ::on-toggle-token + ptk/WatchEvent + (watch [_ _ _] + (let [{:keys [attributes all-attributes on-update-shape]} + (get token-properties (:type token)) + + unapply-tokens? + (cft/shapes-token-applied? token shapes (or all-attributes attributes)) + + shape-ids (map :id shapes)] + (if unapply-tokens? + (rx/of + (unapply-token {:attributes (or attrs all-attributes attributes) + :token token + :shape-ids shape-ids})) + (rx/of + (case (:type token) + :spacing + (apply-spacing-token {:token token + :shapes shapes}) + (apply-token {:attributes (or attrs attributes) + :token token + :shape-ids shape-ids + :on-update-shape on-update-shape})))))))) ;; Events to update the value of attributes with applied tokens --------------------------------------------------------- diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 43b0726a3b..2e1f9af9b1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -14,10 +14,12 @@ [app.common.logic.shapes :as cls] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] + [app.common.types.token :as tk] [app.main.constants :refer [size-presets]] [app.main.data.workspace :as udw] [app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] [app.main.refs :as refs] @@ -25,7 +27,9 @@ [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.numeric-input :refer [numeric-input*]] [app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]] + [app.main.ui.context :as muc] [app.main.ui.ds.buttons.icon-button :refer [icon-button*]] + [app.main.ui.ds.controls.numeric-input :as ni] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.icons :as deprecated-icon] [app.main.ui.workspace.sidebar.options.menus.border-radius :refer [border-radius-menu*]] @@ -87,12 +91,31 @@ shape)] (select-keys shape measure-attrs))) +(mf/defc numeric-input-wrapper* + {::mf/private true} + [{:keys [values name applied-tokens] :rest props}] + (let [tokens (mf/use-ctx muc/active-tokens-by-type) + tokens (mf/with-memo [tokens name] + (delay + (-> (deref tokens) + (select-keys (get tk/tokens-by-input name)) + (not-empty)))) + + props (mf/spread-props props + {:placeholder (if (or (= :multiple (:applied-tokens values)) + (= :multiple (get values name))) + (tr "settings.multiple") "--") + :class (stl/css :numeric-input-measures) + :applied-token (get applied-tokens name) + :tokens tokens + :value (get values name)})] + [:> ni/numeric-input* props])) + (def ^:private xf:map-type (map :type)) (def ^:private xf:mapcat-type-to-options (mapcat type->options)) (mf/defc measures-menu* - {::mf/memo true} - [{:keys [ids values type shapes]}] + [{:keys [ids values applied-tokens type shapes]}] (let [all-types (mf/with-memo [type shapes] ;; We only need this when multiple type is used @@ -284,12 +307,20 @@ (mf/use-fn (mf/deps ids) (fn [value attr] - (st/emit! (udw/trigger-bounding-box-cloaking ids)) - (binding [cts/*wasm-sync* true] - (run! #(do-position-change %1 value attr) shapes)))) + (if (or (string? value) (int? value)) + (do (st/emit! (udw/trigger-bounding-box-cloaking ids)) + (binding [cts/*wasm-sync* true] + (run! #(do-position-change %1 value attr) shapes))) + (do + (let [value2 (:resolved-value value)] + (st/emit! (udw/trigger-bounding-box-cloaking ids) + (dwta/toggle-token {:token value + :attrs #{attr} + :shapes shapes})) + (binding [cts/*wasm-sync* true] + (run! #(do-position-change %1 value2 attr) shapes))))))) ;; ROTATION - on-rotation-change (mf/use-fn (mf/deps ids) @@ -310,6 +341,15 @@ on-pos-y-change (mf/use-fn (mf/deps on-position-change) #(on-position-change % :y)) + on-detach-token + (mf/use-fn + (mf/deps ids) + (fn [token attr] + ;; Review this, detach is having problems + (let [shape-ids (map :id shapes)] + (st/emit! (dwta/unapply-token {:token token + :attributes #{attr} + :shape-ids shape-ids}))))) ;; CLIP CONTENT AND SHOW IN VIEWER on-change-clip-content (mf/use-fn @@ -421,31 +461,30 @@ :disabled (= proportion-lock :multiple) :aria-label (if proportion-lock (tr "workspace.options.size.unlock") (tr "workspace.options.size.lock")) :on-click on-proportion-lock-change}]]) + (when (options :position) [:div {:class (stl/css :position)} - [:div {:class (stl/css-case :x-position true - :disabled disabled-position?) - :title (tr "workspace.options.x")} - [:span {:class (stl/css :icon-text)} "X"] - [:> numeric-input* {:no-validate true - :placeholder (if (= :multiple (:x values)) (tr "settings.multiple") "--") - :on-change on-pos-x-change - :disabled disabled-position? - :class (stl/css :numeric-input) - :value (:x values)}]] + [:> numeric-input-wrapper* + {:disabled disabled-position? + :on-change on-position-change + :on-detach on-detach-token + :icon "character-x" + :name :x + :property (tr "workspace.options.x") + :applied-tokens applied-tokens + :values values}] - [:div {:class (stl/css-case :y-position true - :disabled disabled-position?) - :title (tr "workspace.options.y")} - [:span {:class (stl/css :icon-text)} "Y"] - [:> numeric-input* {:no-validate true - :placeholder (if (= :multiple (:y values)) (tr "settings.multiple") "--") - :disabled disabled-position? - :on-change on-pos-y-change - :class (stl/css :numeric-input) - :value (:y values)}]]]) - (when (or (options :rotation) - (options :radius)) + [:> numeric-input-wrapper* + {:disabled disabled-position? + :on-change on-position-change + :on-detach on-detach-token + :icon "character-y" + :name :y + :property (tr "workspace.options.y") + :applied-tokens applied-tokens + :values values}]]) + + (when (or (options :rotation) (options :radius)) [:div {:class (stl/css :rotation-radius)} (when (options :rotation) [:div {:class (stl/css :rotation) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss index 86d9781c80..96aaf3aa61 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.scss @@ -187,3 +187,7 @@ .checkbox-button { @extend .button-icon; } + +.numeric-input-measures { + --dropdown-width: 247px; +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 56fb1dbdb2..c0bba81644 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -29,6 +29,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + measure-values (select-keys shape measure-attrs) @@ -84,6 +87,7 @@ :values layer-values}] [:> measures-menu* {:ids ids + :applied-tokens applied-tokens :type type :values measure-values :shapes shapes}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index 89e9ad4f8c..35d9586e6e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -30,6 +30,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + measure-values (select-keys shape measure-attrs) @@ -84,6 +87,7 @@ :values layer-values}] [:> measures-menu* {:ids ids + :applied-tokens applied-tokens :type type :values measure-values :shapes shapes}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index f709b90999..fcba591c5d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -33,6 +33,9 @@ ids (mf/with-memo [shape-id] [shape-id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + stroke-values (select-keys shape stroke-attrs) @@ -99,6 +102,7 @@ :type shape-type :values layer-values}] [:> measures-menu* {:ids ids + :applied-tokens applied-tokens :values measure-values :type shape-type :shapes shapes}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index 4ef159d6d5..ae3fb4e65f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -37,6 +37,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + objects (mf/with-memo [shapes-with-children] (d/index-by :id shapes-with-children)) @@ -111,6 +114,7 @@ :values layer-values}] [:> measures-menu* {:type type :ids measure-ids + :applied-tokens applied-tokens :values measure-values :shapes shapes}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index b19523e0f7..11247646ea 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -420,7 +420,7 @@ ;; also don't use the memoized version of get-attrs because it ;; makes no sense because the shapes object are changed on ;; each rerender. - [measure-ids measure-values] + [measure-ids measure-values measure-tokens] (get-attrs* shapes objects :measure)] [:div {:class (stl/css :options)} @@ -434,6 +434,7 @@ {:type type :ids measure-ids :values measure-values + :applied-tokens measure-tokens :shapes shapes}]) (when (some? components) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index bba37db31a..e632809ca7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -30,6 +30,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + measure-values (select-keys shape measure-attrs) @@ -85,6 +88,7 @@ :values layer-values}] [:> measures-menu* {:ids ids :type type + :applied-tokens applied-tokens :values measure-values :shapes shapes}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index da138d37a5..6e7df675b4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -30,6 +30,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + measure-values (select-keys shape measure-attrs) @@ -86,6 +89,7 @@ [:> measures-menu* {:ids ids :type type :values measure-values + :applied-tokens applied-tokens :shapes shapes}] [:& layout-container-menu diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index faba956f2b..daf01c4dc5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -95,6 +95,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + {:keys [tag] :as content} (get shape :content) @@ -153,6 +156,7 @@ [:* [:> measures-menu* {:ids ids :type type + :applied-tokens applied-tokens :values measure-values :shapes shapes}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 26b1c88e4e..51642f9d29 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -35,6 +35,9 @@ ids (mf/with-memo [id] [id]) shapes (mf/with-memo [shape] [shape]) + applied-tokens + (get shape :applied-tokens) + measure-values (select-keys shape measure-attrs) @@ -132,6 +135,7 @@ [:> measures-menu* {:ids ids :type type + :applied-tokens applied-tokens :values measure-values :shapes shapes}]