Skip to content

Commit cbd1b82

Browse files
committed
refactor: structured track layout per scene
1 parent 4f7d401 commit cbd1b82

91 files changed

Lines changed: 2088 additions & 1380 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/web/src/components/editor/panels/assets/views/captions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function Captions() {
5959
setProcessingStep("Extracting audio...");
6060

6161
const audioBlob = await extractTimelineAudio({
62-
tracks: editor.timeline.getTracks(),
62+
tracks: editor.scenes.getActiveScene().tracks,
6363
mediaAssets: editor.media.getAssets(),
6464
totalDuration: editor.timeline.getTotalDuration(),
6565
});

apps/web/src/components/editor/panels/preview/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export function PreviewPanel() {
7373

7474
function RenderTreeController() {
7575
const editor = useEditor();
76-
const tracks = useEditor((e) => e.timeline.getRenderTracks());
76+
const tracks = useEditor(
77+
(e) => e.timeline.getPreviewTracks() ?? e.scenes.getActiveScene().tracks,
78+
);
7779
const mediaAssets = useEditor((e) => e.media.getAssets());
7880
const activeProject = useEditor((e) => e.project.getActive());
7981

apps/web/src/components/editor/panels/properties/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { EmptyView } from "./empty-view";
1717

1818
export function PropertiesPanel() {
1919
const editor = useEditor();
20-
useEditor((e) => e.timeline.getTracks());
20+
useEditor((e) => e.scenes.getActiveSceneOrNull());
2121
useEditor((e) => e.media.getAssets());
2222
const { selectedElements } = useElementSelection();
2323
const { activeTabPerType, setActiveTab } = usePropertiesStore();

apps/web/src/components/editor/panels/properties/tabs/masks-tab.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ export function MasksTab({ element, trackId }: MasksTabProps) {
8787
fallback: element,
8888
});
8989
const maskDefs = masksRegistry.getAll();
90-
const tracks = useEditor((e) => e.timeline.getRenderTracks());
90+
const tracks = useEditor(
91+
(e) => e.timeline.getPreviewTracks() ?? e.scenes.getActiveScene().tracks,
92+
);
9193
const currentTime = useEditor((e) => e.playback.getCurrentTime());
9294
const mediaAssets = useEditor((e) => e.media.getAssets());
9395
const canvasSize = useEditor(

apps/web/src/components/editor/panels/timeline/drop-target.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,16 @@ export function computeDropTarget({
105105
excludeElementId,
106106
targetElementTypes,
107107
}: ComputeDropTargetParams): DropTarget {
108+
const orderedTracks = [...tracks.overlay, tracks.main, ...tracks.audio];
109+
const mainTrackIndex = tracks.overlay.length;
108110
const xPosition =
109111
typeof startTimeOverride === "number"
110112
? startTimeOverride
111113
: isExternalDrop
112114
? playheadTime
113115
: Math.max(0, mouseX / (pixelsPerSecond * zoomLevel));
114116

115-
if (tracks.length === 0) {
117+
if (orderedTracks.length === 0) {
116118
const placementResult = resolveTrackPlacement({
117119
tracks,
118120
elementType,
@@ -139,7 +141,11 @@ export function computeDropTarget({
139141
};
140142
}
141143

142-
const trackAtMouse = getTrackAtY({ mouseY, tracks, verticalDragDirection });
144+
const trackAtMouse = getTrackAtY({
145+
mouseY,
146+
tracks: orderedTracks,
147+
verticalDragDirection,
148+
});
143149

144150
if (!trackAtMouse) {
145151
const isAboveAllTracks = mouseY < 0;
@@ -150,7 +156,7 @@ export function computeDropTarget({
150156
timeSpans: [{ startTime: xPosition, duration: elementDuration, excludeElementId }],
151157
strategy: {
152158
type: "preferIndex",
153-
trackIndex: isAboveAllTracks ? 0 : tracks.length - 1,
159+
trackIndex: isAboveAllTracks ? 0 : orderedTracks.length - 1,
154160
hoverDirection: isAboveAllTracks ? "above" : "below",
155161
createNewTrackOnly: true,
156162
},
@@ -171,12 +177,12 @@ export function computeDropTarget({
171177
}
172178

173179
const { trackIndex, relativeY } = trackAtMouse;
174-
const track = tracks[trackIndex];
180+
const track = orderedTracks[trackIndex];
175181

176182
if (targetElementTypes && targetElementTypes.length > 0) {
177183
const targetElement = findElementAtPosition({
178184
mouseX,
179-
tracks,
185+
tracks: orderedTracks,
180186
trackIndex,
181187
targetElementTypes,
182188
pixelsPerSecond,

apps/web/src/components/editor/panels/timeline/graph-editor/session.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
ScalarGraphKeyframeContext,
1414
SelectedKeyframeRef,
1515
} from "@/lib/animation/types";
16-
import type { TimelineElement, TimelineTrack } from "@/lib/timeline";
16+
import type { SceneTracks, TimelineElement } from "@/lib/timeline";
1717

1818
const GRAPH_LINEAR_CURVE: NormalizedCubicBezier = [0, 0, 1, 1];
1919
const FLAT_VALUE_EPSILON = 1e-6;
@@ -91,10 +91,10 @@ function findElementByKeyframe({
9191
tracks,
9292
keyframe,
9393
}: {
94-
tracks: TimelineTrack[];
94+
tracks: SceneTracks;
9595
keyframe: SelectedKeyframeRef;
9696
}): { element: TimelineElement; trackId: string; elementId: string } | null {
97-
for (const track of tracks) {
97+
for (const track of [...tracks.overlay, tracks.main, ...tracks.audio]) {
9898
if (track.id !== keyframe.trackId) {
9999
continue;
100100
}
@@ -180,7 +180,7 @@ export function resolveGraphEditorSelectionState({
180180
selectedKeyframes,
181181
preferredComponentKey,
182182
}: {
183-
tracks: TimelineTrack[];
183+
tracks: SceneTracks;
184184
selectedKeyframes: SelectedKeyframeRef[];
185185
preferredComponentKey?: string | null;
186186
}): GraphEditorSelectionState {

apps/web/src/components/editor/panels/timeline/graph-editor/use-controller.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
export function useGraphEditorController() {
1616
const editor = useEditor();
1717
const renderTracks = useEditor((currentEditor) =>
18-
currentEditor.timeline.getRenderTracks(),
18+
currentEditor.timeline.getPreviewTracks() ??
19+
currentEditor.scenes.getActiveScene().tracks,
1920
);
2021
const { selectedKeyframes } = useKeyframeSelection();
2122
const [open, setOpen] = useState(false);

apps/web/src/components/editor/panels/timeline/index.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ import {
5858
} from "./track-layout";
5959
import { SELECTED_TRACK_ROW_CLASS } from "./theme";
6060
import { TIMELINE_HORIZONTAL_WHEEL_STEP_PX } from "./interaction";
61-
import { isMainTrack } from "@/lib/timeline/placement";
6261
import { TimelineToolbar } from "./timeline-toolbar";
6362
import { useElementSelection } from "@/hooks/timeline/element/use-element-selection";
6463
import { useTimelineSeek } from "@/hooks/timeline/use-timeline-seek";
@@ -112,7 +111,15 @@ export function Timeline() {
112111
} = useElementSelection();
113112
const editor = useEditor();
114113
const timeline = editor.timeline;
115-
const tracks = useEditor((editor) => editor.timeline.getTracks());
114+
const scene = useEditor((currentEditor) => currentEditor.scenes.getActiveSceneOrNull());
115+
const tracks = useMemo<TimelineTrack[]>(
116+
() =>
117+
scene
118+
? [...scene.tracks.overlay, scene.tracks.main, ...scene.tracks.audio]
119+
: [],
120+
[scene],
121+
);
122+
const mainTrackId = scene?.tracks.main.id ?? null;
116123
const seek = (time: number) => editor.playback.seek({ time });
117124

118125
const timelineRef = useRef<HTMLDivElement>(null);
@@ -424,13 +431,13 @@ export function Timeline() {
424431
/>
425432
<DragLine
426433
dropTarget={dropTarget}
427-
tracks={timeline.getTracks()}
434+
tracks={tracks}
428435
isVisible={isDragOver && !dropTarget?.targetElement}
429436
headerHeight={timelineHeaderHeight}
430437
/>
431438
<DragLine
432439
dropTarget={dragDropTarget}
433-
tracks={timeline.getTracks()}
440+
tracks={tracks}
434441
isVisible={dragState.isDragging}
435442
headerHeight={timelineHeaderHeight}
436443
/>
@@ -508,6 +515,7 @@ export function Timeline() {
508515
{tracks.length > 0 && (
509516
<TimelineTrackRows
510517
dragElementId={dragState.elementId}
518+
mainTrackId={mainTrackId}
511519
zoomLevel={zoomLevel}
512520
dragState={dragState}
513521
tracksScrollRef={tracksScrollRef}
@@ -574,7 +582,14 @@ function TrackLabelsPanel({
574582
hasHorizontalScrollbar: boolean;
575583
}) {
576584
const editor = useEditor();
577-
const tracks = useEditor((e) => e.timeline.getTracks());
585+
const scene = useEditor((e) => e.scenes.getActiveSceneOrNull());
586+
const tracks = useMemo<TimelineTrack[]>(
587+
() =>
588+
scene
589+
? [...scene.tracks.overlay, scene.tracks.main, ...scene.tracks.audio]
590+
: [],
591+
[scene],
592+
);
578593
const { selectedElements } = useElementSelection();
579594
const tracksWithSelection = useMemo(
580595
() => new Set(selectedElements.map((el) => el.trackId)),
@@ -650,6 +665,7 @@ function TrackLabelsPanel({
650665

651666
function TimelineTrackRows({
652667
dragElementId,
668+
mainTrackId,
653669
zoomLevel,
654670
dragState,
655671
tracksScrollRef,
@@ -665,6 +681,7 @@ function TimelineTrackRows({
665681
dropTarget,
666682
}: {
667683
dragElementId: string | null;
684+
mainTrackId: string | null;
668685
zoomLevel: number;
669686
dragState: ElementDragState;
670687
tracksScrollRef: React.RefObject<HTMLDivElement | null>;
@@ -684,7 +701,14 @@ function TimelineTrackRows({
684701
dropTarget: DropTarget | null;
685702
}) {
686703
const timeline = useEditor((e) => e.timeline);
687-
const tracks = useEditor((e) => e.timeline.getTracks());
704+
const scene = useEditor((e) => e.scenes.getActiveSceneOrNull());
705+
const tracks = useMemo<TimelineTrack[]>(
706+
() =>
707+
scene
708+
? [...scene.tracks.overlay, scene.tracks.main, ...scene.tracks.audio]
709+
: [],
710+
[scene],
711+
);
688712
const { selectedElements } = useElementSelection();
689713
const tracksWithSelection = useMemo(
690714
() => new Set(selectedElements.map((el) => el.trackId)),
@@ -779,7 +803,7 @@ function TimelineTrackRows({
779803
? "Show track"
780804
: "Hide track"}
781805
</ContextMenuItem>
782-
{!isMainTrack(track) && (
806+
{track.id !== mainTrackId && (
783807
<ContextMenuItem
784808
icon={<HugeiconsIcon icon={Delete02Icon} />}
785809
onClick={(event: React.MouseEvent) => {

apps/web/src/core/index.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { AudioManager } from "./managers/audio-manager";
1010
import { SelectionManager } from "./managers/selection-manager";
1111
import { registerDefaultEffects } from "@/lib/effects";
1212
import { registerDefaultMasks } from "@/lib/masks";
13-
import { isMainTrack } from "@/lib/timeline/placement";
1413

1514
export class EditorCore {
1615
private static instance: EditorCore | null = null;
@@ -39,11 +38,21 @@ export class EditorCore {
3938
this.audio = new AudioManager(this);
4039
this.selection = new SelectionManager(this);
4140
this.command.registerReactor(() => {
42-
const tracks = this.timeline.getTracks();
43-
const prunedTracks = tracks.filter(
44-
(track) => track.elements.length > 0 || isMainTrack(track),
45-
);
46-
if (prunedTracks.length !== tracks.length) {
41+
const activeScene = this.scenes.getActiveSceneOrNull();
42+
if (!activeScene) {
43+
return;
44+
}
45+
46+
const tracks = activeScene.tracks;
47+
const prunedTracks = {
48+
...tracks,
49+
overlay: tracks.overlay.filter((track) => track.elements.length > 0),
50+
audio: tracks.audio.filter((track) => track.elements.length > 0),
51+
};
52+
if (
53+
prunedTracks.overlay.length !== tracks.overlay.length ||
54+
prunedTracks.audio.length !== tracks.audio.length
55+
) {
4756
this.timeline.updateTracks(prunedTracks);
4857
}
4958
});

apps/web/src/core/managers/audio-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export class AudioManager {
163163
this.playbackSessionId++;
164164
this.playbackLatencyCompensationSeconds = 0;
165165

166-
const tracks = this.editor.timeline.getTracks();
166+
const tracks = this.editor.scenes.getActiveScene().tracks;
167167
const mediaAssets = this.editor.media.getAssets();
168168
const duration = this.editor.timeline.getTotalDuration();
169169

0 commit comments

Comments
 (0)