Merge pull request #9357 from penpot/elenatorro-14100-cljs-optimizations

 Event optimizations on drag
This commit is contained in:
Alejandro Alonso 2026-05-06 16:23:09 +02:00 committed by GitHub
commit c36887e0bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 294 additions and 150 deletions

View File

@ -75,27 +75,28 @@
(reduce check-shape changes mod-obj-changes))) (reduce check-shape changes mod-obj-changes)))
(defn generate-update-shapes (defn generate-update-shapes
[changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects?]}] [changes ids update-fn objects {:keys [attrs changed-sub-attr ignore-tree ignore-touched with-objects? translation?]}]
(let [changes (let [changes (reduce
(->> ids (fn [changes id]
(reduce (let [opts {:attrs attrs
(fn [changes id] :ignore-geometry? (get ignore-tree id)
(let [opts {:attrs attrs :ignore-touched ignore-touched
:ignore-geometry? (get ignore-tree id) :with-objects? with-objects?}]
:ignore-touched ignore-touched (pcb/update-shapes changes [id] update-fn (d/without-nils opts))))
:with-objects? with-objects?}] (cond-> changes
(pcb/update-shapes changes [id] update-fn (d/without-nils opts)))) (some? objects) (pcb/with-objects objects))
(cond-> changes ids)
(some? objects) (pcb/with-objects objects)))) ;; Translation doesn't shift children between grid cells, so
grid-ids ;; cell reassignment + child reorder are no-ops.
(->> ids (filter (partial ctl/grid-layout? objects))) grid-ids (when-not translation?
(->> ids (filter (partial ctl/grid-layout? objects))))
changes (cond-> changes
(seq grid-ids)
(-> (pcb/update-shapes grid-ids ctl/assign-cell-positions {:with-objects? true})
(pcb/reorder-grid-children ids))
changes (not ignore-touched)
(-> changes (generate-unapply-tokens objects changed-sub-attr))]
(pcb/update-shapes grid-ids ctl/assign-cell-positions {:with-objects? true})
(pcb/reorder-grid-children ids)
(cond-> (not ignore-touched)
(generate-unapply-tokens objects changed-sub-attr)))]
changes)) changes))
(defn- generate-update-shape-flags (defn- generate-update-shape-flags

View File

@ -486,53 +486,78 @@
;; 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 parent-validation-cache
"Pre-computes the `children`-derived data for `find-valid-parent-and-frame-ids`.
Build once per gesture and pass as `cache`; values are delays so unused
branches stay unrealized."
[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]
(find-valid-parent-and-frame-ids parent-id objects children pasting? libraries nil))
([parent-id objects children pasting? libraries cache]
(letfn [(get-frame [pid] (letfn [(get-frame [pid]
(if (cfh/frame-shape? objects pid) pid (get-in objects [pid :frame-id])))] (if (cfh/frame-shape? objects pid) pid (get-in objects [pid :frame-id])))]
;; `descendants`, variant-id set, etc. depend only on the moved shapes, not on the ;; Predicates below are ordered so cheap parent/ascendant checks
;; candidate parent. Computing them once per drag (this fn is hot during move) ;; short-circuit before the descendant delays are forced.
;; avoids O(depth * subtree) work when walking invalid ancestors — common with (let [{:keys [top-children all-main? any-variant-container-descendant
;; variants and nested components. descendants-variant-ids-set any-main-descendant]}
(let [children-ids (into #{} (map :id) children) (or cache (parent-validation-cache objects children libraries))]
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 (mapcat #(cfh/get-children-with-self objects %) children-ids)
any-variant-container-descendant (some ctk/is-variant-container? descendants)
descendants-variant-ids-set (into #{} (map get-variant-id) descendants)
;; Same as (some #(some ctk/main-instance? (cfh/get-children-with-self objects (:id %)))
;; children) but a single walk over `descendants`.
any-main-descendant (some ctk/main-instance? descendants)]
(loop [parent-id parent-id] (loop [parent-id parent-id]
(let [parent (get objects parent-id) (let [parent (get objects parent-id)
;; 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
no-changes? no-changes?
(and (every? #(= parent-id (:parent-id %)) top-children) (and (every? #(= parent-id (:parent-id %)) top-children)
(not pasting?)) (not pasting?))
ascendants (cfh/get-parents-with-self objects parent-id) ascendants (cfh/get-parents-with-self objects parent-id)
any-main-ascendant (some ctk/main-instance? ascendants) any-main-ascendant (some ctk/main-instance? ascendants)
any-variant-container-ascendant (some ctk/is-variant-container? ascendants)] any-variant-container-ascendant (some ctk/is-variant-container? ascendants)]
(if (or no-changes? (if (or no-changes?
(and (not (invalid-structure-for-component? objects parent children pasting? libraries)) (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 ;; 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))) (or pasting? (not (ctk/main-instance? parent)) (nil? @any-main-descendant))
;; Don't allow variant-container inside variant container nor main ;; Don't allow variant-container inside variant container nor main
(or (not any-variant-container-descendant) (or (and (not any-variant-container-ascendant) (not any-main-ascendant))
(and (not any-variant-container-ascendant) (not any-main-ascendant))) (not @any-variant-container-descendant))
;; If the parent is a variant-container, all the items should be main ;; If the parent is a variant-container, all the items should be main
(or (not (ctk/is-variant-container? parent)) all-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, ;; 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 ;; so not have the same variant-id of any descendant
(or (not pasting?) (or (not pasting?)
(not (ctk/is-variant? parent)) (not (ctk/is-variant? parent))
(not (contains? descendants-variant-ids-set (:variant-id parent)))))) (not (contains? @descendants-variant-ids-set (:variant-id parent))))))
[parent-id (get-frame parent-id)] [parent-id (get-frame parent-id)]
(recur (:parent-id parent))))))))) (recur (:parent-id parent)))))))))

