refs #13856 Merge branch 'origin/13856-upload-component'
[arvados-workbench2.git] / src / views-components / dialog-create / dialog-collection-create.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 { reduxForm, Field } from 'redux-form';
7 import { compose } from 'redux';
8 import TextField from '@material-ui/core/TextField';
9 import Dialog from '@material-ui/core/Dialog';
10 import DialogActions from '@material-ui/core/DialogActions';
11 import DialogContent from '@material-ui/core/DialogContent';
12 import DialogTitle from '@material-ui/core/DialogTitle';
13 import { Button, StyleRulesCallback, WithStyles, withStyles, CircularProgress } from '@material-ui/core';
14
15 import { COLLECTION_NAME_VALIDATION, COLLECTION_DESCRIPTION_VALIDATION } from '../../validators/create-collection/create-collection-validator';
16 import { FileUpload } from "../../components/file-upload/file-upload";
17 import { connect, DispatchProp } from "react-redux";
18 import { RootState } from "../../store/store";
19 import { collectionUploaderActions, UploadFile } from "../../store/collections/uploader/collection-uploader-actions";
20
21 type CssRules = "button" | "lastButton" | "formContainer" | "textField" | "createProgress" | "dialogActions";
22
23 const styles: StyleRulesCallback<CssRules> = theme => ({
24     button: {
25         marginLeft: theme.spacing.unit
26     },
27     lastButton: {
28         marginLeft: theme.spacing.unit,
29         marginRight: "20px",
30     },
31     formContainer: {
32         display: "flex",
33         flexDirection: "column",
34     },
35     textField: {
36         marginBottom: theme.spacing.unit * 3
37     },
38     createProgress: {
39         position: "absolute",
40         minWidth: "20px",
41         right: "110px"
42     },
43     dialogActions: {
44         marginBottom: theme.spacing.unit * 3
45     }
46 });
47
48 interface DialogCollectionCreateProps {
49     open: boolean;
50     handleClose: () => void;
51     onSubmit: (data: { name: string, description: string }, files: UploadFile[]) => void;
52     handleSubmit: any;
53     submitting: boolean;
54     invalid: boolean;
55     pristine: boolean;
56     files: UploadFile[];
57 }
58
59 interface TextFieldProps {
60     label: string;
61     floatinglabeltext: string;
62     className?: string;
63     input?: string;
64     meta?: any;
65 }
66
67 export const DialogCollectionCreate = compose(
68     connect((state: RootState) => ({
69         files: state.collections.uploader
70     })),
71     reduxForm({ form: 'collectionCreateDialog' }),
72     withStyles(styles))(
73     class DialogCollectionCreate extends React.Component<DialogCollectionCreateProps & DispatchProp & WithStyles<CssRules>> {
74         render() {
75             const { classes, open, handleClose, handleSubmit, onSubmit, submitting, invalid, pristine, files } = this.props;
76             const busy = submitting || files.reduce(
77                 (prev, curr) => prev + (curr.loaded > 0 && curr.loaded < curr.total ? 1 : 0), 0
78             ) > 0;
79             return (
80                 <Dialog
81                     open={open}
82                     onClose={handleClose}
83                     fullWidth={true}
84                     maxWidth='sm'
85                     disableBackdropClick={true}
86                     disableEscapeKeyDown={true}>
87                     <form onSubmit={handleSubmit((data: any) => onSubmit(data, files))}>
88                         <DialogTitle id="form-dialog-title">Create a collection</DialogTitle>
89                         <DialogContent className={classes.formContainer}>
90                             <Field name="name"
91                                     disabled={submitting}
92                                     component={this.renderTextField}
93                                     floatinglabeltext="Collection Name"
94                                     validate={COLLECTION_NAME_VALIDATION}
95                                     className={classes.textField}
96                                     label="Collection Name"/>
97                             <Field name="description"
98                                     disabled={submitting}
99                                     component={this.renderTextField}
100                                     floatinglabeltext="Description - optional"
101                                     validate={COLLECTION_DESCRIPTION_VALIDATION}
102                                     className={classes.textField}
103                                     label="Description - optional"/>
104                             <FileUpload
105                                 files={files}
106                                 disabled={busy}
107                                 onDrop={files => this.props.dispatch(collectionUploaderActions.SET_UPLOAD_FILES(files))}/>
108                         </DialogContent>
109                         <DialogActions className={classes.dialogActions}>
110                             <Button onClick={handleClose} className={classes.button} color="primary"
111                                     disabled={busy}>CANCEL</Button>
112                             <Button type="submit"
113                                     className={classes.lastButton}
114                                     color="primary"
115                                     disabled={invalid || busy || pristine}
116                                     variant="contained">
117                                 CREATE A COLLECTION
118                             </Button>
119                             {busy && <CircularProgress size={20} className={classes.createProgress}/>}
120                         </DialogActions>
121                     </form>
122                 </Dialog>
123             );
124         }
125
126         renderTextField = ({ input, label, meta: { touched, error }, ...custom }: TextFieldProps) => (
127             <TextField
128                 helperText={touched && error}
129                 label={label}
130                 className={this.props.classes.textField}
131                 error={touched && !!error}
132                 autoComplete='off'
133                 {...input}
134                 {...custom}
135             />
136         )
137     }
138 );