mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 10:56:20 +00:00
⚡ Lazy descendant walks in find-valid-parent-and-frame-ids
This commit is contained in:
parent
e29c579d78
commit
4ecf93cba6
@ -482,65 +482,90 @@
|
|||||||
;; or inside its main component if it's in a copy.
|
;; or inside its main component if it's in a copy.
|
||||||
comps-nesting-loop?)))
|
comps-nesting-loop?)))
|
||||||
|
|
||||||
|
(defn make-parent-validation-context
|
||||||
|
"Pre-compute the `children`-derived data used by
|
||||||
|
`find-valid-parent-and-frame-ids`. Build once per gesture and pass
|
||||||
|
the result as the optional `ctx` arg to that function so the
|
||||||
|
per-pointer-move call doesn't re-walk the dragged subtree on each
|
||||||
|
tick. Each cached value is a `delay`; only the ones that the
|
||||||
|
predicate path actually reaches are realized, and the result is
|
||||||
|
then memoized for every later call sharing the same context."
|
||||||
|
[objects children libraries]
|
||||||
|
(let [children-ids (set (map :id children))
|
||||||
|
top-children (remove #(contains? children-ids (:parent-id %)) children)
|
||||||
|
all-main? (every? ctk/main-instance? top-children)
|
||||||
|
get-variant-id (fn [shape]
|
||||||
|
(when (:component-id shape)
|
||||||
|
(-> (get-component-from-shape shape libraries)
|
||||||
|
:variant-id)))
|
||||||
|
descendants (delay (mapcat #(cfh/get-children-with-self objects %) children-ids))
|
||||||
|
any-variant-container-descendant (delay (some ctk/is-variant-container? @descendants))
|
||||||
|
descendants-variant-ids-set (delay (->> @descendants
|
||||||
|
(map get-variant-id)
|
||||||
|
set))
|
||||||
|
any-main-descendant
|
||||||
|
(delay
|
||||||
|
(some
|
||||||
|
(fn [shape]
|
||||||
|
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
|
||||||
|
children))]
|
||||||
|
{:top-children top-children
|
||||||
|
:all-main? all-main?
|
||||||
|
:descendants descendants
|
||||||
|
:any-variant-container-descendant any-variant-container-descendant
|
||||||
|
:descendants-variant-ids-set descendants-variant-ids-set
|
||||||
|
:any-main-descendant any-main-descendant}))
|
||||||
|
|
||||||
(defn find-valid-parent-and-frame-ids
|
(defn find-valid-parent-and-frame-ids
|
||||||
"Navigate trough the ancestors until find one that is valid. Returns [ parent-id frame-id ]"
|
"Navigate trough the ancestors until find one that is valid. Returns [ parent-id frame-id ]"
|
||||||
([parent-id objects children]
|
([parent-id objects children]
|
||||||
(find-valid-parent-and-frame-ids parent-id objects children false nil))
|
(find-valid-parent-and-frame-ids parent-id objects children false nil nil))
|
||||||
([parent-id objects children pasting? libraries]
|
([parent-id objects children pasting? libraries]
|
||||||
(letfn [(get-frame [parent-id]
|
(find-valid-parent-and-frame-ids parent-id objects children pasting? libraries nil))
|
||||||
(if (cfh/frame-shape? objects parent-id) parent-id (get-in objects [parent-id :frame-id])))]
|
([parent-id objects children pasting? libraries ctx]
|
||||||
(let [parent (get objects parent-id)
|
(letfn [(get-frame [pid]
|
||||||
|
(if (cfh/frame-shape? objects pid) pid (get-in objects [pid :frame-id])))]
|
||||||
|
;; The full-subtree walks dominate per-pointer-move cost when
|
||||||
|
;; dragging a layout with many component children. The predicates
|
||||||
|
;; below put cheap ascendant/parent-shape checks first so the
|
||||||
|
;; common drag case (pasting? = false, parent not a main instance,
|
||||||
|
;; no variant ancestors) skips deref entirely. When a `ctx` is
|
||||||
|
;; supplied, the delays inside it are SHARED across the whole
|
||||||
|
;; gesture — once realized for one parent in the walk, every
|
||||||
|
;; subsequent move and recur step reuses the cached result.
|
||||||
|
(let [{:keys [top-children all-main? any-variant-container-descendant
|
||||||
|
descendants-variant-ids-set any-main-descendant]}
|
||||||
|
(or ctx (make-parent-validation-context objects children libraries))]
|
||||||
|
|
||||||
;; We need to check only the top shapes
|
(loop [parent-id parent-id]
|
||||||
children-ids (set (map :id children))
|
(let [parent (get objects parent-id)
|
||||||
top-children (remove #(contains? children-ids (:parent-id %)) children)
|
|
||||||
|
|
||||||
;; We can always move the children to the parent they already have.
|
;; We can always move the children to the parent they already have.
|
||||||
;; But if we are pasting, those are new items, so it is considered a change
|
;; But if we are pasting, those are new items, so it is considered a change
|
||||||
no-changes?
|
no-changes?
|
||||||
(and (every? #(= parent-id (:parent-id %)) top-children)
|
(and (every? #(= parent-id (:parent-id %)) top-children)
|
||||||
(not pasting?))
|
(not pasting?))
|
||||||
|
|
||||||
;; Are all the top-children a main-instance of a component?
|
ascendants (cfh/get-parents-with-self objects parent-id)
|
||||||
all-main?
|
any-main-ascendant (some ctk/main-instance? ascendants)
|
||||||
(every? ctk/main-instance? top-children)
|
any-variant-container-ascendant (some ctk/is-variant-container? ascendants)]
|
||||||
|
|
||||||
ascendants (cfh/get-parents-with-self objects parent-id)
|
(if (or no-changes?
|
||||||
any-main-ascendant (some ctk/main-instance? ascendants)
|
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
|
||||||
any-variant-container-ascendant (some ctk/is-variant-container? ascendants)
|
;; If we are moving (not pasting) into a main component, no descendant can be main
|
||||||
|
(or pasting? (not (ctk/main-instance? parent)) (nil? @any-main-descendant))
|
||||||
get-variant-id (fn [shape]
|
;; Don't allow variant-container inside variant container nor main
|
||||||
(when (:component-id shape)
|
(or (and (not any-variant-container-ascendant) (not any-main-ascendant))
|
||||||
(-> (get-component-from-shape shape libraries)
|
(not @any-variant-container-descendant))
|
||||||
:variant-id)))
|
;; If the parent is a variant-container, all the items should be main
|
||||||
|
(or (not (ctk/is-variant-container? parent)) all-main?)
|
||||||
descendants (mapcat #(cfh/get-children-with-self objects %) children-ids)
|
;; If we are pasting, the parent can't be a "brother" of any of the pasted items,
|
||||||
any-variant-container-descendant (some ctk/is-variant-container? descendants)
|
;; so not have the same variant-id of any descendant
|
||||||
descendants-variant-ids-set (->> descendants
|
(or (not pasting?)
|
||||||
(map get-variant-id)
|
(not (ctk/is-variant? parent))
|
||||||
set)
|
(not (contains? @descendants-variant-ids-set (:variant-id parent))))))
|
||||||
any-main-descendant
|
[parent-id (get-frame parent-id)]
|
||||||
(some
|
(recur (:parent-id parent)))))))))
|
||||||
(fn [shape]
|
|
||||||
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
|
|
||||||
children)]
|
|
||||||
|
|
||||||
(if (or no-changes?
|
|
||||||
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
|
|
||||||
;; If we are moving (not pasting) into a main component, no descendant can be main
|
|
||||||
(or pasting? (nil? any-main-descendant) (not (ctk/main-instance? parent)))
|
|
||||||
;; Don't allow variant-container inside variant container nor main
|
|
||||||
(or (not any-variant-container-descendant)
|
|
||||||
(and (not any-variant-container-ascendant) (not any-main-ascendant)))
|
|
||||||
;; If the parent is a variant-container, all the items should be main
|
|
||||||
(or (not (ctk/is-variant-container? parent)) all-main?)
|
|
||||||
;; If we are pasting, the parent can't be a "brother" of any of the pasted items,
|
|
||||||
;; so not have the same variant-id of any descendant
|
|
||||||
(or (not pasting?)
|
|
||||||
(not (ctk/is-variant? parent))
|
|
||||||
(not (contains? descendants-variant-ids-set (:variant-id parent))))))
|
|
||||||
[parent-id (get-frame parent-id)]
|
|
||||||
(recur (:parent-id parent) objects children pasting? libraries))))))
|
|
||||||
|
|
||||||
;; --- SHAPE UPDATE
|
;; --- SHAPE UPDATE
|
||||||
|
|
||||||
|
|||||||
@ -705,7 +705,16 @@
|
|||||||
(rx/map #(array pos %)))))))]
|
(rx/map #(array pos %)))))))]
|
||||||
(if (empty? shapes)
|
(if (empty? shapes)
|
||||||
(rx/of (finish-transform))
|
(rx/of (finish-transform))
|
||||||
(let [move-stream
|
(let [;; `shapes`/`objects`/`libraries` are stable for the
|
||||||
|
;; whole gesture, so the children-derived subtree
|
||||||
|
;; walks inside `find-valid-parent-and-frame-ids` only
|
||||||
|
;; need to happen once per gesture instead of per
|
||||||
|
;; pointer-move. Build the context here and thread it
|
||||||
|
;; into each call.
|
||||||
|
parent-validation-ctx
|
||||||
|
(ctn/make-parent-validation-context objects shapes libraries)
|
||||||
|
|
||||||
|
move-stream
|
||||||
(->> position
|
(->> position
|
||||||
;; We ask for the snap position but we continue even if the result is not available
|
;; We ask for the snap position but we continue even if the result is not available
|
||||||
(rx/with-latest-from snap-delta)
|
(rx/with-latest-from snap-delta)
|
||||||
@ -720,7 +729,7 @@
|
|||||||
(let [position (gpt/add from-position move-vector)
|
(let [position (gpt/add from-position move-vector)
|
||||||
exclude-frames (if mod? exclude-frames exclude-frames-siblings)
|
exclude-frames (if mod? exclude-frames exclude-frames-siblings)
|
||||||
target-frame (ctst/top-nested-frame objects position exclude-frames)
|
target-frame (ctst/top-nested-frame objects position exclude-frames)
|
||||||
[target-frame _] (ctn/find-valid-parent-and-frame-ids target-frame objects shapes false libraries)
|
[target-frame _] (ctn/find-valid-parent-and-frame-ids target-frame objects shapes false libraries parent-validation-ctx)
|
||||||
flex-layout? (ctl/flex-layout? objects target-frame)
|
flex-layout? (ctl/flex-layout? objects target-frame)
|
||||||
grid-layout? (ctl/grid-layout? objects target-frame)
|
grid-layout? (ctl/grid-layout? objects target-frame)
|
||||||
drop-index (when flex-layout? (gslf/get-drop-index target-frame objects position))
|
drop-index (when flex-layout? (gslf/get-drop-index target-frame objects position))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user