Add onContextMenu props to breadcrumbs
[arvados-workbench2.git] / src / views / workbench / workbench.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 { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
7 import Drawer from '@material-ui/core/Drawer';
8 import { connect, DispatchProp } from "react-redux";
9 import { Route, Switch, RouteComponentProps, withRouter } from "react-router";
10 import authActions from "../../store/auth/auth-action";
11 import dataExplorerActions from "../../store/data-explorer/data-explorer-action";
12 import { User } from "../../models/user";
13 import { RootState } from "../../store/store";
14 import MainAppBar, {
15     MainAppBarActionProps,
16     MainAppBarMenuItem
17 } from '../../views-components/main-app-bar/main-app-bar';
18 import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
19 import { push } from 'react-router-redux';
20 import ProjectTree from '../../views-components/project-tree/project-tree';
21 import { TreeItem } from "../../components/tree/tree";
22 import { Project } from "../../models/project";
23 import { getTreePath, findTreeItem } from '../../store/project/project-reducer';
24 import sidePanelActions from '../../store/side-panel/side-panel-action';
25 import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel';
26 import { ResourceKind } from "../../models/resource";
27 import { ItemMode, setProjectItem } from "../../store/navigation/navigation-action";
28 import projectActions from "../../store/project/project-action";
29 import ProjectPanel from "../project-panel/project-panel";
30 import { sidePanelData } from '../../store/side-panel/side-panel-reducer';
31 import DetailsPanel from '../../views-components/details-panel/details-panel';
32 import { ArvadosTheme } from '../../common/custom-theme';
33 import ContextMenu from '../../components/context-menu/context-menu';
34 import { mockAnchorFromMouseEvent } from '../../components/popover/helpers';
35
36 const drawerWidth = 240;
37 const appBarHeight = 100;
38
39 type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar';
40
41 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
42     root: {
43         flexGrow: 1,
44         zIndex: 1,
45         overflow: 'hidden',
46         position: 'relative',
47         display: 'flex',
48         width: '100vw',
49         height: '100vh'
50     },
51     appBar: {
52         zIndex: theme.zIndex.drawer + 1,
53         position: "absolute",
54         width: "100%"
55     },
56     drawerPaper: {
57         position: 'relative',
58         width: drawerWidth,
59         display: 'flex',
60         flexDirection: 'column',
61     },
62     contentWrapper: {
63         backgroundColor: theme.palette.background.default,
64         display: "flex",
65         flexGrow: 1,
66         minWidth: 0,
67         paddingTop: appBarHeight
68     },
69     content: {
70         padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3}px`,
71         overflowY: "auto",
72         flexGrow: 1
73     },
74     toolbar: theme.mixins.toolbar
75 });
76
77 interface WorkbenchDataProps {
78     projects: Array<TreeItem<Project>>;
79     currentProjectId: string;
80     user?: User;
81     sidePanelItems: SidePanelItem[];
82 }
83
84 interface WorkbenchActionProps {
85 }
86
87 type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
88
89 interface NavBreadcrumb extends Breadcrumb {
90     itemId: string;
91 }
92
93 interface NavMenuItem extends MainAppBarMenuItem {
94     action: () => void;
95 }
96
97 interface WorkbenchState {
98     contextMenu: {
99         anchorEl?: HTMLElement;
100     };
101     isCreationDialogOpen: boolean;
102     anchorEl: any;
103     searchText: string;
104     menuItems: {
105         accountMenu: NavMenuItem[],
106         helpMenu: NavMenuItem[],
107         anonymousMenu: NavMenuItem[]
108     };
109     isDetailsPanelOpened: boolean;
110 }
111
112 class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
113     state = {
114         contextMenu: {
115             anchorEl: undefined
116         },
117         isCreationDialogOpen: false,
118         anchorEl: null,
119         searchText: "",
120         breadcrumbs: [],
121         menuItems: {
122             accountMenu: [
123                 {
124                     label: "Logout",
125                     action: () => this.props.dispatch(authActions.LOGOUT())
126                 },
127                 {
128                     label: "My account",
129                     action: () => this.props.dispatch(push("/my-account"))
130                 }
131             ],
132             helpMenu: [
133                 {
134                     label: "Help",
135                     action: () => this.props.dispatch(push("/help"))
136                 }
137             ],
138             anonymousMenu: [
139                 {
140                     label: "Sign in",
141                     action: () => this.props.dispatch(authActions.LOGIN())
142                 }
143             ]
144         },
145         isDetailsPanelOpened: false
146     };
147
148     mainAppBarActions: MainAppBarActionProps = {
149         onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
150             this.props.dispatch<any>(setProjectItem(itemId, ItemMode.BOTH));
151         },
152         onSearch: searchText => {
153             this.setState({ searchText });
154             this.props.dispatch(push(`/search?q=${searchText}`));
155         },
156         onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(),
157         onDetailsPanelToggle: () => {
158             this.setState(prev => ({ isDetailsPanelOpened: !prev.isDetailsPanelOpened }));
159         },
160         onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: Breadcrumb) => {
161             this.openContextMenu(event, breadcrumb);
162         }
163     };
164
165     toggleSidePanelOpen = (itemId: string) => {
166         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
167     }
168
169     toggleSidePanelActive = (itemId: string) => {
170         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
171         this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
172     }
173
174     handleCreationDialogOpen = () => {
175         this.setState({ isCreationDialogOpen: true });
176     }
177
178     handleCreationDialogClose = () => {
179         this.setState({ isCreationDialogOpen: false });
180     }
181
182     openContextMenu = (event: React.MouseEvent<HTMLElement>, item: any) => {
183         event.preventDefault();
184         this.setState({ contextMenu: { anchorEl: mockAnchorFromMouseEvent(event) } });
185         console.log(item);
186     }
187
188     closeContextMenu = () => {
189         this.setState({ contextMenu: {} });
190     }
191
192     render() {
193         const path = getTreePath(this.props.projects, this.props.currentProjectId);
194         const breadcrumbs = path.map(item => ({
195             label: item.data.name,
196             itemId: item.data.uuid,
197             status: item.status
198         }));
199
200         const { classes, user } = this.props;
201         return (
202             <div className={classes.root}>
203                 <div className={classes.appBar}>
204                     <MainAppBar
205                         breadcrumbs={breadcrumbs}
206                         searchText={this.state.searchText}
207                         user={this.props.user}
208                         menuItems={this.state.menuItems}
209                         {...this.mainAppBarActions}
210                     />
211                 </div>
212                 {user &&
213                     <Drawer
214                         variant="permanent"
215                         classes={{
216                             paper: classes.drawerPaper,
217                         }}>
218                         <div className={classes.toolbar} />
219                         <SidePanel
220                             toggleOpen={this.toggleSidePanelOpen}
221                             toggleActive={this.toggleSidePanelActive}
222                             sidePanelItems={this.props.sidePanelItems}
223                             handleCreationDialogOpen={this.handleCreationDialogOpen}
224                             handleCreationDialogClose={this.handleCreationDialogClose}>
225                             <ProjectTree
226                                 projects={this.props.projects}
227                                 toggleOpen={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.OPEN))}
228                                 toggleActive={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
229                                 handleCreationDialogOpen={this.handleCreationDialogOpen}
230                                 handleCreationDialogClose={this.handleCreationDialogClose}
231                             />
232                         </SidePanel>
233                     </Drawer>}
234                 <main className={classes.contentWrapper}>
235                     <div className={classes.content}>
236                         <Switch>
237                             <Route path="/projects/:id" render={this.renderProjectPanel} />
238                         </Switch>
239                     </div>
240                     <DetailsPanel
241                         isOpened={this.state.isDetailsPanelOpened}
242                         onCloseDrawer={this.mainAppBarActions.onDetailsPanelToggle} />
243                 </main>
244                 <ContextMenu
245                     anchorEl={this.state.contextMenu.anchorEl}
246                     actions={contextMenuActions}
247                     onActionClick={console.log}
248                     onClose={this.closeContextMenu} />
249             </div>
250         );
251     }
252
253     renderProjectPanel = (props: RouteComponentProps<{ id: string }>) => <ProjectPanel
254         onItemRouteChange={itemId => this.props.dispatch<any>(setProjectItem(itemId, ItemMode.ACTIVE))}
255         onItemClick={item => this.props.dispatch<any>(setProjectItem(item.uuid, ItemMode.ACTIVE))}
256         onContextMenu={this.openContextMenu}
257         handleCreationDialogOpen={this.handleCreationDialogOpen}
258         handleCreationDialogClose={this.handleCreationDialogClose}
259         isCreationDialogOpen={this.state.isCreationDialogOpen}
260         {...props} />
261
262 }
263
264 const contextMenuActions = [[{
265     icon: "fas fa-plus fa-fw",
266     name: "New project"
267 }, {
268     icon: "fas fa-users fa-fw",
269     name: "Share"
270 }, {
271     icon: "fas fa-sign-out-alt fa-fw",
272     name: "Move to"
273 }, {
274     icon: "fas fa-star fa-fw",
275     name: "Add to favourite"
276 }, {
277     icon: "fas fa-edit fa-fw",
278     name: "Rename"
279 }, {
280     icon: "fas fa-copy fa-fw",
281     name: "Make a copy"
282 }, {
283     icon: "fas fa-download fa-fw",
284     name: "Download"
285 }], [{
286     icon: "fas fa-trash-alt fa-fw",
287     name: "Remove"
288 }
289 ]];
290
291 export default connect<WorkbenchDataProps>(
292     (state: RootState) => ({
293         projects: state.projects.items,
294         currentProjectId: state.projects.currentItemId,
295         user: state.auth.user,
296         sidePanelItems: state.sidePanel
297     })
298 )(
299     withStyles(styles)(Workbench)
300 );