Project-creation-modal-first-step
[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 TextField from '@material-ui/core/TextField';
7 import Dialog from '@material-ui/core/Dialog';
8 import DialogActions from '@material-ui/core/DialogActions';
9 import DialogContent from '@material-ui/core/DialogContent';
10 import DialogTitle from '@material-ui/core/DialogTitle';
11 import { ProjectPanelItem } from './project-panel-item';
12 import { Grid, Typography, Button, Toolbar, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
13 import { formatDate, formatFileSize } from '../../common/formatters';
14 import DataExplorer from "../../views-components/data-explorer/data-explorer";
15 import { DataColumn, toggleSortDirection } from '../../components/data-table/data-column';
16 import { DataTableFilterItem } from '../../components/data-table-filters/data-table-filters';
17 import { ContextMenuAction } from '../../components/context-menu/context-menu';
18 import { DispatchProp, connect } from 'react-redux';
19 import actions from "../../store/data-explorer/data-explorer-action";
20 import { DataColumns } from '../../components/data-table/data-table';
21 import { ResourceKind } from "../../models/resource";
22 import { RouteComponentProps } from 'react-router';
23 import { RootState } from '../../store/store';
24
25 export const PROJECT_PANEL_ID = "projectPanel";
26
27 type ProjectPanelProps = {
28     currentItemId: string,
29     onItemClick: (item: ProjectPanelItem) => void,
30     onItemRouteChange: (itemId: string) => void
31 }
32     & DispatchProp
33     & WithStyles<CssRules>
34     & RouteComponentProps<{ id: string }>;
35 class ProjectPanel extends React.Component<ProjectPanelProps> {
36     state = {
37         open: false,
38     };
39
40     handleClickOpen = () => {
41         this.setState({ open: true });
42     }
43
44     handleClose = () => {
45         this.setState({ open: false });
46     }
47
48     render() {
49         return <div>
50             <div className={this.props.classes.toolbar}>
51                 <Button color="primary" variant="raised" className={this.props.classes.button}>
52                     Create a collection
53                 </Button>
54                 <Button color="primary" variant="raised" className={this.props.classes.button}>
55                     Run a process
56                 </Button>
57                 <Button color="primary" onClick={this.handleClickOpen} variant="raised" className={this.props.classes.button}>
58                     Create a project
59                 </Button>
60                 <Dialog
61                     open={this.state.open}
62                     onClose={this.handleClose}>
63                     <div className={this.props.classes.dialog}>
64                     <DialogTitle id="form-dialog-title">Create a project</DialogTitle>
65                     <DialogContent className={this.props.classes.dialogContent}>
66                         <TextField
67                             margin="dense"
68                             className={this.props.classes.textField}
69                             id="name"
70                             label="Project name"
71                             fullWidth  />
72                         <TextField
73                             margin="dense"
74                             id="description"
75                             label="Description - optional"
76                             fullWidth />
77                     </DialogContent>
78                     <DialogActions>
79                         <Button onClick={this.handleClose} className={this.props.classes.button} color="primary">CANCEL</Button>
80                         <Button onClick={this.handleClose} className={this.props.classes.lastButton} color="primary" variant="raised">CREATE A PROJECT</Button>
81                     </DialogActions>
82                     </div>
83                 </Dialog>
84             </div>
85             <DataExplorer
86                 id={PROJECT_PANEL_ID}
87                 contextActions={contextMenuActions}
88                 onColumnToggle={this.toggleColumn}
89                 onFiltersChange={this.changeFilters}
90                 onRowClick={this.props.onItemClick}
91                 onSortToggle={this.toggleSort}
92                 onSearch={this.search}
93                 onContextAction={this.executeAction}
94                 onChangePage={this.changePage}
95                 onChangeRowsPerPage={this.changeRowsPerPage} />;
96         </div>;
97     }
98
99     componentDidMount() {
100         this.props.dispatch(actions.SET_COLUMNS({ id: PROJECT_PANEL_ID, columns }));
101     }
102
103     componentWillReceiveProps({ match, currentItemId }: ProjectPanelProps) {
104         if (match.params.id !== currentItemId) {
105             this.props.onItemRouteChange(match.params.id);
106         }
107     }
108
109     toggleColumn = (toggledColumn: DataColumn<ProjectPanelItem>) => {
110         this.props.dispatch(actions.TOGGLE_COLUMN({ id: PROJECT_PANEL_ID, columnName: toggledColumn.name }));
111     }
112
113     toggleSort = (column: DataColumn<ProjectPanelItem>) => {
114         this.props.dispatch(actions.TOGGLE_SORT({ id: PROJECT_PANEL_ID, columnName: column.name }));
115     }
116
117     changeFilters = (filters: DataTableFilterItem[], column: DataColumn<ProjectPanelItem>) => {
118         this.props.dispatch(actions.SET_FILTERS({ id: PROJECT_PANEL_ID, columnName: column.name, filters }));
119     }
120
121     executeAction = (action: ContextMenuAction, item: ProjectPanelItem) => {
122         alert(`Executing ${action.name} on ${item.name}`);
123     }
124
125     search = (searchValue: string) => {
126         this.props.dispatch(actions.SET_SEARCH_VALUE({ id: PROJECT_PANEL_ID, searchValue }));
127     }
128
129     changePage = (page: number) => {
130         this.props.dispatch(actions.SET_PAGE({ id: PROJECT_PANEL_ID, page }));
131     }
132
133     changeRowsPerPage = (rowsPerPage: number) => {
134         this.props.dispatch(actions.SET_ROWS_PER_PAGE({ id: PROJECT_PANEL_ID, rowsPerPage }));
135     }
136
137 }
138
139 type CssRules = "toolbar" | "button" | "lastButton" | "dialogContent" | "textField" | "dialog";
140
141 const styles: StyleRulesCallback<CssRules> = theme => ({
142     toolbar: {
143         paddingBottom: theme.spacing.unit * 3,
144         textAlign: "right"
145     },
146     button: {
147         marginLeft: theme.spacing.unit
148     },
149     lastButton: {
150         marginLeft: theme.spacing.unit,
151         marginRight: "20px",
152     },
153     dialogContent: {
154         marginTop: "20px",
155     },
156     textField: {
157         marginBottom: "32px",
158     },
159     dialog: {
160         minWidth: "550px",
161         minHeight: "320px"
162     }
163 });
164
165 const renderName = (item: ProjectPanelItem) =>
166     <Grid
167         container
168         alignItems="center"
169         wrap="nowrap"
170         spacing={16}>
171         <Grid item>
172             {renderIcon(item)}
173         </Grid>
174         <Grid item>
175             <Typography color="primary">
176                 {item.name}
177             </Typography>
178         </Grid>
179     </Grid>;
180
181
182 const renderIcon = (item: ProjectPanelItem) => {
183     switch (item.kind) {
184         case ResourceKind.PROJECT:
185             return <i className="fas fa-folder fa-lg" />;
186         case ResourceKind.COLLECTION:
187             return <i className="fas fa-th fa-lg" />;
188         default:
189             return <i />;
190     }
191 };
192
193 const renderDate = (date: string) =>
194     <Typography noWrap>
195         {formatDate(date)}
196     </Typography>;
197
198 const renderFileSize = (fileSize?: number) =>
199     <Typography noWrap>
200         {formatFileSize(fileSize)}
201     </Typography>;
202
203 const renderOwner = (owner: string) =>
204     <Typography noWrap color="primary">
205         {owner}
206     </Typography>;
207
208 const renderType = (type: string) =>
209     <Typography noWrap>
210         {type}
211     </Typography>;
212
213 const renderStatus = (item: ProjectPanelItem) =>
214     <Typography noWrap align="center">
215         {item.status || "-"}
216     </Typography>;
217
218 const columns: DataColumns<ProjectPanelItem> = [{
219     name: "Name",
220     selected: true,
221     sortDirection: "desc",
222     render: renderName,
223     width: "450px"
224 }, {
225     name: "Status",
226     selected: true,
227     render: renderStatus,
228     width: "75px"
229 }, {
230     name: "Type",
231     selected: true,
232     filters: [{
233         name: "Collection",
234         selected: true
235     }, {
236         name: "Project",
237         selected: true
238     }],
239     render: item => renderType(item.kind),
240     width: "125px"
241 }, {
242     name: "Owner",
243     selected: true,
244     render: item => renderOwner(item.owner),
245     width: "200px"
246 }, {
247     name: "File size",
248     selected: true,
249     render: item => renderFileSize(item.fileSize),
250     width: "50px"
251 }, {
252     name: "Last modified",
253     selected: true,
254     sortDirection: "none",
255     render: item => renderDate(item.lastModified),
256     width: "150px"
257 }];
258
259 const contextMenuActions = [[{
260     icon: "fas fa-users fa-fw",
261     name: "Share"
262 }, {
263     icon: "fas fa-sign-out-alt fa-fw",
264     name: "Move to"
265 }, {
266     icon: "fas fa-star fa-fw",
267     name: "Add to favourite"
268 }, {
269     icon: "fas fa-edit fa-fw",
270     name: "Rename"
271 }, {
272     icon: "fas fa-copy fa-fw",
273     name: "Make a copy"
274 }, {
275     icon: "fas fa-download fa-fw",
276     name: "Download"
277 }], [{
278     icon: "fas fa-trash-alt fa-fw",
279     name: "Remove"
280 }
281 ]];
282
283 export default withStyles(styles)(
284     connect((state: RootState) => ({ currentItemId: state.projects.currentItemId }))(
285         ProjectPanel));