UNPKG

ngrx-undoable

Version:

[Redux](https://github.com/reactjs/redux)/[Ngrx](https://github.com/ngrx) implementation of [Undo/Redo](http://redux.js.org/docs/recipes/ImplementingUndoHistory.html) based on Actions instead of States

140 lines (139 loc) 6.53 kB
"use strict"; exports.__esModule = true; var undoable_action_1 = require("./undoable.action"); // when grouping actions we will get multidimensional arrays // so this helper is used to flatten the history var flatten = function (x) { return [].concat.apply([], x); }; // since the oldest past is the init action we never want to remove it from the past var doNPastStatesExist = function (past, nStates) { return past.length > nStates; }; var doNFutureStatesExist = function (future, nStates) { return future.length >= nStates; }; // actions can be an array of arrays because of grouped actions, so we flatten it first var calculateState = function (reducer, actions, state) { return flatten(actions).reduce(reducer, state); }; var travelNStates = function (state, nStates, travelOne) { if (nStates === void 0) { nStates = 1; } if (nStates === 0) return state; return travelNStates(travelOne(state, nStates), --nStates, travelOne); }; var createUndo = function (reducer) { return function (state, nStates) { if (nStates === void 0) { nStates = 1; } if (!doNPastStatesExist(state.past, nStates)) return state; var latestPast = state.past[state.past.length - 1]; var futureWithLatestPast = [latestPast].concat(state.future); var pastWithoutLatest = state.past.slice(0, -1); return { past: pastWithoutLatest, present: calculateState(reducer, pastWithoutLatest), future: futureWithLatestPast }; }; }; var createRedo = function (reducer) { return function (state, nStates) { if (nStates === void 0) { nStates = 1; } if (!doNFutureStatesExist(state.future, nStates)) return state; var _a = state.future, latestFuture = _a[0], futureWithoutLatest = _a.slice(1); var pastWithLatestFuture = state.past.concat([latestFuture]); return { past: pastWithLatestFuture, present: calculateState(reducer, [latestFuture], state.present), future: futureWithoutLatest }; }; }; var updateHistory = function (state, newPresent, action, comparator) { if (comparator(state.present, newPresent)) return state; var newPast = state.past.concat([action]); return { past: newPast, present: newPresent, future: [] }; }; var getPastActions = function (state) { return state.past; }; var getPresentAction = function (state) { return state.past[state.past.length - 1]; }; var getFutureActions = function (state) { return state.future; }; var getPresentState = function (state) { return state.present; }; var getPastActionsFlattened = function (state) { return flatten(state.past); }; var getPresentActionFlattened = function (state) { return getPastActionsFlattened(state).slice(-1)[0]; }; var getFutureActionsFlattened = function (state) { return flatten(state.future); }; var getLatestFutureAction = function (state) { return getFutureActions(state)[0]; }; var getLatestFutureActionFlattened = function (state) { return getFutureActionsFlattened(state)[0]; }; /** * Creates the getFutureStates selector. * * The selector is mapping the future actions to future states. * It uses `reduce` instead of `map`, because this way we can reuse the * previous future state to calculate the next future state. * * @param reducer The Reducer that is used to replay actions from the future */ var createGetFutureStates = function (reducer) { return function (state) { return getFutureActions(state) .reduce(function (states, a, i) { return Array.isArray(a) // check if action is grouped ? states.concat([a.reduce(reducer, states[i])]) : states.concat([reducer(states[i], a)]); }, [getPresentState(state)]).slice(1); }; }; // We start with present to calculate future states, but present state should not part be of future so we slice it off /** * Creates the getPastStates selector. * * The selector is mapping the past actions to past states. * It uses `reduce` instead of `map`, because this way we can reuse the * previous past state to calculate the next past state. * * @param reducer The Reducer that is used to replay actions from the past */ var createGetPastStates = function (reducer) { return function (state) { return getPastActions(state).reduce(function (states, a, i) { return Array.isArray(a) // check if action is grouped ? states.concat([a.reduce(reducer, states[i - 1])]) : states.concat([reducer(states[i - 1], a)]); }, []).slice(0, -1); }; }; // Slice the last state since its the present exports.createSelectors = function (reducer) { return { getPresentState: getPresentState, getPastStates: createGetPastStates(reducer), getFutureStates: createGetFutureStates(reducer), getPastActions: getPastActions, getPastActionsFlattened: getPastActionsFlattened, getPresentAction: getPresentAction, getPresentActionFlattened: getPresentActionFlattened, getFutureActions: getFutureActions, getFutureActionsFlattened: getFutureActionsFlattened, getLatestFutureAction: getLatestFutureAction, getLatestFutureActionFlattened: getLatestFutureActionFlattened }; }; var createUndoableReducer = function (reducer, initAction, comparator) { var initialState = { past: [initAction], present: reducer(undefined, initAction), future: [] }; var undo = createUndo(reducer); var redo = createRedo(reducer); return function (state, action) { if (state === void 0) { state = initialState; } switch (action.type) { case undoable_action_1.UndoableTypes.UNDO: return travelNStates(state, action.payload, undo); case undoable_action_1.UndoableTypes.REDO: return travelNStates(state, action.payload, redo); case undoable_action_1.UndoableTypes.GROUP: return updateHistory(state, action.payload.reduce(reducer, state.present), action.payload, comparator); default: return updateHistory(state, reducer(state.present, action), action, comparator); } }; }; exports.undoable = function (reducer, initAction, comparator) { if (initAction === void 0) { initAction = { type: 'ngrx-undoable/INIT' }; } if (comparator === void 0) { comparator = function (s1, s2) { return s1 === s2; }; } return { reducer: createUndoableReducer(reducer, initAction, comparator), selectors: exports.createSelectors(reducer) }; };