"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)}
{/* 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 (
);
}
return (
{/* Zoom controls */}
{/* Image container with checkerboard background for transparency */}
{/* eslint-disable-next-line @next/next/no-img-element */}

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 (
);
}
// --- Media type icon ---
function MediaTypeIcon({ mediaType }: { mediaType: MediaType }) {
const color = mediaTypeColor(mediaType);
switch (mediaType) {
case "image":
return (
);
case "video":
return (
);
case "audio":
return (
);
case "pdf":
return (
);
}
}