Memento-based undo/redo with full encounter snapshots. Undo stack capped at 50 entries, persisted to localStorage. Triggered via buttons in the top bar (inboard of turn navigation) and keyboard shortcuts (Ctrl+Z / Ctrl+Shift+Z, Cmd on Mac, case-insensitive key matching). Clear encounter resets both stacks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
71 lines
1.6 KiB
TypeScript
71 lines
1.6 KiB
TypeScript
import type { DomainError, Encounter } from "./types.js";
|
|
|
|
export interface UndoRedoState {
|
|
readonly undoStack: readonly Encounter[];
|
|
readonly redoStack: readonly Encounter[];
|
|
}
|
|
|
|
const MAX_UNDO_STACK = 50;
|
|
|
|
export const EMPTY_UNDO_REDO_STATE: UndoRedoState = {
|
|
undoStack: [],
|
|
redoStack: [],
|
|
};
|
|
|
|
export function pushUndo(
|
|
state: UndoRedoState,
|
|
snapshot: Encounter,
|
|
): UndoRedoState {
|
|
const newStack = [...state.undoStack, snapshot];
|
|
if (newStack.length > MAX_UNDO_STACK) {
|
|
newStack.shift();
|
|
}
|
|
return { undoStack: newStack, redoStack: [] };
|
|
}
|
|
|
|
export function undo(
|
|
state: UndoRedoState,
|
|
currentEncounter: Encounter,
|
|
): { state: UndoRedoState; encounter: Encounter } | DomainError {
|
|
if (state.undoStack.length === 0) {
|
|
return {
|
|
kind: "domain-error",
|
|
code: "nothing-to-undo",
|
|
message: "Nothing to undo",
|
|
};
|
|
}
|
|
const restored = state.undoStack.at(-1) as Encounter;
|
|
return {
|
|
state: {
|
|
undoStack: state.undoStack.slice(0, -1),
|
|
redoStack: [...state.redoStack, currentEncounter],
|
|
},
|
|
encounter: restored,
|
|
};
|
|
}
|
|
|
|
export function redo(
|
|
state: UndoRedoState,
|
|
currentEncounter: Encounter,
|
|
): { state: UndoRedoState; encounter: Encounter } | DomainError {
|
|
if (state.redoStack.length === 0) {
|
|
return {
|
|
kind: "domain-error",
|
|
code: "nothing-to-redo",
|
|
message: "Nothing to redo",
|
|
};
|
|
}
|
|
const restored = state.redoStack.at(-1) as Encounter;
|
|
return {
|
|
state: {
|
|
undoStack: [...state.undoStack, currentEncounter],
|
|
redoStack: state.redoStack.slice(0, -1),
|
|
},
|
|
encounter: restored,
|
|
};
|
|
}
|
|
|
|
export function clearHistory(): UndoRedoState {
|
|
return EMPTY_UNDO_REDO_STATE;
|
|
}
|