Merge branch '14941-public-favorites-initialize-data-explorer-and-routing'
[arvados.git] / src / components / file-upload / file-upload.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 * as classnames from 'classnames';
7 import {
8     Grid,
9     StyleRulesCallback,
10     Table, TableBody, TableCell, TableHead, TableRow,
11     Typography,
12     WithStyles
13 } from '@material-ui/core';
14 import { withStyles } from '@material-ui/core';
15 import Dropzone from 'react-dropzone';
16 import { CloudUploadIcon } from "../icon/icon";
17 import { formatFileSize, formatProgress, formatUploadSpeed } from "~/common/formatters";
18 import { UploadFile } from '~/store/file-uploader/file-uploader-actions';
19
20 type CssRules = "root" | "dropzone" | "dropzoneWrapper" | "container" | "uploadIcon"
21     | "dropzoneBorder" | "dropzoneBorderLeft" | "dropzoneBorderRight" | "dropzoneBorderTop" | "dropzoneBorderBottom"
22     | "dropzoneBorderHorzActive" | "dropzoneBorderVertActive";
23
24 const styles: StyleRulesCallback<CssRules> = theme => ({
25     root: {
26     },
27     dropzone: {
28         width: "100%",
29         height: "100%",
30         overflow: "auto"
31     },
32     dropzoneWrapper: {
33         width: "100%",
34         height: "200px",
35         position: "relative",
36         border: "1px solid rgba(0, 0, 0, 0.42)"
37     },
38     dropzoneBorder: {
39         content: "",
40         position: "absolute",
41         transition: "transform 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms",
42         pointerEvents: "none",
43         backgroundColor: "#6a1b9a"
44     },
45     dropzoneBorderLeft: {
46         left: -1,
47         top: -1,
48         bottom: -1,
49         width: 2,
50         transform: "scaleY(0)",
51     },
52     dropzoneBorderRight: {
53         right: -1,
54         top: -1,
55         bottom: -1,
56         width: 2,
57         transform: "scaleY(0)",
58     },
59     dropzoneBorderTop: {
60         left: 0,
61         right: 0,
62         top: -1,
63         height: 2,
64         transform: "scaleX(0)",
65     },
66     dropzoneBorderBottom: {
67         left: 0,
68         right: 0,
69         bottom: -1,
70         height: 2,
71         transform: "scaleX(0)",
72     },
73     dropzoneBorderHorzActive: {
74         transform: "scaleY(1)"
75     },
76     dropzoneBorderVertActive: {
77         transform: "scaleX(1)"
78     },
79     container: {
80         height: "100%"
81     },
82     uploadIcon: {
83         verticalAlign: "middle"
84     }
85 });
86
87 interface FileUploadPropsData {
88     files: UploadFile[];
89     disabled: boolean;
90     onDrop: (files: File[]) => void;
91 }
92
93 interface FileUploadState {
94     focused: boolean;
95 }
96
97 export type FileUploadProps = FileUploadPropsData & WithStyles<CssRules>;
98
99 export const FileUpload = withStyles(styles)(
100     class extends React.Component<FileUploadProps, FileUploadState> {
101         constructor(props: FileUploadProps) {
102             super(props);
103             this.state = {
104                 focused: false
105             };
106         }
107         render() {
108             const { classes, onDrop, disabled, files } = this.props;
109             return <div className={"file-upload-dropzone " + classes.dropzoneWrapper}>
110                 <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderLeft, { [classes.dropzoneBorderHorzActive]: this.state.focused })}/>
111                 <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderRight, { [classes.dropzoneBorderHorzActive]: this.state.focused })}/>
112                 <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderTop, { [classes.dropzoneBorderVertActive]: this.state.focused })}/>
113                 <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderBottom, { [classes.dropzoneBorderVertActive]: this.state.focused })}/>
114                 <Dropzone className={classes.dropzone}
115                     onDrop={files => onDrop(files)}
116                     onClick={() => {
117                         const el = document.getElementsByClassName("file-upload-dropzone")[0];
118                         const inputs = el.getElementsByTagName("input");
119                         if (inputs.length > 0) {
120                             inputs[0].focus();
121                         }
122                     }}
123                     disabled={disabled}
124                     inputProps={{
125                         onFocus: () => {
126                             this.setState({
127                                 focused: true
128                             });
129                         },
130                         onBlur: () => {
131                             this.setState({
132                                 focused: false
133                             });
134                         }
135                 }}>
136                     {files.length === 0 &&
137                         <Grid container justify="center" alignItems="center" className={classes.container}>
138                             <Grid item component={"span"}>
139                                 <Typography variant='subtitle1'>
140                                     <CloudUploadIcon className={classes.uploadIcon} /> Drag and drop data or click to browse
141                             </Typography>
142                             </Grid>
143                         </Grid>}
144                     {files.length > 0 &&
145                         <Table style={{ width: "100%" }}>
146                             <TableHead>
147                                 <TableRow>
148                                     <TableCell>File name</TableCell>
149                                     <TableCell>File size</TableCell>
150                                     <TableCell>Upload speed</TableCell>
151                                     <TableCell>Upload progress</TableCell>
152                                 </TableRow>
153                             </TableHead>
154                             <TableBody>
155                                 {files.map(f =>
156                                     <TableRow key={f.id}>
157                                         <TableCell>{f.file.name}</TableCell>
158                                         <TableCell>{formatFileSize(f.file.size)}</TableCell>
159                                         <TableCell>{formatUploadSpeed(f.prevLoaded, f.loaded, f.prevTime, f.currentTime)}</TableCell>
160                                         <TableCell>{formatProgress(f.loaded, f.total)}</TableCell>
161                                     </TableRow>
162                                 )}
163                             </TableBody>
164                         </Table>
165                     }
166                 </Dropzone>
167             </div>;
168         }
169     }
170 );