forked from arqex/freezer-redux-devtools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
freezer-redux-middleware.js
203 lines (172 loc) · 5.04 KB
/
freezer-redux-middleware.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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
var ActionTypes = {
INIT: '@@INIT',
PERFORM_ACTION: 'PERFORM_ACTION',
TOGGLE_ACTION: 'TOGGLE_ACTION'
};
/**
* Redux middleware to make freezer and devtools
* talk to each other.
* @param {Freezer} State Freezer's app state.
*/
function FreezerMiddleware( State ){
return function( next ){
return function FreezerStoreEnhancer( someReducer, someState ){
var commitedState = State.get(),
lastAction = 0,
/**
* Freezer reducer will trigger events on any
* devtool action to synchronize freezer's and
* devtool's states.
*
* @param {Object} state Current devtool state.
* @param {Object} action Action being dispatched.
* @return {Object} Freezer state after the action.
*/
reducer = function( state, action ){
if( action.type == ActionTypes.INIT ){
State.set( state || commitedState );
}
else if( lastAction != ActionTypes.PERFORM_ACTION ) {
// Flag that we are dispatching to not
// to dispatch the same action twice
State.skipDispatch = 1;
State.trigger.apply( State, [ action.type ].concat( action.arguments || [] ) );
}
// The only valid state is freezer's one.
return State.get();
},
store = next( reducer ),
liftedStore = store.liftedStore,
dtStore = store.devToolsStore || store.liftedStore,
toolsDispatcher = dtStore.dispatch
;
// Override devTools store's dispatch, to set commitedState
// on Commit action.
dtStore.dispatch = function( action ){
lastAction = action.type;
// If we are using redux-devtools we need to reset the state
// to the last valid one manually
if( liftedStore && lastAction == ActionTypes.TOGGLE_ACTION ){
var states = dtStore.getState().computedStates,
nextValue = states[ action.id - 1].state
;
State.set( nextValue );
}
toolsDispatcher.apply( dtStore, arguments );
return action;
};
// Dispatch any freezer "fluxy" event to let the devTools
// know about the update.
State.on('afterAll', function( reactionName ){
if( reactionName == 'update')
return;
// We don't dispatch if the flag is true
if( this.skipDispatch )
this.skipDispatch = 0;
else {
var args = [].slice.call( arguments, 1 );
store.dispatch({ type: reactionName, args: args });
}
});
return store;
};
};
}
/**
* Binds freezer store to the chrome's redux-devtools extension.
* @param {Freezer} State Freezer's app state
*/
function supportChromeExtension( State ){
if( !window.devToolsExtension )
return console.warn('Chrome devtools extension not available.');
compose(
FreezerMiddleware( State ),
(window.devToolsExtension || function(f){ return f })()
)(createStore)( function( state ){
return state;
});
}
/**
* Creates a valid redux store. Copied directly from redux.
* https://github.com/rackt/redux
*/
function createStore(reducer, initialState) {
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
var ActionTypes = {
INIT: '@@redux/INIT'
};
function getState() {
return currentState;
}
function subscribe(listener) {
listeners.push(listener);
var isSubscribed = true;
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
function dispatch(action) {
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
listeners.slice().forEach(function (listener) {
return listener();
});
return action;
}
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT });
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT });
return {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
};
}
/**
* Composes single-argument functions from right to left.
* Copied directly from redux.
* https://github.com/rackt/redux
*/
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
return function (arg) {
return funcs.reduceRight(function (composed, f) {
return f(composed);
}, arg);
};
}
if( typeof module != 'undefined' ){
module.exports = {
FreezerMiddleware: FreezerMiddleware,
supportChromeExtension: supportChromeExtension
};
}