浏览代码

Remove specific history handler of whiteboard

Tienson Qin 1 年之前
父节点
当前提交
6c7ba09fb5

+ 16 - 8
e2e-tests/whiteboards.spec.ts

@@ -90,15 +90,15 @@ test('draw a rectangle', async ({ page }) => {
 })
 
 test('undo the rectangle action', async ({ page }) => {
-  await page.keyboard.press(modKey + '+z')
+  await page.keyboard.press(modKey + '+z', { delay: 100 })
   await expect(page.locator('.logseq-tldraw .tl-positioned-svg rect')).toHaveCount(0)
 })
 
 test('redo the rectangle action', async ({ page }) => {
-  await page.keyboard.press(modKey + '+Shift+z')
+  await page.waitForTimeout(100)
+  await page.keyboard.press(modKey + '+Shift+z', { delay: 100 })
 
   await page.keyboard.press('Escape')
-  await page.waitForTimeout(100)
 
   await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(1)
 })
@@ -118,6 +118,7 @@ test('clone the rectangle', async ({ page }) => {
   await page.mouse.up()
   await page.keyboard.up('Alt')
 
+  await page.waitForTimeout(100)
   await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2)
 })
 
@@ -169,11 +170,13 @@ test('connect rectangles with an arrow', async ({ page }) => {
 })
 
 test('delete the first rectangle', async ({ page }) => {
-  await page.keyboard.press('Escape')
-  await page.waitForTimeout(1000)
+  await page.keyboard.press('Escape', { delay: 100 })
+  await page.keyboard.press('Escape', { delay: 100 })
+
   await page.click('.logseq-tldraw .tl-box-container:first-of-type')
   await page.keyboard.press('Delete')
 
+  await page.waitForTimeout(200)
   await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(1)
   await expect(page.locator('.logseq-tldraw .tl-line-container')).toHaveCount(0)
 })
