// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 import React from 'react'; import classnames from 'classnames'; import { Grid, StyleRulesCallback, Table, TableBody, TableCell, TableHead, TableRow, Typography, WithStyles, IconButton } from '@material-ui/core'; import { withStyles } from '@material-ui/core'; import Dropzone from 'react-dropzone'; import { CloudUploadIcon, RemoveIcon } from "../icon/icon"; import { formatFileSize, formatProgress, formatUploadSpeed } from "common/formatters"; import { UploadFile } from 'store/file-uploader/file-uploader-actions'; type CssRules = "root" | "dropzone" | "dropzoneWrapper" | "container" | "uploadIcon" | "dropzoneBorder" | "dropzoneBorderLeft" | "dropzoneBorderRight" | "dropzoneBorderTop" | "dropzoneBorderBottom" | "dropzoneBorderHorzActive" | "dropzoneBorderVertActive" | "deleteButton" | "deleteButtonDisabled" | "deleteIcon"; const styles: StyleRulesCallback<CssRules> = theme => ({ root: { }, dropzone: { width: "100%", height: "100%", overflow: "auto" }, dropzoneWrapper: { width: "100%", height: "200px", position: "relative", border: "1px solid rgba(0, 0, 0, 0.42)", boxSizing: 'border-box', }, dropzoneBorder: { content: "", position: "absolute", transition: "transform 200ms cubic-bezier(0.0, 0, 0.2, 1) 0ms", pointerEvents: "none", backgroundColor: "#6a1b9a" }, dropzoneBorderLeft: { left: -1, top: -1, bottom: -1, width: 2, transform: "scaleY(0)", }, dropzoneBorderRight: { right: -1, top: -1, bottom: -1, width: 2, transform: "scaleY(0)", }, dropzoneBorderTop: { left: 0, right: 0, top: -1, height: 2, transform: "scaleX(0)", }, dropzoneBorderBottom: { left: 0, right: 0, bottom: -1, height: 2, transform: "scaleX(0)", }, dropzoneBorderHorzActive: { transform: "scaleY(1)" }, dropzoneBorderVertActive: { transform: "scaleX(1)" }, container: { height: "100%" }, uploadIcon: { verticalAlign: "middle" }, deleteButton: { cursor: "pointer" }, deleteButtonDisabled: { cursor: "not-allowed" }, deleteIcon: { marginLeft: "-6px" } }); interface FileUploadPropsData { files: UploadFile[]; disabled: boolean; onDrop: (files: File[]) => void; onDelete: (file: UploadFile) => void; } interface FileUploadState { focused: boolean; } export type FileUploadProps = FileUploadPropsData & WithStyles<CssRules>; export const FileUpload = withStyles(styles)( class extends React.Component<FileUploadProps, FileUploadState> { constructor(props: FileUploadProps) { super(props); this.state = { focused: false }; } onDelete = (event: React.MouseEvent<HTMLTableCellElement>, file: UploadFile): void => { const { onDelete, disabled } = this.props; event.stopPropagation(); if (!disabled) { onDelete(file); } let interval = setInterval(() => { const key = Object.keys((window as any).cancelTokens).find(key => key.indexOf(file.file.name) > -1); if (key) { clearInterval(interval); (window as any).cancelTokens[key](); delete (window as any).cancelTokens[key]; } }, 100); } render() { const { classes, onDrop, disabled, files } = this.props; return <div className={"file-upload-dropzone " + classes.dropzoneWrapper}> <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderLeft, { [classes.dropzoneBorderHorzActive]: this.state.focused })} /> <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderRight, { [classes.dropzoneBorderHorzActive]: this.state.focused })} /> <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderTop, { [classes.dropzoneBorderVertActive]: this.state.focused })} /> <div className={classnames(classes.dropzoneBorder, classes.dropzoneBorderBottom, { [classes.dropzoneBorderVertActive]: this.state.focused })} /> <Dropzone className={classes.dropzone} onDrop={files => onDrop(files)} onClick={() => { const el = document.getElementsByClassName("file-upload-dropzone")[0]; const inputs = el.getElementsByTagName("input"); if (inputs.length > 0) { inputs[0].focus(); } }} data-cy="drag-and-drop" disabled={disabled} inputProps={{ onFocus: () => { this.setState({ focused: true }); }, onBlur: () => { this.setState({ focused: false }); } }}> {files.length === 0 && <Grid container justify="center" alignItems="center" className={classes.container}> <Grid item component={"span"}> <Typography variant='subtitle1'> <CloudUploadIcon className={classes.uploadIcon} /> Drag and drop data or click to browse </Typography> </Grid> </Grid>} {files.length > 0 && <Table style={{ width: "100%" }}> <TableHead> <TableRow> <TableCell>File name</TableCell> <TableCell>File size</TableCell> <TableCell>Upload speed</TableCell> <TableCell>Upload progress</TableCell> <TableCell>Delete</TableCell> </TableRow> </TableHead> <TableBody> {files.map(f => <TableRow key={f.id}> <TableCell>{f.file.name}</TableCell> <TableCell>{formatFileSize(f.file.size)}</TableCell> <TableCell>{formatUploadSpeed(f.prevLoaded, f.loaded, f.prevTime, f.currentTime)}</TableCell> <TableCell>{formatProgress(f.loaded, f.total)}</TableCell> <TableCell> <IconButton aria-label="Remove" onClick={(event: React.MouseEvent<HTMLTableCellElement>) => this.onDelete(event, f)} className={disabled ? classnames(classes.deleteButtonDisabled, classes.deleteIcon) : classnames(classes.deleteButton, classes.deleteIcon)} > <RemoveIcon /> </IconButton> </TableCell> </TableRow> )} </TableBody> </Table> } </Dropzone> </div>; } } );