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 { CloseIcon, MaximizeIcon, MoreOptionsIcon } from 'components/icon/icon';
15 import { PaperProps } from '@material-ui/core/Paper';
16 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
18 type CssRules = 'searchBox' | "toolbar" | "toolbarUnderTitle" | "footer" | "root" | 'moreOptionsButton' | 'title';
20 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
22 paddingBottom: theme.spacing.unit * 2
25 paddingTop: theme.spacing.unit,
26 paddingRight: theme.spacing.unit * 2,
41 paddingLeft: theme.spacing.unit * 3,
42 paddingTop: theme.spacing.unit * 3,
47 interface DataExplorerDataProps<T> {
48 fetchMode: DataTableFetchMode;
50 itemsAvailable: number;
51 columns: DataColumns<T>;
55 rowsPerPageOptions: number[];
57 contextMenuColumn: boolean;
58 dataTableDefaultView?: React.ReactNode;
60 hideColumnSelector?: boolean;
61 paperProps?: PaperProps;
62 actions?: React.ReactNode;
63 hideSearchInput?: boolean;
64 title?: React.ReactNode;
66 currentItemUuid: string;
69 interface DataExplorerActionProps<T> {
70 onSetColumns: (columns: DataColumns<T>) => void;
71 onSearch: (value: string) => void;
72 onRowClick: (item: T) => void;
73 onRowDoubleClick: (item: T) => void;
74 onColumnToggle: (column: DataColumn<T>) => void;
75 onContextMenu: (event: React.MouseEvent<HTMLElement>, item: T) => void;
76 onSortToggle: (column: DataColumn<T>) => void;
77 onFiltersChange: (filters: DataTableFilters, column: DataColumn<T>) => void;
78 onChangePage: (page: number) => void;
79 onChangeRowsPerPage: (rowsPerPage: number) => void;
80 onLoadMore: (page: number) => void;
81 extractKey?: (item: T) => React.Key;
84 type DataExplorerProps<T> = DataExplorerDataProps<T> &
85 DataExplorerActionProps<T> & WithStyles<CssRules> & MPVPanelProps;
87 export const DataExplorer = withStyles(styles)(
88 class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
90 if (this.props.onSetColumns) {
91 this.props.onSetColumns(this.props.columns);
96 columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
97 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
98 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
99 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
100 paperKey, fetchMode, currentItemUuid, title,
101 doHidePanel, doMaximizePanel, panelName, panelMaximized
103 return <Paper className={classes.root} {...paperProps} key={paperKey}>
104 {title && <div className={classes.title}>{title}</div>}
105 {(!hideColumnSelector || !hideSearchInput) && <Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
106 <Grid container justify="space-between" wrap="nowrap" alignItems="center">
107 <div className={classes.searchBox}>
108 {!hideSearchInput && <SearchInput
111 onSearch={onSearch} />}
114 {!hideColumnSelector && <ColumnSelector
116 onColumnToggle={onColumnToggle} />}
118 { doMaximizePanel && !!!panelMaximized &&
119 <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
120 <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
123 <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
124 <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
128 columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
130 onRowClick={(_, item: T) => onRowClick(item)}
131 onContextMenu={onContextMenu}
132 onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
133 onFiltersChange={onFiltersChange}
134 onSortToggle={onSortToggle}
135 extractKey={extractKey}
137 defaultView={dataTableDefaultView}
138 currentItemUuid={currentItemUuid}
139 currentRoute={paperKey} />
140 <Toolbar className={classes.footer}>
141 <Grid container justify="flex-end">
142 {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
143 count={itemsAvailable}
144 rowsPerPage={rowsPerPage}
145 rowsPerPageOptions={rowsPerPageOptions}
146 page={this.props.page}
147 onChangePage={this.changePage}
148 onChangeRowsPerPage={this.changeRowsPerPage}
149 // Disable next button on empty lists since that's not default behavior
150 nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
151 component="div" /> : <Button
154 onClick={this.loadMore}
161 changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
162 this.props.onChangePage(page);
165 changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
166 this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
170 this.props.onLoadMore(this.props.page + 1);
173 renderContextMenuTrigger = (item: T) =>
174 <Grid container justify="center">
175 <Tooltip title="More options" disableFocusListener>
176 <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
182 contextMenuColumn: DataColumn<any> = {
186 filters: createTree(),
187 key: "context-actions",
188 render: this.renderContextMenuTrigger