You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.7 KiB
135 lines
4.7 KiB
import React, { FC, ChangeEvent, useState } from 'react'
|
|
import { useSelector, useDispatch } from 'react-redux'
|
|
import classNames from 'classnames'
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|
import { faUpload } from '@fortawesome/free-solid-svg-icons'
|
|
import { uploadBrowserDataToBlockBlob, Aborter, BlockBlobURL, AnonymousCredential } from '@azure/storage-blob'
|
|
|
|
import { useConfig } from 'src/hooks'
|
|
import { setFieldValue } from 'src/actions/forms'
|
|
import { showNotification } from 'src/actions/notifications'
|
|
import { getFieldValue } from 'src/selectors/forms'
|
|
import { apiFetch } from 'src/api/fetch'
|
|
import { AppState, ClassDictionary, SasResponse, NotificationType } from 'src/types'
|
|
|
|
interface Props {
|
|
name: string
|
|
label: string
|
|
help?: string
|
|
previewWidth?: number
|
|
maxSize?: number
|
|
}
|
|
|
|
const FileField: FC<Props> = props => {
|
|
const value = useSelector<AppState, boolean>(state => getFieldValue<boolean>(state, props.name, false))
|
|
const config = useConfig()
|
|
const dispatch = useDispatch()
|
|
|
|
const { name, label, help, previewWidth = 128, maxSize = config.media.defaultMaxSize } = props
|
|
|
|
const [progress, setProgress] = useState(0)
|
|
const [uploading, setUploading] = useState(false)
|
|
const [uploaded, setUploaded] = useState(false)
|
|
|
|
const classes: ClassDictionary = {
|
|
file: true,
|
|
'is-primary': true,
|
|
'has-name': !!value,
|
|
}
|
|
|
|
const handleChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
if (event.target.files) {
|
|
const file = event.target.files[0]
|
|
|
|
if (file.size > maxSize) {
|
|
const maxSizeString = Math.round(maxSize / 1024 / 1024)
|
|
dispatch(showNotification(NotificationType.Error, `Files must be less than ${maxSizeString} MBs`))
|
|
return
|
|
}
|
|
|
|
const ext = file.name.substring(file.name.lastIndexOf('.'))
|
|
const { sas, id } = await apiFetch<SasResponse>({ path: '/api/sas' })
|
|
const filename = `${id}${ext}`
|
|
const blobURL = new BlockBlobURL(`${config.blobUrl}${filename}?${sas}`, BlockBlobURL.newPipeline(new AnonymousCredential()))
|
|
|
|
setUploading(true)
|
|
|
|
await uploadBrowserDataToBlockBlob(Aborter.none, file, blobURL, {
|
|
blockSize: 4 * 1024 * 1024,
|
|
progress: p => {
|
|
setProgress((p.loadedBytes / file.size) * 100)
|
|
}
|
|
})
|
|
|
|
await apiFetch({
|
|
path: '/api/media',
|
|
method: 'post',
|
|
body: {
|
|
name: filename,
|
|
size: file.size,
|
|
type: file.type,
|
|
originalName: file.name,
|
|
}
|
|
})
|
|
|
|
dispatch(setFieldValue(name, filename))
|
|
setUploaded(true)
|
|
setUploading(false)
|
|
}
|
|
}
|
|
|
|
const handleDelete = async () => {
|
|
if (uploaded) {
|
|
await apiFetch({
|
|
path: '/api/media/delete',
|
|
method: 'post',
|
|
body: {
|
|
name: value,
|
|
}
|
|
})
|
|
}
|
|
|
|
dispatch(setFieldValue(name, ''))
|
|
}
|
|
|
|
if (uploading) {
|
|
return (
|
|
<div className="field">
|
|
<label className="label">{label}</label>
|
|
<progress className="progress is-success" value={progress} max="100">{progress}%</progress>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div className="field">
|
|
<label className="label">{label}</label>
|
|
{value &&
|
|
<div style={{ padding: '10px 0px' }}>
|
|
<img src={`${config.blobUrl}${value}`} style={{ width: previewWidth }} />
|
|
<br />
|
|
<a className="is-danger is-size-7" onClick={() => handleDelete()}>Delete</a>
|
|
</div>
|
|
}
|
|
<div className={classNames(classes)}>
|
|
<label className="file-label">
|
|
<input className="file-input" type="file" name={name} onChange={handleChange} />
|
|
<span className="file-cta">
|
|
<span className="file-icon">
|
|
<FontAwesomeIcon icon={faUpload} />
|
|
</span>
|
|
<span className="file-label">
|
|
Upload
|
|
</span>
|
|
</span>
|
|
{value && <span className="file-name">{value}</span>}
|
|
</label>
|
|
</div>
|
|
<p className="help">{help}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default FileField
|