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>> {
97 if (this.props.onSetColumns) {
98 this.props.onSetColumns(this.props.columns);
103 columns, onContextMenu, onFiltersChange, onSortToggle, working, extractKey,
104 rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
105 items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
106 dataTableDefaultView, hideColumnSelector, actions, paperProps, hideSearchInput,
107 paperKey, fetchMode, currentItemUuid, title,
108 doHidePanel, doMaximizePanel, panelName, panelMaximized
110 return <Paper className={classes.root} {...paperProps} key={paperKey}>
111 <Grid container direction="column" wrap="nowrap" className={classes.container}>
112 {title && <Grid item xs className={classes.title}>{title}</Grid>}
113 {(!hideColumnSelector || !hideSearchInput) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
114 <Grid container justify="space-between" wrap="nowrap" alignItems="center">
115 <div className={classes.searchBox}>
116 {!hideSearchInput && <SearchInput
119 onSearch={onSearch} />}
122 {!hideColumnSelector && <ColumnSelector
124 onColumnToggle={onColumnToggle} />}
126 { doMaximizePanel && !!!panelMaximized &&
127 <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
128 <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
131 <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
132 <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
135 <Grid item xs="auto" className={classes.dataTable}><DataTable
136 columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
138 onRowClick={(_, item: T) => onRowClick(item)}
139 onContextMenu={onContextMenu}
140 onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
141 onFiltersChange={onFiltersChange}
142 onSortToggle={onSortToggle}
143 extractKey={extractKey}
145 defaultView={dataTableDefaultView}
146 currentItemUuid={currentItemUuid}
147 currentRoute={paperKey} /></Grid>
148 <Grid item xs><Toolbar className={classes.footer}>
149 <Grid container justify="flex-end">
150 {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
151 count={itemsAvailable}
152 rowsPerPage={rowsPerPage}
153 rowsPerPageOptions={rowsPerPageOptions}
154 page={this.props.page}
155 onChangePage={this.changePage}
156 onChangeRowsPerPage={this.changeRowsPerPage}
157 // Disable next button on empty lists since that's not default behavior
158 nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
159 component="div" /> : <Button
162 onClick={this.loadMore}
170 changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
171 this.props.onChangePage(page);
174 changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
175 this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
179 this.props.onLoadMore(this.props.page + 1);
182 renderContextMenuTrigger = (item: T) =>
183 <Grid container justify="center">
184 <Tooltip title="More options" disableFocusListener>
185 <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
191 contextMenuColumn: DataColumn<any> = {
195 filters: createTree(),
196 key: "context-actions",
197 render: this.renderContextMenuTrigger