11import React , { Component , PropTypes } from 'react' ;
2+ import getParams from 'get-params' ;
23import * as themes from 'redux-devtools-themes' ;
34
45const 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' } } > ×</ 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