"use client"; import { useDragDrop } from "@/hooks/use-drag-drop"; import { processMediaFiles } from "@/lib/media-processing"; import { useMediaStore, type MediaItem } from "@/stores/media-store"; import { useTimelineStore } from "@/stores/timeline-store"; import { Image, Music, Plus, Trash2, Upload, Video } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { AspectRatio } from "../ui/aspect-ratio"; import { Button } from "../ui/button"; import { DragOverlay } from "../ui/drag-overlay"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from "../ui/context-menu"; // MediaPanel lets users add, view, and drag media (images, videos, audio) into the project. // You can upload files or drag them from your computer. Dragging from here to the timeline adds them to your video project. export function MediaPanel() { const { mediaItems, addMediaItem, removeMediaItem } = useMediaStore(); const fileInputRef = useRef(null); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); const [searchQuery, setSearchQuery] = useState(""); const [mediaFilter, setMediaFilter] = useState("all"); const processFiles = async (files: FileList | File[]) => { if (!files || files.length === 0) 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(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(); // Remove tracks automatically when delete media const { tracks, removeTrack } = useTimelineStore.getState(); tracks.forEach((track) => { const clipsToRemove = track.clips.filter((clip) => clip.mediaId === id); clipsToRemove.forEach((clip) => { useTimelineStore.getState().removeClipFromTrack(track.id, clip.id); }); // Only remove track if it becomes empty and has no other clips const updatedTrack = useTimelineStore .getState() .tracks.find((t) => t.id === track.id); if (updatedTrack && updatedTrack.clips.length === 0) { removeTrack(track.id); } }); await removeMediaItem(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 startDrag = (e: React.DragEvent, item: MediaItem) => { // When dragging a media item, set drag data for timeline to read e.dataTransfer.setData( "application/x-media-item", JSON.stringify({ id: item.id, type: item.type, name: item.name, }) ); e.dataTransfer.effectAllowed = "copy"; }; const [filteredMediaItems, setFilteredMediaItems] = useState(mediaItems); useEffect(() => { const filtered = mediaItems.filter((item) => { if (mediaFilter && mediaFilter !== "all" && item.type !== mediaFilter) { return false; } if ( searchQuery && !item.name.toLowerCase().includes(searchQuery.toLowerCase()) ) { return false; } return true; }); setFilteredMediaItems(filtered); }, [mediaItems, mediaFilter, searchQuery]); const renderPreview = (item: MediaItem) => { // Render a preview for each media type (image, video, audio, unknown) // Each preview is draggable to the timeline const baseDragProps = { draggable: true, onDragStart: (e: React.DragEvent) => startDrag(e, item), }; if (item.type === "image") { return (
{item.name}
); } if (item.type === "video") { if (item.thumbnailUrl) { return (
{item.name}
{item.duration && (
{formatDuration(item.duration)}
)}
); } return (
); } if (item.type === "audio") { return (
Audio {item.duration && ( {formatDuration(item.duration)} )}
); } return (
Unknown
); }; return ( <> {/* Hidden file input for uploading media */}
{/* Show overlay when dragging files over the panel */}
{/* Button to add/upload media */}
{/* Search and filter controls */} setSearchQuery(e.target.value)} /> {/* Add media button */}
{/* Show message if no media, otherwise show media grid */} {filteredMediaItems.length === 0 ? (

No media in project

Drag files here or use the button above

) : (
{/* Render each media item as a draggable button */} {filteredMediaItems.map((item) => (
Export clips handleRemove(e, item.id)} > Delete
))}
)}
); }