View File

@ -30,6 +30,7 @@
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.features :as features] [app.main.features :as features]
[app.main.streams :as ms]
[app.render-wasm.api :as wasm.api] [app.render-wasm.api :as wasm.api]
[app.render-wasm.shape :as wasm.shape] [app.render-wasm.shape :as wasm.shape]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@ -617,22 +618,69 @@
(defn set-temporary-selrect (defn set-temporary-selrect
[selrect] [selrect]
(ptk/reify ::set-temporary-selrect (ptk/reify ::set-temporary-selrect
ptk/UpdateEvent ptk/EffectEvent
(update [_ state] (effect [_ _ _]
(assoc state :workspace-selrect selrect)))) (rx/push! ms/workspace-selrect selrect))))
(defn set-temporary-modifiers (defn set-temporary-modifiers
[modifiers] [modifiers]
(ptk/reify ::set-temporary-modifiers (ptk/reify ::set-temporary-modifiers
ptk/UpdateEvent ptk/EffectEvent
(update [_ state] (effect [_ _ _]
(assoc state :workspace-wasm-modifiers modifiers)))) (rx/push! ms/wasm-modifiers modifiers))))
(def ^:private xf:map-key (map key)) (def ^:private xf:map-key (map key))
(defn- expand-translation-entry
"Expand one translation-only geometry entry into [descendant-id matrix]
pairs covering the moved shape's full subtree (every descendant gets
the same matrix)."
[[id data] objects subtree-ids-by-id]
(let [m (:transform data)
sub (or (get subtree-ids-by-id id)
(cfh/get-children-ids-with-self objects id))]
(map (fn [sid] [sid m]) sub)))
(defn- expand-translation-modifiers
"Pure translation propagates as identity to descendants: every shape in
the subtree gets the same matrix. Builds the flat [id matrix] list
directly, skipping the WASM tree walk + FFI roundtrip used by
`propagate-modifiers` for the general (resize/rotate) case.
Only safe when pixel-snap is off: WASM applies pixel correction
per-shape (different scale/translation per descendant), which we
can't replicate cheaply on the CLJS side."
[geometry-entries objects subtree-ids-by-id]
(into []
(mapcat #(expand-translation-entry % objects subtree-ids-by-id))
geometry-entries))
(defn- translate-selrect
"Shift `selrect`'s center by (tx, ty). Width/height/transform are
invariant under pure translation, so only `:center` moves."
[selrect tx ty]
(update selrect :center
(fn [c] (gpt/point (+ (:x c) tx) (+ (:y c) ty)))))
(defn- cached-translation-selrect
"Translation-only fast path for the live selection rect. Avoids a WASM
`get-selection-rect` call per drag frame by caching the gesture-start
base: on the first emission, ask WASM and back out the current delta
to recover the base; on every later emission, shift the cached base
by the new (tx, ty)."
[ids ^js first-matrix cache]
(let [tx (.-e first-matrix)
ty (.-f first-matrix)]
(if-let [base @cache]
(translate-selrect base tx ty)
(let [computed (wasm.api/get-selection-rect ids)]
(vreset! cache (translate-selrect computed (- tx) (- ty)))
computed))))
#_:clj-kondo/ignore #_:clj-kondo/ignore
(defn set-wasm-modifiers (defn set-wasm-modifiers
[modif-tree & {:keys [ignore-constraints ignore-snap-pixel] [modif-tree & {:keys [ignore-constraints ignore-snap-pixel
subtree-ids-by-id selection-rect-cache]
:or {ignore-constraints false ignore-snap-pixel false} :or {ignore-constraints false ignore-snap-pixel false}
:as params}] :as params}]
(ptk/reify ::set-wasm-modifiers (ptk/reify ::set-wasm-modifiers
@ -657,14 +705,24 @@
wasm-props (:wasm-props state) wasm-props (:wasm-props state)
objects (dsh/lookup-page-objects state) objects (dsh/lookup-page-objects state)
snap-pixel? snap-pixel?
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))] (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
translation?
(every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
(set-wasm-props! objects prev-wasm-props wasm-props) (set-wasm-props! objects prev-wasm-props wasm-props)
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)) (when-not translation?
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)))
(let [geometry-entries (parse-geometry-modifiers modif-tree) (let [geometry-entries (parse-geometry-modifiers modif-tree)
modifiers (wasm.api/propagate-modifiers geometry-entries snap-pixel?)] modifiers
(if (and translation? (not snap-pixel?))
(expand-translation-modifiers geometry-entries objects subtree-ids-by-id)
(wasm.api/propagate-modifiers geometry-entries snap-pixel?))]
(wasm.api/set-modifiers modifiers) (wasm.api/set-modifiers modifiers)
(let [ids (into [] xf:map-key geometry-entries) (let [ids (into [] xf:map-key geometry-entries)
selrect (wasm.api/get-selection-rect ids)] selrect
(if (and translation? (not snap-pixel?) selection-rect-cache (seq modifiers))
(cached-translation-selrect ids (second (first modifiers)) selection-rect-cache)
(wasm.api/get-selection-rect ids))]
(rx/of (set-temporary-selrect selrect) (rx/of (set-temporary-selrect selrect)
(set-temporary-modifiers modifiers)))))))) (set-temporary-modifiers modifiers))))))))
@ -693,92 +751,110 @@
#_:clj-kondo/ignore #_:clj-kondo/ignore
(defn apply-wasm-modifiers (defn apply-wasm-modifiers
[modif-tree & {:keys [ignore-constraints ignore-snap-pixel snap-ignore-axis undo-transation?] [modif-tree & {:keys [ignore-constraints ignore-snap-pixel snap-ignore-axis undo-transation?
subtree-ids-by-id]
:or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true} :or {ignore-constraints false ignore-snap-pixel false snap-ignore-axis nil undo-transation? true}
:as params}] :as params}]
(ptk/reify ::apply-wasm-modifiesr (ptk/reify ::apply-wasm-modifiesr
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(wasm.api/clean-modifiers) (let [translation?
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)) (every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))]
(wasm.api/clean-modifiers)
(when-not translation?
(wasm.api/set-structure-modifiers (parse-structure-modifiers modif-tree)))
;; Apply property changes (e.g. grow-type) to WASM shapes before ;; Apply property changes (e.g. grow-type) to WASM shapes before
;; propagating geometry, so propagate_modifiers sees the updated state. ;; propagating geometry, so propagate_modifiers sees the updated state.
(doseq [[id {:keys [property value]}] (extract-property-changes modif-tree)] (doseq [[id {:keys [property value]}] (extract-property-changes modif-tree)]
(when (= property :grow-type) (when (= property :grow-type)
(wasm.api/use-shape id) (wasm.api/use-shape id)
(wasm.api/set-shape-grow-type value))) (wasm.api/set-shape-grow-type value)))
(let [objects (dsh/lookup-page-objects state) (let [objects (dsh/lookup-page-objects state)
geometry-entries geometry-entries
(parse-geometry-modifiers modif-tree) (parse-geometry-modifiers modif-tree)
snap-pixel? snap-pixel?
(and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))
transforms transforms
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)) (if (and translation? (not snap-pixel?))
;; Mirror WASM `propagate_modifiers` in CLJS: splat the
;; translation matrix onto every descendant. Without
;; this step the commit would only touch the dragged
;; primaries and descendants would snap back to their
;; pre-drag positions on drop.
;;
;; Skipped when `snap-pixel?` is on: WASM applies
;; per-shape pixel correction (different scale/translate
;; per descendant) which we can't replicate cheaply on
;; the CLJS side.
(reduce
(fn [acc [id data]]
(let [t (:transform data)
subtree-ids
(or (get subtree-ids-by-id id)
(cfh/get-children-ids-with-self objects id))]
(reduce (fn [a sid] (assoc a sid t)) acc subtree-ids)))
{}
geometry-entries)
(into {} (wasm.api/propagate-modifiers geometry-entries snap-pixel?)))
;; Pure-translation gesture: every shape's modifier only ignore-tree
;; contains `:move` operations (no resize/rotate/scale and (calculate-ignore-tree-wasm transforms objects)
;; no structural mutation)
translation?
(every? #(ctm/only-move? (:modifiers %)) (vals modif-tree))
ignore-tree options
(calculate-ignore-tree-wasm transforms objects) (-> params
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
(assoc :translation? translation?)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
options modif-tree
(-> params (propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state))
(assoc :reg-objects? true)
(assoc :ignore-tree ignore-tree)
(assoc :translation? translation?)
;; Attributes that can change in the transform. This
;; way we don't have to check all the attributes
(assoc :attrs transform-attrs))
modif-tree ids
(propagate-structure-modifiers modif-tree (dsh/lookup-page-objects state)) (into (set (keys modif-tree)) xf:without-uuid-zero (keys transforms))
ids update-shape
(into (set (keys modif-tree)) xf:without-uuid-zero (keys transforms)) (fn [shape]
(let [shape-id (dm/get-prop shape :id)
transform (get transforms shape-id)
modifiers (dm/get-in modif-tree [shape-id :modifiers])]
(-> shape
(gsh/apply-transform transform)
(ctm/apply-structure-modifiers modifiers))))
update-shape bool-ids
(fn [shape] (into #{}
(let [shape-id (dm/get-prop shape :id) (comp
transform (get transforms shape-id) (mapcat (partial cfh/get-parents-with-self objects))
modifiers (dm/get-in modif-tree [shape-id :modifiers])] (filter cfh/bool-shape?)
(-> shape (map :id))
(gsh/apply-transform transform) ids)
(ctm/apply-structure-modifiers modifiers))))
bool-ids undo-id (js/Symbol)]
(into #{} (rx/concat
(comp (if undo-transation?
(mapcat (partial cfh/get-parents-with-self objects)) (rx/of (dwu/start-undo-transaction undo-id))
(filter cfh/bool-shape?) (rx/empty))
(map :id)) (rx/of
ids) (clear-local-transform)
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
(ptk/event ::dwcm/move-frame-comment-threads transforms)
(dwsh/update-shapes ids update-shape options)
undo-id (js/Symbol)] ;; The update to the bool path needs to be in a different operation because it
(rx/concat ;; needs to have the updated children info
(if undo-transation? (dwsh/update-shapes bool-ids path/update-bool-shape (assoc options :with-objects? true)))
(rx/of (dwu/start-undo-transaction undo-id))
(rx/empty))
(rx/of
(clear-local-transform)
(ptk/event ::dwg/move-frame-guides {:ids ids :transforms transforms})
(ptk/event ::dwcm/move-frame-comment-threads transforms)
(dwsh/update-shapes ids update-shape options)
;; The update to the bool path needs to be in a different operation because it (if undo-transation?
;; needs to have the updated children info (rx/of (dwu/commit-undo-transaction undo-id))
(dwsh/update-shapes bool-ids path/update-bool-shape (assoc options :with-objects? true))) (rx/empty))))))))
(if undo-transation?
(rx/of (dwu/commit-undo-transaction undo-id))
(rx/empty)))))))
(def ^:private (def ^:private
xf-rotation-shape xf-rotation-shape

View File

@ -28,6 +28,8 @@
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
;; If anything a translation can mutate is added here, drop the
;; `(when-not translation? …)` guard in `update-shapes` below.
(def ^:private update-layout-attr? #{:hidden}) (def ^:private update-layout-attr? #{:hidden})
(defn- add-undo-group (defn- add-undo-group
@ -180,8 +182,9 @@
(map :id)) (map :id))
update-layout-ids update-layout-ids
(->> (into [] xf-update-layout ids) (when-not translation?
(not-empty)) (->> (into [] xf-update-layout ids)
(not-empty)))
changes changes
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
@ -194,7 +197,8 @@
:changed-sub-attr changed-sub-attr :changed-sub-attr changed-sub-attr
:ignore-tree ignore-tree :ignore-tree ignore-tree
:ignore-touched ignore-touched :ignore-touched ignore-touched
:with-objects? with-objects?}) :with-objects? with-objects?
:translation? translation?})
(cond-> undo-group (cond-> undo-group
(pcb/set-undo-group undo-group)) (pcb/set-undo-group undo-group))
(pcb/set-translation? translation?)) (pcb/set-translation? translation?))

