This API is much better

This commit is contained in:
Dan Abramov 2015-10-16 23:59:32 +03:00
parent 79b51a7c0f
commit 1f21d83931
9 changed files with 97 additions and 119 deletions

View File

@ -26,7 +26,7 @@
"babel-core": "^5.6.18",
"babel-loader": "^5.1.4",
"node-libs-browser": "^0.5.2",
"react-dock": "^0.1.0",
"react-dock": "^0.2.1",
"react-hot-loader": "^1.3.0",
"redux-devtools": "^3.0.0-alpha-8",
"redux-devtools-log-monitor": "^1.0.0-alpha-8",

View File

@ -4,7 +4,7 @@ import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from '../dock/DockMonitor';
export default createDevTools(
<DockMonitor defaultPosition='bottom'>
<DockMonitor>
<LogMonitor theme='ocean' />
</DockMonitor>
);

View File

@ -1,41 +1,44 @@
//
// TODO: extract to a separate project.
//
import React, { cloneElement, Children, Component, PropTypes } from 'react';
import React, { cloneElement, Component, PropTypes } from 'react';
import Dock from 'react-dock';
import { combineReducers } from 'redux';
const POSITIONS = ['left', 'top', 'right', 'bottom'];
import { POSITIONS } from './constants';
import { toggleVisibility, changePosition, changeSize } from './actions';
import reducer from './reducers';
export default class DockMonitor extends Component {
static reducer = reducer;
static propTypes = {
defaultPosition: PropTypes.oneOf(POSITIONS).isRequired,
defaultIsVisible: PropTypes.bool.isRequired,
defaultSize: PropTypes.number.isRequired,
toggleVisibilityShortcut: PropTypes.string.isRequired,
changePositionShortcut: PropTypes.string.isRequired,
children: PropTypes.element,
dispatch: PropTypes.func,
monitorState: PropTypes.shape({
position: PropTypes.oneOf(POSITIONS).isRequired,
size: PropTypes.number.isRequired,
isVisible: PropTypes.bool.isRequired,
child: PropTypes.any
}),
monitorActions: PropTypes.shape({
toggleVisibility: PropTypes.func.isRequired,
changePosition: PropTypes.func.isRequired
childMonitorState: PropTypes.any
})
};
static defaultProps = {
defaultIsVisible: true,
defaultPosition: 'right',
defaultSize: 0.3,
toggleVisibilityShortcut: 'H',
changePositionShortcut: 'Q'
};
componentDidMount() {
constructor(props) {
super(props);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleSizeChange = this.handleSizeChange.bind(this);
}
componentDidMount() {
window.addEventListener('keydown', this.handleKeyDown);
}
@ -53,81 +56,36 @@ export default class DockMonitor extends Component {
const char = String.fromCharCode(key);
switch (char.toUpperCase()) {
case this.props.toggleVisibilityShortcut.toUpperCase():
this.props.monitorActions.toggleVisibility();
this.props.dispatch(toggleVisibility());
break;
case this.props.changePositionShortcut.toUpperCase():
this.props.monitorActions.changePosition();
this.props.dispatch(changePosition());
break;
default:
break;
}
}
render() {
const {
monitorState,
monitorActions,
historyState,
historyActions,
children
} = this.props;
handleSizeChange(requestedSize) {
this.props.dispatch(changeSize(requestedSize));
}
const {
position,
isVisible
} = monitorState;
render() {
const { monitorState, children, ...rest } = this.props;
const { position, isVisible, size } = monitorState;
const childProps = {
...rest,
monitorState: monitorState.childMonitorState
};
return (
<Dock position={position}
isVisible={isVisible}
size={size}
onSizeChange={this.handleSizeChange}
dimMode='none'>
{cloneElement(Children.only(children), {
monitorState: monitorState.child,
monitorActions: monitorActions.child,
historyState,
historyActions
})}
{cloneElement(children, childProps)}
</Dock>
);
}
}
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 };
}
DockMonitor.setup = function setup(props) {
function position(state = props.defaultPosition, action) {
return (action.type === CHANGE_POSITION) ?
POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] :
state;
}
function isVisible(state = props.defaultIsVisible, action) {
return (action.type === TOGGLE_VISIBILITY) ?
!state :
state;
}
const child = Children.only(props.children);
const childSetupResult = child.type.setup(child.props);
return {
reducer: combineReducers({
position,
isVisible,
child: childSetupResult.reducer
}),
actionCreators: {
toggleVisibility,
changePosition,
child: childSetupResult.actionCreators
}
};
}

