Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / components / data-explorer / data-explorer.tsx
index 8d16aed2e17968430cacbdf1c1e35064c8d05e26..27e46d584962c8d3e1cb1ca536b21ab1b4577ecf 100644 (file)
@@ -2,58 +2,71 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React from 'react';
-import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, TablePagination, IconButton, Tooltip, Button } from '@material-ui/core';
-import { ColumnSelector } from 'components/column-selector/column-selector';
-import { DataTable, DataColumns, DataTableFetchMode } from 'components/data-table/data-table';
-import { DataColumn } from 'components/data-table/data-column';
-import { SearchInput } from 'components/search-input/search-input';
-import { ArvadosTheme } from 'common/custom-theme';
-import { createTree } from 'models/tree';
-import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
-import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
-import { PaperProps } from '@material-ui/core/Paper';
-import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
-import { MultiselectToolbar, defaultActions } from 'components/multiselectToolbar/MultiselectToolbar';
+import React from "react";
+import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, TablePagination, IconButton, Tooltip, Button } from "@material-ui/core";
+import { ColumnSelector } from "components/column-selector/column-selector";
+import { DataTable, DataColumns, DataTableFetchMode } from "components/data-table/data-table";
+import { DataColumn } from "components/data-table/data-column";
+import { SearchInput } from "components/search-input/search-input";
+import { ArvadosTheme } from "common/custom-theme";
+import { MultiselectToolbar } from "components/multiselect-toolbar/MultiselectToolbar";
+import { TCheckedList } from "components/data-table/data-table";
+import { createTree } from "models/tree";
+import { DataTableFilters } from "components/data-table-filters/data-table-filters-tree";
+import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreVerticalIcon } from "components/icon/icon";
+import { PaperProps } from "@material-ui/core/Paper";
+import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
 
-type CssRules = 'searchBox' | 'headerMenu' | 'toolbar' | 'footer' | 'root' | 'moreOptionsButton' | 'title' | 'dataTable' | 'container';
+type CssRules = "titleWrapper" | "searchBox" | "headerMenu" | "toolbar" | "footer" | "root" | "moreOptionsButton" | "title" | 'subProcessTitle' | "dataTable" | "container";
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    titleWrapper: {
+        display: "flex",
+        justifyContent: "space-between",
+    },
     searchBox: {
         paddingBottom: 0,
     },
     toolbar: {
         paddingTop: 0,
         paddingRight: theme.spacing.unit,
+        paddingLeft: "10px",
     },
     footer: {
-        overflow: 'auto',
+        overflow: "auto",
     },
     root: {
-        height: '100%',
+        height: "100%",
     },
     moreOptionsButton: {
         padding: 0,
     },
     title: {
-        display: 'inline-block',
+        display: "inline-block",
+        paddingLeft: theme.spacing.unit * 2,
+        paddingTop: theme.spacing.unit * 2,
+        fontSize: "18px",
+        paddingRight: "10px",
+    },
+    subProcessTitle: {
+        display: "inline-block",
         paddingLeft: theme.spacing.unit * 2,
         paddingTop: theme.spacing.unit * 2,
-        fontSize: '18px',
+        fontSize: "18px",
+        flexGrow: 0,
+        paddingRight: "10px",
     },
     dataTable: {
-        height: '100%',
-        overflow: 'auto',
+        height: "100%",
+        overflow: "auto",
     },
     container: {
-        height: '100%',
+        height: "100%",
     },
     headerMenu: {
-        width: '100%',
-        float: 'right',
-        display: 'flex',
-        flexDirection: 'row-reverse',
-        justifyContent: 'space-between',
+        marginLeft: "auto",
+        flexBasis: "initial",
+        flexGrow: 0,
     },
 });
 
