Adds select undo / redo stack

pull/71/head
Steve Ruiz 2021-08-15 15:35:23 +01:00
rodzic 169ed8e742
commit 72f680ce65
4 zmienionych plików z 159 dodań i 38 usunięć

Wyświetl plik

@ -67,7 +67,7 @@ export function useKeyboardShortcuts(tlstate: TLDrawState) {
// Undo Redo
useHotkeys('command+z', (e) => {
useHotkeys('command+z,ctrl+z', (e) => {
tlstate.undo()
e.preventDefault()
})
@ -77,6 +77,18 @@ export function useKeyboardShortcuts(tlstate: TLDrawState) {
e.preventDefault()
})
// Undo Redo
useHotkeys('command+u,ctrl+u', (e) => {
tlstate.undoSelect()
e.preventDefault()
})
useHotkeys('ctrl+shift-u,command+shift+u', (e) => {
tlstate.redoSelect()
e.preventDefault()
})
/* -------------------- Commands -------------------- */
// Camera

Wyświetl plik

@ -136,14 +136,78 @@ describe('TLDrawState', () => {
expect(tlstate.status.current).toBe('idle')
})
it('selects shapes if shift key is lifted before pointerup', () => {
tlstate.deselectAll()
tlu.clickShape('rect1')
tlu.pointShape('rect2', { shiftKey: true })
expect(tlstate.status.current).toBe('pointingBounds')
tlu.stopPointing('rect2')
// it('selects shapes if shift key is lifted before pointerup', () => {
// tlstate.deselectAll()
// tlu.clickShape('rect1')
// tlu.pointShape('rect2', { shiftKey: true })
// expect(tlstate.status.current).toBe('pointingBounds')
// tlu.stopPointing('rect2')
// expect(tlstate.selectedIds).toStrictEqual(['rect2'])
// expect(tlstate.status.current).toBe('idle')
// })
})
describe('Select history', () => {
it('selects, undoes and redoes', () => {
tlstate.reset().loadDocument(mockDocument)
expect(tlstate.selectHistory.pointer).toBe(0)
expect(tlstate.selectHistory.stack).toStrictEqual([[]])
expect(tlstate.selectedIds).toStrictEqual([])
tlu.pointShape('rect1')
expect(tlstate.selectHistory.pointer).toBe(1)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1']])
expect(tlstate.selectedIds).toStrictEqual(['rect1'])
tlu.stopPointing('rect1')
expect(tlstate.selectHistory.pointer).toBe(1)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1']])
expect(tlstate.selectedIds).toStrictEqual(['rect1'])
tlu.clickShape('rect2', { shiftKey: true })
expect(tlstate.selectHistory.pointer).toBe(2)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1'], ['rect1', 'rect2']])
expect(tlstate.selectedIds).toStrictEqual(['rect1', 'rect2'])
tlstate.undoSelect()
expect(tlstate.selectHistory.pointer).toBe(1)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1'], ['rect1', 'rect2']])
expect(tlstate.selectedIds).toStrictEqual(['rect1'])
tlstate.undoSelect()
expect(tlstate.selectHistory.pointer).toBe(0)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1'], ['rect1', 'rect2']])
expect(tlstate.selectedIds).toStrictEqual([])
tlstate.redoSelect()
expect(tlstate.selectHistory.pointer).toBe(1)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1'], ['rect1', 'rect2']])
expect(tlstate.selectedIds).toStrictEqual(['rect1'])
tlstate.select('rect2')
expect(tlstate.selectHistory.pointer).toBe(2)
expect(tlstate.selectHistory.stack).toStrictEqual([[], ['rect1'], ['rect2']])
expect(tlstate.selectedIds).toStrictEqual(['rect2'])
expect(tlstate.status.current).toBe('idle')
tlstate.delete()
expect(tlstate.selectHistory.pointer).toBe(0)
expect(tlstate.selectHistory.stack).toStrictEqual([[]])
expect(tlstate.selectedIds).toStrictEqual([])
tlstate.undoSelect()
expect(tlstate.selectHistory.pointer).toBe(0)
expect(tlstate.selectHistory.stack).toStrictEqual([[]])
expect(tlstate.selectedIds).toStrictEqual([])
})
})
})

Wyświetl plik

