15768: collection copy dialog mostly works Arvados-DCO-1.1-Signed-off-by: Lisa Knox...
[arvados.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 React from "react";
6 import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core/styles";
7 import { Route, Switch } from "react-router";
8 import { ProjectPanel } from "views/project-panel/project-panel";
9 import { DetailsPanel } from "views-components/details-panel/details-panel";
10 import { ArvadosTheme } from "common/custom-theme";
11 import { ContextMenu } from "views-components/context-menu/context-menu";
12 import { FavoritePanel } from "../favorite-panel/favorite-panel";
13 import { TokenDialog } from "views-components/token-dialog/token-dialog";
14 import { RichTextEditorDialog } from "views-components/rich-text-editor-dialog/rich-text-editor-dialog";
15 import { Snackbar } from "views-components/snackbar/snackbar";
16 import { CollectionPanel } from "../collection-panel/collection-panel";
17 import { RenameFileDialog } from "views-components/rename-file-dialog/rename-file-dialog";
18 import { FileRemoveDialog } from "views-components/file-remove-dialog/file-remove-dialog";
19 import { MultipleFilesRemoveDialog } from "views-components/file-remove-dialog/multiple-files-remove-dialog";
20 import { Routes } from "routes/routes";
21 import { SidePanel } from "views-components/side-panel/side-panel";
22 import { ProcessPanel } from "views/process-panel/process-panel";
23 import { ChangeWorkflowDialog } from "views-components/run-process-dialog/change-workflow-dialog";
24 import { CreateProjectDialog } from "views-components/dialog-forms/create-project-dialog";
25 import { CreateCollectionDialog } from "views-components/dialog-forms/create-collection-dialog";
26 import { CopyCollectionDialog, CopyMultiCollectionDialog } from "views-components/dialog-forms/copy-collection-dialog";
27 import { CopyProcessDialog } from "views-components/dialog-forms/copy-process-dialog";
28 import { UpdateCollectionDialog } from "views-components/dialog-forms/update-collection-dialog";
29 import { UpdateProcessDialog } from "views-components/dialog-forms/update-process-dialog";
30 import { UpdateProjectDialog } from "views-components/dialog-forms/update-project-dialog";
31 import { MoveProcessDialog } from "views-components/dialog-forms/move-process-dialog";
32 import { MoveProjectDialog } from "views-components/dialog-forms/move-project-dialog";
33 import { MoveCollectionDialog } from "views-components/dialog-forms/move-collection-dialog";
34 import { FilesUploadCollectionDialog } from "views-components/dialog-forms/files-upload-collection-dialog";
35 import { PartialCopyCollectionDialog } from "views-components/dialog-forms/partial-copy-collection-dialog";
36 import { RemoveProcessDialog } from "views-components/process-remove-dialog/process-remove-dialog";
37 import { MainContentBar } from "views-components/main-content-bar/main-content-bar";
38 import { Grid } from "@material-ui/core";
39 import { TrashPanel } from "views/trash-panel/trash-panel";
40 import { SharedWithMePanel } from "views/shared-with-me-panel/shared-with-me-panel";
41 import { RunProcessPanel } from "views/run-process-panel/run-process-panel";
42 import SplitterLayout from "react-splitter-layout";
43 import { WorkflowPanel } from "views/workflow-panel/workflow-panel";
44 import { RegisteredWorkflowPanel } from "views/workflow-panel/registered-workflow-panel";
45 import { SearchResultsPanel } from "views/search-results-panel/search-results-panel";
46 import { SshKeyPanel } from "views/ssh-key-panel/ssh-key-panel";
47 import { SshKeyAdminPanel } from "views/ssh-key-panel/ssh-key-admin-panel";
48 import { SiteManagerPanel } from "views/site-manager-panel/site-manager-panel";
49 import { UserProfilePanel } from "views/user-profile-panel/user-profile-panel";
50 import { SharingDialog } from "views-components/sharing-dialog/sharing-dialog";
51 import { NotFoundDialog } from "views-components/not-found-dialog/not-found-dialog";
52 import { AdvancedTabDialog } from "views-components/advanced-tab-dialog/advanced-tab-dialog";
53 import { ProcessInputDialog } from "views-components/process-input-dialog/process-input-dialog";
54 import { VirtualMachineUserPanel } from "views/virtual-machine-panel/virtual-machine-user-panel";
55 import { VirtualMachineAdminPanel } from "views/virtual-machine-panel/virtual-machine-admin-panel";
56 import { RepositoriesPanel } from "views/repositories-panel/repositories-panel";
57 import { KeepServicePanel } from "views/keep-service-panel/keep-service-panel";
58 import { ApiClientAuthorizationPanel } from "views/api-client-authorization-panel/api-client-authorization-panel";
59 import { LinkPanel } from "views/link-panel/link-panel";
60 import { RepositoriesSampleGitDialog } from "views-components/repositories-sample-git-dialog/repositories-sample-git-dialog";
61 import { RepositoryAttributesDialog } from "views-components/repository-attributes-dialog/repository-attributes-dialog";
62 import { CreateRepositoryDialog } from "views-components/dialog-forms/create-repository-dialog";
63 import { RemoveRepositoryDialog } from "views-components/repository-remove-dialog/repository-remove-dialog";
64 import { CreateSshKeyDialog } from "views-components/dialog-forms/create-ssh-key-dialog";
65 import { PublicKeyDialog } from "views-components/ssh-keys-dialog/public-key-dialog";
66 import { RemoveApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/remove-dialog";
67 import { RemoveKeepServiceDialog } from "views-components/keep-services-dialog/remove-dialog";
68 import { RemoveLinkDialog } from "views-components/links-dialog/remove-dialog";
69 import { RemoveSshKeyDialog } from "views-components/ssh-keys-dialog/remove-dialog";
70 import { VirtualMachineAttributesDialog } from "views-components/virtual-machines-dialog/attributes-dialog";
71 import { RemoveVirtualMachineDialog } from "views-components/virtual-machines-dialog/remove-dialog";
72 import { RemoveVirtualMachineLoginDialog } from "views-components/virtual-machines-dialog/remove-login-dialog";
73 import { VirtualMachineAddLoginDialog } from "views-components/virtual-machines-dialog/add-login-dialog";
74 import { AttributesApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/attributes-dialog";
75 import { AttributesKeepServiceDialog } from "views-components/keep-services-dialog/attributes-dialog";
76 import { AttributesLinkDialog } from "views-components/links-dialog/attributes-dialog";
77 import { AttributesSshKeyDialog } from "views-components/ssh-keys-dialog/attributes-dialog";
78 import { UserPanel } from "views/user-panel/user-panel";
79 import { UserAttributesDialog } from "views-components/user-dialog/attributes-dialog";
80 import { CreateUserDialog } from "views-components/dialog-forms/create-user-dialog";
81 import { HelpApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/help-dialog";
82 import { DeactivateDialog } from "views-components/user-dialog/deactivate-dialog";
83 import { ActivateDialog } from "views-components/user-dialog/activate-dialog";
84 import { SetupDialog } from "views-components/user-dialog/setup-dialog";
85 import { GroupsPanel } from "views/groups-panel/groups-panel";
86 import { RemoveGroupDialog } from "views-components/groups-dialog/remove-dialog";
87 import { GroupAttributesDialog } from "views-components/groups-dialog/attributes-dialog";
88 import { GroupDetailsPanel } from "views/group-details-panel/group-details-panel";
89 import { RemoveGroupMemberDialog } from "views-components/groups-dialog/member-remove-dialog";
90 import { GroupMemberAttributesDialog } from "views-components/groups-dialog/member-attributes-dialog";
91 import { PartialCopyToCollectionDialog } from "views-components/dialog-forms/partial-copy-to-collection-dialog";
92 import { PublicFavoritePanel } from "views/public-favorites-panel/public-favorites-panel";
93 import { LinkAccountPanel } from "views/link-account-panel/link-account-panel";
94 import { FedLogin } from "./fed-login";
95 import { CollectionsContentAddressPanel } from "views/collection-content-address-panel/collection-content-address-panel";
96 import { AllProcessesPanel } from "../all-processes-panel/all-processes-panel";
97 import { NotFoundPanel } from "../not-found-panel/not-found-panel";
98 import { AutoLogout } from "views-components/auto-logout/auto-logout";
99 import { RestoreCollectionVersionDialog } from "views-components/collections-dialog/restore-version-dialog";
100 import { WebDavS3InfoDialog } from "views-components/webdav-s3-dialog/webdav-s3-dialog";
101 import { pluginConfig } from "plugins";
102 import { ElementListReducer } from "common/plugintypes";
103 import { COLLAPSE_ICON_SIZE } from "views-components/side-panel-toggle/side-panel-toggle";
104 import { Banner } from "views-components/baner/banner";
105
106 type CssRules = "root" | "container" | "splitter" | "asidePanel" | "contentWrapper" | "content";
107
108 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
109     root: {
110         paddingTop: theme.spacing.unit * 7,
111         background: theme.palette.background.default,
112     },
113     container: {
114         position: "relative",
115     },
116     splitter: {
117         "& > .layout-splitter": {
118             width: "2px",
119         },
120         "& > .layout-splitter-disabled": {
121             pointerEvents: "none",
122             cursor: "pointer",
123         },
124     },
125     asidePanel: {
126         paddingTop: theme.spacing.unit,
127         height: "100%",
128     },
129     contentWrapper: {
130         paddingTop: theme.spacing.unit,
131         minWidth: 0,
132     },
133     content: {
134         minWidth: 0,
135         paddingLeft: theme.spacing.unit * 3,
136         paddingRight: theme.spacing.unit * 3,
137         // Reserve vertical space for app bar + MainContentBar
138         minHeight: `calc(100vh - ${theme.spacing.unit * 16}px)`,
139         display: "flex",
140     },
141 });
142
143 interface WorkbenchDataProps {
144     isUserActive: boolean;
145     isNotLinking: boolean;
146     sessionIdleTimeout: number;
147     sidePanelIsCollapsed: boolean;
148 }
149
150 type WorkbenchPanelProps = WithStyles<CssRules> & WorkbenchDataProps;
151
152 const defaultSplitterSize = 90;
153
154 const getSplitterInitialSize = () => {
155     const splitterSize = localStorage.getItem("splitterSize");
156     return splitterSize ? Number(splitterSize) : defaultSplitterSize;
157 };
158
159 const saveSplitterSize = (size: number) => localStorage.setItem("splitterSize", size.toString());
160
161 let routes = (
162     <>
163         <Route
164             path={Routes.PROJECTS}
165             component={ProjectPanel}
166         />
167         <Route
168             path={Routes.COLLECTIONS}
169             component={CollectionPanel}
170         />
171         <Route
172             path={Routes.FAVORITES}
173             component={FavoritePanel}
174         />
175         <Route
176             path={Routes.ALL_PROCESSES}
177             component={AllProcessesPanel}
178         />
179         <Route
180             path={Routes.PROCESSES}
181             component={ProcessPanel}
182         />
183         <Route
184             path={Routes.TRASH}
185             component={TrashPanel}
186         />
187         <Route
188             path={Routes.SHARED_WITH_ME}
189             component={SharedWithMePanel}
190         />
191         <Route
192             path={Routes.RUN_PROCESS}
193             component={RunProcessPanel}
194         />
195         <Route
196             path={Routes.REGISTEREDWORKFLOW}
197             component={RegisteredWorkflowPanel}
198         />
199         <Route
200             path={Routes.WORKFLOWS}
201             component={WorkflowPanel}
202         />
203         <Route
204             path={Routes.SEARCH_RESULTS}
205             component={SearchResultsPanel}
206         />
207         <Route
208             path={Routes.VIRTUAL_MACHINES_USER}
209             component={VirtualMachineUserPanel}
210         />
211         <Route
212             path={Routes.VIRTUAL_MACHINES_ADMIN}
213             component={VirtualMachineAdminPanel}
214         />
215         <Route
216             path={Routes.REPOSITORIES}
217             component={RepositoriesPanel}
218         />
219         <Route
220             path={Routes.SSH_KEYS_USER}
221             component={SshKeyPanel}
222         />
223         <Route
224             path={Routes.SSH_KEYS_ADMIN}
225             component={SshKeyAdminPanel}
226         />
227         <Route
228             path={Routes.SITE_MANAGER}
229             component={SiteManagerPanel}
230         />
231         <Route
232             path={Routes.KEEP_SERVICES}
233             component={KeepServicePanel}
234         />
235         <Route
236             path={Routes.USERS}
237             component={UserPanel}
238         />
239         <Route
240             path={Routes.API_CLIENT_AUTHORIZATIONS}
241             component={ApiClientAuthorizationPanel}
242         />
243         <Route
244             path={Routes.MY_ACCOUNT}
245             component={UserProfilePanel}
246         />
247         <Route
248             path={Routes.USER_PROFILE}
249             component={UserProfilePanel}
250         />
251         <Route
252             path={Routes.GROUPS}
253             component={GroupsPanel}
254         />
255         <Route
256             path={Routes.GROUP_DETAILS}
257             component={GroupDetailsPanel}
258         />
259         <Route
260             path={Routes.LINKS}
261             component={LinkPanel}
262         />
263         <Route
264             path={Routes.PUBLIC_FAVORITES}
265             component={PublicFavoritePanel}
266         />
267         <Route
268             path={Routes.LINK_ACCOUNT}
269             component={LinkAccountPanel}
270         />
271         <Route
272             path={Routes.COLLECTIONS_CONTENT_ADDRESS}
273             component={CollectionsContentAddressPanel}
274         />
275     </>
276 );
277
278 const reduceRoutesFn: (a: React.ReactElement[], b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a);
279
280 routes = React.createElement(
281     React.Fragment,
282     null,
283     pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children))
284 );
285
286 const applyCollapsedState = isCollapsed => {
287     const rightPanel: Element = document.getElementsByClassName("layout-pane")[1];
288     const totalWidth: number = document.getElementsByClassName("splitter-layout")[0]?.clientWidth;
289     const rightPanelExpandedWidth = (totalWidth - COLLAPSE_ICON_SIZE) / (totalWidth / 100);
290     if (rightPanel) {
291         rightPanel.setAttribute("style", `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`);
292     }
293     const splitter = document.getElementsByClassName("layout-splitter")[0];
294     isCollapsed ? splitter?.classList.add("layout-splitter-disabled") : splitter?.classList.remove("layout-splitter-disabled");
295 };
296
297 export const WorkbenchPanel = withStyles(styles)((props: WorkbenchPanelProps) => {
298     //panel size will not scale automatically on window resize, so we do it manually
299     window.addEventListener("resize", () => applyCollapsedState(props.sidePanelIsCollapsed));
300     applyCollapsedState(props.sidePanelIsCollapsed);
301
302     return (
303         <Grid
304             container
305             item
306             xs
307             className={props.classes.root}>
308             {props.sessionIdleTimeout > 0 && <AutoLogout />}
309             <Grid
310                 container
311                 item
312                 xs
313                 className={props.classes.container}>
314                 <SplitterLayout
315                     customClassName={props.classes.splitter}
316                     percentage={true}
317                     primaryIndex={0}
318                     primaryMinSize={10}
319                     secondaryInitialSize={getSplitterInitialSize()}
320                     secondaryMinSize={40}
321                     onSecondaryPaneSizeChange={saveSplitterSize}>
322                     {props.isUserActive && props.isNotLinking && (
323                         <Grid
324                             container
325                             item
326                             xs
327                             component="aside"
328                             direction="column"
329                             className={props.classes.asidePanel}>
330                             <SidePanel />
331                         </Grid>
332                     )}
333                     <Grid
334                         container
335                         item
336                         xs
337                         component="main"
338                         direction="column"
339                         className={props.classes.contentWrapper}>
340                         <Grid
341                             item
342                             xs>
343                             {props.isNotLinking && <MainContentBar />}
344                         </Grid>
345                         <Grid
346                             item
347                             xs
348                             className={props.classes.content}>
349                             <Switch>
350                                 {routes.props.children}
351                                 <Route
352                                     path={Routes.NO_MATCH}
353                                     component={NotFoundPanel}
354                                 />
355                             </Switch>
356                         </Grid>
357                     </Grid>
358                 </SplitterLayout>
359             </Grid>
360             <Grid item>
361                 <DetailsPanel />
362             </Grid>
363             <AdvancedTabDialog />
364             <AttributesApiClientAuthorizationDialog />
365             <AttributesKeepServiceDialog />
366             <AttributesLinkDialog />
367             <AttributesSshKeyDialog />
368             <ChangeWorkflowDialog />
369             <ContextMenu />
370             <CopyCollectionDialog />
371             <CopyMultiCollectionDialog />
372             <CopyProcessDialog />
373             <CreateCollectionDialog />
374             <CreateProjectDialog />
375             <CreateRepositoryDialog />
376             <CreateSshKeyDialog />
377             <CreateUserDialog />
378             <TokenDialog />
379             <FileRemoveDialog />
380             <FilesUploadCollectionDialog />
381             <GroupAttributesDialog />
382             <GroupMemberAttributesDialog />
383             <HelpApiClientAuthorizationDialog />
384             <MoveCollectionDialog />
385             <MoveProcessDialog />
386             <MoveProjectDialog />
387             <MultipleFilesRemoveDialog />
388             <PublicKeyDialog />
389             <PartialCopyCollectionDialog />
390             <PartialCopyToCollectionDialog />
391             <ProcessInputDialog />
392             <RestoreCollectionVersionDialog />
393             <RemoveApiClientAuthorizationDialog />
394             <RemoveGroupDialog />
395             <RemoveGroupMemberDialog />
396             <RemoveKeepServiceDialog />
397             <RemoveLinkDialog />
398             <RemoveProcessDialog />
399             <RemoveRepositoryDialog />
400             <RemoveSshKeyDialog />
401             <RemoveVirtualMachineDialog />
402             <RemoveVirtualMachineLoginDialog />
403             <VirtualMachineAddLoginDialog />
404             <RenameFileDialog />
405             <RepositoryAttributesDialog />
406             <RepositoriesSampleGitDialog />
407             <RichTextEditorDialog />
408             <SharingDialog />
409             <NotFoundDialog />
410             <Snackbar />
411             <UpdateCollectionDialog />
412             <UpdateProcessDialog />
413             <UpdateProjectDialog />
414             <UserAttributesDialog />
415             <DeactivateDialog />
416             <ActivateDialog />
417             <SetupDialog />
418             <VirtualMachineAttributesDialog />
419             <FedLogin />
420             <WebDavS3InfoDialog />
421             <Banner />
422             {React.createElement(React.Fragment, null, pluginConfig.dialogs)}
423         </Grid>
424     );
425 });