|
import { type ObservablePrimitive, internal, observable } from "@legendapp/state" |
|
import { Todo } from "./state" |
|
|
|
export function trackHistory<T>(obs: ObservablePrimitive<T>) { |
|
let history = [] as Todo[] |
|
let historyPointer = 0 |
|
let restoringFromHistory = false |
|
|
|
const undoable$ = observable(false) |
|
const redoable$ = observable(false) |
|
|
|
obs.onChange(({ getPrevious }) => { |
|
if (restoringFromHistory) return |
|
|
|
// Don't save history if this is a remote change. |
|
// History will be saved remotely by the client making the local change. |
|
if (internal.globalState.isLoadingRemote || internal.globalState.isLoadingLocal) return |
|
|
|
// if the history array is empty, grab the previous value (it's probably the best initial value) |
|
if (!history.length) { |
|
const previous = getPrevious() |
|
if (previous) history.push(internal.clone(previous)) |
|
historyPointer = 0 |
|
} |
|
|
|
// We're just going to store a copy of the whole object every time it changes. |
|
const snapshot = internal.clone(obs.get()) |
|
|
|
// Because we may have undone to a previous state, we need to truncate the history now |
|
// that we are making this new change. We'll keep the last 40 states from the pointer. |
|
history = history.slice(0, historyPointer + 1) |
|
history.push(snapshot) |
|
|
|
// We're going to keep a pointer to the current history state. |
|
// This way, we can undo to many previous states, and redo. |
|
historyPointer = history.length - 1 |
|
|
|
// update undoable/redoable |
|
undoable$.set(historyPointer > 0) |
|
redoable$.set(historyPointer < history.length - 1) |
|
}) |
|
|
|
return { |
|
undo() { |
|
if (historyPointer > 0) { |
|
historyPointer-- |
|
|
|
const snapshot = history[historyPointer] |
|
restoringFromHistory = true |
|
obs.set(snapshot as any) |
|
restoringFromHistory = false |
|
} else { |
|
console.warn("Already at the beginning of history") |
|
} |
|
|
|
// update undoable/redoable |
|
undoable$.set(historyPointer > 0) |
|
redoable$.set(historyPointer < history.length - 1) |
|
}, |
|
redo() { |
|
if (historyPointer < history.length - 1) { |
|
historyPointer++ |
|
const snapshot = history[historyPointer] |
|
restoringFromHistory = true |
|
obs.set(snapshot as any) |
|
restoringFromHistory = false |
|
} else { |
|
console.warn("Already at the end of history") |
|
} |
|
|
|
// update undoable/redoable |
|
undoable$.set(historyPointer > 0) |
|
redoable$.set(historyPointer < history.length - 1) |
|
}, |
|
undoable$, |
|
redoable$, |
|
} |
|
} |