mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
♻️ Write the touched groups with the :set-touched operation
Two operations set the touched groups of a shape: a :set of :touched, which assigns the value as given, and :set-touched, which keeps them only on a copy and drops an empty set. update-shapes and the undo of add-component wrote the first while their own undo and sync wrote the second. The contract prefers :set-touched; both builders now write it, and an update with only its touched groups changed still records their undo. A :set of :touched is still applied, for changes already stored. Case AE leaves no empty touched set on the shape a detach turned plain; both golden masters record the state again for it. comp-touched-test: an update that sets the touched groups of a copy shape writes one :set-touched operation, and its undo restores them. Benchmark (lower quantile, ms; baseline / now / ratio): deepest-edit-through-three-levels 35.2 / 16.6 / 0.47 edit-through-a-chain-of-dependents 68.9 / 32.8 / 0.48 library-update-into-copies 83.1 / 42.8 / 0.51 main-edit-among-many-shapes 4.8 / 2.3 / 0.48 main-edit-into-10-copies 1.8 / 1.0 / 0.54 main-edit-into-100-copies 15.4 / 8.9 / 0.58 main-edit-into-1000-copies 153.1 / 82.0 / 0.54 variant-switch-into-copies 32.1 / 17.7 / 0.55 AI-assisted-by: claude-opus-5-5
This commit is contained in:
parent
18cdf3b22a
commit
ebcd3c9b03
@ -608,9 +608,16 @@
|
||||
new-val (get new attr)
|
||||
changed? (not= old-val new-val)
|
||||
|
||||
;; the touched groups are written whole, with the
|
||||
;; operation of their own
|
||||
touched? (= attr :touched)
|
||||
|
||||
rops
|
||||
(cond-> rops
|
||||
changed?
|
||||
(and changed? touched?)
|
||||
(conj {:type :set-touched :touched new-val})
|
||||
|
||||
(and changed? (not touched?))
|
||||
(conj {:type :set :attr attr :val new-val
|
||||
:ignore-geometry ignore-geometry?
|
||||
:ignore-touched (if (set? ignore-touched)
|
||||
@ -619,7 +626,7 @@
|
||||
|
||||
uops
|
||||
(cond-> uops
|
||||
changed?
|
||||
(and changed? (not touched?))
|
||||
(conj {:type :set :attr attr :val old-val
|
||||
:ignore-touched true}))]
|
||||
|
||||
@ -642,10 +649,10 @@
|
||||
|
||||
;; a touch also clears :remote-synced
|
||||
uops (cond-> uops
|
||||
(seq uops)
|
||||
(seq rops)
|
||||
(conj {:type :set-touched :touched (:touched old-obj)})
|
||||
|
||||
(and (seq uops) (:remote-synced old-obj))
|
||||
(and (seq rops) (:remote-synced old-obj))
|
||||
(conj {:type :set-remote-synced :remote-synced true}))
|
||||
|
||||
change (cond-> {:type :mod-obj :id id}
|
||||
@ -898,9 +905,8 @@
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref shape)}
|
||||
{:type :set
|
||||
:attr :touched
|
||||
:val (:touched shape)}]})]
|
||||
{:type :set-touched
|
||||
:touched (:touched shape)}]})]
|
||||
(-> changes
|
||||
(update :redo-changes
|
||||
(fn [redo-changes]
|
||||
|
||||
@ -309,6 +309,25 @@
|
||||
(t/is (true? (:remote-synced (ths/get-shape file'' :copy-child))))
|
||||
(t/is (nil? (:touched (ths/get-shape file'' :copy-child))))))
|
||||
|
||||
(t/deftest test-an-update-writes-touched-groups-whole
|
||||
;; an update that sets the touched groups of a copy shape writes them with
|
||||
;; the operation of their own, and its undo gives back the ones it had
|
||||
(let [file (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
page (thf/current-page file)
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(thi/id :copy-child)}
|
||||
#(assoc % :touched #{:shadow-group})
|
||||
(:objects page)
|
||||
{})
|
||||
ops (mapcat :operations (:redo-changes changes))
|
||||
file' (thf/apply-changes file changes)
|
||||
file'' (thf/apply-undo-changes file' changes)]
|
||||
(t/is (= [{:type :set-touched :touched #{:shadow-group}}] ops))
|
||||
(t/is (= #{:shadow-group} (:touched (ths/get-shape file' :copy-child))))
|
||||
(t/is (nil? (:touched (ths/get-shape file'' :copy-child))))))
|
||||
|
||||
(t/deftest test-touched-set-by-an-update-then-its-attrs-touch
|
||||
;; an update sets the touched groups of a copy shape and changes an
|
||||
;; attribute: the attribute touches its group on top of them, whatever the
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
:ad
|
||||
[{:transcript "create-component {:component \"main\", :id sync-main-component}\n create-component {:component \"s1\", :id sync-s1-component}\n make-nested-component {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-0}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-1}\n swap-shape {:target #fn, :to \"s1\", :keep-touched? false}\n update-main {:target #fn, :updated sync-main-copyhead-0}", :states ["9835a328e680007d" "8cc31cfc15b28449" "40c27ac7ca18d972"]}]
|
||||
:ae
|
||||
[{:transcript "create-component {:component \"main\", :id sync-main-component}\n create-component {:component \"s1\", :id sync-s1-component}\n make-nested-component {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-0}\n swap-shape {:target #fn, :to \"s1\", :keep-touched? false}\n detach {:target #fn, :detached sync-main-copyhead-0}\n change-property {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["a3cb16fb335bc929" "c59b7ba151b736fb" "ccd586f1f2b263e4" "07eb2229667eeb92"]}
|
||||
[{:transcript "create-component {:component \"main\", :id sync-main-component}\n create-component {:component \"s1\", :id sync-s1-component}\n make-nested-component {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-0}\n swap-shape {:target #fn, :to \"s1\", :keep-touched? false}\n detach {:target #fn, :detached sync-main-copyhead-0}\n change-property {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["a3cb16fb335bc929" "e10b1283627fbeb6" "9be2dfab826240b0" "1fc0e554196dfd2b"]}
|
||||
{:transcript "create-component {:component \"main\", :id sync-main-component}\n create-component {:component \"s1\", :id sync-s1-component}\n make-nested-component {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-0}\n swap-shape {:target #fn, :to \"s1\", :keep-touched? false}\n duplicate {:target #fn, :new-head 5}\n change-property {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["a3cb16fb335bc929" "d291c9ca9a187b1b" "99bb6214c05d575b" "0694d4feab3cb633"]}]
|
||||
:af
|
||||
[{:transcript "swap-shape {:target :copy-child, :to :other, :keep-touched? false}\n move-to-cell {:board :main-root, :child :main-child, :row 1, :column 3}", :states ["a3bca1c5fd1b9b29" "8a7fa3fde8815d35" "1c786eab89f462da"]}
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
:case-ad-update-main-pushes-a-swapped-child
|
||||
[{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"s1\", :id sync-s1-component}\n {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:component \"main\", :copy sync-main-copyhead-1}\n {:target #fn, :to \"s1\", :keep-touched? false}\n {:target #fn}", :states ["70154c2f90c4d94f" "8276a39e105869aa" "60ae4e382dab157a"]}]
|
||||
:case-ae-detached-or-duplicated-swapped-copy-follows-its-component
|
||||
[{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"s1\", :id sync-s1-component}\n {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :to \"s1\", :keep-touched? false}\n {:target #fn}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["d74e87bca3fa2eb9" "04d085bb07bb3857" "b886602f9de07d6e" "b57c324aaa781800"]}
|
||||
[{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"s1\", :id sync-s1-component}\n {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :to \"s1\", :keep-touched? false}\n {:target #fn}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["d74e87bca3fa2eb9" "ffc66442a5849acd" "ad43d75dcc79bc8a" "6fa02ad66793cf43"]}
|
||||
{:transcript " {:component \"main\", :id sync-main-component}\n {:component \"s1\", :id sync-s1-component}\n {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n {:component \"main\", :copy sync-main-copyhead-0}\n {:target #fn, :to \"s1\", :keep-touched? false}\n {:target #fn, :new-head 5}\n {:target #fn, :property :fills, :value \"#0000ff\"}", :states ["d74e87bca3fa2eb9" "e2162174653b4d7b" "0bd8763796243e1f" "f612d9fde86170a6"]}]
|
||||
:case-af-grid-cell-move-reaches-a-swapped-copy-child
|
||||
[{:transcript " {:target :copy-child, :to :other, :keep-touched? false}\n {:board :main-root, :child :main-child, :row 1, :column 3}", :states ["5a6af6c0b8f86ac3" "ec2294c072f52ae5" "8cd63b096547fb1b"]}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user