diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index ae6b98d..46aaf13 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -100,6 +100,28 @@ export function Timeline() { const [isScrubbing, setIsScrubbing] = useState(false); const [scrubTime, setScrubTime] = useState(null); + // Dynamic timeline width calculation based on playhead position and duration + const dynamicTimelineWidth = Math.max( + (duration || 0) * 50 * zoomLevel, // Base width from duration + (currentTime + 30) * 50 * 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 isUpdatingRef = useRef(false); + const lastRulerSync = useRef(0); + const lastTracksSync = useRef(0); + + // New refs for direct playhead DOM manipulation + const rulerPlayheadRef = useRef(null); + const tracksPlayheadRef = useRef(null); + + // Refs to store initial mouse and scroll positions for drag calculations + const initialMouseXRef = useRef(0); + const initialTimelineScrollLeftRef = useRef(0); + // Update timeline duration when tracks change useEffect(() => { const totalDuration = getTotalDuration(); @@ -562,6 +584,61 @@ export function Timeline() { }; }, [isInTimeline]); + // --- 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; + if (!rulerViewport || !tracksViewport) return; + const handleRulerScroll = () => { + const now = Date.now(); + if (isUpdatingRef.current || now - lastRulerSync.current < 16) return; + lastRulerSync.current = now; + isUpdatingRef.current = true; + tracksViewport.scrollLeft = rulerViewport.scrollLeft; + isUpdatingRef.current = false; + }; + const handleTracksScroll = () => { + const now = Date.now(); + if (isUpdatingRef.current || now - lastTracksSync.current < 16) return; + lastTracksSync.current = now; + isUpdatingRef.current = true; + rulerViewport.scrollLeft = tracksViewport.scrollLeft; + isUpdatingRef.current = false; + }; + rulerViewport.addEventListener('scroll', handleRulerScroll); + tracksViewport.addEventListener('scroll', handleTracksScroll); + return () => { + rulerViewport.removeEventListener('scroll', handleRulerScroll); + tracksViewport.removeEventListener('scroll', handleTracksScroll); + }; + }, [duration, zoomLevel]); + + // --- Playhead auto-scroll 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; + if (!rulerViewport || !tracksViewport) return; + const playheadPx = playheadPosition * 50 * zoomLevel; + const viewportWidth = rulerViewport.clientWidth; + const scrollMin = 0; + const scrollMax = rulerViewport.scrollWidth - viewportWidth; + // Center the playhead if it's not visible (60px buffer) + const desiredScroll = Math.max( + scrollMin, + Math.min(scrollMax, playheadPx - viewportWidth / 2) + ); + if ( + playheadPx < rulerViewport.scrollLeft + 60 || + playheadPx > rulerViewport.scrollLeft + viewportWidth - 60 + ) { + rulerViewport.scrollLeft = tracksViewport.scrollLeft = desiredScroll; + } + // Keep both scrolls in sync + if (rulerViewport.scrollLeft !== tracksViewport.scrollLeft) { + rulerViewport.scrollLeft = tracksViewport.scrollLeft = Math.max(rulerViewport.scrollLeft, tracksViewport.scrollLeft); + } + }, [playheadPosition, duration, zoomLevel]); + return (
- +
{ // Calculate the clicked time position and seek to it @@ -876,17 +953,12 @@ export function Timeline() { {/* Timeline Tracks Content */}
-
- {/* Timeline grid and clips area (with left margin for sifdebar) */} +
)}
-
+