mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 19:06:18 +00:00
Merge pull request #8785 from penpot/ladybenko-fix-repeatable-key
🐛 Fix repeateable keys triggering an infinite React loop in text editor v2
This commit is contained in:
commit
8db63c9770
@ -55,6 +55,7 @@
|
|||||||
|
|
||||||
(declare v2-update-text-shape-content)
|
(declare v2-update-text-shape-content)
|
||||||
(declare v2-update-text-editor-styles)
|
(declare v2-update-text-editor-styles)
|
||||||
|
(declare v2-sync-wasm-text-layout)
|
||||||
|
|
||||||
;; -- Content helpers
|
;; -- Content helpers
|
||||||
|
|
||||||
@ -907,10 +908,43 @@
|
|||||||
(ptk/reify ::v2-update-text-editor-styles
|
(ptk/reify ::v2-update-text-editor-styles
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
|
;; `stylechange` can fire on every `selectionchange` while typing.
|
||||||
|
;; Avoid swapping the global store when the computed styles are unchanged,
|
||||||
|
;; otherwise we can end up in store->rerender->selectionchange loops.
|
||||||
(let [merged-styles (merge (txt/get-default-text-attrs)
|
(let [merged-styles (merge (txt/get-default-text-attrs)
|
||||||
(get-in state [:workspace-global :default-font])
|
(get-in state [:workspace-global :default-font])
|
||||||
new-styles)]
|
new-styles)
|
||||||
(update-in state [:workspace-v2-editor-state id] (fnil merge {}) merged-styles)))))
|
prev (get-in state [:workspace-v2-editor-state id])]
|
||||||
|
(if (= merged-styles prev)
|
||||||
|
state
|
||||||
|
(assoc-in state [:workspace-v2-editor-state id] merged-styles))))))
|
||||||
|
|
||||||
|
(defn v2-sync-wasm-text-layout
|
||||||
|
"Live-sync WASM text layout from the DOM editor without writing shape :content.
|
||||||
|
Intended to be called from Text Editor v2 `needslayout` events (coalesced)."
|
||||||
|
[id content]
|
||||||
|
(ptk/reify ::v2-sync-wasm-text-layout
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [objects (dsh/lookup-page-objects state)
|
||||||
|
shape (get objects id)]
|
||||||
|
(if-not (and (some? shape) (cfh/text-shape? shape))
|
||||||
|
(rx/empty)
|
||||||
|
(let [new-size (dwwt/get-wasm-text-new-size shape content)
|
||||||
|
modifiers (when (and (some? new-size)
|
||||||
|
(not= :fixed (:grow-type shape)))
|
||||||
|
(dwwt/resize-wasm-text-modifiers shape content))]
|
||||||
|
;; `get-wasm-text-new-size` has the side effect of syncing WASM's internal text
|
||||||
|
;; content/layout. Only non-fixed grow-types need geometry modifiers updates.
|
||||||
|
(if (some? modifiers)
|
||||||
|
(rx/of (dwm/set-wasm-modifiers modifiers))
|
||||||
|
(rx/empty))))))
|
||||||
|
|
||||||
|
ptk/EffectEvent
|
||||||
|
(effect [_ _ _]
|
||||||
|
;; While typing, v2 only commits shape :content on debounced `change`.
|
||||||
|
;; We still need to repaint the WASM canvas for live preview.
|
||||||
|
(wasm.api/request-render "text-editor-v2-needslayout"))))
|
||||||
|
|
||||||
(defn v2-update-text-shape-position-data
|
(defn v2-update-text-shape-position-data
|
||||||
[shape-id position-data]
|
[shape-id position-data]
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.text.content :as content]
|
[app.util.text.content :as content]
|
||||||
[app.util.text.content.styles :as styles]
|
[app.util.text.content.styles :as styles]
|
||||||
|
[app.util.timers :as ts]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
@ -48,6 +49,20 @@
|
|||||||
result (.-textContent editor-root)]
|
result (.-textContent editor-root)]
|
||||||
(when (not= result "") result))))
|
(when (not= result "") result))))
|
||||||
|
|
||||||
|
(defn coalesce-per-tick
|
||||||
|
"Return a function that runs `f` at most once per JS tick.
|
||||||
|
Rationale: `needslayout` can fire on every input (including key repeat). We want
|
||||||
|
to break nested store update loops."
|
||||||
|
[f]
|
||||||
|
(let [scheduled?* (atom false)]
|
||||||
|
(fn []
|
||||||
|
(when-not @scheduled?*
|
||||||
|
(reset! scheduled?* true)
|
||||||
|
(ts/asap
|
||||||
|
(fn []
|
||||||
|
(reset! scheduled?* false)
|
||||||
|
(f)))))))
|
||||||
|
|
||||||
(defn- get-fonts
|
(defn- get-fonts
|
||||||
[content]
|
[content]
|
||||||
(let [extract-fn (juxt :font-id :font-variant-id)
|
(let [extract-fn (juxt :font-id :font-variant-id)
|
||||||
@ -136,14 +151,16 @@
|
|||||||
(st/emit! (dwt/v2-update-text-editor-styles shape-id styles))))
|
(st/emit! (dwt/v2-update-text-editor-styles shape-id styles))))
|
||||||
|
|
||||||
on-needs-layout
|
on-needs-layout
|
||||||
(fn []
|
(coalesce-per-tick
|
||||||
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
(fn []
|
||||||
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
(when-let [content (content/dom->cljs (dwt/get-editor-root instance))]
|
||||||
:update-name? true
|
;; For WASM renderer, use the dedicated layout sync to avoid touching shape content
|
||||||
:save-undo? false)))
|
;; during `needslayout` bursts. For non-wasm, keep existing behavior.
|
||||||
;; FIXME: We need to find a better way to trigger layout changes.
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
#_(st/emit!
|
(st/emit! (dwt/v2-sync-wasm-text-layout shape-id content))
|
||||||
(dwt/v2-update-text-shape-position-data shape-id [])))
|
(st/emit! (dwt/v2-update-text-shape-content shape-id content
|
||||||
|
:update-name? true
|
||||||
|
:save-undo? false))))))
|
||||||
|
|
||||||
on-change
|
on-change
|
||||||
(fn []
|
(fn []
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user