diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 3c68fa0e4..decdfe333 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -129,6 +129,10 @@ [:p2 {:optional true} token-name-ref] [:p3 {:optional true} token-name-ref] [:p4 {:optional true} token-name-ref] + [:m1 {:optional true} token-name-ref] + [:m2 {:optional true} token-name-ref] + [:m3 {:optional true} token-name-ref] + [:m4 {:optional true} token-name-ref] [:x {:optional true} token-name-ref] [:y {:optional true} token-name-ref]]) diff --git a/frontend/src/app/main/ui/workspace/tokens/changes.cljs b/frontend/src/app/main/ui/workspace/tokens/changes.cljs index ceba897e4..3b1ab1797 100644 --- a/frontend/src/app/main/ui/workspace/tokens/changes.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/changes.cljs @@ -203,6 +203,26 @@ (filter ctsl/any-layout?) (map :id)))) +(defn- shape-ids-with-layout-parent [state page-id shape-ids] + (let [objects (dsh/lookup-page-objects state)] + (->> (dsh/lookup-shapes state page-id shape-ids) + (eduction + (filter #(ctsl/any-layout-immediate-child? objects %)) + (map :id))))) + +(defn update-layout-item-margin + ([value shape-ids attrs] (update-layout-item-margin value shape-ids attrs nil)) + ([value shape-ids attrs page-id] + (ptk/reify ::update-layout-item-margin + ptk/WatchEvent + (watch [_ state _] + (let [ids-with-layout-parent (shape-ids-with-layout-parent state (or page-id (:current-page-id state)) shape-ids)] + (rx/of + (dwsl/update-layout ids-with-layout-parent + {:layout-item-margin (zipmap attrs (repeat value))} + {:ignore-touched true + :page-id page-id}))))))) + (defn update-layout-padding ([value shape-ids attrs] (update-layout-padding value shape-ids attrs nil)) ([value shape-ids attrs page-id] @@ -222,7 +242,7 @@ (ptk/reify ::update-layout-spacing ptk/WatchEvent (watch [_ state _] - (let [ids-with-layout (shape-ids-with-layout state page-id shape-ids) + (let [ids-with-layout (shape-ids-with-layout state (or page-id (:current-page-id state)) shape-ids) layout-attributes (attributes->layout-gap attributes value)] (rx/of (dwsl/update-layout ids-with-layout 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 9961ea407..fba2a7bc8 100644 --- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs @@ -22,6 +22,7 @@ [app.util.dom :as dom] [app.util.i18n :refer [tr]] [app.util.timers :as timers] + [clojure.set :as set] [okulary.core :as l] [potok.v2.core :as ptk] [rumext.v2 :as mf])) @@ -83,85 +84,106 @@ attribute-labels)] (concat [all-action] single-actions))) +(defn layout-spacing-items [{:keys [token selected-shapes all-attr-labels horizontal-attr-labels vertical-attr-labels on-update-shape]}] + (let [horizontal-attrs (into #{} (keys horizontal-attr-labels)) + vertical-attrs (into #{} (keys vertical-attr-labels)) + attrs (set/union horizontal-attrs vertical-attrs) + {:keys [all-selected? selected-pred shape-ids]} (attribute-actions token selected-shapes attrs) + horizontal-selected? (and + (not all-selected?) + (every? selected-pred horizontal-attrs)) + vertical-selected? (and + (not all-selected?) + (every? selected-pred vertical-attrs)) + multi-items [{:title (tr "labels.all") + :selected? all-selected? + :action (fn [] + (let [props {:attributes attrs + :token token + :shape-ids shape-ids}] + (if all-selected? + (st/emit! (wtch/unapply-token props)) + (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))))} + {:title "Horizontal" + :selected? horizontal-selected? + :action (fn [] + (let [props {:token token + :shape-ids shape-ids} + event (cond + all-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attrs)) + horizontal-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attrs)) + :else (wtch/apply-token (assoc props + :attributes horizontal-attrs + :on-update-shape on-update-shape)))] + (st/emit! event)))} + {:title "Vertical" + :selected? vertical-selected? + :action (fn [] + (let [props {:token token + :shape-ids shape-ids} + event (cond + all-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attrs)) + vertical-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attrs)) + :else (wtch/apply-token (assoc props + :attributes vertical-attrs + :on-update-shape on-update-shape)))] + (st/emit! event)))}] + single-items (map (fn [[attr title]] + (let [same-axis-selected? (cond + (get horizontal-attrs attr) horizontal-selected? + (get vertical-attrs attr) vertical-selected? + :else true) + selected? (and + (not all-selected?) + (not same-axis-selected?) + (selected-pred attr))] + {:title title + :selected? selected? + :action #(let [props {:attributes #{attr} + :token token + :shape-ids shape-ids} + event (cond + all-selected? (-> (assoc props :attributes-to-remove attrs) + (wtch/apply-token)) + selected? (wtch/unapply-token props) + :else (-> (assoc props :on-update-shape on-update-shape) + (wtch/apply-token)))] + (st/emit! event))})) + all-attr-labels)] + (concat multi-items single-items))) + (defn spacing-attribute-actions [{:keys [token selected-shapes] :as context-data}] - (let [on-update-shape-padding wtch/update-layout-padding - padding-attrs {:p1 "Top" - :p2 "Right" - :p3 "Bottom" - :p4 "Left"} - all-padding-attrs (into #{} (keys padding-attrs)) - {:keys [all-selected? selected-pred shape-ids]} (attribute-actions token selected-shapes all-padding-attrs) - horizontal-attributes #{:p2 :p4} - horizontal-padding-selected? (and - (not all-selected?) - (every? selected-pred horizontal-attributes)) - vertical-attributes #{:p1 :p3} - vertical-padding-selected? (and - (not all-selected?) - (every? selected-pred vertical-attributes)) - padding-items [{:title (tr "labels.all") - :selected? all-selected? - :action (fn [] - (let [props {:attributes all-padding-attrs - :token token - :shape-ids shape-ids}] - (if all-selected? - (st/emit! (wtch/unapply-token props)) - (st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-padding))))))} - {:title "Horizontal" - :selected? horizontal-padding-selected? - :action (fn [] - (let [props {:token token - :shape-ids shape-ids} - event (cond - all-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-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-padding)))] - (st/emit! event)))} - {:title "Vertical" - :selected? vertical-padding-selected? - :action (fn [] - (let [props {:token token - :shape-ids shape-ids} - event (cond - 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-padding)))] - (st/emit! event)))}] - single-padding-items (->> padding-attrs - (map (fn [[attr title]] - (let [same-axis-selected? (cond - (get horizontal-attributes attr) horizontal-padding-selected? - (get vertical-attributes attr) vertical-padding-selected? - :else true) - selected? (and - (not all-selected?) - (not same-axis-selected?) - (selected-pred attr))] - {:title title - :selected? selected? - :action #(let [props {:attributes #{attr} - :token token - :shape-ids shape-ids} - event (cond - all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs) - (wtch/apply-token)) - selected? (wtch/unapply-token props) - :else (-> (assoc props :on-update-shape on-update-shape-padding) - (wtch/apply-token)))] - (st/emit! event))})))) + (let [padding-items (layout-spacing-items {:token token + :selected-shapes selected-shapes + :all-attr-labels {:p1 "Padding top" + :p2 "Padding right" + :p3 "Padding bottom" + :p4 "Padding left"} + :horizontal-attr-labels {:p2 "Padding right" + :p4 "Padding left"} + :vertical-attr-labels {:p1 "Padding top" + :p3 "Padding bottom"} + :on-update-shape wtch/update-layout-padding}) + margin-items (layout-spacing-items {:token token + :selected-shapes selected-shapes + :all-attr-labels {:m1 "Margin top" + :m2 "Margin right" + :m3 "Margin bottom" + :m4 "Margin left"} + :horizontal-attr-labels {:m2 "Margin right" + :m4 "Margin left"} + :vertical-attr-labels {:m1 "Margin top" + :m3 "Margin bottom"} + :on-update-shape wtch/update-layout-item-margin}) gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap" :row-gap "Row Gap"} :on-update-shape wtch/update-layout-spacing} context-data)] - (concat padding-items - single-padding-items + (concat gap-items [:separator] - gap-items))) + padding-items + [:separator] + margin-items))) (defn sizing-attribute-actions [context-data] (concat diff --git a/frontend/src/app/main/ui/workspace/tokens/update.cljs b/frontend/src/app/main/ui/workspace/tokens/update.cljs index 9973b544b..b60a8c441 100644 --- a/frontend/src/app/main/ui/workspace/tokens/update.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/update.cljs @@ -3,7 +3,6 @@ [app.common.files.helpers :as cfh] [app.common.types.token :as ctt] [app.main.data.helpers :as dsh] - [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.undo :as dwu] @@ -26,8 +25,8 @@ 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))})) + #{:p1 :p2 :p3 :p4} wtch/update-layout-padding + #{:m1 :m2 :m3 :m4} wtch/update-layout-item-margin #{: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