[ABANDONED] React/Redux front end for the Flexor social network.
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.
 
 

132 lines
4.7 KiB

import React, { FC, ChangeEvent, useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUpload } from '@fortawesome/free-solid-svg-icons'
import { BlockBlobClient, AnonymousCredential } from '@azure/storage-blob'
import { useTheme } from '../../hooks'
import { setFieldValue } from '../../actions/forms'
import { showNotification } from '../../actions/notifications'
import { getFieldValue } from '../../selectors/forms'
import { apiFetch } from '../../api/fetch'
import { MEDIA_DEFAULT_MAX_SIZE } from '../../constants'
import { AppState, AppThunkDispatch, SasResponse, NotificationType } from '../../types'
import Progress from '../../components/progress'
import FieldLabel from '../../components/controls/field-label'
interface Props {
name: string
label: string
help?: string
previewWidth?: number
maxSize?: number
}
const FileField: FC<Props> = props => {
const theme = useTheme()
const value = useSelector<AppState, string>(state => getFieldValue<string>(state, props.name, ''))
const dispatch = useDispatch<AppThunkDispatch>()
const { name, label, help, previewWidth = 128, maxSize = MEDIA_DEFAULT_MAX_SIZE } = props
const [progress, setProgress] = useState(0)
const [uploading, setUploading] = useState(false)
const [uploaded, setUploaded] = useState(false)
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, blobUrl, id } = await apiFetch<SasResponse>({ path: '/v1/sas' })
const filename = `${id}${ext}`
setUploading(true)
try {
const blockBlobClient = new BlockBlobClient(`${blobUrl}/${filename}?${sas}`, new AnonymousCredential())
await blockBlobClient.uploadBrowserData(file, {
onProgress: p => {
setProgress((p.loadedBytes / file.size) * 100)
}
})
await apiFetch({
path: '/v1/media',
method: 'post',
body: {
name: filename,
size: file.size,
type: file.type,
originalName: file.name,
}
})
dispatch(setFieldValue(name, `${blobUrl}/${filename}`))
setUploaded(true)
} catch (err) {
console.error(err)
dispatch(showNotification(NotificationType.Error, `Upload error: ${err}`))
}
setUploading(false)
}
}
const handleDelete = async () => {
if (uploaded) {
await apiFetch({
path: `/v1/media?name=${value}`,
method: 'delete',
})
}
dispatch(setFieldValue(name, ''))
}
if (uploading) {
return (
<div className="field">
<FieldLabel>{label}</FieldLabel>
<Progress value={progress} />
</div>
)
}
return (
<div className="field">
<FieldLabel>{label}</FieldLabel>
{value &&
<div style={{ padding: '1rem 0px' }}>
<img src={value} style={{ width: previewWidth }} />
<div style={{ color: theme.secondary, fontSize: '0.8rem' }}>
{value.split('/').pop()}
&nbsp;&nbsp;
(<a style={{ color: theme.red }} onClick={() => handleDelete()}>Delete</a>)
</div>
</div>
}
{!value &&
<div>
<label className="file-input" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
<input type="file" name={name} onChange={handleChange} />
<div className="icon">
<FontAwesomeIcon icon={faUpload} />
</div>
<span>Choose a file...</span>
</label>
<p className="help" style={{ color: theme.text }}>{help}</p>
</div>
}
</div>
)
}
export default FileField