fix undo/redo issues (#3658)
Fix some issues with the new undo/redo system - there were a few things that were undoable that shouldn't be, and a few things that weren't but should ### Change Type - [x] `sdk` — Changes the tldraw SDK - [x] `bugfix` — Bug fix
This commit is contained in:
parent
29b6407cdc
commit
8ba46fef49
11 changed files with 108 additions and 9 deletions
|
@ -56,7 +56,9 @@ export function useUrlState(onChangeUrl: (params: UrlStateParams) => void) {
|
||||||
url.searchParams.get(PARAMS.page) ?? 'page:' + url.searchParams.get(PARAMS.p)
|
url.searchParams.get(PARAMS.page) ?? 'page:' + url.searchParams.get(PARAMS.p)
|
||||||
if (newPageId) {
|
if (newPageId) {
|
||||||
if (editor.store.has(newPageId as TLPageId)) {
|
if (editor.store.has(newPageId as TLPageId)) {
|
||||||
editor.setCurrentPage(newPageId as TLPageId)
|
editor.history.ignore(() => {
|
||||||
|
editor.setCurrentPage(newPageId as TLPageId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1450,15 +1450,18 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
setSelectedShapes(shapes: TLShapeId[] | TLShape[]): this {
|
setSelectedShapes(shapes: TLShapeId[] | TLShape[]): this {
|
||||||
return this.batch(() => {
|
return this.batch(
|
||||||
const ids = shapes.map((shape) => (typeof shape === 'string' ? shape : shape.id))
|
() => {
|
||||||
const { selectedShapeIds: prevSelectedShapeIds } = this.getCurrentPageState()
|
const ids = shapes.map((shape) => (typeof shape === 'string' ? shape : shape.id))
|
||||||
const prevSet = new Set(prevSelectedShapeIds)
|
const { selectedShapeIds: prevSelectedShapeIds } = this.getCurrentPageState()
|
||||||
|
const prevSet = new Set(prevSelectedShapeIds)
|
||||||
|
|
||||||
if (ids.length === prevSet.size && ids.every((id) => prevSet.has(id))) return null
|
if (ids.length === prevSet.size && ids.every((id) => prevSet.has(id))) return null
|
||||||
|
|
||||||
this.store.put([{ ...this.getCurrentPageState(), selectedShapeIds: ids }])
|
this.store.put([{ ...this.getCurrentPageState(), selectedShapeIds: ids }])
|
||||||
})
|
},
|
||||||
|
{ history: 'record-preserveRedoStack' }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2030,7 +2033,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
||||||
|
|
||||||
this.batch(() => {
|
this.batch(() => {
|
||||||
const camera = { ...currentCamera, ...point }
|
const camera = { ...currentCamera, ...point }
|
||||||
this.store.put([camera]) // include id and meta here
|
this.history.ignore(() => {
|
||||||
|
this.store.put([camera]) // include id and meta here
|
||||||
|
})
|
||||||
|
|
||||||
// Dispatch a new pointer move because the pointer's page will have changed
|
// Dispatch a new pointer move because the pointer's page will have changed
|
||||||
// (its screen position will compute to a new page position given the new camera position)
|
// (its screen position will compute to a new page position given the new camera position)
|
||||||
|
|
|
@ -19,3 +19,10 @@ it('centers on the point with animation', () => {
|
||||||
jest.advanceTimersByTime(200)
|
jest.advanceTimersByTime(200)
|
||||||
expect(editor.getViewportPageCenter()).toMatchObject({ x: 400, y: 400 })
|
expect(editor.getViewportPageCenter()).toMatchObject({ x: 400, y: 400 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is not undoable', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.centerOnPoint({ x: 400, y: 400 })
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getViewportPageCenter()).toMatchObject({ x: 400, y: 400 })
|
||||||
|
})
|
||||||
|
|
|
@ -14,6 +14,13 @@ describe('When panning', () => {
|
||||||
editor.expectCameraToBe(200, 200, 1)
|
editor.expectCameraToBe(200, 200, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Is not undoable', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.pan({ x: 200, y: 200 })
|
||||||
|
editor.undo()
|
||||||
|
editor.expectCameraToBe(200, 200, 1)
|
||||||
|
})
|
||||||
|
|
||||||
it('Updates the pageBounds', () => {
|
it('Updates the pageBounds', () => {
|
||||||
const screenBounds = editor.getViewportScreenBounds()
|
const screenBounds = editor.getViewportScreenBounds()
|
||||||
const beforeScreenBounds = new Box(
|
const beforeScreenBounds = new Box(
|
||||||
|
|
|
@ -27,4 +27,18 @@ describe('When resetting zoom', () => {
|
||||||
editor.resetZoom()
|
editor.resetZoom()
|
||||||
expect(editor.getViewportScreenBounds().center.clone()).toMatchObject(center)
|
expect(editor.getViewportScreenBounds().center.clone()).toMatchObject(center)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is not undoable', () => {
|
||||||
|
editor.zoomOut()
|
||||||
|
editor.mark()
|
||||||
|
editor.resetZoom()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getZoomLevel()).toBe(1)
|
||||||
|
|
||||||
|
editor.mark()
|
||||||
|
editor.zoomIn()
|
||||||
|
editor.resetZoom()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getZoomLevel()).toBe(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -55,3 +55,29 @@ it('Deleting the parent also deletes descendants', () => {
|
||||||
expect(editor.getShape(ids.box2)).toBeUndefined()
|
expect(editor.getShape(ids.box2)).toBeUndefined()
|
||||||
expect(editor.getShape(ids.ellipse1)).toBeUndefined()
|
expect(editor.getShape(ids.ellipse1)).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('preserves the redo stack', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.select(ids.box1)
|
||||||
|
editor.translateSelection(10, 10)
|
||||||
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 110, y: 110 })
|
||||||
|
|
||||||
|
editor.mark()
|
||||||
|
editor.translateSelection(10, 10)
|
||||||
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 120, y: 120 })
|
||||||
|
|
||||||
|
editor.undo()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 100, y: 100 })
|
||||||
|
|
||||||
|
editor.deselect()
|
||||||
|
editor.redo()
|
||||||
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 110, y: 110 })
|
||||||
|
|
||||||
|
editor.select(ids.box2)
|
||||||
|
editor.redo()
|
||||||
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 120, y: 120 })
|
||||||
|
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getShape(ids.box1)).toMatchObject({ x: 110, y: 110 })
|
||||||
|
})
|
||||||
|
|
|
@ -25,6 +25,13 @@ it('zooms by increments', () => {
|
||||||
expect(editor.getZoomLevel()).toBe(ZOOMS[6])
|
expect(editor.getZoomLevel()).toBe(ZOOMS[6])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is ignored by undo/redo', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.zoomIn()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getZoomLevel()).toBe(ZOOMS[4])
|
||||||
|
})
|
||||||
|
|
||||||
it('preserves the screen center', () => {
|
it('preserves the screen center', () => {
|
||||||
const viewportCenter = editor.getViewportPageCenter().toJson()
|
const viewportCenter = editor.getViewportPageCenter().toJson()
|
||||||
const screenCenter = editor.getViewportScreenCenter().toJson()
|
const screenCenter = editor.getViewportScreenCenter().toJson()
|
||||||
|
|
|
@ -22,6 +22,13 @@ it('zooms by increments', () => {
|
||||||
expect(editor.getZoomLevel()).toBe(ZOOMS[0])
|
expect(editor.getZoomLevel()).toBe(ZOOMS[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is ignored by undo/redo', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.zoomOut()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getZoomLevel()).toBe(ZOOMS[2])
|
||||||
|
})
|
||||||
|
|
||||||
it('does not zoom out when camera is frozen', () => {
|
it('does not zoom out when camera is frozen', () => {
|
||||||
editor.setCamera({ x: 0, y: 0, z: 1 })
|
editor.setCamera({ x: 0, y: 0, z: 1 })
|
||||||
expect(editor.getCamera()).toMatchObject({ x: 0, y: 0, z: 1 })
|
expect(editor.getCamera()).toMatchObject({ x: 0, y: 0, z: 1 })
|
||||||
|
|
|
@ -48,3 +48,10 @@ it('does not zoom to bounds when camera is frozen', () => {
|
||||||
editor.zoomToBounds(new Box(200, 300, 300, 300))
|
editor.zoomToBounds(new Box(200, 300, 300, 300))
|
||||||
expect(editor.getViewportPageCenter().toJson()).toCloselyMatchObject({ x: 500, y: 500 })
|
expect(editor.getViewportPageCenter().toJson()).toCloselyMatchObject({ x: 500, y: 500 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is ignored by undo/redo', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.zoomToBounds(new Box(200, 300, 300, 300))
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getViewportPageCenter().toJson()).toCloselyMatchObject({ x: 350, y: 450 })
|
||||||
|
})
|
||||||
|
|
|
@ -18,3 +18,11 @@ it('does not zoom to bounds when camera is frozen', () => {
|
||||||
editor.zoomToFit()
|
editor.zoomToFit()
|
||||||
expect(editor.getCamera()).toMatchObject(cameraBefore)
|
expect(editor.getCamera()).toMatchObject(cameraBefore)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is ignored by undo/redo', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.zoomToFit()
|
||||||
|
const camera = editor.getCamera()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getCamera()).toBe(camera)
|
||||||
|
})
|
||||||
|
|
|
@ -40,3 +40,12 @@ it('does not zoom to selection when camera is frozen', () => {
|
||||||
editor.zoomToSelection()
|
editor.zoomToSelection()
|
||||||
expect(editor.getCamera()).toMatchObject(cameraBefore)
|
expect(editor.getCamera()).toMatchObject(cameraBefore)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('is ignored by undo/redo', () => {
|
||||||
|
editor.mark()
|
||||||
|
editor.setSelectedShapes([ids.box1, ids.box2])
|
||||||
|
editor.zoomToSelection()
|
||||||
|
const camera = editor.getCamera()
|
||||||
|
editor.undo()
|
||||||
|
expect(editor.getCamera()).toBe(camera)
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in a new issue