mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 19:06:18 +00:00
⚡ Add micro optimizations to layer-item component
This commit is contained in:
parent
a728d5a5f2
commit
a080a9e646
@ -10,6 +10,7 @@
|
|||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
|
[app.common.math :as mth]
|
||||||
[app.common.types.component :as ctk]
|
[app.common.types.component :as ctk]
|
||||||
[app.common.types.components-list :as ctkl]
|
[app.common.types.components-list :as ctkl]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
@ -186,26 +187,54 @@
|
|||||||
|
|
||||||
children]))
|
children]))
|
||||||
|
|
||||||
;; Memoized for performance
|
|
||||||
(mf/defc layer-item*
|
(mf/defc layer-item*
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [index item selected objects is-sortable is-filtered depth parent-size is-component-child highlighted style render-children]
|
[{:keys [index item selected objects
|
||||||
|
is-sortable is-filtered depth is-component-child
|
||||||
|
highlighted style render-children parent-size]
|
||||||
:or {render-children true}}]
|
:or {render-children true}}]
|
||||||
(let [id (:id item)
|
(let [id (get item :id)
|
||||||
blocked? (:blocked item)
|
blocked? (get item :blocked)
|
||||||
hidden? (:hidden item)
|
hidden? (get item :hidden)
|
||||||
|
|
||||||
|
shapes (get item :shapes)
|
||||||
|
shapes (mf/with-memo [shapes objects]
|
||||||
|
(loop [counter 0
|
||||||
|
shapes (seq shapes)
|
||||||
|
result (list)]
|
||||||
|
|
||||||
|
(if-let [id (first shapes)]
|
||||||
|
(if-let [obj (get objects id)]
|
||||||
|
(do
|
||||||
|
;; NOTE: this is a bit hacky, but reduces substantially
|
||||||
|
;; the allocation; If we use enumeration, we allocate
|
||||||
|
;; new sequence and add one iteration on each render,
|
||||||
|
;; independently if objects are changed or not. If we
|
||||||
|
;; store counter on metadata, we still need to create a
|
||||||
|
;; new allocation for each shape; with this method we
|
||||||
|
;; bypass this by mutating a private property on the
|
||||||
|
;; object removing extra allocation and extra iteration
|
||||||
|
;; on every request.
|
||||||
|
(unchecked-set obj "__$__counter" counter)
|
||||||
|
(recur (inc counter)
|
||||||
|
(rest shapes)
|
||||||
|
(conj result obj)))
|
||||||
|
(recur (inc counter)
|
||||||
|
(rest shapes)
|
||||||
|
result))
|
||||||
|
|
||||||
|
(-> result vec not-empty))))
|
||||||
|
|
||||||
drag-disabled* (mf/use-state false)
|
drag-disabled* (mf/use-state false)
|
||||||
drag-disabled? (deref drag-disabled*)
|
drag-disabled? (deref drag-disabled*)
|
||||||
|
|
||||||
scroll-middle-ref (mf/use-ref true)
|
scroll-middle-ref (mf/use-ref true)
|
||||||
expanded-iref (mf/with-memo [id]
|
expanded-iref (mf/with-memo [id]
|
||||||
(-> (l/in [:expanded id])
|
(l/derived #(dm/get-in % [:expanded id]) refs/workspace-local))
|
||||||
(l/derived refs/workspace-local)))
|
|
||||||
is-expanded (mf/deref expanded-iref)
|
is-expanded (mf/deref expanded-iref)
|
||||||
|
|
||||||
is-selected (contains? selected id)
|
is-selected (contains? selected id)
|
||||||
is-highlighted (contains? highlighted id)
|
is-highlighted (contains? highlighted id)
|
||||||
|
|
||||||
container? (or (cfh/frame-shape? item)
|
container? (or (cfh/frame-shape? item)
|
||||||
(cfh/group-shape? item))
|
(cfh/group-shape? item))
|
||||||
@ -339,7 +368,8 @@
|
|||||||
:else
|
:else
|
||||||
(cfh/get-parent-id objects id))
|
(cfh/get-parent-id objects id))
|
||||||
|
|
||||||
[parent-id _] (ctn/find-valid-parent-and-frame-ids parent-id objects (map #(get objects %) selected) false files)
|
[parent-id _]
|
||||||
|
(ctn/find-valid-parent-and-frame-ids parent-id objects (map #(get objects %) selected) false files)
|
||||||
|
|
||||||
parent (get objects parent-id)
|
parent (get objects parent-id)
|
||||||
current-index (d/index-of (:shapes parent) id)
|
current-index (d/index-of (:shapes parent) id)
|
||||||
@ -381,10 +411,9 @@
|
|||||||
:index index
|
:index index
|
||||||
:name (:name item)}
|
:name (:name item)}
|
||||||
;; We don't want to change the structure of component copies
|
;; We don't want to change the structure of component copies
|
||||||
:draggable? (and
|
:draggable? (and ^boolean is-sortable
|
||||||
is-sortable
|
^boolean (not is-read-only)
|
||||||
(not is-read-only)
|
^boolean (not (ctn/has-any-copy-parent? objects item))))]
|
||||||
(not (ctn/has-any-copy-parent? objects item))))]
|
|
||||||
|
|
||||||
(mf/with-effect [is-selected selected]
|
(mf/with-effect [is-selected selected]
|
||||||
(let [single? (= (count selected) 1)
|
(let [single? (= (count selected) 1)
|
||||||
@ -411,40 +440,47 @@
|
|||||||
|
|
||||||
;; Setup scroll-driven lazy loading when expanded
|
;; Setup scroll-driven lazy loading when expanded
|
||||||
;; and ensures selected children are loaded immediately
|
;; and ensures selected children are loaded immediately
|
||||||
(mf/with-effect [is-expanded (:shapes item) selected]
|
(mf/with-effect [is-expanded shapes selected]
|
||||||
(let [shapes-vec (:shapes item)
|
(let [total (count shapes)]
|
||||||
total (count shapes-vec)]
|
(if ^boolean is-expanded
|
||||||
(if is-expanded
|
|
||||||
(let [;; Children are rendered in reverse order, so index 0 in render = last in shapes-vec
|
(let [;; Children are rendered in reverse order, so index 0 in render = last in shapes-vec
|
||||||
;; Find if any selected id is a direct child and get its render index
|
;; Find if any selected id is a direct child and get its render index
|
||||||
selected-child-render-idx
|
selected-child-render-idx
|
||||||
(when (and (> total default-chunk-size) (seq selected))
|
(when (> total default-chunk-size)
|
||||||
(let [shapes-reversed (vec (reverse shapes-vec))]
|
(some (fn [sel-id]
|
||||||
(some (fn [sel-id]
|
(let [idx (.indexOf shapes sel-id)]
|
||||||
(let [idx (.indexOf shapes-reversed sel-id)]
|
(when (>= idx 0) idx)))
|
||||||
(when (>= idx 0) idx)))
|
selected))
|
||||||
selected)))
|
|
||||||
;; Load at least enough to include the selected child plus extra
|
;; Load at least enough to include the selected child plus extra
|
||||||
;; for context (so it can be centered in the scroll view)
|
;; for context (so it can be centered in the scroll view)
|
||||||
min-count (if selected-child-render-idx
|
min-count
|
||||||
(+ selected-child-render-idx default-chunk-size)
|
(if selected-child-render-idx
|
||||||
default-chunk-size)
|
(+ selected-child-render-idx default-chunk-size)
|
||||||
current @children-count*
|
default-chunk-size)
|
||||||
new-count (min total (max current default-chunk-size min-count))]
|
|
||||||
|
current-count
|
||||||
|
@children-count*
|
||||||
|
|
||||||
|
new-count
|
||||||
|
(mth/min total (mth/max current-count default-chunk-size min-count))]
|
||||||
|
|
||||||
(reset! children-count* new-count))
|
(reset! children-count* new-count))
|
||||||
(reset! children-count* 0)))
|
|
||||||
(fn []
|
(reset! children-count* 0))
|
||||||
(when-let [obs (mf/ref-val observer-ref)]
|
|
||||||
(.disconnect obs)
|
(fn []
|
||||||
(mf/set-ref-val! obs nil))))
|
(when-let [obs (mf/ref-val observer-ref)]
|
||||||
|
(.disconnect obs)
|
||||||
|
(mf/set-ref-val! obs nil)))))
|
||||||
|
|
||||||
;; Re-observe sentinel whenever children-count changes (sentinel moves)
|
;; Re-observe sentinel whenever children-count changes (sentinel moves)
|
||||||
;; and (shapes item) to reconnect observer after shape changes
|
;; and (shapes item) to reconnect observer after shape changes
|
||||||
(mf/with-effect [children-count is-expanded (:shapes item)]
|
(mf/with-effect [children-count is-expanded shapes]
|
||||||
(let [total (count (:shapes item))
|
(let [total (count shapes)
|
||||||
node (mf/ref-val name-node-ref)
|
name-node (mf/ref-val name-node-ref)
|
||||||
scroll-node (dom/get-parent-with-data node "scroll-container")
|
scroll-node (dom/get-parent-with-data name-node "scroll-container")
|
||||||
lazy-node (mf/ref-val lazy-ref)]
|
lazy-node (mf/ref-val lazy-ref)]
|
||||||
|
|
||||||
;; Disconnect previous observer
|
;; Disconnect previous observer
|
||||||
(when-let [obs (mf/ref-val observer-ref)]
|
(when-let [obs (mf/ref-val observer-ref)]
|
||||||
@ -452,16 +488,15 @@
|
|||||||
(mf/set-ref-val! observer-ref nil))
|
(mf/set-ref-val! observer-ref nil))
|
||||||
|
|
||||||
;; Setup new observer if there are more children to load
|
;; Setup new observer if there are more children to load
|
||||||
(when (and is-expanded
|
(when (and ^boolean is-expanded
|
||||||
(< children-count total)
|
^boolean (< children-count total)
|
||||||
scroll-node
|
^boolean scroll-node
|
||||||
lazy-node)
|
^boolean lazy-node)
|
||||||
(let [cb (fn [entries]
|
(let [cb (fn [entries]
|
||||||
(when (and (seq entries)
|
(when (and (pos? (alength entries))
|
||||||
(.-isIntersecting (first entries)))
|
(.-isIntersecting ^js (aget entries 0)))
|
||||||
;; Load next chunk when sentinel intersects
|
;; Load next chunk when sentinel intersects
|
||||||
(let [current @children-count*
|
(let [next-count (mth/min total (+ children-count default-chunk-size))]
|
||||||
next-count (min total (+ current default-chunk-size))]
|
|
||||||
(reset! children-count* next-count))))
|
(reset! children-count* next-count))))
|
||||||
observer (js/IntersectionObserver. cb #js {:root scroll-node})]
|
observer (js/IntersectionObserver. cb #js {:root scroll-node})]
|
||||||
(.observe observer lazy-node)
|
(.observe observer lazy-node)
|
||||||
@ -494,29 +529,27 @@
|
|||||||
:on-toggle-blocking toggle-blocking
|
:on-toggle-blocking toggle-blocking
|
||||||
:style style}
|
:style style}
|
||||||
|
|
||||||
(when (and render-children
|
(when (and ^boolean render-children
|
||||||
(:shapes item)
|
^boolean shapes
|
||||||
is-expanded)
|
^boolean is-expanded)
|
||||||
[:div {:class (stl/css-case
|
[:div {:class (stl/css-case
|
||||||
:element-children true
|
:element-children true
|
||||||
:parent-selected is-selected
|
:parent-selected is-selected
|
||||||
:sticky-children parent-board?)
|
:sticky-children parent-board?)
|
||||||
:data-testid (dm/str "children-" id)}
|
:data-testid (dm/str "children-" id)}
|
||||||
(let [all-children (reverse (d/enumerate (:shapes item)))
|
(for [item (take children-count shapes)]
|
||||||
visible (take children-count all-children)]
|
[:> layer-item*
|
||||||
(for [[index id] visible]
|
{:item item
|
||||||
(when-let [item (get objects id)]
|
:highlighted highlighted
|
||||||
[:> layer-item*
|
:selected selected
|
||||||
{:item item
|
:index (unchecked-get item "__$__counter")
|
||||||
:highlighted highlighted
|
:objects objects
|
||||||
:selected selected
|
:key (dm/str (get item :id))
|
||||||
:index index
|
:is-sortable is-sortable
|
||||||
:objects objects
|
:depth depth
|
||||||
:key (dm/str id)
|
:parent-size parent-size
|
||||||
:is-sortable is-sortable
|
:is-component-child is-component-tree}])
|
||||||
:depth depth
|
|
||||||
:parent-size parent-size
|
(when (< children-count (count shapes))
|
||||||
:is-component-child is-component-tree}])))
|
|
||||||
(when (< children-count (count (:shapes item)))
|
|
||||||
[:div {:ref lazy-ref
|
[:div {:ref lazy-ref
|
||||||
:class (stl/css :lazy-load-sentinel)}])])]))
|
:class (stl/css :lazy-load-sentinel)}])])]))
|
||||||
|
|||||||
@ -22,12 +22,11 @@
|
|||||||
(def ^:private space-for-icons 110)
|
(def ^:private space-for-icons 110)
|
||||||
|
|
||||||
(def lens:shape-for-rename
|
(def lens:shape-for-rename
|
||||||
(-> (l/in [:workspace-local :shape-for-rename])
|
(-> #(dm/get-in % [:workspace-local :shape-for-rename])
|
||||||
(l/derived st/state)))
|
(l/derived st/state)))
|
||||||
|
|
||||||
(mf/defc layer-name*
|
(mf/defc layer-name*
|
||||||
{::mf/wrap-props false
|
{::mf/forward-ref true}
|
||||||
::mf/forward-ref true}
|
|
||||||
[{:keys [shape-id shape-name is-shape-touched disabled-double-click
|
[{:keys [shape-id shape-name is-shape-touched disabled-double-click
|
||||||
on-start-edit on-stop-edit depth parent-size is-selected
|
on-start-edit on-stop-edit depth parent-size is-selected
|
||||||
type-comp type-frame component-id is-hidden is-blocked
|
type-comp type-frame component-id is-hidden is-blocked
|
||||||
|
|||||||
@ -78,36 +78,35 @@
|
|||||||
(mf/defc layers-tree*
|
(mf/defc layers-tree*
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
[{:keys [objects is-filtered parent-size] :as props}]
|
[{:keys [objects is-filtered parent-size] :as props}]
|
||||||
(let [selected (use-selected-shapes)
|
(let [selected (use-selected-shapes)
|
||||||
highlighted (mf/deref highlighted-shapes-ref)
|
highlighted (mf/deref highlighted-shapes-ref)
|
||||||
root (get objects uuid/zero)
|
root (get objects uuid/zero)
|
||||||
|
|
||||||
shapes (get root :shapes)
|
shapes (get root :shapes)
|
||||||
shapes (mf/with-memo [shapes objects]
|
shapes (mf/with-memo [shapes objects]
|
||||||
(loop [counter 0
|
(loop [counter 0
|
||||||
shapes (seq shapes)
|
shapes (seq shapes)
|
||||||
result (list)]
|
result (list)]
|
||||||
|
(if-let [id (first shapes)]
|
||||||
(if-let [id (first shapes)]
|
(if-let [obj (get objects id)]
|
||||||
(if-let [obj (get objects id)]
|
(do
|
||||||
(do
|
;; NOTE: this is a bit hacky, but reduces substantially
|
||||||
;; NOTE: this is a bit hacky, but reduces substantially
|
;; the allocation; If we use enumeration, we allocate
|
||||||
;; the allocation; If we use enumeration, we allocate
|
;; new sequence and add one iteration on each render,
|
||||||
;; new sequence and add one iteration on each render,
|
;; independently if objects are changed or not. If we
|
||||||
;; independently if objects are changed or not. If we
|
;; store counter on metadata, we still need to create a
|
||||||
;; store counter on metadata, we still need to create a
|
;; new allocation for each shape; with this method we
|
||||||
;; new allocation for each shape; with this method we
|
;; bypass this by mutating a private property on the
|
||||||
;; bypass this by mutating a private property on the
|
;; object removing extra allocation and extra iteration
|
||||||
;; object removing extra allocation and extra iteration
|
;; on every request.
|
||||||
;; on every request.
|
(unchecked-set obj "__$__counter" counter)
|
||||||
(unchecked-set obj "__$__counter" counter)
|
(recur (inc counter)
|
||||||
(recur (inc counter)
|
(rest shapes)
|
||||||
(rest shapes)
|
(conj result obj)))
|
||||||
(conj result obj)))
|
(recur (inc counter)
|
||||||
(recur (inc counter)
|
(rest shapes)
|
||||||
(rest shapes)
|
result))
|
||||||
result))
|
result)))]
|
||||||
result)))]
|
|
||||||
|
|
||||||
[:div {:class (stl/css :element-list) :data-testid "layer-item"}
|
[:div {:class (stl/css :element-list) :data-testid "layer-item"}
|
||||||
[:> hooks/sortable-container* {}
|
[:> hooks/sortable-container* {}
|
||||||
@ -194,6 +193,7 @@
|
|||||||
keys
|
keys
|
||||||
(filter #(not= uuid/zero %))
|
(filter #(not= uuid/zero %))
|
||||||
vec)]
|
vec)]
|
||||||
|
|
||||||
(update reparented-objects uuid/zero assoc :shapes reparented-shapes)))
|
(update reparented-objects uuid/zero assoc :shapes reparented-shapes)))
|
||||||
|
|
||||||
;; --- Layers Toolbox
|
;; --- Layers Toolbox
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user