apeman-react-upload
Version:
apeman react package for file upload components.
250 lines (228 loc) • 6.01 kB
JSX
/**
* apeman react package for file upload components.
* @class ApUpload
*/
import React, { PropTypes as types } from 'react'
import classnames from 'classnames'
import async from 'async'
import path from 'path'
import uuid from 'uuid'
import { ApImage } from 'apeman-react-image'
import { ApSpinner } from 'apeman-react-spinner'
import { ApButton } from 'apeman-react-button'
/** @lends ApUpload */
const ApUpload = React.createClass({
// --------------------
// Specs
// --------------------
propTypes: {
/** Name of input */
name: types.string,
/** DOM id of input */
id: types.string,
/** Allow multiple upload */
multiple: types.bool,
/** Handler for change event */
onChange: types.func,
/** Handler for load event */
onLoad: types.func,
/** Handler for error event */
onError: types.func,
/** Image width */
width: types.number,
/** Image height */
height: types.number,
/** Guide text */
text: types.string,
/** Accept file type */
accept: types.string,
/** Guide icon */
icon: types.string,
/** Icon for close images */
closeIcon: types.string,
/** Spinner theme */
spinner: types.string,
/** Value of input */
value: types.oneOfType([
types.string,
types.array
])
},
mixins: [],
statics: {
readFile (file, callback) {
let reader = new window.FileReader()
reader.onerror = function onerror (err) {
callback(err)
}
reader.onload = function onload (ev) {
callback(null, ev.target.result)
}
reader.readAsDataURL(file)
},
isImageUrl (url) {
const imageExtensions = [
'.jpg',
'.jpeg',
'.svg',
'.gif',
'.png'
]
return /^data:image/.test(url) || !!~imageExtensions.indexOf(path.extname(url))
}
},
getInitialState () {
const s = this
let { props } = s
let hasValue = props.value && props.value.length > 0
return {
spinning: false,
error: null,
urls: hasValue ? [].concat(props.value) : null
}
},
getDefaultProps () {
return {
name: null,
id: `ap-upload-${uuid.v4()}`,
multiple: false,
width: 180,
height: 180,
accept: null,
text: 'Upload file',
icon: 'fa fa-cloud-upload',
closeIcon: 'fa fa-close',
spinnerIcon: ApSpinner.DEFAULT_THEME,
onChange: null,
onLoad: null,
onError: null
}
},
render () {
const s = this
let { state, props } = s
let { width, height } = props
return (
<div className={classnames('ap-upload', props.className)}
style={Object.assign({}, props.style)}>
<input type='file'
className='ap-upload-input'
multiple={ props.multiple }
name={ props.name }
id={ props.id }
accept={ props.accept }
onChange={s.handleChange}
style={{ width, height }}
/>
<label className='ap-upload-label' htmlFor={ props.id }>
<span className='ap-upload-aligner'>
</span>
<span className='ap-upload-label-inner'>
<i className={ classnames('ap-upload-icon', props.icon) }/>
<span className='ap-upload-text'>{props.text}</span>
{ props.children }
</span>
</label>
{ s._renderPreviewImage(state.urls, width, height) }
{ s._renderRemoveButton(!!(state.urls && state.urls.length > 0), props.closeIcon) }
{ s._renderSpinner(state.spinning, props.spinner) }
</div>
)
},
// --------------------
// Lifecycle
// --------------------
componentWillReceiveProps (nextProps) {
const s = this
const { props } = s
let { value } = nextProps
let hasValue = value && value.length > 0
if (hasValue && (props.value !== value)) {
s.setState({ urls: [].concat(value) })
}
},
// ------------------
// Custom
// ------------------
handleChange (e) {
const s = this
let { props } = s
let { target } = e
let files = Array.prototype.slice.call(target.files, 0)
let { onChange, onError, onLoad } = props
s.setState({ spinning: true })
if (onChange) {
onChange(e)
}
async.concat(files, ApUpload.readFile, (error, urls) => {
s.setState({
spinning: false,
error,
urls
})
if (error) {
if (onError) {
onError(error)
}
} else {
if (onLoad) {
let loaded = Object.assign({ urls, target })
onLoad(loaded)
}
}
})
},
handleRemove () {
const s = this
let { props } = s
let { onLoad } = props
s.setState({
error: null,
urls: null
})
if (onLoad) {
onLoad([])
}
},
// ------------------
// Private
// ------------------
_renderSpinner (spinning, theme) {
const s = this
return (
<ApSpinner enabled={spinning} theme={theme}>
</ApSpinner>
)
},
_renderRemoveButton (removable, icon) {
const s = this
if (!removable) {
return null
}
return (
<ApButton onTap={ s.handleRemove } className='ap-upload-remove-button'>
<i className={ classnames('ap-upload-remove-icon', icon) }/>
</ApButton>
)
},
_renderPreviewImage (urls, width, height) {
if (!urls) {
return null
}
const s = this
return urls
.filter((url) => ApUpload.isImageUrl(url))
.map((url, i) => (
<ApImage key={ url }
src={ url }
height={ height }
width={ width }
className={ classnames('ap-upload-preview-image') }
style={ { left: `${i * 10}%`, top: `${i * 10}%` } }
scale='fit'>
</ApImage>
))
}
})
export default ApUpload