feat(ActionButton): better feedback on async actions

This commit is contained in:
Julien Fontanet
2016-05-06 17:18:41 +02:00
parent add65e41da
commit 7e5e463ef2
4 changed files with 158 additions and 87 deletions

View File

@@ -1,45 +1,28 @@
import _ from 'messages'
import Icon from 'icon'
import ActionButton from 'action-button'
import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import {
Button,
DropdownButton,
ButtonGroup,
ButtonToolbar,
MenuItem
ButtonToolbar
} from 'react-bootstrap-4/lib'
const ActionBar = ({ actions, display = 'text' }) => (
const ActionBar = ({ actions }) => (
<ButtonToolbar>
{map(actions, (action, index) =>
!action.dropdownItems
? <Tooltip content={_(action.label)}><Button bsStyle='secondary' key={index} onClick={() => action.handler()} style={{width: '2.3em', fontSize: '1.8em', borderRadius: '15px'}}>
<Content key={index} display={display} label={action.label} icon={action.icon} />
</Button></Tooltip>
: <ButtonGroup bsStyle='secondary' pullRight key={index}>
<DropdownButton
key={index}
id={`dropdown-${index}`}
pullRight
bsStyle='secondary'
onClick={() => action.handler()}
noCaret
style={{width: '2.3em', fontSize: '1.8em', borderRadius: '15px'}}
title={<Content display={display} label={action.label} icon={action.icon} />}
>
<MenuItem key={index} eventKey={index} onClick={action.handler}>
<Content key={index} display='both' label={action.label} icon={action.icon} main />
</MenuItem>
{map(action.dropdownItems, ({label, icon, handler}, index) => (
<MenuItem key={index} eventKey={index} onClick={handler}>
<Content key={index} display='both' label={label} icon={icon} />
</MenuItem>
))}
</DropdownButton>
</ButtonGroup>
)}
{map(actions, ({ handler, label, icon }, index) => (
<Tooltip key={index} content={_(label)}>
<ActionButton
btnStyle='secondary'
handler={handler}
icon={icon}
style={{
borderRadius: '15px',
fontSize: '1.8em',
width: '2.3em'
}}
/>
</Tooltip>
))}
</ButtonToolbar>
)
ActionBar.propTypes = {
@@ -47,18 +30,9 @@ ActionBar.propTypes = {
React.PropTypes.shape({
label: React.PropTypes.string.isRequired,
icon: React.PropTypes.string.isRequired,
handler: React.PropTypes.func,
dropdownItems: React.PropTypes.array
handler: React.PropTypes.func
})
).isRequired,
display: React.PropTypes.oneOf(['icon', 'text', 'both'])
}
export default ActionBar
const Content = ({display, icon, label, main}) => (
<span style={{fontWeight: main && 'bold'}}>
{icon && (display === 'icon' || display === 'both') ? <Icon icon={icon} /> : null}
{display === 'both' && <span>&nbsp;&nbsp;</span>}
{label && (display === 'text' || display === 'both') ? _(label) : null}
</span>
)
export { ActionBar as default }

View File

@@ -0,0 +1,71 @@
import Icon from 'icon'
import React, { Component } from 'react'
import { Button } from 'react-bootstrap-4/lib'
import { autobind, propTypes } from 'utils'
@propTypes({
btnStyle: propTypes.string,
handler: propTypes.func.isRequired,
icon: propTypes.string.isRequired,
size: propTypes.oneOf([
'large',
'small'
])
})
export default class ActionButton extends Component {
constructor () {
super()
this.state = {}
}
@autobind
async _execute () {
if (this.state.working) {
return
}
const { handler } = this.props
try {
this.setState({
error: null,
working: true
})
await handler()
this.setState({
working: false
})
} catch (error) {
this.setState({
error,
working: false
})
}
}
render () {
const {
props: {
btnStyle,
children,
className,
icon,
size: bsSize,
style
},
state: { error, working }
} = this
return <Button
bsStyle={error ? 'warning' : btnStyle}
disabled={working}
onClick={this._execute}
{...{ bsSize, className, style }}
>
<Icon icon={working ? 'loading' : icon} />
{children && ' '}
{children}
</Button>
}
}

View File

@@ -775,11 +775,6 @@ $select-input-height: 40px; // Bootstrap input height
// CONTENT TAB STYLE ===========================================================
.btn-tab {
margin-bottom: 1em;
margin-left: 1em;
}
.btn-huge {
font-size: 4em;
}

View File

@@ -1,11 +1,35 @@
import _ from 'messages'
import Icon from 'icon'
import ActionButton from 'action-button'
import CopyToClipboard from 'react-copy-to-clipboard'
import isEmpty from 'lodash/isEmpty'
import React from 'react'
import { convertVm, deleteVm, restartVm, suspendVm, stopVm, recoveryStartVm, cloneVm } from 'xo'
import { Row, Col } from 'grid'
import { normalizeXenToolsStatus, osFamily } from 'utils'
import {
cloneVm,
convertVm,
deleteVm,
recoveryStartVm,
restartVm,
stopVm,
suspendVm
} from 'xo'
const TabButton = ({
btnStyle,
handler,
icon,
labelId
}) => (
<ActionButton
size='large'
style={{
marginBottom: '1em',
marginLeft: '1em'
}}
{...{ btnStyle, handler, icon }}
>{_(labelId)}</ActionButton>
)
export default ({
vm
@@ -14,49 +38,56 @@ export default ({
<Col smallSize={12} className='text-xs-right'>
{vm.power_state === 'Running'
? <span>
<button className='btn btn-lg btn-primary btn-tab' onClick={() => {
suspendVm(vm)
}}>
<Icon icon='vm-suspend' size={1} /> {_('suspendVmLabel')}
</button>
<button className='btn btn-lg btn-warning btn-tab' onClick={() => {
restartVm(vm, true)
}}>
<Icon icon='vm-force-reboot' size={1} /> {_('forceRebootVmLabel')}
</button>
<button className='btn btn-lg btn-warning btn-tab' onClick={() => {
stopVm(vm, true)
}}>
<Icon icon='vm-force-shutdown' size={1} /> {_('forceShutdownVmLabel')}
</button>
<TabButton
btnStyle='primary'
handler={() => suspendVm(vm)}
icon='vm-suspend'
labelId='suspendVmLabel'
/>
<TabButton
btnStyle='warning'
handler={() => restartVm(vm, true)}
icon='vm-force-reboot'
labelId='forceRebootVmLabel'
/>
<TabButton
btnStyle='warning'
handler={() => stopVm(vm, true)}
icon='vm-force-shutdown'
labelId='forceShutdownVmLabel'
/>
</span>
: null
}
{vm.power_state === 'Halted'
? <span>
<button className='btn btn-lg btn-primary btn-tab' onClick={() => {
recoveryStartVm(vm)
}}>
<Icon icon='vm-recovery-mode' size={1} /> {_('recoveryModeLabel')}
</button>
<button className='btn btn-lg btn-primary btn-tab' onClick={() => {
cloneVm(vm, true)
}}>
<Icon icon='vm-clone' size={1} /> {_('cloneVmLabel')}
</button>
<button className='btn btn-lg btn-danger btn-tab' onClick={() => {
convertVm(vm)
}}>
<Icon icon='vm-create-template' size={1} /> {_('vmConvertButton')}
</button>
<TabButton
btnStyle='primary'
handler={() => recoveryStartVm(vm)}
icon='vm-recovery-mode'
labelId='recoveryModeLabel'
/>
<TabButton
btnStyle='primary'
handler={() => cloneVm(vm, true)}
icon='vm-clone'
labelId='cloneVmLabel'
/>
<TabButton
btnStyle='danger'
handler={() => convertVm(vm)}
icon='vm-create-template'
labelId='vmConvertButton'
/>
</span>
: null
}
<button className='btn btn-lg btn-danger btn-tab' onClick={() => {
deleteVm(vm)
}}>
<Icon icon='vm-delete' size={1} /> {_('vmRemoveButton')}
</button>
<TabButton
btnStyle='danger'
handler={() => deleteVm(vm)}
icon='vm-delete'
labelId='vmRemoveButton'
/>
</Col>
<Col smallSize={12}>
<h3>{_('xenSettingsLabel')}</h3>