@@ -218,6 +221,8 @@ test('undo the color switch', async ({ page }) => {
 test('undo the shape conversion', async ({ page }) => {
   await page.keyboard.press(modKey + '+z')
 
+  await page.waitForTimeout(100)
+
   await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2)
   await expect(page.locator('.logseq-tldraw .tl-ellipse-container')).toHaveCount(0)
 })
@@ -231,9 +236,9 @@ test('locked elements should not be removed', async ({ page }) => {
   await page.mouse.down()
   await page.mouse.up()
   await page.mouse.move(bounds.x + 520, bounds.y + 520)
-  await page.keyboard.press(`${modKey}+l`)
-  await page.keyboard.press('Delete')
-  await page.keyboard.press(`${modKey}+Shift+l`)
+  await page.keyboard.press(`${modKey}+l`, { delay: 100 })
+  await page.keyboard.press('Delete', { delay: 100 })
+  await page.keyboard.press(`${modKey}+Shift+l`, { delay: 100 })
 
   await expect(page.locator('.logseq-tldraw .tl-box-container')).toHaveCount(2)
 
@@ -260,12 +265,15 @@ test('move arrow to front', async ({ page }) => {
 test('undo the move action', async ({ page }) => {
   await page.keyboard.press(modKey + '+z')
 
+  await page.waitForTimeout(100)
+
   await expect(page.locator('.logseq-tldraw .tl-canvas .tl-layer > div:first-of-type > div:first-of-type')).toHaveClass('tl-line-container')
 })
 
 test('cleanup the shapes', async ({ page }) => {
   await page.keyboard.press(`${modKey}+a`)
   await page.keyboard.press('Delete')
+  await page.waitForTimeout(100)
   await expect(page.locator('[data-type=Shape]')).toHaveCount(0)
 })
 

+ 60 - 29
src/main/frontend/extensions/tldraw.cljs

@@ -19,6 +19,7 @@
             [frontend.state :as state]
             [frontend.util :as util]
             [goog.object :as gobj]
+            [goog.functions :refer [debounce]]
             [promesa.core :as p]
             [rum.core :as rum]
             [frontend.ui :as ui]
@@ -141,6 +142,64 @@
 
 (defonce *transact-result (atom nil))
 
+(defn- on-persist
+  [page-name app info]
+  (->
+   (p/let [_ @*transact-result
+           result (p/do!
+                   (state/set-state! [:whiteboard/last-persisted-at (state/get-current-repo)] (util/time-ms))
+                   (whiteboard-handler/<transact-tldr-delta! page-name app (.-replace info)))]
+     (reset! *transact-result result))
+   (p/catch (fn [^js error]
+              (js/console.error error)
+              (notification/show! [:div
+                                   (str "Save whiteboard failed, error:" (.-cause error))])))))
+
+(rum/defc tldraw-inner < rum/static
+  {:will-remount (fn [old-state new-state]
+                   (let [page-name (first (:rum/args old-state))
+                         old-data (nth (:rum/args old-state) 1)
+                         new-data (nth (:rum/args new-state) 1)
+                         old-shapes (let [shapes (some-> (gobj/get old-data "pages")
+                                                         first
+                                                         (gobj/get "shapes"))]
+                                      (zipmap (map #(gobj/get % "id") shapes)
+                                              shapes))
+                         new-shapes (some-> (gobj/get new-data "pages")
+                                            first
+                                            (gobj/get "shapes"))
+                         updated-shapes (filter (fn [shape]
+                                                  (when-let [old (get old-shapes (gobj/get shape "id"))]
+                                                    (not= (gobj/get shape "type") (gobj/get old "type"))))
+                                                new-shapes)]
+                     ;; FIXME: this should be handled by tldraw, any data changes should re-render the updated shapes
+                     (when (seq updated-shapes)
+                       (whiteboard-handler/update-shapes! updated-shapes))
+
+                     (whiteboard-handler/update-shapes-index! page-name))
+                   new-state)}
+  [page-name data populate-onboarding? loaded-app on-mount]
+  [:div.draw.tldraw.whiteboard.relative.w-full.h-full
+   {:style {:overscroll-behavior "none"}
+    :on-blur (fn [e]
+               (when (#{"INPUT" "TEXTAREA"} (.-tagName (gobj/get e "target")))
+                 (state/clear-edit!)))
+        ;; wheel -> overscroll may cause browser navigation
+    :on-wheel util/stop-propagation}
+
+   (when
+    (and populate-onboarding? (not loaded-app))
+     [:div.absolute.inset-0.flex.items-center.justify-center
+      {:style {:z-index 200}}
+      (ui/loading "Loading onboarding whiteboard ...")])
+
+   (tldraw {:renderers tldraw-renderers
+            :handlers (get-tldraw-handlers page-name)
+            :onMount on-mount
+            :readOnly config/publishing?
+            :onPersist #(on-persist page-name %1 %2)
+            :model data})])
+
 (rum/defc tldraw-app-inner < rum/reactive
   [page-name block-id loaded-app set-loaded-app]
   (let [populate-onboarding? (whiteboard-handler/should-populate-onboarding-whiteboard? page-name)
@@ -156,35 +215,7 @@
                                     (set-loaded-app tln))))))
         data (whiteboard-handler/page-name->tldr! page-name)]
     (when data
-      [:div.draw.tldraw.whiteboard.relative.w-full.h-full
-       {:style {:overscroll-behavior "none"}
-        :on-blur (fn [e]
-                   (when (#{"INPUT" "TEXTAREA"} (.-tagName (gobj/get e "target")))
-                     (state/clear-edit!)))
-        ;; wheel -> overscroll may cause browser navigation
-        :on-wheel util/stop-propagation}
-
-       (when
-        (and populate-onboarding? (not loaded-app))
-         [:div.absolute.inset-0.flex.items-center.justify-center
-          {:style {:z-index 200}}
-          (ui/loading "Loading onboarding whiteboard ...")])
-       (tldraw {:renderers tldraw-renderers
-                :handlers (get-tldraw-handlers page-name)
-                :onMount on-mount
-                :readOnly config/publishing?
-                :onPersist (fn [app info]
-                             (->
-                              (p/let [_ @*transact-result
-                                      result (p/do!
-                                              (state/set-state! [:whiteboard/last-persisted-at (state/get-current-repo)] (util/time-ms))
-                                              (whiteboard-handler/<transact-tldr-delta! page-name app (.-replace info)))]
-                                (reset! *transact-result result))
-                              (p/catch (fn [^js error]
-                                         (js/console.error error)
-                                         (notification/show! [:div
-                                                              (str "Save whiteboard failed, error:" (.-cause error))])))))
-                :model data})])))
+      (tldraw-inner page-name data populate-onboarding? loaded-app on-mount))))
 
 (rum/defc tldraw-app
   [page-name block-id]

+ 0 - 6
src/main/frontend/handler/events.cljs

@@ -885,12 +885,6 @@
   (when (and command (not (string/blank? content)))
     (shell-handler/run-cli-command-wrapper! command content)))
 
