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