Merge branch 'master'
[arvados-workbench2.git] / src / views / project-panel / project-panel.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 { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
7 import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
8 import { DispatchProp, connect } from 'react-redux';
9 import { DataColumns } from '~/components/data-table/data-table';
10 import { RouteComponentProps } from 'react-router';
11 import { RootState } from '~/store/store';
12 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
13 import { ContainerRequestState } from '~/models/container-request';
14 import { SortDirection } from '~/components/data-table/data-column';
15 import { ResourceKind } from '~/models/resource';
16 import { resourceLabel } from '~/common/labels';
17 import { ArvadosTheme } from '~/common/custom-theme';
18 import { ResourceFileSize, ResourceLastModifiedDate, ProcessStatus, ResourceType, ResourceOwner } from '~/views-components/data-explorer/renderers';
19 import { ProjectIcon } from '~/components/icon/icon';
20 import { ResourceName } from '~/views-components/data-explorer/renderers';
21 import { ResourcesState, getResource } from '~/store/resources/resources';
22 import { loadDetailsPanel } from '~/store/details-panel/details-panel-action';
23 import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
24 import { contextMenuActions } from '~/store/context-menu/context-menu-actions';
25 import { CollectionResource } from '~/models/collection';
26 import { ProjectResource } from '~/models/project';
27 import { navigateTo } from '~/store/navigation/navigation-action';
28 import { getProperty } from '~/store/properties/properties';
29 import { PROJECT_PANEL_CURRENT_UUID } from '~/store/project-panel/project-panel-action';
30 import { openCollectionCreateDialog } from '../../store/collections/collection-create-actions';
31 import { openProjectCreateDialog } from '~/store/projects/project-create-actions';
32
33 type CssRules = 'root' | "toolbar" | "button";
34
35 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
36     root: {
37         position: 'relative',
38         width: '100%',
39         height: '100%'
40     },
41     toolbar: {
42         paddingBottom: theme.spacing.unit * 3,
43         textAlign: "right"
44     },
45     button: {
46         marginLeft: theme.spacing.unit
47     },
48 });
49
50 export enum ProjectPanelColumnNames {
51     NAME = "Name",
52     STATUS = "Status",
53     TYPE = "Type",
54     OWNER = "Owner",
55     FILE_SIZE = "File size",
56     LAST_MODIFIED = "Last modified"
57 }
58
59 export interface ProjectPanelFilter extends DataTableFilterItem {
60     type: ResourceKind | ContainerRequestState;
61 }
62
63 export const projectPanelColumns: DataColumns<string, ProjectPanelFilter> = [
64     {
65         name: ProjectPanelColumnNames.NAME,
66         selected: true,
67         configurable: true,
68         sortDirection: SortDirection.ASC,
69         filters: [],
70         render: uuid => <ResourceName uuid={uuid} />,
71         width: "450px"
72     },
73     {
74         name: "Status",
75         selected: true,
76         configurable: true,
77         sortDirection: SortDirection.NONE,
78         filters: [
79             {
80                 name: ContainerRequestState.COMMITTED,
81                 selected: true,
82                 type: ContainerRequestState.COMMITTED
83             },
84             {
85                 name: ContainerRequestState.FINAL,
86                 selected: true,
87                 type: ContainerRequestState.FINAL
88             },
89             {
90                 name: ContainerRequestState.UNCOMMITTED,
91                 selected: true,
92                 type: ContainerRequestState.UNCOMMITTED
93             }
94         ],
95         render: uuid => <ProcessStatus uuid={uuid} />,
96         width: "75px"
97     },
98     {
99         name: ProjectPanelColumnNames.TYPE,
100         selected: true,
101         configurable: true,
102         sortDirection: SortDirection.NONE,
103         filters: [
104             {
105                 name: resourceLabel(ResourceKind.COLLECTION),
106                 selected: true,
107                 type: ResourceKind.COLLECTION
108             },
109             {
110                 name: resourceLabel(ResourceKind.PROCESS),
111                 selected: true,
112                 type: ResourceKind.PROCESS
113             },
114             {
115                 name: resourceLabel(ResourceKind.PROJECT),
116                 selected: true,
117                 type: ResourceKind.PROJECT
118             }
119         ],
120         render: uuid => <ResourceType uuid={uuid} />,
121         width: "125px"
122     },
123     {
124         name: ProjectPanelColumnNames.OWNER,
125         selected: true,
126         configurable: true,
127         sortDirection: SortDirection.NONE,
128         filters: [],
129         render: uuid => <ResourceOwner uuid={uuid} />,
130         width: "200px"
131     },
132     {
133         name: ProjectPanelColumnNames.FILE_SIZE,
134         selected: true,
135         configurable: true,
136         sortDirection: SortDirection.NONE,
137         filters: [],
138         render: uuid => <ResourceFileSize uuid={uuid} />,
139         width: "50px"
140     },
141     {
142         name: ProjectPanelColumnNames.LAST_MODIFIED,
143         selected: true,
144         configurable: true,
145         sortDirection: SortDirection.NONE,
146         filters: [],
147         render: uuid => <ResourceLastModifiedDate uuid={uuid} />,
148         width: "150px"
149     }
150 ];
151
152 export const PROJECT_PANEL_ID = "projectPanel";
153
154 interface ProjectPanelDataProps {
155     currentItemId: string;
156     resources: ResourcesState;
157 }
158
159 type ProjectPanelProps = ProjectPanelDataProps & DispatchProp
160     & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
161
162 export const ProjectPanel = withStyles(styles)(
163     connect((state: RootState) => ({
164         currentItemId: getProperty(PROJECT_PANEL_CURRENT_UUID)(state.properties),
165         resources: state.resources
166     }))(
167         class extends React.Component<ProjectPanelProps> {
168             render() {
169                 const { classes } = this.props;
170                 return <div className={classes.root}>
171                     <div className={classes.toolbar}>
172                         <Button color="primary" onClick={this.handleNewCollectionClick} variant="raised" className={classes.button}>
173                             Create a collection
174                         </Button>
175                         <Button color="primary" variant="raised" className={classes.button}>
176                             Run a process
177                         </Button>
178                         <Button color="primary" onClick={this.handleNewProjectClick} variant="raised" className={classes.button}>
179                             New project
180                         </Button>
181                     </div>
182                     <DataExplorer
183                         id={PROJECT_PANEL_ID}
184                         onRowClick={this.handleRowClick}
185                         onRowDoubleClick={this.handleRowDoubleClick}
186                         onContextMenu={this.handleContextMenu}
187                         defaultIcon={ProjectIcon}
188                         defaultMessages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']} />
189                 </div>;
190             }
191
192             handleNewProjectClick = () => {
193                 this.props.dispatch<any>(openProjectCreateDialog(this.props.currentItemId));
194             }
195
196             handleNewCollectionClick = () => {
197                 this.props.dispatch<any>(openCollectionCreateDialog(this.props.currentItemId));
198             }
199
200             handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
201                 event.preventDefault();
202                 const resource = getResource(resourceUuid)(this.props.resources) as CollectionResource | ProjectResource | undefined;
203                 if (resource) {
204                     let kind: ContextMenuKind;
205
206                     if (resource.kind === ResourceKind.PROJECT) {
207                         kind = ContextMenuKind.PROJECT;
208                     } else if (resource.kind === ResourceKind.COLLECTION) {
209                         kind = ContextMenuKind.COLLECTION_RESOURCE;
210                     } else {
211                         kind = ContextMenuKind.RESOURCE;
212                     }
213                     if (kind !== ContextMenuKind.RESOURCE) {
214                         this.props.dispatch(
215                             contextMenuActions.OPEN_CONTEXT_MENU({
216                                 position: { x: event.clientX, y: event.clientY },
217                                 resource: {
218                                     uuid: resource.uuid,
219                                     name: resource.name || '',
220                                     description: resource.description,
221                                     kind,
222                                 }
223                             })
224                         );
225                     }
226                 }
227             }
228
229             handleRowDoubleClick = (uuid: string) => {
230                 this.props.dispatch<any>(navigateTo(uuid));
231             }
232
233             handleRowClick = (uuid: string) => {
234                 this.props.dispatch(loadDetailsPanel(uuid));
235             }
236
237         }
238     )
239 );