diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index c5f28cb9b..831e47433 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -672,11 +672,11 @@ (apply-changes-local))) (defn update-token - [changes token] - (let [token-id (:id token)] - (-> changes - (update :redo-changes conj {:type :mod-token :id token-id :token token}) - (apply-changes-local)))) + [changes {:keys [id] :as token} prev-token] + (-> changes + (update :redo-changes conj {:type :mod-token :id id :token token}) + (update :undo-changes conj {:type :mod-token :id id :token (or prev-token token)}) + (apply-changes-local))) (defn delete-token [changes token-id] diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs index e0b827518..f83ce1507 100644 --- a/frontend/src/app/main/data/tokens.cljs +++ b/frontend/src/app/main/data/tokens.cljs @@ -95,14 +95,18 @@ (let [workspace-data (deref refs/workspace-data)] (get (:tokens workspace-data) id))) -(defn add-token +(defn update-create-token [token] (let [token (update token :id #(or % (uuid/next)))] (ptk/reify ::add-token ptk/WatchEvent (watch [it _ _] - (let [changes (-> (pcb/empty-changes it) - (pcb/add-token token))] + (let [prev-token (get-token-data-from-token-id (:id token)) + changes (if prev-token + (-> (pcb/empty-changes it) + (pcb/update-token token prev-token)) + (-> (pcb/empty-changes it) + (pcb/add-token token)))] (rx/of (dch/commit-changes changes))))))) (defn delete-token @@ -122,7 +126,7 @@ (let [new-token (-> (get-token-data-from-token-id id) (dissoc :id) (update :name #(str/concat % "-copy")))] - (add-token new-token))) + (update-create-token new-token))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TEMP (Move to test) diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index 915216bef..3ba521c39 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -8,20 +8,17 @@ (:require [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.state-helpers :as wsh] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] - [app.main.store :as st] [app.main.ui.workspace.tokens.style-dictionary :as sd] [app.main.ui.workspace.tokens.token :as wtt] [beicon.v2.core :as rx] [clojure.set :as set] - [potok.v2.core :as ptk] - [promesa.core :as p])) + [potok.v2.core :as ptk])) ;; Token Updates --------------------------------------------------------------- diff --git a/frontend/src/app/main/ui/workspace/tokens/form.cljs b/frontend/src/app/main/ui/workspace/tokens/form.cljs index 9ac30f39d..5094f29ef 100644 --- a/frontend/src/app/main/ui/workspace/tokens/form.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/form.cljs @@ -8,6 +8,7 @@ (:require-macros [app.main.style :as stl]) (:require ["lodash.debounce" :as debounce] + [app.main.ui.workspace.tokens.update :as wtu] [app.common.data :as d] [app.main.data.modal :as modal] [app.main.data.tokens :as dt] @@ -100,7 +101,7 @@ Token names should only contain letters and digits separated by . characters.")} new-tokens (update tokens token-id merge {:id token-id :value input :name token-name})] - (-> (sd/resolve-tokens+ new-tokens {:debug? true}) + (-> (sd/resolve-tokens+ new-tokens #_ {:debug? true}) (p/then (fn [resolved-tokens] (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)] @@ -240,12 +241,13 @@ Token names should only contain letters and digits separated by . characters.")} ;; 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) - :value final-value} - final-description (assoc :description final-description) - (:id token) (assoc :id (:id token)))] - (st/emit! (dt/add-token token)) + (let [new-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/update-create-token new-token)) + (st/emit! (wtu/update-workspace-tokens)) (modal/hide!)))))))))] [:form {:on-submit on-submit} 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 911fbafe9..1944abe10 100644 --- a/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/style_dictionary.cljs @@ -111,6 +111,9 @@ (defonce !tokens-cache (atom nil)) +(defn get-cached-tokens [tokens] + (get @!tokens-cache tokens tokens)) + (defn use-resolved-tokens "The StyleDictionary process function is async, so we can't use resolved values directly. diff --git a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs index 6b6c16916..a083bed54 100644 --- a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs @@ -66,7 +66,8 @@ :key :rotation}]}}] [:spacing {:title "Spacing" - :attributes ctt/spacing-keys + :attributes #{:column-gap :row-gap} + :all-attributes ctt/spacing-keys :on-update-shape wtch/update-layout-spacing :modal {:key :tokens/spacing :fields [{:label "Spacing" diff --git a/frontend/src/app/main/ui/workspace/tokens/update.cljs b/frontend/src/app/main/ui/workspace/tokens/update.cljs new file mode 100644 index 000000000..1543d8d06 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/tokens/update.cljs @@ -0,0 +1,132 @@ +(ns app.main.ui.workspace.tokens.update + (:require + [app.common.types.token :as ctt] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.undo :as dwu] + [app.main.refs :as refs] + [app.main.ui.workspace.tokens.changes :as wtch] + [app.main.ui.workspace.tokens.style-dictionary :as wtsd] + [beicon.v2.core :as rx] + [clojure.data :as data] + [clojure.set :as set] + [potok.v2.core :as ptk])) + +;; Constants ------------------------------------------------------------------- + +(def filter-existing-values? false) + +(def attributes->shape-update + {#{:rx :ry} (fn [v ids _] (wtch/update-shape-radius-all v ids)) + #{:r1 :r2 :r3 :r4} wtch/update-shape-radius-single-corner + ctt/stroke-width-keys wtch/update-stroke-width + ctt/sizing-keys wtch/update-shape-dimensions + ctt/opacity-keys wtch/update-opacity + #{:x :y} wtch/update-shape-position + #{:p1 :p2 :p3 :p4} (fn [resolved-value shape-ids attrs] + (dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat resolved-value))})) + #{:column-gap :row-gap} wtch/update-layout-spacing + #{:width :height} wtch/update-shape-dimensions + #{:layout-item-min-w :layout-item-min-h :layout-item-max-w :layout-item-max-h} wtch/update-layout-sizing-limits + ctt/rotation-keys wtch/update-rotation}) + +(def attribute-actions-map + (reduce + (fn [acc [ks action]] + (into acc (map (fn [k] [k action]) ks))) + {} attributes->shape-update)) + +;; Helpers --------------------------------------------------------------------- + +(defn deep-merge + "Like d/deep-merge but unions set values." + ([a b] + (cond + (map? a) (merge-with deep-merge a b) + (set? a) (set/union a b) + :else b)) + ([a b & rest] + (reduce deep-merge a (cons b rest)))) + +;; Data flows ------------------------------------------------------------------ + +(defn invert-collect-key-vals + [xs resolved-tokens shape] + (-> (reduce + (fn [acc [k v]] + (let [resolved-token (get resolved-tokens v) + resolved-value (get resolved-token :resolved-value) + skip? (or + (not (get resolved-tokens v)) + (and filter-existing-values? (= (get shape k) resolved-value)))] + (if skip? + acc + (update acc resolved-value (fnil conj #{}) k)))) + {} xs))) + +(defn split-attribute-groups [attrs-values-map] + (reduce + (fn [acc [attrs v]] + (cond + (some attrs #{:rx :ry}) (let [[_ a b] (data/diff #{:rx :ry} attrs)] + (cond-> (assoc acc b v) + ;; Exact match in attrs + a (assoc a v))) + + (some attrs #{:widht :height}) (let [[_ a b] (data/diff #{:width :height} attrs)] + (cond-> (assoc acc b v) + ;; Exact match in attrs + a (assoc a v))) + (some attrs ctt/spacing-keys) (let [[_ rst gap] (data/diff #{:row-gap :column-gap} attrs) + [_ position padding] (data/diff #{:p1 :p2 :p3 :p4} rst)] + (cond-> acc + (seq gap) (assoc gap v) + (seq position) (assoc position v) + (seq padding) (assoc padding v))) + attrs (assoc acc attrs v))) + {} attrs-values-map)) + +(defn shape-ids-by-values + [attrs-values-map object-id] + (->> (map (fn [[value attrs]] [attrs {value #{object-id}}]) attrs-values-map) + (into {}))) + +(defn collect-shapes-update-info [resolved-tokens shapes] + (reduce + (fn [acc [object-id {:keys [applied-tokens] :as shape}]] + (if (seq applied-tokens) + (let [applied-tokens (-> (invert-collect-key-vals applied-tokens resolved-tokens shape) + (shape-ids-by-values object-id) + (split-attribute-groups))] + (deep-merge acc applied-tokens)) + acc)) + {} shapes)) + +(defn actionize-shapes-update-info [shapes-update-info] + (mapcat (fn [[attrs update-infos]] + (let [action (some attribute-actions-map attrs)] + (map + (fn [[v shape-ids]] + (action v shape-ids attrs)) + update-infos))) + shapes-update-info)) + +(defn update-tokens [resolved-tokens] + (->> @refs/workspace-page-objects + (collect-shapes-update-info resolved-tokens) + (actionize-shapes-update-info))) + +(defn update-workspace-tokens [] + (ptk/reify ::update-workspace-tokens + ptk/WatchEvent + (watch [_ state _] + (->> + (rx/from (wtsd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) + (rx/mapcat + (fn [sd-tokens] + (let [undo-id (js/Symbol)] + (rx/concat + (rx/of (dwu/start-undo-transaction undo-id)) + (rx/concat + (->> (update-tokens sd-tokens) + (rx/concat))) + (rx/of (dwu/commit-undo-transaction undo-id))))))))))