export interface StateHistory { past: T[]; present: T; future: T[]; } export interface HistoryOptions { undoable: boolean; limit?: number; } export function jump(state: StateHistory, n: number): StateHistory { if (!isStateHistory(state)) { throw Error("Provided state is not of type StateHistory"); } if (n > 0) return jumpToFuture(state, n - 1); if (n < 0) return jumpToPast(state, state.past.length + n); return state; } function jumpToFuture(state: StateHistory, index: number): StateHistory { if (index < 0 || index >= state.future.length) { return state; } const { past, future, present } = state; const newPast = [...past, present, ...future.slice(0, index)]; const newPresent = future[index]; const newFuture = future.slice(index + 1); return { past: newPast, present: newPresent, future: newFuture }; } function jumpToPast(state: StateHistory, index: number): StateHistory { if (index < 0 || index >= state.past.length) { return state; } const { past, future, present } = state; const newPast = past.slice(0, index); const newFuture = [...past.slice(index + 1), present, ...future]; const newPresent = past[index]; return { past: newPast, present: newPresent, future: newFuture }; } export function nextStateHistory(presentStateHistory: StateHistory, nextPresent: T): StateHistory { return { ...presentStateHistory, ...{ past: [...presentStateHistory.past, presentStateHistory.present], present: nextPresent, future: [] } }; } export function applyLimits(state: T | Partial>, limit: number): T { // the TS type guard is working really funny here if (isStateHistory(state as Partial>)) { if ((state as StateHistory).past.length > limit) { (state as StateHistory).past = (state as StateHistory).past.slice((state as StateHistory).past.length - limit); } if ((state as StateHistory).future.length > limit) { (state as StateHistory).future = (state as StateHistory).future.slice(0, limit); } } return state as T; } export function isStateHistory(history: Partial>): history is StateHistory { return typeof history.present !== 'undefined' && typeof history.future !== 'undefined' && typeof history.past !== 'undefined' && Array.isArray(history.future) && Array.isArray(history.past); }