@@ -78,10 +91,12 @@ interface DataExplorerDataProps<T> {
     actions?: React.ReactNode;
     hideSearchInput?: boolean;
     title?: React.ReactNode;
+    progressBar?: React.ReactNode;
     paperKey?: string;
     currentItemUuid: string;
     elementPath?: string;
     isMSToolbarVisible: boolean;
+    checkedList: TCheckedList;
 }
 
 interface DataExplorerActionProps<T> {
@@ -98,6 +113,7 @@ interface DataExplorerActionProps<T> {
     onLoadMore: (page: number) => void;
     extractKey?: (item: T) => React.Key;
     toggleMSToolbar: (isVisible: boolean) => void;
+    setCheckedListOnStore: (checkedList: TCheckedList) => void;
 }
 
 type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules> & MPVPanelProps;
@@ -106,13 +122,15 @@ export const DataExplorer = withStyles(styles)(
     class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
         state = {
             showLoading: false,
-            prevRefresh: '',
-            prevRoute: '',
+            prevRefresh: "",
+            prevRoute: "",
         };
 
+        multiSelectToolbarInTitle = !this.props.title && !this.props.progressBar;
+
         componentDidUpdate(prevProps: DataExplorerProps<T>) {
-            const currentRefresh = this.props.currentRefresh || '';
-            const currentRoute = this.props.currentRoute || '';
+            const currentRefresh = this.props.currentRefresh || "";
+            const currentRoute = this.props.currentRoute || "";
 
             if (currentRoute !== this.state.prevRoute) {
                 // Component already mounted, but the user comes from a route change,
@@ -145,8 +163,8 @@ export const DataExplorer = withStyles(styles)(
             // Component just mounted, so we need to show the loading indicator.
             this.setState({
                 showLoading: this.props.working,
-                prevRefresh: this.props.currentRefresh || '',
-                prevRoute: this.props.currentRoute || '',
+                prevRefresh: this.props.currentRefresh || "",
+                prevRoute: this.props.currentRoute || "",
             });
         }
 
@@ -177,62 +195,116 @@ export const DataExplorer = withStyles(styles)(
                 paperKey,
                 fetchMode,
                 currentItemUuid,
+                currentRoute,
                 title,
+                progressBar,
                 doHidePanel,
                 doMaximizePanel,
                 doUnMaximizePanel,
                 panelName,
                 panelMaximized,
                 elementPath,
-                isMSToolbarVisible,
                 toggleMSToolbar,
+                setCheckedListOnStore,
+                checkedList,
             } = this.props;
             return (
-                <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={this.props['data-cy']}>
-                    <Grid container direction='column' wrap='nowrap' className={classes.container}>
-                        <div>
+                <Paper
+                    className={classes.root}
+                    {...paperProps}
+                    key={paperKey}
+                    data-cy={this.props["data-cy"]}
+                >
+                    <Grid
+                        container
+                        direction="column"
+                        wrap="nowrap"
+                        className={classes.container}
+                    >
+                        <div className={classes.titleWrapper} style={currentRoute?.includes('search-results') || !!progressBar ? {marginBottom: '-20px'} : {}}>
                             {title && (
-                                <Grid item xs className={classes.title}>
+                                <Grid
+                                    item
+                                    xs
+                                    className={!!progressBar ? classes.subProcessTitle : classes.title}
+                                >
                                     {title}
                                 </Grid>
                             )}
+                            {!!progressBar && progressBar}
+                            {this.multiSelectToolbarInTitle && <MultiselectToolbar />}
                             {(!hideColumnSelector || !hideSearchInput || !!actions) && (
-                                <Grid className={classes.headerMenu} item xs>
+                                <Grid
+                                    className={classes.headerMenu}
+                                    item
+                                    xs
+                                >
                                     <Toolbar className={classes.toolbar}>
-                                        {!hideSearchInput && (
-                                            <div className={classes.searchBox}>
-                                                {!hideSearchInput && <SearchInput label={searchLabel} value={searchValue} selfClearProp={''} onSearch={onSearch} />}
-                                            </div>
-                                        )}
-                                        {actions}
-                                        {!hideColumnSelector && <ColumnSelector columns={columns} onColumnToggle={onColumnToggle} />}
+                                        <Grid container justify="space-between" wrap="nowrap" alignItems="center">
+                                            {!hideSearchInput && (
+                                                <div className={classes.searchBox}>
+                                                    {!hideSearchInput && (
+                                                        <SearchInput
+                                                            label={searchLabel}
+                                                            value={searchValue}
+                                                            selfClearProp={""}
+                                                            onSearch={onSearch}
+                                                        />
+                                                    )}
+                                                </div>
+                                            )}
+                                            {actions}
+                                            {!hideColumnSelector && (
+                                                <ColumnSelector
+                                                    columns={columns}
+                                                    onColumnToggle={onColumnToggle}
+                                                />
+                                            )}
+                                        </Grid>
                                         {doUnMaximizePanel && panelMaximized && (
-                                            <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
+                                            <Tooltip
+                                                title={`Unmaximize ${panelName || "panel"}`}
+                                                disableFocusListener
+                                            >
                                                 <IconButton onClick={doUnMaximizePanel}>
                                                     <UnMaximizeIcon />
                                                 </IconButton>
                                             </Tooltip>
                                         )}
                                         {doMaximizePanel && !panelMaximized && (
-                                            <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
+                                            <Tooltip
+                                                title={`Maximize ${panelName || "panel"}`}
+                                                disableFocusListener
+                                            >
                                                 <IconButton onClick={doMaximizePanel}>
                                                     <MaximizeIcon />
                                                 </IconButton>
                                             </Tooltip>
                                         )}
                                         {doHidePanel && (
-                                            <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                                                <IconButton disabled={panelMaximized} onClick={doHidePanel}>
+                                            <Tooltip
+                                                title={`Close ${panelName || "panel"}`}
+                                                disableFocusListener
+                                            >
+                                                <IconButton
+                                                    disabled={panelMaximized}
+                                                    onClick={doHidePanel}
+                                                >
                                                     <CloseIcon />
                                                 </IconButton>
                                             </Tooltip>
                                         )}
                                     </Toolbar>
-                                    {isMSToolbarVisible && <MultiselectToolbar actions={defaultActions} />}
                                 </Grid>
                             )}
                         </div>
-                        <Grid item xs='auto' className={classes.dataTable}>
+                        {!this.multiSelectToolbarInTitle && <MultiselectToolbar />}
+                        <Grid
+                            item
+                            xs="auto"
+                            className={classes.dataTable}
+                            style={currentRoute?.includes('search-results')  || !!progressBar ? {marginTop: '-10px'} : {}}
+                        >
                             <DataTable
                                 columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
                                 items={items}
@@ -248,16 +320,24 @@ export const DataExplorer = withStyles(styles)(
                                 currentItemUuid={currentItemUuid}
                                 currentRoute={paperKey}
                                 toggleMSToolbar={toggleMSToolbar}
+                                setCheckedListOnStore={setCheckedListOnStore}
+                                checkedList={checkedList}
                             />
                         </Grid>
-                        <Grid item xs>
+                        <Grid
+                            item
+                            xs
+                        >
                             <Toolbar className={classes.footer}>
                                 {elementPath && (
                                     <Grid container>
-                                        <span data-cy='element-path'>{elementPath}</span>
+                                        <span data-cy="element-path">{elementPath}</span>
                                     </Grid>
                                 )}
-                                <Grid container={!elementPath} justify='flex-end'>
+                                <Grid
+                                    container={!elementPath}
+                                    justify="flex-end"
+                                >
                                     {fetchMode === DataTableFetchMode.PAGINATED ? (
                                         <TablePagination
                                             count={itemsAvailable}
@@ -268,10 +348,14 @@ export const DataExplorer = withStyles(styles)(
                                             onChangeRowsPerPage={this.changeRowsPerPage}
                                             // Disable next button on empty lists since that's not default behavior
                                             nextIconButtonProps={itemsAvailable > 0 ? {} : { disabled: true }}
-                                            component='div'
+                                            component="div"
                                         />
                                     ) : (
-                                        <Button variant='text' size='medium' onClick={this.loadMore}>
+                                        <Button
+                                            variant="text"
+                                            size="medium"
+                                            onClick={this.loadMore}
+                                        >
                                             Load more
                                         </Button>
                                     )}
@@ -287,7 +371,7 @@ export const DataExplorer = withStyles(styles)(
             this.props.onChangePage(page);
         };
 
-        changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
+        changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = event => {
             this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
         };
 
@@ -296,21 +380,33 @@ export const DataExplorer = withStyles(styles)(
         };
 
         renderContextMenuTrigger = (item: T) => (
-            <Grid container justify='center'>
-                <Tooltip title='More options' disableFocusListener>
-                    <IconButton className={this.props.classes.moreOptionsButton} onClick={(event) => this.props.onContextMenu(event, item)}>
-                        <MoreOptionsIcon />
+            <Grid
+                container
+                justify="center"
+            >
+                <Tooltip
+                    title="More options"
+                    disableFocusListener
+                >
+                    <IconButton
+                        className={this.props.classes.moreOptionsButton}
+                        onClick={event => {
+                            event.stopPropagation()
+                            this.props.onContextMenu(event, item)
+                        }}
+                    >
+                        <MoreVerticalIcon />
                     </IconButton>
                 </Tooltip>
             </Grid>
         );
 
         contextMenuColumn: DataColumn<any, any> = {
-            name: 'Actions',
+            name: "Actions",
             selected: true,
             configurable: false,
             filters: createTree(),
-            key: 'context-actions',
+            key: "context-actions",
             render: this.renderContextMenuTrigger,
         };
     }