mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
♻️ Look up head components once and pair switch shapes first
- find-ref-shape, find-content-ref-shape, find-ref-component and the walk that finds the container of a main each found a head's file data and component the same way: the local file or a library, then the component, deleted or not. ctf/head-component gives both, or nil when they are not there. - ctf/get-component-shape, the child of a component by id, shared its name with ctn/get-component-shape, the top copy root of a shape; it is get-component-child. - generate-keep-touched found, in one reduce, the shape in the new copy that each touched child of the old copy relates to, carried the child's overrides into it or kept a swapped child in its place, and collected the parents of the related shapes in a binding named parents-of-swapped, though it held the parent of every related shape. It now pairs the shapes first (the old child, the main shape it refers to, the related new shape, and whether the old child was swapped), then carries each pair in a reduce, and gives the parents from the pairs. The pairing reads the page as it was before the switch, as the reduce did, so the result is the same. No behavior change: the golden masters and the suites are unchanged. Benchmark (lower quantile, ms; baseline / now / ratio): deepest-edit-through-three-levels 35.2 / 16.5 / 0.47 edit-through-a-chain-of-dependents 68.9 / 34.4 / 0.50 library-update-into-copies 83.1 / 37.7 / 0.45 main-edit-among-many-shapes 4.8 / 2.6 / 0.53 main-edit-into-10-copies 1.8 / 0.7 / 0.42 main-edit-into-100-copies 15.4 / 8.4 / 0.55 main-edit-into-1000-copies 153.1 / 77.9 / 0.51 variant-switch-into-copies 32.1 / 18.2 / 0.57 AI-assisted-by: claude-opus-5-5
This commit is contained in:
parent
fa193a07f6
commit
a54124536c
@ -241,7 +241,7 @@
|
||||
|
||||
When walking up through nested component copies, each intermediate component
|
||||
may live in a different library/file. We resolve the right file-data for each
|
||||
component via `ctf/find-component-file` so we can locate components that span
|
||||
component via `ctf/head-component` so we can locate components that span
|
||||
multiple libraries."
|
||||
[container-inst shape-inst shape-main library file libraries component]
|
||||
(loop [shape-inst' shape-inst
|
||||
@ -251,17 +251,11 @@
|
||||
(if (some? (ctn/get-shape container (:id shape-main)))
|
||||
container
|
||||
(let [parent (ctn/get-shape container-inst (:parent-id shape-inst'))
|
||||
shape-inst' (ctn/get-head-shape (:objects container-inst) parent)
|
||||
next-file (some-> shape-inst'
|
||||
:component-file
|
||||
(->> (ctf/find-component-file file libraries)))
|
||||
next-data (some-> next-file :data)
|
||||
component' (when next-data
|
||||
(or (ctkl/get-component next-data (:component-id shape-inst'))
|
||||
(ctkl/get-deleted-component next-data (:component-id shape-inst'))))]
|
||||
(if (some? component')
|
||||
(recur shape-inst' next-data component')
|
||||
nil))))))
|
||||
shape-inst' (ctn/get-head-shape (:objects container-inst) parent)]
|
||||
(when-let [[next-data component'] (when (some? shape-inst')
|
||||
(ctf/head-component file libraries shape-inst'
|
||||
:include-deleted? true))]
|
||||
(recur shape-inst' next-data component')))))))
|
||||
|
||||
(defn- generate-sync-shape-direct
|
||||
"Generate changes to synchronize one shape that is the root of a component
|
||||
|
||||
@ -374,49 +374,31 @@
|
||||
;; Creates a map to quickly find a child of the orig-ref-shape by its shape-path
|
||||
o-ref-shapes-p-map (into {} (map (juxt :id :shape-path)) o-ref-shapes-wp)
|
||||
|
||||
;; Process each touched children of the original-shape
|
||||
[changes parents-of-swapped]
|
||||
(reduce
|
||||
(fn [[changes parent-of-swapped] orig-child-touched]
|
||||
(let [;; The switched shape itself relates to the root of the new one
|
||||
root? (= (:id orig-child-touched) (:id original-shape))
|
||||
;; Each touched child of the original-shape, with the shape it refers
|
||||
;; to in the main and the child of the new-shape that has the same
|
||||
;; shape-path; the switched shape itself relates to the new root
|
||||
pairs
|
||||
(keep (fn [old]
|
||||
(let [root? (= (:id old) (:id original-shape))
|
||||
old-ref (if root?
|
||||
orig-base-ref-shape
|
||||
(find-shape-ref-child-of container libraries old (:id orig-base-ref-shape)))
|
||||
new (get new-shapes-map (get o-ref-shapes-p-map (:id old-ref)))]
|
||||
(when new
|
||||
{:old old :old-ref old-ref :new new
|
||||
:swapped? (and (not root?) (ctk/swapped? old))})))
|
||||
orig-touched)
|
||||
|
||||
swapped? (and (not root?) (ctk/swapped? orig-child-touched))
|
||||
|
||||
;; orig-child-touched is in a copy. Get the referenced shape on the main component
|
||||
orig-ref-shape (if root?
|
||||
orig-base-ref-shape
|
||||
(find-shape-ref-child-of container libraries orig-child-touched (:id orig-base-ref-shape)))
|
||||
|
||||
orig-ref-id (:id orig-ref-shape)
|
||||
|
||||
;; Get the shape path of the referenced main
|
||||
shape-path (get o-ref-shapes-p-map orig-ref-id)
|
||||
|
||||
;; Get its related shape in the children of new-shape: the one that
|
||||
;; has the same shape-path
|
||||
related-shape-in-new (get new-shapes-map shape-path)
|
||||
|
||||
parents-of-swapped (if related-shape-in-new
|
||||
(conj parent-of-swapped (:parent-id related-shape-in-new))
|
||||
parent-of-swapped)
|
||||
;; If there is a related shape, keep its data
|
||||
changes
|
||||
(if related-shape-in-new
|
||||
(if swapped?
|
||||
;; If the orig-child-touched was swapped, keep it
|
||||
(keep-swapped-item changes related-shape-in-new orig-child-touched
|
||||
ldata page libraries)
|
||||
;; If the orig-child-touched wasn't swapped, copy
|
||||
;; the touched attributes into it
|
||||
(clsw/update-attrs-on-switch
|
||||
changes related-shape-in-new orig-child-touched
|
||||
new-shape original-shape orig-ref-shape container))
|
||||
changes)]
|
||||
[changes parents-of-swapped]))
|
||||
[changes []]
|
||||
orig-touched)]
|
||||
[changes parents-of-swapped]))
|
||||
;; A swapped child is kept as it is, and the others carry their
|
||||
;; touched attributes into their new child
|
||||
changes
|
||||
(reduce (fn [changes {:keys [old old-ref new swapped?]}]
|
||||
(if swapped?
|
||||
(keep-swapped-item changes new old ldata page libraries)
|
||||
(clsw/update-attrs-on-switch changes new old new-shape original-shape old-ref container)))
|
||||
changes
|
||||
pairs)]
|
||||
[changes (mapv (comp :parent-id :new) pairs)]))
|
||||
|
||||
(defn- shape-index
|
||||
"Position of `shape-id` among the children of `parent-id`; 0 when it is not
|
||||
|
||||
@ -326,6 +326,17 @@
|
||||
file
|
||||
(get libraries component-file)))
|
||||
|
||||
(defn head-component
|
||||
"The file data and the component of the head `head`, found in the local
|
||||
`file` or in `libraries`, as `[file-data component]`; nil when they are
|
||||
not there."
|
||||
[file libraries head & {:keys [include-deleted?]}]
|
||||
(let [file-data (:data (find-component-file file libraries (:component-file head)))
|
||||
component (when (some? file-data)
|
||||
(ctkl/get-component file-data (:component-id head) include-deleted?))]
|
||||
(when (some? component)
|
||||
[file-data component])))
|
||||
|
||||
(defn get-component
|
||||
"Retrieve a component from a library."
|
||||
[libraries library-id component-id & {:keys [include-deleted?] :or {include-deleted? false}}]
|
||||
@ -373,7 +384,7 @@
|
||||
(ctn/get-shape (:main-instance-id component)))
|
||||
(ctk/get-deleted-component-root component)))
|
||||
|
||||
(defn get-component-shape
|
||||
(defn get-component-child
|
||||
"Retrieve one shape in the component by id. If with-context? is true, add the
|
||||
file and container where the shape resides in its metadata."
|
||||
[file-data component shape-id & {:keys [with-context?] :or {with-context? false}}]
|
||||
@ -400,7 +411,7 @@
|
||||
"Retrieve the shape in the component that is referenced by the instance shape."
|
||||
[file-data component shape & {:keys [with-context?] :or {with-context? false}}]
|
||||
(when (:shape-ref shape)
|
||||
(get-component-shape file-data component (:shape-ref shape) :with-context? with-context?)))
|
||||
(get-component-child file-data component (:shape-ref shape) :with-context? with-context?)))
|
||||
|
||||
(defn get-content-ref-shape
|
||||
"Retrieve the shape in the component whose content the instance shape
|
||||
@ -421,31 +432,25 @@
|
||||
"Locate the nearest component in the local file or libraries, and retrieve the shape
|
||||
referenced by the instance shape."
|
||||
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
|
||||
(let [find-ref-shape-in-head
|
||||
(fn [head-shape]
|
||||
(let [component-file (find-component-file file libraries (:component-file head-shape))
|
||||
component (when (some? component-file)
|
||||
(ctkl/get-component (:data component-file) (:component-id head-shape) include-deleted?))]
|
||||
(when (some? component)
|
||||
(get-ref-shape (:data component-file) component shape :with-context? with-context?))))]
|
||||
(some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) shape))))
|
||||
(some (fn [head]
|
||||
(when-let [[file-data component] (head-component file libraries head
|
||||
:include-deleted? include-deleted?)]
|
||||
(get-ref-shape file-data component shape :with-context? with-context?)))
|
||||
(ctn/get-parent-heads (:objects container) shape)))
|
||||
|
||||
(defn find-content-ref-shape
|
||||
"Retrieve the shape whose content the instance shape follows: the main root
|
||||
of its own component for a swapped head, the referenced shape otherwise."
|
||||
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
|
||||
(if (ctk/swapped? shape)
|
||||
(let [component-file (find-component-file file libraries (:component-file shape))
|
||||
file-data (:data component-file)
|
||||
component (when (some? component-file)
|
||||
(ctkl/get-component file-data (:component-id shape) include-deleted?))
|
||||
root (when (some? component)
|
||||
(get-component-root file-data component))]
|
||||
(cond-> root
|
||||
(and (some? root) with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container (get-component-container file-data component)})))
|
||||
(when-let [[file-data component] (head-component file libraries shape
|
||||
:include-deleted? include-deleted?)]
|
||||
(let [root (get-component-root file-data component)]
|
||||
(cond-> root
|
||||
(and (some? root) with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container (get-component-container file-data component)}))))
|
||||
(find-ref-shape file container libraries shape
|
||||
:include-deleted? include-deleted?
|
||||
:with-context? with-context?)))
|
||||
@ -523,18 +528,12 @@
|
||||
"Locate the nearest component in the local file or libraries that is referenced by the
|
||||
instance shape."
|
||||
[file page libraries shape & {:keys [include-deleted?] :or {include-deleted? false}}]
|
||||
(let [find-ref-component-in-head
|
||||
(fn [head-shape]
|
||||
(let [component-file (find-component-file file libraries (:component-file head-shape))
|
||||
component (when (some? component-file)
|
||||
(ctkl/get-component (:data component-file)
|
||||
(:component-id head-shape)
|
||||
include-deleted?))]
|
||||
(when (some? component)
|
||||
(when (get-ref-shape (:data component-file) component shape)
|
||||
component))))]
|
||||
|
||||
(some find-ref-component-in-head (ctn/get-parent-copy-heads (:objects page) shape))))
|
||||
(some (fn [head]
|
||||
(when-let [[file-data component] (head-component file libraries head
|
||||
:include-deleted? include-deleted?)]
|
||||
(when (get-ref-shape file-data component shape)
|
||||
component)))
|
||||
(ctn/get-parent-copy-heads (:objects page) shape)))
|
||||
|
||||
(declare find-remote-shape*)
|
||||
|
||||
|
||||
@ -249,7 +249,7 @@
|
||||
(t/is (= #{(thi/id :main) (thi/id :main-child)}
|
||||
(set (map :id (ctf/get-component-shapes data component)))))
|
||||
(t/testing "a shape of the component, with the component as its container"
|
||||
(let [child (ctf/get-component-shape data component (thi/id :main-child)
|
||||
(let [child (ctf/get-component-child data component (thi/id :main-child)
|
||||
:with-context? true)]
|
||||
(t/is (= (thi/id :main-child) (:id child)))
|
||||
(t/is (= :component (:type (:container (meta child)))))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user