import { getData, setData } from "@helpers/alpine"; import { roughSizeOfObject } from "@helpers/lang"; export default function (Alpine) { const maxHistorySize = 100; Alpine.directive("undo", (el, { expression }, { evaluate }) => { const data = getData(el); const history = Alpine.reactive({ stack: [], stackPos: -1, adding: false, clear() { history.stack.length = 0; history.stackPos = -1; }, add(name, newValue, oldValue) { if (!history.adding && newValue !== oldValue) { if (history.stackPos < history.stackSize - 1) { // clear stack above this position const stack = Alpine.raw(history.stack); history.stack = stack.slice(0, history.stackPos + 1); } history.stack.push({ name, newValue, oldValue }); // Don't let stack grow beyond max size if (history.stackSize > maxHistorySize) { history.stack.pop(); } else { history.stackPos++; } } }, undo() { if (!data.undo) { console.error("Missing `undo` method"); return; } if (history.undoable) { history.adding = true; const entry = history.stack[history.stackPos]; data.undo(entry.name, entry.oldValue); history.stackPos--; this.$nextTick(() => (history.adding = false)); } }, redo() { if (!data.redo) { console.error("Missing `redo` method"); return; } if (history.redoable) { history.adding = true; history.stackPos++; const entry = history.stack[history.stackPos]; data.redo(entry.name, entry.newValue); this.$nextTick(() => (history.adding = false)); } }, get undoable() { return history.stackPos >= 0; }, get redoable() { return history.stackPos < history.stackSize - 1; }, get stackSize() { return history.stack.length; }, get stackMemoryUsage() { return roughSizeOfObject(history.stack); }, }); setData(el, { history }); }); }