From 79bc3574facd4e61191aeef75b8b3ada5e997af6 Mon Sep 17 00:00:00 2001 From: pkupczyk <piotr.kupczyk@id.ethz.ch> Date: Wed, 19 Jan 2022 12:39:20 +0100 Subject: [PATCH] SSDM-12155 : Table Widget : Improve table usability - clean up grid configuration widgets --- openbis_ng_ui/src/js/common/messages.js | 4 +- .../common/form/RadioGroupField.jsx | 164 ++++++++++++++++++ .../src/js/components/common/grid/Grid.jsx | 20 +-- .../{GridConfig.jsx => GridColumnsConfig.jsx} | 57 +++--- ...ConfigRow.jsx => GridColumnsConfigRow.jsx} | 12 +- .../js/components/common/grid/GridExports.jsx | 127 +++++++------- .../common/grid/GridFiltersConfig.jsx | 121 +++++++++++++ 7 files changed, 395 insertions(+), 110 deletions(-) create mode 100644 openbis_ng_ui/src/js/components/common/form/RadioGroupField.jsx rename openbis_ng_ui/src/js/components/common/grid/{GridConfig.jsx => GridColumnsConfig.jsx} (76%) rename openbis_ng_ui/src/js/components/common/grid/{GridConfigRow.jsx => GridColumnsConfigRow.jsx} (85%) create mode 100644 openbis_ng_ui/src/js/components/common/grid/GridFiltersConfig.jsx diff --git a/openbis_ng_ui/src/js/common/messages.js b/openbis_ng_ui/src/js/common/messages.js index 43c9dded2ce..2b87c8b0177 100644 --- a/openbis_ng_ui/src/js/common/messages.js +++ b/openbis_ng_ui/src/js/common/messages.js @@ -237,7 +237,7 @@ const messages_en = { [keys.CODE]: 'Code', [keys.COLLECTION_TYPES]: 'Collection Types', [keys.COLLECTION_TYPE]: 'Collection Type', - [keys.COLUMN_FILTERS]: 'Per Column', + [keys.COLUMN_FILTERS]: 'Filter Per Column', [keys.COLUMNS]: 'Columns', [keys.CONFIRMATION]: 'Confirmation', [keys.CONFIRMATION_ACTIVATE_USER]: 'Are you sure you want to activate the user?', @@ -293,7 +293,7 @@ const messages_en = { [keys.FREEZES]: 'Freezes', [keys.GENERATED_CODE_PREFIX]: 'Generated code prefix', [keys.GENERATE_CODES]: 'Generate Codes', - [keys.GLOBAL_FILTER]: 'Global', + [keys.GLOBAL_FILTER]: 'Global Filter', [keys.GROUPS]: 'Groups', [keys.GROUP]: 'Group', [keys.HIDE]: 'hide', diff --git a/openbis_ng_ui/src/js/components/common/form/RadioGroupField.jsx b/openbis_ng_ui/src/js/components/common/form/RadioGroupField.jsx new file mode 100644 index 00000000000..c269d191254 --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/form/RadioGroupField.jsx @@ -0,0 +1,164 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import Radio from '@material-ui/core/Radio' +import Typography from '@material-ui/core/Typography' +import FormFieldContainer from '@src/js/components/common/form/FormFieldContainer.jsx' +import FormFieldLabel from '@src/js/components/common/form/FormFieldLabel.jsx' +import logger from '@src/js/common/logger.js' + +const styles = theme => ({ + container: {}, + radioContainer: { + display: 'flex', + padding: `${theme.spacing(1) / 2}px 0px`, + '&:first-child': { + paddingTop: 0 + }, + '&:last-child': { + paddingBottom: 0 + } + }, + radio: { + padding: '2px', + marginRight: '4px' + }, + label: { + cursor: 'pointer' + }, + labelDisabled: { + cursor: 'inherit' + } +}) + +class RadioGroupField extends React.PureComponent { + static defaultProps = { + mode: 'edit' + } + + constructor(props) { + super(props) + + const references = {} + if (props.options) { + props.options.forEach(option => { + if (option.value) { + references[option.value] = React.createRef() + } + }) + } + + this.references = references + this.action = null + this.handleLabelClick = this.handleLabelClick.bind(this) + this.handleChange = this.handleChange.bind(this) + this.handleFocus = this.handleFocus.bind(this) + } + + handleLabelClick(value) { + const reference = this.getReference(value) + if (reference && reference.current) { + reference.current.click() + } + } + + handleChange(event) { + this.handleEvent(event, this.props.onChange) + } + + handleFocus(event) { + this.handleEvent(event, this.props.onFocus) + if (this.action) { + this.action.focusVisible() + } + } + + handleEvent(event, handler) { + const { name } = this.props + + if (handler) { + const newEvent = { + ...event, + target: { + ...event.target, + name: name, + value: event.target.value + } + } + delete newEvent.target.checked + handler(newEvent) + } + } + + render() { + logger.log(logger.DEBUG, 'RadioGroupField.render') + + const { + description, + options, + error, + metadata, + mode, + styles, + classes, + onClick + } = this.props + + if (mode !== 'view' && mode !== 'edit') { + throw 'Unsupported mode: ' + mode + } + + return ( + <FormFieldContainer + description={description} + error={error} + metadata={metadata} + styles={styles} + onClick={onClick} + > + <div className={classes.container}> + {options.map(option => this.renderOption(option))} + </div> + </FormFieldContainer> + ) + } + + renderOption(option) { + const { value, disabled, mode, styles, classes, onClick } = this.props + + const isDisabled = disabled || mode !== 'edit' + + return ( + <div key={option.value} className={classes.radioContainer}> + <Radio + inputRef={this.getReference(option.value)} + action={action => (this.action = action)} + value={option.value} + checked={option.value === value} + disabled={isDisabled} + onChange={this.handleChange} + onFocus={this.handleFocus} + onClick={onClick} + classes={{ root: classes.radio }} + size='small' + /> + <Typography + component='label' + className={isDisabled ? classes.labelDisabled : classes.label} + onClick={() => this.handleLabelClick(option.value)} + > + <FormFieldLabel + label={option.label} + styles={styles} + onClick={onClick} + /> + </Typography> + </div> + ) + } + + getReference(value) { + return this.references[value] + } +} + +export default withStyles(styles)(RadioGroupField) diff --git a/openbis_ng_ui/src/js/components/common/grid/Grid.jsx b/openbis_ng_ui/src/js/components/common/grid/Grid.jsx index a473211dc4d..1a778a577d8 100644 --- a/openbis_ng_ui/src/js/components/common/grid/Grid.jsx +++ b/openbis_ng_ui/src/js/components/common/grid/Grid.jsx @@ -14,7 +14,7 @@ import GridRow from '@src/js/components/common/grid/GridRow.jsx' import GridActions from '@src/js/components/common/grid/GridActions.jsx' import GridExports from '@src/js/components/common/grid/GridExports.jsx' import GridPaging from '@src/js/components/common/grid/GridPaging.jsx' -import GridConfig from '@src/js/components/common/grid/GridConfig.jsx' +import GridColumnsConfig from '@src/js/components/common/grid/GridColumnsConfig.jsx' import GridFiltersConfig from '@src/js/components/common/grid/GridFiltersConfig.jsx' import ComponentContext from '@src/js/components/common/ComponentContext.js' import logger from '@src/js/common/logger.js' @@ -52,9 +52,6 @@ const styles = theme => ({ zIndex: '200', backgroundColor: theme.palette.background.paper }, - tableBody: { - '& tr:last-child td': {} - }, tableFooter: { position: 'sticky', bottom: 0, @@ -180,9 +177,7 @@ class Grid extends React.PureComponent { this.controller.handlePageSizeChange } /> - <GridConfig - filterModes={filterModes} - filterMode={filterMode} + <GridColumnsConfig columns={allColumns} columnsVisibility={columnsVisibility} loading={loading} @@ -192,22 +187,11 @@ class Grid extends React.PureComponent { onOrderChange={ this.controller.handleColumnOrderChange } - onFilterModeChange={ - this.controller.handleFilterModeChange - } /> <GridFiltersConfig filterModes={filterModes} filterMode={filterMode} - columns={allColumns} - columnsVisibility={columnsVisibility} loading={loading} - onVisibleChange={ - this.controller.handleColumnVisibleChange - } - onOrderChange={ - this.controller.handleColumnOrderChange - } onFilterModeChange={ this.controller.handleFilterModeChange } diff --git a/openbis_ng_ui/src/js/components/common/grid/GridConfig.jsx b/openbis_ng_ui/src/js/components/common/grid/GridColumnsConfig.jsx similarity index 76% rename from openbis_ng_ui/src/js/components/common/grid/GridConfig.jsx rename to openbis_ng_ui/src/js/components/common/grid/GridColumnsConfig.jsx index fa2d6a89133..3c6659d5d8c 100644 --- a/openbis_ng_ui/src/js/components/common/grid/GridConfig.jsx +++ b/openbis_ng_ui/src/js/components/common/grid/GridColumnsConfig.jsx @@ -3,13 +3,11 @@ import React from 'react' import autoBind from 'auto-bind' import { withStyles } from '@material-ui/core/styles' import { DragDropContext, Droppable } from 'react-beautiful-dnd' -import Button from '@src/js/components/common/form/Button.jsx' +import Popover from '@material-ui/core/Popover' import Mask from '@src/js/components/common/loading/Mask.jsx' import Container from '@src/js/components/common/form/Container.jsx' -import Link from '@src/js/components/common/form/Link.jsx' -import SettingsIcon from '@material-ui/icons/Settings' -import GridConfigRow from '@src/js/components/common/grid/GridConfigRow.jsx' -import Popover from '@material-ui/core/Popover' +import Button from '@src/js/components/common/form/Button.jsx' +import GridColumnsConfigRow from '@src/js/components/common/grid/GridColumnsConfigRow.jsx' import messages from '@src/js/common/messages.js' import logger from '@src/js/common/logger.js' @@ -19,17 +17,20 @@ const styles = theme => ({ alignItems: 'center', paddingRight: theme.spacing(1) }, - filters: { - paddingBottom: theme.spacing(1) - }, - columnsList: { + columns: { listStyle: 'none', margin: 0, padding: 0 + }, + buttons: { + marginBottom: theme.spacing(1), + '& button': { + marginRight: theme.spacing(1) + } } }) -class GridConfig extends React.PureComponent { +class GridColumnsConfig extends React.PureComponent { constructor(props) { super(props) autoBind(this) @@ -77,7 +78,7 @@ class GridConfig extends React.PureComponent { } render() { - logger.log(logger.DEBUG, 'GridConfig.render') + logger.log(logger.DEBUG, 'GridColumnsConfig.render') const { classes, loading } = this.props const { el } = this.state @@ -85,13 +86,11 @@ class GridConfig extends React.PureComponent { return ( <div className={classes.container}> <Button - label='Columns' + label={messages.get(messages.COLUMNS)} color='default' variant='outlined' onClick={this.handleOpen} - > - <SettingsIcon fontSize='small' /> - </Button> + /> <Popover open={Boolean(el)} anchorEl={el} @@ -106,7 +105,7 @@ class GridConfig extends React.PureComponent { }} > <Mask visible={loading}> - <Container>{this.renderColumns()}</Container> + <Container square={true}>{this.renderColumns()}</Container> </Mask> </Popover> </div> @@ -117,16 +116,26 @@ class GridConfig extends React.PureComponent { const { classes, columns, columnsVisibility, onVisibleChange } = this.props return ( <div> + <div className={classes.buttons}> + <Button + label={messages.get(messages.SHOW_ALL)} + onClick={this.handleShowAll} + /> + <Button + label={messages.get(messages.HIDE_ALL)} + onClick={this.handleHideAll} + /> + </div> <DragDropContext onDragEnd={this.handleDragEnd}> <Droppable droppableId='root'> {provided => ( <ol ref={provided.innerRef} {...provided.droppableProps} - className={classes.columnsList} + className={classes.columns} > {columns.map((column, index) => ( - <GridConfigRow + <GridColumnsConfigRow key={column.name} column={column} visible={columnsVisibility[column.name]} @@ -139,19 +148,9 @@ class GridConfig extends React.PureComponent { )} </Droppable> </DragDropContext> - <br /> - <Button - label={messages.get(messages.SHOW_ALL)} - onClick={this.handleShowAll} - /> - <span> </span> - <Button - label={messages.get(messages.HIDE_ALL)} - onClick={this.handleHideAll} - /> </div> ) } } -export default _.flow(withStyles(styles))(GridConfig) +export default _.flow(withStyles(styles))(GridColumnsConfig) diff --git a/openbis_ng_ui/src/js/components/common/grid/GridConfigRow.jsx b/openbis_ng_ui/src/js/components/common/grid/GridColumnsConfigRow.jsx similarity index 85% rename from openbis_ng_ui/src/js/components/common/grid/GridConfigRow.jsx rename to openbis_ng_ui/src/js/components/common/grid/GridColumnsConfigRow.jsx index bc8e62ceed2..d70a2b119ca 100644 --- a/openbis_ng_ui/src/js/components/common/grid/GridConfigRow.jsx +++ b/openbis_ng_ui/src/js/components/common/grid/GridColumnsConfigRow.jsx @@ -9,7 +9,13 @@ const styles = theme => ({ row: { display: 'flex', alignItems: 'center', - padding: `${theme.spacing(1) / 2}px 0px` + padding: `${theme.spacing(1) / 2}px 0px`, + '&:first-child': { + paddingTop: 0 + }, + '&:last-child': { + paddingBottom: 0 + } }, label: { marginLeft: 0 @@ -20,7 +26,7 @@ const styles = theme => ({ } }) -class GridConfigRow extends React.PureComponent { +class GridColumnsConfigRow extends React.PureComponent { constructor(props) { super(props) this.handleVisibleChange = this.handleVisibleChange.bind(this) @@ -61,4 +67,4 @@ class GridConfigRow extends React.PureComponent { } } -export default withStyles(styles)(GridConfigRow) +export default withStyles(styles)(GridColumnsConfigRow) diff --git a/openbis_ng_ui/src/js/components/common/grid/GridExports.jsx b/openbis_ng_ui/src/js/components/common/grid/GridExports.jsx index e919b40a69c..6826c69ad64 100644 --- a/openbis_ng_ui/src/js/components/common/grid/GridExports.jsx +++ b/openbis_ng_ui/src/js/components/common/grid/GridExports.jsx @@ -12,14 +12,23 @@ import logger from '@src/js/common/logger.js' const styles = theme => ({ container: { - padding: theme.spacing(1), - paddingLeft: theme.spacing(1) + display: 'flex', + alignItems: 'center' }, popup: { maxWidth: '300px' }, field: { - paddingBottom: theme.spacing(1) + paddingBottom: theme.spacing(1), + '&:first-child': { + paddingTop: 0 + }, + '&:last-child': { + paddingBottom: 0 + } + }, + button: { + paddingTop: theme.spacing(1) } }) @@ -113,63 +122,65 @@ class GridExports extends React.PureComponent { horizontal: 'left' }} > - <Container className={classes.popup}> - <div className={classes.field}> - <SelectField - label={messages.get(messages.COLUMNS)} - name='columns' - options={[ - { - label: messages.get(messages.ALL_COLUMNS), - value: GridExportOptions.ALL_COLUMNS - }, - { - label: messages.get(messages.VISIBLE_COLUMNS), - value: GridExportOptions.VISIBLE_COLUMNS - } - ]} - value={exportOptions.columns} - variant='standard' - onChange={this.handleChange} - /> - </div> - <div className={classes.field}> - <SelectField - label={messages.get(messages.ROWS)} - name='rows' - options={rowsOptions} - value={exportOptions.rows} - variant='standard' - onChange={this.handleChange} - /> - </div> - <div className={classes.field}> - <SelectField - label={messages.get(messages.VALUES)} - name='values' - options={[ - { - label: messages.get(messages.PLAIN_TEXT), - value: GridExportOptions.PLAIN_TEXT - }, - { - label: messages.get(messages.RICH_TEXT), - value: GridExportOptions.RICH_TEXT - } - ]} - value={exportOptions.values} - variant='standard' - onChange={this.handleChange} - /> - </div> - {exportOptions.values === GridExportOptions.PLAIN_TEXT && ( + <Container square={true} className={classes.popup}> + <div> + <div className={classes.field}> + <SelectField + label={messages.get(messages.COLUMNS)} + name='columns' + options={[ + { + label: messages.get(messages.ALL_COLUMNS), + value: GridExportOptions.ALL_COLUMNS + }, + { + label: messages.get(messages.VISIBLE_COLUMNS), + value: GridExportOptions.VISIBLE_COLUMNS + } + ]} + value={exportOptions.columns} + variant='standard' + onChange={this.handleChange} + /> + </div> <div className={classes.field}> - <Message type='warning'> - {messages.get(messages.EXPORT_PLAIN_TEXT_WARNING)} - </Message> + <SelectField + label={messages.get(messages.ROWS)} + name='rows' + options={rowsOptions} + value={exportOptions.rows} + variant='standard' + onChange={this.handleChange} + /> </div> - )} - <div className={classes.field}> + <div className={classes.field}> + <SelectField + label={messages.get(messages.VALUES)} + name='values' + options={[ + { + label: messages.get(messages.PLAIN_TEXT), + value: GridExportOptions.PLAIN_TEXT + }, + { + label: messages.get(messages.RICH_TEXT), + value: GridExportOptions.RICH_TEXT + } + ]} + value={exportOptions.values} + variant='standard' + onChange={this.handleChange} + /> + </div> + {exportOptions.values === GridExportOptions.PLAIN_TEXT && ( + <div className={classes.field}> + <Message type='warning'> + {messages.get(messages.EXPORT_PLAIN_TEXT_WARNING)} + </Message> + </div> + )} + </div> + <div className={classes.button}> <Button label={messages.get(messages.EXPORT)} type='neutral' diff --git a/openbis_ng_ui/src/js/components/common/grid/GridFiltersConfig.jsx b/openbis_ng_ui/src/js/components/common/grid/GridFiltersConfig.jsx new file mode 100644 index 00000000000..bb9a5fbbf70 --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/grid/GridFiltersConfig.jsx @@ -0,0 +1,121 @@ +import _ from 'lodash' +import React from 'react' +import autoBind from 'auto-bind' +import { withStyles } from '@material-ui/core/styles' +import Popover from '@material-ui/core/Popover' +import Mask from '@src/js/components/common/loading/Mask.jsx' +import Container from '@src/js/components/common/form/Container.jsx' +import RadioGroupField from '@src/js/components/common/form/RadioGroupField.jsx' +import Button from '@src/js/components/common/form/Button.jsx' +import GridFilterOptions from '@src/js/components/common/grid/GridFilterOptions.js' +import messages from '@src/js/common/messages.js' +import logger from '@src/js/common/logger.js' + +const styles = theme => ({ + container: { + display: 'flex', + alignItems: 'center', + paddingRight: theme.spacing(1) + } +}) + +class GridFiltersConfig extends React.PureComponent { + constructor(props) { + super(props) + autoBind(this) + this.state = { + el: null + } + } + + handleOpen(event) { + this.setState({ + el: event.currentTarget + }) + } + + handleClose() { + this.setState({ + el: null + }) + } + + handleFilterModeChange(event) { + const { onFilterModeChange } = this.props + if (onFilterModeChange) { + onFilterModeChange(event.target.value) + } + } + + render() { + logger.log(logger.DEBUG, 'GridFiltersConfig.render') + + const { filterModes, loading, classes } = this.props + const { el } = this.state + + if (filterModes && filterModes.length <= 1) { + return null + } + + return ( + <div className={classes.container}> + <Button + label={messages.get(messages.FILTERS)} + color='default' + variant='outlined' + onClick={this.handleOpen} + /> + <Popover + open={Boolean(el)} + anchorEl={el} + onClose={this.handleClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'left' + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'left' + }} + > + <Mask visible={loading}> + <Container square={true}>{this.renderFilters()}</Container> + </Mask> + </Popover> + </div> + ) + } + + renderFilters() { + const { filterModes, filterMode } = this.props + + const allOptions = [ + { + value: GridFilterOptions.COLUMN_FILTERS, + label: messages.get(messages.COLUMN_FILTERS) + }, + { + value: GridFilterOptions.GLOBAL_FILTER, + label: messages.get(messages.GLOBAL_FILTER) + } + ] + + const chosenOptions = allOptions.filter( + option => + filterModes === null || + filterModes === undefined || + filterModes.includes(option.value) + ) + + return ( + <RadioGroupField + name='filterMode' + value={filterMode} + options={chosenOptions} + onChange={this.handleFilterModeChange} + /> + ) + } +} + +export default _.flow(withStyles(styles))(GridFiltersConfig) -- GitLab