-(defmethod handle :whiteboard/undo [[_ e]]
-  (whiteboard-handler/undo! e))
-
-(defmethod handle :whiteboard/redo [[_ e]]
-  (whiteboard-handler/redo! e))
-
 (defmethod handle :editor/quick-capture [[_ args]]
   (quick-capture/quick-capture args))
 

+ 16 - 13
src/main/frontend/handler/history.cljs

@@ -6,7 +6,8 @@
             [frontend.util :as util]
             [frontend.handler.route :as route-handler]
             [goog.dom :as gdom]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [logseq.db :as ldb]))
 
 (defn restore-cursor!
   [{:keys [last-edit-block container pos end-pos]} undo?]
@@ -40,18 +41,20 @@
 
 (defn undo!
   [e]
-  (util/stop e)
-  (p/do!
-   (state/set-state! [:editor/last-replace-ref-content-tx (state/get-current-repo)] nil)
-   (editor/save-current-block!)
-   (state/clear-editor-action!)
-   (state/set-block-op-type! nil)
-   (let [cursor-state (undo-redo/undo)]
-     (state/set-state! :ui/restore-cursor-state (select-keys cursor-state [:editor-cursor :app-state])))))
+  (when (ldb/request-finished?)
+    (util/stop e)
+    (p/do!
+     (state/set-state! [:editor/last-replace-ref-content-tx (state/get-current-repo)] nil)
+     (editor/save-current-block!)
+     (state/clear-editor-action!)
+     (state/set-block-op-type! nil)
+     (let [cursor-state (undo-redo/undo)]
+       (state/set-state! :ui/restore-cursor-state (select-keys cursor-state [:editor-cursor :app-state]))))))
 
 (defn redo!
   [e]
-  (util/stop e)
-  (state/clear-editor-action!)
-  (let [cursor-state (undo-redo/redo)]
-    (state/set-state! :ui/restore-cursor-state (select-keys cursor-state [:editor-cursor :app-state]))))
+  (when (ldb/request-finished?)
+    (util/stop e)
+    (state/clear-editor-action!)
+    (let [cursor-state (undo-redo/redo)]
+      (state/set-state! :ui/restore-cursor-state (select-keys cursor-state [:editor-cursor :app-state])))))

+ 31 - 121
src/main/frontend/handler/whiteboard.cljs

@@ -57,9 +57,8 @@
                               :shapes shapes})]})))
 
 (defn build-page-block
-  [page-name tldraw-page assets shapes-index]
-  (let [page-entity (model/get-page page-name)
-        get-k #(gobj/get tldraw-page %)]
+  [page-entity page-name tldraw-page assets shapes-index]
+  (let [get-k #(gobj/get tldraw-page %)]
     {:block/original-name page-name
      :block/name (util/page-name-sanity-lc page-name)
      :block/type "whiteboard"
@@ -103,10 +102,6 @@
                               (mapv (fn [b] (pu/get-property b :logseq.tldraw.shape)))
                               (remove nil?)))
         deleted-shapes-tx (mapv (fn [id] [:db/retractEntity [:block/uuid (uuid id)]]) deleted-ids)
-        changed-shapes (set/difference upsert-shapes created-shapes)
-        prev-changed-blocks (when (seq changed-shapes)
-                              (db/pull-many repo '[*] (mapv (fn [shape]
-                                                              [:block/uuid (uuid (:id shape))]) changed-shapes)))
         upserted-blocks (->> (map #(shape->block % page-name) upsert-shapes)
                              (remove (fn [b]
                                        (= (:nonce
@@ -116,19 +111,20 @@
                                           (:nonce
                                            (pu/get-property
                                             b
-                                            :logseq.tldraw.shape))))))]
+                                            :logseq.tldraw.shape))))))
+        page-entity (model/get-page page-name)
+        page-block (build-page-block page-entity page-name tl-page assets shapes-index)]
     (when (or (seq upserted-blocks)
-              (seq deleted-shapes-tx))
-      {:page-block (build-page-block page-name tl-page assets shapes-index)
+              (seq deleted-shapes-tx)
+              (not= (:block/properties page-block)
+                    (:block/properties page-entity)))
+      {:page-block page-block
        :upserted-blocks (map sqlite-util/block-with-timestamps upserted-blocks)
        :delete-blocks deleted-shapes-tx
+       :deleted-shapes deleted-shapes
+       :new-shapes created-shapes
        :metadata {:whiteboard/transact? (not replace?)
-                  :replace? replace?
-                  :data {:page-name page-name
-                         :deleted-shapes deleted-shapes
-                         :new-shapes created-shapes
-                         :changed-shapes changed-shapes
-                         :prev-changed-blocks prev-changed-blocks}}})))
+                  :replace? replace?}})))
 
 (defonce *last-shapes-nonce (atom {}))
 (defn <transact-tldr-delta!
@@ -150,12 +146,10 @@
                       (get-in @*last-shapes-nonce [repo page-name])
                       (set (->> (model/get-whiteboard-id-nonces repo page-name)
                                 (map #(update % :id str)))))
-        {:keys [page-block upserted-blocks delete-blocks metadata] :as result}
+        {:keys [page-block new-shapes deleted-shapes upserted-blocks delete-blocks metadata] :as result}
         (compute-tx app tl-page new-id-nonces db-id-nonces page-name replace?)]
     (when (seq result)
       (let [tx-data (concat delete-blocks [page-block] upserted-blocks)
-            new-shapes (get-in metadata [:data :new-shapes])
-            deleted-shapes (get-in metadata [:data :deleted-shapes])
             metadata' (cond
                     ;; group
                         (some #(= "group" (:type %)) new-shapes)
@@ -171,10 +165,7 @@
 
                         (assoc metadata :whiteboard/op :new-arrow)
                         :else
-                        metadata)
-            metadata' (if (seq (concat upserted-blocks delete-blocks))
-                        metadata'
-                        (assoc metadata :undo? true))]
+                        metadata)]
         (swap! *last-shapes-nonce assoc-in [repo page-name] new-id-nonces)
         (if (contains? #{:new-arrow} (:whiteboard/op metadata'))
           (state/set-state! :whiteboard/pending-tx-data
@@ -339,6 +330,23 @@
               (= page-name (:block/name (first whiteboards)))))
          (not (state/get-onboarding-whiteboard?)))))
 
