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 const dataCy = this.props["data-cy"];
114 return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={dataCy}>
115 <Grid container direction="column" wrap="nowrap" className={classes.container}>
116 {title && <Grid item xs className={classes.title}>{title}</Grid>}
117 {(!hideColumnSelector || !hideSearchInput || !!actions) && <Grid item xs><Toolbar className={title ? classes.toolbarUnderTitle : classes.toolbar}>
118 <Grid container justify="space-between" wrap="nowrap" alignItems="center">
119 {!hideSearchInput && <div className={classes.searchBox}>
120 {!hideSearchInput && <SearchInput
123 selfClearProp={currentItemUuid}
124 onSearch={onSearch} />}
127 {!hideColumnSelector && <ColumnSelector
129 onColumnToggle={onColumnToggle} />}
131 { doMaximizePanel && !panelMaximized &&
132 <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
133 <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
136 <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
137 <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
140 <Grid item xs="auto" className={classes.dataTable}><DataTable
141 columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
143 onRowClick={(_, item: T) => onRowClick(item)}
144 onContextMenu={onContextMenu}
145 onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
146 onFiltersChange={onFiltersChange}
147 onSortToggle={onSortToggle}
148 extractKey={extractKey}
150 defaultView={dataTableDefaultView}
151 currentItemUuid={currentItemUuid}
152 currentRoute={paperKey} /></Grid>
153 <Grid item xs><Toolbar className={classes.footer}>
154 <Grid container justify="flex-end">
155 {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
156 count={itemsAvailable}
157 rowsPerPage={rowsPerPage}
158 rowsPerPageOptions={rowsPerPageOptions}
159 page={this.props.page}
160 onChangePage={this.changePage}
161 onChangeRowsPerPage={this.changeRowsPerPage}
162 // Disable next button on empty lists since that's not default behavior
163 nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
164 component="div" /> : <Button
167 onClick={this.loadMore}
175 changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
176 this.props.onChangePage(page);
179 changeRowsPerPage: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement> = (event) => {
180 this.props.onChangeRowsPerPage(parseInt(event.target.value, 10));
184 this.props.onLoadMore(this.props.page + 1);
187 renderContextMenuTrigger = (item: T) =>
188 <Grid container justify="center">
189 <Tooltip title="More options" disableFocusListener>
190 <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
196 contextMenuColumn: DataColumn<any> = {
200 filters: createTree(),
201 key: "context-actions",
202 render: this.renderContextMenuTrigger