Created
January 1, 2021 16:43
-
-
Save csenio/246b453db80a6755f2d1ea8b15ba3b88 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// full explanation here: https://jsco.dev/blog/undo-across-multiple-stores-in-svelte | |
/* Use like this: | |
* import undoable, { undoTracker } from "./undoable.js"; | |
* const myStore = undoable(INITIAL_VALUE) | |
* if(undoTracker.can_undo) undoTracker.undo() | |
* if(undoTracker.can_redo) undoTracker.redo() | |
*/ | |
import {writable, derived, get} from "svelte/store" | |
import {tick} from "svelte" | |
function createUndoTracker() { | |
const undoStore = writable([]) | |
const redoStore = writable([]) | |
let redoStack = [] | |
redoStore.subscribe((val) => (redoStack = val)) | |
let undoStack = [] | |
undoStore.subscribe((val) => (undoStack = val)) | |
function redo() { | |
if (redoStack.length === 0) return | |
const [lastUpdate, ...restUpdates] = redoStack | |
// update origin store | |
lastUpdate.store.set(lastUpdate.to) | |
// remove from redo store | |
redoStore.set(restUpdates) | |
// add back to undo store | |
undoStore.update((arr) => [lastUpdate, ...arr]) | |
} | |
function undo() { | |
if (undoStack.length === 0) return | |
const [lastUpdate, ...restUpdates] = undoStack | |
// update origin store | |
lastUpdate.store.set(lastUpdate.from) | |
// remove from undo store | |
undoStore.set(restUpdates) | |
// add to redo store | |
redoStore.update((arr) => [lastUpdate, ...arr]) | |
} | |
let currentPatch = false | |
function track({from, to, store, synchronous}) { | |
if (synchronous) { | |
undoStore.update((stack) => [{from, to, store}, ...stack]) | |
} else if (!currentPatch) { | |
currentPatch = { | |
from, | |
timerId: setTimeout(() => { | |
undoStore.update((stack) => [ | |
{from: currentPatch.from, to, store}, | |
...stack, | |
]) | |
currentPatch = false | |
}, 200), | |
} | |
} else { | |
// patch updates that happen very frequently together | |
clearTimeout(currentPatch.timerId) | |
currentPatch.timerId = setTimeout(() => { | |
console.log("track", {from: currentPatch.from, to, store}) | |
undoStore.update((stack) => [ | |
{from: currentPatch.from, to, store}, | |
...stack, | |
]) | |
currentPatch = false | |
}, 200) | |
} | |
} | |
const can_undo = derived(undoStore, (arr) => arr.length > 0) | |
const can_redo = derived(redoStore, (arr) => arr.length > 0) | |
return { | |
undoStore, | |
redoStore, | |
track, | |
undo, | |
can_undo, | |
redo, | |
can_redo, | |
} | |
} | |
export const undoTracker = createUndoTracker() | |
export default function undoable(initialData, options) { | |
const store = writable(initialData) | |
const {subscribe, set, update} = store | |
function trackUndoState(previous) { | |
tick().then(() => | |
undoTracker.track({ | |
from: previous, | |
to: get(store), | |
store, | |
synchronous: options?.noDebounce, | |
}), | |
) | |
} | |
function _set(newValue) { | |
trackUndoState(get(store)) | |
set(newValue) | |
} | |
function _update(updater) { | |
trackUndoState(get(store)) | |
update(updater) | |
} | |
return { | |
subscribe, | |
set: _set, | |
update: _update, | |
reset: () => set(0), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment