mirror of
				https://github.com/reduxjs/redux-devtools.git
				synced 2025-10-30 23:47:35 +03:00 
			
		
		
		
	Figure out monitor composition
This commit is contained in:
		
							parent
							
								
									1edf2d25c8
								
							
						
					
					
						commit
						c1256ed8ff
					
				|  | @ -28,8 +28,8 @@ | |||
|     "node-libs-browser": "^0.5.2", | ||||
|     "react-dock": "^0.1.0", | ||||
|     "react-hot-loader": "^1.3.0", | ||||
|     "redux-devtools": "^3.0.0-alpha-1", | ||||
|     "redux-devtools-log-monitor": "^1.0.0-alpha-3", | ||||
|     "redux-devtools": "^3.0.0-alpha-4", | ||||
|     "redux-devtools-log-monitor": "^1.0.0-alpha-4", | ||||
|     "webpack": "^1.9.11", | ||||
|     "webpack-dev-server": "^1.9.0" | ||||
|   } | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| import React, { Component } from 'react'; | ||||
| import { Provider } from 'react-redux'; | ||||
| import { DevToolsProvider } from 'redux-devtools'; | ||||
| import CounterApp from './CounterApp'; | ||||
| import Dock from 'react-dock'; | ||||
| import LogMonitor from 'redux-devtools-log-monitor'; | ||||
| import DockMonitor from '../dock/DockMonitor'; | ||||
| 
 | ||||
| export default class Root extends Component { | ||||
|   render() { | ||||
|  | @ -12,9 +13,11 @@ export default class Root extends Component { | |||
|         <Provider store={store}> | ||||
|           <CounterApp /> | ||||
|         </Provider> | ||||
|         <Dock position='right' isVisible dimMode='none'> | ||||
|           <LogMonitor store={store} /> | ||||
|         </Dock> | ||||
|         <DevToolsProvider store={store}> | ||||
|           <DockMonitor> | ||||
|             <LogMonitor /> | ||||
|           </DockMonitor> | ||||
|         </DevToolsProvider> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										109
									
								
								examples/counter/src/dock/DockMonitor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								examples/counter/src/dock/DockMonitor.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,109 @@ | |||
| //
 | ||||
| // TODO: extract to a separate project.
 | ||||
| //
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import Dock from 'react-dock'; | ||||
| import MapProvider from './MapProvider'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { combineReducers } from 'redux'; | ||||
| 
 | ||||
| const TOGGLE_VISIBILITY = '@@redux-devtools/dock/TOGGLE_VISIBILITY'; | ||||
| function toggleVisibility() { | ||||
|   return { type: TOGGLE_VISIBILITY }; | ||||
| } | ||||
| 
 | ||||
| const CHANGE_POSITION = '@@redux-devtools/dock/CHANGE_POSITION'; | ||||
| function changePosition() { | ||||
|   return { type: CHANGE_POSITION }; | ||||
| } | ||||
| 
 | ||||
| const POSITIONS = ['left', 'top', 'right', 'bottom']; | ||||
| 
 | ||||
| function wrapReducer(options = {}) { | ||||
|   const { | ||||
|     isVisible: initialIsVisible = true, | ||||
|     position: initialPosition = 'right' | ||||
|   } = options; | ||||
| 
 | ||||
|   function position(state = initialPosition, action) { | ||||
|     return (action.type === CHANGE_POSITION) ? | ||||
|       POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] : | ||||
|       state; | ||||
|   } | ||||
| 
 | ||||
|   function isVisible(state = initialIsVisible, action) { | ||||
|     return (action.type === TOGGLE_VISIBILITY) ? | ||||
|       !state : | ||||
|       state; | ||||
|   } | ||||
| 
 | ||||
|   return childMonitorReducer => combineReducers({ | ||||
|     childMonitorState: childMonitorReducer, | ||||
|     position, | ||||
|     isVisible | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function mapUpstreamStateToDownstreamState(state) { | ||||
|   return { | ||||
|     devToolsState: state.devToolsState, | ||||
|     monitorState: state.monitorState.childMonitorState | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| @connect( | ||||
|   state => state.monitorState, | ||||
|   { toggleVisibility, changePosition } | ||||
| ) | ||||
| export default class DockMonitor extends Component { | ||||
|   static propTypes = { | ||||
|     position: PropTypes.oneOf(['left', 'top', 'right', 'bottom']).isRequired, | ||||
|     isVisible: PropTypes.bool.isRequired, | ||||
|     childMonitorState: PropTypes.any, | ||||
|     toggleVisibility: PropTypes.func.isRequired, | ||||
|     changePosition: PropTypes.func.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this.handleKeyDown = this.handleKeyDown.bind(this); | ||||
|     window.addEventListener('keydown', this.handleKeyDown); | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount() { | ||||
|     window.removeEventListener('keydown', this.handleKeyDown); | ||||
|   } | ||||
| 
 | ||||
|   handleKeyDown(e) { | ||||
|     if (!e.ctrlKey) { | ||||
|       return; | ||||
|     } | ||||
|     e.preventDefault(); | ||||
| 
 | ||||
|     const key = event.keyCode || event.which; | ||||
|     const char = String.fromCharCode(key); | ||||
|     switch (char) { | ||||
|     case 'H': | ||||
|       this.props.toggleVisibility(); | ||||
|       break; | ||||
|     case 'D': | ||||
|       this.props.changePosition(); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     const { position, isVisible, children } = this.props; | ||||
|     return ( | ||||
|       <Dock position={position} | ||||
|             isVisible={isVisible} | ||||
|             dimMode='none'> | ||||
|         <MapProvider mapState={mapUpstreamStateToDownstreamState}> | ||||
|           {children} | ||||
|         </MapProvider> | ||||
|       </Dock> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| DockMonitor.wrapReducer = wrapReducer; | ||||
							
								
								
									
										52
									
								
								examples/counter/src/dock/MapProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								examples/counter/src/dock/MapProvider.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| //
 | ||||
| // TODO: extract to a separate project.
 | ||||
| //
 | ||||
| 
 | ||||
| import React, { Children, Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| const identity = _ => _; | ||||
| function mapStore(store, { mapAction = identity, mapState = identity }) { | ||||
|   return { | ||||
|     ...store, | ||||
|     dispatch(action) { | ||||
|       return store.dispatch(mapAction(action)); | ||||
|     }, | ||||
|     subscribe(listener) { | ||||
|       return store.subscribe(listener); | ||||
|     }, | ||||
|     getState() { | ||||
|       return mapState(store.getState()); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default class MapProvider extends Component { | ||||
|   static propTypes = { | ||||
|     mapAction: PropTypes.func, | ||||
|     mapState: PropTypes.func | ||||
|   }; | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|     store: PropTypes.object.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   static childContextTypes = { | ||||
|     store: PropTypes.object.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   getChildContext() { | ||||
|     return { | ||||
|       store: this.store | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   constructor(props, context) { | ||||
|     super(props, context); | ||||
|     this.store = mapStore(context.store, props); | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     return Children.only(this.props.children); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -1,15 +1,28 @@ | |||
| import { createStore, applyMiddleware, compose } from 'redux'; | ||||
| import { devTools, persistState } from 'redux-devtools'; | ||||
| import devTools, { persistState } from 'redux-devtools'; | ||||
| import thunk from 'redux-thunk'; | ||||
| import rootReducer from '../reducers'; | ||||
| import { createMonitorReducer } from 'redux-devtools-log-monitor'; | ||||
| import DockMonitor from '../dock/DockMonitor'; | ||||
| import LogMonitor from 'redux-devtools-log-monitor'; | ||||
| 
 | ||||
| const finalCreateStore = compose( | ||||
|   applyMiddleware(thunk), | ||||
|   devTools(createMonitorReducer({ | ||||
|     isVisibleOnLoad: true | ||||
|   })), | ||||
|   persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)) | ||||
|   applyMiddleware( | ||||
|     thunk | ||||
|   ), | ||||
|   devTools( | ||||
|     LogMonitor.createReducer({ | ||||
|       preserveScrollTop: true | ||||
|     }), | ||||
|     DockMonitor.wrapReducer({ | ||||
|       position: 'right', | ||||
|       isVisible: true | ||||
|     }) | ||||
|   ), | ||||
|   persistState( | ||||
|     window.location.href.match( | ||||
|       /[?&]debug_session=([^&]+)\b/ | ||||
|     ) | ||||
|   ) | ||||
| )(createStore); | ||||
| 
 | ||||
| export default function configureStore(initialState) { | ||||
|  |  | |||
							
								
								
									
										37
									
								
								src/DevToolsProvider.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/DevToolsProvider.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import React, { Component } from 'react'; | ||||
| import { Provider } from 'react-redux'; | ||||
| 
 | ||||
| export default class DevToolsProvider extends Component { | ||||
|   static propTypes = { | ||||
|     store(props, propName, componentName) { | ||||
|       if (!props.store) { | ||||
|         return new Error('Required prop `store` was not specified in `' + componentName + '`.'); | ||||
|       } | ||||
|       if (!props.store.devToolsStore) { | ||||
|         return new Error( | ||||
|           'Could not find the DevTools store inside the `store` prop passed to `' + | ||||
|           componentName + | ||||
|           '`. Have you applied the devTools() store enhancer?' | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { store, children } = this.props; | ||||
|     if (!store) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const { devToolsStore } = store; | ||||
|     if (!devToolsStore) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Provider store={devToolsStore}> | ||||
|         {children} | ||||
|       </Provider> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -16,38 +16,6 @@ export default function connectMonitor(monitorActionCreators = {}) { | |||
|     } | ||||
|     const ConnectedMonitor = connect(mapStateToProps, mapDispatchToProps)(Monitor); | ||||
| 
 | ||||
|     class DevTools extends Component { | ||||
|       static Monitor = Monitor; | ||||
| 
 | ||||
|       static propTypes = { | ||||
|         store(props, propName, componentName) { | ||||
|           if (!props.store) { | ||||
|             return new Error('Required prop `store` was not specified in `' + componentName + '`.'); | ||||
|           } | ||||
|           if (!props.store.devToolsStore) { | ||||
|             return new Error( | ||||
|               'Could not find the DevTools store inside the `store` prop passed to `' + | ||||
|               componentName + | ||||
|               '`. Have you applied the devTools() store enhancer?' | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|       }; | ||||
| 
 | ||||
|       render() { | ||||
|         const { store } = this.props; | ||||
|         if (!store) { | ||||
|           return null; | ||||
|         } | ||||
| 
 | ||||
|         const { devToolsStore } = store; | ||||
|         if (!devToolsStore) { | ||||
|           return null; | ||||
|         } | ||||
| 
 | ||||
|         return <ConnectedMonitor {...this.props} store={devToolsStore} />; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return DevTools; | ||||
|   }; | ||||
|  |  | |||
|  | @ -1,3 +1,5 @@ | |||
| import { combineReducers, compose } from 'redux'; | ||||
| 
 | ||||
| const ActionTypes = { | ||||
|   PERFORM_ACTION: 'PERFORM_ACTION', | ||||
|   RESET: 'RESET', | ||||
|  | @ -77,20 +79,19 @@ function recomputeStates(reducer, committedState, stagedActions, skippedActions) | |||
| /** | ||||
|  * Lifts the app state reducer into a DevTools state reducer. | ||||
|  */ | ||||
| function liftReducer(reducer, monitorReducer, initialState) { | ||||
|   const initialLiftedState = { | ||||
|     committedState: initialState, | ||||
| function createDevToolsStateReducer(reducer, initialCommittedState) { | ||||
|   const initialState = { | ||||
|     committedState: initialCommittedState, | ||||
|     stagedActions: [INIT_ACTION], | ||||
|     skippedActions: {}, | ||||
|     currentStateIndex: 0, | ||||
|     monitorState: monitorReducer(undefined, INIT_ACTION), | ||||
|     timestamps: [Date.now()] | ||||
|   }; | ||||
| 
 | ||||
|   /** | ||||
|    * Manages how the DevTools actions modify the DevTools state. | ||||
|    */ | ||||
|   return function liftedReducer(liftedState = initialLiftedState, liftedAction) { | ||||
|   return function devToolsState(state = initialState, action) { | ||||
|     let shouldRecomputeStates = true; | ||||
|     let { | ||||
|       committedState, | ||||
|  | @ -98,36 +99,35 @@ function liftReducer(reducer, monitorReducer, initialState) { | |||
|       skippedActions, | ||||
|       computedStates, | ||||
|       currentStateIndex, | ||||
|       monitorState, | ||||
|       timestamps | ||||
|     } = liftedState; | ||||
|     } = state; | ||||
| 
 | ||||
|     switch (liftedAction.type) { | ||||
|     switch (action.type) { | ||||
|     case ActionTypes.RESET: | ||||
|       committedState = initialState; | ||||
|       stagedActions = [INIT_ACTION]; | ||||
|       skippedActions = {}; | ||||
|       currentStateIndex = 0; | ||||
|       timestamps = [liftedAction.timestamp]; | ||||
|       timestamps = [action.timestamp]; | ||||
|       break; | ||||
|     case ActionTypes.COMMIT: | ||||
|       committedState = computedStates[currentStateIndex].state; | ||||
|       stagedActions = [INIT_ACTION]; | ||||
|       skippedActions = {}; | ||||
|       currentStateIndex = 0; | ||||
|       timestamps = [liftedAction.timestamp]; | ||||
|       timestamps = [action.timestamp]; | ||||
|       break; | ||||
|     case ActionTypes.ROLLBACK: | ||||
|       stagedActions = [INIT_ACTION]; | ||||
|       skippedActions = {}; | ||||
|       currentStateIndex = 0; | ||||
|       timestamps = [liftedAction.timestamp]; | ||||
|       timestamps = [action.timestamp]; | ||||
|       break; | ||||
|     case ActionTypes.TOGGLE_ACTION: | ||||
|       skippedActions = toggle(skippedActions, liftedAction.index); | ||||
|       skippedActions = toggle(skippedActions, action.index); | ||||
|       break; | ||||
|     case ActionTypes.JUMP_TO_STATE: | ||||
|       currentStateIndex = liftedAction.index; | ||||
|       currentStateIndex = action.index; | ||||
|       // Optimization: we know the history has not changed.
 | ||||
|       shouldRecomputeStates = false; | ||||
|       break; | ||||
|  | @ -142,8 +142,8 @@ function liftReducer(reducer, monitorReducer, initialState) { | |||
|         currentStateIndex++; | ||||
|       } | ||||
| 
 | ||||
|       stagedActions = [...stagedActions, liftedAction.action]; | ||||
|       timestamps = [...timestamps, liftedAction.timestamp]; | ||||
|       stagedActions = [...stagedActions, action.action]; | ||||
|       timestamps = [...timestamps, action.timestamp]; | ||||
| 
 | ||||
|       // Optimization: we know that the past has not changed.
 | ||||
|       shouldRecomputeStates = false; | ||||
|  | @ -151,7 +151,7 @@ function liftReducer(reducer, monitorReducer, initialState) { | |||
|       const previousEntry = computedStates[computedStates.length - 1]; | ||||
|       const nextEntry = computeNextEntry( | ||||
|         reducer, | ||||
|         liftedAction.action, | ||||
|         action.action, | ||||
|         previousEntry.state, | ||||
|         previousEntry.error | ||||
|       ); | ||||
|  | @ -170,15 +170,12 @@ function liftReducer(reducer, monitorReducer, initialState) { | |||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     monitorState = monitorReducer(monitorState, liftedAction); | ||||
| 
 | ||||
|     return { | ||||
|       committedState, | ||||
|       stagedActions, | ||||
|       skippedActions, | ||||
|       computedStates, | ||||
|       currentStateIndex, | ||||
|       monitorState, | ||||
|       timestamps | ||||
|     }; | ||||
|   }; | ||||
|  | @ -200,7 +197,7 @@ function liftAction(action) { | |||
|  * Unlifts the DevTools state to the app state. | ||||
|  */ | ||||
| function unliftState(liftedState) { | ||||
|   const { computedStates, currentStateIndex } = liftedState; | ||||
|   const { computedStates, currentStateIndex } = liftedState.devToolsState; | ||||
|   const { state } = computedStates[currentStateIndex]; | ||||
|   return state; | ||||
| } | ||||
|  | @ -208,24 +205,29 @@ function unliftState(liftedState) { | |||
| /** | ||||
|  * Unlifts the DevTools store to act like the app's store. | ||||
|  */ | ||||
| function unliftStore(liftedStore, monitorReducer) { | ||||
| function mapToComputedStateStore(devToolsStore, wrapReducer) { | ||||
|   let lastDefinedState; | ||||
| 
 | ||||
|   return { | ||||
|     ...liftedStore, | ||||
|     devToolsStore: liftedStore, | ||||
|     ...devToolsStore, | ||||
| 
 | ||||
|     devToolsStore, | ||||
| 
 | ||||
|     dispatch(action) { | ||||
|       liftedStore.dispatch(liftAction(action)); | ||||
|       devToolsStore.dispatch(liftAction(action)); | ||||
|       return action; | ||||
|     }, | ||||
| 
 | ||||
|     getState() { | ||||
|       const state = unliftState(liftedStore.getState()); | ||||
|       const state = unliftState(devToolsStore.getState()); | ||||
|       if (state !== undefined) { | ||||
|         lastDefinedState = state; | ||||
|       } | ||||
|       return lastDefinedState; | ||||
|     }, | ||||
| 
 | ||||
|     replaceReducer(nextReducer) { | ||||
|       liftedStore.replaceReducer(liftReducer(nextReducer, monitorReducer)); | ||||
|       devToolsStore.replaceReducer(wrapReducer(nextReducer)); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|  | @ -237,18 +239,23 @@ export const ActionCreators = { | |||
|   reset() { | ||||
|     return { type: ActionTypes.RESET, timestamp: Date.now() }; | ||||
|   }, | ||||
| 
 | ||||
|   rollback() { | ||||
|     return { type: ActionTypes.ROLLBACK, timestamp: Date.now() }; | ||||
|   }, | ||||
| 
 | ||||
|   commit() { | ||||
|     return { type: ActionTypes.COMMIT, timestamp: Date.now() }; | ||||
|   }, | ||||
| 
 | ||||
|   sweep() { | ||||
|     return { type: ActionTypes.SWEEP }; | ||||
|   }, | ||||
| 
 | ||||
|   toggleAction(index) { | ||||
|     return { type: ActionTypes.TOGGLE_ACTION, index }; | ||||
|   }, | ||||
| 
 | ||||
|   jumpToState(index) { | ||||
|     return { type: ActionTypes.JUMP_TO_STATE, index }; | ||||
|   } | ||||
|  | @ -257,11 +264,21 @@ export const ActionCreators = { | |||
| /** | ||||
|  * Redux DevTools store enhancer. | ||||
|  */ | ||||
| export default function devTools(monitorReducer = () => undefined) { | ||||
| export default function devTools( | ||||
|   monitorReducer = () => null, | ||||
|   ...monitorReducerWrappers | ||||
| ) { | ||||
|   return next => (reducer, initialState) => { | ||||
|     const liftedReducer = liftReducer(reducer, monitorReducer, initialState); | ||||
|     const liftedStore = next(liftedReducer); | ||||
|     const store = unliftStore(liftedStore, monitorReducer); | ||||
|     return store; | ||||
|     const finalMonitorReducer = compose( | ||||
|       ...monitorReducerWrappers.slice().reverse() | ||||
|     )(monitorReducer); | ||||
| 
 | ||||
|     const wrapReducer = (r) => combineReducers({ | ||||
|       devToolsState: createDevToolsStateReducer(r, initialState), | ||||
|       monitorState: finalMonitorReducer | ||||
|     }); | ||||
| 
 | ||||
|     const devToolsStore = next(wrapReducer(reducer)); | ||||
|     return mapToComputedStateStore(devToolsStore, wrapReducer); | ||||
|   }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,3 +1,3 @@ | |||
| export { default as devTools } from './devTools'; | ||||
| export { default, ActionCreators, ActionTypes } from './devTools'; | ||||
| export { default as DevToolsProvider } from './DevToolsProvider'; | ||||
| export { default as persistState } from './persistState'; | ||||
| export { default as connectMonitor } from './connectMonitor'; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user