From 7bf0984698e43b9aa2dd879f65951318ed410390 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:20:04 +0100 Subject: codebase overhaul (#697) --- .../editor/timeline/timeline-playhead.tsx | 244 +++++++-------------- 1 file changed, 82 insertions(+), 162 deletions(-) (limited to 'apps/web/src/components/editor/timeline/timeline-playhead.tsx') diff --git a/apps/web/src/components/editor/timeline/timeline-playhead.tsx b/apps/web/src/components/editor/timeline/timeline-playhead.tsx index a1be5b26..58d0adb0 100644 --- a/apps/web/src/components/editor/timeline/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline/timeline-playhead.tsx @@ -1,172 +1,92 @@ "use client"; -import { useRef, useState, useEffect } from "react"; -import { TimelineTrack } from "@/types/timeline"; +import { useRef } from "react"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; -import { useTimelinePlayhead } from "@/hooks/use-timeline-playhead"; +import { useTimelinePlayhead } from "@/hooks/timeline/use-timeline-playhead"; +import { useEditor } from "@/hooks/use-editor"; interface TimelinePlayheadProps { - currentTime: number; - duration: number; - zoomLevel: number; - tracks: TimelineTrack[]; - seek: (time: number) => void; - rulerRef: React.RefObject; - rulerScrollRef: React.RefObject; - tracksScrollRef: React.RefObject; - trackLabelsRef?: React.RefObject; - timelineRef: React.RefObject; - playheadRef?: React.RefObject; - isSnappingToPlayhead?: boolean; + zoomLevel: number; + rulerRef: React.RefObject; + rulerScrollRef: React.RefObject; + tracksScrollRef: React.RefObject; + timelineRef: React.RefObject; + playheadRef?: React.RefObject; + isSnappingToPlayhead?: boolean; } export function TimelinePlayhead({ - currentTime, - duration, - zoomLevel, - tracks, - seek, - rulerRef, - rulerScrollRef, - tracksScrollRef, - trackLabelsRef, - timelineRef, - playheadRef: externalPlayheadRef, - isSnappingToPlayhead = false, + zoomLevel, + rulerRef, + rulerScrollRef, + tracksScrollRef, + timelineRef, + playheadRef: externalPlayheadRef, + isSnappingToPlayhead = false, }: TimelinePlayheadProps) { - const internalPlayheadRef = useRef(null); - const playheadRef = externalPlayheadRef || internalPlayheadRef; - const [scrollLeft, setScrollLeft] = useState(0); - - const { playheadPosition, handlePlayheadMouseDown } = useTimelinePlayhead({ - currentTime, - duration, - zoomLevel, - seek, - rulerRef, - rulerScrollRef, - tracksScrollRef, - playheadRef, - }); - - // Track scroll position to lock playhead to frame - useEffect(() => { - const tracksViewport = tracksScrollRef.current; - - if (!tracksViewport) return; - - const handleScroll = () => { - setScrollLeft(tracksViewport.scrollLeft); - }; - - // Set initial scroll position - setScrollLeft(tracksViewport.scrollLeft); - - tracksViewport.addEventListener("scroll", handleScroll); - return () => tracksViewport.removeEventListener("scroll", handleScroll); - }, [tracksScrollRef]); - - // Use timeline container height minus a few pixels for breathing room - const timelineContainerHeight = timelineRef.current?.offsetHeight || 400; - const totalHeight = timelineContainerHeight - 4; - - // Get dynamic track labels width, fallback to 0 if no tracks or no ref - const trackLabelsWidth = - tracks.length > 0 && trackLabelsRef?.current - ? trackLabelsRef.current.offsetWidth - : 0; - - // Calculate position locked to timeline content (accounting for scroll) - const timelinePosition = - playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; - const rawLeftPosition = trackLabelsWidth + timelinePosition - scrollLeft; - - // Get the timeline content width and viewport width for right boundary - const timelineContentWidth = - duration * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; - const tracksViewport = tracksScrollRef.current; - const viewportWidth = tracksViewport?.clientWidth || 1000; - - // Constrain playhead to never appear outside the timeline area - const leftBoundary = trackLabelsWidth; - const rightBoundary = Math.min( - trackLabelsWidth + timelineContentWidth - scrollLeft, // Don't go beyond timeline content - trackLabelsWidth + viewportWidth // Don't go beyond viewport - ); - - const leftPosition = Math.max( - leftBoundary, - Math.min(rightBoundary, rawLeftPosition) - ); - - // Debug logging when playhead might go outside - if (rawLeftPosition < leftBoundary || rawLeftPosition > rightBoundary) { - console.log( - "PLAYHEAD VISUAL DEBUG:", - JSON.stringify({ - playheadPosition, - timelinePosition, - trackLabelsWidth, - scrollLeft, - rawLeftPosition, - constrainedLeftPosition: leftPosition, - leftBoundary, - rightBoundary, - timelineContentWidth, - viewportWidth, - zoomLevel, - }) - ); - } - - return ( -
- {/* The playhead line spanning full height */} -
- - {/* Playhead dot indicator at the top (in ruler area) */} -
-
- ); + const editor = useEditor(); + const duration = editor.timeline.getTotalDuration(); + const internalPlayheadRef = useRef(null); + const playheadRef = externalPlayheadRef || internalPlayheadRef; + + const { playheadPosition, handlePlayheadMouseDown } = useTimelinePlayhead({ + zoomLevel, + rulerRef, + rulerScrollRef, + tracksScrollRef, + playheadRef, + }); + + const timelineContainerHeight = + tracksScrollRef.current?.clientHeight ?? + timelineRef.current?.clientHeight ?? + 400; + const totalHeight = Math.max(0, timelineContainerHeight - 4); + + const timelinePosition = + playheadPosition * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const leftPosition = timelinePosition; + + const handlePlayheadKeyDown = ( + event: React.KeyboardEvent, + ) => { + if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; + + event.preventDefault(); + const step = 1 / Math.max(1, editor.project.getActive().settings.fps); + const direction = event.key === "ArrowRight" ? 1 : -1; + const nextTime = Math.max( + 0, + Math.min(duration, playheadPosition + direction * step), + ); + + editor.playback.seek({ time: nextTime }); + }; + + return ( +
+
+ +
+
+ ); } - -// Also export a hook for getting ruler handlers -export function useTimelinePlayheadRuler({ - currentTime, - duration, - zoomLevel, - seek, - rulerRef, - rulerScrollRef, - tracksScrollRef, - playheadRef, -}: Omit) { - const { handleRulerMouseDown, isDraggingRuler } = useTimelinePlayhead({ - currentTime, - duration, - zoomLevel, - seek, - rulerRef, - rulerScrollRef, - tracksScrollRef, - playheadRef, - }); - - return { handleRulerMouseDown, isDraggingRuler }; -} - -export { TimelinePlayhead as default }; -- cgit v1.3.1