"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import { useTimelineStore } from "@/stores/timeline-store"; import { usePlaybackStore } from "@/stores/playback-store"; import { useTimelineZoom } from "@/hooks/use-timeline-zoom"; import { useSelectionBox } from "@/hooks/use-selection-box"; import { SnapPoint } from "@/hooks/use-timeline-snapping"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; import { TimelineToolbar } from "./timeline-toolbar"; import { TimelineContent } from "./timeline-content"; import { useTimelineDragHandlers } from "./timeline-drag-handlers"; import { useTimelineActionHandlers } from "./timeline-action-handlers"; import { useTimelineScrollSync } from "./timeline-scroll-sync"; import { useTimelineContentClick } from "./timeline-content-click"; import { useTimelineWheelHandler } from "./timeline-wheel-handler"; export function Timeline() { // Timeline shows all tracks (video, audio, effects) and their elements. // You can drag media here to add it to your project. // elements can be trimmed, deleted, and moved. const { tracks, getTotalDuration, selectedElements, clearSelectedElements, setSelectedElements, dragState, snappingEnabled, } = useTimelineStore(); const { currentTime, duration, seek, setDuration } = usePlaybackStore(); const [isDragOver, setIsDragOver] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); const timelineRef = useRef(null); const rulerRef = useRef(null); const [isInTimeline, setIsInTimeline] = useState(false); // Timeline zoom functionality const { zoomLevel, setZoomLevel, handleWheel } = useTimelineZoom({ containerRef: timelineRef, isInTimeline, }); // Dynamic timeline width calculation based on playhead position and duration const dynamicTimelineWidth = Math.max( (duration || 0) * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel, // Base width from duration (currentTime + 30) * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel, // Width to show current time + 30 seconds buffer timelineRef.current?.clientWidth || 1000 // Minimum width ); // Scroll synchronization and auto-scroll to playhead const rulerScrollRef = useRef(null); const tracksScrollRef = useRef(null); const trackLabelsRef = useRef(null); const playheadRef = useRef(null); const trackLabelsScrollRef = useRef(null); // Selection box functionality const tracksContainerRef = useRef(null); const { selectionBox, handleMouseDown: handleSelectionMouseDown, isSelecting, justFinishedSelecting, } = useSelectionBox({ containerRef: tracksContainerRef, playheadRef, onSelectionComplete: (elements) => { console.log(JSON.stringify({ onSelectionComplete: elements.length })); setSelectedElements(elements); }, }); // Calculate snap indicator state const [currentSnapPoint, setCurrentSnapPoint] = useState( null ); const showSnapIndicator = dragState.isDragging && snappingEnabled && currentSnapPoint !== null; // Callback to handle snap point changes from TimelineTrackContent const handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => { setCurrentSnapPoint(snapPoint); }, []); // Timeline content click handler const { handleTimelineContentClick } = useTimelineContentClick({ duration, zoomLevel, seek, rulerScrollRef, tracksScrollRef, clearSelectedElements, isSelecting, justFinishedSelecting, playheadRef, }); // Update timeline duration when tracks change useEffect(() => { const totalDuration = getTotalDuration(); setDuration(Math.max(totalDuration, 10)); // Minimum 10 seconds for empty timeline }, [tracks, setDuration, getTotalDuration]); // Drag handlers const { dragProps } = useTimelineDragHandlers({ isDragOver, setIsDragOver, isProcessing, setIsProcessing, progress, setProgress, }); // Action handlers const { handleSplitSelected, handleDuplicateSelected, handleFreezeSelected, handleSplitAndKeepLeft, handleSplitAndKeepRight, handleSeparateAudio, handleDeleteSelected, } = useTimelineActionHandlers(); // Scroll synchronization useTimelineScrollSync({ rulerScrollRef, tracksScrollRef, }); // Wheel event handling useTimelineWheelHandler({ timelineRef, isInTimeline, handleWheel, }); return (
setIsInTimeline(true)} onMouseLeave={() => setIsInTimeline(false)} > {/* Toolbar */} {/* Timeline Container */}
setIsInTimeline(true)} onMouseLeave={() => setIsInTimeline(false)} onMouseDown={handleSelectionMouseDown} onClick={handleTimelineContentClick} >
); }