import { useRef, useState } from 'react' import type { FolderEntry } from '@/lib/client-types' import { validateFolder } from '@/lib/validate-folder' import { FolderIcon } from '@/components/ui/icons' function buildAssetUrls(model: File, supportFiles: File[]) { const assetUrls: Record = {} const modelUrl = URL.createObjectURL(model) assetUrls[model.name.toLowerCase()] = modelUrl for (const file of supportFiles) { assetUrls[file.name.toLowerCase()] = URL.createObjectURL(file) } return { modelUrl, assetUrls } } function readDroppedFile(entry: FileSystemFileEntry) { return new Promise((resolve, reject) => { entry.file(resolve, reject) }) } function readDirectoryEntries(reader: FileSystemDirectoryReader) { return new Promise((resolve, reject) => { reader.readEntries(resolve, reject) }) } async function collectEntryFiles(entry: FileSystemEntry): Promise { if (entry.isFile) { return [await readDroppedFile(entry as FileSystemFileEntry)] } const reader = (entry as FileSystemDirectoryEntry).createReader() const files: File[] = [] while (true) { const entries = await readDirectoryEntries(reader) if (entries.length === 0) break for (const child of entries) { files.push(...await collectEntryFiles(child)) } } return files } interface FolderDropzoneProps { isUploading: boolean onFolderSelected: (entry: FolderEntry) => void onError: (message: string) => void } export default function FolderDropzone({ isUploading, onFolderSelected, onError, }: FolderDropzoneProps) { const inputRef = useRef(null) const [isDragActive, setIsDragActive] = useState(false) const processFiles = (files: File[], fallbackFolderName = 'folder') => { if (files.length === 0) return const folderName = files[0].webkitRelativePath?.split('/')[0] || fallbackFolderName const validation = validateFolder(files) if (!validation.ok) { onError(validation.errors.join(' | ')) return } const { modelUrl, assetUrls } = buildAssetUrls( validation.model, validation.textures.map((texture) => texture.file), ) const entry: FolderEntry = { folderName, modelFile: validation.model, textures: validation.textures, status: 'pending', progress: 0, warnings: validation.warnings, modelUrl, assetUrls, viewerOpen: true, } onFolderSelected(entry) } const handleChange = (e: React.ChangeEvent) => { const selected = e.target.files if (!selected || selected.length === 0) return processFiles(Array.from(selected)) e.target.value = '' } const handleDragOver = (e: React.DragEvent) => { e.preventDefault() if (isUploading) return setIsDragActive(true) } const handleDragLeave = (e: React.DragEvent) => { if (!e.currentTarget.contains(e.relatedTarget as Node | null)) { setIsDragActive(false) } } const handleDrop = async (e: React.DragEvent) => { e.preventDefault() setIsDragActive(false) if (isUploading) return try { const items = Array.from(e.dataTransfer.items) const rootEntries = items .filter((item) => item.kind === 'file') .map((item) => item.webkitGetAsEntry?.()) .filter((entry): entry is FileSystemEntry => entry !== null) const directoryEntries = rootEntries.filter((entry) => entry.isDirectory) if (directoryEntries.length > 0) { if (directoryEntries.length > 1) { onError('Deposez un seul dossier a la fois') return } const rootEntry = directoryEntries[0] as FileSystemDirectoryEntry const files = await collectEntryFiles(rootEntry) processFiles(files, rootEntry.name) return } const droppedFiles = Array.from(e.dataTransfer.files) if (droppedFiles.length === 0) return processFiles(droppedFiles) } catch { onError('Impossible de lire le dossier depose') } } return ( <> )} multiple className="hidden" disabled={isUploading} onChange={handleChange} />
{ if (isUploading) return inputRef.current?.click() }} onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={(e) => { void handleDrop(e) }} className={` relative border-2 border-dashed rounded-2xl p-8 text-center cursor-pointer transition-all duration-200 bg-black-800 ${isUploading ? 'cursor-not-allowed opacity-60 border-white/20' : ''} ${!isUploading ? 'border-white/30 hover:border-white/50 hover:bg-black-700' : ''} ${isDragActive ? 'border-white/70 bg-black-700' : ''} `} >

Deposez votre dossier ici ou cliquez pour parcourir

) }