-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
undoable-reducer.js
105 lines (90 loc) · 2.52 KB
/
undoable-reducer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* External dependencies
*/
import { combineReducers } from 'redux';
import { includes } from 'lodash';
/**
* Reducer enhancer which transforms the result of the original reducer into an
* object tracking its own history (past, present, future).
*
* @param {Function} reducer Original reducer
* @param {?Object} options Optional options
* @param {?Array} options.resetTypes Action types upon which to clear past
* @return {Function} Enhanced reducer
*/
export function undoable( reducer, options = {} ) {
const initialState = {
past: [],
present: reducer( undefined, {} ),
future: [],
};
return ( state = initialState, action ) => {
const { past, present, future } = state;
switch ( action.type ) {
case 'UNDO':
return {
past: past.slice( 0, past.length - 1 ),
present: past[ past.length - 1 ],
future: [ present, ...future ],
};
case 'REDO':
return {
past: [ ...past, present ],
present: future[ 0 ],
future: future.slice( 1 ),
};
}
const nextPresent = reducer( present, action );
if ( includes( options.resetTypes, action.type ) ) {
return {
past: [],
present: nextPresent,
future: [],
};
}
if ( present === nextPresent ) {
return state;
}
return {
past: [ ...past, present ],
present: nextPresent,
future: [],
};
};
}
/**
* A wrapper for combineReducers which applies an undo history to the combined
* reducer. As a convenience, properties of the reducers object are accessible
* via object getters, with history assigned to a nested history property.
*
* @see undoable
*
* @param {Object} reducers Object of reducers
* @param {?Object} options Optional options
* @return {Function} Combined reducer
*/
export function combineUndoableReducers( reducers, options ) {
const reducer = undoable( combineReducers( reducers ), options );
function withGetters( history ) {
const state = { history };
const keys = Object.getOwnPropertyNames( history.present );
const getters = keys.reduce( ( memo, key ) => {
memo[ key ] = {
get: function() {
return this.history.present[ key ];
},
};
return memo;
}, {} );
Object.defineProperties( state, getters );
return state;
}
const initialState = withGetters( reducer( undefined, {} ) );
return ( state = initialState, action ) => {
const nextState = reducer( state.history, action );
if ( nextState === state.history.present ) {
return state;
}
return withGetters( nextState );
};
}