import * as React from 'react'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; import CloudUploadIcon from '@material-ui/icons/CloudUpload'; import ErrorIcon from '@material-ui/icons/Error'; const NO_FILE_ERROR_CODE = 1; const BAD_EXTENSION_ERROR_CODE = 2; const TOO_BIG_ERROR_CODE = 3; const UPLOADING_ERROR_CODE = 4; export type ImageLoaded = { file: Object; dataUrl: string; }; export type ImageUploaded = { url: string; }; export type ImageUploadType = ( file: File, reportProgress: (progress: number) => void ) => Promise; export type ImageUploadProps = { imageLoaded?: (image: ImageLoaded) => void; imageUpload: ImageUploadType; imageUploadError?: (errorCode: number) => void; imageUploaded: (resp: ImageUploaded) => void; buttonContent?: JSX.Element | string; icon?: JSX.Element; style?: React.CSSProperties; maxFileSize?: number; allowedExtensions?: string[]; }; export type ImageUploadState = { isUploading: boolean; hasError: boolean; errorText: string; progress: number; }; class ImageUpload extends React.Component { static defaultProps = { buttonContent: 'Upload image', icon: , allowedExtensions: ['jpg', 'jpeg', 'png'], maxFileSize: 5242880, }; fileInput: HTMLInputElement; state: ImageUploadState = { isUploading: false, hasError: false, errorText: '', progress: 0, }; props: ImageUploadProps; hasExtension = (fileName: string) => { const patternPart = this.props.allowedExtensions ? this.props.allowedExtensions.map(a => a.toLowerCase()).join('|') : ''; const pattern = '(' + patternPart.replace(/\./g, '\\.') + ')$'; return new RegExp(pattern, 'i').test(fileName.toLowerCase()); } handleError = (errorCode: number) => { let errorText = ''; switch (errorCode) { case NO_FILE_ERROR_CODE: errorText = 'No file selected'; break; case BAD_EXTENSION_ERROR_CODE: errorText = 'Bad file type'; break; case TOO_BIG_ERROR_CODE: errorText = 'Too big'; break; case UPLOADING_ERROR_CODE: errorText = 'Error while uploading'; break; default: errorText = 'Unknown error'; break; } // Need to flick "isUploading" because otherwise the handler doesn't fire properly this.setState({ hasError: true, errorText, isUploading: true }, () => this.setState({ isUploading: false }) ); setTimeout(() => this.setState({ hasError: false, errorText: '' }), 5000); } handleFileSelected: React.ChangeEventHandler = e => { if (!e.target.files || !e.target.files[0]) { this.handleError(NO_FILE_ERROR_CODE); return; } const file = e.target.files[0]; if (!this.hasExtension(file.name)) { this.handleError(BAD_EXTENSION_ERROR_CODE); return; } if (file.size > this.props.maxFileSize) { this.handleError(TOO_BIG_ERROR_CODE); return; } if (this.props.imageLoaded) { this.readFile(file).then(data => this.props.imageLoaded(data)); } if (this.props.imageUpload) { this.setState({ isUploading: true }); this.props .imageUpload(file, this.handleReportProgress) .then(resp => { this.setState({ progress: undefined, isUploading: false }); this.props.imageUploaded && this.props.imageUploaded(resp); }) .catch(error => { this.setState({ isUploading: false }); this.props.imageUploadError && this.props.imageUploadError(error); }); } } readFile(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); // Read the image via FileReader API and save image result in state. reader.onload = function(e: ProgressEvent) { // Add the file name to the data URL // tslint:disable-next-line:no-any let dataUrl: string = (e.target as any).result; dataUrl = dataUrl.replace(';base64', `;name=${file.name};base64`); resolve({ file, dataUrl }); }; reader.readAsDataURL(file); }); } handleFileUploadClick: React.MouseEventHandler = () => this.fileInput.click() handleReportProgress = (progress: number) => this.setState({ progress }); renderChildren = () => { if (this.state.isUploading) { return ; } if (this.state.hasError) { return ( {this.state.errorText} ); } return ( {this.props.buttonContent} {this.props.icon} ); } render() { return ( {!this.state.isUploading && ( (this.fileInput = fileInput)} type="file" onChange={this.handleFileSelected} /> )} ); } } export default ImageUpload;