refactor: thinner hooks

This commit is contained in:
Maze Winther
2026-04-26 17:31:28 +02:00
parent e6c6193638
commit 56ca09692a
48 changed files with 5355 additions and 4264 deletions
+30 -27
View File
@@ -30,7 +30,7 @@ import {
type ReactNode,
} from "react";
import type { MediaTime } from "@/wasm";
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";
@@ -288,20 +288,15 @@ export function Timeline() {
isReady: tracks.length > 0,
});
const {
dragState,
dragDropTarget,
handleElementMouseDown,
handleElementClick,
lastMouseXRef,
} = useElementInteraction({
const { dragView, handleElementMouseDown, handleElementClick } =
useElementInteraction({
zoomLevel,
timelineRef,
tracksContainerRef,
tracksScrollRef,
snappingEnabled,
onSnapPointChange: handleSnapPointChange,
});
const isElementDragging = dragView.kind === "dragging";
const {
dragState: bookmarkDragState,
@@ -392,10 +387,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,
@@ -458,9 +462,9 @@ export function Timeline() {
headerHeight={timelineHeaderHeight}
/>
<DragLine
dropTarget={dragDropTarget}
dropTarget={isElementDragging ? dragView.dropTarget : null}
tracks={tracks}
isVisible={dragState.isDragging}
isVisible={isElementDragging}
headerHeight={timelineHeaderHeight}
/>
@@ -541,9 +545,7 @@ export function Timeline() {
<TimelineTrackRows
mainTrackId={mainTrackId}
zoomLevel={zoomLevel}
dragState={dragState}
tracksScrollRef={tracksScrollRef}
lastMouseXRef={lastMouseXRef}
dragView={dragView}
onResizeStart={handleResizeStart}
onElementMouseDown={handleElementMouseDown}
onElementClick={handleElementClick}
@@ -719,9 +721,7 @@ function TrackLabelsPanel({
function TimelineTrackRows({
mainTrackId,
zoomLevel,
dragState,
tracksScrollRef,
lastMouseXRef,
dragView,
onResizeStart,
onElementMouseDown,
onElementClick,
@@ -733,9 +733,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"];
@@ -777,8 +775,16 @@ function TimelineTrackRows({
[tracks, expandedElementIds],
);
const draggingElementIds = useMemo(
() =>
dragView.kind === "dragging"
? dragView.memberTimeOffsets
: (null as ReadonlyMap<string, MediaTime> | 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) => {
@@ -792,7 +798,7 @@ function TimelineTrackRows({
if (bHasDragged) return -1;
return 0;
});
}, [tracks, dragState.dragElementIds]);
}, [tracks, draggingElementIds]);
return (
<>
@@ -812,10 +818,7 @@ function TimelineTrackRows({
<TimelineTrackContent
track={track}
zoomLevel={zoomLevel}
dragState={dragState}
rulerScrollRef={tracksScrollRef}
tracksScrollRef={tracksScrollRef}
lastMouseXRef={lastMouseXRef}
dragView={dragView}
onResizeStart={onResizeStart}
onElementMouseDown={onElementMouseDown}
onElementClick={onElementClick}
@@ -34,7 +34,7 @@ import {
import type {
TimelineElement as TimelineElementType,
TimelineTrack,
ElementDragState,
ElementDragView,
VideoElement,
ImageElement,
AudioElement,
@@ -49,12 +49,7 @@ import {
import { buildWaveformGainSamples } from "@/timeline/audio-state";
import { getTimelinePixelsPerSecond } from "@/timeline";
import { buildWaveformSourceKey } from "@/media/waveform-summary";
import {
addMediaTime,
type MediaTime,
TICKS_PER_SECOND,
ZERO_MEDIA_TIME,
} from "@/wasm";
import { addMediaTime, type MediaTime, TICKS_PER_SECOND } from "@/wasm";
import {
getActionDefinition,
type TAction,
@@ -219,7 +214,7 @@ interface TimelineElementProps {
event: React.MouseEvent,
element: TimelineElementType,
) => void;
dragState: ElementDragState;
dragView: ElementDragView;
isDropTarget?: boolean;
}
@@ -231,7 +226,7 @@ export function TimelineElement({
onResizeStart,
onElementMouseDown,
onElementClick,
dragState,
dragView,
isDropTarget = false,
}: TimelineElementProps) {
const mediaAssets = useEditor((e) => e.media.getAssets());
@@ -257,16 +252,18 @@ export function TimelineElement({
selected.elementId === element.id && selected.trackId === track.id,
);
const isBeingDragged = dragState.dragElementIds.includes(element.id);
const isDragging = dragView.kind === "dragging";
const dragTimeOffset = isDragging
? dragView.memberTimeOffsets.get(element.id)
: undefined;
const isBeingDragged = dragTimeOffset !== undefined;
const dragOffsetY =
isBeingDragged && dragState.isDragging
? dragState.currentMouseY - dragState.startMouseY
isDragging && isBeingDragged
? dragView.currentMouseY - dragView.startMouseY
: 0;
const dragTimeOffset =
dragState.dragTimeOffsets[element.id] ?? ZERO_MEDIA_TIME;
const elementStartTime =
isBeingDragged && dragState.isDragging
? addMediaTime({ a: dragState.currentTime, b: dragTimeOffset })
isDragging && isBeingDragged
? addMediaTime({ a: dragView.currentTime, b: dragTimeOffset })
: renderElement.startTime;
const displayedStartTime = elementStartTime;
const displayedDuration = renderElement.duration;
@@ -388,7 +385,7 @@ export function TimelineElement({
? `${baseTrackHeight + expansionHeight}px`
: "100%",
transform:
isBeingDragged && dragState.isDragging
isDragging && isBeingDragged
? `translate3d(0, ${dragOffsetY}px, 0)`
: undefined,
}}
@@ -5,18 +5,12 @@ import { TimelineElement } from "./timeline-element";
import type { TimelineTrack } from "@/timeline";
import type { TimelineElement as TimelineElementType } from "@/timeline";
import { TIMELINE_LAYERS } from "./layers";
import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale";
import { useEdgeAutoScroll } from "@/timeline/hooks/use-edge-auto-scroll";
import type { ElementDragState } from "@/timeline";
import { useEditor } from "@/editor/use-editor";
import type { ElementDragView } from "@/timeline";
interface TimelineTrackContentProps {
track: TimelineTrack;
zoomLevel: number;
dragState: ElementDragState;
rulerScrollRef: React.RefObject<HTMLDivElement | null>;
tracksScrollRef: React.RefObject<HTMLDivElement | null>;
lastMouseXRef: React.RefObject<number>;
dragView: ElementDragView;
onResizeStart: (params: {
event: React.MouseEvent;
element: TimelineElementType;
@@ -42,10 +36,7 @@ interface TimelineTrackContentProps {
export function TimelineTrackContent({
track,
zoomLevel,
dragState,
rulerScrollRef,
tracksScrollRef,
lastMouseXRef,
dragView,
onResizeStart,
onElementMouseDown,
onElementClick,
@@ -55,15 +46,6 @@ export function TimelineTrackContent({
targetElementId = null,
}: TimelineTrackContentProps) {
const { isElementSelected } = useElementSelection();
const duration = useEditor((e) => e.timeline.getTotalDuration());
useEdgeAutoScroll({
isActive: dragState.isDragging,
getMouseClientX: () => lastMouseXRef.current ?? 0,
rulerScrollRef,
tracksScrollRef,
contentWidth: duration * BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel,
});
return (
<div className="relative size-full">
@@ -120,7 +102,7 @@ export function TimelineTrackContent({
onElementClick={(event, element) =>
onElementClick({ event, element, track })
}
dragState={dragState}
dragView={dragView}
isDropTarget={element.id === targetElementId}
/>
);