summaryrefslogtreecommitdiff
path: root/apps/web/src/hooks
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-07 04:41:27 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-07 04:41:27 +0200
commitcbd1b8212370c67b34a70dccd21bd82de72fead2 (patch)
treed97cbee2aae22c22b59c59a50e9f8a6af0ae8ccf /apps/web/src/hooks
parent4f7d401a974047d37673ee8e3840dceaaddc295c (diff)
downloadopencut-cbd1b8212370c67b34a70dccd21bd82de72fead2.tar.gz
refactor: structured track layout per scene
Diffstat (limited to 'apps/web/src/hooks')
-rw-r--r--apps/web/src/hooks/actions/use-editor-actions.ts16
-rw-r--r--apps/web/src/hooks/timeline/element/use-element-interaction.ts24
-rw-r--r--apps/web/src/hooks/timeline/element/use-element-resize.ts2
-rw-r--r--apps/web/src/hooks/timeline/use-bookmark-drag.ts2
-rw-r--r--apps/web/src/hooks/timeline/use-timeline-drag-drop.ts55
-rw-r--r--apps/web/src/hooks/timeline/use-timeline-playhead.ts2
-rw-r--r--apps/web/src/hooks/use-element-preview.ts13
-rw-r--r--apps/web/src/hooks/use-mask-handles.ts4
-rw-r--r--apps/web/src/hooks/use-preview-interaction.ts4
-rw-r--r--apps/web/src/hooks/use-transform-handles.ts4
10 files changed, 85 insertions, 41 deletions
diff --git a/apps/web/src/hooks/actions/use-editor-actions.ts b/apps/web/src/hooks/actions/use-editor-actions.ts
index 599b4260..be04a7d3 100644
--- a/apps/web/src/hooks/actions/use-editor-actions.ts
+++ b/apps/web/src/hooks/actions/use-editor-actions.ts
@@ -179,11 +179,12 @@ export function useEditorActions() {
"split",
() => {
const currentTime = editor.playback.getCurrentTime();
+ const tracks = editor.scenes.getActiveScene().tracks;
const elementsToSplit =
selectedElements.length > 0
? selectedElements
: getElementsAtTime({
- tracks: editor.timeline.getTracks(),
+ tracks,
time: currentTime,
});
@@ -201,11 +202,12 @@ export function useEditorActions() {
"split-left",
() => {
const currentTime = editor.playback.getCurrentTime();
+ const tracks = editor.scenes.getActiveScene().tracks;
const elementsToSplit =
selectedElements.length > 0
? selectedElements
: getElementsAtTime({
- tracks: editor.timeline.getTracks(),
+ tracks,
time: currentTime,
});
@@ -233,11 +235,12 @@ export function useEditorActions() {
"split-right",
() => {
const currentTime = editor.playback.getCurrentTime();
+ const tracks = editor.scenes.getActiveScene().tracks;
const elementsToSplit =
selectedElements.length > 0
? selectedElements
: getElementsAtTime({
- tracks: editor.timeline.getTracks(),
+ tracks,
time: currentTime,
});
@@ -310,7 +313,12 @@ export function useEditorActions() {
useActionHandler(
"select-all",
() => {
- const allElements = editor.timeline.getTracks().flatMap((track) =>
+ const scene = editor.scenes.getActiveScene();
+ const allElements = [
+ ...scene.tracks.overlay,
+ scene.tracks.main,
+ ...scene.tracks.audio,
+ ].flatMap((track) =>
track.elements.map((element) => ({
trackId: track.id,
elementId: element.id,
diff --git a/apps/web/src/hooks/timeline/element/use-element-interaction.ts b/apps/web/src/hooks/timeline/element/use-element-interaction.ts
index 6fcd4168..98918bea 100644
--- a/apps/web/src/hooks/timeline/element/use-element-interaction.ts
+++ b/apps/web/src/hooks/timeline/element/use-element-interaction.ts
@@ -21,6 +21,7 @@ import { registerCanceller } from "@/lib/cancel-interaction";
import type {
DropTarget,
ElementDragState,
+ SceneTracks,
TimelineElement,
TimelineTrack,
} from "@/lib/timeline";
@@ -101,7 +102,7 @@ function getDragDropTarget({
clientY: number;
elementId: string;
trackId: string;
- tracks: TimelineTrack[];
+ tracks: SceneTracks;
tracksContainerRef: RefObject<HTMLDivElement | null>;
tracksScrollRef: RefObject<HTMLDivElement | null>;
headerRef?: RefObject<HTMLElement | null>;
@@ -113,7 +114,11 @@ function getDragDropTarget({
const scrollContainer = tracksScrollRef.current;
if (!containerRect || !scrollContainer) return null;
- const sourceTrack = tracks.find(({ id }) => id === trackId);
+ const sourceTrack = [
+ ...tracks.overlay,
+ tracks.main,
+ ...tracks.audio,
+ ].find(({ id }) => id === trackId);
const movingElement = sourceTrack?.elements.find(
({ id }) => id === elementId,
);
@@ -163,7 +168,12 @@ export function useElementInteraction({
}: UseElementInteractionProps) {
const editor = useEditor();
const isShiftHeldRef = useShiftKey();
- const tracks = editor.timeline.getTracks();
+ const sceneTracks = editor.scenes.getActiveScene().tracks;
+ const tracks = [
+ ...sceneTracks.overlay,
+ sceneTracks.main,
+ ...sceneTracks.audio,
+ ];
const {
isElementSelected,
selectElement,
@@ -242,7 +252,7 @@ export function useElementInteraction({
const startSnap = snapElementEdge({
targetTime: frameSnappedTime,
elementDuration,
- tracks,
+ tracks: sceneTracks,
playheadTime,
zoomLevel,
excludeElementId: movingElement.id,
@@ -252,7 +262,7 @@ export function useElementInteraction({
const endSnap = snapElementEdge({
targetTime: frameSnappedTime,
elementDuration,
- tracks,
+ tracks: sceneTracks,
playheadTime,
zoomLevel,
excludeElementId: movingElement.id,
@@ -376,7 +386,7 @@ export function useElementInteraction({
clientY,
elementId: dragState.elementId,
trackId: dragState.trackId,
- tracks,
+ tracks: sceneTracks,
tracksContainerRef,
tracksScrollRef,
headerRef,
@@ -436,7 +446,7 @@ export function useElementInteraction({
clientY,
elementId: dragState.elementId,
trackId: dragState.trackId,
- tracks,
+ tracks: sceneTracks,
tracksContainerRef,
tracksScrollRef,
headerRef,
diff --git a/apps/web/src/hooks/timeline/element/use-element-resize.ts b/apps/web/src/hooks/timeline/element/use-element-resize.ts
index dec87eef..bb0d880c 100644
--- a/apps/web/src/hooks/timeline/element/use-element-resize.ts
+++ b/apps/web/src/hooks/timeline/element/use-element-resize.ts
@@ -196,7 +196,7 @@ export function useTimelineElementResize({
const minDuration = Math.round(TICKS_PER_SECOND * projectFps.denominator / projectFps.numerator);
const shouldSnap = snappingEnabled && !isShiftHeldRef.current;
if (shouldSnap) {
- const tracks = editor.timeline.getTracks();
+ const tracks = editor.scenes.getActiveScene().tracks;
const playheadTime = editor.playback.getCurrentTime();
const snapPoints = findSnapPoints({
tracks,
diff --git a/apps/web/src/hooks/timeline/use-bookmark-drag.ts b/apps/web/src/hooks/timeline/use-bookmark-drag.ts
index 90edbede..3ff1cb21 100644
--- a/apps/web/src/hooks/timeline/use-bookmark-drag.ts
+++ b/apps/web/src/hooks/timeline/use-bookmark-drag.ts
@@ -44,7 +44,7 @@ export function useBookmarkDrag({
}: UseBookmarkDragProps) {
const editor = useEditor();
const isShiftHeldRef = useShiftKey();
- const tracks = editor.timeline.getTracks();
+ const tracks = editor.scenes.getActiveScene().tracks;
const activeScene = editor.scenes.getActiveScene();
const bookmarks = activeScene?.bookmarks ?? [];
const playheadTime = editor.playback.getCurrentTime();
diff --git a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts b/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
index 7b9ee2ee..009f0ff6 100644
--- a/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-drag-drop.ts
@@ -17,7 +17,6 @@ import { AddTrackCommand, InsertElementCommand } from "@/lib/commands/timeline";
import { BatchCommand } from "@/lib/commands";
import { computeDropTarget } from "@/components/editor/panels/timeline/drop-target";
import { getDragData, hasDragData } from "@/lib/drag-data";
-import { isMainTrack } from "@/lib/timeline/placement";
import type { TrackType, DropTarget, ElementType } from "@/lib/timeline";
import type {
MediaDragData,
@@ -149,13 +148,13 @@ export function useTimelineDragDrop({
? (dragData as MediaDragData).targetElementTypes
: undefined;
- const tracks = editor.timeline.getTracks();
+ const sceneTracks = editor.scenes.getActiveScene().tracks;
const currentTime = editor.playback.getCurrentTime();
const target = computeDropTarget({
elementType,
mouseX,
mouseY,
- tracks,
+ tracks: sceneTracks,
playheadTime: currentTime,
isExternalDrop: isExternal,
elementDuration: duration,
@@ -230,7 +229,11 @@ export function useTimelineDragDrop({
return;
}
- const tracks = editor.timeline.getTracks();
+ const tracks = [
+ ...editor.scenes.getActiveScene().tracks.overlay,
+ editor.scenes.getActiveScene().tracks.main,
+ ...editor.scenes.getActiveScene().tracks.audio,
+ ];
const track = tracks[target.trackIndex];
if (!track) return;
editor.timeline.insertElement({
@@ -267,7 +270,11 @@ export function useTimelineDragDrop({
return;
}
- const tracks = editor.timeline.getTracks();
+ const tracks = [
+ ...editor.scenes.getActiveScene().tracks.overlay,
+ editor.scenes.getActiveScene().tracks.main,
+ ...editor.scenes.getActiveScene().tracks.audio,
+ ];
const track = tracks[target.trackIndex];
if (!track) return;
editor.timeline.insertElement({
@@ -305,7 +312,11 @@ export function useTimelineDragDrop({
return;
}
- const tracks = editor.timeline.getTracks();
+ const tracks = [
+ ...editor.scenes.getActiveScene().tracks.overlay,
+ editor.scenes.getActiveScene().tracks.main,
+ ...editor.scenes.getActiveScene().tracks.audio,
+ ];
const track = tracks[target.trackIndex];
if (!track) return;
editor.timeline.insertElement({
@@ -352,7 +363,11 @@ export function useTimelineDragDrop({
return;
}
- const tracks = editor.timeline.getTracks();
+ const tracks = [
+ ...editor.scenes.getActiveScene().tracks.overlay,
+ editor.scenes.getActiveScene().tracks.main,
+ ...editor.scenes.getActiveScene().tracks.audio,
+ ];
const track = tracks[target.trackIndex];
if (!track) return;
editor.timeline.insertElement({
@@ -380,7 +395,11 @@ export function useTimelineDragDrop({
return;
}
- const tracks = editor.timeline.getTracks();
+ const tracks = [
+ ...editor.scenes.getActiveScene().tracks.overlay,
+ editor.scenes.getActiveScene().tracks.main,
+ ...editor.scenes.getActiveScene().tracks.audio,
+ ];
const effectTrack = tracks.find((t) => t.type === "effect");
let trackId: string;
@@ -447,16 +466,14 @@ export function useTimelineDragDrop({
const duration =
createdAsset.duration ??
DEFAULT_NEW_ELEMENT_DURATION;
- const currentTracks = editor.timeline.getTracks();
+ const sceneTracks = editor.scenes.getActiveScene().tracks;
const currentTime = editor.playback.getCurrentTime();
- const onlyTrack = currentTracks[0];
const reuseMainTrackId =
createdAsset.type !== "audio" &&
- currentTracks.length === 1 &&
- !!onlyTrack &&
- isMainTrack(onlyTrack) &&
- onlyTrack.elements.length === 0
- ? onlyTrack.id
+ sceneTracks.overlay.length === 0 &&
+ sceneTracks.audio.length === 0 &&
+ sceneTracks.main.elements.length === 0
+ ? sceneTracks.main.id
: null;
const dropTarget = reuseMainTrackId
? null
@@ -464,7 +481,7 @@ export function useTimelineDragDrop({
elementType: createdAsset.type,
mouseX,
mouseY,
- tracks: currentTracks,
+ tracks: sceneTracks,
playheadTime: currentTime,
isExternalDrop: true,
elementDuration: duration,
@@ -488,7 +505,11 @@ export function useTimelineDragDrop({
trackId = addTrackCmd.getTrackId();
editor.command.execute({ command: addTrackCmd });
} else {
- trackId = currentTracks[dropTarget.trackIndex]?.id;
+ trackId = [
+ ...sceneTracks.overlay,
+ sceneTracks.main,
+ ...sceneTracks.audio,
+ ][dropTarget.trackIndex]?.id;
}
}
diff --git a/apps/web/src/hooks/timeline/use-timeline-playhead.ts b/apps/web/src/hooks/timeline/use-timeline-playhead.ts
index 70364e90..1886222f 100644
--- a/apps/web/src/hooks/timeline/use-timeline-playhead.ts
+++ b/apps/web/src/hooks/timeline/use-timeline-playhead.ts
@@ -90,7 +90,7 @@ export function useTimelinePlayhead({
const shouldSnap = snappingEnabled && !isShiftHeldRef.current;
const time = (() => {
if (!shouldSnap) return frameTime;
- const tracks = editor.timeline.getTracks();
+ const tracks = editor.scenes.getActiveScene().tracks;
const bookmarks = editor.scenes.getActiveScene()?.bookmarks ?? [];
const snapPoints = findSnapPoints({
tracks,
diff --git a/apps/web/src/hooks/use-element-preview.ts b/apps/web/src/hooks/use-element-preview.ts
index d717143d..4f28be83 100644
--- a/apps/web/src/hooks/use-element-preview.ts
+++ b/apps/web/src/hooks/use-element-preview.ts
@@ -1,5 +1,5 @@
import { useEditor } from "@/hooks/use-editor";
-import type { TimelineElement } from "@/lib/timeline";
+import { findTrackInSceneTracks, type TimelineElement } from "@/lib/timeline";
/**
* Subscribes to render tracks and returns the live (preview-aware) version of
@@ -18,13 +18,14 @@ export function useElementPreview<T extends TimelineElement>({
fallback: T;
}) {
const editor = useEditor();
- useEditor((e) => e.timeline.getRenderTracks());
+ useEditor((e) => e.timeline.getPreviewTracks());
+ const previewTracks = editor.timeline.getPreviewTracks();
const renderElement =
- (editor.timeline
- .getRenderTracks()
- .find((t) => t.id === trackId)
- ?.elements.find((el) => el.id === elementId) as T | undefined) ??
+ (findTrackInSceneTracks({
+ tracks: previewTracks ?? editor.scenes.getActiveScene().tracks,
+ trackId,
+ })?.elements.find((element) => element.id === elementId) as T | undefined) ??
fallback;
const previewUpdates = (updates: Partial<TimelineElement>) =>
diff --git a/apps/web/src/hooks/use-mask-handles.ts b/apps/web/src/hooks/use-mask-handles.ts
index 63fdd674..d2400ee0 100644
--- a/apps/web/src/hooks/use-mask-handles.ts
+++ b/apps/web/src/hooks/use-mask-handles.ts
@@ -45,7 +45,9 @@ export function useMaskHandles({
null,
);
- const tracks = useEditor((e) => e.timeline.getRenderTracks());
+ const tracks = useEditor(
+ (e) => e.timeline.getPreviewTracks() ?? e.scenes.getActiveScene().tracks,
+ );
const currentTime = useEditor((e) => e.playback.getCurrentTime());
const mediaAssets = useEditor((e) => e.media.getAssets());
const canvasSize = useEditor(
diff --git a/apps/web/src/hooks/use-preview-interaction.ts b/apps/web/src/hooks/use-preview-interaction.ts
index 90fa0450..2a3e5c28 100644
--- a/apps/web/src/hooks/use-preview-interaction.ts
+++ b/apps/web/src/hooks/use-preview-interaction.ts
@@ -180,7 +180,7 @@ export function usePreviewInteraction({
({ clientX, clientY }: React.MouseEvent) => {
if (editingText || isMaskMode) return;
- const tracks = editor.timeline.getTracks();
+ const tracks = editor.scenes.getActiveScene().tracks;
const currentTime = editor.playback.getCurrentTime();
const mediaAssets = editor.media.getAssets();
const canvasSize = editor.project.getActive().settings.canvasSize;
@@ -238,7 +238,7 @@ export function usePreviewInteraction({
if (isMaskMode) return;
if (button !== 0) return;
- const tracks = editor.timeline.getTracks();
+ const tracks = editor.scenes.getActiveScene().tracks;
const currentTime = editor.playback.getCurrentTime();
const mediaAssets = editor.media.getAssets();
const canvasSize = editor.project.getActive().settings.canvasSize;
diff --git a/apps/web/src/hooks/use-transform-handles.ts b/apps/web/src/hooks/use-transform-handles.ts
index 659aa598..7495a387 100644
--- a/apps/web/src/hooks/use-transform-handles.ts
+++ b/apps/web/src/hooks/use-transform-handles.ts
@@ -132,7 +132,9 @@ export function useTransformHandles({
);
const selectedElements = useEditor((e) => e.selection.getSelectedElements());
- const tracks = useEditor((e) => e.timeline.getRenderTracks());
+ const tracks = useEditor(
+ (e) => e.timeline.getPreviewTracks() ?? e.scenes.getActiveScene().tracks,
+ );
const currentTime = useEditor((e) => e.playback.getCurrentTime());
const currentTimeRef = useRef(currentTime);
currentTimeRef.current = currentTime;