import { createSignal, For, Show } from 'solid-js'; import { cn } from '@/lib/utils'; import { ModalPortal } from './ModalPortal'; import './FileUpload.css'; export interface FileUploadProps { isOpen?: boolean; onClose?: () => void; onFilesChange?: (files: UploadedFile[]) => void; maxFileSize?: number; // in MB acceptedTypes?: string[]; class?: string; } export interface UploadedFile { id: string; name: string; size: number; type: string; status: 'uploading' | 'completed' | 'error'; progress: number; url?: string; } const defaultAcceptedTypes = [ 'image/jpeg', 'image/png', 'application/pdf', 'video/mp4' ]; const defaultMaxFileSize = 50; // 50MB export const FileUpload = (props: FileUploadProps) => { const [files, setFiles] = createSignal([]); const [isDragging, setIsDragging] = createSignal(false); const [urlInput, setUrlInput] = createSignal(''); // Generate unique ID for files const generateId = () => Math.random().toString(36).substr(2, 9); // Format file size const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 KB'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; // Get file extension const getFileExtension = (filename: string) => { return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toUpperCase(); }; // Get file icon color based on type const getFileTypeColor = (type: string) => { if (type.startsWith('image/')) return 'bg-success-base'; if (type.startsWith('video/')) return 'bg-warning-base'; if (type === 'application/pdf') return 'bg-error-base'; return 'bg-neutral-base'; }; // Validate file const validateFile = (file: File) => { const maxSize = (props.maxFileSize || defaultMaxFileSize) * 1024 * 1024; const acceptedTypes = props.acceptedTypes || defaultAcceptedTypes; if (file.size > maxSize) { alert(`File size exceeds ${props.maxFileSize || defaultMaxFileSize}MB limit`); return false; } if (!acceptedTypes.includes(file.type) && !acceptedTypes.some(type => file.type.includes(type))) { alert('File type not supported'); return false; } return true; }; // Handle file selection const handleFileSelect = (selectedFiles: FileList | null | undefined) => { if (!selectedFiles) return; const newFiles: UploadedFile[] = []; Array.from(selectedFiles).forEach(file => { if (validateFile(file)) { const uploadedFile: UploadedFile = { id: generateId(), name: file.name, size: file.size, type: file.type, status: 'uploading', progress: 0 }; newFiles.push(uploadedFile); } }); if (newFiles.length > 0) { const updatedFiles = [...files(), ...newFiles]; setFiles(updatedFiles); props.onFilesChange?.(updatedFiles); // Simulate upload progress newFiles.forEach(file => { simulateUpload(file.id); }); } }; // Simulate file upload const simulateUpload = (fileId: string) => { let progress = 0; const interval = setInterval(() => { progress += Math.random() * 30; if (progress >= 100) { progress = 100; clearInterval(interval); setFiles(prev => prev.map(file => file.id === fileId ? { ...file, status: 'completed', progress: 100 } : file )); } else { setFiles(prev => prev.map(file => file.id === fileId ? { ...file, progress } : file )); } }, 500); }; // Handle drag events const handleDragOver = (e: DragEvent) => { e.preventDefault(); setIsDragging(true); }; const handleDragLeave = (e: DragEvent) => { e.preventDefault(); setIsDragging(false); }; const handleDrop = (e: DragEvent) => { e.preventDefault(); setIsDragging(false); handleFileSelect(e.dataTransfer?.files); }; // Handle URL import const handleUrlImport = () => { const url = urlInput().trim(); if (url) { // Extract filename from URL or use default const filename = url.split('/').pop() || 'imported-file'; const extension = filename.includes('.') ? getFileExtension(filename) : 'PDF'; const newFile: UploadedFile = { id: generateId(), name: filename, size: 0, // Unknown size for URL imports type: extension === 'PDF' ? 'application/pdf' : 'application/octet-stream', status: 'uploading', progress: 0, url }; const updatedFiles = [...files(), newFile]; setFiles(updatedFiles); props.onFilesChange?.(updatedFiles); // Simulate URL import simulateUpload(newFile.id); setUrlInput(''); } }; // Remove file const removeFile = (fileId: string) => { const updatedFiles = files().filter(file => file.id !== fileId); setFiles(updatedFiles); props.onFilesChange?.(updatedFiles); }; // Close dialog const handleClose = () => { props.onClose?.(); }; if (!props.isOpen) { return null; } return ( <>
); };