mirror of
https://github.com/penpot/penpot.git
synced 2025-07-31 10:28:32 +02:00
✨ Fix spacing token for frame children
This commit is contained in:
parent
f7627e515a
commit
1f15e9b81e
6 changed files with 182 additions and 106 deletions
|
@ -92,19 +92,32 @@
|
|||
|
||||
(def opacity-keys (schema-keys schema:opacity))
|
||||
|
||||
(def ^:private schema:spacing
|
||||
(def ^:private schema:spacing-gap
|
||||
[:map
|
||||
[:row-gap {:optional true} token-name-ref]
|
||||
[:column-gap {:optional true} token-name-ref]
|
||||
[:column-gap {:optional true} token-name-ref]])
|
||||
|
||||
(def ^:private schema:spacing-padding
|
||||
[:map
|
||||
[:p1 {:optional true} token-name-ref]
|
||||
[:p2 {:optional true} token-name-ref]
|
||||
[:p3 {:optional true} token-name-ref]
|
||||
[:p4 {:optional true} token-name-ref]
|
||||
[:p4 {:optional true} token-name-ref]])
|
||||
|
||||
(def ^:private schema:spacing-margin
|
||||
[:map
|
||||
[:m1 {:optional true} token-name-ref]
|
||||
[:m2 {:optional true} token-name-ref]
|
||||
[:m3 {:optional true} token-name-ref]
|
||||
[:m4 {:optional true} token-name-ref]])
|
||||
|
||||
(def ^:private schema:spacing
|
||||
(reduce mu/union [schema:spacing-gap
|
||||
schema:spacing-padding
|
||||
schema:spacing-margin]))
|
||||
|
||||
(def spacing-margin-keys (schema-keys schema:spacing-margin))
|
||||
|
||||
(def spacing-keys (schema-keys schema:spacing))
|
||||
|
||||
(def ^:private schema:dimensions
|
||||
|
|
|
@ -31,88 +31,6 @@
|
|||
|
||||
(declare token-properties)
|
||||
|
||||
;; 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))
|
||||
(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)
|
||||
|
||||
shape-ids (or (->> (select-keys objects shape-ids)
|
||||
(filter (fn [[_ shape]]
|
||||
(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)]
|
||||
(rx/of
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"}))
|
||||
(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
|
||||
(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 % (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]}]
|
||||
(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 all-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})))))))
|
||||
|
||||
;; Events to update the value of attributes with applied tokens ---------------------------------------------------------
|
||||
|
||||
;; (note that dwsh/update-shapes function returns an event)
|
||||
|
@ -380,6 +298,123 @@
|
|||
{:ignore-touched true
|
||||
:page-id page-id})))))
|
||||
|
||||
;; 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))
|
||||
(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)]
|
||||
(rx/of
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "apply-tokens"}))
|
||||
(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
|
||||
(on-update-shape resolved-value shape-ids attributes))
|
||||
(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]}]
|
||||
(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 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 attributes
|
||||
:token token
|
||||
:shape-ids shape-ids
|
||||
:on-update-shape on-update-shape}))))))))
|
||||
|
||||
;; Map token types to different properties used along the cokde ---------------------------------------------
|
||||
|
||||
;; FIXME: the values should be lazy evaluated, probably a function,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.config :as cf]
|
||||
|
@ -61,6 +62,10 @@
|
|||
(mf/with-memo [selected objects]
|
||||
(into [] (keep (d/getf objects)) selected))
|
||||
|
||||
is-selected-inside-layout
|
||||
(mf/with-memo [selected-shapes objects]
|
||||
(some #(ctsl/any-layout-immediate-child? objects %) selected-shapes))
|
||||
|
||||
active-theme-tokens
|
||||
(mf/with-memo [tokens-lib]
|
||||
(if tokens-lib
|
||||
|
@ -148,6 +153,7 @@
|
|||
:is-open (get open-status type false)
|
||||
:type type
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens'
|
||||
:tokens tokens}]))
|
||||
|
||||
|
@ -155,5 +161,6 @@
|
|||
[:> token-group* {:key (name type)
|
||||
:type type
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout :is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens'
|
||||
:tokens []}])]))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.modal :as modal]
|
||||
|
@ -34,11 +35,13 @@
|
|||
(some #(contains? m %) ks))
|
||||
|
||||
(defn clean-separators
|
||||
"Cleans up `:separator` inside of `items`
|
||||
Will clean consecutive items like `[:separator :separator {}]`
|
||||
And will return nil for lists consisting only of `:separator` items."
|
||||
"Cleans up `:separator` inside of `items` with these rules:
|
||||
- Clean consecutive items like `[:separator :separator {}]`
|
||||
- Returns nil for lists consisting only of `:separator` items.
|
||||
- Removes `:separator` at the beginning of the `items`"
|
||||
[items]
|
||||
(let [items' (dedupe items)]
|
||||
(let [items' (->> (dedupe items)
|
||||
(drop-while #(= % :separator)))]
|
||||
(when-not (every? #(= % :separator) items')
|
||||
items')))
|
||||
|
||||
|
@ -190,7 +193,7 @@
|
|||
|
||||
|
||||
|
||||
(defn spacing-attribute-actions [{:keys [token selected-shapes allowed-shape-attributes] :as context-data}]
|
||||
(defn spacing-attribute-actions [{:keys [token selected-shapes allowed-shape-attributes is-selected-inside-layout] :as context-data}]
|
||||
(let [padding-attr-labels {:p1 "Padding top"
|
||||
:p2 "Padding right"
|
||||
:p3 "Padding bottom"
|
||||
|
@ -209,7 +212,9 @@
|
|||
:m2 "Margin right"
|
||||
:m3 "Margin bottom"
|
||||
:m4 "Margin left"}
|
||||
margin-items (when (key-in-map? allowed-shape-attributes margin-attr-labels)
|
||||
margin-items (when (or
|
||||
is-selected-inside-layout
|
||||
(key-in-map? allowed-shape-attributes margin-attr-labels))
|
||||
(layout-spacing-items {:token token
|
||||
:selected-shapes selected-shapes
|
||||
:all-attr-labels margin-attr-labels
|
||||
|
@ -224,11 +229,13 @@
|
|||
:hint (tr "workspace.tokens.gaps")
|
||||
:on-update-shape dwta/update-layout-spacing}
|
||||
context-data)]
|
||||
(concat gap-items
|
||||
(when padding-items [:separator])
|
||||
padding-items
|
||||
(when margin-items [:separator])
|
||||
margin-items)))
|
||||
(->> (concat
|
||||
gap-items
|
||||
[:separator]
|
||||
padding-items
|
||||
[:separator]
|
||||
margin-items)
|
||||
(clean-separators))))
|
||||
|
||||
(defn sizing-attribute-actions [context-data]
|
||||
(->>
|
||||
|
@ -446,9 +453,17 @@
|
|||
|
||||
(mf/defc token-context-menu-tree
|
||||
[{:keys [width errors] :as mdata}]
|
||||
(let [objects (mf/deref refs/workspace-page-objects)
|
||||
(let [objects (mf/deref refs/workspace-page-objects)
|
||||
selected (mf/deref refs/selected-shapes)
|
||||
selected-shapes (into [] (keep (d/getf objects)) selected)
|
||||
|
||||
selected-shapes
|
||||
(mf/with-memo [selected objects]
|
||||
(into [] (keep (d/getf objects)) selected))
|
||||
|
||||
is-selected-inside-layout
|
||||
(mf/with-memo [selected-shapes objects]
|
||||
(some #(ctsl/any-layout-immediate-child? objects %) selected-shapes))
|
||||
|
||||
token-name (:token-name mdata)
|
||||
token (mf/deref (refs/workspace-token-in-selected-set token-name))
|
||||
selected-token-set-name (mf/deref refs/selected-token-set-name)]
|
||||
|
@ -457,7 +472,8 @@
|
|||
:token token
|
||||
:errors errors
|
||||
:selected-token-set-name selected-token-set-name
|
||||
:selected-shapes selected-shapes}]]))
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout}]]))
|
||||
|
||||
(mf/defc token-context-menu
|
||||
[]
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
(mf/defc token-group*
|
||||
{::mf/private true}
|
||||
[{:keys [type tokens selected-shapes active-theme-tokens is-open]}]
|
||||
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens is-open]}]
|
||||
(let [{:keys [modal title]}
|
||||
(get dwta/token-properties type)
|
||||
editing-ref (mf/deref refs/workspace-editor-state)
|
||||
|
@ -115,6 +115,7 @@
|
|||
{:key (:name token)
|
||||
:token token
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:on-click on-token-pill-click
|
||||
:on-context-menu on-context-menu}])]])]]))
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
[app.main.ui.ds.foundations.utilities.token.token-status :refer [token-status-icon*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -164,17 +165,20 @@
|
|||
(cft/shapes-applied-all? ids-by-attributes shape-ids attributes)))
|
||||
|
||||
(defn attributes-match-selection?
|
||||
[selected-shapes attrs]
|
||||
(some (fn [shape]
|
||||
(ctt/any-appliable-attr? attrs (:type shape)))
|
||||
selected-shapes))
|
||||
[selected-shapes attrs & {:keys [selected-inside-layout?]}]
|
||||
(or
|
||||
;; Edge-case for allowing margin attribute on shapes inside layout parent
|
||||
(and selected-inside-layout? (set/subset? ctt/spacing-margin-keys attrs))
|
||||
(some (fn [shape]
|
||||
(ctt/any-appliable-attr? attrs (:type shape)))
|
||||
selected-shapes)))
|
||||
|
||||
(def token-types-with-status-icon
|
||||
#{:color :border-radius :rotation :sizing :dimensions :opacity :spacing :stroke-width})
|
||||
|
||||
(mf/defc token-pill*
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [on-click token on-context-menu selected-shapes active-theme-tokens]}]
|
||||
[{:keys [on-click token on-context-menu selected-shapes is-selected-inside-layout active-theme-tokens]}]
|
||||
(let [{:keys [name value errors type]} token
|
||||
|
||||
has-selected? (pos? (count selected-shapes))
|
||||
|
@ -201,7 +205,7 @@
|
|||
has-selected?
|
||||
(not applied?)
|
||||
(not half-applied?)
|
||||
(not (attributes-match-selection? selected-shapes attributes)))
|
||||
(not (attributes-match-selection? selected-shapes attributes {:selected-inside-layout? is-selected-inside-layout})))
|
||||
|
||||
;; FIXME: move to context or props
|
||||
can-edit? (:can-edit (deref refs/permissions))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue