"use client"; import { ScrollArea } from "../ui/scroll-area"; import { Button } from "../ui/button"; import { Scissors, ArrowLeftToLine, ArrowRightToLine, Trash2, Snowflake, Copy, SplitSquareHorizontal, } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger, TooltipProvider, } from "../ui/tooltip"; import { DragOverlay } from "../ui/drag-overlay"; import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store"; import { useMediaStore } from "@/stores/media-store"; import { processMediaFiles } from "@/lib/media-processing"; import { ImageTimelineTreatment } from "@/components/ui/image-timeline-treatment"; import { toast } from "sonner"; import { useState, useRef } from "react"; export function Timeline() { const { tracks, addTrack, addClipToTrack } = useTimelineStore(); const { mediaItems, addMediaItem } = useMediaStore(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const dragCounterRef = useRef(0); const handleDragEnter = (e: React.DragEvent) => { e.preventDefault(); // Don't show overlay for timeline clips or other internal drags if (e.dataTransfer.types.includes("application/x-timeline-clip")) { return; } dragCounterRef.current += 1; if (!isDragOver) { setIsDragOver(true); } }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); // Don't update state for timeline clips if (e.dataTransfer.types.includes("application/x-timeline-clip")) { return; } dragCounterRef.current -= 1; if (dragCounterRef.current === 0) { setIsDragOver(false); } }; const handleDrop = async (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); dragCounterRef.current = 0; // Check if this is a timeline clip drop - now we'll handle it! const timelineClipData = e.dataTransfer.getData( "application/x-timeline-clip" ); if (timelineClipData) { // Timeline clips dropped on the main timeline area (not on a specific track) // For now, we'll just ignore these - clips should be dropped on specific tracks return; } // Check if this is an internal media item drop const mediaItemData = e.dataTransfer.getData("application/x-media-item"); if (mediaItemData) { try { const { id, type, name } = JSON.parse(mediaItemData); // Find the full media item from the store const mediaItem = mediaItems.find((item) => item.id === id); if (!mediaItem) { toast.error("Media item not found"); return; } // Determine track type based on media type let trackType: "video" | "audio" | "effects"; if (type === "video") { trackType = "video"; } else if (type === "audio") { trackType = "audio"; } else { // For images, we'll put them on video tracks trackType = "video"; } // Create a new track and get its ID const newTrackId = addTrack(trackType); // Add the clip to the new track addClipToTrack(newTrackId, { mediaId: mediaItem.id, name: mediaItem.name, duration: mediaItem.duration || 5, // Default 5 seconds for images }); toast.success(`Added ${name} to ${trackType} track`); } catch (error) { console.error("Error parsing media item data:", error); toast.error("Failed to add media to timeline"); } } else if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { // Handle external file drops setIsProcessing(true); try { const processedItems = await processMediaFiles(e.dataTransfer.files); for (const processedItem of processedItems) { // Add to media store first addMediaItem(processedItem); // The media item now has an ID, let's get it from the latest state // Since addMediaItem is synchronous, we can get the latest item const currentMediaItems = useMediaStore.getState().mediaItems; const addedItem = currentMediaItems.find( (item) => item.name === processedItem.name && item.url === processedItem.url ); if (addedItem) { // Determine track type based on media type let trackType: "video" | "audio" | "effects"; if (processedItem.type === "video") { trackType = "video"; } else if (processedItem.type === "audio") { trackType = "audio"; } else { // For images, we'll put them on video tracks trackType = "video"; } // Create a new track and get its ID const newTrackId = addTrack(trackType); // Add the clip to the new track addClipToTrack(newTrackId, { mediaId: addedItem.id, name: addedItem.name, duration: addedItem.duration || 5, // Default 5 seconds for images }); toast.success(`Added ${processedItem.name} to timeline`); } } } catch (error) { console.error("Error processing external files:", error); toast.error("Failed to process dropped files"); } finally { setIsProcessing(false); } } }; const dragProps = { onDragEnter: handleDragEnter, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, }; return (
No tracks in timeline
Add a video or audio track to get started