mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
♻️ Use interactive update functions only on user actions
This commit is contained in:
committed by
Andrés Moya
parent
3cdbc27de9
commit
fc5e4a821b
@@ -10,6 +10,7 @@
|
||||
[app.common.files.tokens :as cft]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.shape.token :as ctst]
|
||||
[app.common.types.stroke :as cts]
|
||||
[app.common.types.text :as txt]
|
||||
[app.common.types.token :as ctt]
|
||||
@@ -284,53 +285,71 @@
|
||||
(when (number? value)
|
||||
(generate-text-shape-update {:letter-spacing (str value)} shape-ids page-id))))
|
||||
|
||||
(defn- generate-font-variant-text-shape-update
|
||||
"Generate shape update for either updating `:font-family` or `font-weight`.
|
||||
Try to find the closest weight variant."
|
||||
(defn warn-font-variant-not-found! []
|
||||
(st/emit!
|
||||
(ntf/show {:content (tr "workspace.tokens.font-variant-not-found")
|
||||
:type :toast
|
||||
:level :warning
|
||||
:timeout 7000})))
|
||||
|
||||
(defn- update-closest-font-variant-id-by-weight
|
||||
[txt-attrs target-variant font-id on-mismatch]
|
||||
(let [font (fonts/get-font-data font-id)
|
||||
variant (when font
|
||||
(fonts/find-closest-variant font (:weight target-variant) (:style target-variant)))
|
||||
call-on-mismatch? (when (and (fn? on-mismatch) variant)
|
||||
(or
|
||||
(not= (:font-weight target-variant) (:weight variant))
|
||||
(when (:font-style target-variant)
|
||||
(not= (:font-style target-variant) (:style variant)))))]
|
||||
(when call-on-mismatch?
|
||||
(on-mismatch))
|
||||
(cond-> txt-attrs
|
||||
(:id variant) (assoc :font-variant-id (:id variant)))))
|
||||
|
||||
(defn- generate-font-family-text-shape-update
|
||||
[txt-attrs shape-ids page-id on-mismatch]
|
||||
(let [update-node? (fn [node]
|
||||
(let [not-found-font (= (:font-id txt-attrs) (str uuid/zero))
|
||||
update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
update-fn (fn [node _]
|
||||
(let [font (if (= (:font-id txt-attrs) (str uuid/zero))
|
||||
(fonts/get-font-data (:font-id node))
|
||||
(fonts/get-font-data (:font-id txt-attrs)))
|
||||
variant (when font
|
||||
(fonts/find-closest-variant font (:weight node) (:style node)))
|
||||
update-fn (fn [node find-closest-weight?]
|
||||
(let [font-id (if not-found-font (:font-id node) (:font-id txt-attrs))
|
||||
txt-attrs (cond-> txt-attrs
|
||||
(:id variant) (assoc :font-variant-id (:id variant)))
|
||||
call-on-mismatch? (when (and (fn? on-mismatch) variant)
|
||||
(or
|
||||
(not= (:weight txt-attrs) (:weight variant))
|
||||
(when (:style txt-attrs)
|
||||
(not= (:style txt-attrs) (:style variant)))))]
|
||||
(if (or variant (not font))
|
||||
(do
|
||||
(when call-on-mismatch? (on-mismatch variant))
|
||||
(-> node
|
||||
(d/txt-merge txt-attrs)
|
||||
(cty/remove-typography-from-node)))
|
||||
node)))]
|
||||
find-closest-weight? (update-closest-font-variant-id-by-weight node font-id on-mismatch))]
|
||||
(-> node
|
||||
(d/txt-merge txt-attrs)
|
||||
(cty/remove-typography-from-node))))]
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? update-fn nil)
|
||||
(fn [shape]
|
||||
(txt/update-text-content shape update-node? #(update-fn %1 (ctst/font-weight-applied? shape)) nil))
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))
|
||||
|
||||
(defn- create-font-family-text-attrs
|
||||
[value]
|
||||
(let [font-family (-> (first value)
|
||||
;; Strip quotes around font-family like `"Inter"`
|
||||
(str/trim #"[\"']"))
|
||||
font (some-> font-family
|
||||
(fonts/find-font-family))]
|
||||
(if font
|
||||
{:font-id (:id font)
|
||||
:font-family (:family font)}
|
||||
{:font-id (str uuid/zero)
|
||||
:font-family font-family})))
|
||||
|
||||
(defn update-font-family
|
||||
([value shape-ids attributes] (update-font-family value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(let [font-family (-> (first value)
|
||||
;; Strip quotes around font-family like `"Inter"`
|
||||
(str/trim #"[\"']"))
|
||||
font-family (some-> font-family
|
||||
(fonts/find-font-family))
|
||||
text-attrs (if font-family
|
||||
{:font-id (:id font-family)
|
||||
:font-family (:family font-family)}
|
||||
{:font-id (str uuid/zero)
|
||||
:font-family font-family})]
|
||||
(when text-attrs
|
||||
(generate-font-variant-text-shape-update text-attrs shape-ids page-id nil)))))
|
||||
(when-let [text-attrs (create-font-family-text-attrs value)]
|
||||
(generate-font-family-text-shape-update text-attrs shape-ids page-id nil))))
|
||||
|
||||
(defn update-font-family-interactive
|
||||
([value shape-ids attributes] (update-font-family-interactive value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(when-let [text-attrs (create-font-family-text-attrs value)]
|
||||
(generate-font-family-text-shape-update text-attrs shape-ids page-id warn-font-variant-not-found!))))
|
||||
|
||||
(defn update-font-size
|
||||
([value shape-ids attributes] (update-font-size value shape-ids attributes nil))
|
||||
@@ -360,22 +379,35 @@
|
||||
(st/emit! (ptk/data-event :expand-text-more-options))
|
||||
(update-text-decoration value shape-ids attributes page-id))))
|
||||
|
||||
(defn- generate-font-weight-text-shape-update
|
||||
[font-variant shape-ids page-id on-mismatch]
|
||||
(let [font-variant (assoc font-variant
|
||||
:font-weight (:weight font-variant)
|
||||
:font-style (:style font-variant))
|
||||
update-node? (fn [node]
|
||||
(or (txt/is-text-node? node)
|
||||
(txt/is-paragraph-node? node)))
|
||||
update-fn (fn [node _]
|
||||
(let [txt-attrs (update-closest-font-variant-id-by-weight font-variant font-variant (:font-id node) on-mismatch)]
|
||||
(-> node
|
||||
(d/txt-merge txt-attrs)
|
||||
(cty/remove-typography-from-node))))]
|
||||
(dwsh/update-shapes shape-ids
|
||||
#(txt/update-text-content % update-node? update-fn nil)
|
||||
{:ignore-touched true
|
||||
:page-id page-id})))
|
||||
|
||||
(defn update-font-weight
|
||||
([value shape-ids attributes] (update-font-weight value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(when-let [font-variant (ctt/valid-font-weight-variant value)]
|
||||
(generate-font-variant-text-shape-update font-variant shape-ids page-id nil))))
|
||||
(generate-font-weight-text-shape-update font-variant shape-ids page-id nil))))
|
||||
|
||||
(defn update-font-weight-interactive
|
||||
([value shape-ids attributes] (update-font-weight-interactive value shape-ids attributes nil))
|
||||
([value shape-ids _attributes page-id]
|
||||
(when-let [font-variant (ctt/valid-font-weight-variant value)]
|
||||
(let [on-mismatch #(st/emit! (ntf/show {:content (tr "workspace.tokens.font-variant-not-found")
|
||||
:type :toast
|
||||
:level :warning
|
||||
:timeout 7000}))]
|
||||
(generate-font-variant-text-shape-update font-variant shape-ids page-id on-mismatch)))))
|
||||
(generate-font-weight-text-shape-update font-variant shape-ids page-id warn-font-variant-not-found!))))
|
||||
|
||||
(defn- apply-functions-map
|
||||
"Apply map of functions `fs` to a map of values `vs` using `args`.
|
||||
@@ -404,6 +436,21 @@
|
||||
value
|
||||
[shape-ids attributes page-id])))))
|
||||
|
||||
(defn update-typography-interactive
|
||||
([value shape-ids attributes] (update-typography value shape-ids attributes nil))
|
||||
([value shape-ids attributes page-id]
|
||||
(when (map? value)
|
||||
(rx/merge
|
||||
(apply-functions-map
|
||||
{:font-size update-font-size
|
||||
:font-family update-font-family-interactive
|
||||
:font-weight update-font-weight-interactive
|
||||
:letter-spacing update-letter-spacing
|
||||
:text-case update-text-case
|
||||
:text-decoration update-text-decoration-interactive}
|
||||
value
|
||||
[shape-ids attributes page-id])))))
|
||||
|
||||
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
|
||||
|
||||
(defn apply-token
|
||||
@@ -579,7 +626,7 @@
|
||||
:font-family
|
||||
{:title "Font Family"
|
||||
:attributes ctt/font-family-keys
|
||||
:on-update-shape update-font-family
|
||||
:on-update-shape update-font-family-interactive
|
||||
:modal {:key :tokens/font-family
|
||||
:fields [{:label "Font Family"
|
||||
:key :font-family}]}}
|
||||
|
||||
@@ -22,38 +22,6 @@
|
||||
[clojure.set :as set]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; Constants -------------------------------------------------------------------
|
||||
|
||||
(def ^:private filter-existing-values? false)
|
||||
|
||||
(def ^:private attributes->shape-update
|
||||
{ctt/border-radius-keys dwta/update-shape-radius-for-corners
|
||||
ctt/color-keys dwta/update-fill-stroke
|
||||
ctt/stroke-width-keys dwta/update-stroke-width
|
||||
ctt/sizing-keys dwta/update-shape-dimensions
|
||||
ctt/opacity-keys dwta/update-opacity
|
||||
#{:line-height} dwta/update-line-height
|
||||
#{:font-size} dwta/update-font-size
|
||||
#{:letter-spacing} dwta/update-letter-spacing
|
||||
#{:font-family} dwta/update-font-family
|
||||
#{:text-case} dwta/update-text-case
|
||||
#{:text-decoration} dwta/update-text-decoration
|
||||
#{:font-weight} dwta/update-font-weight
|
||||
#{:typography} dwta/update-typography
|
||||
#{:x :y} dwta/update-shape-position
|
||||
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
|
||||
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
|
||||
#{:column-gap :row-gap} dwta/update-layout-spacing
|
||||
#{:width :height} dwta/update-shape-dimensions
|
||||
#{:layout-item-min-w :layout-item-min-h :layout-item-max-w :layout-item-max-h} dwta/update-layout-sizing-limits
|
||||
ctt/rotation-keys dwta/update-rotation})
|
||||
|
||||
(def attribute-actions-map
|
||||
(reduce
|
||||
(fn [acc [ks action]]
|
||||
(into acc (map (fn [k] [k action]) ks)))
|
||||
{} attributes->shape-update))
|
||||
|
||||
;; Helpers ---------------------------------------------------------------------
|
||||
|
||||
;; TODO: see if this can be replaced by more standard functions
|
||||
@@ -67,6 +35,56 @@
|
||||
([a b & rest]
|
||||
(reduce deep-merge a (cons b rest))))
|
||||
|
||||
(defn- flatten-set-keyed-map
|
||||
"Flattens a map where the keys are sets of keywords."
|
||||
[m into-m]
|
||||
(reduce
|
||||
(fn [acc [ks action]]
|
||||
(into acc (map (fn [k] [k action]) ks)))
|
||||
into-m m))
|
||||
|
||||
;; Constants -------------------------------------------------------------------
|
||||
|
||||
(def ^:private filter-existing-values? false)
|
||||
|
||||
(def ^:private attributes->shape-update
|
||||
{ctt/border-radius-keys dwta/update-shape-radius-for-corners
|
||||
ctt/color-keys dwta/update-fill-stroke
|
||||
ctt/stroke-width-keys dwta/update-stroke-width
|
||||
ctt/sizing-keys dwta/update-shape-dimensions
|
||||
ctt/opacity-keys dwta/update-opacity
|
||||
ctt/rotation-keys dwta/update-rotation
|
||||
|
||||
;; Typography
|
||||
ctt/font-family-keys dwta/update-font-family
|
||||
ctt/font-size-keys dwta/update-font-size
|
||||
ctt/font-weight-keys dwta/update-font-weight
|
||||
ctt/letter-spacing-keys dwta/update-letter-spacing
|
||||
ctt/text-case-keys dwta/update-text-case
|
||||
ctt/text-decoration-keys dwta/update-text-decoration
|
||||
ctt/typography-token-keys dwta/update-typography
|
||||
#{:line-height} dwta/update-line-height
|
||||
|
||||
;; Layout
|
||||
#{:x :y} dwta/update-shape-position
|
||||
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
|
||||
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin
|
||||
#{:column-gap :row-gap} dwta/update-layout-spacing
|
||||
#{:width :height} dwta/update-shape-dimensions
|
||||
#{:layout-item-min-w :layout-item-min-h :layout-item-max-w :layout-item-max-h} dwta/update-layout-sizing-limits})
|
||||
|
||||
(def ^:private attribute-actions-map
|
||||
(flatten-set-keyed-map attributes->shape-update {}))
|
||||
|
||||
(def ^:private interactive-attributes->shape-update
|
||||
{ctt/font-family-keys dwta/update-font-family-interactive
|
||||
ctt/font-weight-keys dwta/update-font-weight-interactive
|
||||
ctt/text-decoration-keys dwta/update-text-decoration-interactive
|
||||
ctt/typography-token-keys dwta/update-typography-interactive})
|
||||
|
||||
(def ^:private interactive-attribute-actions-map
|
||||
(flatten-set-keyed-map interactive-attributes->shape-update attribute-actions-map))
|
||||
|
||||
;; Data flows ------------------------------------------------------------------
|
||||
|
||||
(defn- invert-collect-key-vals
|
||||
@@ -130,19 +148,26 @@
|
||||
|
||||
[tokens frame-ids text-ids])))
|
||||
|
||||
(defn- actionize-shapes-update-info [page-id shapes-update-info]
|
||||
(mapcat (fn [[attrs update-infos]]
|
||||
(let [action (some attribute-actions-map attrs)]
|
||||
(assert (fn? action) "missing action function on attributes->shape-update")
|
||||
(map
|
||||
(fn [[v shape-ids]]
|
||||
(action v shape-ids attrs page-id))
|
||||
update-infos)))
|
||||
shapes-update-info))
|
||||
(defn- actionize-shapes-update-info
|
||||
[page-id shapes-update-info interactive?]
|
||||
(let [attribute-actions (if interactive?
|
||||
interactive-attribute-actions-map
|
||||
attribute-actions-map)]
|
||||
(mapcat (fn [[attrs update-infos]]
|
||||
(let [action (some attribute-actions attrs)]
|
||||
(assert (fn? action) "missing action function on attributes->shape-update")
|
||||
(map
|
||||
(fn [[v shape-ids]]
|
||||
(action v shape-ids attrs page-id))
|
||||
update-infos)))
|
||||
shapes-update-info)))
|
||||
|
||||
(defn propagate-tokens
|
||||
"Propagate tokens values to all shapes where they are applied"
|
||||
[state resolved-tokens]
|
||||
"Propagate tokens values to all shapes where they are applied
|
||||
|
||||
Pass `interactive?` to indicate the propagation was triggered by a user interaction
|
||||
and should use update functions that may execute ui side-effects like showing warnings."
|
||||
[state resolved-tokens interactive?]
|
||||
(let [file-id (get state :current-file-id)
|
||||
current-page-id (get state :current-page-id)
|
||||
fdata (dsh/lookup-file-data state file-id)
|
||||
@@ -162,7 +187,7 @@
|
||||
(collect-shapes-update-info resolved-tokens (:objects page))
|
||||
|
||||
actions
|
||||
(actionize-shapes-update-info page-id attrs)
|
||||
(actionize-shapes-update-info page-id attrs interactive?)
|
||||
|
||||
;; Composed updates return observables and need to be executed differently
|
||||
{:keys [observable normal]} (group-by #(if (rx/observable? %) :observable :normal) actions)]
|
||||
@@ -193,7 +218,11 @@
|
||||
(l/inf :status "END" :hint "propagate-tokens" :elapsed elapsed)))))))
|
||||
|
||||
(defn propagate-workspace-tokens
|
||||
[]
|
||||
"Updates styles for tokens.
|
||||
|
||||
Pass `interactive?` to indicate the propagation was triggered by a user interaction
|
||||
and should use update functions that may execute ui side-effects like showing warnings."
|
||||
[& {:keys [interactive?]}]
|
||||
(ptk/reify ::propagate-workspace-tokens
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
@@ -205,5 +234,5 @@
|
||||
(let [undo-id (js/Symbol)]
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id :timeout false))
|
||||
(propagate-tokens state sd-tokens)
|
||||
(propagate-tokens state sd-tokens interactive?)
|
||||
(rx/of (dwu/commit-undo-transaction undo-id)))))))))))
|
||||
|
||||
@@ -469,7 +469,7 @@ custom-input-token-value-props: Custom props passed to the custom-input-token-va
|
||||
{:name final-name
|
||||
:value (:value valid-token)
|
||||
:description final-description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(dwtp/propagate-workspace-tokens :interactive? true)
|
||||
(modal/hide)))))))))
|
||||
|
||||
on-delete-token
|
||||
|
||||
@@ -908,7 +908,7 @@
|
||||
(t/is (= (:typography (:applied-tokens text-1')) "typography.heading"))
|
||||
|
||||
(t/is (= (:font-size style-text-blocks) "24"))
|
||||
(t/is (= (:font-weight style-text-blocks) "400"))
|
||||
(t/is (= (:font-weight style-text-blocks) "700"))
|
||||
(t/is (= (:font-family style-text-blocks) "sourcesanspro"))
|
||||
(t/is (= (:letter-spacing style-text-blocks) "2"))
|
||||
(t/is (= (:text-transform style-text-blocks) "uppercase"))
|
||||
|
||||
Reference in New Issue
Block a user