///
import { Cell, Default, handler, lift, recipe, str } from "commontools";
interface CounterRedoStackArgs {
value: Default;
undoStack: Default;
redoStack: Default;
}
const sanitizeNumber = (input: unknown): number => {
if (typeof input !== "number" || !Number.isFinite(input)) {
return 0;
}
return input;
};
const sanitizeStack = (input: unknown): number[] => {
if (!Array.isArray(input)) {
return [];
}
return input.filter((item): item is number => {
return typeof item === "number" && Number.isFinite(item);
});
};
const applyChange = handler(
(
event: { amount?: number } | undefined,
context: {
value: Cell;
undoStack: Cell;
redoStack: Cell;
},
) => {
const amount =
typeof event?.amount === "number" && Number.isFinite(event.amount)
? event.amount
: 1;
const currentValue = sanitizeNumber(context.value.get());
const nextValue = currentValue + amount;
const undoEntries = sanitizeStack(context.undoStack.get());
context.undoStack.set([...undoEntries, currentValue]);
context.value.set(nextValue);
context.redoStack.set([]);
},
);
const undoLast = handler(
(
_event: unknown,
context: {
value: Cell;
undoStack: Cell;
redoStack: Cell;
},
) => {
const undoEntries = sanitizeStack(context.undoStack.get());
if (undoEntries.length === 0) {
return;
}
const redoEntries = sanitizeStack(context.redoStack.get());
const currentValue = sanitizeNumber(context.value.get());
const previousValue = undoEntries[undoEntries.length - 1];
context.undoStack.set(undoEntries.slice(0, undoEntries.length - 1));
context.redoStack.set([...redoEntries, currentValue]);
context.value.set(previousValue);
},
);
const redoNext = handler(
(
_event: unknown,
context: {
value: Cell;
undoStack: Cell;
redoStack: Cell;
},
) => {
const redoEntries = sanitizeStack(context.redoStack.get());
if (redoEntries.length === 0) {
return;
}
const undoEntries = sanitizeStack(context.undoStack.get());
const currentValue = sanitizeNumber(context.value.get());
const nextValue = redoEntries[redoEntries.length - 1];
context.redoStack.set(redoEntries.slice(0, redoEntries.length - 1));
context.undoStack.set([...undoEntries, currentValue]);
context.value.set(nextValue);
},
);
export const counterRedoStack = recipe(
"Counter Redo Stack",
({ value, undoStack, redoStack }) => {
const currentValue = lift((raw: number | undefined) => sanitizeNumber(raw))(
value,
);
const undoHistory = lift((entries: number[] | undefined) =>
sanitizeStack(entries)
)(undoStack);
const redoHistory = lift((entries: number[] | undefined) =>
sanitizeStack(entries)
)(redoStack);
const undoCount = lift((entries: number[]) => entries.length)(undoHistory);
const redoCount = lift((entries: number[]) => entries.length)(redoHistory);
const canUndo = lift((entries: number[]) => entries.length > 0)(
undoHistory,
);
const canRedo = lift((entries: number[]) => entries.length > 0)(
redoHistory,
);
const status =
str`Value ${currentValue} | undo ${undoCount} | redo ${redoCount}`;
return {
value,
undoStack,
redoStack,
currentValue,
undoHistory,
redoHistory,
undoCount,
redoCount,
canUndo,
canRedo,
status,
apply: applyChange({ value, undoStack, redoStack }),
undo: undoLast({ value, undoStack, redoStack }),
redo: redoNext({ value, undoStack, redoStack }),
};
},
);