zoom to affected shapes after undo/redo (#2293)
This PR makes it so that any shapes affected by an undo/redo action, along with any shapes that are selected after an undo/redo action, are visible in the viewport. ### Change Type - [x] `patch` — Bug fix ### Release Notes - Make sure affected shapes are visible after undo/redo
This commit is contained in:
parent
e3d21e0b2b
commit
f7ae99dd1f
2 changed files with 117 additions and 2 deletions
|
@ -99,6 +99,7 @@ import {
|
|||
sortByIndex,
|
||||
} from '../utils/reordering/reordering'
|
||||
import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
|
||||
import { uniq } from '../utils/uniq'
|
||||
import { uniqueId } from '../utils/uniqueId'
|
||||
import { arrowBindingsIndex } from './derivations/arrowBindingsIndex'
|
||||
import { parentsToChildren } from './derivations/parentsToChildren'
|
||||
|
@ -781,6 +782,29 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
}
|
||||
)
|
||||
|
||||
private _ensureAffectedShapesAreMadeVisible(fn: () => void) {
|
||||
// here we collect all the ids of shapes that are created or updated by the function
|
||||
// along with the selectedIds of the current page
|
||||
// then we attempt to make sure that they are all visible in the viewport after
|
||||
// the function is run.
|
||||
const changes = this.store.extractingChanges(fn)
|
||||
const affectedRecordIds = uniq(
|
||||
Object.keys(changes.added)
|
||||
.concat(Object.keys(changes.updated))
|
||||
.concat(this.getSelectedShapeIds())
|
||||
)
|
||||
const shapes = compact(
|
||||
affectedRecordIds.map((id) => (isShapeId(id) ? this.getShape(id) : null))
|
||||
)
|
||||
if (!shapes.length) return this
|
||||
const bounds = Box2d.Common(compact(shapes.map((shape) => this.getShapePageBounds(shape))))
|
||||
const viewport = this.getViewportPageBounds()
|
||||
if (!viewport.contains(bounds)) {
|
||||
this.zoomToBounds(bounds, this.getCamera().z, { duration: 220 })
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo to the last mark.
|
||||
*
|
||||
|
@ -792,7 +816,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
undo(): this {
|
||||
this._ensureAffectedShapesAreMadeVisible(() => {
|
||||
this.history.undo()
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -825,7 +851,9 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|||
* @public
|
||||
*/
|
||||
redo(): this {
|
||||
this._ensureAffectedShapesAreMadeVisible(() => {
|
||||
this.history.redo()
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
AssetRecordType,
|
||||
BaseBoxShapeUtil,
|
||||
Box2d,
|
||||
PageRecordType,
|
||||
TLShape,
|
||||
createShapeId,
|
||||
|
@ -645,3 +646,89 @@ describe('when the user prefers light UI', () => {
|
|||
expect(editor.user.getIsDarkMode()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('undo and redo', () => {
|
||||
test('cause the camera to move if the affected shapes are offscreen', () => {
|
||||
editor = new TestEditor({})
|
||||
editor.setScreenBounds(new Box2d(0, 0, 1000, 1000))
|
||||
editor.user.updateUserPreferences({ animationSpeed: 0 })
|
||||
|
||||
const boxId = createShapeId('box')
|
||||
editor.createShapes([{ id: boxId, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } }])
|
||||
editor.panZoomIntoView([boxId])
|
||||
editor.mark()
|
||||
const cameraBefore = editor.getCamera()
|
||||
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: boxId,
|
||||
type: 'geo',
|
||||
x: 100,
|
||||
y: 100,
|
||||
props: {
|
||||
geo: 'cloud',
|
||||
w: 100,
|
||||
h: 100,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(editor.getCamera()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "camera:page:page",
|
||||
"meta": Object {},
|
||||
"typeName": "camera",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 1,
|
||||
}
|
||||
`)
|
||||
|
||||
editor.undo()
|
||||
expect(editor.getCamera()).toEqual(cameraBefore)
|
||||
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: boxId,
|
||||
type: 'geo',
|
||||
x: -500,
|
||||
y: -500,
|
||||
},
|
||||
])
|
||||
editor.mark()
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: boxId,
|
||||
type: 'geo',
|
||||
x: 500,
|
||||
y: 500,
|
||||
},
|
||||
])
|
||||
editor.undo()
|
||||
|
||||
expect(editor.getCamera()).not.toEqual(cameraBefore)
|
||||
expect(editor.getCamera()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "camera:page:page",
|
||||
"meta": Object {},
|
||||
"typeName": "camera",
|
||||
"x": 950,
|
||||
"y": 950,
|
||||
"z": 1,
|
||||
}
|
||||
`)
|
||||
|
||||
editor.redo()
|
||||
|
||||
expect(editor.getCamera()).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"id": "camera:page:page",
|
||||
"meta": Object {},
|
||||
"typeName": "camera",
|
||||
"x": -50,
|
||||
"y": -50,
|
||||
"z": 1,
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue