105 lines
3.7 KiB
TypeScript
105 lines
3.7 KiB
TypeScript
|
|
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState, type RefObject } from 'react'
|
||
|
|
import VectorIconTop from '../../vectors/top.svg'
|
||
|
|
import './styles/File.css'
|
||
|
|
import type { BackendLimit } from '../../functions/BackendTypes'
|
||
|
|
|
||
|
|
export interface PropsForInputFile {
|
||
|
|
limits: BackendLimit['upload'] | undefined
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface HandleForInputFile {
|
||
|
|
getPreview: () => HTMLVideoElement | HTMLImageElement | undefined
|
||
|
|
getValue: () => File | undefined
|
||
|
|
}
|
||
|
|
|
||
|
|
const InputFile = forwardRef<HandleForInputFile, PropsForInputFile>(({ limits }, ref) => {
|
||
|
|
const componentID = useId()
|
||
|
|
const previewRef = useRef<HTMLVideoElement | HTMLImageElement>(undefined)
|
||
|
|
const inputRef = useRef<HTMLInputElement>(null)
|
||
|
|
|
||
|
|
const [fileInstance, setFileInstance] = useState<File>()
|
||
|
|
const [fileObjectURL, setFileObjectURL] = useState<string>()
|
||
|
|
|
||
|
|
function updateInput(file?: File) {
|
||
|
|
if (fileObjectURL) {
|
||
|
|
URL.revokeObjectURL(fileObjectURL)
|
||
|
|
}
|
||
|
|
const accept = file && !!limits?.mime_types.find((t) => file.type === t)
|
||
|
|
if (accept) {
|
||
|
|
setFileObjectURL(URL.createObjectURL(file))
|
||
|
|
setFileInstance(file)
|
||
|
|
} else {
|
||
|
|
setFileObjectURL(undefined)
|
||
|
|
setFileInstance(undefined)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
useImperativeHandle(ref, () => ({
|
||
|
|
getPreview: () => previewRef.current,
|
||
|
|
getValue: () => fileInstance,
|
||
|
|
}))
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
return () => {
|
||
|
|
fileObjectURL && URL.revokeObjectURL(fileObjectURL)
|
||
|
|
}
|
||
|
|
}, [fileObjectURL])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
id="input-file"
|
||
|
|
className="input-file"
|
||
|
|
tabIndex={0}
|
||
|
|
onClick={() => inputRef.current?.click()}
|
||
|
|
onDragOver={(e) => e.preventDefault()}
|
||
|
|
onDrop={(e) => {
|
||
|
|
e.preventDefault()
|
||
|
|
updateInput(e.dataTransfer.files.item(0)!)
|
||
|
|
}}>
|
||
|
|
<input
|
||
|
|
id={componentID}
|
||
|
|
ref={inputRef}
|
||
|
|
type="file"
|
||
|
|
accept={limits?.mime_types ? limits.mime_types.join(',') : '*/*'}
|
||
|
|
onChange={(e) => {
|
||
|
|
e.preventDefault()
|
||
|
|
updateInput(e.target.files?.item(0) ?? undefined)
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{!fileInstance && (
|
||
|
|
<div className="prompt">
|
||
|
|
<img className="icon animation-fade-in" src={VectorIconTop} />
|
||
|
|
<span className="header animation-scroll-in">DRAG OR CLICK TO UPLOAD A FILE</span>
|
||
|
|
{limits && (
|
||
|
|
<span className="subheader animation-scroll-in">
|
||
|
|
MAX: {limits.video_width_max} × {limits.video_height_max}; SIZE:{' '}
|
||
|
|
{Math.floor(limits.filesize / 1024 / 1024)}MB; DURATION:{' '}
|
||
|
|
{Math.floor(limits.duration / 10) * 10} SECS;
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{fileInstance && (
|
||
|
|
<div className="preview">
|
||
|
|
{fileInstance.type.startsWith('video') && (
|
||
|
|
<video
|
||
|
|
ref={previewRef as RefObject<HTMLVideoElement>}
|
||
|
|
autoPlay
|
||
|
|
loop
|
||
|
|
muted
|
||
|
|
src={fileObjectURL}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
{fileInstance.type.startsWith('image') && (
|
||
|
|
<img ref={previewRef as RefObject<HTMLImageElement>} src={fileObjectURL} />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
export default InputFile
|