undox
Version:
Redux implementation of Undo/Redo based on storing actions instead of states.
124 lines (123 loc) • 6.53 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
exports.__esModule = true;
exports.undox = exports.createSelectors = void 0;
var undox_action_1 = require("./undox.action");
// helper used to flatten the history if grouped actions are in it
var flatten = function (x) { return [].concat.apply([], x); };
// 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 getFutureActions = function (state) { return state.history.slice(state.index + 1); };
var getPastActionsWithPresent = function (state, isPastLimitExceeded) {
var startIdx = isPastLimitExceeded ? 2 : 1;
return __spreadArrays([state.history[0]], state.history.slice(startIdx, state.index + 1));
};
var getPastActions = function (state) { return state.history.slice(0, state.index); };
var getPresentState = function (state) { return state.present; };
exports.createSelectors = function (reducer) {
return {
getPastStates: function (state) {
return getPastActions(state)
.reduce(function (states, a, i) {
return Array.isArray(a)
? __spreadArrays(states, [a.reduce(reducer, states[i - 1])]) : __spreadArrays(states, [reducer(states[i - 1], a)]);
}, []);
},
getFutureStates: function (state) {
return getFutureActions(state)
.reduce(function (states, a, i) {
return Array.isArray(a)
? __spreadArrays(states, [a.reduce(reducer, states[i])]) : __spreadArrays(states, [reducer(states[i], a)]);
}, [getPresentState(state)]).slice(1);
},
getPresentState: getPresentState,
getPastActions: function (state) { return flatten(getPastActions(state)); },
getPresentAction: function (state) { return state.history[state.index]; },
getFutureActions: function (state) { return flatten(getFutureActions(state)); }
};
};
var doNPastStatesExist = function (_a, nStates) {
var index = _a.index;
return index >= nStates;
};
var doNFutureStatesExist = function (_a, nStates) {
var history = _a.history, index = _a.index;
return history.length - 1 - index >= nStates;
};
var group = function (state, action, reducer, comparator, limit) {
var presentState = getPresentState(state);
var nextState = action.payload.reduce(reducer, state.present);
if (comparator(presentState, nextState))
return state;
var isPastLimitExceeded = pastLimitExceeded(state.index, limit);
return __assign(__assign({}, state), { history: __spreadArrays(getPastActionsWithPresent(state, isPastLimitExceeded), [action.payload]), index: state.index + 1, present: nextState });
};
var undo = function (reducer, state, _a, limit) {
var _b = _a.payload, payload = _b === void 0 ? 1 : _b;
var nPastStatesExist = doNPastStatesExist(state, payload);
var index = nPastStatesExist ? state.index - payload : 0;
var futureExceeded = (state.history.length - state.index) > limit.future;
var history = futureExceeded ? state.history.slice(0, -payload) : state.history;
var isPastLimitExceeded = pastLimitExceeded(state.index, limit);
var newState = __assign(__assign({}, state), { history: history,
index: index });
return __assign(__assign({}, newState), { present: calculateState(reducer, getPastActionsWithPresent(!isPastLimitExceeded ? newState : __assign(__assign({}, newState), { index: state.index }), isPastLimitExceeded), newState.initial) });
};
var redo = function (reducer, state, _a) {
var _b = _a.payload, payload = _b === void 0 ? 1 : _b;
var latestFuture = state.history.slice(state.index + 1, state.index + 1 + payload);
return __assign(__assign({}, state), { index: doNFutureStatesExist(state, payload) ? state.index + payload : state.history.length - 1, present: calculateState(reducer, latestFuture, getPresentState(state)) });
};
var pastLimitExceeded = function (index, limit) { return index >= limit.past; };
var delegate = function (state, action, reducer, comparator, limit) {
var nextPresent = reducer(state.present, action);
var index = state.index;
var isPastLimitExceeded = pastLimitExceeded(index, limit);
if (comparator(state.present, nextPresent))
return state;
var newHistory = __spreadArrays(getPastActionsWithPresent(state, isPastLimitExceeded), [action]);
var newState = __assign(__assign({}, state), { history: newHistory, index: isPastLimitExceeded ? state.index : state.index + 1, present: nextPresent, initial: isPastLimitExceeded ? reducer(state.initial, state.history[1]) : state.initial });
return newState;
};
exports.undox = function (reducer, initAction, comparator, limit) {
if (initAction === void 0) { initAction = { type: 'undox/INIT' }; }
if (comparator === void 0) { comparator = function (s1, s2) { return s1 === s2; }; }
var noLimit = { past: Infinity, future: Infinity };
var initial = reducer(undefined, initAction);
var initialState = {
history: [initAction],
present: initial,
index: 0,
initial: initial
};
return function (state, action) {
if (state === void 0) { state = initialState; }
switch (action.type) {
case undox_action_1.UndoxTypes.UNDO:
return undo(reducer, state, action, limit || noLimit);
case undox_action_1.UndoxTypes.REDO:
return redo(reducer, state, action, limit || noLimit);
case undox_action_1.UndoxTypes.GROUP:
return group(state, action, reducer, comparator, limit || noLimit);
default:
return delegate(state, action, reducer, comparator, limit || noLimit);
}
};
};