Skip to content

Commit 546116f

Browse files
committed
Add action creators selector and forms, close #2
1 parent 7f4d585 commit 546116f

File tree

3 files changed

+160
-11
lines changed

3 files changed

+160
-11
lines changed

examples/counter/src/containers/DevTools.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import DockMonitor from 'redux-devtools-dock-monitor';
55
import MultipleMonitors from 'redux-devtools-dispatch/MultipleMonitors'; // use redux-devtools-dispatch/lib/MultipleMonitors with the package
66
import Dispatcher from 'redux-devtools-dispatch';
77

8+
import * as actionCreators from '../actions/CounterActions';
9+
810
export default createDevTools(
911
<DockMonitor toggleVisibilityKey='ctrl-h'
1012
changePositionKey='ctrl-q'>
1113
<MultipleMonitors>
1214
<LogMonitor />
13-
<Dispatcher initEmpty />
15+
<Dispatcher actionCreators={actionCreators} initEmpty />
1416
</MultipleMonitors>
1517
</DockMonitor>
1618
);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"redux-devtools": "^3.0.0"
4141
},
4242
"dependencies": {
43+
"get-params": "^0.1.2",
4344
"redux-devtools-themes": "^1.0.0"
4445
}
4546
}

src/Dispatcher.js

Lines changed: 156 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { Component, PropTypes } from 'react';
2+
import getParams from 'get-params';
23
import * as themes from 'redux-devtools-themes';
34

45
const styles = {
@@ -11,16 +12,23 @@ const styles = {
1112
fontSize: '0.8em',
1213
textDecoration: 'none',
1314
border: 'none',
14-
position: 'absolute',
15-
bottom: '3px',
16-
right: '5px'
1715
},
1816
content: {
1917
margin: '5px',
2018
padding: '5px',
2119
borderRadius: '3px',
2220
outline: 'none',
23-
overflow: 'auto'
21+
flex: '1 1 80%',
22+
overflow: 'auto',
23+
},
24+
label: {
25+
margin: '5px',
26+
padding: '5px',
27+
flex: '1 1 20%',
28+
overflow: 'hidden',
29+
textOverflow: 'ellipsis',
30+
direction: 'rtl',
31+
textAlign: 'left',
2432
},
2533
};
2634

@@ -40,33 +48,122 @@ export default class Dispatcher extends Component {
4048
}),
4149

4250
initEmpty: PropTypes.bool,
51+
actionCreators: PropTypes.oneOfType([
52+
PropTypes.object,
53+
PropTypes.array,
54+
]),
4355
theme: PropTypes.oneOfType([
4456
PropTypes.object,
4557
PropTypes.string,
4658
]),
4759
};
4860

4961
static defaultProps = {
50-
select: (state) => state,
5162
theme: 'nicinabox',
52-
preserveScrollTop: true,
5363
initEmpty: false,
64+
actionCreators: {},
5465
};
5566

5667
static update = () => {};
5768

5869
constructor(props, context) {
5970
super(props, context);
71+
this.state = {
72+
selectedActionCreator: 'default',
73+
args: [],
74+
error: null,
75+
};
76+
}
77+
78+
selectActionCreator(e) {
79+
const selectedActionCreator = e.target.value;
80+
let args = [];
81+
if(selectedActionCreator !== 'default') {
82+
// Shrink the number args to the number of the new ones
83+
args = this.state.args.slice(0, this.getActionCreators()[selectedActionCreator].args.length);
84+
}
85+
this.setState({
86+
selectedActionCreator,
87+
args,
88+
});
89+
}
90+
91+
handleArg(e, argIndex) {
92+
const args = [
93+
...this.state.args.slice(0, argIndex),
94+
this.refs['arg' + argIndex].textContent,
95+
...this.state.args.slice(argIndex + 1),
96+
];
97+
this.setState({args});
6098
}
6199

62100
launchAction() {
63-
this.context.store.dispatch(JSON.parse(this.refs.action.textContent.replace(/\s+/g, ' ')));
101+
try {
102+
let actionCreator = () => ({}), argsToInject = [];
103+
if(this.state.selectedActionCreator !== 'default') {
104+
actionCreator = this.getSelectedActionCreator().func;
105+
argsToInject = this.state.args.map(
106+
(arg) => (new Function('return ' + arg))()
107+
);
108+
} else {
109+
actionCreator = new Function('return ' + this.refs.action.textContent);
110+
}
111+
112+
this.context.store.dispatch(actionCreator(...argsToInject));
113+
114+
this.setState({error: null});
115+
} catch(e) {
116+
this.setState({error: e.message});
117+
}
64118
}
65119

