// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Table, TableBody, TableRow, TableCell, TableHead, StyleRulesCallback, Theme, WithStyles, withStyles, Typography, Grid } from '@material-ui/core';
-import { Column } from './column';
-import ColumnsConfigurator from "./columns-configurator/columns-configurator";
-
-export interface DataExplorerProps<T> {
- items: T[];
- columns: Array<Column<T>>;
- onColumnToggle: (column: Column<T>) => void;
- onItemClick?: (item: T) => void;
+import { DataTable, DataColumn, ColumnSelector } from "../../components/data-table";
+import { Typography, Grid, Paper, Toolbar } from '@material-ui/core';
+import IconButton from '@material-ui/core/IconButton';
+import MoreVertIcon from "@material-ui/icons/MoreVert";
+import { formatFileSize, formatDate } from '../../common/formatters';
+import { DataItem } from './data-item';
+import { mockAnchorFromMouseEvent } from '../popover/helpers';
+import ContextMenu from '../context-menu/context-menu';
+
+export interface DataExplorerContextActions {
+ onAddToFavourite: (dataIitem: DataItem) => void;
+ onCopy: (dataIitem: DataItem) => void;
+ onDownload: (dataIitem: DataItem) => void;
+ onMoveTo: (dataIitem: DataItem) => void;
+ onRemove: (dataIitem: DataItem) => void;
+ onRename: (dataIitem: DataItem) => void;
+ onShare: (dataIitem: DataItem) => void;
+}
+interface DataExplorerProps {
+ items: DataItem[];
+ onItemClick: (item: DataItem) => void;
+ contextActions: DataExplorerContextActions;
+}
+
+interface DataExplorerState {
+ columns: Array<DataColumn<DataItem>>;
+ contextMenu: {
+ anchorEl?: HTMLElement;
+ item?: DataItem;
+ };
}
-class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<CssRules>> {
+class DataExplorer extends React.Component<DataExplorerProps, DataExplorerState> {
+ state: DataExplorerState = {
+ contextMenu: {},
+ columns: [{
+ name: "Name",
+ selected: true,
+ render: item => this.renderName(item)
+ }, {
+ name: "Status",
+ selected: true,
+ render: item => renderStatus(item.status)
+ }, {
+ name: "Type",
+ selected: true,
+ render: item => renderType(item.type)
+ }, {
+ name: "Owner",
+ selected: true,
+ render: item => renderOwner(item.owner)
+ }, {
+ name: "File size",
+ selected: true,
+ render: item => renderFileSize(item.fileSize)
+ }, {
+ name: "Last modified",
+ selected: true,
+ render: item => renderDate(item.lastModified)
+ }, {
+ name: "Actions",
+ selected: true,
+ configurable: false,
+ renderHeader: () => null,
+ render: item => this.renderActions(item)
+ }]
+ };
+
+ contextMenuActions = [[{
+ icon: "fas fa-users fa-fw",
+ name: "Share",
+ onClick: this.handleContextAction("onShare")
+ }, {
+ icon: "fas fa-sign-out-alt fa-fw",
+ name: "Move to",
+ onClick: this.handleContextAction("onMoveTo")
+ }, {
+ icon: "fas fa-star fa-fw",
+ name: "Add to favourite",
+ onClick: this.handleContextAction("onAddToFavourite")
+ }, {
+ icon: "fas fa-edit fa-fw",
+ name: "Rename",
+ onClick: this.handleContextAction("onRename")
+ }, {
+ icon: "fas fa-copy fa-fw",
+ name: "Make a copy",
+ onClick: this.handleContextAction("onCopy")
+ }, {
+ icon: "fas fa-download fa-fw",
+ name: "Download",
+ onClick: this.handleContextAction("onDownload")
+ }], [{
+ icon: "fas fa-trash-alt fa-fw",
+ name: "Remove",
+ onClick: this.handleContextAction("onRemove")
+ }
+ ]];
+
render() {
- const { items, columns, classes, onItemClick, onColumnToggle } = this.props;
- return (
- <div>
+ return <Paper>
+ <ContextMenu
+ {...this.state.contextMenu}
+ actions={this.contextMenuActions}
+ onClose={this.closeContextMenu} />
+ <Toolbar>
<Grid container justify="flex-end">
- <ColumnsConfigurator {...{ columns, onColumnToggle }} />
+ <ColumnSelector
+ columns={this.state.columns}
+ onColumnToggle={this.toggleColumn} />
</Grid>
- <div className={classes.tableContainer}>
- {
- items.length > 0 ? (
- <Table>
- <TableHead>
- <TableRow>
- {
- columns.filter(column => column.selected).map(({ header, renderHeader }, index) => (
- <TableCell key={index}>
- {renderHeader ? renderHeader() : header}
- </TableCell>
- ))
- }
- </TableRow>
- </TableHead>
- <TableBody className={classes.tableBody}>
- {
- items.map((item, index) => (
- <TableRow key={index} hover onClick={() => onItemClick && onItemClick(item)}>
- {
- columns.filter(column => column.selected).map((column, index) => (
- <TableCell key={index}>
- {column.render(item)}
- </TableCell>
- ))
- }
- </TableRow>
- ))
- }
- </TableBody>
- </Table>
- ) : (
- <Typography>No items</Typography>
- )
- }
-
- </div>
- </div>
- );
+ </Toolbar>
+ <DataTable
+ columns={this.state.columns}
+ items={this.props.items}
+ onRowContextMenu={this.openItemMenuOnRowClick} />
+ <Toolbar />
+ </Paper>;
+ }
+
+ toggleColumn = (column: DataColumn<DataItem>) => {
+ const index = this.state.columns.indexOf(column);
+ const columns = this.state.columns.slice(0);
+ columns.splice(index, 1, { ...column, selected: !column.selected });
+ this.setState({ columns });
+ }
+
+ renderName = (item: DataItem) =>
+ <Grid
+ container
+ alignItems="center"
+ wrap="nowrap"
+ spacing={16}
+ onClick={() => this.props.onItemClick(item)}>
+ <Grid item>
+ {renderIcon(item)}
+ </Grid>
+ <Grid item>
+ <Typography color="primary">
+ {item.name}
+ </Typography>
+ </Grid>
+ </Grid>
+
+ renderActions = (item: DataItem) =>
+ <Grid container justify="flex-end">
+ <IconButton onClick={event => this.openItemMenuOnActionsClick(event, item)}>
+ <MoreVertIcon />
+ </IconButton>
+ </Grid>
+
+ openItemMenuOnRowClick = (event: React.MouseEvent<HTMLElement>, item: DataItem) => {
+ event.preventDefault();
+ this.setState({
+ contextMenu: {
+ anchorEl: mockAnchorFromMouseEvent(event),
+ item
+ }
+ });
+ }
+
+ openItemMenuOnActionsClick = (event: React.MouseEvent<HTMLElement>, item: DataItem) => {
+ this.setState({
+ contextMenu: {
+ anchorEl: event.currentTarget,
+ item
+ }
+ });
}
-}
-type CssRules = "tableBody" | "tableContainer";
+ closeContextMenu = () => {
+ this.setState({ contextMenu: {} });
+ }
-const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
- tableContainer: {
- overflowX: 'auto'
- },
- tableBody: {
- background: theme.palette.background.paper
+ handleContextAction(action: keyof DataExplorerContextActions) {
+ return (item: DataItem) => {
+ this.closeContextMenu();
+ this.props.contextActions[action](item);
+ };
}
-});
-export default withStyles(styles)(DataExplorer);
+}
+
+const renderIcon = (dataItem: DataItem) => {
+ switch (dataItem.type) {
+ case "arvados#group":
+ return <i className="fas fa-folder fa-lg" />;
+ case "arvados#groupList":
+ return <i className="fas fa-th fa-lg" />;
+ default:
+ return <i />;
+ }
+};
+
+const renderDate = (date: string) =>
+ <Typography noWrap>
+ {formatDate(date)}
+ </Typography>;
+
+const renderFileSize = (fileSize?: number) =>
+ <Typography noWrap>
+ {formatFileSize(fileSize)}
+ </Typography>;
+
+const renderOwner = (owner: string) =>
+ <Typography noWrap color="primary">
+ {owner}
+ </Typography>;
+
+const renderType = (type: string) =>
+ <Typography noWrap>
+ {type}
+ </Typography>;
+
+const renderStatus = (status?: string) =>
+ <Typography noWrap align="center">
+ {status || "-"}
+ </Typography>;
+
+export default DataExplorer;