parent
af4904ce8d
commit
9f29a047a7
@ -1,7 +1,7 @@
|
||||
import * as CM from 'complex-matcher'
|
||||
import _ from 'intl'
|
||||
import classNames from 'classnames'
|
||||
import defined, { get } from '@xen-orchestra/defined'
|
||||
import defined from '@xen-orchestra/defined'
|
||||
import DropdownMenu from 'react-bootstrap-4/lib/DropdownMenu' // https://phabricator.babeljs.io/T6662 so Dropdown.Menu won't work like https://react-bootstrap.github.io/components.html#btn-dropdowns-custom
|
||||
import DropdownToggle from 'react-bootstrap-4/lib/DropdownToggle' // https://phabricator.babeljs.io/T6662 so Dropdown.Toggle won't work https://react-bootstrap.github.io/components.html#btn-dropdowns-custom
|
||||
import PropTypes from 'prop-types'
|
||||
@ -9,10 +9,10 @@ import React from 'react'
|
||||
import Shortcuts from 'shortcuts'
|
||||
import { Input as DebouncedInput } from 'debounce-input-decorator'
|
||||
import { Portal } from 'react-overlays'
|
||||
import { routerShape } from 'react-router/lib/PropTypes'
|
||||
import { Set } from 'immutable'
|
||||
import { Dropdown, MenuItem } from 'react-bootstrap-4/lib'
|
||||
import { injectState, provideState } from 'reaclette'
|
||||
import { withRouter } from 'react-router'
|
||||
import {
|
||||
ceil,
|
||||
filter,
|
||||
@ -242,9 +242,9 @@ const Action = decorate([
|
||||
|
||||
const LEVELS = [undefined, 'primary', 'warning', 'danger']
|
||||
// page number and sort info are optional for backward compatibility
|
||||
const URL_STATE_RE = /^(?:(\d+)(?:_(\d+)(_desc)?)?-)?(.*)$/
|
||||
const URL_STATE_RE = /^(?:(\d+)(?:_(\d+)(?:_(desc|asc))?)?-)?(.*)$/
|
||||
|
||||
export default class SortedTable extends Component {
|
||||
class SortedTable extends Component {
|
||||
static propTypes = {
|
||||
defaultColumn: PropTypes.number,
|
||||
defaultFilter: PropTypes.string,
|
||||
@ -301,7 +301,7 @@ export default class SortedTable extends Component {
|
||||
// DOM node selector like body or .my-class
|
||||
// The shortcuts will be enabled when the node is focused
|
||||
shortcutsTarget: PropTypes.string,
|
||||
stateUrlParam: PropTypes.string,
|
||||
stateUrlParam: PropTypes.string.isRequired,
|
||||
|
||||
// @deprecated, use `data-${key}` instead
|
||||
userData: PropTypes.any,
|
||||
@ -311,10 +311,6 @@ export default class SortedTable extends Component {
|
||||
itemsPerPage: 10,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
router: routerShape,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
@ -332,50 +328,12 @@ export default class SortedTable extends Component {
|
||||
return isEmpty(userData) ? undefined : userData
|
||||
})
|
||||
|
||||
let selectedColumn = props.defaultColumn
|
||||
if (selectedColumn == null) {
|
||||
selectedColumn = findIndex(props.columns, 'default')
|
||||
|
||||
if (selectedColumn === -1) {
|
||||
selectedColumn = 0
|
||||
}
|
||||
}
|
||||
|
||||
const state = (this.state = {
|
||||
all: false, // whether all items are selected (accross pages)
|
||||
filter: defined(() => props.filters[props.defaultFilter], ''),
|
||||
page: 1,
|
||||
selectedColumn,
|
||||
sortOrder:
|
||||
props.columns[selectedColumn].sortOrder === 'desc' ? 'desc' : 'asc',
|
||||
})
|
||||
|
||||
const urlState = get(
|
||||
() => context.router.location.query[props.stateUrlParam]
|
||||
)
|
||||
|
||||
let matches
|
||||
if (
|
||||
urlState !== undefined &&
|
||||
(matches = URL_STATE_RE.exec(urlState)) !== null
|
||||
) {
|
||||
state.filter = matches[4]
|
||||
const page = matches[1]
|
||||
if (page !== undefined) {
|
||||
state.page = +page
|
||||
}
|
||||
let selectedColumn = matches[2]
|
||||
if (
|
||||
selectedColumn !== undefined &&
|
||||
(selectedColumn = +selectedColumn) < props.columns.length
|
||||
) {
|
||||
state.selectedColumn = selectedColumn
|
||||
state.sortOrder = matches[3] !== undefined ? 'desc' : 'asc'
|
||||
}
|
||||
}
|
||||
|
||||
this._getSelectedColumn = () =>
|
||||
this.props.columns[this.state.selectedColumn]
|
||||
this.props.columns[this._getSelectedColumnId()]
|
||||
|
||||
let getAllItems = () => this.props.collection
|
||||
if ('rowTransform' in props) {
|
||||
@ -392,14 +350,11 @@ export default class SortedTable extends Component {
|
||||
this._getItems = createSort(
|
||||
createFilter(
|
||||
getAllItems,
|
||||
createSelector(
|
||||
() => this.state.filter,
|
||||
filter => {
|
||||
try {
|
||||
return CM.parse(filter).createPredicate()
|
||||
} catch (_) {}
|
||||
}
|
||||
)
|
||||
createSelector(this._getFilter, filter => {
|
||||
try {
|
||||
return CM.parse(filter).createPredicate()
|
||||
} catch (_) {}
|
||||
})
|
||||
),
|
||||
createSelector(
|
||||
() => this._getSelectedColumn().valuePath,
|
||||
@ -410,12 +365,12 @@ export default class SortedTable extends Component {
|
||||
? object => sortCriteria(object, userData)
|
||||
: sortCriteria
|
||||
),
|
||||
() => this.state.sortOrder
|
||||
this._getSortOrder
|
||||
)
|
||||
|
||||
this._getVisibleItems = createPager(
|
||||
this._getItems,
|
||||
() => this.state.page,
|
||||
this._getPage,
|
||||
() => this.props.itemsPerPage
|
||||
)
|
||||
|
||||
@ -491,7 +446,7 @@ export default class SortedTable extends Component {
|
||||
case 'ROW_ACTION':
|
||||
if (item !== undefined) {
|
||||
if (rowLink !== undefined) {
|
||||
this.context.router.push(
|
||||
this.props.router.push(
|
||||
typeof rowLink === 'function'
|
||||
? rowLink(item, userData)
|
||||
: rowLink
|
||||
@ -507,8 +462,6 @@ export default class SortedTable extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._checkUpdatePage()
|
||||
|
||||
// Force one Portal refresh.
|
||||
// Because Portal cannot see the container reference at first rendering.
|
||||
if (this.props.paginationContainer) {
|
||||
@ -517,19 +470,14 @@ export default class SortedTable extends Component {
|
||||
}
|
||||
|
||||
_sort = columnId => {
|
||||
const { state } = this
|
||||
let sortOrder
|
||||
|
||||
if (state.selectedColumn === columnId) {
|
||||
sortOrder = state.sortOrder === 'desc' ? 'asc' : 'desc'
|
||||
} else {
|
||||
sortOrder =
|
||||
this.props.columns[columnId].sortOrder === 'desc' ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
this._setVisibleState({
|
||||
this._updateQueryString({
|
||||
selectedColumn: columnId,
|
||||
sortOrder,
|
||||
sortOrder:
|
||||
this._getSelectedColumnId() === columnId
|
||||
? this._getSortOrder() === 'desc'
|
||||
? 'asc'
|
||||
: 'desc'
|
||||
: defined(this.props.columns[columnId].sortOrder, 'asc'),
|
||||
})
|
||||
}
|
||||
|
||||
@ -548,58 +496,37 @@ export default class SortedTable extends Component {
|
||||
this.setState({ selectedItemsIds: newSelectedItems })
|
||||
}
|
||||
}
|
||||
|
||||
this._checkUpdatePage()
|
||||
}
|
||||
|
||||
_saveUrlState = () => {
|
||||
const { filter, page, selectedColumn, sortOrder } = this.state
|
||||
const { router } = this.context
|
||||
const { location } = router
|
||||
_updateQueryString({
|
||||
filter = this._getFilter(),
|
||||
page = this._getPage(),
|
||||
selectedColumn = this._getSelectedColumnId(),
|
||||
sortOrder = this._getSortOrder(),
|
||||
}) {
|
||||
const { location, router } = this.props
|
||||
router.replace({
|
||||
...location,
|
||||
query: {
|
||||
...location.query,
|
||||
[this.props.stateUrlParam]: `${page}_${selectedColumn}${
|
||||
sortOrder === 'desc' ? '_desc' : ''
|
||||
}-${filter}`,
|
||||
[this.props
|
||||
.stateUrlParam]: `${page}_${selectedColumn}_${sortOrder}-${filter}`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// update state in the state and update the URL param
|
||||
_setVisibleState(state) {
|
||||
this.setState(state, this.props.stateUrlParam && this._saveUrlState)
|
||||
}
|
||||
|
||||
_setFilter = filter => {
|
||||
this._setVisibleState({
|
||||
this.setState({
|
||||
highlighted: undefined,
|
||||
})
|
||||
this._updateQueryString({
|
||||
filter,
|
||||
page: 1,
|
||||
highlighted: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
_checkUpdatePage() {
|
||||
const { page } = this.state
|
||||
if (page === 1) {
|
||||
return
|
||||
}
|
||||
|
||||
const n = this._getItems().length
|
||||
const { itemsPerPage } = this.props
|
||||
if (n < itemsPerPage) {
|
||||
return this._setPage(1)
|
||||
}
|
||||
|
||||
const last = ceil(n / itemsPerPage)
|
||||
if (page > last) {
|
||||
return this._setPage(last)
|
||||
}
|
||||
}
|
||||
|
||||
_setPage(page) {
|
||||
this._setVisibleState({ page })
|
||||
this._updateQueryString({ page })
|
||||
}
|
||||
_setPage = this._setPage.bind(this)
|
||||
|
||||
@ -704,6 +631,63 @@ export default class SortedTable extends Component {
|
||||
this._selectItem(+target.name, target.checked, event.nativeEvent.shiftKey)
|
||||
}
|
||||
|
||||
_getParsedQueryString = createSelector(
|
||||
() => this.props.router.location.query[this.props.stateUrlParam],
|
||||
(urlState = '') => {
|
||||
const [, page, selectedColumnId, sortOrder, filter] =
|
||||
URL_STATE_RE.exec(urlState) || []
|
||||
return {
|
||||
filter,
|
||||
page,
|
||||
selectedColumnId,
|
||||
sortOrder,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
_getFilter = createSelector(
|
||||
() => this._getParsedQueryString().filter,
|
||||
() => this.props.filters,
|
||||
() => this.props.defaultFilter,
|
||||
(filter, filters, defaultFilter) =>
|
||||
defined(filter, () => filters[defaultFilter], '')
|
||||
)
|
||||
|
||||
_getNPages = createSelector(
|
||||
() => this._getItems().length,
|
||||
() => this.props.itemsPerPage,
|
||||
(nItems, itemsPerPage) => ceil(nItems / itemsPerPage)
|
||||
)
|
||||
|
||||
_getPage = createSelector(
|
||||
() => this._getParsedQueryString().page,
|
||||
this._getNPages,
|
||||
(page = 1, lastPage) => Math.min(+page, lastPage)
|
||||
)
|
||||
|
||||
_getSelectedColumnId = createSelector(
|
||||
() => this._getParsedQueryString().selectedColumnId,
|
||||
() => this.props.columns,
|
||||
() => this.props.defaultColumn,
|
||||
(columnIndex, columns, defaultColumnIndex) =>
|
||||
columnIndex !== undefined && (columnIndex = +columnIndex) < columns.length
|
||||
? columnIndex
|
||||
: defined(
|
||||
defaultColumnIndex,
|
||||
(columnIndex = findIndex(columns, 'default')) !== -1
|
||||
? columnIndex
|
||||
: 0
|
||||
)
|
||||
)
|
||||
|
||||
_getSortOrder = createSelector(
|
||||
() => this._getParsedQueryString().sortOrder,
|
||||
this._getSelectedColumnId,
|
||||
() => this.props.columns,
|
||||
(sortOrder, selectedColumnIndex, columns) =>
|
||||
defined(sortOrder, columns[selectedColumnIndex].sortOrder, 'asc')
|
||||
)
|
||||
|
||||
_getGroupedActions = createSelector(
|
||||
() => this.props.groupedActions,
|
||||
() => this.props.actions,
|
||||
@ -847,9 +831,9 @@ export default class SortedTable extends Component {
|
||||
|
||||
const paginationInstance = displayPagination && (
|
||||
<Pagination
|
||||
pages={ceil(nItems / itemsPerPage)}
|
||||
pages={this._getNPages()}
|
||||
onChange={this._setPage}
|
||||
value={state.page}
|
||||
value={this._getPage()}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -858,7 +842,7 @@ export default class SortedTable extends Component {
|
||||
filters={props.filters}
|
||||
onChange={this._setFilter}
|
||||
ref='filterInput'
|
||||
value={state.filter}
|
||||
value={this._getFilter()}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -959,7 +943,9 @@ export default class SortedTable extends Component {
|
||||
this._sort
|
||||
}
|
||||
sortIcon={
|
||||
state.selectedColumn === key ? state.sortOrder : 'sort'
|
||||
this._getSelectedColumnId() === key
|
||||
? this._getSortOrder()
|
||||
: 'sort'
|
||||
}
|
||||
/>
|
||||
))}
|
||||
@ -1006,3 +992,5 @@ export default class SortedTable extends Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(SortedTable)
|
||||
|
Loading…
Reference in New Issue
Block a user