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>
46 lines
1.3 KiB
TypeScript
46 lines
1.3 KiB
TypeScript
import type { Encounter, UndoRedoState } from "@initiative/domain";
|
|
import { EMPTY_UNDO_REDO_STATE } from "@initiative/domain";
|
|
import { rehydrateEncounter } from "./encounter-storage.js";
|
|
|
|
const UNDO_KEY = "initiative:encounter:undo";
|
|
const REDO_KEY = "initiative:encounter:redo";
|
|
|
|
export function saveUndoRedoStacks(state: UndoRedoState): void {
|
|
try {
|
|
localStorage.setItem(UNDO_KEY, JSON.stringify(state.undoStack));
|
|
localStorage.setItem(REDO_KEY, JSON.stringify(state.redoStack));
|
|
} catch {
|
|
// Silently swallow errors (quota exceeded, storage unavailable)
|
|
}
|
|
}
|
|
|
|
function loadStack(key: string): readonly Encounter[] {
|
|
try {
|
|
const raw = localStorage.getItem(key);
|
|
if (raw === null) return [];
|
|
|
|
const parsed: unknown = JSON.parse(raw);
|
|
if (!Array.isArray(parsed)) return [];
|
|
|
|
const valid: Encounter[] = [];
|
|
for (const entry of parsed) {
|
|
const rehydrated = rehydrateEncounter(entry);
|
|
if (rehydrated !== null) {
|
|
valid.push(rehydrated);
|
|
}
|
|
}
|
|
return valid;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function loadUndoRedoStacks(): UndoRedoState {
|
|
const undoStack = loadStack(UNDO_KEY);
|
|
const redoStack = loadStack(REDO_KEY);
|
|
if (undoStack.length === 0 && redoStack.length === 0) {
|
|
return EMPTY_UNDO_REDO_STATE;
|
|
}
|
|
return { undoStack, redoStack };
|
|
}
|