Create data table filter component
[arvados.git] / src / views-components / data-explorer / data-explorer.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from 'react';
6 import { Typography, Grid, Paper, Toolbar } from '@material-ui/core';
7 import IconButton from '@material-ui/core/IconButton';
8 import MoreVertIcon from "@material-ui/icons/MoreVert";
9 import { formatFileSize, formatDate } from '../../common/formatters';
10 import { DataItem } from './data-item';
11 import ContextMenu from "../../components/context-menu/context-menu";
12 import ColumnSelector from "../../components/column-selector/column-selector";
13 import DataTable from "../../components/data-table/data-table";
14 import { mockAnchorFromMouseEvent } from "../../components/popover/helpers";
15 import { DataColumn, toggleSortDirection, resetSortDirection } from "../../components/data-table/data-column";
16
17 export interface DataExplorerContextActions {
18     onAddToFavourite: (dataIitem: DataItem) => void;
19     onCopy: (dataIitem: DataItem) => void;
20     onDownload: (dataIitem: DataItem) => void;
21     onMoveTo: (dataIitem: DataItem) => void;
22     onRemove: (dataIitem: DataItem) => void;
23     onRename: (dataIitem: DataItem) => void;
24     onShare: (dataIitem: DataItem) => void;
25 }
26 interface DataExplorerProps {
27     items: DataItem[];
28     onItemClick: (item: DataItem) => void;
29     contextActions: DataExplorerContextActions;
30 }
31
32 interface DataExplorerState {
33     columns: Array<DataColumn<DataItem>>;
34     contextMenu: {
35         anchorEl?: HTMLElement;
36         item?: DataItem;
37     };
38 }
39
40 class DataExplorer extends React.Component<DataExplorerProps, DataExplorerState> {
41     state: DataExplorerState = {
42         contextMenu: {},
43         columns: [{
44             name: "Name",
45             selected: true,
46             sortDirection: "asc",
47             onSortToggle: () => this.toggleSort("Name"),
48             render: item => this.renderName(item)
49         }, {
50             name: "Status",
51             selected: true,
52             onFiltersChange: console.log,
53             filters: [{
54                 name: "In progress", 
55                 selected: true
56             }, {
57                 name: "Complete",
58                 selected: true
59             }],
60             render: item => renderStatus(item.status)
61         }, {
62             name: "Type",
63             selected: true,
64             onFiltersChange: console.log,
65             filters: [{
66                 name: "Collection", 
67                 selected: true
68             }, {
69                 name: "Group",
70                 selected: true
71             }],
72             render: item => renderType(item.type)
73         }, {
74             name: "Owner",
75             selected: true,
76             render: item => renderOwner(item.owner)
77         }, {
78             name: "File size",
79             selected: true,
80             render: item => renderFileSize(item.fileSize)
81         }, {
82             name: "Last modified",
83             selected: true,
84             onSortToggle: () => this.toggleSort("Last modified"),
85             render: item => renderDate(item.lastModified)
86         }, {
87             name: "Actions",
88             selected: true,
89             configurable: false,
90             renderHeader: () => null,
91             render: item => this.renderActions(item)
92         }]
93     };
94
95     contextMenuActions = [[{
96         icon: "fas fa-users fa-fw",
97         name: "Share",
98         onClick: this.handleContextAction("onShare")
99     }, {
100         icon: "fas fa-sign-out-alt fa-fw",
101         name: "Move to",
102         onClick: this.handleContextAction("onMoveTo")
103     }, {
104         icon: "fas fa-star fa-fw",
105         name: "Add to favourite",
106         onClick: this.handleContextAction("onAddToFavourite")
107     }, {
108         icon: "fas fa-edit fa-fw",
109         name: "Rename",
110         onClick: this.handleContextAction("onRename")
111     }, {
112         icon: "fas fa-copy fa-fw",
113         name: "Make a copy",
114         onClick: this.handleContextAction("onCopy")
115     }, {
116         icon: "fas fa-download fa-fw",
117         name: "Download",
118         onClick: this.handleContextAction("onDownload")
119     }], [{
120         icon: "fas fa-trash-alt fa-fw",
121         name: "Remove",
122         onClick: this.handleContextAction("onRemove")
123     }
124     ]];
125
126     render() {
127         return <Paper>
128             <ContextMenu
129                 {...this.state.contextMenu}
130                 actions={this.contextMenuActions}
131                 onClose={this.closeContextMenu} />
132             <Toolbar>
133                 <Grid container justify="flex-end">
134                     <ColumnSelector
135                         columns={this.state.columns}
136                         onColumnToggle={this.toggleColumn} />
137                 </Grid>
138             </Toolbar>
139             <DataTable
140                 columns={this.state.columns}
141                 items={this.props.items}
142                 onRowContextMenu={this.openItemMenuOnRowClick} />
143             <Toolbar />
144         </Paper>;
145     }
146
147     toggleColumn = (column: DataColumn<DataItem>) => {
148         const index = this.state.columns.indexOf(column);
149         const columns = this.state.columns.slice(0);
150         columns.splice(index, 1, { ...column, selected: !column.selected });
151         this.setState({ columns });
152     }
153
154     renderName = (item: DataItem) =>
155         <Grid
156             container
157             alignItems="center"
158             wrap="nowrap"
159             spacing={16}
160             onClick={() => this.props.onItemClick(item)}>
161             <Grid item>
162                 {renderIcon(item)}
163             </Grid>
164             <Grid item>
165                 <Typography color="primary">
166                     {item.name}
167                 </Typography>
168             </Grid>
169         </Grid>
170
171     renderActions = (item: DataItem) =>
172         <Grid container justify="flex-end">
173             <IconButton onClick={event => this.openItemMenuOnActionsClick(event, item)}>
174                 <MoreVertIcon />
175             </IconButton>
176         </Grid>
177
178     openItemMenuOnRowClick = (event: React.MouseEvent<HTMLElement>, item: DataItem) => {
179         event.preventDefault();
180         this.setState({
181             contextMenu: {
182                 anchorEl: mockAnchorFromMouseEvent(event),
183                 item
184             }
185         });
186     }
187
188     openItemMenuOnActionsClick = (event: React.MouseEvent<HTMLElement>, item: DataItem) => {
189         this.setState({
190             contextMenu: {
191                 anchorEl: event.currentTarget,
192                 item
193             }
194         });
195     }
196
197     closeContextMenu = () => {
198         this.setState({ contextMenu: {} });
199     }
200
201     handleContextAction(action: keyof DataExplorerContextActions) {
202         return (item: DataItem) => {
203             this.closeContextMenu();
204             this.props.contextActions[action](item);
205         };
206     }
207
208     toggleSort = (columnName: string) => {
209         this.setState({
210             columns: this.state.columns.map((column, index) =>
211                 column.name === columnName ? toggleSortDirection(column) : resetSortDirection(column))
212         });
213     }
214
215 }
216
217 const renderIcon = (dataItem: DataItem) => {
218     switch (dataItem.type) {
219         case "arvados#group":
220             return <i className="fas fa-folder fa-lg" />;
221         case "arvados#groupList":
222             return <i className="fas fa-th fa-lg" />;
223         default:
224             return <i />;
225     }
226 };
227
228 const renderDate = (date: string) =>
229     <Typography noWrap>
230         {formatDate(date)}
231     </Typography>;
232
233 const renderFileSize = (fileSize?: number) =>
234     <Typography noWrap>
235         {formatFileSize(fileSize)}
236     </Typography>;
237
238 const renderOwner = (owner: string) =>
239     <Typography noWrap color="primary">
240         {owner}
241     </Typography>;
242
243 const renderType = (type: string) =>
244     <Typography noWrap>
245         {type}
246     </Typography>;
247
248 const renderStatus = (status?: string) =>
249     <Typography noWrap align="center">
250         {status || "-"}
251     </Typography>;
252
253 export default DataExplorer;