mirror of
https://github.com/penpot/penpot.git
synced 2026-10-03 01:06:14 +00:00
🐛 Fix problems with react loops (#11941)
This commit is contained in:
parent
b890b94d27
commit
c0714def01
@ -1473,12 +1473,17 @@
|
||||
(rx/filter (complement :skip-component-sync?))
|
||||
;; Keep waits pending while component changes are checked.
|
||||
(rx/map start-sync-barrier)
|
||||
(rx/observe-on :async))
|
||||
(rx/share))
|
||||
|
||||
get-component-events
|
||||
(fn [[event old-data]]
|
||||
(let [{:keys [file-id changes save-undo? undo-group]} event
|
||||
changed-components
|
||||
;; Buffers commits until a timer turn passes with no new ones. The
|
||||
;; debounce timer (not a microtask) queues behind any timers already
|
||||
;; due, so a burst of commits is inspected as one batch in one task.
|
||||
commit-batches-s
|
||||
(rx/buffer-until (rx/debounce 0 commits-s) commits-s)
|
||||
|
||||
get-component-changes
|
||||
(fn [old-data {:keys [file-id changes save-undo? undo-group]}]
|
||||
(let [changed-components
|
||||
(when (and old-data
|
||||
(or (nil? file-id) (= file-id (:id old-data))))
|
||||
(into #{}
|
||||
@ -1486,29 +1491,39 @@
|
||||
changes))]
|
||||
(cond
|
||||
(empty? changed-components)
|
||||
(rx/empty)
|
||||
nil
|
||||
|
||||
save-undo?
|
||||
(do
|
||||
(log/info :hint "detected component changes"
|
||||
:ids (map str changed-components)
|
||||
:undo-group undo-group)
|
||||
(->> (rx/from changed-components)
|
||||
(rx/map #(component-changed
|
||||
% (:id old-data) undo-group))))
|
||||
(map #(vector ::component-changed % undo-group)
|
||||
changed-components))
|
||||
|
||||
:else
|
||||
;; Undos only bump :modified-at.
|
||||
(->> (rx/from changed-components)
|
||||
(rx/map touch-component)))))
|
||||
(map #(vector ::touch-component %) changed-components))))
|
||||
|
||||
;; One event per distinct component change in the batch
|
||||
get-component-events
|
||||
(fn [batch old-data]
|
||||
(->> batch
|
||||
(mapcat #(get-component-changes old-data (first %)))
|
||||
(distinct)
|
||||
(map (fn [[type component-id undo-group]]
|
||||
(if (= type ::component-changed)
|
||||
(component-changed component-id (:id old-data) undo-group)
|
||||
(touch-component component-id))))
|
||||
(rx/from)))
|
||||
|
||||
component-events-s
|
||||
(->> commits-s
|
||||
(->> commit-batches-s
|
||||
(rx/with-latest-from workspace-buffer-s)
|
||||
(rx/mapcat
|
||||
(fn [[[event task] old-data]]
|
||||
(->> (get-component-events [event old-data])
|
||||
(rx/finalize #(finish-sync-barrier! task)))))
|
||||
(fn [[batch old-data]]
|
||||
(->> (get-component-events batch old-data)
|
||||
(rx/finalize #(run! (comp finish-sync-barrier! second) batch)))))
|
||||
;; Close barriers left behind when the page shuts down.
|
||||
(rx/finalize #(wrf/finish-tasks! @pending-sync-barriers*))
|
||||
(rx/share))
|
||||
|
||||
@ -202,12 +202,6 @@
|
||||
|
||||
file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
||||
current-colors* (mf/use-state [])
|
||||
current-colors (deref current-colors*)
|
||||
|
||||
grouped-colors* (mf/use-state {})
|
||||
grouped-colors (deref grouped-colors*)
|
||||
|
||||
open-groups* (mf/use-state #{})
|
||||
open-groups (deref open-groups*)
|
||||
|
||||
@ -221,13 +215,15 @@
|
||||
[{:value "recent" :label (tr "workspace.libraries.colors.recent-colors") :id "recent"}
|
||||
{:value "file" :label (tr "workspace.libraries.colors.file-library") :id "file"}])
|
||||
|
||||
;; Equal-stable, since `select*` resets its selection when options change
|
||||
options
|
||||
(mf/with-memo [library-options libraries file-id]
|
||||
(into library-options
|
||||
(comp
|
||||
(map val)
|
||||
(map (fn [lib] {:value (d/name (:id lib)) :label (:name lib) :id (d/name (:id lib))})))
|
||||
(dissoc libraries file-id)))
|
||||
(h/use-equal-memo
|
||||
(mf/with-memo [library-options libraries file-id]
|
||||
(into library-options
|
||||
(comp
|
||||
(map val)
|
||||
(map (fn [lib] {:value (d/name (:id lib)) :label (:name lib) :id (d/name (:id lib))})))
|
||||
(dissoc libraries file-id))))
|
||||
|
||||
on-library-change
|
||||
(mf/use-fn
|
||||
@ -281,49 +277,49 @@
|
||||
(fn [s]
|
||||
(if (contains? s path)
|
||||
(disj s path)
|
||||
(conj s path))))))]
|
||||
(conj s path))))))
|
||||
|
||||
;; Load library colors when the selected library (or filter options) change.
|
||||
;;
|
||||
;; flat current-colors* -- used for the grid view and the recent list view.
|
||||
;; grouped grouped-colors* -- used for the library grouped list view.
|
||||
;;
|
||||
;; Library colors are fully converted with `library-color->color` here so
|
||||
;; the render path never needs to do it. `flat-colors` is materialised as
|
||||
;; an eager vector so realisation does not leak into render time.
|
||||
;; open-groups* is reset to #{} (all groups expanded) on every library switch.
|
||||
(mf/with-effect [selected recent-colors libraries file-id valid-color?]
|
||||
(let [resolved-file-id (if (= selected :file) file-id selected)]
|
||||
(reset! open-groups* #{})
|
||||
(if (= selected :recent)
|
||||
(let [colors (into []
|
||||
(comp
|
||||
(filter valid-color?)
|
||||
(map-indexed (fn [index color]
|
||||
(let [color (if (map? color) color {:color color})]
|
||||
(vary-meta color assoc ::id (dm/str index)))))
|
||||
(take-while some?))
|
||||
(sort ctc/sort-colors (reverse recent-colors)))]
|
||||
(reset! current-colors* colors)
|
||||
(reset! grouped-colors* {}))
|
||||
resolved-file-id
|
||||
(if (= selected :file) file-id selected)
|
||||
|
||||
(let [raw-colors (->> (dm/get-in libraries [resolved-file-id :data :colors])
|
||||
(vals)
|
||||
(filter valid-color?)
|
||||
(sort-by :name))
|
||||
;; Colors of the selected library; stable across unrelated commits
|
||||
library-colors
|
||||
(when (not= selected :recent)
|
||||
(dm/get-in libraries [resolved-file-id :data :colors]))
|
||||
|
||||
;; Eager vector for the grid view -- index-based ::id for keying.
|
||||
flat-colors (into []
|
||||
(map-indexed (fn [index color]
|
||||
(-> (ctc/library-color->color color resolved-file-id)
|
||||
(vary-meta assoc ::id (dm/str index)))))
|
||||
raw-colors)
|
||||
;; Flat (grid and recent list) and grouped (library list) colors, converted for rendering
|
||||
[current-colors grouped-colors]
|
||||
(mf/with-memo [selected recent-colors library-colors resolved-file-id valid-color?]
|
||||
(if (= selected :recent)
|
||||
[(into []
|
||||
(comp
|
||||
(filter valid-color?)
|
||||
(map-indexed (fn [index color]
|
||||
(let [color (if (map? color) color {:color color})]
|
||||
(vary-meta color assoc ::id (dm/str index)))))
|
||||
(take-while some?))
|
||||
(sort ctc/sort-colors (reverse recent-colors)))
|
||||
{}]
|
||||
|
||||
;; Group tree with colors already converted -- no conversions at render time.
|
||||
grouped (some-> (grp/group-assets raw-colors false)
|
||||
(convert-grouped-colors resolved-file-id))]
|
||||
(reset! current-colors* flat-colors)
|
||||
(reset! grouped-colors* (or grouped {}))))))
|
||||
(let [raw-colors (->> (vals library-colors)
|
||||
(filter valid-color?)
|
||||
(sort-by :name))
|
||||
|
||||
;; Eager vector for the grid view -- index-based ::id for keying.
|
||||
flat-colors (into []
|
||||
(map-indexed (fn [index color]
|
||||
(-> (ctc/library-color->color color resolved-file-id)
|
||||
(vary-meta assoc ::id (dm/str index)))))
|
||||
raw-colors)
|
||||
|
||||
;; Group tree with colors already converted -- no conversions at render time.
|
||||
grouped (some-> (grp/group-assets raw-colors false)
|
||||
(convert-grouped-colors resolved-file-id))]
|
||||
[flat-colors (or grouped {})])))]
|
||||
|
||||
;; Expands all groups when the selected library changes
|
||||
(mf/with-effect [selected]
|
||||
(reset! open-groups* #{}))
|
||||
|
||||
[:div {:class (stl/css :libraries)}
|
||||
[:div {:class (stl/css :select-wrapper)}
|
||||
|
||||
@ -121,10 +121,15 @@
|
||||
(reset! internal-color* color)
|
||||
(on-change color))))]
|
||||
|
||||
;; Syncs with color changes made outside the ramp; the colors the ramp
|
||||
;; emits come back with the same components and are skipped
|
||||
(mf/use-effect
|
||||
(mf/deps color)
|
||||
(fn []
|
||||
(reset! internal-color* (enrich-color-map color))))
|
||||
(let [color (enrich-color-map color)]
|
||||
(when (not= (select-keys color [:h :s :v :alpha])
|
||||
(select-keys internal-color [:h :s :v :alpha]))
|
||||
(reset! internal-color* color)))))
|
||||
|
||||
[:*
|
||||
[:> value-saturation-selector*
|
||||
|
||||
@ -100,6 +100,21 @@
|
||||
(when (= (:type panel) :component-swap)
|
||||
[:> component-menu* {:shapes (:shapes panel) :is-swap-opened true}]))
|
||||
|
||||
(defn- get-shapes-with-children
|
||||
"Returns the shapes of `selected` together with all their descendants."
|
||||
[objects selected]
|
||||
(loop [queue (into #queue [] selected)
|
||||
visited selected]
|
||||
(if-let [id (peek queue)]
|
||||
(let [shape (get objects id)
|
||||
children (:shapes shape)]
|
||||
(if (seq children)
|
||||
(let [new-children (remove visited children)]
|
||||
(recur (into (pop queue) new-children)
|
||||
(into visited new-children)))
|
||||
(recur (pop queue) visited)))
|
||||
(sequence (keep (d/getf objects)) visited))))
|
||||
|
||||
(mf/defc design-menu*
|
||||
{::mf/private true}
|
||||
[{:keys [selected objects page-id file-id shapes]}]
|
||||
@ -145,29 +160,16 @@
|
||||
(->> (dm/get-in grid-edition [edition :selected])
|
||||
(map #(dm/get-in objects [edition :layout-grid-cells %])))
|
||||
|
||||
shapes-with-children*
|
||||
(mf/use-state nil)
|
||||
;; Deferred, so the subtree walk runs in a background render
|
||||
deferred-selected
|
||||
(mf/use-deferred selected)
|
||||
|
||||
_ (mf/use-effect
|
||||
(mf/deps selected objects shapes)
|
||||
(fn []
|
||||
(reset! shapes-with-children* nil)
|
||||
(let [result
|
||||
(loop [queue (into #queue [] selected)
|
||||
visited selected]
|
||||
(if-let [id (peek queue)]
|
||||
(let [shape (get objects id)
|
||||
children (:shapes shape)]
|
||||
(if (seq children)
|
||||
(let [new-children (remove visited children)]
|
||||
(recur (into (pop queue) new-children)
|
||||
(into visited new-children)))
|
||||
(recur (pop queue) visited)))
|
||||
(sequence (keep (d/getf objects)) visited)))]
|
||||
(reset! shapes-with-children* result))))
|
||||
deferred-objects
|
||||
(mf/use-deferred objects)
|
||||
|
||||
shapes-with-children
|
||||
(deref shapes-with-children*)
|
||||
(mf/with-memo [deferred-selected deferred-objects]
|
||||
(get-shapes-with-children deferred-objects deferred-selected))
|
||||
|
||||
total-selected
|
||||
(count selected)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user