View File

@ -138,9 +138,12 @@
(ptk/reify ::finish-transform (ptk/reify ::finish-transform
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(-> state (update state :workspace-local dissoc :transform :duplicate-move-started?))
(update :workspace-local dissoc :transform :duplicate-move-started?)
(dissoc :workspace-selrect :workspace-wasm-modifiers))))) ptk/EffectEvent
(effect [_ _ _]
(rx/push! ms/wasm-modifiers nil)
(rx/push! ms/workspace-selrect nil))))
;; -- Resize -------------------------------------------------------- ;; -- Resize --------------------------------------------------------
@ -655,10 +658,6 @@
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)
@ -705,7 +704,21 @@
(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 ;; Per-gesture caches: `shapes`/`objects`/`libraries` are
;; stable for the gesture, so build once and thread through.
(let [parent-validation-cache
(ctn/parent-validation-cache objects shapes libraries)
subtree-ids-by-id
(into {}
(map (fn [id]
[id (cfh/get-children-ids-with-self objects id)]))
ids)
selection-rect-cache
(volatile! nil)
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)
@ -719,14 +732,8 @@
(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)
raw-target (ctst/top-nested-frame objects position exclude-frames) target-frame (ctst/top-nested-frame objects position exclude-frames)
cache @find-valid-for-raw-cache [target-frame _] (ctn/find-valid-parent-and-frame-ids target-frame objects shapes false libraries parent-validation-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))
@ -779,7 +786,10 @@
(rx/sample 16) (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
:subtree-ids-by-id subtree-ids-by-id
:selection-rect-cache selection-rect-cache))))
(->> move-stream (->> move-stream
(rx/with-latest-from ms/mouse-position-alt) (rx/with-latest-from ms/mouse-position-alt)
@ -804,7 +814,8 @@
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(dwm/apply-wasm-modifiers modifiers (dwm/apply-wasm-modifiers modifiers
:snap-ignore-axis snap-ignore-axis :snap-ignore-axis snap-ignore-axis
:undo-transation? false) :undo-transation? false
:subtree-ids-by-id subtree-ids-by-id)
(move-shapes-to-frame ids target-frame drop-index drop-cell) (move-shapes-to-frame ids target-frame drop-index drop-cell)
(finish-transform) (finish-transform)
(dwu/commit-undo-transaction undo-id)))))))) (dwu/commit-undo-transaction undo-id))))))))

