0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-01 03:49:22 -05:00

Synchronize tokens in components

This commit is contained in:
Andrés Moya 2024-11-19 16:14:02 +01:00 committed by Andrés Moya
parent 2a766a7190
commit 6077ba6690
4 changed files with 92 additions and 23 deletions

View file

@ -1153,7 +1153,7 @@
; We need to trigger a sync if the shape has changed any ; We need to trigger a sync if the shape has changed any
; attribute that participates in components synchronization. ; attribute that participates in components synchronization.
(and (= (:type operation) :set) (and (= (:type operation) :set)
(get ctk/sync-attrs (:attr operation)))) (ctk/component-attr? (:attr operation))))
any-sync? (some need-sync? operations)] any-sync? (some need-sync? operations)]
(when any-sync? (when any-sync?
(if page-id (if page-id

View file

@ -27,6 +27,7 @@
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl] [app.common.types.shape.layout :as ctl]
[app.common.types.token :as cto]
[app.common.types.typography :as cty] [app.common.types.typography :as cty]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[clojure.set :as set] [clojure.set :as set]
@ -1479,6 +1480,44 @@
[{:type :set-remote-synced [{:type :set-remote-synced
:remote-synced (:remote-synced shape)}]})))))) :remote-synced (:remote-synced shape)}]}))))))
(defn- update-tokens
"Token synchronization algorithm. Copy the applied tokens that have changed
in the origin shape to the dest shape (applying or removing as necessary).
Only the given token attributes are synced."
[changes container dest-shape orig-shape token-attrs]
(let [orig-tokens (get orig-shape :applied-tokens {})
dest-tokens (get dest-shape :applied-tokens {})
dest-tokens' (reduce (fn [dest-tokens' token-attr]
(let [orig-token (get orig-tokens token-attr)
dest-token (get dest-tokens token-attr)]
(if (= orig-token dest-token)
dest-tokens'
(if (nil? orig-token)
(dissoc dest-tokens' token-attr)
(assoc dest-tokens' token-attr orig-token)))))
dest-tokens
token-attrs)]
(if (= dest-tokens dest-tokens')
changes
(-> changes
(update :redo-changes conj (make-change
container
{:type :mod-obj
:id (:id dest-shape)
:operations [{:type :set
:attr :applied-tokens
:val dest-tokens'
:ignore-touched true}]}))
(update :undo-changes conj (make-change
container
{:type :mod-obj
:id (:id dest-shape)
:operations [{:type :set
:attr :applied-tokens
:val dest-tokens
:ignore-touched true}]}))))))
(defn- update-attrs (defn- update-attrs
"The main function that implements the attribute sync algorithm. Copy "The main function that implements the attribute sync algorithm. Copy
attributes that have changed in the origin shape to the dest shape. attributes that have changed in the origin shape to the dest shape.
@ -1511,37 +1550,41 @@
(loop [attrs (->> (seq (keys ctk/sync-attrs)) (loop [attrs (->> (seq (keys ctk/sync-attrs))
;; We don't update the flex-child attrs ;; We don't update the flex-child attrs
(remove ctk/swap-keep-attrs) (remove ctk/swap-keep-attrs)
;; We don't do automatic update of the `layout-grid-cells` property. ;; We don't do automatic update of the `layout-grid-cells` property.
(remove #(= :layout-grid-cells %))) (remove #(= :layout-grid-cells %)))
applied-tokens #{}
roperations [] roperations []
uoperations '()] uoperations '()]
(let [attr (first attrs)] (let [attr (first attrs)]
(if (nil? attr) (if (nil? attr)
(if (empty? roperations) (if (and (empty? roperations) (empty? applied-tokens))
changes changes
(let [all-parents (cfh/get-parent-ids (:objects container) (let [all-parents (cfh/get-parent-ids (:objects container)
(:id dest-shape))] (:id dest-shape))]
(-> changes (cond-> changes
(update :redo-changes conj (make-change (seq roperations)
container (-> (update :redo-changes conj (make-change
{:type :mod-obj container
:id (:id dest-shape) {:type :mod-obj
:operations roperations})) :id (:id dest-shape)
(update :redo-changes conj (make-change :operations roperations}))
container (update :redo-changes conj (make-change
{:type :reg-objects container
:shapes all-parents})) {:type :reg-objects
(update :undo-changes conj (make-change :shapes all-parents}))
container (update :undo-changes conj (make-change
{:type :mod-obj container
:id (:id dest-shape) {:type :mod-obj
:operations (vec uoperations)})) :id (:id dest-shape)
(update :undo-changes concat [(make-change :operations (vec uoperations)}))
container (update :undo-changes concat [(make-change
{:type :reg-objects container
:shapes all-parents})])))) {:type :reg-objects
:shapes all-parents})]))
(seq applied-tokens)
(update-tokens container dest-shape origin-shape applied-tokens))))
(let [;; position-data is a special case because can be affected by :geometry-group and :content-group (let [;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data ;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again ;; so it's calculated again
@ -1564,14 +1607,21 @@
:val (get dest-shape attr) :val (get dest-shape attr)
:ignore-touched true} :ignore-touched true}
attr-group (get ctk/sync-attrs attr)] attr-group (get ctk/sync-attrs attr)
token-attrs (cto/shape-attr->token-attrs attr)
applied-tokens' (cond-> applied-tokens
(not (and (touched attr-group)
omit-touched?))
(into token-attrs))]
(if (or (= (get origin-shape attr) (get dest-shape attr)) (if (or (= (get origin-shape attr) (get dest-shape attr))
(and (touched attr-group) omit-touched?)) (and (touched attr-group) omit-touched?))
(recur (next attrs) (recur (next attrs)
applied-tokens'
roperations roperations
uoperations) uoperations)
(recur (next attrs) (recur (next attrs)
applied-tokens'
(conj roperations roperation) (conj roperations roperation)
(conj uoperations uoperation))))))))) (conj uoperations uoperation)))))))))

View file

@ -140,6 +140,14 @@
:layout-item-z-index :layout-item-z-index
:layout-item-align-self}) :layout-item-align-self})
(defn component-attr?
"Check if some attribute is one that is involved in component syncrhonization.
Note that design tokens also are involved, although they go by an alternate
route and thus they are not part of :sync-attrs."
[attr]
(or (get sync-attrs attr)
(= :applied-tokens attr)))
(defn instance-root? (defn instance-root?
"Check if this shape is the head of a top instance." "Check if this shape is the head of a top instance."
[shape] [shape]

View file

@ -163,3 +163,14 @@
::spacing ::spacing
::rotation ::rotation
::dimensions]) ::dimensions])
(defn shape-attr->token-attrs
[shape-attr]
(cond
(= :fills shape-attr) #{:fill}
(= :strokes shape-attr) #{:stroke-color :stroke-width}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}
(spacing-keys shape-attr) #{shape-attr}
(rotation-keys shape-attr) #{shape-attr}))