UNPKG

react-dropit

Version:
378 lines (252 loc) 6.87 kB
import nanoid from 'nanoid' import Thumb from './thumb' import 'babel-polyfill' import $ from 'jquery' import '../sass' class Error extends React.Component { componentDidMount () { $('.react-dropit-error').fadeIn() setTimeout(() => { $('.react-dropit-error').fadeOut(() => { this.props.removeError() }) }, 10000) } render () { return <span style={{display: 'none'}} className="react-dropit-error">{this.props.err}</span> } } export class DropIt extends React.Component { state = { id: nanoid(5), files: [], className: '', err: '' } componentDidMount () { this.initValue() } initValue = () => { const { value, multiple } = this.props let files = [] if (multiple) { if (Array.isArray(value)) files = value } else { files = [value] } files = files.filter(file => file).map(value => ({id: nanoid(5), value})) this.setState({files}) } getFiles = (files) => { const newFiles = [] for (let file of files) { if (typeof file == 'object') newFiles.push(file) } return newFiles } checkType = (fileType) => { const { type } = this.props if (type) { if (type.constructor.name === 'RegExp') return type.test(fileType) if (type === 'image') return fileType.indexOf('image/') === 0 } return true; } filterFiles = async (files) => { let { size, errorSizeMsg, errorTypeMsg, multiple } = this.props if (!multiple) { await this.setState({files: []}) files = files.splice(0, 1) }; if (isNaN(size) || size < 1) size = 1024 let sizeInByte = size * 1024 files = files.filter(file => { if (file.size > sizeInByte) { this.setState({err: errorSizeMsg || 'Max file size: 1MB'}) return false } if (!this.checkType(file.type)) { this.setState({err: errorTypeMsg || 'File format is incorrect.'}) return false } return true }) return files } allowAddMore = () => { let { maxFiles, errorMaxFilesMsg } = this.props if (isNaN(maxFiles) || maxFiles > 100 || maxFiles < 1) maxFiles = 10; if (maxFiles > this.state.files.length) return true this.setState({err: errorMaxFilesMsg || `Max files: ${maxFiles}`}); } onDrop = (e) => { e.preventDefault() this.setState({onDragEnterClassName: ''}) let newFiles = this.getFiles(e.dataTransfer.files) this.process(newFiles) } process = async (newFiles) => { if (!this.allowAddMore()) return; newFiles = await this.filterFiles(newFiles) this.updateState(newFiles) } updateState = newFiles => { const files = [...this.state.files] newFiles = newFiles.map(file => { file = { id: nanoid(5), value: file } files.unshift(file) return file }) this.setState({files}, () => { if (this.props.autoUpload) this.upload(newFiles) else this.callExternalOnchange() }) } progressHandlingFunction = ({loaded, total, id}) => { this.setState({ [id]: Math.ceil(loaded / total) * 100 }) } uploadFiles = (newFiles, cb) => { const length = newFiles.length , uploadedFiles = [] const finish = ({id, url}) => { uploadedFiles.push({id, value: url}) if (uploadedFiles.length == length && typeof cb == 'function') cb(uploadedFiles) } const { uploadUrl } = this.props for (let file of newFiles) { const { id, value } = file const form = new FormData(); form.append('file', value) $.ajax({ url: uploadUrl || '/upload', type: 'post', processData: false, contentType: false, success: (res) => { finish({id, url: res.url}) }, error: () => { finish({id, url: ''}) }, data: form, xhr: () => { const xhr = $.ajaxSettings.xhr(); if(xhr.upload){ // check if upload property exists xhr.upload.addEventListener( 'progress', ({loaded, total}) => this.progressHandlingFunction({loaded, total, id}), false ); // for handling the progress of the upload } return xhr; } }) } } upload = (newFiles) => { const { upload } = this.props let uploadFiles = this.uploadFiles if (typeof upload === 'function') uploadFiles = upload; uploadFiles(newFiles, (result) => { if (!Array.isArray(result)) return; // update files' url const files = [...this.state.files] for (let file of result) { for (let key in files) { if (files[key].id === file.id) { files[key].value = file.value } } } this.setState({files}, this.callExternalOnchange) }) } onDragOver = e => { e.preventDefault() e.stopPropagation() } removeFile = id => { const files = this.state.files.filter(file => file.id !== id) this.setState({files}, this.callExternalOnchange) } callExternalOnchange = () => { const { onChange, multiple } = this.props if (typeof onChange === 'function') { if (multiple) onChange(this.state.files.map(file => file.value)); else onChange(this.state.files[0].value) } } renderThumbs = () => { const { files } = this.state , { type, thumbnailClassName, thumbnail: Thumbnail } = this.props; return Array.isArray(files) && files.map(file => { let ThumbComponent = Thumb, percent = this.state[file.id] if (Thumbnail) ThumbComponent = Thumbnail return <ThumbComponent percent={percent} className={thumbnailClassName} removeFile={this.removeFile} key={file.id} {...file} /> }) } onChange = evt => { const newFiles = this.getFiles(evt.target.files) this.process(newFiles) } onDragEnter = (e) => { e.preventDefault() this.setState({ onDragEnterClassName: 'react-dropit' }) } onDragLeave = (e) => { e.preventDefault() this.setState({ onDragEnterClassName: '' }) } render () { const { err, onDragEnterClassName, files = [] } =this.state , { multiple, placeholder, thumbnailContainerClassName, className, dropzoneClassName } = this.props return ( <div className={`${className || 'react-dropit'}`}> {err && <Error err={err} removeError={() => this.setState({err: ''})} />} <div onDragLeave={this.onDragLeave} onDragEnter={this.onDragEnter} className={`${dropzoneClassName || 'react-dropit-dropzone'} ${onDragEnterClassName || ''}`} onDrop={this.onDrop} onDragOver={this.onDragOver}> <input multiple onChange={this.onChange} style={{display: 'none'}} id={this.state.id} type="file" name="files[]" /> { <label htmlFor={this.state.id}> { ((!multiple && (!files || !files.length)) || multiple) && (placeholder || 'Drop or click here') } </label> } {!multiple && this.renderThumbs()} </div> { multiple && ( <div className={thumbnailContainerClassName || 'react-dropit-tn-conatiner'}> {this.renderThumbs()} </div> ) } </div> ) } } export default DropIt