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]),
)
}