From 0386624ff5fd6d82d64d6b103ddc1a7e5045f45b Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 4 Aug 2025 12:35:14 +0200 Subject: fix: click empty area on timeline to move playhead --- apps/web/src/components/editor/timeline/index.tsx | 117 +++++++++++++++++++--- 1 file changed, 102 insertions(+), 15 deletions(-) (limited to 'apps/web/src/components/editor/timeline/index.tsx') diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx index 7a2cc7f2..dd81b7b3 100644 --- a/apps/web/src/components/editor/timeline/index.tsx +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -167,11 +167,32 @@ export function Timeline() { const handleTimelineMouseDown = useCallback((e: React.MouseEvent) => { // Only track mouse down on timeline background areas (not elements) const target = e.target as HTMLElement; + console.log( + JSON.stringify({ + debug_mousedown: "START", + target_class: target.className, + target_parent_class: target.parentElement?.className, + clientX: e.clientX, + clientY: e.clientY, + timeStamp: e.timeStamp, + }) + ); + const isTimelineBackground = !target.closest(".timeline-element") && !playheadRef.current?.contains(target) && !target.closest("[data-track-labels]"); + console.log( + JSON.stringify({ + debug_mousedown: "CHECK", + isTimelineBackground, + hasTimelineElement: !!target.closest(".timeline-element"), + hasPlayhead: !!playheadRef.current?.contains(target), + hasTrackLabels: !!target.closest("[data-track-labels]"), + }) + ); + if (isTimelineBackground) { mouseTrackingRef.current = { isMouseDown: true, @@ -179,12 +200,38 @@ export function Timeline() { downY: e.clientY, downTime: e.timeStamp, }; + console.log( + JSON.stringify({ + debug_mousedown: "TRACKED", + mouseTracking: mouseTrackingRef.current, + }) + ); + } else { + console.log( + JSON.stringify({ + debug_mousedown: "IGNORED - not timeline background", + }) + ); } }, []); // Timeline content click to seek handler const handleTimelineContentClick = useCallback( (e: React.MouseEvent) => { + console.log( + JSON.stringify({ + debug_click: "START", + target: (e.target as HTMLElement).className, + target_parent: (e.target as HTMLElement).parentElement?.className, + mouseTracking: mouseTrackingRef.current, + isSelecting, + justFinishedSelecting, + clickX: e.clientX, + clickY: e.clientY, + timeStamp: e.timeStamp, + }) + ); + const { isMouseDown, downX, downY, downTime } = mouseTrackingRef.current; // Reset mouse tracking @@ -199,8 +246,8 @@ export function Timeline() { if (!isMouseDown) { console.log( JSON.stringify({ - ignoredClickWithoutMouseDown: true, - timeStamp: e.timeStamp, + debug_click: "REJECTED - no mousedown", + mouseTracking: mouseTrackingRef.current, }) ); return; @@ -214,11 +261,10 @@ export function Timeline() { if (deltaX > 5 || deltaY > 5 || deltaTime > 500) { console.log( JSON.stringify({ - ignoredDragNotClick: true, + debug_click: "REJECTED - movement too large", deltaX, deltaY, deltaTime, - timeStamp: e.timeStamp, }) ); return; @@ -226,27 +272,54 @@ export function Timeline() { // Don't seek if this was a selection box operation if (isSelecting || justFinishedSelecting) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - selection operation", + isSelecting, + justFinishedSelecting, + }) + ); return; } // Don't seek if clicking on timeline elements, but still deselect if ((e.target as HTMLElement).closest(".timeline-element")) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - clicked timeline element", + }) + ); return; } // Don't seek if clicking on playhead if (playheadRef.current?.contains(e.target as Node)) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - clicked playhead", + }) + ); return; } // Don't seek if clicking on track labels if ((e.target as HTMLElement).closest("[data-track-labels]")) { + console.log( + JSON.stringify({ + debug_click: "REJECTED - clicked track labels", + }) + ); clearSelectedElements(); return; } // Clear selected elements when clicking empty timeline area - console.log(JSON.stringify({ clearingSelectedElements: true })); + console.log( + JSON.stringify({ + debug_click: "PROCEEDING - clearing elements", + clearingSelectedElements: true, + }) + ); clearSelectedElements(); // Determine if we're clicking in ruler or tracks area @@ -254,24 +327,39 @@ export function Timeline() { "[data-ruler-area]" ); + console.log( + JSON.stringify({ + debug_click: "CALCULATING POSITION", + isRulerClick, + clientX: e.clientX, + clientY: e.clientY, + target_element: (e.target as HTMLElement).tagName, + target_class: (e.target as HTMLElement).className, + }) + ); + 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 rulerContent = rulerScrollRef.current; + if (!rulerContent) { + console.log( + JSON.stringify({ + debug_click: "ERROR - no ruler container found", + }) + ); + 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 tracksContent = tracksScrollRef.current; + if (!tracksContent) { + return; + } const rect = tracksContent.getBoundingClientRect(); mouseX = e.clientX - rect.left; scrollLeft = tracksContent.scrollLeft; @@ -289,7 +377,6 @@ export function Timeline() { // Use frame snapping for timeline clicking const projectFps = activeProject?.fps || 30; const time = snapTimeToFrame(rawTime, projectFps); - seek(time); }, [ -- cgit v1.3.1 From 9c6556ea05693701450c1977267b761e0fb3488c Mon Sep 17 00:00:00 2001 From: Mathis Boulais <77585805+MathisBls@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:57:47 +0200 Subject: fix: restore horizontal scroll sync after Radix ScrollArea removal (#518) --- apps/web/src/components/editor/timeline/index.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'apps/web/src/components/editor/timeline/index.tsx') diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx index dd81b7b3..bb8dfbc0 100644 --- a/apps/web/src/components/editor/timeline/index.tsx +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -513,15 +513,9 @@ export function Timeline() { // --- Scroll synchronization effect --- useEffect(() => { - const rulerViewport = rulerScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - const tracksViewport = tracksScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; - const trackLabelsViewport = trackLabelsScrollRef.current?.querySelector( - "[data-radix-scroll-area-viewport]" - ) as HTMLElement; + const rulerViewport = rulerScrollRef.current; + const tracksViewport = tracksScrollRef.current; + const trackLabelsViewport = trackLabelsScrollRef.current; if (!rulerViewport || !tracksViewport) return; -- cgit v1.3.1 From ce58b5199adae444c6ade029c14c496379932d83 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 6 Aug 2025 12:52:11 +0200 Subject: feat: external files always create new tracks at playhead position --- apps/web/src/components/editor/timeline/index.tsx | 18 ++++++++++++++++-- .../src/components/editor/timeline/timeline-track.tsx | 12 ++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'apps/web/src/components/editor/timeline/index.tsx') diff --git a/apps/web/src/components/editor/timeline/index.tsx b/apps/web/src/components/editor/timeline/index.tsx index bb8dfbc0..8bb20d72 100644 --- a/apps/web/src/components/editor/timeline/index.tsx +++ b/apps/web/src/components/editor/timeline/index.tsx @@ -53,7 +53,7 @@ import { SelectionBox } from "../selection-box"; import { useSelectionBox } from "@/hooks/use-selection-box"; import { SnapIndicator } from "../snap-indicator"; import { SnapPoint } from "@/hooks/use-timeline-snapping"; -import type { DragData, TimelineTrack } from "@/types/timeline"; +import type { DragData, TimelineTrack, TrackType } from "@/types/timeline"; import { getTrackHeight, getCumulativeHeightBefore, @@ -490,7 +490,21 @@ export function Timeline() { item.name === processedItem.name && item.url === processedItem.url ); if (addedItem) { - useTimelineStore.getState().addMediaToNewTrack(addedItem); + const trackType: TrackType = + addedItem.type === "audio" ? "audio" : "media"; + const targetTrackId = useTimelineStore + .getState() + .insertTrackAt(trackType, 0); + + useTimelineStore.getState().addElementToTrack(targetTrackId, { + type: "media", + mediaId: addedItem.id, + name: addedItem.name, + duration: addedItem.duration || 5, + startTime: currentTime, + trimStart: 0, + trimEnd: 0, + }); } } } catch (error) { diff --git a/apps/web/src/components/editor/timeline/timeline-track.tsx b/apps/web/src/components/editor/timeline/timeline-track.tsx index 75910c46..ff4797fb 100644 --- a/apps/web/src/components/editor/timeline/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline/timeline-track.tsx @@ -15,6 +15,7 @@ import { usePlaybackStore } from "@/stores/playback-store"; import type { TimelineElement as TimelineElementType, DragData, + TrackType, } from "@/types/timeline"; import { snapTimeToFrame, @@ -1061,7 +1062,7 @@ export function TimelineTrackContent({ return; } - // Process and add files to timeline at the drop position + // Process and add files to new timeline tracks at playhead position processMediaFiles(e.dataTransfer.files) .then(async (processedItems) => { for (const processedItem of processedItems) { @@ -1074,13 +1075,16 @@ export function TimelineTrackContent({ ); if (addedItem) { - // Add to current track at the drop position - addElementToTrack(track.id, { + const trackType: TrackType = + addedItem.type === "audio" ? "audio" : "media"; + const targetTrackId = insertTrackAt(trackType, 0); + + addElementToTrack(targetTrackId, { type: "media", mediaId: addedItem.id, name: addedItem.name, duration: addedItem.duration || 5, - startTime: snappedTime, + startTime: currentTime, trimStart: 0, trimEnd: 0, }); -- cgit v1.3.1