diff --git a/src/components/EndpointForm.tsx b/src/components/EndpointForm.tsx index 79faddd..50cca11 100644 --- a/src/components/EndpointForm.tsx +++ b/src/components/EndpointForm.tsx @@ -181,34 +181,50 @@ export function EndpointForm({ endpoint, onSubmit, onPriceCheck, isSubmitting }: }, [models]); const handleFileChange = useCallback(async (name: string, file: File | File[] | null) => { - setImagePreviews((prev) => { - const oldPreviews = prev[name]; - if (oldPreviews) { - oldPreviews.forEach((p) => URL.revokeObjectURL(p.url)); - } - const newPreviews = { ...prev }; - delete newPreviews[name]; - return newPreviews; - }); - if (file) { - setFiles((prev) => ({ ...prev, [name]: file })); + const incomingFiles = Array.isArray(file) ? file : [file]; + const isMulti = Array.isArray(file); + + // In multi mode, append to existing files + const mergedFiles = isMulti + ? [...(Array.isArray(files[name]) ? files[name] as File[] : files[name] ? [files[name] as File] : []), ...incomingFiles] + : incomingFiles; - const fileArray = Array.isArray(file) ? file : [file]; - const imageFiles = fileArray.filter((f) => f.type.startsWith('image/')); + setFiles((prev) => ({ ...prev, [name]: isMulti ? mergedFiles : mergedFiles[0] })); + const imageFiles = incomingFiles.filter((f) => f.type.startsWith('image/')); if (imageFiles.length > 0) { - const previews = await Promise.all(imageFiles.map(generateImagePreview)); - setImagePreviews((prev) => ({ ...prev, [name]: previews })); + const newPreviews = await Promise.all(imageFiles.map(generateImagePreview)); + setImagePreviews((prev) => ({ + ...prev, + [name]: isMulti ? [...(prev[name] || []), ...newPreviews] : newPreviews, + })); + } else if (!isMulti) { + // Single mode non-image: clear old previews + setImagePreviews((prev) => { + const old = prev[name]; + if (old) old.forEach((p) => URL.revokeObjectURL(p.url)); + const next = { ...prev }; + delete next[name]; + return next; + }); } } else { + // Clear all + setImagePreviews((prev) => { + const old = prev[name]; + if (old) old.forEach((p) => URL.revokeObjectURL(p.url)); + const next = { ...prev }; + delete next[name]; + return next; + }); setFiles((prev) => { const newFiles = { ...prev }; delete newFiles[name]; return newFiles; }); } - }, []); + }, [files]); const toggleNullable = useCallback((name: string, disabled: boolean) => { setNullableDisabled((prev) => ({ ...prev, [name]: disabled })); diff --git a/src/components/form/FileUploadField.tsx b/src/components/form/FileUploadField.tsx index a38df2f..192c7cc 100644 --- a/src/components/form/FileUploadField.tsx +++ b/src/components/form/FileUploadField.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useState, useRef, DragEvent } from 'react'; import { Upload, X, FolderOpen } from 'lucide-react'; import { formatFileSize } from '@/lib/format-utils'; import { OutputPicker } from './OutputPicker'; @@ -41,8 +41,49 @@ export function FileUploadField({ onModeChange, }: FileUploadFieldProps) { const [pickerOpen, setPickerOpen] = useState(false); + const [isDragging, setIsDragging] = useState(false); + const dragCounter = useRef(0); const isImageAccept = accept?.includes('image/') ?? false; + const handleDragEnter = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + dragCounter.current++; + if (e.dataTransfer.types.includes('Files')) { + setIsDragging(true); + } + }; + + const handleDragLeave = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + dragCounter.current--; + if (dragCounter.current === 0) { + setIsDragging(false); + } + }; + + const handleDragOver = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragging(false); + dragCounter.current = 0; + + const droppedFiles = e.dataTransfer.files; + if (!droppedFiles || droppedFiles.length === 0) return; + + if (isMultiMode) { + onFileChange(name, Array.from(droppedFiles)); + } else { + onFileChange(name, droppedFiles[0]); + } + }; + const fileArray = files ? (Array.isArray(files) ? files : [files]) : []; const fileCount = fileArray.length; const fileLabel = @@ -118,43 +159,60 @@ export function FileUploadField({ )} -
- - - {isImageAccept && ( - +
+ {isDragging && ( +
+ Drop {isMultiMode ? 'file(s)' : 'file'} here +
)} +
+ + + {isImageAccept && ( + + )} +
{isImageAccept && (