Skip to content

Instantly share code, notes, and snippets.

@Sawtaytoes
Last active April 10, 2019 00:08
Show Gist options
  • Save Sawtaytoes/bb310267e0e236cbdb40d476c955c338 to your computer and use it in GitHub Desktop.
Save Sawtaytoes/bb310267e0e236cbdb40d476c955c338 to your computer and use it in GitHub Desktop.
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[], subtractor: number, redos: T[]|null }>((d, x) => {
let { state, subtractor, redos } = d;
if (x === UNDO_TOKEN) {
// We were notified of an "undo". pop state.
if (state && state.length > 1) {
d.subtractor += state[state.length - 1] - state[state.length - 2];
redos = redos || (d.redos = []);
redos.push(state.pop());
}
} else if (x === REDO_TOKEN) {
if (redos && redos.length > 0) {
d.subtractor -= redos[redos.length - 1] - state[state.length - 1];
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 - subtractor);
}
return d;
}, { state: null, subtractor: 0, 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