refs #master Merge branch 'origin/master' into 14007-ts-paths
[arvados.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         render: renderName,
60         width: "450px"
61     },
62     {
63         name: "Status",
64         selected: true,
65         configurable: true,
66         filters: [
67             {
68                 name: ContainerRequestState.COMMITTED,
69                 selected: true,
70                 type: ContainerRequestState.COMMITTED
71             },
72             {
73                 name: ContainerRequestState.FINAL,
74                 selected: true,
75                 type: ContainerRequestState.FINAL
76             },
77             {
78                 name: ContainerRequestState.UNCOMMITTED,
79                 selected: true,
80                 type: ContainerRequestState.UNCOMMITTED
81             }
82         ],
83         render: renderStatus,
84         width: "75px"
85     },
86     {
87         name: ProjectPanelColumnNames.TYPE,
88         selected: true,
89         configurable: true,
90         filters: [
91             {
92                 name: resourceLabel(ResourceKind.COLLECTION),
93                 selected: true,
94                 type: ResourceKind.COLLECTION
95             },
96             {
97                 name: resourceLabel(ResourceKind.PROCESS),
98                 selected: true,
99                 type: ResourceKind.PROCESS
100             },
101             {
102                 name: resourceLabel(ResourceKind.PROJECT),
103                 selected: true,
104                 type: ResourceKind.PROJECT
105             }
106         ],
107         render: item => renderType(item.kind),
108         width: "125px"
109     },
110     {
111         name: ProjectPanelColumnNames.OWNER,
112         selected: true,
113         configurable: true,
114         render: item => renderOwner(item.owner),
115         width: "200px"
116     },
117     {
118         name: ProjectPanelColumnNames.FILE_SIZE,
119         selected: true,
120         configurable: true,
121         render: item => renderFileSize(item.fileSize),
122         width: "50px"
123     },
124     {
125         name: ProjectPanelColumnNames.LAST_MODIFIED,
126         selected: true,
127         configurable: true,
128         sortDirection: SortDirection.NONE,
129         render: item => renderDate(item.lastModified),
130         width: "150px"
131     }
132 ];
133
134 export const PROJECT_PANEL_ID = "projectPanel";
135
136 interface ProjectPanelDataProps {
137     currentItemId: string;
138 }
139
140 interface ProjectPanelActionProps {
141     onItemClick: (item: ProjectPanelItem) => void;
142     onContextMenu: (event: React.MouseEvent<HTMLElement>, item: ProjectPanelItem) => void;
143     onProjectCreationDialogOpen: (ownerUuid: string) => void;
144     onCollectionCreationDialogOpen: (ownerUuid: string) => void;
145     onItemDoubleClick: (item: ProjectPanelItem) => void;
146     onItemRouteChange: (itemId: string) => void;
147 }
148
149 type ProjectPanelProps = ProjectPanelDataProps & ProjectPanelActionProps & DispatchProp
150     & WithStyles<CssRules> & RouteComponentProps<{ id: string }>;
151
152 export const ProjectPanel = withStyles(styles)(
153     connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
154         class extends React.Component<ProjectPanelProps> {
155             render() {
156                 const { classes } = this.props;
157                 return <div className={classes.root}>
158                     <div className={classes.toolbar}>
159                         <Button color="primary" onClick={this.handleNewCollectionClick} variant="raised" className={classes.button}>
160                             Create a collection
161                         </Button>
162                         <Button color="primary" variant="raised" className={classes.button}>
163                             Run a process
164                         </Button>
165                         <Button color="primary" onClick={this.handleNewProjectClick} variant="raised" className={classes.button}>
166                             New project
167                         </Button>
168                     </div>
169                     <DataExplorer
170                         id={PROJECT_PANEL_ID}
171                         columns={columns}
172                         onRowClick={this.props.onItemClick}
173                         onRowDoubleClick={this.props.onItemDoubleClick}
174                         onContextMenu={this.props.onContextMenu}
175                         extractKey={(item: ProjectPanelItem) => item.uuid}
176                         defaultIcon={ProjectIcon}
177                         defaultMessages={['Your project is empty.', 'Please create a project or create a collection and upload a data.']} />
178                 </div>;
179             }
180
181             handleNewProjectClick = () => {
182                 this.props.onProjectCreationDialogOpen(this.props.currentItemId);
183             }
184
185             handleNewCollectionClick = () => {
186                 this.props.onCollectionCreationDialogOpen(this.props.currentItemId);
187             }
188
189             componentWillReceiveProps({ match, currentItemId, onItemRouteChange }: ProjectPanelProps) {
190                 if (match.params.id !== currentItemId) {
191                     onItemRouteChange(match.params.id);
192                 }
193             }
194
195             componentDidMount() {
196                 if (this.props.match.params.id && this.props.currentItemId === '') {
197                     this.props.dispatch<any>(restoreBranch(this.props.match.params.id));
198                 }
199             }
200         }
201     )
202 );