Files
Michael Ramos 4465950f0c feat: add bounded annotation undo and redo (#1426)
* feat: add bounded undo and redo history

* fix: address undo redo review feedback

* chore: refresh guide viewer manifest

* fix undo history review regressions

* chore: refresh guide viewer manifest
2026-08-31 09:12:13 -07:00

59 lines
1.8 KiB
TypeScript

import { DEFAULT_STATE, type AnnotatorState, type Stroke } from './types';
/** Internal image-annotator state that adds redo without widening the published state contract. */
export interface StrokeHistoryState extends AnnotatorState {
/** Strokes removed by undo, newest redo candidate last. */
futureStrokes: Stroke[];
}
/** Initial state for the image annotator's internal stroke history. */
export const DEFAULT_STROKE_HISTORY_STATE: StrokeHistoryState = {
...DEFAULT_STATE,
futureStrokes: [],
};
/** Commit a completed stroke and invalidate the abandoned redo branch. */
export function recordStroke(state: StrokeHistoryState, stroke: Stroke): StrokeHistoryState {
return {
...state,
strokes: [...state.strokes, stroke],
futureStrokes: [],
currentStroke: null,
};
}
/** Move the latest visible stroke to the redo stack. */
export function undoStroke(state: StrokeHistoryState): StrokeHistoryState {
const stroke = state.strokes.at(-1);
if (!stroke) return state;
return {
...state,
strokes: state.strokes.slice(0, -1),
futureStrokes: [...state.futureStrokes, stroke],
currentStroke: null,
};
}
/** Restore the latest stroke removed by undo. */
export function redoStroke(state: StrokeHistoryState): StrokeHistoryState {
const stroke = state.futureStrokes.at(-1);
if (!stroke) return state;
return {
...state,
strokes: [...state.strokes, stroke],
futureStrokes: state.futureStrokes.slice(0, -1),
currentStroke: null,
};
}
/** Clear the canvas and invalidate both stroke branches. */
export function clearStrokeHistory(state: StrokeHistoryState): StrokeHistoryState {
if (state.strokes.length === 0 && state.futureStrokes.length === 0 && state.currentStroke === null) return state;
return {
...state,
strokes: [],
futureStrokes: [],
currentStroke: null,
};
}