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