1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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';
17 type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
19 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
21 paddingBottom: theme.spacing.unit * 2
24 paddingTop: theme.spacing.unit * 2
39 paddingLeft: theme.spacing.unit * 3,
40 paddingTop: theme.spacing.unit * 3,
45 interface DataExplorerDataProps<T> {
46 fetchMode: DataTableFetchMode;
48 itemsAvailable: number;
49 columns: DataColumns<T>;
53 rowsPerPageOptions: number[];
55 contextMenuColumn: boolean;
56 dataTableDefaultView?: React.ReactNode;
58 hideColumnSelector?: boolean;
59 paperProps?: PaperProps;
60 actions?: React.ReactNode;
61 hideSearchInput?: boolean;
62 title?: React.ReactNode;
64 currentItemUuid: string;
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;
82 type DataExplorerProps<T> = DataExplorerDataProps<T> & DataExplorerActionProps<T> & WithStyles<CssRules>;
84 export const DataExplorer = withStyles(styles)(
85 class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>, { currentItemUuid: string }> {
89 currentItemUuid: props.currentItemUuid
93 if (this.props.onSetColumns) {
94 this.props.onSetColumns(this.props.columns);
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
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
115 selfClearProp={currentItemUuid}
116 onSearch={onSearch} />}
119 {!hideColumnSelector && <ColumnSelector
121 onColumnToggle={onColumnToggle} />}
125 columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
127 onRowClick={(_, item: T) => onRowClick(item)}
128 onContextMenu={onContextMenu}
129 onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
130 onFiltersChange={onFiltersChange}
131 onSortToggle={onSortToggle}
132 extractKey={extractKey}
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
151 onClick={this.loadMore}
158 changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
159 this.props.onChangePage(page);
162 changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
163 this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
167 this.props.onLoadMore(this.props.page + 1);
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)}>
179 contextMenuColumn: DataColumn<any> = {
183 filters: createTree(),
184 key: "context-actions",
185 render: this.renderContextMenuTrigger