Skip to content

Instantly share code, notes, and snippets.

@mattmccray
Last active Sep 6, 2020
Embed
What would you like to do?
Super Simple Undo Manager
type UndoFn = () => void
type Command = () => UndoFn | Promise<UndoFn>
interface UndoContext {
command: Command
revert: UndoFn | Promise<UndoFn>
}
export class UndoManager {
undoStack: UndoContext[] = []
redoStack: UndoContext[] = []
get canUndo() { return this.undoStack.length > 0 }
get canRedo() { return this.redoStack.length > 0 }
async execute(command: Command) {
const revert = command()
this.undoStack.push({ command, revert })
}
async undo() {
if (!this.canUndo) return
const context = this.undoStack.pop()
if (typeof context.revert === 'function')
context.revert()
else
(await context.revert)()
this.redoStack.push(context)
}
async redo() {
if (!this.canRedo) return
const context = this.redoStack.pop()
this.execute(context.command)
}
}
/**
* Example Usage
*/
const mgr = new UndoManager()
// Actions:
function newNote(title: string) {
// For stable undo/redo context data, use closure
const id = Date.now().toString(36)
let note: any
mgr.execute(() => {
/// Creates new note with `id`
note = { id, title }
return () => {
// Undo create note
}
})
// You can return whatever from your action
return note
}
function deleteNote(note: any) {
mgr.execute(async () => {
// Call server?
await Promise.resolve(true)
return async () => {
// You can return an async method too, if you want.
}
})
}
// Interactions:
newNote("Test One")
newNote("Test Two")
mgr.undo()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment