theme-editor-svelte
Version:
Universal theme editor for svelte
71 lines (70 loc) • 2.45 kB
JavaScript
/* eslint-disable no-undef */
import { Debounced, watch } from 'runed';
import diff, {} from 'microdiff';
import { applyPatch, get, invertPatch } from './utils.js';
/**
* Tracks the change history of a value, providing undo and redo capabilities.
*
* @see {@link https://runed.dev/docs/utilities/state-history}
*/
export class StateHistoryDiff {
#redoStack = $state([]);
#ignoreUpdate = false;
#set;
log = $state([]);
snapshot = $state(null);
canUndo = $derived(this.log.length > 1);
canRedo = $derived(this.#redoStack.length > 0);
constructor(value, set, options) {
this.#redoStack = [];
this.#set = set;
this.undo = this.undo.bind(this);
this.redo = this.redo.bind(this);
this.snapshot = get(value);
const addEvent = (event) => {
this.log.push(event);
const capacity$ = options?.capacity ? get(options?.capacity) : undefined;
if (capacity$ && this.log.length > capacity$) {
this.log = this.log.slice(-capacity$);
}
};
const debouncedValue = new Debounced(() => get(value), 300);
watch(() => debouncedValue.current, (v) => {
if (this.#ignoreUpdate) {
this.#ignoreUpdate = false;
return;
}
const difference = diff($state.snapshot(this.snapshot), v);
if (!difference.length)
return;
this.snapshot = v;
addEvent({ difference, timestamp: new Date().getTime() });
console.log('Added Event', difference);
this.#redoStack = [];
});
watch(() => get(options?.capacity ?? 0), (c) => {
if (!c)
return;
this.log = this.log.slice(-c);
});
}
undo() {
const curr = this.log.at(-1);
if (!curr)
return;
this.#ignoreUpdate = true;
this.#redoStack.push(curr);
this.log.pop();
const newSnapshot = applyPatch($state.snapshot(this.snapshot), invertPatch(curr.difference));
this.#set(newSnapshot);
}
redo() {
const nextEvent = this.#redoStack.pop();
if (!nextEvent)
return;
this.#ignoreUpdate = true;
this.log.push(nextEvent);
const newSnapshot = applyPatch($state.snapshot(this.snapshot), nextEvent.difference);
this.#set(newSnapshot);
}
}