17579: Added search input clear after project change
[arvados-workbench2.git] / src / components / data-explorer / data-explorer.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { Grid, Paper, Toolbar, StyleRulesCallback, withStyles, WithStyles, TablePagination, IconButton, Tooltip, Button } from '@material-ui/core';
7 import { ColumnSelector } from "components/column-selector/column-selector";
8 import { DataTable, DataColumns, DataTableFetchMode } from "components/data-table/data-table";
9 import { DataColumn } from "components/data-table/data-column";
10 import { SearchInput } from 'components/search-input/search-input';
11 import { ArvadosTheme } from "common/custom-theme";
12 import { createTree } from 'models/tree';
13 import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
14 import { MoreOptionsIcon } from 'components/icon/icon';
15 import { PaperProps } from '@material-ui/core/Paper';
16
17 type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
18
19 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
20     searchBox: {
21         paddingBottom: theme.spacing.unit * 2
22     },
23     toolbar: {
24         paddingTop: theme.spacing.unit * 2
25     },
26     toolbarUnderTitle: {
27         paddingTop: 0
28     },
29     footer: {
30         overflow: 'auto'
31     },
32     root: {
33         height: '100%'
34     },
35     moreOptionsButton: {
36         padding: 0
37     },
38     title: {
39         paddingLeft: theme.spacing.unit * 3,
40         paddingTop: theme.spacing.unit * 3,
41         fontSize: '18px'
42     }
43 });
44
45 interface DataExplorerDataProps<T> {
46     fetchMode: DataTableFetchMode;
47     items: T[];
48     itemsAvailable: number;
49     columns: DataColumns<T>;
50     searchLabel?: string;
51     searchValue: string;
52     rowsPerPage: number;
53     rowsPerPageOptions: number[];
54     page: number;
55     contextMenuColumn: boolean;
56     dataTableDefaultView?: React.ReactNode;
57     working?: boolean;
58     hideColumnSelector?: boolean;
59     paperProps?: PaperProps;
60     actions?: React.ReactNode;
61     hideSearchInput?: boolean;
62     title?: React.ReactNode;
63     paperKey?: string;
64     currentItemUuid: string;
65 }
66
67 interface DataExplorerActionProps<T> {
68     onSetColumns: (columns: DataColumns<T>) => void;
69     onSearch: (value: string) => void;
70     onRowClick: (item: T) => void;
71     onRowDoubleClick: (item: T) => void;
72     onColumnToggle: (column: DataColumn<T>) => void;
73     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: T) => void;
74     onSortToggle: (column: DataColumn<T>) => void;
75     onFiltersChange: (filters: DataTableFilters, column: DataColumn<T>) => void;
76     onChangePage: (page: number) => void;
77     onChangeRowsPerPage: (rowsPerPage: number) => void;
78     onLoadMore: (page: number) => void;
79     extractKey?: (item: T) => React.Key;
80 }
81
82 type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
83
84 export const DataExplorer = withStyles(styles)(
85     class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>, { currentItemUuid: string }> {
86         constructor(props) {
87             super(props);
88             this.state = {
89                 currentItemUuid: props.currentItemUuid
90             };
91         }
92         componentDidMount() {
93             if (this.props.onSetColumns) {
94                 this.props.onSetColumns(this.props.columns);
95             }
96         }
97
98         render() {
99             const {
100                 columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
101                 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
102                 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
103                 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
104                 paperKey, fetchMode, currentItemUuid, title
105             } = this.props;
106
107             return <Paper className={classes.root} {...paperProps} key={paperKey}>
108                 {title && <div className={classes.title}>{title}</div>}
109                 {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
110                     <Grid container justify="space-between" wrap="nowrap" alignItems="center">
111                         <div className={classes.searchBox}>
112                             {!hideSearchInput && <SearchInput
113                                 label={searchLabel}
114                                 value={searchValue}
115                                 selfClearProp={currentItemUuid}
116                                 onSearch={onSearch} />}
117                         </div>
118                         {actions}
119                         {!hideColumnSelector && <ColumnSelector
120                             columns={columns}
121                             onColumnToggle={onColumnToggle} />}
122                     </Grid>
123                 </Toolbar>}
124                 <DataTable
125                     columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
126                     items={items}
127                     onRowClick={(_, item: T) => onRowClick(item)}
128                     onContextMenu={onContextMenu}
129                     onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
130                     onFiltersChange={onFiltersChange}
131                     onSortToggle={onSortToggle}
132                     extractKey={extractKey}
133                     working={working}
134                     defaultView={dataTableDefaultView}
135                     currentItemUuid={currentItemUuid}
136                     currentRoute={paperKey} />
137                 <Toolbar className={classes.footer}>
138                     <Grid container justify="flex-end">
139                         {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
140                             count={itemsAvailable}
141                             rowsPerPage={rowsPerPage}
142                             rowsPerPageOptions={rowsPerPageOptions}
143                             page={this.props.page}
144                             onChangePage={this.changePage}
145                             onChangeRowsPerPage={this.changeRowsPerPage}
146                             // Disable next button on empty lists since that's not default behavior
147                             nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
148                             component="div" /> : <Button
149                                 variant="text"
150                                 size="medium"
151                                 onClick={this.loadMore}
152                             >Load more</Button>}
153                     </Grid>
154                 </Toolbar>
155             </Paper>;
156         }
157
158         changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
159             this.props.onChangePage(page);
160         }
161
162         changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
163             this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
164         }
165
166         loadMore = () => {
167             this.props.onLoadMore(this.props.page + 1);
168         }
169
170         renderContextMenuTrigger = (item: T) =>
171             <Grid container justify="center">
172                 <Tooltip title="More options" disableFocusListener>
173                     <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
174                         <MoreOptionsIcon />
175                     </IconButton>
176                 </Tooltip>
177             </Grid>
178
179         contextMenuColumn: DataColumn<any> = {
180             name: "Actions",
181             selected: true,
182             configurable: false,
183             filters: createTree(),
184             key: "context-actions",
185             render: this.renderContextMenuTrigger
186         };
187     }
188 );