Compare commits
1 Commits
vhd_hashes
...
fbeauchamp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32478e470b |
@@ -55,6 +55,7 @@
|
||||
"gulp-uglify": "^1.5.1",
|
||||
"gulp-watch": "^4.3.5",
|
||||
"history": "^2.0.0-rc2",
|
||||
"jsonrpc-websocket-client": "0.0.1-4",
|
||||
"mocha": "^2.3.4",
|
||||
"must": "^0.13.1",
|
||||
"nice-pipe": "^0.3.4",
|
||||
@@ -64,6 +65,8 @@
|
||||
"react-intl": "^1.2.2",
|
||||
"react-redux": "^4.0.6",
|
||||
"react-router": "^2.0.0-rc5",
|
||||
"react-router-redux": "^2.1.0",
|
||||
"readable-stream": "^2.0.5",
|
||||
"redux": "^3.0.5",
|
||||
"redux-devtools": "^3.0.1",
|
||||
"redux-router": "^1.0.0-beta7",
|
||||
@@ -72,6 +75,7 @@
|
||||
"source-map-support": "^0.4.0",
|
||||
"standard": "^5.4.1",
|
||||
"trace": "^2.0.2",
|
||||
"vinyl": "^1.1.1",
|
||||
"watchify": "^3.7.0",
|
||||
"xo-lib": "^0.8.0-1"
|
||||
},
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
import store from './store'
|
||||
import XoApp from './xo-app'
|
||||
|
||||
render(
|
||||
<Provider store={ store }>
|
||||
<XoApp />
|
||||
</Provider>,
|
||||
<XoApp/>,
|
||||
document.getElementsByTagName('xo-app')[0]
|
||||
)
|
||||
|
||||
@@ -1,46 +1,89 @@
|
||||
import Xo from 'xo-lib'
|
||||
import { createBackoff } from 'jsonrpc-websocket-client'
|
||||
/*import Xo from 'xo-lib'
|
||||
import { createBackoff } from 'jsonrpc-websocket-client'*/
|
||||
|
||||
// ===================================================================
|
||||
/* action type */
|
||||
export const VM_CREATE = 'VM_CREATE' // create a VM in store
|
||||
export const VM_EDIT = 'VM_EDIT' // edit a vm in store
|
||||
export const VM_SAVE = 'VM_SAVE' // want to save a vm from store to server
|
||||
export const VM_SAVED = 'VM_SAVED' // VM is saved on server
|
||||
|
||||
const createAction = (() => {
|
||||
const { defineProperty } = Object
|
||||
const identity = payload => payload
|
||||
export const SIGN_IN = 'SIGN_IN' // ask to signin
|
||||
export const SIGNED_IN = 'SIGNED_IN' // is signed in
|
||||
export const SIGN_OUT = 'SIGN_OUT' // want to sign out
|
||||
export const SIGNED_OUT = 'SIGNED_OUT' // signed out
|
||||
export const SESSION_PATCH = 'SESSION_PATCH' // signed out
|
||||
|
||||
return (type, payloadCreator = identity) => defineProperty(
|
||||
(...args) => ({
|
||||
type,
|
||||
payload: payloadCreator(...args)
|
||||
}),
|
||||
'toString',
|
||||
{ value: () => type }
|
||||
)
|
||||
})()
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const xo = new Xo({
|
||||
url: 'localhost:9000'
|
||||
})
|
||||
{
|
||||
const connect = () => {
|
||||
xo.open(createBackoff()).catch(error => {
|
||||
console.error('failed to connect to xo-server', error)
|
||||
})
|
||||
/* action creator
|
||||
* they HAVE TO return an action with the mandatory field type, and an optiona payload
|
||||
* they MAY dispatch ( emit ) other actions, async or not
|
||||
* action will be used by reducers
|
||||
*/
|
||||
export function patchSession (patch) {
|
||||
return {
|
||||
type: SESSION_PATCH,
|
||||
payload: patch
|
||||
}
|
||||
xo.on('scheduledAttempt', ({ delay }) => {
|
||||
console.log('next attempt in %s ms', delay)
|
||||
})
|
||||
}
|
||||
export function signIn () {
|
||||
// using redux thunk https://github.com/gaearon/redux-thunk
|
||||
// instead of returning one promise, it can dispatch multiple events
|
||||
// that way it become trivial to inform user of progress
|
||||
console.log(' will signin')
|
||||
return dispatch => {
|
||||
setTimeout(() => {
|
||||
// Yay! Can invoke sync or async actions with `dispatch`
|
||||
dispatch(signedIn({userId: Math.floor(Math.random() * 1000)}))
|
||||
}, 2500)
|
||||
|
||||
xo.on('closed', connect)
|
||||
connect()
|
||||
dispatch({type: 'SIGN_IN'}) // immediatly inform the sore that we'll try to signin
|
||||
}
|
||||
}
|
||||
|
||||
export const updateStatus = createAction('UPDATE_STATUS')
|
||||
export const signIn = createAction('SIGN_IN', async credentials => {
|
||||
await xo.signIn(credentials)
|
||||
})
|
||||
export const signOut = createAction('SIGN_OUT')
|
||||
// you can also directly return an action
|
||||
export function signedIn (payload) {
|
||||
return {
|
||||
type: 'SIGNED_IN',
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
export const increment = createAction('INCREMENT')
|
||||
export const decrement = createAction('DECREMENT')
|
||||
export function VMCreate (payload) {
|
||||
return {
|
||||
type: VM_CREATE,
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
export function VMEdit( payload){
|
||||
// should check if there s a vm id ?
|
||||
return {
|
||||
type: VM_EDIT,
|
||||
payload
|
||||
}
|
||||
}
|
||||
|
||||
export function VMSave (vmId) {
|
||||
//should call xoApi and save to server
|
||||
return dispatch => {
|
||||
setTimeout(function(){
|
||||
console.log('really save')
|
||||
// Yay! Can invoke sync or async actions with `dispatch`
|
||||
dispatch(VMSaved({id: Math.floor(Math.random() * 1000)}))
|
||||
}, 1000)
|
||||
|
||||
dispatch({
|
||||
type: VM_SAVE,
|
||||
payload: {id: vmId}
|
||||
}) // immediatly inform the sore that we'll try to save
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function VMSaved (vm) {
|
||||
//xoAPi is happy, let's tell everyone this vm is save
|
||||
return {
|
||||
type: VM_SAVED,
|
||||
payload: vm
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import reduxPromise from 'redux-promise'
|
||||
// import reduxThunk from 'redux-thunk'
|
||||
import { createHashHistory } from 'history'
|
||||
|
||||
import {
|
||||
applyMiddleware,
|
||||
combineReducers,
|
||||
compose,
|
||||
createStore
|
||||
} from 'redux'
|
||||
import {
|
||||
reduxReactRouter,
|
||||
routerStateReducer
|
||||
} from 'redux-router'
|
||||
|
||||
import * as reducers from './reducers'
|
||||
import reducer from './reducers'
|
||||
import thunk from 'redux-thunk'
|
||||
import initialState from './initialStoreState'
|
||||
|
||||
|
||||
let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
|
||||
|
||||
|
||||
const store = createStoreWithMiddleware(reducer)
|
||||
|
||||
export default store
|
||||
|
||||
// ===================================================================
|
||||
|
||||
/*
|
||||
export default compose(
|
||||
applyMiddleware(reduxPromise),
|
||||
// applyMiddleware(reduxThunk),
|
||||
@@ -28,3 +32,4 @@ export default compose(
|
||||
}))
|
||||
|
||||
export * as actions from './actions'
|
||||
*/
|
||||
|
||||
11
src/store/initialStoreState.js
Normal file
11
src/store/initialStoreState.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export default {
|
||||
xoApi: {
|
||||
}, // somehting like xoApi.all
|
||||
components: {}, // UI state for each widget /components
|
||||
session: {
|
||||
isLoggingIn: false,
|
||||
isLoggingOut: false,
|
||||
isLoggued: false
|
||||
} // session specific information
|
||||
}
|
||||
@@ -1,58 +1,110 @@
|
||||
import * as actions from './actions'
|
||||
import { combineReducers } from 'redux'
|
||||
|
||||
// ===================================================================
|
||||
import initialState from './initialStoreState'
|
||||
/* Reducers are synchronous and repeatable : it's not the place to put api call
|
||||
*/
|
||||
|
||||
// import { combineReducers } from 'redux'
|
||||
function sessionReducer (currentSession = initialState.session, action) {
|
||||
switch (action.type) {
|
||||
case actions.SESSION_PATCH :
|
||||
let payload = action.payload
|
||||
// don't change loggued state from this action
|
||||
delete payload.isLoggingIn
|
||||
delete payload.isLoggingOut
|
||||
delete payload.isLoggued
|
||||
return Object.assign(
|
||||
{},
|
||||
currentSession,
|
||||
payload // login , password
|
||||
)
|
||||
case actions.SIGN_IN :
|
||||
// user tried to sign in
|
||||
return Object.assign(
|
||||
{},
|
||||
currentSession,
|
||||
action.payload, // login , password
|
||||
{isLoggingIn: true}
|
||||
)
|
||||
|
||||
const createAsyncHandler = ({ error, next }) => (state, action) => {
|
||||
if (action.error) {
|
||||
if (error) {
|
||||
return error(state, action.payload)
|
||||
}
|
||||
} else {
|
||||
if (next) {
|
||||
return next(state, action.payload)
|
||||
}
|
||||
case actions.SIGN_OUT :
|
||||
return Object.assign({}, currentSession, {isLoggingOut: true})
|
||||
|
||||
case actions.SIGNED_OUT:
|
||||
// when user is signed out : reset session
|
||||
return initialState.session
|
||||
|
||||
case actions.SIGNED_IN :
|
||||
// when user is signed in : session informations are received from server
|
||||
return Object.assign(
|
||||
{},
|
||||
currentSession,
|
||||
action.payload,
|
||||
{
|
||||
isLoggued: true,
|
||||
isLoggingIn: false,
|
||||
isLoggingOut: false,
|
||||
password: 'redacted , don t need it '
|
||||
}
|
||||
)
|
||||
|
||||
default :
|
||||
return currentSession // reducer HAVE TO return a state, even when they did nothing
|
||||
}
|
||||
}
|
||||
|
||||
function xoApiReducer (state = initialState.xoApi, action) {
|
||||
let payload = action.payload || {}
|
||||
switch (action.type){
|
||||
case actions.VM_EDIT:
|
||||
payload = Object.assign({},
|
||||
state[action.payload.id],
|
||||
payload,
|
||||
{isSaved: false}
|
||||
)
|
||||
return Object.assign(
|
||||
{},
|
||||
state,
|
||||
{
|
||||
[action.payload.id]: action.payload
|
||||
}
|
||||
)
|
||||
|
||||
case actions.VM_SAVE:
|
||||
console.log(' SAVE ',action.payload)
|
||||
return Object.assign(
|
||||
{},
|
||||
state,
|
||||
{
|
||||
[action.payload.id]: {isSaving:true}
|
||||
}
|
||||
)
|
||||
return state
|
||||
case actions.VM_SAVED:
|
||||
// put the VM at its real id if it's a creation
|
||||
payload = Object.assign({},
|
||||
state[action.payload.id],
|
||||
payload,
|
||||
{isSaved: true, isSaving: false}
|
||||
)
|
||||
console.log('VM_SAVED',payload)
|
||||
return Object.assign(
|
||||
{},
|
||||
state,
|
||||
{
|
||||
[action.payload.id]: payload
|
||||
}
|
||||
)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// Action handlers are reducers but bound to a specific action.
|
||||
const combineActionHandlers = (initialState, handlers) => {
|
||||
for (const action in handlers) {
|
||||
const handler = handlers[action]
|
||||
if (typeof handler === 'object') {
|
||||
handlers[action] = createAsyncHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
return (state = initialState, action) => {
|
||||
const handler = handlers[action.type]
|
||||
|
||||
return handler
|
||||
? handler(state, action)
|
||||
: state
|
||||
}
|
||||
function componentsReducer (state = initialState.components, action) {
|
||||
return state
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const counter = combineActionHandlers(0, {
|
||||
[actions.decrement]: counter => counter - 1,
|
||||
[actions.increment]: counter => counter + 1
|
||||
})
|
||||
|
||||
export const user = combineActionHandlers(null, {
|
||||
[actions.signIn]: {
|
||||
next: user => {
|
||||
console.log(String(actions.signIn), user)
|
||||
return user
|
||||
}
|
||||
},
|
||||
[actions.signOut]: () => null
|
||||
})
|
||||
|
||||
export const status = combineActionHandlers('disconnected', {
|
||||
[actions.updateStatus]: status => status
|
||||
export default combineReducers({
|
||||
session: sessionReducer,
|
||||
components: componentsReducer,
|
||||
xoApi: xoApiReducer
|
||||
})
|
||||
|
||||
@@ -1,54 +1,109 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link, Route, IndexLink, IndexRoute } from 'react-router'
|
||||
import { ReduxRouter } from 'redux-router'
|
||||
import { connect, Provider } from 'react-redux'
|
||||
import store from '../store'
|
||||
import LoginForm from './login/loginForm.js'
|
||||
import VMForm from './vm/form.js'
|
||||
import { Router, Route, Link } from 'react-router'
|
||||
// from https://github.com/rackt/react-router/blob/master/upgrade-guides/v2.0.0.md#no-default-history
|
||||
import { hashHistory } from 'react-router'
|
||||
|
||||
import About from './about'
|
||||
import Home from './home'
|
||||
import { actions } from '../store'
|
||||
|
||||
let XoApp = class extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
counter: PropTypes.number
|
||||
};
|
||||
const Dashboard = React.createClass({
|
||||
render() {
|
||||
return <div>Welcome to the app!</div>
|
||||
}
|
||||
})
|
||||
|
||||
render () {
|
||||
const App = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<ul>
|
||||
<li><Link to='/about'>About</Link></li>
|
||||
<li><IndexLink to='/'>Home</IndexLink></li>
|
||||
<li><button onClick={ () => this.props.signIn({
|
||||
email: 'admin@admin.net',
|
||||
password: 'admin'
|
||||
}) }>Sign in</button></li>
|
||||
<li><button onClick={ () => this.props.signOut() }>Sign out</button></li>
|
||||
<li><button onClick={ this.props.increment }>Increment</button></li>
|
||||
<li><button onClick={ this.props.decrement }>Decrement</button></li>
|
||||
<li><Link to="/vm/create">createvm</Link></li>
|
||||
<li><Link to="/inbox">Inbox</Link></li>
|
||||
</ul>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
<p>{ this.props.user }</p>
|
||||
<p>{ this.props.counter }</p>
|
||||
|
||||
{ this.props.children }
|
||||
|
||||
const Inbox = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Inbox</h2>
|
||||
{this.props.children || "Welcome to your Inbox"}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const Message = React.createClass({
|
||||
render() {
|
||||
return <h3>Message {this.props.params.id}</h3>
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
class XoAppUnconnected extends Component {
|
||||
render () {
|
||||
const {isLoggued, login, password, userId} = this.props
|
||||
return (
|
||||
<div>
|
||||
<h2> Xen Orchestra {login} {isLoggued ? 'connecté' : ' non connecté'}</h2>
|
||||
{!isLoggued &&
|
||||
<LoginForm />
|
||||
}
|
||||
{isLoggued &&
|
||||
<Router history={hashHistory}>
|
||||
<Route path="/" component={App}>
|
||||
<Route path="vm/:vmId" component={ VMForm } />
|
||||
<Route path="inbox" component={Inbox}>
|
||||
<Route path="messages/:id" component={Message} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
const XoApp = connect(state => state.session)(XoAppUnconnected)
|
||||
|
||||
/*
|
||||
* Provider allow the compontn directly bellow XoApp to have acces to the store
|
||||
* and to the dispatch method
|
||||
*/
|
||||
export default () =>
|
||||
<Provider store={ store }>
|
||||
<XoApp/>
|
||||
</Provider>
|
||||
/*
|
||||
|
||||
import About from './about'
|
||||
import Home from './home'
|
||||
import CreateVm from './create-vm'
|
||||
//import CreatVM from './create-vm'
|
||||
|
||||
class XoApp extends Component {
|
||||
static propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
render () {
|
||||
|
||||
const pick = propNames => object => {
|
||||
const props = {}
|
||||
for (const name of propNames) {
|
||||
props[name] = object[name]
|
||||
}
|
||||
return props
|
||||
|
||||
_do (action) {
|
||||
return () => this.props.dispatch(action)
|
||||
}
|
||||
}
|
||||
|
||||
XoApp = connect(pick([
|
||||
'counter',
|
||||
'user'
|
||||
]), actions)(XoApp)
|
||||
XoApp = connect(state => state)(XoApp)
|
||||
|
||||
export default () => <div>
|
||||
<h1>Xen Orchestra</h1>
|
||||
@@ -57,6 +112,7 @@ export default () => <div>
|
||||
<Route path='/' component={ XoApp }>
|
||||
<IndexRoute component={ Home } />
|
||||
<Route path='/about' component={ About } />
|
||||
<Route path='/create-vm' component={ CreateVm } />
|
||||
</Route>
|
||||
</ReduxRouter>
|
||||
</div>
|
||||
</div>*/
|
||||
|
||||
61
src/xo-app/login/loginForm.js
Normal file
61
src/xo-app/login/loginForm.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
import {signIn, signedIn, patchSession } from '../../store/actions'
|
||||
|
||||
class LoginForm extends Component {
|
||||
|
||||
handleSigninIn (e) {
|
||||
const { actions } = this.props
|
||||
e.preventDefault()
|
||||
actions.signIn()
|
||||
}
|
||||
|
||||
updateLogin (event) {
|
||||
this.props.actions.patchSession({login: event.target.value})
|
||||
}
|
||||
|
||||
updatePassword (event) {
|
||||
this.props.actions.patchSession({password: event.target.value})
|
||||
}
|
||||
|
||||
render () {
|
||||
/*remeber : this.propos.session is from the connect call*/
|
||||
const { login, password, isLoggingIn } = this.props.session
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={(e) => this.handleSigninIn(e)}>
|
||||
<input type='text' name='login' value={login} onChange={(e) => this.updateLogin(e)/* autobinding is only in ES5*/}/>
|
||||
<input type='password' name='password' value={password} onChange={(e) => this.updatePassword(e)}/>
|
||||
{isLoggingIn &&
|
||||
<p>signing in , waiting for server</p>
|
||||
}
|
||||
{!isLoggingIn &&
|
||||
<input type='submit' value='Sign in'/>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/* Which part of the global app state this component can see ?
|
||||
* make it as small as possible to reduce the rerender */
|
||||
export default connect(
|
||||
(state) => {
|
||||
return {session: state.session}
|
||||
},
|
||||
/* Transmit action and actions creators
|
||||
* It can be usefull to transmit only a few selected actions.
|
||||
* Also bind them to dispatch , so the component can call action creator directly,
|
||||
* without having to manually wrap each
|
||||
*/
|
||||
(dispatch) => {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
signIn, patchSession, signedIn
|
||||
},
|
||||
dispatch) }
|
||||
}
|
||||
)(LoginForm)
|
||||
100
src/xo-app/vm/form.js
Normal file
100
src/xo-app/vm/form.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { VMEdit, VMSave } from '../../store/actions'
|
||||
|
||||
// should move to glbal style
|
||||
const fieldsetStyle = {
|
||||
'display': 'flex',
|
||||
'flexDirection': 'row'
|
||||
}
|
||||
|
||||
const inputContainerStyle = {
|
||||
'flex': 1
|
||||
}
|
||||
|
||||
const legendStyle = {
|
||||
'width': '150px'
|
||||
}
|
||||
|
||||
/* I don't use fieldset / legend here since they don't
|
||||
* really like being styled with flex
|
||||
*/
|
||||
|
||||
class VmForm extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
vmName: '',
|
||||
vmTemplateId: 2
|
||||
}
|
||||
}
|
||||
saveAuthorized () {
|
||||
return !!this.state.vmName && !!this.state.vmTemplateId
|
||||
}
|
||||
save (e){
|
||||
e.preventDefault()
|
||||
this.props.actions.VMSave(this.props.routeParams.vmId)
|
||||
}
|
||||
patch (field,value){
|
||||
this.props.actions.VMEdit({
|
||||
id:this.props.routeParams.vmId,
|
||||
[field]:value
|
||||
})
|
||||
}
|
||||
render () {
|
||||
const s = this.state
|
||||
var saveable = this.saveAuthorized()
|
||||
const {name,templateId, isSaving, isSaved } = this.props.vm || {}
|
||||
return (
|
||||
<form onSubmit={(e)=>this.save(e)}>
|
||||
<h2>Vm name : {name}</h2>
|
||||
<div style={fieldsetStyle}>
|
||||
<div style={legendStyle}>
|
||||
|
||||
</div>
|
||||
<div style={inputContainerStyle}>
|
||||
<label htmlFor='vm-edit-name'>Name </label>
|
||||
<input id ='vm-edit-name' type='text' value={name} onChange={(e)=>this.patch("name",e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
{!isSaving && !isSaved &&
|
||||
<button type='submit'>Save</button>
|
||||
}
|
||||
{isSaving &&
|
||||
<p>Saving to server</p>
|
||||
}
|
||||
{!isSaving && isSaved &&
|
||||
<p>Not modified</p>
|
||||
}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Which part of the global app state this component can see ?
|
||||
* make it as small as possible to reduce the rerender */
|
||||
//ownprop is the prop given to the component
|
||||
function mapStateToProps(state, ownProps) {
|
||||
console.log(ownProps.routeParams.vmId);
|
||||
return {
|
||||
vm :state.xoApi[ownProps.routeParams.vmId]
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
/* Transmit action and actions creators
|
||||
* It can be usefull to transmit only a few selected actions.
|
||||
* Also bind them to dispatch , so the component can call action creator directly,
|
||||
* without having to manually wrap each
|
||||
*/
|
||||
(dispatch) => {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
VMEdit, VMSave
|
||||
},
|
||||
dispatch) }
|
||||
}
|
||||
)(VmForm)
|
||||
Reference in New Issue
Block a user