merge-conflicts
[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, Theme, 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 } from "react-router";
10 import authActions from "../../store/auth/auth-action";
11 import { User } from "../../models/user";
12 import { RootState } from "../../store/store";
13 import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar';
14 import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs';
15 import { push } from 'react-router-redux';
16 import projectActions, { getProjectList } from "../../store/project/project-action";
17 import ProjectTree from '../../views-components/project-tree/project-tree';
18 import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
19 import { Project } from "../../models/project";
20 import { getTreePath } from '../../store/project/project-reducer';
21 import ProjectPanel from '../project-panel/project-panel';
22 import sidePanelActions from '../../store/side-panel/side-panel-action';
23 import { projectService } from '../../services/services';
24 import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel';
25
26 const drawerWidth = 240;
27 const appBarHeight = 102;
28
29 type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar';
30
31 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
32     root: {
33         flexGrow: 1,
34         zIndex: 1,
35         overflow: 'hidden',
36         position: 'relative',
37         display: 'flex',
38         width: '100vw',
39         height: '100vh'
40     },
41     appBar: {
42         zIndex: theme.zIndex.drawer + 1,
43         backgroundColor: '#692498',
44         position: "absolute",
45         width: "100%"
46     },
47     drawerPaper: {
48         position: 'relative',
49         width: drawerWidth,
50         display: 'flex',
51         flexDirection: 'column',
52     },
53     contentWrapper: {
54         backgroundColor: theme.palette.background.default,
55         display: "flex",
56         flexGrow: 1,
57         minWidth: 0,
58         paddingTop: appBarHeight
59     },
60     content: {
61         padding: theme.spacing.unit * 3,
62         overflowY: "auto",
63         flexGrow: 1
64     },
65     toolbar: theme.mixins.toolbar
66 });
67
68 interface WorkbenchDataProps {
69     projects: Array<TreeItem<Project>>;
70     user?: User;
71     sidePanelItems: SidePanelItem[];
72 }
73
74 interface WorkbenchActionProps {
75 }
76
77 type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
78
79 interface NavBreadcrumb extends Breadcrumb {
80     itemId: string;
81     status: TreeItemStatus;
82 }
83
84 interface NavMenuItem extends MainAppBarMenuItem {
85     action: () => void;
86 }
87
88 interface WorkbenchState {
89     anchorEl: any;
90     breadcrumbs: NavBreadcrumb[];
91     searchText: string;
92     menuItems: {
93         accountMenu: NavMenuItem[],
94         helpMenu: NavMenuItem[],
95         anonymousMenu: NavMenuItem[]
96     };
97 }
98
99 class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
100     state = {
101         anchorEl: null,
102         searchText: "",
103         breadcrumbs: [],
104         menuItems: {
105             accountMenu: [
106                 {
107                     label: "Logout",
108                     action: () => this.props.dispatch(authActions.LOGOUT())
109                 },
110                 {
111                     label: "My account",
112                     action: () => this.props.dispatch(push("/my-account"))
113                 }
114             ],
115             helpMenu: [
116                 {
117                     label: "Help",
118                     action: () => this.props.dispatch(push("/help"))
119                 }
120             ],
121             anonymousMenu: [
122                 {
123                     label: "Sign in",
124                     action: () => this.props.dispatch(authActions.LOGIN())
125                 }
126             ]
127         }
128     };
129
130
131     mainAppBarActions: MainAppBarActionProps = {
132         onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => {
133             this.toggleProjectTreeItemOpen(itemId, status);
134         },
135         onSearch: searchText => {
136             this.setState({ searchText });
137             this.props.dispatch(push(`/search?q=${searchText}`));
138         },
139         onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action()
140     };
141
142     toggleProjectTreeItemOpen = (itemId: string, status: TreeItemStatus) => {
143         if (status === TreeItemStatus.Loaded) {
144             this.openProjectItem(itemId);
145             this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId));
146             this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
147         } else {
148             this.props.dispatch<any>(getProjectList(itemId))
149                 .then(() => {
150                     this.openProjectItem(itemId);
151                     this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId));
152                     this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
153                 });
154         }
155     }
156
157     toggleProjectTreeItemActive = (itemId: string, status: TreeItemStatus) => {
158         if (status === TreeItemStatus.Loaded) {
159             this.openProjectItem(itemId);
160             this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
161             this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId));
162         } else {
163             this.props.dispatch<any>(getProjectList(itemId))
164                 .then(() => {
165                     this.openProjectItem(itemId);
166                     this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
167                     this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId));
168                 });
169         }
170     }
171
172     toggleSidePanelOpen = (itemId: string) => {
173         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId));
174     }
175
176     toggleSidePanelActive = (itemId: string) => {
177         this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId));
178         this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId));
179     }
180
181     openProjectItem = (itemId: string) => {
182         const branch = getTreePath(this.props.projects, itemId);
183         this.setState({
184             breadcrumbs: branch.map(item => ({
185                 label: item.data.name,
186                 itemId: item.data.uuid,
187                 status: item.status
188             }))
189         });
190         this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId));
191         this.props.dispatch(push(`/project/${itemId}`));
192     }
193
194     render() {
195         const { classes, user, projects, sidePanelItems } = this.props;
196         return (
197             <div className={classes.root}>
198                 <div className={classes.appBar}>
199                     <MainAppBar
200                         breadcrumbs={this.state.breadcrumbs}
201                         searchText={this.state.searchText}
202                         user={this.props.user}
203                         menuItems={this.state.menuItems}
204                         {...this.mainAppBarActions}
205                     />
206                 </div>
207                 {user &&
208                     <Drawer
209                         variant="permanent"
210                         classes={{
211                             paper: classes.drawerPaper,
212                         }}>
213                         <div className={classes.toolbar} />
214                         <SidePanel
215                             toggleOpen={this.toggleSidePanelOpen}
216                             toggleActive={this.toggleSidePanelActive}
217                             sidePanelItems={sidePanelItems}>
218                             <ProjectTree
219                                 projects={projects}
220                                 toggleOpen={this.toggleProjectTreeItemOpen}
221                                 toggleActive={this.toggleProjectTreeItemActive} />
222                         </SidePanel>
223                     </Drawer>}
224                 <main className={classes.contentWrapper}>
225                     <div className={classes.content}>
226                         <Switch>
227                             <Route path="/project/:name" component={ProjectPanel} />
228                         </Switch>
229                     </div>
230                 </main>
231             </div>
232         );
233     }
234 }
235
236 export default connect<WorkbenchDataProps>(
237     (state: RootState) => ({
238         projects: state.projects,
239         user: state.auth.user,
240         sidePanelItems: state.sidePanel,
241     })
242 )(
243     withStyles(styles)(Workbench)
244 );