From 56e6edcc5ab497388bcfad78a60681b99ca5c374 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Thu, 17 Jul 2025 17:37:18 +0200 Subject: move timeline component --- apps/web/src/components/editor/timeline/index.tsx | 193 ++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 apps/web/src/components/editor/timeline/index.tsx (limited to 'apps/web/src/components/editor/timeline') diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx new file mode 100644 index 00000000..6d90670a --- /dev/null +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -0,0 +1,193 @@ +"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} + > + +
+
+ ); +} -- cgit v1.3.1