'use client'; import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { X, Download, File, Image as ImageIcon, Film, Music, FileText, Code, Loader2, Archive } from 'lucide-react'; import api from '@/lib/api'; import dynamic from 'next/dynamic'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { formatSize, getFileDisplayName } from '@/lib/utils'; const DynamicPdfViewer = dynamic(() => import('./PdfViewer'), { ssr: false }); const DynamicStlPreview = dynamic(() => import('./StlPreview'), { ssr: false }); const DynamicThreeMfPreview = dynamic(() => import('./ThreeMfPreview'), { ssr: false }); interface FilePreviewProps { file: { name: string; path: string; size: number; mime_type: string; } | null; onClose: () => void; downloadToken?: string; } // getFileDisplayName imported from utils export default function FilePreview({ file, onClose, downloadToken }: FilePreviewProps) { const [content, setContent] = useState(null); const [archiveItems, setArchiveItems] = useState<{name: string, size: number, path: string}[] | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { function handleKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') onClose(); } if (file) { window.addEventListener('keydown', handleKeyDown); } return () => window.removeEventListener('keydown', handleKeyDown); }, [file, onClose]); useEffect(() => { if (!file) return; const type = getPreviewType(file); if (type === 'text' || type === 'code') { if (file.size > 2 * 1024 * 1024) { // 2MB limit for text preview setError('File is too large to preview. Please download it.'); return; } setLoading(true); setError(null); if (!downloadToken) return; fetch(api.getDownloadUrl(file.path, downloadToken)) .then((res) => { if (!res.ok) throw new Error('Failed to load file content'); return res.text(); }) .then((text) => setContent(text)) .catch((err) => setError(err.message)) .finally(() => setLoading(false)); } else if (type === 'archive') { setLoading(true); setError(null); api.getArchiveContents(file.path) .then(data => { if (data.error) throw new Error(data.error); setArchiveItems(data.items || []); }) .catch(err => setError(err.message)) .finally(() => setLoading(false)); } }, [file, downloadToken]); if (!file) return null; const previewType = getPreviewType(file); const downloadUrl = downloadToken ? api.getDownloadUrl(file.path, downloadToken) : ''; const forceDownloadUrl = downloadToken ? api.getDownloadUrl(file.path, downloadToken, true) : ''; return ( e.stopPropagation()} className={`relative flex flex-col max-w-[95vw] max-h-[95vh] rounded-2xl overflow-hidden shadow-2xl ${ (previewType === 'image' || previewType === 'video' || previewType === 'pdf' || previewType === 'stl' || previewType === '3mf' || previewType === 'audio') ? 'w-fit' : 'w-full' } ${ (previewType === 'image' || previewType === 'video' || previewType === 'audio') ? 'h-fit' : 'h-full' }`} style={{ backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)' }} > {/* Header */}
{getIcon(previewType)} {getFileDisplayName(file.name)} {formatSize(file.size)}
{/* Content Area */}
{previewType === 'image' && ( {file.name} )} {previewType === 'video' && (
); } // --- Helpers --- type PreviewType = 'image' | 'video' | 'audio' | 'text' | 'code' | 'pdf' | 'stl' | '3mf' | 'archive' | 'unsupported'; function getPreviewType(file: { name: string; mime_type: string }): PreviewType { const ext = file.name.split('.').pop()?.toLowerCase() || ''; if (ext === 'pdf' || file.mime_type === 'application/pdf') return 'pdf'; if (ext === 'stl' || file.mime_type === 'model/stl') return 'stl'; if (ext === '3mf' || file.mime_type === 'model/3mf') return '3mf'; if (['zip', '7z'].includes(ext)) return 'archive'; if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(ext)) return 'image'; if (['mp4', 'webm', 'ogg'].includes(ext)) return 'video'; // Only browser-supported videos if (['mp3', 'wav', 'flac', 'm4a', 'aac'].includes(ext)) return 'audio'; if (['txt', 'md', 'csv', 'json', 'log'].includes(ext)) return 'text'; if (['js', 'ts', 'jsx', 'tsx', 'html', 'css', 'go', 'py', 'rs', 'c', 'cpp', 'java', 'sh', 'yml', 'yaml', 'xml'].includes(ext)) return 'code'; if (file.mime_type?.startsWith('image/')) return 'image'; if (file.mime_type?.startsWith('video/')) return 'video'; if (file.mime_type?.startsWith('audio/')) return 'audio'; if (file.mime_type?.startsWith('text/')) return 'text'; return 'unsupported'; } function getIcon(type: PreviewType) { const props = { className: "w-5 h-5", style: { color: 'var(--color-text-secondary)' } }; switch (type) { case 'image': return ; case 'video': return ; case 'audio': return ; case 'text': return ; case 'code': return ; case 'archive': return ; default: return ; } } // formatSize imported from utils