refs #14161 Merge branch 'origin/14161-inputs-focus-enter-action'
[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
22 import './file-upload.css';
23 import { DOMElement, RefObject } from "react";
24
25 const styles: StyleRulesCallback<CssRules> = theme => ({
26     root: {
27     },
28     dropzone: {
29         width: "100%",
30         height: "100%",
31         overflow: "auto"
32     },
33     dropzoneWrapper: {
34         width: "100%",
35         height: "200px",
36         position: "relative",
37         border: "1px solid rgba(0, 0, 0, 0.42)"
38     },
39     container: {
40         height: "100%"
41     },
42     uploadIcon: {
43         verticalAlign: "middle"
44     }
45 });
46
47 interface FileUploadPropsData {
48     files: UploadFile[];
49     disabled: boolean;
50     onDrop: (files: File[]) => void;
51 }
52
53 interface FileUploadState {
54     focused: boolean;
55 }
56
57 export type FileUploadProps = FileUploadPropsData & WithStyles<CssRules>;
58
59 export const FileUpload = withStyles(styles)(
60     class extends React.Component<FileUploadProps, FileUploadState> {
61         constructor(props: FileUploadProps) {
62             super(props);
63             this.state = {
64                 focused: false
65             };
66         }
67         render() {
68             const { classes, onDrop, disabled, files } = this.props;
69             return <div className={"file-upload-dropzone " + classes.dropzoneWrapper}>
70                 <div className={classnames("dropzone-border-left", { "dropzone-border-left-active": this.state.focused })}/>
71                 <div className={classnames("dropzone-border-right", { "dropzone-border-right-active": this.state.focused })}/>
72                 <div className={classnames("dropzone-border-top", { "dropzone-border-top-active": this.state.focused })}/>
73                 <div className={classnames("dropzone-border-bottom", { "dropzone-border-bottom-active": this.state.focused })}/>
74                 <Dropzone className={classes.dropzone}
75                     onDrop={files => onDrop(files)}
76                     onClick={(e) => {
77                         const el = document.getElementsByClassName("file-upload-dropzone")[0];
78                         const inputs = el.getElementsByTagName("input");
79                         if (inputs.length > 0) {
80                             inputs[0].focus();
81                         }
82                     }}
83                     disabled={disabled}
84                     inputProps={{
85                         onFocus: () => {
86                             this.setState({
87                                 focused: true
88                             });
89                         },
90                         onBlur: () => {
91                             this.setState({
92                                 focused: false
93                             });
94                         }
95                 }}>
96                     {files.length === 0 &&
97                         <Grid container justify="center" alignItems="center" className={classes.container}>
98                             <Grid item component={"span"}>
99                                 <Typography variant={"subheading"}>
100                                     <CloudUploadIcon className={classes.uploadIcon} /> Drag and drop data or click to browse
101                             </Typography>
102                             </Grid>
103                         </Grid>}
104                     {files.length > 0 &&
105                         <Table style={{ width: "100%" }}>
106                             <TableHead>
107                                 <TableRow>
108                                     <TableCell>File name</TableCell>
109                                     <TableCell>File size</TableCell>
110                                     <TableCell>Upload speed</TableCell>
111                                     <TableCell>Upload progress</TableCell>
112                                 </TableRow>
113                             </TableHead>
114                             <TableBody>
115                                 {files.map(f =>
116                                     <TableRow key={f.id}>
117                                         <TableCell>{f.file.name}</TableCell>
118                                         <TableCell>{formatFileSize(f.file.size)}</TableCell>
119                                         <TableCell>{formatUploadSpeed(f.prevLoaded, f.loaded, f.prevTime, f.currentTime)}</TableCell>
120                                         <TableCell>{formatProgress(f.loaded, f.total)}</TableCell>
121                                     </TableRow>
122                                 )}
123                             </TableBody>
124                         </Table>
125                     }
126                 </Dropzone>
127             </div>;
128         }
129     }
130 );