+(defn update-shapes!
+  [shapes]
+  (when-let [app (state/active-tldraw-app)]
+    (let [^js api (.-api app)]
+      (apply (.-updateShapes api) (bean/->js shapes)))))
+
+(defn update-shapes-index!
+  [page-name]
+  (when-let [app (state/active-tldraw-app)]
+    (let [tl-page ^js (second (first (.-pages app)))]
+      (when tl-page
+        (when-let [page (db/entity [:block/name page-name])]
+         (let [page-metadata (pu/get-property page :logseq.tldraw.page)
+               shapes-index (:shapes-index page-metadata)]
+           (when (seq shapes-index)
+             (.updateShapesIndex tl-page (bean/->js shapes-index)))))))))
+
 (defn populate-onboarding-whiteboard
   [api]
   (when (some? api)
@@ -348,109 +356,11 @@
         (p/catch
          (fn [e] (js/console.warn "Failed to populate onboarding whiteboard" e))))))
 
-(defn- delete-shapes!
-  [^js api shapes]
-  (apply (.-deleteShapes api) (map :id shapes)))
-
-(defn- create-shapes!
-  [^js api shapes]
-  (apply (.-createShapes api) (bean/->js shapes)))
-
-(defn- update-shapes!
-  [^js api shapes]
-  (apply (.-updateShapes api) (bean/->js shapes)))
-
-(defn- select-shapes
-  [^js api ids]
-  (apply (.-selectShapes api) ids))
-
 (defn cleanup!
   [^js tl-page]
   (let [shapes (.-shapes tl-page)]
     (.cleanup tl-page (map #(.-id %) shapes))))
 
-(defn update-bindings!
-  [^js tl-page page-name]
-  (when-let [page (db/entity [:block/name page-name])]
-    (let [page-metadata (pu/get-property page :logseq.tldraw.page)
-          bindings (:bindings page-metadata)]
-      (when (seq bindings)
-        (.updateBindings tl-page (bean/->js bindings))))))
-
-(defn update-shapes-index!
-  [^js tl-page page-name]
-  (when-let [page (db/entity [:block/name page-name])]
-    (let [page-metadata (pu/get-property page :logseq.tldraw.page)
-          shapes-index (:shapes-index page-metadata)]
-      (when (seq shapes-index)
-        (.updateShapesIndex tl-page (bean/->js shapes-index))))))
-
-(defn undo!
-  [{:keys [tx-meta]}]
-  (history/pause-listener!)
-  (try
-    (when-let [app (state/active-tldraw-app)]
-      (let [{:keys [page-name deleted-shapes new-shapes changed-shapes prev-changed-blocks]} (:data tx-meta)
-            whiteboard-op (:whiteboard/op tx-meta)
-            ^js api (.-api app)
-            tl-page ^js (second (first (.-pages app)))]
-        (when api
-          (update-bindings! tl-page page-name)
-          (update-shapes-index! tl-page page-name)
-          (case whiteboard-op
-            :group
-            (do
-              (select-shapes api (map :id new-shapes))
-              (.unGroup api))
-            :un-group
-            (do
-              (select-shapes api (mapcat :children deleted-shapes))
-              (.doGroup api))
-            (do
-              (when (seq deleted-shapes)
-                (create-shapes! api deleted-shapes))
-              (when (seq new-shapes)
-                (delete-shapes! api new-shapes))
-              (when (seq changed-shapes)
-                (let [prev-shapes (map (fn [b] (pu/get-property b :logseq.tldraw.shape))
-                                       prev-changed-blocks)]
-                  (update-shapes! api prev-shapes))))))))
-    (catch :default e
-      (js/console.error e)))
-  (history/resume-listener!))
-
-(defn redo!
-  [{:keys [tx-meta]}]
-  (history/pause-listener!)
-  (try
-    (when-let [app (state/active-tldraw-app)]
-      (let [{:keys [page-name deleted-shapes new-shapes changed-shapes]} (:data tx-meta)
-            whiteboard-op (:whiteboard/op tx-meta)
-            ^js api (.-api app)
-            tl-page ^js (second (first (.-pages app)))]
-        (when api
-          (update-bindings! tl-page page-name)
-          (update-shapes-index! tl-page page-name)
-          (case whiteboard-op
-            :group
-            (do
-              (select-shapes api (mapcat :children new-shapes))
-              (.doGroup api))
-            :un-group
-            (do
-              (select-shapes api (map :id deleted-shapes))
-              (.unGroup api))
-            (do
-              (when (seq deleted-shapes)
-                (delete-shapes! api deleted-shapes))
-              (when (seq new-shapes)
-                (create-shapes! api new-shapes))
-              (when (seq changed-shapes)
-                (update-shapes! api changed-shapes)))))))
-    (catch :default e
-      (js/console.error e)))
-  (history/resume-listener!))
-
 (defn onboarding-show
   []
   (when (not (or (state/sub :whiteboard/onboarding-tour?)

+ 13 - 12
src/main/frontend/modules/editor/undo_redo.cljs

@@ -136,9 +136,18 @@
       (pop-undo))
     (pop-undo)))
 
+(defn pause-listener!
+  []
+  (reset! *pause-listener true))
+
+(defn resume-listener!
+  []
+  (reset! *pause-listener false))
+
 (defn undo
   []
   (when-let [e (smart-pop-undo)]
+    (pause-listener!)
     (state/set-editor-op! :undo)
     (let [{:keys [txs tx-meta tx-id]} e
           new-txs (get-txs false txs)
@@ -146,8 +155,7 @@
       (push-redo e)
       (p/do!
        (transact! new-txs (assoc tx-meta :undo? true))
-       (when (:whiteboard/transact? tx-meta)
-         (state/pub-event! [:whiteboard/undo e]))
+
        (when (= :rename-page (:outliner-op tx-meta))
          (when-let [old-page (:old-name (:data tx-meta))]
            (route-handler/redirect-to-page! old-page)))
@@ -159,6 +167,7 @@
 (defn redo
   []
   (when-let [{:keys [txs tx-meta tx-id] :as e} (smart-pop-redo)]
+    (pause-listener!)
     (state/set-editor-op! :redo)
     (let [new-txs (get-txs true txs)
           editor-cursor (let [s (get @(get @state/state :history/tx->editor-cursor) tx-id)]
@@ -168,8 +177,6 @@
       (push-undo e)
       (p/do!
        (transact! new-txs (assoc tx-meta :redo? true))
-       (when (:whiteboard/transact? tx-meta)
-         (state/pub-event! [:whiteboard/redo e]))
 
        (when (= :rename-page (:outliner-op tx-meta))
          (when-let [new-page (:new-name (:data tx-meta))]
@@ -188,13 +195,6 @@
     (notification/show!
      [:p (str "Undo/redo mode: " mode)])))
 
-(defn pause-listener!
-  []
-  (reset! *pause-listener true))
-
-(defn resume-listener!
-  []
-  (reset! *pause-listener false))
 
 (defn listen-db-changes!
   [{:keys [tx-id tx-data tx-meta blocks pages]}]
@@ -216,4 +216,5 @@
                                            :ui/sidebar-open?
                                            :ui/sidebar-collapsed-blocks
                                            :sidebar/blocks])}]
-      (push-undo entity))))
+      (push-undo entity)))
+  (resume-listener!))

+ 4 - 1
src/main/frontend/modules/outliner/pipeline.cljs

@@ -41,7 +41,10 @@
 
 (defn invoke-hooks
   [{:keys [request-id tx-meta tx-data deleted-block-uuids affected-keys blocks] :as opts}]
-  ;; (prn :debug :request-id request-id)
+  ;; (prn :debug
+  ;;      :request-id request-id
+  ;;      :tx-meta tx-meta
+  ;;      :tx-data tx-data)
   (let [{:keys [from-disk? new-graph? local-tx? undo? redo?]} tx-meta
         repo (state/get-current-repo)
         tx-report {:tx-meta tx-meta

+ 1 - 1
src/main/frontend/modules/shortcut/config.cljs

@@ -133,7 +133,7 @@
                                              :fn      #(.sendToBack ^js (state/active-tldraw-app))}
 
    :whiteboard/bring-forward                {:binding "close-square-bracket"
-                                             :fn      #(.bringForward ^js (state/active-tldraw-app))}
+                                             :fn      #(.bringForward ^js (state/active-tldraw-app) false)}
 
    :whiteboard/bring-to-front               {:binding "shift+close-square-bracket"
                                              :fn      #(.bringToFront ^js (state/active-tldraw-app))}

+ 3 - 3
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -278,9 +278,9 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     this.app.createNewLineBinding(shape, clone)
     this.app.history.resume()
     this.app.persist();
-    setTimeout(() => this.editShape(clone)) 
+    setTimeout(() => this.editShape(clone))
   }
-  
+
   /** Clone shapes with given context */
   cloneShapes = ({
     shapes,
@@ -460,7 +460,7 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
       this.app.currentPage.addShapes(group)
       this.app.setSelectedShapes([group])
       // the shapes in the group should also be moved to the bottom of the array (to be on top on the canvas)
-      this.app.bringForward(selectedShapes)
+      this.app.bringForward(selectedShapes, true)
     }
     this.app.persist()
   }

+ 2 - 2
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -277,8 +277,8 @@ export class TLApp<
     return this.shapes.find(group => group.props.children?.includes(shape.id))
   }
 
-  bringForward = (shapes: S[] | string[] = this.selectedShapesArray): this => {
-    if (shapes.length > 0 && !this.readOnly) this.currentPage.bringForward(shapes)
+  bringForward = (shapes: S[] | string[] = this.selectedShapesArray, skipPersist: boolean): this => {
+    if (shapes.length > 0 && !this.readOnly) this.currentPage.bringForward(shapes, skipPersist)
     return this
   }
 

+ 5 - 6
tldraw/packages/core/src/lib/TLPage/TLPage.ts

@@ -50,6 +50,9 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
         const changedShapeIds = [...allIds].filter(s => {
           return lastShapesNounces[s] !== newShapesNouncesMap[s]
         })
+
+        // const changedShapes = changedShapeIds.map((id) => this.getShapeById(id))
+
         requestAnimationFrame(() => {
           this.cleanup(changedShapeIds)
         })
@@ -133,7 +136,7 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
     return shapeInstances
   }
 
-  @action bringForward = (shapes: S[] | string[]): this => {
+  @action bringForward = (shapes: S[] | string[], skipPersist: boolean): this => {
     const shapesToMove = this.parseShapesArg(shapes)
     shapesToMove
       .sort((a, b) => this.shapes.indexOf(b) - this.shapes.indexOf(a))
@@ -146,7 +149,7 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
         this.shapes[index] = this.shapes[index + 1]
         this.shapes[index + 1] = t
       })
-    this.app.persist()
+    if (!skipPersist) this.app.persist()
     return this
   }
 
@@ -286,10 +289,6 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
         bindings: newBindings,
       })
     }
-
-    if (shapeChanged || bindingChanged) {
-      this.app.persist(true)
-    }
   }
 
   private updateArrowBindings = (lineShape: TLLineShape) => {