Files
initiative/apps/web/src/persistence/undo-redo-storage.ts
Lukas 17cc6ed72c Add undo/redo for all encounter actions
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>
2026-03-26 23:30:33 +01:00

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 };
}