View File

@ -0,0 +1,14 @@
export const TOGGLE_VISIBILITY = '@@redux-devtools/dock/TOGGLE_VISIBILITY';
export function toggleVisibility() {
return { type: TOGGLE_VISIBILITY };
}
export const CHANGE_POSITION = '@@redux-devtools/dock/CHANGE_POSITION';
export function changePosition() {
return { type: CHANGE_POSITION };
}
export const CHANGE_SIZE = '@@redux-devtools/dock/CHANGE_SIZE';
export function changeSize(size) {
return { type: CHANGE_SIZE, size: size };
}

View File

@ -0,0 +1 @@
export const POSITIONS = ['left', 'top', 'right', 'bottom'];

View File

@ -0,0 +1,34 @@
import { CHANGE_POSITION, CHANGE_SIZE, TOGGLE_VISIBILITY } from './actions';
import { POSITIONS } from './constants';
function position(state = props.defaultPosition, action, props) {
return (action.type === CHANGE_POSITION) ?
POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] :
state;
}
function size(state = props.defaultSize, action, props) {
return (action.type === CHANGE_SIZE) ?
action.size :
state;
}
function isVisible(state = props.defaultIsVisible, action, props) {
return (action.type === TOGGLE_VISIBILITY) ?
!state :
state;
}
function childMonitorState(state, action, props) {
const child = props.children;
return child.type.reducer(state, action, child.props);
}
export default function reducer(state = {}, action, props) {
return {
position: position(state.position, action, props),
isVisible: isVisible(state.isVisible, action, props),
size: size(state.size, action, props),
childMonitorState: childMonitorState(state.childMonitorState, action, props)
};
}

View File

@ -1,20 +0,0 @@
import { bindActionCreators } from 'redux';
export default function bindActionCreatorsDeep(actionCreators, dispatch) {
return Object.keys(actionCreators).reduce((result, key) => {
if (!actionCreators[key]) {
return result;
}
switch (typeof actionCreators[key]) {
case 'object':
result[key] = bindActionCreatorsDeep(actionCreators[key], dispatch);
break;
case 'function':
result[key] = bindActionCreators(actionCreators[key], dispatch);
break;
default:
break;
}
return result;
}, {});
}

View File

@ -1,39 +1,30 @@
import React, { Children, Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import bindActionCreatorsDeep from './bindActionCreatorsDeep';
import instrument, { ActionCreators as historyActionCreators } from './instrument';
import instrument from './instrument';
export default function createDevTools(children) {
const child = Children.only(children);
const { type: Monitor } = child;
const { reducer, actionCreators } = Monitor.setup(child.props);
const monitorElement = Children.only(children);
const monitorProps = monitorElement.props;
const Monitor = monitorElement.type;
const enhancer = instrument((state, action) =>
Monitor.reducer(state, action, monitorProps)
);
function mapStateToProps(state) {
return {
historyState: state.historyState,
...state.historyState,
monitorState: state.monitorState
};
}
function mapDispatchToProps(dispatch) {
return {
historyActions: bindActionCreators(historyActionCreators, dispatch),
monitorActions: bindActionCreatorsDeep(actionCreators, dispatch)
};
}
const ConnectedMonitor = connect(
mapStateToProps,
mapDispatchToProps
)(Monitor);
const ConnectedMonitor = connect(mapStateToProps)(Monitor);
return class DevTools extends Component {
static contextTypes = {
store: PropTypes.object.isRequired
};
static instrument = () => instrument(reducer);
static instrument = () => enhancer;
constructor(props, context) {
super(props, context);
@ -42,7 +33,7 @@ export default function createDevTools(children) {
render() {
return (
<ConnectedMonitor {...child.props}
<ConnectedMonitor {...monitorProps}
store={this.instrumentedStore} />
);
}

View File

@ -1,3 +1,3 @@
export { default as instrument, ActionTypes } from './instrument';
export { default as instrument, ActionCreators, ActionTypes } from './instrument';
export { default as persistState } from './persistState';
export { default as createDevTools } from './createDevTools';