Manage overrides on variants switch

This commit is contained in:
Pablo Alba 2025-05-07 09:29:41 +02:00 committed by GitHub
parent ea095a98ba
commit 751bed4117
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 153 additions and 14 deletions

View file

@ -25,6 +25,7 @@
[app.common.types.file :as ctf] [app.common.types.file :as ctf]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[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]
@ -43,6 +44,12 @@
(def log-shape-ids #{}) (def log-shape-ids #{})
(def log-container-ids #{}) (def log-container-ids #{})
(def updatable-attrs (->> (seq (keys ctk/sync-attrs))
;; We don't update the flex-child attrs
(remove ctk/swap-keep-attrs)
;; We don't do automatic update of the `layout-grid-cells` property.
(remove #(= :layout-grid-cells %))))
(defn enabled-shape? (defn enabled-shape?
[id container] [id container]
(or (empty? log-shape-ids) (or (empty? log-shape-ids)
@ -1704,11 +1711,7 @@
origin-shape (reposition-shape origin-shape origin-root dest-root) origin-shape (reposition-shape origin-shape origin-root dest-root)
touched (get dest-shape :touched #{})] touched (get dest-shape :touched #{})]
(loop [attrs (->> (seq (keys ctk/sync-attrs)) (loop [attrs updatable-attrs
;; We don't update the flex-child attrs
(remove ctk/swap-keep-attrs)
;; We don't do automatic update of the `layout-grid-cells` property.
(remove #(= :layout-grid-cells %)))
roperations [] roperations []
uoperations '()] uoperations '()]
@ -1730,6 +1733,47 @@
roperations' roperations'
uoperations'))))))) uoperations')))))))
(defn update-attrs-on-switch
"Copy attributes that have changed in the origin shape to the dest shape. Used on variants switch"
[changes dest-shape origin-shape dest-root origin-root origin-ref-shape container]
(let [;; We need to sync only the position relative to the origin of the component.
;; (see update-attrs for a full explanation)
origin-shape (reposition-shape origin-shape origin-root dest-root)
touched (get dest-shape :touched #{})
touched-origin (get origin-shape :touched #{})]
(loop [attrs updatable-attrs
roperations [{:type :set-touched :touched (:touched origin-shape)}]
uoperations (list {:type :set-touched :touched (:touched dest-shape)})]
(if-let [attr (first attrs)]
(let [attr-group (get ctk/sync-attrs attr)
[roperations' uoperations']
(if (or
;; If the attribute is not valid for the destiny, don't copy it
(not (cts/is-allowed-attr? attr (:type dest-shape)))
;; If the values are already equal, don't copy it
(= (get origin-shape attr) (get dest-shape attr))
;; If the referenced shape on the original component doesn't have the same value, don't copy it
;; Exceptions: :points :selrect and :content can be different
(and
(not (contains? #{:points :selrect :content} attr))
(not= (get origin-ref-shape attr) (get dest-shape attr)))
;; The :content attr cant't be copied to elements of different type
(and (= attr :content) (not= (:type origin-shape) (:type dest-shape)))
;; If the attr is not touched in the origin shape, don't copy it
(not (touched-origin attr-group)))
[roperations uoperations]
(add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))]
(recur (next attrs)
roperations'
uoperations'))
(cond-> changes
(> (count roperations) 1)
(add-update-attr-changes dest-shape container roperations uoperations)
:always
(generate-update-tokens container dest-shape origin-shape touched false))))))
(defn- propagate-attrs (defn- propagate-attrs
"Helper that puts the origin attributes (attrs) into dest but only if "Helper that puts the origin attributes (attrs) into dest but only if
not touched the group or if omit-touched? flag is true" not touched the group or if omit-touched? flag is true"

View file

@ -1,12 +1,30 @@
(ns app.common.logic.variants (ns app.common.logic.variants
(:require (:require
[app.common.files.changes-builder :as pcb] [app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv] [app.common.files.variant :as cfv]
[app.common.logic.libraries :as cll] [app.common.logic.libraries :as cll]
[app.common.logic.variant-properties :as clvp] [app.common.logic.variant-properties :as clvp]
[app.common.types.components-list :as ctcl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.variant :as ctv])) [app.common.types.variant :as ctv]))
(defn- generate-path
[path objects base-id shape]
(let [get-type #(case %
:frame :container
:group :container
:rect :shape
:circle :shape
:bool :shape
:path :shape
%)]
(if (= base-id (:id shape))
path
(generate-path (str path " " (:name shape) (get-type (:type shape))) objects base-id (get objects (:parent-id shape))))))
(defn generate-add-new-variant (defn generate-add-new-variant
[changes shape variant-id new-component-id new-shape-id prop-num] [changes shape variant-id new-component-id new-shape-id prop-num]
(let [data (pcb/get-library-data changes) (let [data (pcb/get-library-data changes)
@ -28,3 +46,30 @@
(-> changes (-> changes
(clvp/generate-update-property-value new-component-id prop-num value) (clvp/generate-update-property-value new-component-id prop-num value)
(pcb/change-parent (:parent-id shape) [new-shape] 0)))) (pcb/change-parent (:parent-id shape) [new-shape] 0))))
(defn generate-keep-touched
[changes new-shape original-shape original-shapes page]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
orig-comp (ctcl/get-component data (:component-id original-shape) true)
new-path-map (into {}
(map (fn [shape] {(generate-path "" objects (:id new-shape) shape) shape}))
(cfh/get-children-with-self objects (:id new-shape)))
orig-touched (filter (comp seq :touched) original-shapes)
orig-objects (into {} (map (juxt :id identity) original-shapes))
container (ctn/make-container page :page)]
(reduce
(fn [changes touched-shape]
(let [path (generate-path "" orig-objects (:id original-shape) touched-shape)
related-shape (get new-path-map path)
orig-ref-shape (ctf/get-ref-shape data orig-comp touched-shape)]
(if related-shape
(cll/update-attrs-on-switch
changes related-shape touched-shape new-shape original-shape orig-ref-shape container)
changes)))
changes
orig-touched)))

View file

@ -397,6 +397,50 @@
(or (some :fill-image fills) (or (some :fill-image fills)
(some :stroke-image strokes))) (some :stroke-image strokes)))
;; Valid attributes
(def ^:private allowed-shape-attrs #{:page-id :component-id :component-file :component-root :main-instance
:remote-synced :shape-ref :touched :blocked :collapsed :locked
:hidden :masked-group :fills :proportion :proportion-lock :constraints-h
:constraints-v :fixed-scroll :r1 :r2 :r3 :r4 :opacity :grids :exports
:strokes :blend-mode :interactions :shadow :blur :grow-type :applied-tokens
:plugin-data})
(def ^:private allowed-shape-geom-attrs #{:x :y :width :height})
(def ^:private allowed-shape-base-attrs #{:id :name :type :selrect :points :transform :transform-inverse :parent-id :frame-id})
(def ^:private allowed-bool-attrs #{:shapes :bool-type :content})
(def ^:private allowed-group-attrs #{:shapes})
(def ^:private allowed-frame-attrs #{:shapes :hide-fill-on-export :show-content :hide-in-viewer})
(def ^:private allowed-image-attrs #{:metadata})
(def ^:private allowed-svg-attrs #{:content})
(def ^:private allowed-path-attrs #{:content})
(def ^:private allowed-text-attrs #{:content})
(def ^:private allowed-generic-attrs (set/union allowed-shape-attrs allowed-shape-geom-attrs allowed-shape-base-attrs))
(defn is-allowed-attr?
[attr type]
(case type
:group (or (contains? allowed-group-attrs attr)
(contains? allowed-generic-attrs attr))
:frame (or (contains? allowed-frame-attrs attr)
(contains? allowed-generic-attrs attr))
:bool (or (contains? allowed-bool-attrs attr)
(contains? allowed-shape-attrs attr)
(contains? allowed-shape-base-attrs attr))
:rect (contains? allowed-generic-attrs attr)
:circle (contains? allowed-generic-attrs attr)
:image (or (contains? allowed-image-attrs attr)
(contains? allowed-generic-attrs attr))
:svg-raw (or (contains? allowed-svg-attrs attr)
(contains? allowed-generic-attrs attr))
:path (or (contains? allowed-path-attrs attr)
(contains? allowed-shape-attrs attr)
(contains? allowed-shape-base-attrs attr))
:text (or (contains? allowed-text-attrs attr)
(contains? allowed-generic-attrs attr))))
;; --- Initialization ;; --- Initialization
(def ^:private minimal-rect-attrs (def ^:private minimal-rect-attrs

View file

@ -16,6 +16,7 @@
[app.common.logging :as log] [app.common.logging :as log]
[app.common.logic.libraries :as cll] [app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls] [app.common.logic.shapes :as cls]
[app.common.logic.variants :as clv]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
@ -988,7 +989,7 @@
(defn component-swap (defn component-swap
"Swaps a component with another one" "Swaps a component with another one"
[shape file-id id-new-component] [shape file-id id-new-component keep-touched?]
(dm/assert! (uuid? id-new-component)) (dm/assert! (uuid? id-new-component))
(dm/assert! (uuid? file-id)) (dm/assert! (uuid? file-id))
(ptk/reify ::component-swap (ptk/reify ::component-swap
@ -996,12 +997,13 @@
(watch [it state _] (watch [it state _]
;; First delete shapes so we have space in the layout otherwise we can have problems ;; First delete shapes so we have space in the layout otherwise we can have problems
;; in the grid creating new rows/columns to make space ;; in the grid creating new rows/columns to make space
(let [libraries (dsh/lookup-libraries state) (let [libraries (dsh/lookup-libraries state)
page (dsh/lookup-page state) page (dsh/lookup-page state)
objects (:objects page) objects (:objects page)
parent (get objects (:parent-id shape)) parent (get objects (:parent-id shape))
ldata (dsh/lookup-file-data state file-id) ldata (dsh/lookup-file-data state file-id)
orig-shapes (when keep-touched? (cfh/get-children-with-self objects (:id shape)))
;; If the target parent is a grid layout we need to pass the target cell ;; If the target parent is a grid layout we need to pass the target cell
target-cell (when (ctl/grid-layout? parent) target-cell (when (ctl/grid-layout? parent)
@ -1018,7 +1020,11 @@
[new-shape all-parents changes] [new-shape all-parents changes]
(-> (pcb/empty-changes it (:id page)) (-> (pcb/empty-changes it (:id page))
(pcb/set-undo-group undo-group) (pcb/set-undo-group undo-group)
(cll/generate-component-swap objects shape ldata page libraries id-new-component index target-cell keep-props-values))] (cll/generate-component-swap objects shape ldata page libraries id-new-component index target-cell keep-props-values))
changes (if keep-touched?
(clv/generate-keep-touched changes new-shape shape orig-shapes page)
changes)]
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
@ -1047,7 +1053,7 @@
:undo-id undo-id) :undo-id undo-id)
(rx/concat (rx/concat
(rx/of (dwu/start-undo-transaction undo-id)) (rx/of (dwu/start-undo-transaction undo-id))
(rx/map #(component-swap % file-id id-new-component) (rx/from shapes)) (rx/map #(component-swap % file-id id-new-component false) (rx/from shapes))
(rx/of (dwu/commit-undo-transaction undo-id)) (rx/of (dwu/commit-undo-transaction undo-id))
(rx/of (dwsp/open-specialized-panel :component-swap))))))) (rx/of (dwsp/open-specialized-panel :component-swap)))))))

View file

@ -327,7 +327,7 @@
(filter #(= (dm/get-in % [:variant-properties pos :value]) val))) (filter #(= (dm/get-in % [:variant-properties pos :value]) val)))
nearest-comp (apply min-key #(ctv/distance target-props (:variant-properties %)) valid-comps)] nearest-comp (apply min-key #(ctv/distance target-props (:variant-properties %)) valid-comps)]
(when nearest-comp (when nearest-comp
(st/emit! (dwl/component-swap shape (:component-file shape) (:id nearest-comp))))))))] (st/emit! (dwl/component-swap shape (:component-file shape) (:id nearest-comp) true)))))))]
[:* [:*
(for [[pos prop] (map vector (range) properties)] (for [[pos prop] (map vector (range) properties)]