View File

@ -17,6 +17,8 @@
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.workspace.tokens.selected-set :as dwts] [app.main.data.workspace.tokens.selected-set :as dwts]
[app.main.store :as st] [app.main.store :as st]
[app.main.streams :as ms]
[beicon.v2.core :as rx]
[okulary.core :as l])) [okulary.core :as l]))
;; ---- Global refs ;; ---- Global refs
@ -161,7 +163,9 @@
(l/derived :workspace-tokens st/state)) (l/derived :workspace-tokens st/state))
(def workspace-selrect (def workspace-selrect
(l/derived :workspace-selrect st/state)) (let [a (atom nil)]
(rx/sub! ms/workspace-selrect #(reset! a %))
a))
;; WARNING: Don't use directly from components, this is a proxy to ;; WARNING: Don't use directly from components, this is a proxy to
;; improve performance of selected-shapes and ;; improve performance of selected-shapes and
@ -385,7 +389,9 @@
(l/derived :workspace-wasm-editor-styles st/state)) (l/derived :workspace-wasm-editor-styles st/state))
(def workspace-wasm-modifiers (def workspace-wasm-modifiers
(l/derived :workspace-wasm-modifiers st/state)) (let [a (atom nil)]
(rx/sub! ms/wasm-modifiers #(reset! a %))
a))
(def ^:private workspace-modifiers-with-objects (def ^:private workspace-modifiers-with-objects
(l/derived (l/derived

View File

@ -22,6 +22,16 @@
(or ^boolean (kbd/keyboard-event? event) (or ^boolean (kbd/keyboard-event? event)
^boolean (mse/mouse-event? event))) ^boolean (mse/mouse-event? event)))
;; Live preview state for an interactive transform. Pushed by drag
;; events at the cadence set by upstream `rx/sample` (see
;; `transforms.cljs`); subscribed via plain atoms in `app.main.refs` so
;; updates bypass the Redux store and don't re-run unrelated lenses.
(defonce wasm-modifiers
(rx/behavior-subject nil))
(defonce workspace-selrect
(rx/behavior-subject nil))
;; --- Derived streams ;; --- Derived streams
(defonce ^:private pointer (defonce ^:private pointer

View File

@ -73,7 +73,7 @@
nil))) nil)))
(mf/defc shape-options* (mf/defc shape-options*
{::mf/wrap [#(mf/throttle % 100)] {::mf/wrap [#(mf/throttle % 200)]
::mf/private true} ::mf/private true}
[{:keys [shapes shapes-with-children selected page-id file-id libraries]}] [{:keys [shapes shapes-with-children selected page-id file-id libraries]}]
(if (= 1 (count selected)) (if (= 1 (count selected))

View File

@ -75,6 +75,11 @@
(track! :set-structure-modifiers) (track! :set-structure-modifiers)
nil) nil)
(defn- mock-set-modifiers
[_modifiers]
(track! :set-modifiers)
nil)
(defn- mock-set-shape-grow-type (defn- mock-set-shape-grow-type
[_grow-type] [_grow-type]
(track! :set-shape-grow-type) (track! :set-shape-grow-type)
@ -141,6 +146,7 @@
{:clean-modifiers wasm.api/clean-modifiers {:clean-modifiers wasm.api/clean-modifiers
:set-structure-modifiers wasm.api/set-structure-modifiers :set-structure-modifiers wasm.api/set-structure-modifiers
:propagate-modifiers wasm.api/propagate-modifiers :propagate-modifiers wasm.api/propagate-modifiers
:set-modifiers wasm.api/set-modifiers
:set-shape-grow-type wasm.api/set-shape-grow-type :set-shape-grow-type wasm.api/set-shape-grow-type
:set-shape-text-content wasm.api/set-shape-text-content :set-shape-text-content wasm.api/set-shape-text-content
:set-shape-text-images wasm.api/set-shape-text-images :set-shape-text-images wasm.api/set-shape-text-images
@ -152,6 +158,7 @@
(set! wasm.api/clean-modifiers mock-clean-modifiers) (set! wasm.api/clean-modifiers mock-clean-modifiers)
(set! wasm.api/set-structure-modifiers mock-set-structure-modifiers) (set! wasm.api/set-structure-modifiers mock-set-structure-modifiers)
(set! wasm.api/propagate-modifiers mock-propagate-modifiers) (set! wasm.api/propagate-modifiers mock-propagate-modifiers)
(set! wasm.api/set-modifiers mock-set-modifiers)
(set! wasm.api/set-shape-grow-type mock-set-shape-grow-type) (set! wasm.api/set-shape-grow-type mock-set-shape-grow-type)
(set! wasm.api/set-shape-text-content mock-set-shape-text-content) (set! wasm.api/set-shape-text-content mock-set-shape-text-content)
(set! wasm.api/set-shape-text-images mock-set-shape-text-images) (set! wasm.api/set-shape-text-images mock-set-shape-text-images)
@ -167,6 +174,7 @@
(set! wasm.api/clean-modifiers (:clean-modifiers orig)) (set! wasm.api/clean-modifiers (:clean-modifiers orig))
(set! wasm.api/set-structure-modifiers (:set-structure-modifiers orig)) (set! wasm.api/set-structure-modifiers (:set-structure-modifiers orig))
(set! wasm.api/propagate-modifiers (:propagate-modifiers orig)) (set! wasm.api/propagate-modifiers (:propagate-modifiers orig))
(set! wasm.api/set-modifiers (:set-modifiers orig))
(set! wasm.api/set-shape-grow-type (:set-shape-grow-type orig)) (set! wasm.api/set-shape-grow-type (:set-shape-grow-type orig))
(set! wasm.api/set-shape-text-content (:set-shape-text-content orig)) (set! wasm.api/set-shape-text-content (:set-shape-text-content orig))
(set! wasm.api/set-shape-text-images (:set-shape-text-images orig)) (set! wasm.api/set-shape-text-images (:set-shape-text-images orig))

View File

@ -56,10 +56,13 @@
;; guide has moved ;; guide has moved
(t/is (= (:position guide') 100)) (t/is (= (:position guide') 100))
;; WASM mocks were exercised ;; WASM bridge was exercised. `dw/update-position`
(t/is (pos? (thw/call-count :clean-modifiers))) ;; routes through `apply-wasm-modifiers`, which for
(t/is (pos? (thw/call-count :set-structure-modifiers))) ;; translation-only updates calls only `clean-modifiers`
(t/is (pos? (thw/call-count :propagate-modifiers))))))))))) ;; and computes the per-descendant transforms in CLJS
;; (skipping `set-structure-modifiers` and
;; `propagate-modifiers`).
(t/is (pos? (thw/call-count :clean-modifiers)))))))))))