chore(package): update react-virtualized (#2556)

`form/select` has been redeveloped for the occasion with the following features:
- not clearable when required and not multiple
- when no options available, marked as loading
- when required and only one option, it is automatically selected
This commit is contained in:
Julien Fontanet 2018-01-24 14:51:12 +01:00 committed by GitHub
parent 23e992de85
commit dc12896b19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 290 additions and 406 deletions

View File

@ -132,7 +132,7 @@
"react-shortcuts": "^2.0.0", "react-shortcuts": "^2.0.0",
"react-sparklines": "1.6.0", "react-sparklines": "1.6.0",
"react-test-renderer": "^15.6.2", "react-test-renderer": "^15.6.2",
"react-virtualized": "^8.0.8", "react-virtualized": "^9.15.0",
"readable-stream": "^2.3.3", "readable-stream": "^2.3.3",
"redux": "^3.7.2", "redux": "^3.7.2",
"redux-thunk": "^2.0.1", "redux-thunk": "^2.0.1",

View File

@ -10,7 +10,7 @@ import getEventValue from './get-event-value'
const VERBOSE = false const VERBOSE = false
const get = (object, path, depth) => { const get = (object, path, depth) => {
if (depth >= path.length) { if (object == null || depth >= path.length) {
return object return object
} }

View File

@ -17,7 +17,6 @@ import propTypes from '../prop-types-decorator'
import { formatSizeRaw, parseSize } from '../utils' import { formatSizeRaw, parseSize } from '../utils'
export Select from './select' export Select from './select'
export SelectPlainObject from './select-plain-object'
// =================================================================== // ===================================================================

View File

@ -1,118 +0,0 @@
import uncontrollableInput from 'uncontrollable-input'
import Component from 'base-component'
import find from 'lodash/find'
import map from 'lodash/map'
import React from 'react'
import propTypes from '../prop-types-decorator'
import Select from './select'
@propTypes({
autoFocus: propTypes.bool,
disabled: propTypes.bool,
optionRenderer: propTypes.func,
multi: propTypes.bool,
onChange: propTypes.func,
options: propTypes.array,
placeholder: propTypes.node,
predicate: propTypes.func,
required: propTypes.bool,
value: propTypes.any,
})
@uncontrollableInput()
export default class SelectPlainObject extends Component {
componentDidMount () {
const { options, value } = this.props
this.setState({
options: this._computeOptions(options),
value: this._computeValue(value, this.props),
})
}
componentWillReceiveProps (newProps) {
if (newProps !== this.props) {
this.setState({
options: this._computeOptions(newProps.options),
value: this._computeValue(newProps.value, newProps),
})
}
}
_computeValue (value, props = this.props) {
let { optionKey } = props
optionKey || (optionKey = 'id')
const reduceValue = value =>
value != null ? value[optionKey] || value : ''
if (props.multi) {
if (!Array.isArray(value)) {
value = [value]
}
return map(value, reduceValue)
}
return reduceValue(value)
}
_computeOptions (options) {
const { optionKey = 'id' } = this.props
const { optionRenderer = o => o.label || o[optionKey] || o } = this.props
return map(options, option => ({
value: option[optionKey] || option,
label: optionRenderer(option),
}))
}
_getObject (value) {
if (value == null) {
return undefined
}
const { optionKey = 'id', options } = this.props
const pickValue = value => {
value = value.value || value
return find(
options,
option => option[optionKey] === value || option === value
)
}
if (this.props.multi) {
return map(value, pickValue)
}
return pickValue(value)
}
_handleChange = value => {
const { onChange } = this.props
if (onChange) {
onChange(this._getObject(value))
}
}
_renderOption = option => option.label
render () {
const { props, state } = this
return (
<Select
autoFocus={props.autoFocus}
disabled={props.disabled}
multi={props.multi}
onChange={this._handleChange}
openOnFocus
optionRenderer={this._renderOption}
options={state.options}
placeholder={props.placeholder}
required={props.required}
value={state.value}
valueRenderer={this._renderOption}
/>
)
}
}

View File

@ -1,138 +1,160 @@
import map from 'lodash/map' import classNames from 'classnames'
import React, { Component } from 'react' import isEmpty from 'lodash/isEmpty'
import PropTypes from 'prop-types'
import React from 'react'
import ReactSelect from 'react-select' import ReactSelect from 'react-select'
import sum from 'lodash/sum' import uncontrollableInput from 'uncontrollable-input'
import { AutoSizer, CellMeasurer, List } from 'react-virtualized' import {
AutoSizer,
import propTypes from '../prop-types-decorator' CellMeasurer,
CellMeasurerCache,
const SELECT_MENU_STYLE = { List,
overflow: 'hidden', } from 'react-virtualized'
}
const SELECT_STYLE = { const SELECT_STYLE = {
minWidth: '10em', minWidth: '10em',
} }
const MENU_STYLE = {
const LIST_STYLE = { overflow: 'hidden',
whiteSpace: 'normal',
} }
const MAX_OPTIONS = 5 @uncontrollableInput()
export default class Select extends React.PureComponent {
// See: https://github.com/bvaughn/react-virtualized-select/blob/master/source/VirtualizedSelect/VirtualizedSelect.js
@propTypes({
maxHeight: propTypes.number,
})
export default class Select extends Component {
static defaultProps = { static defaultProps = {
maxHeight: 200, maxHeight: 200,
optionRenderer: (option, labelKey) => option[labelKey],
multi: ReactSelect.defaultProps.multi,
options: [],
required: ReactSelect.defaultProps.required,
valueKey: ReactSelect.defaultProps.valueKey,
} }
_renderMenu = ({ focusedOption, options, ...otherOptions }) => { static propTypes = {
const { maxHeight } = this.props autoSelectSingleOption: PropTypes.bool, // default to props.required
maxHeight: PropTypes.number,
options: PropTypes.array.isRequired, // cannot be an object
}
const focusedOptionIndex = options.indexOf(focusedOption) _cellMeasurerCache = new CellMeasurerCache({
let height = options.length > MAX_OPTIONS && maxHeight fixedWidth: true,
const wrappedRowRenderer = ({ index, key, style }) =>
this._optionRenderer({
...otherOptions,
focusedOption,
focusedOptionIndex,
key,
option: options[index],
options,
style,
}) })
// https://github.com/JedWatson/react-select/blob/dd32c27d7ea338a93159da5e40bc06697d0d86f9/src/utils/defaultMenuRenderer.js#L4
_renderMenu (opts) {
const { focusOption, options, selectValue } = opts
const focusFromEvent = event =>
focusOption(options[event.currentTarget.dataset.index])
const selectFromEvent = event =>
selectValue(options[event.currentTarget.dataset.index])
const renderRow = opts2 =>
this._renderRow(opts, opts2, focusFromEvent, selectFromEvent)
let focusedOptionIndex = options.indexOf(opts.focusedOption)
if (focusedOptionIndex === -1) {
focusedOptionIndex = undefined
}
const { length } = options
const { maxHeight } = this.props
const { rowHeight } = this._cellMeasurerCache
let height = 0
for (let i = 0; i < length; ++i) {
height += rowHeight({ index: i })
if (height > maxHeight) {
height = maxHeight
break
}
}
return ( return (
<AutoSizer disableHeight> <AutoSizer disableHeight>
{({ width }) => {({ width }) => (
width ? (
<CellMeasurer
cellRenderer={({ rowIndex }) =>
wrappedRowRenderer({ index: rowIndex })
}
columnCount={1}
rowCount={options.length}
// FIXME: 16 px: ugly workaround to take into account the scrollbar
// during the offscreen render to measure the row height
// See https://github.com/bvaughn/react-virtualized/issues/401
width={width - 16}
>
{({ getRowHeight }) => {
if (options.length <= MAX_OPTIONS) {
height = sum(
map(options, (_, index) => getRowHeight({ index }))
)
}
return (
<List <List
deferredMeasurementCache={this._cellMeasurerCache}
height={height} height={height}
rowCount={options.length} rowCount={length}
rowHeight={getRowHeight} rowHeight={rowHeight}
rowRenderer={wrappedRowRenderer} rowRenderer={renderRow}
scrollToIndex={focusedOptionIndex} scrollToIndex={focusedOptionIndex}
style={LIST_STYLE}
width={width} width={width}
/> />
) )}
}}
</CellMeasurer>
) : null
}
</AutoSizer> </AutoSizer>
) )
} }
_renderMenu = this._renderMenu.bind(this)
_optionRenderer = ({ _renderRow (
{
focusedOption, focusedOption,
focusOption, focusOption,
key, inputValue,
labelKey, optionClassName,
option, optionRenderer,
style, options,
selectValue, selectValue,
}) => { },
let className = 'Select-option' { index, key, parent, style },
focusFromEvent,
if (option === focusedOption) { selectFromEvent
className += ' is-focused' ) {
} const option = options[index]
const { disabled } = option const { disabled } = option
if (disabled) {
className += ' is-disabled'
}
const { props } = this
return ( return (
<div <CellMeasurer
className={className} cache={this._cellMeasurerCache}
onClick={disabled ? undefined : () => selectValue(option)} columnIndex={0}
onMouseOver={disabled ? undefined : () => focusOption(option)}
style={style}
key={key} key={key}
parent={parent}
rowIndex={index}
> >
{props.optionRenderer(option, labelKey)} <div
className={classNames('Select-option', optionClassName, {
'is-disabled': disabled,
'is-focused': option === focusedOption,
})}
data-index={index}
onClick={disabled ? undefined : selectFromEvent}
onMouseEnter={disabled ? undefined : focusFromEvent}
style={style}
title={option.title}
>
{optionRenderer(option, index, inputValue)}
</div> </div>
</CellMeasurer>
) )
} }
componentDidMount () {
this.componentDidUpdate()
}
componentDidUpdate () {
const { props } = this
const { autoSelectSingleOption = props.required, options } = props
if (autoSelectSingleOption && options != null && options.length === 1) {
const value = options[0][props.valueKey]
props.onChange(props.multi ? [value] : value)
}
}
render () { render () {
const { props } = this
const { multi } = props
return ( return (
<ReactSelect <ReactSelect
closeOnSelect={!this.props.multi}
{...this.props}
backspaceToRemoveMessage='' backspaceToRemoveMessage=''
menuRenderer={this._renderMenu} clearable={multi || !props.required}
menuStyle={SELECT_MENU_STYLE} closeOnSelect={!multi}
isLoading={!props.disabled && isEmpty(props.options)}
style={SELECT_STYLE} style={SELECT_STYLE}
valueRenderer={props.optionRenderer}
{...props}
menuRenderer={this._renderMenu}
menuStyle={MENU_STYLE}
/> />
) )
} }

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import classNames from 'classnames' import PropTypes from 'prop-types'
import { parse as parseRemote } from 'xo-remote-parser' import { parse as parseRemote } from 'xo-remote-parser'
import { import {
assign, assign,
@ -23,14 +23,12 @@ import {
import _ from './intl' import _ from './intl'
import Button from './button' import Button from './button'
import Component from './base-component'
import Icon from './icon' import Icon from './icon'
import propTypes from './prop-types-decorator'
import renderXoItem from './render-xo-item' import renderXoItem from './render-xo-item'
import Select from './form/select'
import store from './store' import store from './store'
import Tooltip from './tooltip' import Tooltip from './tooltip'
import uncontrollableInput from 'uncontrollable-input' import uncontrollableInput from 'uncontrollable-input'
import { Select } from './form'
import { import {
createCollectionWrapper, createCollectionWrapper,
createFilter, createFilter,
@ -112,23 +110,18 @@ const options = props => ({
* ] * ]
* } * }
*/ */
@propTypes({ class GenericSelect extends React.Component {
autoFocus: propTypes.bool, static propTypes = {
clearable: propTypes.bool, hasSelectAll: PropTypes.bool,
disabled: propTypes.bool, multi: PropTypes.bool,
hasSelectAll: propTypes.bool, onChange: PropTypes.func.isRequired,
multi: propTypes.bool, xoContainers: PropTypes.array,
onChange: propTypes.func, xoObjects: PropTypes.oneOfType([
placeholder: propTypes.any.isRequired, PropTypes.array,
required: propTypes.bool, PropTypes.objectOf(PropTypes.array),
value: propTypes.any,
xoContainers: propTypes.array,
xoObjects: propTypes.oneOfType([
propTypes.array,
propTypes.objectOf(propTypes.array),
]).isRequired, ]).isRequired,
}) }
export class GenericSelect extends Component {
_getObjectsById = createSelector( _getObjectsById = createSelector(
() => this.props.xoObjects, () => this.props.xoObjects,
objects => objects =>
@ -142,8 +135,8 @@ export class GenericSelect extends Component {
// createCollectionWrapper with a depth? // createCollectionWrapper with a depth?
const { name } = this.constructor const { name } = this.constructor
let options = [] let options
if (!containers) { if (containers === undefined) {
if (__DEV__ && !isArray(objects)) { if (__DEV__ && !isArray(objects)) {
throw new Error( throw new Error(
`${name}: without xoContainers, xoObjects must be an array` `${name}: without xoContainers, xoObjects must be an array`
@ -151,12 +144,14 @@ export class GenericSelect extends Component {
} }
options = map(objects, getOption) options = map(objects, getOption)
} else if (__DEV__ && isArray(objects)) { } else {
if (__DEV__ && isArray(objects)) {
throw new Error( throw new Error(
`${name}: with xoContainers, xoObjects must be an object` `${name}: with xoContainers, xoObjects must be an object`
) )
} }
options = []
forEach(containers, container => { forEach(containers, container => {
options.push({ options.push({
disabled: true, disabled: true,
@ -167,11 +162,11 @@ export class GenericSelect extends Component {
options.push(getOption(object, container)) options.push(getOption(object, container))
}) })
}) })
}
const values = this._getSelectValue()
const objectsById = this._getObjectsById() const objectsById = this._getObjectsById()
const addIfMissing = val => { const addIfMissing = val => {
if (val && !objectsById[val]) { if (val != null && !(val in objectsById)) {
options.push({ options.push({
disabled: true, disabled: true,
id: val, id: val,
@ -185,6 +180,7 @@ export class GenericSelect extends Component {
} }
} }
const values = this._getSelectedIds()
if (isArray(values)) { if (isArray(values)) {
forEach(values, addIfMissing) forEach(values, addIfMissing)
} else { } else {
@ -195,27 +191,25 @@ export class GenericSelect extends Component {
} }
) )
_getSelectValue = createSelector( _getSelectedIds = createSelector(
() => this.props.value, () => this.props.value,
createCollectionWrapper(getIds) createCollectionWrapper(getIds)
) )
_getNewSelectedObjects = createSelector( _getSelectedObjects = (() => {
const helper = createSelector(
this._getObjectsById, this._getObjectsById,
value => value, value => value,
(objectsById, value) => (objectsById, value) =>
value == null isArray(value)
? value
: isArray(value)
? map(value, value => objectsById[value.value]) ? map(value, value => objectsById[value.value])
: objectsById[value.value] : objectsById[value.value]
) )
return value => (value == null ? value : helper(value))
})()
_onChange = value => { _onChange = value => {
const { onChange } = this.props this.props.onChange(this._getSelectedObjects(value))
if (onChange) {
onChange(this._getNewSelectedObjects(value))
}
} }
_selectAll = () => { _selectAll = () => {
@ -225,46 +219,31 @@ export class GenericSelect extends Component {
// GroupBy: Display option with margin if not disabled and containers exists. // GroupBy: Display option with margin if not disabled and containers exists.
_renderOption = option => ( _renderOption = option => (
<span <span
className={classNames( className={
!option.disabled && this.props.xoContainers && 'ml-1' !option.disabled && this.props.xoContainers !== undefined
)} ? 'ml-1'
: undefined
}
> >
{renderXoItem(option.xoItem)} {renderXoItem(option.xoItem)}
</span> </span>
) )
render () { render () {
const { const { hasSelectAll, xoContainers, xoObjects, ...props } = this.props
autoFocus,
disabled,
hasSelectAll,
multi,
placeholder,
required,
clearable = Boolean(multi || !required),
} = this.props
const select = ( const select = (
<Select <Select
{...{ {...props}
autoFocus,
clearable,
disabled,
multi,
placeholder,
required,
}}
onChange={this._onChange} onChange={this._onChange}
openOnFocus openOnFocus
optionRenderer={this._renderOption} optionRenderer={this._renderOption}
options={this._getOptions()} options={this._getOptions()}
value={this._getSelectValue()} value={this._getSelectedIds()}
valueRenderer={this._renderOption}
/> />
) )
if (!multi || !hasSelectAll) { if (!props.multi || !hasSelectAll) {
return select return select
} }
@ -295,16 +274,15 @@ const makeStoreSelect = (createSelectors, defaultProps) =>
const makeSubscriptionSelect = (subscribe, props) => const makeSubscriptionSelect = (subscribe, props) =>
uncontrollableInput(options)( uncontrollableInput(options)(
class extends Component { class extends React.PureComponent {
constructor (props) { state = {}
super(props)
this._getFilteredXoContainers = createFilter( _getFilteredXoContainers = createFilter(
() => this.state.xoContainers, () => this.state.xoContainers,
() => this.props.containerPredicate () => this.props.containerPredicate
) )
this._getFilteredXoObjects = createSelector( _getFilteredXoObjects = createSelector(
() => this.state.xoObjects, () => this.state.xoObjects,
() => this.state.xoContainers && this._getFilteredXoContainers(), () => this.state.xoContainers && this._getFilteredXoContainers(),
() => this.props.predicate, () => this.props.predicate,
@ -324,7 +302,6 @@ const makeSubscriptionSelect = (subscribe, props) =>
} }
} }
) )
}
componentWillMount () { componentWillMount () {
this.componentWillUnmount = subscribe(::this.setState) this.componentWillUnmount = subscribe(::this.setState)
@ -577,10 +554,7 @@ export const SelectHighLevelObject = makeStoreSelect(
// =================================================================== // ===================================================================
export const SelectVdi = propTypes({ export const SelectVdi = makeStoreSelect(
srPredicate: propTypes.func,
})(
makeStoreSelect(
() => { () => {
const getSrs = createGetObjectsOfType('SR').filter( const getSrs = createGetObjectsOfType('SR').filter(
(_, props) => props.srPredicate (_, props) => props.srPredicate
@ -605,8 +579,10 @@ export const SelectVdi = propTypes({
} }
}, },
{ placeholder: _('selectVdis') } { placeholder: _('selectVdis') }
)
) )
SelectVdi.propTypes = {
srPredicate: PropTypes.func,
}
// =================================================================== // ===================================================================
@ -747,7 +723,7 @@ export const SelectResourceSet = makeSubscriptionSelect(
// =================================================================== // ===================================================================
export class SelectResourceSetsVmTemplate extends Component { export class SelectResourceSetsVmTemplate extends React.PureComponent {
get value () { get value () {
return this.refs.select.value return this.refs.select.value
} }
@ -782,7 +758,7 @@ export class SelectResourceSetsVmTemplate extends Component {
// =================================================================== // ===================================================================
export class SelectResourceSetsSr extends Component { export class SelectResourceSetsSr extends React.PureComponent {
get value () { get value () {
return this.refs.select.value return this.refs.select.value
} }
@ -813,7 +789,7 @@ export class SelectResourceSetsSr extends Component {
// =================================================================== // ===================================================================
export class SelectResourceSetsVdi extends Component { export class SelectResourceSetsVdi extends React.PureComponent {
get value () { get value () {
return this.refs.select.value return this.refs.select.value
} }
@ -853,7 +829,7 @@ export class SelectResourceSetsVdi extends Component {
// =================================================================== // ===================================================================
export class SelectResourceSetsNetwork extends Component { export class SelectResourceSetsNetwork extends React.PureComponent {
get value () { get value () {
return this.refs.select.value return this.refs.select.value
} }
@ -894,12 +870,13 @@ export class SelectResourceSetsNetwork extends Component {
ipPools: subscribeIpPools, ipPools: subscribeIpPools,
resourceSets: subscribeResourceSets, resourceSets: subscribeResourceSets,
})) }))
@propTypes({ export class SelectResourceSetIp extends React.Component {
containerPredicate: propTypes.func, static propTypes = {
predicate: propTypes.func, containerPredicate: PropTypes.func,
resourceSetId: propTypes.string.isRequired, predicate: PropTypes.func,
}) resourceSetId: PropTypes.string.isRequired,
export class SelectResourceSetIp extends Component { }
get value () { get value () {
return this.refs.select.value return this.refs.select.value
} }
@ -958,7 +935,7 @@ export class SelectResourceSetIp extends Component {
// =================================================================== // ===================================================================
export class SelectSshKey extends Component { export class SelectSshKey extends React.PureComponent {
get value () { get value () {
return this.refs.select.value return this.refs.select.value
} }

View File

@ -5,12 +5,12 @@ import endsWith from 'lodash/endsWith'
import Icon from 'icon' import Icon from 'icon'
import React from 'react' import React from 'react'
import replace from 'lodash/replace' import replace from 'lodash/replace'
import Select from 'form/select'
import Tooltip from 'tooltip' import Tooltip from 'tooltip'
import { Container, Col, Row } from 'grid' import { Container, Col, Row } from 'grid'
import { createSelector } from 'reselect' import { createSelector } from 'reselect'
import { formatSize } from 'utils' import { formatSize } from 'utils'
import { FormattedDate } from 'react-intl' import { FormattedDate } from 'react-intl'
import { SelectPlainObject } from 'form'
import { filter, includes, isEmpty, map } from 'lodash' import { filter, includes, isEmpty, map } from 'lodash'
import { scanDisk, scanFiles } from 'xo' import { scanDisk, scanFiles } from 'xo'
@ -251,23 +251,25 @@ export default class RestoreFileModalBody extends Component {
return ( return (
<div> <div>
<SelectPlainObject <Select
labelKey='name'
onChange={this._onBackupChange} onChange={this._onBackupChange}
optionKey='id'
optionRenderer={backupOptionRenderer} optionRenderer={backupOptionRenderer}
options={backups} options={backups}
placeholder={_('restoreFilesSelectBackup')} placeholder={_('restoreFilesSelectBackup')}
value={backup} value={backup}
valueKey='id'
/> />
{backup && [ {backup && [
<br />, <br />,
<SelectPlainObject <Select
labelKey='name'
onChange={this._onDiskChange} onChange={this._onDiskChange}
optionKey='id'
optionRenderer={diskOptionRenderer} optionRenderer={diskOptionRenderer}
options={backup.disks} options={backup.disks}
placeholder={_('restoreFilesSelectDisk')} placeholder={_('restoreFilesSelectDisk')}
value={disk} value={disk}
valueKey='id'
/>, />,
]} ]}
{scanDiskError && ( {scanDiskError && (
@ -279,13 +281,14 @@ export default class RestoreFileModalBody extends Component {
!scanDiskError && !scanDiskError &&
!noPartitions && [ !noPartitions && [
<br />, <br />,
<SelectPlainObject <Select
labelKey='name'
onChange={this._onPartitionChange} onChange={this._onPartitionChange}
optionKey='id'
optionRenderer={partitionOptionRenderer} optionRenderer={partitionOptionRenderer}
options={partitions} options={partitions}
placeholder={_('restoreFilesSelectPartition')} placeholder={_('restoreFilesSelectPartition')}
value={partition} value={partition}
valueKey='id'
/>, />,
]} ]}
{(partition || (disk && !scanDiskError && noPartitions)) && [ {(partition || (disk && !scanDiskError && noPartitions)) && [
@ -311,13 +314,14 @@ export default class RestoreFileModalBody extends Component {
</Col> </Col>
</Row> </Row>
</Container>, </Container>,
<SelectPlainObject <Select
labelKey='name'
onChange={this._onFileChange} onChange={this._onFileChange}
optionKey='id'
optionRenderer={fileOptionRenderer} optionRenderer={fileOptionRenderer}
options={this._getSelectableFiles()} options={this._getSelectableFiles()}
placeholder={_('restoreFilesSelectFiles')} placeholder={_('restoreFilesSelectFiles')}
value={null} value={null}
valueKey='id'
/>, />,
<br />, <br />,
<div> <div>

View File

@ -22,7 +22,7 @@ import { addSubscriptions, noop } from 'utils'
import { Container, Row, Col } from 'grid' import { Container, Row, Col } from 'grid'
import { FormattedDate, injectIntl } from 'react-intl' import { FormattedDate, injectIntl } from 'react-intl'
import { info, error } from 'notification' import { info, error } from 'notification'
import { SelectPlainObject, Toggle } from 'form' import { Select, Toggle } from 'form'
import { import {
importBackup, importBackup,
@ -210,14 +210,15 @@ class _ModalBody extends Component {
return ( return (
<div> <div>
<SelectPlainObject <Select
labelKey='name'
onChange={this.linkState('backup')} onChange={this.linkState('backup')}
optionKey='path'
optionRenderer={backupOptionRenderer} optionRenderer={backupOptionRenderer}
options={props.backups} options={props.backups}
placeholder={props.intl.formatMessage( placeholder={props.intl.formatMessage(
messages.importBackupModalSelectBackup messages.importBackupModalSelectBackup
)} )}
valueKey='path'
/> />
<br /> <br />
<ChooseSrForEachVdisModal <ChooseSrForEachVdisModal

View File

@ -3,6 +3,7 @@ import ActionButton from 'action-button'
import ActionRowButton from 'action-row-button' import ActionRowButton from 'action-row-button'
import Button from 'button' import Button from 'button'
import Component from 'base-component' import Component from 'base-component'
import defined from 'xo-defined'
import delay from 'lodash/delay' import delay from 'lodash/delay'
import find from 'lodash/find' import find from 'lodash/find'
import forEach from 'lodash/forEach' import forEach from 'lodash/forEach'
@ -13,6 +14,7 @@ import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map' import map from 'lodash/map'
import mapValues from 'lodash/mapValues' import mapValues from 'lodash/mapValues'
import React from 'react' import React from 'react'
import Select from 'form/select'
import size from 'lodash/size' import size from 'lodash/size'
import Tooltip from 'tooltip' import Tooltip from 'tooltip'
import Upgrade from 'xoa-upgrade' import Upgrade from 'xoa-upgrade'
@ -21,7 +23,6 @@ import { createSelector } from 'selectors'
import { error } from 'notification' import { error } from 'notification'
import { generateUiSchema } from 'xo-json-schema-input' import { generateUiSchema } from 'xo-json-schema-input'
import { injectIntl } from 'react-intl' import { injectIntl } from 'react-intl'
import { SelectPlainObject } from 'form'
import { SelectSubject } from 'select-objects' import { SelectSubject } from 'select-objects'
import { import {
@ -119,17 +120,7 @@ export default class Jobs extends Component {
}) })
} }
componentWillReceiveProps (props) {
const { currentUser } = props
const { owner } = this.state
if (currentUser && !owner) {
this.setState({ owner: currentUser.id })
}
}
componentWillMount () { componentWillMount () {
this.setState({ owner: this.props.user && this.props.user.id })
this.componentWillUnmount = subscribeJobs(jobs => { this.componentWillUnmount = subscribeJobs(jobs => {
const j = {} const j = {}
for (const id in jobs) { for (const id in jobs) {
@ -279,7 +270,7 @@ export default class Jobs extends Component {
params.value params.value
), ),
}, },
userId: owner, userId: owner !== undefined ? owner : this.props.currentUser.id,
timeout: timeout ? timeout * 1e3 : undefined, timeout: timeout ? timeout * 1e3 : undefined,
} }
@ -372,8 +363,8 @@ export default class Jobs extends Component {
type === 'user' && permission === 'admin' type === 'user' && permission === 'admin'
render () { render () {
const { state } = this const { props, state } = this
const { action, actions, job, jobs, owner } = state const { action, actions, job, jobs } = state
const { formatMessage } = this.props.intl const { formatMessage } = this.props.intl
const isJobUserMissing = this._getIsJobUserMissing() const isJobUserMissing = this._getIsJobUserMissing()
@ -387,7 +378,7 @@ export default class Jobs extends Component {
placeholder={_('jobOwnerPlaceholder')} placeholder={_('jobOwnerPlaceholder')}
predicate={this._subjectPredicate} predicate={this._subjectPredicate}
required required
value={owner} value={defined(state.owner, () => props.currentUser.id)}
/> />
<input <input
type='text' type='text'
@ -397,12 +388,13 @@ export default class Jobs extends Component {
pattern='[^_]+' pattern='[^_]+'
required required
/> />
<SelectPlainObject <Select
ref='method' labelKey='method'
options={actions}
optionKey='method'
onChange={this._handleSelectMethod} onChange={this._handleSelectMethod}
options={actions}
placeholder={_('jobActionPlaceHolder')} placeholder={_('jobActionPlaceHolder')}
ref='method'
valueKey='method'
/> />
<input <input
type='number' type='number'

View File

@ -10,7 +10,7 @@ import React, { Component } from 'react'
import Scheduler, { SchedulePreview } from 'scheduling' import Scheduler, { SchedulePreview } from 'scheduling'
import { error } from 'notification' import { error } from 'notification'
import { injectIntl } from 'react-intl' import { injectIntl } from 'react-intl'
import { SelectPlainObject, Toggle } from 'form' import { Select, Toggle } from 'form'
import { import {
createSchedule, createSchedule,
deleteSchedule, deleteSchedule,
@ -223,10 +223,11 @@ export default class Schedules extends Component {
/> />
</div> </div>
<div className='form-group'> <div className='form-group'>
<SelectPlainObject <Select
labelKey='name'
ref='job' ref='job'
options={map(jobs)} options={map(jobs)}
optionKey='id' valueKey='id'
placeholder={this.props.intl.formatMessage( placeholder={this.props.intl.formatMessage(
messages.jobScheduleJobPlaceHolder messages.jobScheduleJobPlaceHolder
)} )}

View File

@ -381,6 +381,7 @@ export class Edit extends Component {
</Col> </Col>
<Col mediumSize={4}> <Col mediumSize={4}>
<SelectSubject <SelectSubject
autoSelectSingleOption={false}
hasSelectAll hasSelectAll
multi multi
onChange={this.linkState('subjects')} onChange={this.linkState('subjects')}
@ -390,6 +391,7 @@ export class Edit extends Component {
</Col> </Col>
<Col mediumSize={4}> <Col mediumSize={4}>
<SelectPool <SelectPool
autoSelectSingleOption={false}
hasSelectAll hasSelectAll
multi multi
onChange={this._updateSelectedPools} onChange={this._updateSelectedPools}
@ -414,6 +416,7 @@ export class Edit extends Component {
<Row> <Row>
<Col mediumSize={4}> <Col mediumSize={4}>
<SelectVmTemplate <SelectVmTemplate
autoSelectSingleOption={false}
disabled={!state.nPools} disabled={!state.nPools}
hasSelectAll hasSelectAll
multi multi
@ -425,6 +428,7 @@ export class Edit extends Component {
</Col> </Col>
<Col mediumSize={4}> <Col mediumSize={4}>
<SelectSr <SelectSr
autoSelectSingleOption={false}
disabled={!state.nPools} disabled={!state.nPools}
hasSelectAll hasSelectAll
multi multi
@ -436,6 +440,7 @@ export class Edit extends Component {
</Col> </Col>
<Col mediumSize={4}> <Col mediumSize={4}>
<SelectNetwork <SelectNetwork
autoSelectSingleOption={false}
disabled={!state.nSrs} disabled={!state.nSrs}
hasSelectAll hasSelectAll
multi multi
@ -535,8 +540,8 @@ export class Edit extends Component {
<Col mediumSize={7}> <Col mediumSize={7}>
<SelectIpPool <SelectIpPool
onChange={this._onChangeIpPool} onChange={this._onChangeIpPool}
value=''
predicate={this._getIpPoolPredicate()} predicate={this._getIpPoolPredicate()}
value={null}
/> />
</Col> </Col>
</Row> </Row>

View File

@ -166,7 +166,7 @@ export default class Acls extends Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.state = { this.state = {
action: '', action: null,
objects: [], objects: [],
subjects: [], subjects: [],
typeFilters: {}, typeFilters: {},

View File

@ -1270,7 +1270,7 @@ babel-runtime@^5.8.25:
dependencies: dependencies:
core-js "^1.0.0" core-js "^1.0.0"
babel-runtime@^6.11.6, babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
version "6.26.0" version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies: dependencies:
@ -7292,14 +7292,15 @@ react-transition-group@^2.2.0:
prop-types "^15.5.8" prop-types "^15.5.8"
warning "^3.0.0" warning "^3.0.0"
react-virtualized@^8.0.8: react-virtualized@^9.15.0:
version "8.11.4" version "9.17.3"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-8.11.4.tgz#0bb94f1ecbd286d07145ce63983d0a11724522c0" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.17.3.tgz#3854588f067235c00ae5cd134d96508956630033"
dependencies: dependencies:
babel-runtime "^6.11.6" babel-runtime "^6.26.0"
classnames "^2.2.3" classnames "^2.2.3"
dom-helpers "^2.4.0 || ^3.0.0" dom-helpers "^2.4.0 || ^3.0.0"
loose-envify "^1.3.0" loose-envify "^1.3.0"
prop-types "^15.5.4"
react@^15.4.1: react@^15.4.1:
version "15.6.2" version "15.6.2"