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' | 'dataTable' | 'container';
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,
54 interface DataExplorerDataProps<T> {
55 fetchMode: DataTableFetchMode;
57 itemsAvailable: number;
58 columns: DataColumns<T>;
62 rowsPerPageOptions: number[];
64 contextMenuColumn: boolean;
65 dataTableDefaultView?: React.ReactNode;
67 hideColumnSelector?: boolean;
68 paperProps?: PaperProps;
69 actions?: React.ReactNode;
70 hideSearchInput?: boolean;
71 title?: React.ReactNode;
73 currentItemUuid: string;
76 interface DataExplorerActionProps<T> {
77 onSetColumns: (columns: DataColumns<T>) => void;
78 onSearch: (value: string) => void;
79 onRowClick: (item: T) => void;
80 onRowDoubleClick: (item: T) => void;
81 onColumnToggle: (column: DataColumn<T>) => void;
82 onContextMenu: (event: React.MouseEvent<HTMLElement>, item: T) => void;
83 onSortToggle: (column: DataColumn<T>) => void;
84 onFiltersChange: (filters: DataTableFilters, column: DataColumn<T>) => void;
85 onChangePage: (page: number) => void;
86 onChangeRowsPerPage: (rowsPerPage: number) => void;
87 onLoadMore: (page: number) => void;
88 extractKey?: (item: T) => React.Key;
91 type DataExplorerProps<T> = DataExplorerDataProps<T> &
92 DataExplorerActionProps<T> & WithStyles<CssRules> & MPVPanelProps;
94 export const DataExplorer = withStyles(styles)(
95 class DataExplorerGeneric<T> extends React.Component<DataExplorerProps<T>> {
98 if (this.props.onSetColumns) {
99 this.props.onSetColumns(this.props.columns);
105 columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
106 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
107 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
108 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
109 paperKey, fetchMode, currentItemUuid, title,
110 doHidePanel, doMaximizePanel, panelName, panelMaximized
113 return <Paper className={classes.root} {...paperProps} key={paperKey}>
114 <Grid container direction="column" wrap="nowrap" className={classes.container}>
115 {title && <Grid item xs className={classes.title}>{title}</Grid>}
116 {(!hideColumnSelector || !hideSearchInput) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
117 <Grid container justify="space-between" wrap="nowrap" alignItems="center">
118 <div className={classes.searchBox}>
119 {!hideSearchInput && <SearchInput
122 selfClearProp={currentItemUuid}
123 onSearch={onSearch} />}
126 {!hideColumnSelector && <ColumnSelector
128 onColumnToggle={onColumnToggle} />}
130 { doMaximizePanel && !panelMaximized &&
131 <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
132 <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
135 <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
136 <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
139 <Grid item xs="auto" className={classes.dataTable}><DataTable
140 columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
142 onRowClick={(_, item: T) => onRowClick(item)}
143 onContextMenu={onContextMenu}
144 onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
145 onFiltersChange={onFiltersChange}
146 onSortToggle={onSortToggle}
147 extractKey={extractKey}
149 defaultView={dataTableDefaultView}
150 currentItemUuid={currentItemUuid}
151 currentRoute={paperKey} /></Grid>
152 <Grid item xs><Toolbar className={classes.footer}>
153 <Grid container justify="flex-end">
154 {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
155 count={itemsAvailable}
156 rowsPerPage={rowsPerPage}
157 rowsPerPageOptions={rowsPerPageOptions}
158 page={this.props.page}
159 onChangePage={this.changePage}
160 onChangeRowsPerPage={this.changeRowsPerPage}
161 // Disable next button on empty lists since that's not default behavior
162 nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
163 component="div" /> : <Button
166 onClick={this.loadMore}
174 changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
175 this.props.onChangePage(page);
178 changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
179 this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
183 this.props.onLoadMore(this.props.page + 1);
186 renderContextMenuTrigger = (item: T) =>
187 <Grid container justify="center">
188 <Tooltip title="More options" disableFocusListener>
189 <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
195 contextMenuColumn: DataColumn<any> = {
199 filters: createTree(),
200 key: "context-actions",
201 render: this.renderContextMenuTrigger