react-dropit
Version:
Uploading files is now easier :))
378 lines (252 loc) • 6.87 kB
JavaScript
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