Created
August 6, 2017 12:41
-
-
Save dmytro-y-dev/285fc877e2712dea6aedd9a9d4184e89 to your computer and use it in GitHub Desktop.
Simple React file input for admin-on-rest that supports uploading of exactly one file and automatically encodes uploads to base64
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import PropTypes from 'prop-types'; | |
import Dropzone from 'react-dropzone'; | |
import {translate} from 'admin-on-rest'; | |
import FileInputPreview from "admin-on-rest/lib/mui/input/FileInputPreview"; | |
const defaultStyle = { | |
dropZone: { | |
background: '#efefef', | |
cursor: 'pointer', | |
padding: '1rem', | |
textAlign: 'center', | |
color: '#999', | |
}, | |
preview: { | |
float: 'left', | |
}, | |
}; | |
export class SingleFileInput extends Component { | |
static propTypes = { | |
accept: PropTypes.string, | |
children: PropTypes.element, | |
disableClick: PropTypes.bool, | |
elStyle: PropTypes.object, | |
input: PropTypes.object, | |
itemStyle: PropTypes.object, | |
labelSingle: PropTypes.string, | |
maxSize: PropTypes.number, | |
minSize: PropTypes.number, | |
removeStyle: PropTypes.object, | |
style: PropTypes.object, | |
placeholder: PropTypes.node, | |
}; | |
static defaultProps = { | |
addLabel: true, | |
addField: true, | |
itemStyle: {}, | |
labelSingle: 'aor.input.file.upload_single', | |
onUpload: () => {}, | |
removeStyle: { display: 'inline-block' }, | |
}; | |
constructor(props) { | |
super(props); | |
let file = props.input.value; | |
this.state = { | |
file: this.transformFile(file), | |
}; | |
} | |
componentWillReceiveProps(nextProps) { | |
let file = nextProps.input.value; | |
this.setState({ file: this.transformFile(file) }); | |
} | |
onDrop = async (files) => { | |
const updatedFile = await this.setUploadedFile(files[0]); | |
this.setState({ file: updatedFile }); | |
this.props.input.onChange(updatedFile); | |
} | |
onRemove = file => () => { | |
this.setState({ file: null }); | |
this.props.input.onChange(null); | |
} | |
setUploadedFile = (file) => new Promise(async (resolve, reject) => { | |
const convertFileToBase64 = file => new Promise((resolve, reject) => { | |
if (!(file instanceof File)) { | |
resolve(undefined); | |
} | |
const reader = new FileReader(); | |
reader.readAsDataURL(file); | |
reader.onload = () => resolve(reader.result); | |
reader.onerror = reject; | |
}); | |
let transformedFile = this.transformFile(file); | |
transformedFile.content = await convertFileToBase64(file); | |
resolve(transformedFile); | |
}) | |
// turn a browser dropped file structure into expected structure | |
transformFile = (file) => { | |
if (!(file instanceof File)) { | |
return file; | |
} | |
const { title } = React.Children.toArray(this.props.children)[0].props; | |
const transformedFile = { | |
preview: file.preview, | |
name: file.name, | |
mimetype: file.type, | |
size: file.size, | |
lastModified: file.lastModified | |
}; | |
if (title) { | |
transformedFile[title] = file.name; | |
} | |
return transformedFile; | |
} | |
label() { | |
const { translate, placeholder, labelSingle } = this.props; | |
if (placeholder) { | |
return placeholder; | |
} | |
return ( | |
<p>{translate(labelSingle)}</p> | |
); | |
} | |
render() { | |
const { | |
accept, | |
children, | |
disableClick, | |
elStyle, | |
itemStyle, | |
maxSize, | |
minSize, | |
style, | |
removeStyle, | |
} = this.props; | |
const finalStyle = { | |
...defaultStyle, | |
...style, | |
}; | |
return ( | |
<div style={elStyle}> | |
<Dropzone | |
onDrop={this.onDrop} | |
accept={accept} | |
disableClick={disableClick} | |
maxSize={maxSize} | |
minSize={minSize} | |
multiple={false} | |
style={finalStyle.dropZone} | |
> | |
{this.label()} | |
</Dropzone> | |
{ children && this.state.file && ( | |
<div className="previews"> | |
<FileInputPreview | |
file={this.state.file} | |
itemStyle={itemStyle} | |
onRemove={this.onRemove(this.state.file)} | |
removeStyle={removeStyle} | |
> | |
{React.cloneElement(children, { | |
record: this.state.file, | |
style: defaultStyle.preview, | |
})} | |
</FileInputPreview> | |
</div> | |
) } | |
</div> | |
); | |
} | |
} | |
export default translate(SingleFileInput); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment