From 3a241d91120c61fd671073d99d6099f026f7bdec Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Fri, 11 Jul 2025 00:25:07 +0200 Subject: feat: improve playhead --- apps/web/src/components/editor/timeline.tsx | 137 ++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 40 deletions(-) (limited to 'apps/web/src/components/editor/timeline.tsx') diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 15c51879..e302ac1a 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -46,7 +46,6 @@ import { import { TimelineTrackContent } from "./timeline-track"; import { TimelinePlayhead, - TimelinePlayheadTracks, useTimelinePlayheadRuler, } from "./timeline-playhead"; import type { DragData, TimelineTrack } from "@/types/timeline"; @@ -124,6 +123,7 @@ export function Timeline() { // Scroll synchronization and auto-scroll to playhead const rulerScrollRef = useRef(null); const tracksScrollRef = useRef(null); + const trackLabelsRef = useRef(null); const isUpdatingRef = useRef(false); const lastRulerSync = useRef(0); const lastTracksSync = useRef(0); @@ -139,6 +139,77 @@ export function Timeline() { tracksScrollRef, }); + // Timeline content click to seek handler + const handleTimelineContentClick = useCallback( + (e: React.MouseEvent) => { + // Don't seek if clicking on timeline elements, but still deselect + if ((e.target as HTMLElement).closest(".timeline-element")) { + return; + } + + // Don't seek if clicking on playhead + if ((e.target as HTMLElement).closest(".playhead")) { + return; + } + + // Don't seek if clicking on track labels + if ((e.target as HTMLElement).closest("[data-track-labels]")) { + clearSelectedElements(); + return; + } + + // Clear selected elements when clicking empty timeline area + clearSelectedElements(); + + // Determine if we're clicking in ruler or tracks area + const isRulerClick = (e.target as HTMLElement).closest( + "[data-ruler-area]" + ); + + let mouseX: number; + let scrollLeft = 0; + + if (isRulerClick) { + // Calculate based on ruler position + const rulerContent = rulerScrollRef.current?.querySelector( + "[data-radix-scroll-area-viewport]" + ) as HTMLElement; + if (!rulerContent) return; + const rect = rulerContent.getBoundingClientRect(); + mouseX = e.clientX - rect.left; + scrollLeft = rulerContent.scrollLeft; + } else { + // Calculate based on tracks content position + const tracksContent = tracksScrollRef.current?.querySelector( + "[data-radix-scroll-area-viewport]" + ) as HTMLElement; + if (!tracksContent) return; + const rect = tracksContent.getBoundingClientRect(); + mouseX = e.clientX - rect.left; + scrollLeft = tracksContent.scrollLeft; + } + + const time = Math.max( + 0, + Math.min( + duration, + (mouseX + scrollLeft) / + (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel) + ) + ); + + seek(time); + }, + [ + duration, + zoomLevel, + seek, + rulerScrollRef, + tracksScrollRef, + clearSelectedElements, + ] + ); + // Update timeline duration when tracks change useEffect(() => { const totalDuration = getTotalDuration(); @@ -224,14 +295,6 @@ export function Timeline() { } }; - // Add new click handler for deselection - const handleTimelineClick = (e: React.MouseEvent) => { - // If clicking empty area (not on an element) and not starting marquee, deselect all elements - if (!(e.target as HTMLElement).closest(".timeline-element")) { - clearSelectedElements(); - } - }; - // Mouse move to update marquee useEffect(() => { if (!marquee || !marquee.active) return; @@ -802,7 +865,22 @@ export function Timeline() { {/* Timeline Container */} -
+
+ {/* Timeline Header with Ruler */}
{/* Track Labels Header */} @@ -817,17 +895,16 @@ export function Timeline() {
{/* Time markers */} {(() => { @@ -896,18 +973,6 @@ export function Timeline() { ); }).filter(Boolean); })()} - - {/* Playhead in ruler */} -
@@ -917,7 +982,11 @@ export function Timeline() {
{/* Track Labels */} {tracks.length > 0 && ( -
+
{tracks.map((track) => (
{tracks.length === 0 ? ( @@ -996,18 +1065,6 @@ export function Timeline() { ))} - - {/* Playhead for tracks area */} - )}
-- cgit v1.3.1