"use client"; import { useState, useCallback } from "react"; // --- Types --- export type MediaType = "image" | "video" | "audio" | "pdf"; type MediaViewerProps = { /** URL to serve the raw file (e.g. /api/workspace/raw-file?path=...) */ url: string; /** Original filename for display */ filename: string; /** Detected media type */ mediaType: MediaType; /** Original workspace path for download/copy */ filePath?: string; }; // --- Extension → MediaType mapping --- const IMAGE_EXTS = new Set([ "jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "avif", "heic", "heif", "ico", "tiff", "tif", ]); const VIDEO_EXTS = new Set(["mp4", "webm", "mov", "avi", "mkv"]); const AUDIO_EXTS = new Set(["mp3", "wav", "ogg", "m4a", "aac", "flac"]); const PDF_EXTS = new Set(["pdf"]); /** Returns the media type for a filename, or null if it's not a known media file. */ export function detectMediaType(filename: string): MediaType | null { const ext = filename.split(".").pop()?.toLowerCase() ?? ""; if (IMAGE_EXTS.has(ext)) {return "image";} if (VIDEO_EXTS.has(ext)) {return "video";} if (AUDIO_EXTS.has(ext)) {return "audio";} if (PDF_EXTS.has(ext)) {return "pdf";} return null; } // --- Icons --- function DownloadIcon() { return ( ); } function ExternalLinkIcon() { return ( ); } function ZoomInIcon() { return ( ); } function ZoomOutIcon() { return ( ); } function mediaTypeLabel(mediaType: MediaType): string { switch (mediaType) { case "image": return "Image"; case "video": return "Video"; case "audio": return "Audio"; case "pdf": return "PDF"; } } function mediaTypeColor(mediaType: MediaType): string { switch (mediaType) { case "image": return "#60a5fa"; case "video": return "#c084fc"; case "audio": return "#f59e0b"; case "pdf": return "#ef4444"; } } // --- Main Component --- export function MediaViewer({ url, filename, mediaType, filePath }: MediaViewerProps) { return (
{/* Header bar */}
{filename} {mediaTypeLabel(mediaType)}
{/* Open in new tab */} { (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)"; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = "transparent"; }} > {/* Download */} { (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)"; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = "transparent"; }} >
{/* Content */}
{mediaType === "image" && } {mediaType === "video" && } {mediaType === "audio" && } {mediaType === "pdf" && }
{/* Footer with path */} {filePath && (
{filePath}
)}
); } // --- Image Viewer (with zoom) --- function ImageViewer({ url, filename }: { url: string; filename: string }) { const [zoom, setZoom] = useState(1); const [error, setError] = useState(false); const handleZoomIn = useCallback(() => setZoom((z) => Math.min(z * 1.5, 5)), []); const handleZoomOut = useCallback(() => setZoom((z) => Math.max(z / 1.5, 0.25)), []); const handleReset = useCallback(() => setZoom(1), []); if (error) { return (
🖼

Failed to load image

); } return (
{/* Zoom controls */}
{/* Image container with checkerboard background for transparency */}
{/* eslint-disable-next-line @next/next/no-img-element */} {filename} setError(true)} style={{ transform: `scale(${zoom})`, transformOrigin: "center center", transition: "transform 200ms ease", maxWidth: zoom <= 1 ? "100%" : "none", display: "block", }} draggable={false} />
); } // --- Video Viewer --- function VideoViewer({ url }: { url: string }) { return (
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
); } // --- Audio Viewer --- function AudioViewer({ url, filename }: { url: string; filename: string }) { return (
{/* Visual representation */}

{filename}

{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
); } // --- PDF Viewer --- function PdfViewer({ url }: { url: string }) { return (