@ -36,6 +36,7 @@ import {
History,
TLDrawStatus,
ParametersExceptFirst,
SelectHistory,
} from '~types'
import { TLDR } from './tldr'
import { defaultStyle } from '~shape'
@ -89,6 +90,10 @@ export class TLDrawState implements TLCallbacks {
stack: [],
pointer: -1,
}
selectHistory: SelectHistory = {
stack: [[]],
pointer: 0,
}
clipboard?: TLDrawShape[]
session?: Session
pointedId?: string
@ -521,6 +526,8 @@ export class TLDrawState implements TLCallbacks {
this.pages = Utils.deepClone(document.pages)
this.pageStates = Utils.deepClone(document.pageStates)
this.currentPageId = Object.values(this.pages)[0].id
this.selectHistory.pointer = 0
this.selectHistory.stack = [[]]
this.setState((data) => ({
page: this.pages[this.currentPageId],
@ -673,6 +680,8 @@ export class TLDrawState implements TLCallbacks {
TLDrawStatus.Idle
)
this.clearSelectHistory()
this._onChange?.(this, `command:${command.id}`)
return this
@ -697,6 +706,8 @@ export class TLDrawState implements TLCallbacks {
history.pointer--
this.clearSelectHistory()
this._onChange?.(this, `undo:${command.id}`)
return this
@ -721,6 +732,8 @@ export class TLDrawState implements TLCallbacks {
TLDrawStatus.Idle
)
this.addToSelectHistory(this.selectedIds)
this._onChange?.(this, `redo:${command.id}`)
return this
@ -739,8 +752,36 @@ export class TLDrawState implements TLCallbacks {
return this
}
private clearSelectHistory() {
this.selectHistory.pointer = 0
this.selectHistory.stack = [this.selectedIds]
}
private addToSelectHistory(ids: string[]) {
if (this.selectHistory.pointer < this.selectHistory.stack.length) {
this.selectHistory.stack = this.selectHistory.stack.slice(0, this.selectHistory.pointer + 1)
}
this.selectHistory.pointer++
this.selectHistory.stack.push(ids)
}
undoSelect() {
if (this.selectHistory.pointer > 0) {
this.selectHistory.pointer--
this.setSelectedIds(this.selectHistory.stack[this.selectHistory.pointer])
}
}
redoSelect() {
if (this.selectHistory.pointer < this.selectHistory.stack.length - 1) {
this.selectHistory.pointer++
this.setSelectedIds(this.selectHistory.stack[this.selectHistory.pointer])
}
}
select = (...ids: string[]) => {
this.setSelectedIds(ids)
this.addToSelectHistory(ids)
return this
}
@ -756,11 +797,26 @@ export class TLDrawState implements TLCallbacks {
selectedIds: Object.keys(data.page.shapes),
},
}))
this.addToSelectHistory(this.selectedIds)
return this
}
deselectAll = () => {
this.setSelectedIds([])
if (this.selectedIds.length) {
this.setState((data) => ({
appState: {
...data.appState,
activeTool: 'select',
activeToolType: 'select',
},
pageState: {
...data.pageState,
selectedIds: [],
},
}))
this.addToSelectHistory(this.selectedIds)
}
return this
}
@ -1472,18 +1528,19 @@ export class TLDrawState implements TLCallbacks {
if (info.shiftKey) {
// Unless we just shift-selected the shape, remove it from the selected shapes
if (this.pointedId !== info.target) {
this.setSelectedIds(data.pageState.selectedIds.filter((id) => id !== info.target))
this.select(...data.pageState.selectedIds.filter((id) => id !== info.target))
}
} else {
this.setSelectedIds([info.target])
if (this.pointedId !== info.target && this.selectedIds.length > 1) {
this.select(info.target)
}
}
} else if (this.pointedId === info.target) {
if (info.shiftKey) {
this.setSelectedIds([...data.pageState.selectedIds, info.target])
this.select(...data.pageState.selectedIds, info.target)
} else {
this.setSelectedIds([info.target])
this.select(info.target)
}
this.pointedId = undefined
}
this.setStatus(TLDrawStatus.Idle)
@ -1594,13 +1651,15 @@ export class TLDrawState implements TLCallbacks {
}
if (!data.pageState.selectedIds.includes(info.target)) {
// Set the pointed ID to the shape that was clicked.
this.pointedId = info.target
// Set the pointed ID to the shape that was clicked.
// If the shape is not selected; then if the user is pressing shift,
// add the shape to the current selection; otherwise, set the shape as
// the only selected shape.
this.setSelectedIds([info.target], info.shiftKey)
this.select(
...(info.shiftKey ? [...data.pageState.selectedIds, info.target] : [info.target])
)
}
this.setStatus(TLDrawStatus.PointingBounds)
@ -1616,31 +1675,12 @@ export class TLDrawState implements TLCallbacks {
}
}
onReleaseShape: TLPointerEventHandler = (info) => {
// const data = this.getState()
// switch (this.status.current) {
// case TLDrawStatus.PointingBounds: {
// switch (this.appState.activeTool) {
// case 'select': {
// if (data.pageState.selectedIds.includes(info.target)) {
// // If the shape is not selected; then if the user is pressing shift,
// // add the shape to the current selection; otherwise, set the shape as
// // the only selected shape.
// this.setSelectedIds([info.target], info.shiftKey)
// }
// this.setStatus(TLDrawStatus.Idle)
// break
// }
// }
// break
// }
// }
onReleaseShape: TLPointerEventHandler = () => {
// Unused
}
onDoubleClickShape: TLPointerEventHandler = () => {
// if (this.selectedIds.includes(info.target)) {
// this.setSelectedIds([info.target])
// }
// TODO (drill into group)
}
onRightPointShape: TLPointerEventHandler = () => {

Wyświetl plik

@ -62,6 +62,11 @@ export interface History {
stack: Command[]
}
export interface SelectHistory {
pointer: number
stack: string[][]
}
export interface Session {
id: string
status: TLDrawStatus