diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index 802903c08..3680e3a2c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -6,13 +6,14 @@ (ns app.main.ui.workspace.tokens.changes (:require - [app.common.types.token :as ctt] [app.common.types.shape.radius :as ctsr] + [app.common.types.token :as ctt] [app.main.data.tokens :as dt] - [app.main.data.workspace.changes :as dch] [app.main.data.workspace :as udw] + [app.main.data.workspace.changes :as dch] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.transforms :as dwt] + [app.main.data.workspace.undo :as dwu] [app.main.store :as st] [app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.style-dictionary :as sd] @@ -24,6 +25,67 @@ ;; Token Updates --------------------------------------------------------------- +(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] :as _props}] + (ptk/reify ::apply-token + ptk/WatchEvent + (watch [_ state _] + (->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) + (rx/mapcat + (fn [sd-tokens] + (let [undo-id (js/Symbol) + resolved-value (-> (get sd-tokens (:id token)) + (wtt/resolve-token-value)) + tokenized-attributes (wtt/attributes-map attributes (:id token))] + (rx/of + (dwu/start-undo-transaction undo-id) + (dch/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 + (on-update-shape resolved-value shape-ids attributes)) + (dwu/commit-undo-transaction undo-id))))))))) + +(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 % (wtt/remove-attributes-for-token-id attributes (:id token) %))] + (dch/update-shapes + shape-ids + (fn [shape] + (update shape :applied-tokens remove-token)))))))) + +(defn toggle-token + [{:keys [token-type-props token shapes] :as _props}] + (ptk/reify ::on-toggle-token + ptk/WatchEvent + (watch [_ _ _] + (let [{:keys [attributes on-update-shape]} token-type-props + unapply-tokens? (wtt/shapes-token-applied? token shapes (:attributes token-type-props)) + shape-ids (map :id shapes)] + (if unapply-tokens? + (rx/of + (unapply-token {:attributes attributes + :token token + :shape-ids shape-ids})) + (rx/of + (apply-token {:attributes attributes + :token token + :shape-ids shape-ids + :on-update-shape on-update-shape}))))))) + (defn on-apply-token [{:keys [token token-type-props selected-shapes] :as _props}] (let [{:keys [attributes on-apply on-update-shape] :or {on-apply dt/update-token-from-attributes}} token-type-props @@ -125,9 +187,9 @@ :attributes attributes) :else token-type-props)] - (wtc/on-apply-token {:token token - :token-type-props updated-token-type-props - :selected-shapes selected-shapes}))) + (on-apply-token {:token token + :token-type-props updated-token-type-props + :selected-shapes selected-shapes}))) (defn update-layout-sizing-limits [value shape-ids attributes] (ptk/reify ::update-layout-sizing-limits diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs index c3e85ba0c..60729d406 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -16,7 +16,6 @@ [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] [app.main.ui.workspace.tokens.changes :as wtch] - [app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token-types :as wtty] [app.util.dom :as dom] @@ -45,8 +44,8 @@ {:title title :selected? selected? :action #(if selected? - (st/emit! (wtc/unapply-token props)) - (st/emit! (wtc/apply-token (assoc props :on-update-shape on-update-shape))))})) + (st/emit! (wtch/unapply-token props)) + (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))})) attributes))) (defn all-or-sepearate-actions [{:keys [attribute-labels on-update-shape-all on-update-shape]} @@ -59,8 +58,8 @@ {:title "All" :selected? all-selected? :action #(if all-selected? - (st/emit! (wtc/unapply-token props)) - (st/emit! (wtc/apply-token (assoc props :on-update-shape (or on-update-shape-all on-update-shape)))))}) + (st/emit! (wtch/unapply-token props)) + (st/emit! (wtch/apply-token (assoc props :on-update-shape (or on-update-shape-all on-update-shape)))))}) single-actions (map (fn [[attr title]] (let [selected? (selected-pred attr)] {:title title @@ -70,10 +69,10 @@ :shape-ids shape-ids} event (cond all-selected? (-> (assoc props :attributes-to-remove attributes) - (wtc/apply-token)) - selected? (wtc/unapply-token props) + (wtch/apply-token)) + selected? (wtch/unapply-token props) :else (-> (assoc props :on-update-shape on-update-shape) - (wtc/apply-token)))] + (wtch/apply-token)))] (st/emit! event))})) attribute-labels)] (concat [all-action] single-actions))) @@ -102,19 +101,19 @@ :token token :shape-ids shape-ids}] (if all-selected? - (st/emit! (wtc/unapply-token props)) - (st/emit! (wtc/apply-token (assoc props :on-update-shape on-update-shape))))))} + (st/emit! (wtch/unapply-token props)) + (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))))} {:title "Horizontal" :selected? horizontal-padding-selected? :action (fn [] (let [props {:token token :shape-ids shape-ids} event (cond - all-selected? (wtc/apply-token (assoc props :attributes-to-remove vertical-attributes)) - horizontal-padding-selected? (wtc/apply-token (assoc props :attributes-to-remove horizontal-attributes)) - :else (wtc/apply-token (assoc props - :attributes horizontal-attributes - :on-update-shape on-update-shape)))] + all-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes)) + horizontal-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attributes)) + :else (wtch/apply-token (assoc props + :attributes horizontal-attributes + :on-update-shape on-update-shape)))] (st/emit! event)))} {:title "Vertical" :selected? vertical-padding-selected? @@ -122,11 +121,11 @@ (let [props {:token token :shape-ids shape-ids} event (cond - all-selected? (wtc/apply-token (assoc props :attributes-to-remove vertical-attributes)) - vertical-padding-selected? (wtc/apply-token (assoc props :attributes-to-remove vertical-attributes)) - :else (wtc/apply-token (assoc props - :attributes vertical-attributes - :on-update-shape on-update-shape)))] + all-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes)) + vertical-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes)) + :else (wtch/apply-token (assoc props + :attributes vertical-attributes + :on-update-shape on-update-shape)))] (st/emit! event)))}] single-padding-items (->> padding-attrs (map (fn [[attr title]] @@ -145,10 +144,10 @@ :shape-ids shape-ids} event (cond all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs) - (wtc/apply-token)) - selected? (wtc/unapply-token props) + (wtch/apply-token)) + selected? (wtch/unapply-token props) :else (-> (assoc props :on-update-shape on-update-shape) - (wtc/apply-token)))] + (wtch/apply-token)))] (st/emit! event))})))) gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap" :row-gap "Row Gap"} diff --git a/frontend/src/app/main/ui/workspace/tokens/core.cljs b/frontend/src/app/main/ui/workspace/tokens/core.cljs index b6dfb8000..a68d7e046 100644 --- a/frontend/src/app/main/ui/workspace/tokens/core.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/core.cljs @@ -6,25 +6,12 @@ (ns app.main.ui.workspace.tokens.core (:require - [app.common.data :as d :refer [ordered-map]] - [app.common.types.shape.radius :as ctsr] - [app.common.types.token :as ctt] - [app.main.data.tokens :as dt] - [app.main.data.workspace :as udw] - [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.shape-layout :as dwsl] - [app.main.data.workspace.transforms :as dwt] - [app.main.data.workspace.undo :as dwu] + [app.common.data :as d] [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.token :as wtt] [app.util.dom :as dom] [app.util.webapi :as wapi] - [beicon.v2.core :as rx] - [cuerdas.core :as str] - [potok.v2.core :as ptk] - [promesa.core :as p])) + [cuerdas.core :as str])) ;; Helpers --------------------------------------------------------------------- @@ -48,86 +35,6 @@ (cond-> (assoc item :label name) (wtt/token-applied? item shape (or selected-attributes attributes)) (assoc :selected? true)))))) -;; Events ---------------------------------------------------------------------- - -(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] :as _props}] - (ptk/reify ::apply-token - ptk/WatchEvent - (watch [_ state _] - (->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) - (rx/mapcat - (fn [sd-tokens] - (let [undo-id (js/Symbol) - resolved-value (-> (get sd-tokens (:id token)) - (resolve-token-value)) - tokenized-attributes (wtt/attributes-map attributes (:id token))] - (rx/of - (dwu/start-undo-transaction undo-id) - (dch/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 - (on-update-shape resolved-value shape-ids attributes)) - (dwu/commit-undo-transaction undo-id))))))))) - -(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 % (wtt/remove-attributes-for-token-id attributes (:id token) %))] - (dch/update-shapes - shape-ids - (fn [shape] - (update shape :applied-tokens remove-token)))))))) - -(defn toggle-token - [{:keys [token-type-props token shapes] :as _props}] - (ptk/reify ::on-toggle-token - ptk/WatchEvent - (watch [_ _ _] - (let [{:keys [attributes on-update-shape]} token-type-props - unapply-tokens? (wtt/shapes-token-applied? token shapes (:attributes token-type-props)) - shape-ids (map :id shapes)] - (if unapply-tokens? - (rx/of - (unapply-token {:attributes attributes - :token token - :shape-ids shape-ids})) - (rx/of - (apply-token {:attributes attributes - :token token - :shape-ids shape-ids - :on-update-shape on-update-shape}))))))) - -(defn on-apply-token [{:keys [token token-type-props selected-shapes] :as _props}] - (let [{:keys [attributes on-apply on-update-shape] - :or {on-apply dt/update-token-from-attributes}} token-type-props - shape-ids (->> selected-shapes - (eduction - (remove #(wtt/shapes-token-applied? token % attributes)) - (map :id)))] - - (p/let [sd-tokens (sd/resolve-workspace-tokens+ {:debug? true})] - (let [resolved-token (get sd-tokens (:id token)) - resolved-token-value (resolve-token-value resolved-token)] - (doseq [shape selected-shapes] - (st/emit! (on-apply {:token-id (:id token) - :shape-id (:id shape) - :attributes attributes})) - (on-update-shape resolved-token-value shape-ids attributes)))))) - ;; JSON export functions ------------------------------------------------------- (defn encode-tokens diff --git a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs index 0dee240ab..9c2218716 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sidebar.cljs @@ -14,6 +14,7 @@ [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets.common :as cmm] + [app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.core :as wtc] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.token :as wtt] @@ -100,9 +101,9 @@ (dom/stop-propagation event) (when (seq selected-shapes) (st/emit! - (wtc/toggle-token {:token token - :shapes selected-shapes - :token-type-props token-type-props}))))) + (wtch/toggle-token {:token token + :shapes selected-shapes + :token-type-props token-type-props}))))) tokens-count (count tokens)] [:div {:on-click on-toggle-open-click} [:& cmm/asset-section {:icon (mf/fnc icon-wrapper [_] diff --git a/frontend/test/token_tests/logic/token_actions_test.cljs b/frontend/test/token_tests/logic/token_actions_test.cljs index 5c1ca4c06..1b6565d00 100644 --- a/frontend/test/token_tests/logic/token_actions_test.cljs +++ b/frontend/test/token_tests/logic/token_actions_test.cljs @@ -34,14 +34,14 @@ (let [file (setup-file) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [(wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rx :ry} - :token (toht/get-token file :token-1) - :on-update-shape wtch/update-shape-radius-all}) - (wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rx :ry} - :token (toht/get-token file :token-2) - :on-update-shape wtch/update-shape-radius-all})]] + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rx :ry} + :token (toht/get-token file :token-1) + :on-update-shape wtch/update-shape-radius-all}) + (wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rx :ry} + :token (toht/get-token file :token-2) + :on-update-shape wtch/update-shape-radius-all})]] (tohs/run-store-async store done events (fn [new-state] @@ -62,18 +62,18 @@ store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) events [;; Apply `:token-1` to all border radius attributes - (wtc/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4} - :token (toht/get-token file :token-1) - :shape-ids [(:id rect-1)] - :on-update-shape wtch/update-shape-radius-all}) + (wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4} + :token (toht/get-token file :token-1) + :shape-ids [(:id rect-1)] + :on-update-shape wtch/update-shape-radius-all}) ;; Apply single `:r1` attribute to same shape ;; while removing other attributes from the border-radius set ;; but keep `:r4` for testing purposes - (wtc/apply-token {:attributes #{:r1} - :attributes-to-remove #{:rx :ry :r1 :r2 :r3} - :token (toht/get-token file :token-2) - :shape-ids [(:id rect-1)] - :on-update-shape wtch/update-shape-radius-all})]] + (wtch/apply-token {:attributes #{:r1} + :attributes-to-remove #{:rx :ry :r1 :r2 :r3} + :token (toht/get-token file :token-2) + :shape-ids [(:id rect-1)] + :on-update-shape wtch/update-shape-radius-all})]] (tohs/run-store-async store done events (fn [new-state] @@ -95,10 +95,10 @@ (let [file (setup-file) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [(wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rx :ry} - :token (toht/get-token file :token-2) - :on-update-shape wtch/update-shape-radius-all})]] + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rx :ry} + :token (toht/get-token file :token-2) + :on-update-shape wtch/update-shape-radius-all})]] (tohs/run-store-async store done events (fn [new-state] @@ -121,10 +121,10 @@ :type :dimensions})) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [(wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:width :height} - :token (toht/get-token file :token-target) - :on-update-shape wtch/update-shape-dimensions})]] + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:width :height} + :token (toht/get-token file :token-target) + :on-update-shape wtch/update-shape-dimensions})]] (tohs/run-store-async store done events (fn [new-state] @@ -147,10 +147,10 @@ :type :sizing})) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [(wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:width :height} - :token (toht/get-token file :token-target) - :on-update-shape wtch/update-shape-dimensions})]] + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:width :height} + :token (toht/get-token file :token-target) + :on-update-shape wtch/update-shape-dimensions})]] (tohs/run-store-async store done events (fn [new-state] @@ -173,10 +173,10 @@ :type :opacity})) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [(wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:opacity} - :token (toht/get-token file :token-target) - :on-update-shape wtch/update-opacity})]] + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:opacity} + :token (toht/get-token file :token-target) + :on-update-shape wtch/update-opacity})]] (tohs/run-store-async store done events (fn [new-state] @@ -198,10 +198,10 @@ :type :rotation})) store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) - events [(wtc/apply-token {:shape-ids [(:id rect-1)] - :attributes #{:rotation} - :token (toht/get-token file :token-target) - :on-update-shape wtch/update-rotation})]] + events [(wtch/apply-token {:shape-ids [(:id rect-1)] + :attributes #{:rotation} + :token (toht/get-token file :token-target) + :on-update-shape wtch/update-rotation})]] (tohs/run-store-async store done events (fn [new-state] @@ -220,10 +220,10 @@ store (ths/setup-store file) rect-1 (cths/get-shape file :rect-1) rect-2 (cths/get-shape file :rect-2) - events [(wtc/toggle-token {:shapes [rect-1 rect-2] - :token-type-props {:attributes #{:rx :ry} - :on-update-shape wtch/update-shape-radius-all} - :token (toht/get-token file :token-2)})]] + events [(wtch/toggle-token {:shapes [rect-1 rect-2] + :token-type-props {:attributes #{:rx :ry} + :on-update-shape wtch/update-shape-radius-all} + :token (toht/get-token file :token-2)})]] (tohs/run-store-async store done events (fn [new-state] @@ -253,9 +253,9 @@ rect-without-token (cths/get-shape file :rect-2) rect-with-other-token (cths/get-shape file :rect-3) - events [(wtc/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token] - :token (toht/get-token file :token-1) - :token-type-props {:attributes #{:rx :ry}}})]] + events [(wtch/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token] + :token (toht/get-token file :token-1) + :token-type-props {:attributes #{:rx :ry}}})]] (tohs/run-store-async store done events (fn [new-state] @@ -287,9 +287,9 @@ rect-without-token (cths/get-shape file :rect-2) rect-with-other-token-2 (cths/get-shape file :rect-3) - events [(wtc/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2] - :token (toht/get-token file :token-1) - :token-type-props {:attributes #{:rx :ry}}})]] + events [(wtch/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2] + :token (toht/get-token file :token-1) + :token-type-props {:attributes #{:rx :ry}}})]] (tohs/run-store-async store done events (fn [new-state]