Merge branch 'master' into 14039-details-view-improvements
[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 { ProjectPanelItem } from './project-panel-item';
7 import { Button, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
8 import { DataExplorer } from "~/views-components/data-explorer/data-explorer";
9 import { DispatchProp, connect } from 'react-redux';
10 import { DataColumns } from '~/components/data-table/data-table';
11 import { RouteComponentProps } from 'react-router';
12 import { RootState } from '~/store/store';
13 import { DataTableFilterItem } from '~/components/data-table-filters/data-table-filters';
14 import { ContainerRequestState } from '~/models/container-request';
15 import { SortDirection } from '~/components/data-table/data-column';
16 import { ResourceKind } from '~/models/resource';
17 import { resourceLabel } from '~/common/labels';
18 import { ArvadosTheme } from '~/common/custom-theme';
19 import { renderName, renderStatus, renderType, renderOwner, renderFileSize, renderDate } from '~/views-components/data-explorer/renderers';
20 import { restoreBranch } from '~/store/navigation/navigation-action';
21 import { ProjectIcon } from '~/components/icon/icon';
22
23 type CssRules = 'root' | "toolbar" | "button";
24
25 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
26     root: {
27         position: 'relative',
28         width: '100%',
29         height: '100%'
30     },
31     toolbar: {
32         paddingBottom: theme.spacing.unit * 3,
33         textAlign: "right"
34     },
35     button: {
36         marginLeft: theme.spacing.unit
37     },
38 });
39
40 export enum ProjectPanelColumnNames {
41     NAME = "Name",
42     STATUS = "Status",
43     TYPE = "Type",
44     OWNER = "Owner",
45     FILE_SIZE = "File size",
46     LAST_MODIFIED = "Last modified"
47 }
48
49 export interface ProjectPanelFilter extends DataTableFilterItem {
50     type: ResourceKind | ContainerRequestState;
51 }
52
53 export const columns: DataColumns<ProjectPanelItem, ProjectPanelFilter> = [
54     {
55         name: ProjectPanelColumnNames.NAME,
56         selected: true,
57         configurable: true,
58         sortDirection: SortDirection.ASC,
59         filters: [],
60         render: renderName,
61         width: "450px"
62     },
63     {
64         name: "Status",
65         selected: true,
66         configurable: true,
67         sortDirection: SortDirection.NONE,
68         filters: [
69             {
70                 name: ContainerRequestState.COMMITTED,
71                 selected: true,
72                 type: ContainerRequestState.COMMITTED
73             },
74             {
75                 name: ContainerRequestState.FINAL,
76                 selected: true,
77                 type: ContainerRequestState.FINAL
78             },
79             {
80                 name: ContainerRequestState.UNCOMMITTED,
81                 selected: true,
82                 type: ContainerRequestState.UNCOMMITTED
83             }
84         ],
85         render: renderStatus,
86         width: "75px"
87     },
88     {
89         name: ProjectPanelColumnNames.TYPE,
90         selected: true,
91         configurable: true,
92         sortDirection: SortDirection.NONE,
93         filters: [
94             {
95                 name: resourceLabel(ResourceKind.COLLECTION),
96                 selected: true,
97                 type: ResourceKind.COLLECTION
98             },
99             {
100                 name: resourceLabel(ResourceKind.PROCESS),
101                 selected: true,
102                 type: ResourceKind.PROCESS
103             },
104             {
105                 name: resourceLabel(ResourceKind.PROJECT),
106                 selected: true,
107                 type: ResourceKind.PROJECT
108             }
109         ],
110         render: item => renderType(item.kind),
111         width: "125px"
112     },
113     {
114         name: ProjectPanelColumnNames.OWNER,
115         selected: true,
116         configurable: true,
117         sortDirection: SortDirection.NONE,
118         filters: [],
119         render: item => renderOwner(item.owner),
120         width: "200px"
121     },
122     {
123         name: ProjectPanelColumnNames.FILE_SIZE,
124         selected: true,
125         configurable: true,
126         sortDirection: SortDirection.NONE,
127         filters: [],
128         render: item => renderFileSize(item.fileSize),
129         width: "50px"
130     },
131     {
132         name: ProjectPanelColumnNames.LAST_MODIFIED,
133         selected: true,
134         configurable: true,
135         sortDirection: SortDirection.NONE,
136         filters: [],
137         render: item => renderDate(item.lastModified),
138         width: "150px"
139     }
140 ];
141
142 export const PROJECT_PANEL_ID = "projectPanel";
143
144 interface ProjectPanelDataProps {
145     currentItemId: string;
146 }
147
148 interface ProjectPanelActionProps {
149     onItemClick: (item: ProjectPanelItem) => void;
150     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
151     onProjectCreationDialogOpen: (ownerUuid: string) => void;
152     onCollectionCreationDialogOpen: (ownerUuid: string) => void;
153     onItemDoubleClick: (item: ProjectPanelItem) => void;
154     onItemRouteChange: (itemId: string) => void;
155 }
156
157 type ProjectPanelProps = ProjectPanelDataProps & ProjectPanelActionProps & DispatchProp
158     & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
159
160 export const ProjectPanel = withStyles(styles)(
161     connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
162         class extends React.Component<ProjectPanelProps> {
163             render() {
164                 const { classes } = this.props;
165                 return <div className={classes.root}>
166                     <div className={classes.toolbar}>
167                         <Button color="primary" onClick={this.handleNewCollectionClick} variant="raised" className={classes.button}>
168                             Create a collection
169                         </Button>
170                         <Button color="primary" variant="raised" className={classes.button}>
171                             Run a process
172                         </Button>
173                         <Button color="primary" onClick={this.handleNewProjectClick} variant="raised" className={classes.button}>
174                             New project
175                         </Button>
176                     </div>
177                     <DataExplorer
178                         id={PROJECT_PANEL_ID}
179                         columns={columns}
180                         onRowClick={this.props.onItemClick}
181                         onRowDoubleClick={this.props.onItemDoubleClick}
182                         onContextMenu={this.props.onContextMenu}
183                         extractKey={(item: ProjectPanelItem) => item.uuid}
184                         defaultIcon={ProjectIcon}
185                         defaultMessages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']} />
186                 </div>;
187             }
188
189             handleNewProjectClick = () => {
190                 this.props.onProjectCreationDialogOpen(this.props.currentItemId);
191             }
192
193             handleNewCollectionClick = () => {
194                 this.props.onCollectionCreationDialogOpen(this.props.currentItemId);
195             }
196
197             componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: ProjectPanelProps) {
198                 if (match.params.id !== currentItemId) {
199                     onItemRouteChange(match.params.id);
200                 }
201             }
202
203             componentDidMount() {
204                 if (this.props.match.params.id && this.props.currentItemId === '') {
205                     this.props.dispatch<any>(restoreBranch(this.props.match.params.id));
206                 }
207             }
208         }
209     )
210 );