refactor: centralize editor interaction state

Move timeline and preview gestures into dedicated controllers so hooks stay thin around editor state and DOM events.

This also routes drag and playback synchronization through manager APIs and reuses persistent wasm surfaces so scrubbing, seeking, and preview rendering stay in sync.

Made-with: Cursor
This commit is contained in:
Maze Winther
2026-04-25 03:56:22 +02:00
parent 0dffefdd59
commit 6a22a3ecbd
38 changed files with 5692 additions and 4773 deletions
+36 -33
View File
@@ -29,7 +29,7 @@ import {
useState,
type ReactNode,
} from "react";
import type { ElementDragState, DropTarget } from "@/timeline";
import type { ElementDragView, DropTarget } from "@/timeline";
import { TimelineTrackContent } from "./timeline-track";
import { TimelinePlayhead } from "./timeline-playhead";
import { SelectionBox } from "@/selection/selection-box";
@@ -287,20 +287,15 @@ export function Timeline() {
isReady: tracks.length > 0,
});
const {
dragState,
dragDropTarget,
handleElementMouseDown,
handleElementClick,
lastMouseXRef,
} = useElementInteraction({
zoomLevel,
timelineRef,
tracksContainerRef,
tracksScrollRef,
snappingEnabled,
onSnapPointChange: handleSnapPointChange,
});
const { dragView, handleElementMouseDown, handleElementClick } =
useElementInteraction({
zoomLevel,
tracksContainerRef,
tracksScrollRef,
snappingEnabled,
onSnapPointChange: handleSnapPointChange,
});
const isElementDragging = dragView.kind === "dragging";
const {
dragState: bookmarkDragState,
@@ -391,10 +386,19 @@ export function Timeline() {
contentWidth: dynamicTimelineWidth,
});
useEdgeAutoScroll({
isActive: isElementDragging,
getMouseClientX: () =>
dragView.kind === "dragging" ? dragView.currentMouseX : 0,
rulerScrollRef,
tracksScrollRef,
contentWidth: dynamicTimelineWidth,
});
const showSnapIndicator =
snappingEnabled &&
currentSnapPoint !== null &&
(dragState.isDragging || bookmarkDragState.isDragging || isResizing);
(isElementDragging || bookmarkDragState.isDragging || isResizing);
const {
handleTracksMouseDown,
@@ -457,9 +461,9 @@ export function Timeline() {
headerHeight={timelineHeaderHeight}
/>
<DragLine
dropTarget={dragDropTarget}
dropTarget={isElementDragging ? dragView.dropTarget : null}
tracks={tracks}
isVisible={dragState.isDragging}
isVisible={isElementDragging}
headerHeight={timelineHeaderHeight}
/>
@@ -540,9 +544,7 @@ export function Timeline() {
<TimelineTrackRows
mainTrackId={mainTrackId}
zoomLevel={zoomLevel}
dragState={dragState}
tracksScrollRef={tracksScrollRef}
lastMouseXRef={lastMouseXRef}
dragView={dragView}
onResizeStart={handleResizeStart}
onElementMouseDown={handleElementMouseDown}
onElementClick={handleElementClick}
@@ -718,9 +720,7 @@ function TrackLabelsPanel({
function TimelineTrackRows({
mainTrackId,
zoomLevel,
dragState,
tracksScrollRef,
lastMouseXRef,
dragView,
onResizeStart,
onElementMouseDown,
onElementClick,
@@ -732,9 +732,7 @@ function TimelineTrackRows({
}: {
mainTrackId: string | null;
zoomLevel: number;
dragState: ElementDragState;
tracksScrollRef: React.RefObject<HTMLDivElement | null>;
lastMouseXRef: React.RefObject<number>;
dragView: ElementDragView;
onResizeStart: React.ComponentProps<
typeof TimelineTrackContent
>["onResizeStart"];
@@ -776,8 +774,16 @@ function TimelineTrackRows({
[tracks, expandedElementIds],
);
const draggingElementIds = useMemo(
() =>
dragView.kind === "dragging"
? dragView.memberTimeOffsets
: (null as ReadonlyMap<string, number> | null),
[dragView],
);
const sortedTracks = useMemo(() => {
const draggingElementIds = new Set(dragState.dragElementIds);
if (!draggingElementIds)
return tracks.map((track, index) => ({ track, index }));
return [...tracks]
.map((track, index) => ({ track, index }))
.sort((a, b) => {
@@ -791,7 +797,7 @@ function TimelineTrackRows({
if (bHasDragged) return -1;
return 0;
});
}, [tracks, dragState.dragElementIds]);
}, [tracks, draggingElementIds]);
return (
<>
@@ -811,10 +817,7 @@ function TimelineTrackRows({
<TimelineTrackContent
track={track}
zoomLevel={zoomLevel}
dragState={dragState}
rulerScrollRef={tracksScrollRef}
tracksScrollRef={tracksScrollRef}
lastMouseXRef={lastMouseXRef}
dragView={dragView}
onResizeStart={onResizeStart}
onElementMouseDown={onElementMouseDown}
onElementClick={onElementClick}