mirror of
https://github.com/penpot/penpot.git
synced 2026-09-06 20:18:39 +00:00
⚡ Events enhancements
* ⚡ Compute moved-subtree data once in find-valid-parent-and-frame-ids * ⚡ Smooth WASM drag preview stream * ⚡ Limit WASM outline modifier application
This commit is contained in:
parent
34cc0e9d56
commit
528d006b8d
@ -491,60 +491,50 @@
|
|||||||
([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))
|
||||||
([parent-id objects children pasting? libraries]
|
([parent-id objects children pasting? libraries]
|
||||||
(letfn [(get-frame [parent-id]
|
(letfn [(get-frame [pid]
|
||||||
(if (cfh/frame-shape? objects parent-id) parent-id (get-in objects [parent-id :frame-id])))]
|
(if (cfh/frame-shape? objects pid) pid (get-in objects [pid :frame-id])))]
|
||||||
(let [parent (get objects parent-id)
|
;; `descendants`, variant-id set, etc. depend only on the moved shapes, not on the
|
||||||
|
;; candidate parent. Computing them once per drag (this fn is hot during move)
|
||||||
;; We need to check only the top shapes
|
;; avoids O(depth * subtree) work when walking invalid ancestors — common with
|
||||||
children-ids (set (map :id children))
|
;; variants and nested components.
|
||||||
|
(let [children-ids (into #{} (map :id) children)
|
||||||
top-children (remove #(contains? children-ids (:parent-id %)) children)
|
top-children (remove #(contains? children-ids (:parent-id %)) children)
|
||||||
|
all-main? (every? ctk/main-instance? top-children)
|
||||||
;; We can always move the children to the parent they already have.
|
get-variant-id
|
||||||
;; But if we are pasting, those are new items, so it is considered a change
|
(fn [shape]
|
||||||
no-changes?
|
(when (:component-id shape)
|
||||||
(and (every? #(= parent-id (:parent-id %)) top-children)
|
(-> (get-component-from-shape shape libraries)
|
||||||
(not pasting?))
|
:variant-id)))
|
||||||
|
|
||||||
;; Are all the top-children a main-instance of a component?
|
|
||||||
all-main?
|
|
||||||
(every? ctk/main-instance? top-children)
|
|
||||||
|
|
||||||
ascendants (cfh/get-parents-with-self objects parent-id)
|
|
||||||
any-main-ascendant (some ctk/main-instance? ascendants)
|
|
||||||
any-variant-container-ascendant (some ctk/is-variant-container? ascendants)
|
|
||||||
|
|
||||||
get-variant-id (fn [shape]
|
|
||||||
(when (:component-id shape)
|
|
||||||
(-> (get-component-from-shape shape libraries)
|
|
||||||
:variant-id)))
|
|
||||||
|
|
||||||
descendants (mapcat #(cfh/get-children-with-self objects %) children-ids)
|
descendants (mapcat #(cfh/get-children-with-self objects %) children-ids)
|
||||||
any-variant-container-descendant (some ctk/is-variant-container? descendants)
|
any-variant-container-descendant (some ctk/is-variant-container? descendants)
|
||||||
descendants-variant-ids-set (->> descendants
|
descendants-variant-ids-set (into #{} (map get-variant-id) descendants)
|
||||||
(map get-variant-id)
|
;; Same as (some #(some ctk/main-instance? (cfh/get-children-with-self objects (:id %)))
|
||||||
set)
|
;; children) but a single walk over `descendants`.
|
||||||
any-main-descendant
|
any-main-descendant (some ctk/main-instance? descendants)]
|
||||||
(some
|
(loop [parent-id parent-id]
|
||||||
(fn [shape]
|
(let [parent (get objects parent-id)
|
||||||
(some ctk/main-instance? (cfh/get-children-with-self objects (:id shape))))
|
no-changes?
|
||||||
children)]
|
(and (every? #(= parent-id (:parent-id %)) top-children)
|
||||||
|
(not pasting?))
|
||||||
(if (or no-changes?
|
ascendants (cfh/get-parents-with-self objects parent-id)
|
||||||
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
|
any-main-ascendant (some ctk/main-instance? ascendants)
|
||||||
;; If we are moving (not pasting) into a main component, no descendant can be main
|
any-variant-container-ascendant (some ctk/is-variant-container? ascendants)]
|
||||||
(or pasting? (nil? any-main-descendant) (not (ctk/main-instance? parent)))
|
(if (or no-changes?
|
||||||
;; Don't allow variant-container inside variant container nor main
|
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
|
||||||
(or (not any-variant-container-descendant)
|
;; If we are moving (not pasting) into a main component, no descendant can be main
|
||||||
(and (not any-variant-container-ascendant) (not any-main-ascendant)))
|
(or pasting? (nil? any-main-descendant) (not (ctk/main-instance? parent)))
|
||||||
;; If the parent is a variant-container, all the items should be main
|
;; Don't allow variant-container inside variant container nor main
|
||||||
(or (not (ctk/is-variant-container? parent)) all-main?)
|
(or (not any-variant-container-descendant)
|
||||||
;; If we are pasting, the parent can't be a "brother" of any of the pasted items,
|
(and (not any-variant-container-ascendant) (not any-main-ascendant)))
|
||||||
;; so not have the same variant-id of any descendant
|
;; If the parent is a variant-container, all the items should be main
|
||||||
(or (not pasting?)
|
(or (not (ctk/is-variant-container? parent)) all-main?)
|
||||||
(not (ctk/is-variant? parent))
|
;; If we are pasting, the parent can't be a "brother" of any of the pasted items,
|
||||||
(not (contains? descendants-variant-ids-set (:variant-id parent))))))
|
;; so not have the same variant-id of any descendant
|
||||||
[parent-id (get-frame parent-id)]
|
(or (not pasting?)
|
||||||
(recur (:parent-id parent) objects children pasting? libraries))))))
|
(not (ctk/is-variant? parent))
|
||||||
|
(not (contains? descendants-variant-ids-set (:variant-id parent))))))
|
||||||
|
[parent-id (get-frame parent-id)]
|
||||||
|
(recur (:parent-id parent)))))))))
|
||||||
|
|
||||||
;; --- SHAPE UPDATE
|
;; --- SHAPE UPDATE
|
||||||
|
|
||||||
|
|||||||
@ -653,6 +653,10 @@
|
|||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [prev-cell-data (volatile! nil)
|
(let [prev-cell-data (volatile! nil)
|
||||||
|
;; Cache the resolved valid parent while hovering the same raw target frame.
|
||||||
|
;; `find-valid-parent-and-frame-ids` may walk many ancestors for variants/components,
|
||||||
|
;; and the result is stable during the gesture (objects/libraries are constant here).
|
||||||
|
find-valid-for-raw-cache (volatile! {:raw nil :pair nil})
|
||||||
page-id (:current-page-id state)
|
page-id (:current-page-id state)
|
||||||
libraries (dsh/lookup-libraries state)
|
libraries (dsh/lookup-libraries state)
|
||||||
objects (dsh/lookup-page-objects state page-id)
|
objects (dsh/lookup-page-objects state page-id)
|
||||||
@ -713,15 +717,22 @@
|
|||||||
(fn [[move-vector mod?]]
|
(fn [[move-vector mod?]]
|
||||||
(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)
|
raw-target (ctst/top-nested-frame objects position exclude-frames)
|
||||||
[target-frame _] (ctn/find-valid-parent-and-frame-ids target-frame objects shapes false libraries)
|
cache @find-valid-for-raw-cache
|
||||||
|
[target-frame _]
|
||||||
|
(if (= raw-target (:raw cache))
|
||||||
|
(:pair cache)
|
||||||
|
(let [pair (ctn/find-valid-parent-and-frame-ids raw-target objects shapes false libraries)]
|
||||||
|
(vreset! find-valid-for-raw-cache {:raw raw-target :pair pair})
|
||||||
|
pair))
|
||||||
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))
|
||||||
cell-data (when (and grid-layout? (not mod?)) (get-drop-cell target-frame objects position))]
|
cell-data (when (and grid-layout? (not mod?)) (get-drop-cell target-frame objects position))]
|
||||||
(array move-vector target-frame drop-index cell-data))))
|
(array move-vector target-frame drop-index cell-data))))
|
||||||
|
|
||||||
(rx/take-until stopper))
|
(rx/take-until stopper)
|
||||||
|
(rx/share))
|
||||||
|
|
||||||
modifiers-stream
|
modifiers-stream
|
||||||
(->> move-stream
|
(->> move-stream
|
||||||
@ -761,6 +772,9 @@
|
|||||||
(rx/merge
|
(rx/merge
|
||||||
(->> modifiers-stream
|
(->> modifiers-stream
|
||||||
(rx/take-until duplicate-stopper)
|
(rx/take-until duplicate-stopper)
|
||||||
|
;; Sample at a fixed cadence to keep preview smooth. Unlike a throttle,
|
||||||
|
;; this tends to avoid perceptible "jumps" while still capping WASM work.
|
||||||
|
(rx/sample 16)
|
||||||
(rx/map
|
(rx/map
|
||||||
(fn [[modifiers snap-ignore-axis]]
|
(fn [[modifiers snap-ignore-axis]]
|
||||||
(dwm/set-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
(dwm/set-wasm-modifiers modifiers :snap-ignore-axis snap-ignore-axis))))
|
||||||
|
|||||||
@ -78,6 +78,39 @@
|
|||||||
[selected objects modifiers]
|
[selected objects modifiers]
|
||||||
(apply-modifiers-to-objects objects (select-keys (into {} modifiers) selected)))
|
(apply-modifiers-to-objects objects (select-keys (into {} modifiers) selected)))
|
||||||
|
|
||||||
|
(defn- apply-wasm-modifiers-to-ids
|
||||||
|
"Like `apply-modifiers-to-objects`, but only updates ids in `id-set`. During WASM
|
||||||
|
drag, `wasm-modifiers` can list every propagated descendant (large variants); SVG
|
||||||
|
outlines only need geometry for `selected` / hover / highlight — not the whole page."
|
||||||
|
[objects wasm-modifiers id-set]
|
||||||
|
(if (or (empty? wasm-modifiers) (empty? id-set))
|
||||||
|
objects
|
||||||
|
(reduce
|
||||||
|
(fn [objs pair]
|
||||||
|
(let [[id t] pair]
|
||||||
|
(if (and (contains? id-set id) (contains? objs id))
|
||||||
|
(update objs id gsh/apply-transform t)
|
||||||
|
objs)))
|
||||||
|
objects
|
||||||
|
wasm-modifiers)))
|
||||||
|
|
||||||
|
(defn- outline-wasm-source-ids
|
||||||
|
"Superset of shape ids that `shape-outlines` may look up (all outline usages here)."
|
||||||
|
[base-objects selected highlighted edition hover-ids hover frame-hover]
|
||||||
|
(let [outlined-frame-id (->> hover-ids
|
||||||
|
(filter #(cfh/frame-shape? (get base-objects %)))
|
||||||
|
(remove selected)
|
||||||
|
(last))
|
||||||
|
ids (-> #{}
|
||||||
|
(into (or selected #{}))
|
||||||
|
(into (or highlighted #{}))
|
||||||
|
(into (or hover-ids #{})))]
|
||||||
|
(cond-> ids
|
||||||
|
(uuid? (:id hover)) (conj (:id hover))
|
||||||
|
(uuid? frame-hover) (conj frame-hover)
|
||||||
|
(uuid? outlined-frame-id) (conj outlined-frame-id)
|
||||||
|
(uuid? edition) (disj edition))))
|
||||||
|
|
||||||
(mf/defc viewport*
|
(mf/defc viewport*
|
||||||
[{:keys [selected wglobal layout file page palete-size]}]
|
[{:keys [selected wglobal layout file page palete-size]}]
|
||||||
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
|
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
|
||||||
@ -129,10 +162,6 @@
|
|||||||
(into [] (keep (d/getf objects-modified)))
|
(into [] (keep (d/getf objects-modified)))
|
||||||
(not-empty))
|
(not-empty))
|
||||||
|
|
||||||
objects-for-outlines
|
|
||||||
(mf/with-memo [base-objects wasm-modifiers]
|
|
||||||
(apply-modifiers-to-objects base-objects wasm-modifiers))
|
|
||||||
|
|
||||||
;; STATE
|
;; STATE
|
||||||
alt? (mf/use-state false)
|
alt? (mf/use-state false)
|
||||||
shift? (mf/use-state false)
|
shift? (mf/use-state false)
|
||||||
@ -178,6 +207,18 @@
|
|||||||
(when (some? parent-id)
|
(when (some? parent-id)
|
||||||
(get base-objects parent-id)))))
|
(get base-objects parent-id)))))
|
||||||
|
|
||||||
|
outline-wasm-ids
|
||||||
|
(mf/with-memo
|
||||||
|
[base-objects selected highlighted edition @hover-ids @hover @frame-hover]
|
||||||
|
(outline-wasm-source-ids base-objects selected highlighted edition @hover-ids @hover @frame-hover))
|
||||||
|
|
||||||
|
objects-for-outlines
|
||||||
|
(mf/with-memo
|
||||||
|
[base-objects wasm-modifiers outline-wasm-ids]
|
||||||
|
(if (seq wasm-modifiers)
|
||||||
|
(apply-wasm-modifiers-to-ids base-objects wasm-modifiers outline-wasm-ids)
|
||||||
|
base-objects))
|
||||||
|
|
||||||
zoom (d/check-num zoom 1)
|
zoom (d/check-num zoom 1)
|
||||||
|
|
||||||
drawing-tool (:tool drawing)
|
drawing-tool (:tool drawing)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user