"use client"; import { useDragDrop } from "@/hooks/use-drag-drop"; import { processMediaFiles } from "@/lib/media-processing"; import { useMediaStore, type MediaItem } from "@/stores/media-store"; import { ArrowDown01, CloudUpload, Grid2X2, Image, List, Loader2, Music, Search, Video, } from "lucide-react"; import { useEffect, useRef, useState, useMemo } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { MediaDragOverlay } from "@/components/editor/media-panel/drag-overlay"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { DraggableMediaItem } from "@/components/ui/draggable-item"; import { useProjectStore } from "@/stores/project-store"; import { useTimelineStore } from "@/stores/timeline-store"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { usePanelStore } from "@/stores/panel-store"; function MediaItemWithContextMenu({ item, children, onRemove, }: { item: MediaItem; children: React.ReactNode; onRemove: (e: React.MouseEvent, id: string) => Promise; }) { return ( {children} Export clips onRemove(e, item.id)} > Delete ); } export function MediaView() { const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore(); const { activeProject } = useProjectStore(); const { mediaViewMode, setMediaViewMode } = usePanelStore(); const fileInputRef = useRef(null); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); const [searchQuery, setSearchQuery] = useState(""); const [mediaFilter, setMediaFilter] = useState("all"); const [sortBy, setSortBy] = useState<"name" | "type" | "duration" | "size">( "name" ); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); const processFiles = async (files: FileList | File[]) => { if (!files || files.length === 0) return; if (!activeProject) { toast.error("No active project"); return; } setIsProcessing(true); setProgress(0); try { // Process files (extract metadata, generate thumbnails, etc.) const processedItems = await processMediaFiles(files, (p) => setProgress(p) ); // Add each processed media item to the store for (const item of processedItems) { await addMediaItem(activeProject.id, item); } } catch (error) { // Show error toast if processing fails console.error("Error processing files:", error); toast.error("Failed to process files"); } finally { setIsProcessing(false); setProgress(0); } }; const { isDragOver, dragProps } = useDragDrop({ // When files are dropped, process them onDrop: processFiles, }); const handleFileSelect = () => fileInputRef.current?.click(); // Open file picker const handleFileChange = (e: React.ChangeEvent) => { // When files are selected via file picker, process them if (e.target.files) processFiles(e.target.files); e.target.value = ""; // Reset input }; const handleRemove = async (e: React.MouseEvent, id: string) => { // Remove a media item from the store e.stopPropagation(); if (!activeProject) { toast.error("No active project"); return; } // Media store now handles cascade deletion automatically await removeMediaItem(activeProject.id, id); }; const formatDuration = (duration: number) => { // Format seconds as mm:ss const min = Math.floor(duration / 60); const sec = Math.floor(duration % 60); return `${min}:${sec.toString().padStart(2, "0")}`; }; const [filteredMediaItems, setFilteredMediaItems] = useState(mediaItems); useEffect(() => { let filtered = mediaItems.filter((item) => { if (item.ephemeral) return false; if (mediaFilter && mediaFilter !== "all" && item.type !== mediaFilter) { return false; } if ( searchQuery && !item.name.toLowerCase().includes(searchQuery.toLowerCase()) ) { return false; } return true; }); filtered.sort((a, b) => { let valueA: any; let valueB: any; switch (sortBy) { case "name": valueA = a.name.toLowerCase(); valueB = b.name.toLowerCase(); break; case "type": valueA = a.type; valueB = b.type; break; case "duration": valueA = a.duration || 0; valueB = b.duration || 0; break; case "size": valueA = a.file.size; valueB = b.file.size; break; default: return 0; } if (valueA < valueB) return sortOrder === "asc" ? -1 : 1; if (valueA > valueB) return sortOrder === "asc" ? 1 : -1; return 0; }); setFilteredMediaItems(filtered); }, [mediaItems, mediaFilter, searchQuery, sortBy, sortOrder]); const previewComponents = useMemo(() => { const previews = new Map(); filteredMediaItems.forEach((item) => { let preview: React.ReactNode; if (item.type === "image") { preview = (
{item.name}
); } else if (item.type === "video") { if (item.thumbnailUrl) { preview = (
{item.name}
{item.duration && (
{formatDuration(item.duration)}
)}
); } else { preview = (
); } } else if (item.type === "audio") { preview = (
Audio {item.duration && ( {formatDuration(item.duration)} )}
); } else { preview = (
Unknown
); } previews.set(item.id, preview); }); return previews; }, [filteredMediaItems]); const renderPreview = (item: MediaItem) => previewComponents.get(item.id); return ( <> {/* Hidden file input for uploading media */}
{/* Search and filter controls */}

{mediaViewMode === "grid" ? "Switch to list view" : "Switch to grid view"}

{ if (sortBy === "name") { setSortOrder( sortOrder === "asc" ? "desc" : "asc" ); } else { setSortBy("name"); setSortOrder("asc"); } }} > Name{" "} {sortBy === "name" && (sortOrder === "asc" ? "↑" : "↓")} { if (sortBy === "type") { setSortOrder( sortOrder === "asc" ? "desc" : "asc" ); } else { setSortBy("type"); setSortOrder("asc"); } }} > Type{" "} {sortBy === "type" && (sortOrder === "asc" ? "↑" : "↓")} { if (sortBy === "duration") { setSortOrder( sortOrder === "asc" ? "desc" : "asc" ); } else { setSortBy("duration"); setSortOrder("asc"); } }} > Duration{" "} {sortBy === "duration" && (sortOrder === "asc" ? "↑" : "↓")} { if (sortBy === "size") { setSortOrder( sortOrder === "asc" ? "desc" : "asc" ); } else { setSortBy("size"); setSortOrder("asc"); } }} > File Size{" "} {sortBy === "size" && (sortOrder === "asc" ? "↑" : "↓")}

Sort by {sortBy} ( {sortOrder === "asc" ? "ascending" : "descending"})

{isDragOver || filteredMediaItems.length === 0 ? ( ) : mediaViewMode === "grid" ? ( ) : ( )}
); } function GridView({ filteredMediaItems, renderPreview, handleRemove, }: { filteredMediaItems: MediaItem[]; renderPreview: (item: MediaItem) => React.ReactNode; handleRemove: (e: React.MouseEvent, id: string) => Promise; }) { return (
{filteredMediaItems.map((item) => ( useTimelineStore.getState().addMediaAtTime(item, currentTime) } rounded={false} variant="card" /> ))}
); } function ListView({ filteredMediaItems, renderPreview, handleRemove, formatDuration, }: { filteredMediaItems: MediaItem[]; renderPreview: (item: MediaItem) => React.ReactNode; handleRemove: (e: React.MouseEvent, id: string) => Promise; formatDuration: (duration: number) => string; }) { return (
{filteredMediaItems.map((item) => ( useTimelineStore.getState().addMediaAtTime(item, currentTime) } variant="compact" /> ))}
); }