UNPKG

redux-live-undo

Version:

A high-order reducer that enables generic undo/redo functionality without giving up live updates

149 lines (123 loc) 5.09 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = Undoable; var _lodash = require('lodash.clone'); var _lodash2 = _interopRequireDefault(_lodash); var _lodash3 = require('lodash.initial'); var _lodash4 = _interopRequireDefault(_lodash3); var _lodash5 = require('lodash.last'); var _lodash6 = _interopRequireDefault(_lodash5); var _lodash7 = require('lodash.mapvalues'); var _lodash8 = _interopRequireDefault(_lodash7); var _ActionTypes = require('./ActionTypes'); var _NextState2 = require('./NextState'); var _NextState3 = _interopRequireDefault(_NextState2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* * Maintains a state tree for the past, present, and future setting values. This tree allows the `present` state to be * updated live (ie. for every keystroke) while making checkpoints along the way to allow the user to undo and redo * checkpointed edits. Below is a table that shows how this state tree changes with each action. * * New states are generated by child reducers passed in to the reducers argument. * Usage: combineReducers(Undoable({ settings: settingsReducer })); | action | checkpoint | past | present | future | |----------------------|------------|--------------------------------------|--------------------|----------------------| | initial | | `[{ title: "a" }]` | `{ title: "a" }` | `[]` | | UPDATE | false | `[{ title: "a" }]` | `{ title: "ab" }` | `[]` | | UPDATE | false | `[{ title: "a" }]` | `{ title: "abc" }` | `[]` | | UPDATE | true | `[{ title: "a" }, { title: "abc" }]` | `{ title: "abc" }` | `[]` | | UNDO | -- | `[{ title: "a" }]` | `{ title: "a" }` | `[{ title: "abc" }]` | | REDO | -- | `[{ title: "a" }, { title: "abc" }]` | `{ title: "abc" }` | `[]` | */ function Undoable(reducers) { var initialHistory = (0, _lodash8.default)(reducers, function (r) { return r(); }); var initialState = { past: [initialHistory], present: initialHistory, future: [] }; return function () { var state = arguments.length <= 0 || arguments[0] === undefined ? initialState : arguments[0]; var action = arguments[1]; switch (action.type) { /* * - Concat the present state to the end of the future array. * - Copy the last past state to the present state * - Remove the last past state */ case _ActionTypes.UNDO: { // Do not allow undos if there is only one object in the past state. if (state.past.length < 2) { return state; } return { past: (0, _lodash4.default)((0, _lodash2.default)(state.past)), // all but the last one present: state.past[state.past.length - 2], future: state.future.concat([state.present]) }; } /* * - Concat the present state to the end of the past array. * - Copy the last future state to the present state * - Remove the last future state */ case _ActionTypes.REDO: { // Do not allow undos if there are no objects in the future state. if (state.future.length < 1) { return state; } return { past: state.past.concat([(0, _lodash6.default)(state.future)]), present: (0, _lodash6.default)(state.future), future: (0, _lodash4.default)((0, _lodash2.default)(state.future)) // all but last }; } /* * If this is not a history checkpoint: * - Update the present state * - Clear the future state * * If this is a history checkpoint: * - Update the present state * - Push a copy of the present state into the past state * - Clear the future state */ default: { var _NextState = (0, _NextState3.default)(reducers, state, action); var nextState = _NextState.nextState; var anyChanged = _NextState.anyChanged; // New states can only be pushed into the history if anything we care about actually changed. if (anyChanged) { if (action.undoableIrreversibleCheckpoint) { return { past: [nextState], // irreversible checkpoints should clear out history present: nextState, future: [] // clear the future state so we don't lose these new edits by a redo }; } else if (action.undoableHistoryCheckpoint) { return { past: state.past.concat([nextState]), present: nextState, future: [] // clear the future state so we don't lose these new edits by a redo }; } else { return Object.assign({}, state, { present: nextState, future: [] // clear the future state so we don't lose these new edits by a redo }); } } else { return Object.assign({}, state, { present: nextState }); } }} }; }