66120
componentDidMount() {
121+
this.resetCustomAction();
122+
}
123+
124+
componentDidUpdate(prevProps, prevState) {
125+
if(this.state.selectedActionCreator === 'default' && prevState.selectedActionCreator !== 'default') {
126+
this.resetCustomAction();
127+
}
128+
}
129+
130+
resetCustomAction() {
67131
this.refs.action.innerHTML = this.props.initEmpty ? '<br/>' : '{<br/>"type": ""<br/>}';
68132
}
69133

134+
getSelectedActionCreator() {
135+
return this.getActionCreators()[this.state.selectedActionCreator];
136+
}
137+
138+
getActionCreators() {
139+
const { actionCreators } = this.props;
140+
141+
if(Array.isArray(actionCreators)) {
142+
return actionCreators;
143+
}
144+
145+
const flatTree = function(object, namespace = '') {
146+
let functions = [];
147+
for(let propertyName in object) {
148+
const prop = object[propertyName];
149+
if(object.hasOwnProperty(propertyName)) {
150+
if(typeof prop === "function") {
151+
functions.push({
152+
name: namespace + (prop.name || 'anonymous'),
153+
func: prop,
154+
args: getParams(prop),
155+
});
156+
} else if(typeof prop === "object") {
157+
functions = functions.concat(flatTree(prop, namespace + propertyName + '.'));
158+
}
159+
}
160+
}
161+
return functions;
162+
};
163+
164+
return flatTree(actionCreators);
165+
}
166+
70167
getTheme() {
71168
let { theme } = this.props;
72169
if (typeof theme !== 'string') {
@@ -82,12 +179,61 @@ export default class Dispatcher extends Component {
82179
}
83180

84181
render() {
85-
const theme = this.getTheme();
182+
const theme = this.getTheme(),
183+
contentEditableStyle = {...styles.content, color: theme.base06, backgroundColor: theme.base00},
184+
buttonStyle = {...styles.button, color: theme.base06, backgroundColor: theme.base00},
185+
actionCreators = this.getActionCreators();
186+
187+
let fields = <div contentEditable style={contentEditableStyle} ref='action'></div>;
188+
if(this.state.selectedActionCreator !== 'default') {
189+
fields = this.getSelectedActionCreator().args.map((param, i) => (
190+
<div key={i} style={{display: 'flex'}}>
191+
<span style={{...styles.label, color: theme.base06}}>{param}</span>
192+
<div contentEditable style={contentEditableStyle} ref={'arg' + i} onInput={(e) => this.handleArg(e, i)}></div>
193+
</div>
194+
));
195+
}
196+
197+
let error = '';
198+
if(this.state.error) {
199+
error = (
200+
<div style={{color: theme.base06, background: '#FC2424', padding: '5px', display: 'flex'}}>
201+
<div style={{flex: '1', alignItems: 'center'}}><p style={{margin: '0px'}}>{this.state.error}</p></div>
202+
<div style={{alignItems: 'center'}}>
203+
<button onClick={() => this.setState({error: null})} style={{...buttonStyle, margin: '0'}}>&times;</button>
204+
</div>
205+
</div>
206+
);
207+
}
208+
209+
let dispatchButtonStyle = buttonStyle;
210+
if(actionCreators.length <= 0) {
211+
dispatchButtonStyle = {
212+
...buttonStyle,
213+
position: 'absolute',
214+
bottom: '3px',
215+
right: '5px',
216+
background: theme.base02,
217+
};
218+
}
219+
220+
const dispatchButton = <button style={dispatchButtonStyle} onClick={this.launchAction.bind(this)}>Dispatch</button>;
86221

87222
return (
88223
<div style={{background: theme.base02, fontFamily: 'monaco,Consolas,Lucida Console,monospace'}}>
89-
<div contentEditable='true' style={{...styles.content, color: theme.base06, backgroundColor: theme.base00}} ref='action'></div>
90-
<button style={{...styles.button, color: theme.base06, backgroundColor: theme.base02}} onClick={this.launchAction.bind(this)}>Dispatch</button>
224+
{error}
225+
{fields}
226+
{actionCreators.length > 0 ? <div style={{display: 'flex'}}>
227+
<select onChange={this.selectActionCreator.bind(this)} style={{flex: '1', fontFamily: 'inherit'}} defaultValue={this.state.selectedActionCreator || 'default'}>
228+
<option value="default">Custom action</option>
229+
{actionCreators.map(({ name, func, args }, i) => (
230+
<option key={i} value={i}>
231+
{name + '(' + args.join(', ') + ')'}
232+
</option>
233+
))}
234+
</select>
235+
{dispatchButton}
236+
</div> : dispatchButton}
91237
</div>
92238
);
93239
}

0 commit comments

Comments
 (0)