浏览代码

fix: history undo/redo issues

Peng Xiao 3 年之前
父节点
当前提交
31df972970

+ 1 - 1
tldraw/apps/tldraw-logseq/src/components/AppUI.tsx

@@ -13,7 +13,7 @@ export const AppUI = observer(function AppUI() {
   return (
     <>
       {/* <ToolBar /> */}
-      <Minimap />
+      {/* <Minimap /> */}
       {isDev && <StatusBar />}
       {isDev && <DevTools />}
       <PrimaryTools />

+ 1 - 1
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -152,7 +152,7 @@ export function usePaste() {
       ...shapesToCreate,
     ]
 
-    app.transaction(() => {
+    app.wrapUpdate(() => {
       if (assetsToCreate.length > 0) {
         app.createAssets(assetsToCreate)
       }

+ 2 - 4
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -211,16 +211,14 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     }, [isActivated])
 
     const onPageNameChanged = React.useCallback((id: string) => {
-      transaction(() => {
-        app.history.resume()
+      app.wrapUpdate(() => {
         this.update({
           pageId: id,
           size: [600, 320],
-          blockType: 'page',
+          blockType: 'P',
         })
         this.setDraft(false)
         app.setActivatedShapes([])
-        app.persist()
       })
     }, [])
 

+ 3 - 0
tldraw/demo/vite.config.js

@@ -22,6 +22,9 @@ export default defineConfig({
   ],
   server: {
     port: '3031',
+    fs: {
+      strict: false
+    }
   },
   resolve: {
     alias: [

+ 1 - 2
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -135,6 +135,7 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
 
   cameraToCenter = (): this => {
     const { shapes } = this.app.currentPage
+    if (shapes.length === 0) return this
     // Viewport should be focused to existing shapes
     const commonBounds = BoundsUtils.getCommonBounds(shapes.map(shape => shape.bounds))
     this.app.viewport.update({
@@ -177,13 +178,11 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
   }
 
   undo = () => {
-    this.app.undo()
     this.app.undo()
     return this
   }
 
   redo = () => {
-    this.app.redo()
     this.app.redo()
     return this
   }

+ 9 - 10
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -471,12 +471,6 @@ export class TLApp<
     } else {
       this.selectionRotation = 0
     }
-    if (process.env.NODE_ENV === 'development') {
-      console.log(
-        'setSelectedShapes',
-        newSelectedShapes.map(s => toJS(s.serialized))
-      )
-    }
     return this
   }
 
@@ -720,12 +714,17 @@ export class TLApp<
     return Shape
   }
 
-  transaction = (fn: () => void) => {
+  wrapUpdate = (fn: () => void) => {
     transaction(() => {
-      this.history.pause()
+      const shouldSave = !this.history.isPaused
+      if (shouldSave) {
+        this.history.pause()
+      }
       fn()
-      this.history.resume()
-      this.persist()
+      if (shouldSave) {
+        this.history.resume()
+        this.persist()
+      }
     })
   }
 

+ 1 - 0
tldraw/packages/core/src/lib/TLBaseLineBindingState.ts

@@ -219,6 +219,7 @@ export class TLBaseLineBindingState<
   onExit: TLStateEvents<S, K>['onExit'] = () => {
     this.app.clearBindingShape()
     this.app.history.resume()
+    this.app.persist()
   }
 
   onKeyDown: TLStateEvents<S>['onKeyDown'] = (info, e) => {

+ 26 - 12
tldraw/packages/core/src/lib/TLHistory.ts

@@ -1,3 +1,4 @@
+import { computed, makeObservable, observable } from 'mobx'
 import { TLApp, TLPage, TLDocumentModel, TLShape } from '~lib'
 import type { TLEventMap } from '~types'
 import { deepEqual } from '~utils'
@@ -5,6 +6,7 @@ import { deepEqual } from '~utils'
 export class TLHistory<S extends TLShape = TLShape, K extends TLEventMap = TLEventMap> {
   constructor(app: TLApp<S, K>) {
     this.app = app
+    makeObservable(this)
   }
 
   app: TLApp<S, K>
@@ -16,6 +18,16 @@ export class TLHistory<S extends TLShape = TLShape, K extends TLEventMap = TLEve
     return this.app.selectedTool.currentState.id === 'creating'
   }
 
+  @computed
+  get canUndo() {
+    return this.pointer > 0
+  }
+
+  @computed
+  get canRedo() {
+    return this.pointer < this.stack.length - 1
+  }
+
   pause = () => {
     if (this.isPaused) return
     this.isPaused = true
@@ -34,7 +46,7 @@ export class TLHistory<S extends TLShape = TLShape, K extends TLEventMap = TLEve
     this.app.notify('persist', null)
   }
 
-  persist = () => {
+  persist = (replace = false) => {
     if (this.isPaused || this.creating) return
 
     const { serialized } = this.app
@@ -42,12 +54,16 @@ export class TLHistory<S extends TLShape = TLShape, K extends TLEventMap = TLEve
     // Do not persist if the serialized state is the same as the last one
     if (deepEqual(this.stack[this.pointer], serialized)) return
 
-    if (this.pointer < this.stack.length) {
-      this.stack = this.stack.slice(0, this.pointer + 1)
-    }
+    if (replace) {
+      this.stack[this.pointer] = serialized
+    } else {
+      if (this.pointer < this.stack.length) {
+        this.stack = this.stack.slice(0, this.pointer + 1)
+      }
 
-    this.stack.push(serialized)
-    this.pointer = this.stack.length - 1
+      this.stack.push(serialized)
+      this.pointer = this.stack.length - 1
+    }
 
     this.app.notify('persist', null)
   }
@@ -55,24 +71,21 @@ export class TLHistory<S extends TLShape = TLShape, K extends TLEventMap = TLEve
   undo = () => {
     if (this.isPaused) return
     if (this.app.selectedTool.currentState.id !== 'idle') return
-    if (this.pointer > 0) {
+    if (this.canUndo) {
       this.pointer--
       const snapshot = this.stack[this.pointer]
       this.deserialize(snapshot)
+      this.app.notify('persist', null)
     }
-
-    this.app.notify('persist', null)
   }
 
   redo = () => {
     if (this.isPaused) return
     if (this.app.selectedTool.currentState.id !== 'idle') return
-    if (this.pointer < this.stack.length - 1) {
+    if (this.canRedo) {
       this.pointer++
       const snapshot = this.stack[this.pointer]
       this.deserialize(snapshot)
-    }
-    else{
       this.app.notify('persist', null)
     }
   }
@@ -98,6 +111,7 @@ export class TLHistory<S extends TLShape = TLShape, K extends TLEventMap = TLEve
               // Update the shape
               if (shape.nonce !== serializedShape.nonce) {
                 shape.update(serializedShape, true)
+                shape.nonce = serializedShape.nonce!
               }
               shapesMap.delete(serializedShape.id)
             } else {

+ 10 - 13
tldraw/packages/core/src/lib/TLPage/TLPage.ts

@@ -47,7 +47,7 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
       () => ({
         id: this.id,
         name: this.name,
-        shapes: this.shapes.map(shape => toJS(shape.props)),
+        shapes: toJS(this.shapes.map(shape => toJS(shape.props))),
         bindings: toJS(this.bindings),
         nonce: this.nonce,
         activatedShape: toJS(this.app.activatedIds),
@@ -104,10 +104,8 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
             const ShapeClass = this.app.getShapeClass(shape.type)
             return new ShapeClass(shape)
           })
-    shapeInstances.forEach(instance => observe(instance, this.app.saveState))
     this.shapes.push(...shapeInstances)
     this.bump()
-    this.app.saveState()
     return shapeInstances
   }
 
@@ -213,7 +211,7 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
   }
 
   /**
-   * Recalculate binding positions
+   * Recalculate binding positions etc. Will also persist state when needed.
    *
    * @param curr
    * @param prev
@@ -279,18 +277,17 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
     const shapesToDelete = this.shapes.filter(s => s.draft && !this.app.activatedShapes.includes(s))
 
     if (!deepEqual(updated, curr) || shapesToDelete.length) {
-      transaction(() => {
-        this.app.history.resume()
-        this.update({
-          bindings: updated.bindings,
-        })
+      this.update({
+        bindings: updated.bindings,
+      })
 
-        this.removeShapes(...shapesToDelete)
+      this.removeShapes(...shapesToDelete)
 
-        updated.shapes.forEach(shape => {
-          this.getShapeById(shape.id)?.update(shape)
-        })
+      updated.shapes.forEach(shape => {
+        this.getShapeById(shape.id)?.update(shape)
       })
+
+      this.app.persist(true)
     }
   }
 

+ 3 - 2
tldraw/packages/core/src/lib/shapes/TLShape/TLShape.tsx

@@ -104,7 +104,8 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
   canEdit: TLFlag = false
   canBind: TLFlag = false
   canActivate: TLFlag = false
-  nonce = 0
+  
+  @observable nonce = 0
 
   bindingDistance = BINDING_DISTANCE
 
@@ -282,7 +283,7 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
 
   protected getCachedSerialized = (): TLShapeModel<P> => {
     if (this.isDirty || !this.lastSerialized) {
-      this.nonce++
+      this.nonce = Date.now()
       this.setIsDirty(false)
       this.setLastSerialized(this.getSerialized())
     }

+ 28 - 1
tldraw/packages/core/src/lib/tools/TLLineTool/states/IdleState.tsx

@@ -1,6 +1,6 @@
 import type { TLLineTool } from '../TLLineTool'
 import { TLShape, TLApp, TLToolState, TLLineShape } from '~lib'
-import type { TLEventMap, TLStateEvents } from '~types'
+import { TLEventMap, TLEvents, TLStateEvents, TLTargetType } from '~types'
 
 export class IdleState<
   S extends TLShape,
@@ -29,4 +29,31 @@ export class IdleState<
       }
     }
   }
+
+  onPointerEnter: TLEvents<S>['pointer'] = info => {
+    if (info.order) return
+
+    switch (info.type) {
+      case TLTargetType.Shape: {
+        this.app.setHoveredShape(info.shape.id)
+        break
+      }
+      case TLTargetType.Selection: {
+        if (!(info.handle === 'background' || info.handle === 'center')) {
+          this.tool.transition('hoveringSelectionHandle', info)
+        }
+        break
+      }
+    }
+  }
+
+  onPointerLeave: TLEvents<S>['pointer'] = info => {
+    if (info.order) return
+
+    if (info.type === TLTargetType.Shape) {
+      if (this.app.hoveredId) {
+        this.app.setHoveredShape(undefined)
+      }
+    }
+  }
 }

+ 2 - 35
tldraw/packages/react/src/hooks/useStylesheet.ts

@@ -67,8 +67,8 @@ const css = (strings: TemplateStringsArray, ...args: unknown[]) =>
 
 const defaultTheme: TLTheme = {
   accent: 'rgb(255, 0, 0)',
-  brushFill: 'rgba(0,0,0,.05)',
-  brushStroke: 'rgba(0,0,0,.25)',
+  brushFill: 'var(--ls-scrollbar-background-color, rgba(0, 0, 0, .05))',
+  brushStroke: 'var(--ls-scrollbar-thumb-hover-color, rgba(0, 0, 0, .05))',
   selectStroke: 'rgb(66, 133, 244)',
   selectFill: 'rgba(65, 132, 244, 0.05)',
   binding: 'rgba(65, 132, 244, 0.5)',
@@ -78,39 +78,6 @@ const defaultTheme: TLTheme = {
 }
 
 const tlcss = css`
-  @font-face {
-    font-family: 'Recursive';
-    font-style: normal;
-    font-weight: 500;
-    font-display: swap;
-    src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
-      format('woff2');
-    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
-      U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-  }
-
-  @font-face {
-    font-family: 'Recursive';
-    font-style: normal;
-    font-weight: 700;
-    font-display: swap;
-    src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
-      format('woff2');
-    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
-      U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-  }
-
-  @font-face {
-    font-family: 'Recursive Mono';
-    font-style: normal;
-    font-weight: 420;
-    font-display: swap;
-    src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImqvTxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
-      format('woff2');
-    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
-      U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-  }
-
   .tl-container {
     --tl-cursor: inherit;
     --tl-zoom: 1;