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