Spaces:
Running
Running
File size: 3,997 Bytes
a62d4c5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
import { useState } from 'react'
import * as m from '../paraglide/messages'
type FileSelectProps = {
onSelection: (file: File) => void
}
export default function FileSelect(props: FileSelectProps) {
const { onSelection } = props
const [dragHover, setDragHover] = useState(false)
const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
function onFileSelected(file: File) {
if (!file) {
return
}
// Skip non-image files
const isImage = file.type.match('image.*')
if (!isImage) {
return
}
try {
// Check if file is larger than 10mb
if (file.size > 10 * 1024 * 1024) {
throw new Error('file too large')
}
onSelection(file)
} catch (e) {
// eslint-disable-next-line
alert(`error: ${(e as any).message}`)
}
}
async function getFile(entry: any): Promise<File> {
return new Promise(resolve => {
entry.file((file: File) => resolve(file))
})
}
/* eslint-disable no-await-in-loop */
// Drop handler function to get all files
async function getAllFileEntries(items: DataTransferItemList) {
const fileEntries: Array<File> = []
// Use BFS to traverse entire directory/file structure
const queue = []
// Unfortunately items is not iterable i.e. no forEach
for (let i = 0; i < items.length; i += 1) {
queue.push(items[i].webkitGetAsEntry())
}
while (queue.length > 0) {
const entry = queue.shift()
if (entry?.isFile) {
// Only append images
const file = await getFile(entry)
fileEntries.push(file)
} else if (entry?.isDirectory) {
queue.push(
...(await readAllDirectoryEntries((entry as any).createReader()))
)
}
}
return fileEntries
}
// Get all the entries (files or sub-directories) in a directory
// by calling readEntries until it returns empty array
async function readAllDirectoryEntries(directoryReader: any) {
const entries = []
let readEntries = await readEntriesPromise(directoryReader)
while (readEntries.length > 0) {
entries.push(...readEntries)
readEntries = await readEntriesPromise(directoryReader)
}
return entries
}
/* eslint-enable no-await-in-loop */
// Wrap readEntries in a promise to make working with readEntries easier
// readEntries will return only some of the entries in a directory
// e.g. Chrome returns at most 100 entries at a time
async function readEntriesPromise(directoryReader: any): Promise<any> {
return new Promise((resolve, reject) => {
directoryReader.readEntries(resolve, reject)
})
}
async function handleDrop(ev: React.DragEvent) {
ev.preventDefault()
const items = await getAllFileEntries(ev.dataTransfer.items)
setDragHover(false)
onFileSelected(items[0])
}
return (
<label
htmlFor={uploadElemId}
className="block w-full h-full group relative cursor-pointer rounded-md font-medium focus-within:outline-none"
>
<div
className={[
'w-full h-full flex items-center justify-center px-6 pt-5 pb-6 text-xl',
'border-4 border-dashed rounded-md',
'hover:border-black hover:bg-primary',
'text-center',
dragHover ? 'border-black bg-primary' : 'bg-gray-100 border-gray-300',
].join(' ')}
onDrop={handleDrop}
onDragOver={ev => {
ev.stopPropagation()
ev.preventDefault()
setDragHover(true)
}}
onDragLeave={() => setDragHover(false)}
>
<input
id={uploadElemId}
name={uploadElemId}
type="file"
className="sr-only"
onChange={ev => {
const file = ev.currentTarget.files?.[0]
if (file) {
onFileSelected(file)
}
}}
accept="image/png, image/jpeg, image/webp"
/>
<p>{m.drop_zone()}</p>
</div>
</label>
)
}
|