a3ee07ee63758978df4cf38d133b015c7fed8632
[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 { connect, DispatchProp } from "react-redux";
8 import { Route, Switch } from "react-router";
9 import { login, logout } from "~/store/auth/auth-action";
10 import { User } from "~/models/user";
11 import { RootState } from "~/store/store";
12 import { MainAppBar, MainAppBarActionProps, MainAppBarMenuItem } from '~/views-components/main-app-bar/main-app-bar';
13 import { Breadcrumb } from '~/components/breadcrumbs/breadcrumbs';
14 import { push } from 'react-router-redux';
15 import { TreeItem } from "~/components/tree/tree";
16 import { ProjectPanel } from "~/views/project-panel/project-panel";
17 import { DetailsPanel } from '~/views-components/details-panel/details-panel';
18 import { ArvadosTheme } from '~/common/custom-theme';
19 import { CreateProjectDialog } from "~/views-components/create-project-dialog/create-project-dialog";
20 import { detailsPanelActions } from "~/store/details-panel/details-panel-action";
21 import { openContextMenu } from '~/store/context-menu/context-menu-actions';
22 import { ProjectResource } from '~/models/project';
23 import { ContextMenu, ContextMenuKind } from "~/views-components/context-menu/context-menu";
24 import { FavoritePanel } from "../favorite-panel/favorite-panel";
25 import { CurrentTokenDialog } from '~/views-components/current-token-dialog/current-token-dialog';
26 import { Snackbar } from '~/views-components/snackbar/snackbar';
27 import { CreateCollectionDialog } from '~/views-components/create-collection-dialog/create-collection-dialog';
28 import { CollectionPanel } from '../collection-panel/collection-panel';
29 import { UpdateCollectionDialog } from '~/views-components/update-collection-dialog/update-collection-dialog.';
30 import { UpdateProjectDialog } from '~/views-components/update-project-dialog/update-project-dialog';
31 import { AuthService } from "~/services/auth-service/auth-service";
32 import { RenameFileDialog } from '~/views-components/rename-file-dialog/rename-file-dialog';
33 import { FileRemoveDialog } from '~/views-components/file-remove-dialog/file-remove-dialog';
34 import { MultipleFilesRemoveDialog } from '~/views-components/file-remove-dialog/multiple-files-remove-dialog';
35 import { DialogCollectionCreateWithSelectedFile } from '~/views-components/create-collection-dialog-with-selected/create-collection-dialog-with-selected';
36 import { UploadCollectionFilesDialog } from '~/views-components/upload-collection-files-dialog/upload-collection-files-dialog';
37 import { ProjectCopyDialog } from '~/views-components/project-copy-dialog/project-copy-dialog';
38 import { CollectionPartialCopyDialog } from '~/views-components/collection-partial-copy-dialog/collection-partial-copy-dialog';
39 import { MoveProjectDialog } from '~/views-components/move-project-dialog/move-project-dialog';
40 import { MoveCollectionDialog } from '~/views-components/move-collection-dialog/move-collection-dialog';
41 import { SidePanel } from '~/views-components/side-panel/side-panel';
42 import { Routes } from '~/routes/routes';
43 import { navigateTo } from '~/store/navigation/navigation-action';
44 import { Breadcrumbs } from '~/views-components/breadcrumbs/breadcrumbs';
45
46
47 const APP_BAR_HEIGHT = 100;
48
49 type CssRules = 'root' | 'appBar' | 'content' | 'contentWrapper';
50
51 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
52     root: {
53         flexGrow: 1,
54         zIndex: 1,
55         overflow: 'hidden',
56         position: 'relative',
57         display: 'flex',
58         width: '100vw',
59         height: '100vh'
60     },
61     appBar: {
62         zIndex: theme.zIndex.drawer + 1,
63         position: "absolute",
64         width: "100%"
65     },
66     contentWrapper: {
67         backgroundColor: theme.palette.background.default,
68         display: "flex",
69         flexGrow: 1,
70         minWidth: 0,
71         paddingTop: APP_BAR_HEIGHT
72     },
73     content: {
74         padding: `${theme.spacing.unit}px ${theme.spacing.unit * 3}px`,
75         overflowY: "auto",
76         flexGrow: 1,
77         position: 'relative'
78     },
79 });
80
81 interface WorkbenchDataProps {
82     projects: Array<TreeItem<ProjectResource>>;
83     currentProjectId: string;
84     user?: User;
85     currentToken?: string;
86 }
87
88 interface WorkbenchGeneralProps {
89     authService: AuthService;
90     buildInfo: string;
91 }
92
93 interface WorkbenchActionProps {
94 }
95
96 type WorkbenchProps = WorkbenchDataProps & WorkbenchGeneralProps & WorkbenchActionProps & DispatchProp<any> & WithStyles<CssRules>;
97
98 interface NavBreadcrumb extends Breadcrumb {
99     itemId: string;
100 }
101
102 interface NavMenuItem extends MainAppBarMenuItem {
103     action: () => void;
104 }
105
106 interface WorkbenchState {
107     isCurrentTokenDialogOpen: boolean;
108     anchorEl: any;
109     searchText: string;
110     menuItems: {
111         accountMenu: NavMenuItem[],
112         helpMenu: NavMenuItem[],
113         anonymousMenu: NavMenuItem[]
114     };
115 }
116
117 export const Workbench = withStyles(styles)(
118     connect<WorkbenchDataProps>(
119         (state: RootState) => ({
120             projects: state.projects.items,
121             currentProjectId: state.projects.currentItemId,
122             user: state.auth.user,
123             currentToken: state.auth.apiToken,
124         })
125     )(
126         class extends React.Component<WorkbenchProps, WorkbenchState> {
127             state = {
128                 isCurrentTokenDialogOpen: false,
129                 anchorEl: null,
130                 searchText: "",
131                 breadcrumbs: [],
132                 menuItems: {
133                     accountMenu: [
134                         {
135                             label: 'Current token',
136                             action: () => this.toggleCurrentTokenModal()
137                         },
138                         {
139                             label: "Logout",
140                             action: () => this.props.dispatch(logout())
141                         },
142                         {
143                             label: "My account",
144                             action: () => this.props.dispatch(push("/my-account"))
145                         }
146                     ],
147                     helpMenu: [
148                         {
149                             label: "Help",
150                             action: () => this.props.dispatch(push("/help"))
151                         }
152                     ],
153                     anonymousMenu: [
154                         {
155                             label: "Sign in",
156                             action: () => this.props.dispatch(login())
157                         }
158                     ]
159                 }
160             };
161
162             render() {
163                 const { classes, user } = this.props;
164                 return (
165                     <div className={classes.root}>
166                         <div className={classes.appBar}>
167                             <MainAppBar
168                                 breadcrumbs={Breadcrumbs}
169                                 searchText={this.state.searchText}
170                                 user={this.props.user}
171                                 menuItems={this.state.menuItems}
172                                 buildInfo={this.props.buildInfo}
173                                 {...this.mainAppBarActions} />
174                         </div>
175                         {user && <SidePanel />}
176                         <main className={classes.contentWrapper}>
177                             <div className={classes.content}>
178                                 <Switch>
179                                     <Route path={Routes.PROJECTS} component={ProjectPanel} />
180                                     <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
181                                     <Route path={Routes.FAVORITES} component={FavoritePanel} />
182                                 </Switch>
183                             </div>
184                             {user && <DetailsPanel />}
185                         </main>
186                         <ContextMenu />
187                         <Snackbar />
188                         <CreateProjectDialog />
189                         <CreateCollectionDialog />
190                         <RenameFileDialog />
191                         <CollectionPartialCopyDialog />
192                         <DialogCollectionCreateWithSelectedFile />
193                         <FileRemoveDialog />
194                         <ProjectCopyDialog />
195                         <MultipleFilesRemoveDialog />
196                         <UpdateCollectionDialog />
197                         <UploadCollectionFilesDialog />
198                         <UpdateProjectDialog />
199                         <MoveCollectionDialog />
200                         <MoveProjectDialog />
201                         <CurrentTokenDialog
202                             currentToken={this.props.currentToken}
203                             open={this.state.isCurrentTokenDialogOpen}
204                             handleClose={this.toggleCurrentTokenModal} />
205                     </div>
206                 );
207             }
208
209             mainAppBarActions: MainAppBarActionProps = {
210                 onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
211                     this.props.dispatch(navigateTo(itemId));
212                 },
213                 onSearch: searchText => {
214                     this.setState({ searchText });
215                     this.props.dispatch(push(`/search?q=${searchText}`));
216                 },
217                 onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action(),
218                 onDetailsPanelToggle: () => {
219                     this.props.dispatch(detailsPanelActions.TOGGLE_DETAILS_PANEL());
220                 },
221                 onContextMenu: (event: React.MouseEvent<HTMLElement>, breadcrumb: NavBreadcrumb) => {
222                     this.props.dispatch<any>(openContextMenu(event, {
223                         uuid: breadcrumb.itemId,
224                         name: breadcrumb.label,
225                         kind: ContextMenuKind.PROJECT
226                     }));
227                 }
228             };
229
230             toggleCurrentTokenModal = () => {
231                 this.setState({ isCurrentTokenDialogOpen: !this.state.isCurrentTokenDialogOpen });
232             }
233         }
234     )
235 );