Skip to content

Instantly share code, notes, and snippets.

@Sawtaytoes
Last active April 9, 2019 23:30
Show Gist options
  • Save Sawtaytoes/66f318b64712c11c9d95e2c790e5d678 to your computer and use it in GitHub Desktop.
Save Sawtaytoes/66f318b64712c11c9d95e2c790e5d678 to your computer and use it in GitHub Desktop.
/**
* Subscribes to the `undoNotifier`, and emits all values from
* `source`. When `undoNotifier` emits, it will emit previously
* emitted values back through time.
*
* If a `redoNotifier` is passed, it's subscribed to, and when
* it emits, will "redo" anything that was "undone", unless new
* values have come from the source.
*
* TODO: Add an upper-bounds to the undo state collected.
*/
function undo<T>(undoNotifier: Observable<any>, redoNotifier: Observable<any> = EMPTY) {
return (source: Observable<T>) => merge(
undoNotifier.pipe(map(() => UNDO_TOKEN)),
redoNotifier.pipe(map(() => REDO_TOKEN)),
source,
).pipe(
scan<T, { state: T[], redos: T[]|null }>((d, x) => {
console.log({d, x})
let { state, redos } = d;
if (x === UNDO_TOKEN) {
// We were notified of an "undo". pop state.
if (state && state.length > 1) {
redos = redos || (d.redos = []);
redos.push(state.pop());
}
} else if (x === REDO_TOKEN) {
if (redos && redos.length > 0) {
state.push(redos.pop());
}
} else {
if (redos) {
// clear our redos as new history is written
redos.length = 0;
}
state = state || (d.state = []);
// It's not an "undo", push state
state.push(x);
}
return d;
}, { state: null, redos: null }),
// we only care about state past here
map(x => x.state),
// Don't emit if we don't have state
filter(x => x !== null),
// Take the last value from state
map(state => state[state.length - 1]),
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment