From cbd1b8212370c67b34a70dccd21bd82de72fead2 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 7 Apr 2026 04:41:27 +0200 Subject: refactor: structured track layout per scene --- .../src/lib/commands/timeline/track/add-track.ts | 86 +++++++++++++++++++--- .../lib/commands/timeline/track/remove-track.ts | 65 ++++++++-------- .../commands/timeline/track/toggle-track-mute.ts | 80 ++++++++++---------- .../timeline/track/toggle-track-visibility.ts | 85 +++++++++++---------- 4 files changed, 191 insertions(+), 125 deletions(-) (limited to 'apps/web/src/lib/commands/timeline/track') diff --git a/apps/web/src/lib/commands/timeline/track/add-track.ts b/apps/web/src/lib/commands/timeline/track/add-track.ts index 29bdbb0a..66b9c348 100644 --- a/apps/web/src/lib/commands/timeline/track/add-track.ts +++ b/apps/web/src/lib/commands/timeline/track/add-track.ts @@ -1,5 +1,5 @@ import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { TrackType, TimelineTrack } from "@/lib/timeline"; +import type { SceneTracks, TrackType } from "@/lib/timeline"; import { generateUUID } from "@/utils/id"; import { EditorCore } from "@/core"; import { @@ -9,7 +9,7 @@ import { export class AddTrackCommand extends Command { private trackId: string; - private savedState: TimelineTrack[] | null = null; + private savedState: SceneTracks | null = null; constructor( private type: TrackType, @@ -21,21 +21,28 @@ export class AddTrackCommand extends Command { execute(): CommandResult | undefined { const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); + this.savedState = editor.scenes.getActiveScene().tracks; - const newTrack: TimelineTrack = buildEmptyTrack({ - id: this.trackId, - type: this.type, - }); - - const updatedTracks = [...(this.savedState || [])]; const insertIndex = this.index ?? getDefaultInsertIndexForTrack({ - tracks: updatedTracks, + tracks: this.savedState, trackType: this.type, }); - updatedTracks.splice(insertIndex, 0, newTrack); + + const updatedTracks = + this.type === "audio" + ? buildAudioTrackState({ + tracks: this.savedState, + insertIndex, + trackId: this.trackId, + }) + : buildOverlayTrackState({ + tracks: this.savedState, + insertIndex, + trackId: this.trackId, + trackType: this.type, + }); editor.timeline.updateTracks(updatedTracks); return undefined; @@ -52,3 +59,60 @@ export class AddTrackCommand extends Command { return this.trackId; } } + +function buildAudioTrackState({ + tracks, + insertIndex, + trackId, +}: { + tracks: SceneTracks; + insertIndex: number; + trackId: string; +}): SceneTracks { + const audioInsertIndex = Math.max( + 0, + insertIndex - tracks.overlay.length - 1, + ); + const newTrack = buildEmptyTrack({ + id: trackId, + type: "audio", + }); + return { + ...tracks, + audio: [ + ...tracks.audio.slice(0, audioInsertIndex), + newTrack, + ...tracks.audio.slice(audioInsertIndex), + ], + }; +} + +function buildOverlayTrackState({ + tracks, + insertIndex, + trackId, + trackType, +}: { + tracks: SceneTracks; + insertIndex: number; + trackId: string; + trackType: Exclude; +}): SceneTracks { + const overlayInsertIndex = Math.min(insertIndex, tracks.overlay.length); + const newTrack = + trackType === "video" + ? buildEmptyTrack({ id: trackId, type: "video" }) + : trackType === "text" + ? buildEmptyTrack({ id: trackId, type: "text" }) + : trackType === "graphic" + ? buildEmptyTrack({ id: trackId, type: "graphic" }) + : buildEmptyTrack({ id: trackId, type: "effect" }); + return { + ...tracks, + overlay: [ + ...tracks.overlay.slice(0, overlayInsertIndex), + newTrack, + ...tracks.overlay.slice(overlayInsertIndex), + ], + }; +} diff --git a/apps/web/src/lib/commands/timeline/track/remove-track.ts b/apps/web/src/lib/commands/timeline/track/remove-track.ts index c2ea7ef0..5fcaa898 100644 --- a/apps/web/src/lib/commands/timeline/track/remove-track.ts +++ b/apps/web/src/lib/commands/timeline/track/remove-track.ts @@ -1,35 +1,30 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { EditorCore } from "@/core"; -import type { TimelineTrack } from "@/lib/timeline"; -import { getMainTrack } from "@/lib/timeline/placement"; - -export class RemoveTrackCommand extends Command { - private savedState: TimelineTrack[] | null = null; - - constructor(private trackId: string) { - super(); - } - - execute(): CommandResult | undefined { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - const targetTrack = this.savedState.find( - (track) => track.id === this.trackId, - ); - const mainTrack = getMainTrack({ tracks: this.savedState }); - if (mainTrack?.id === targetTrack?.id) { - return; - } - const updatedTracks = this.savedState.filter( - (track) => track.id !== this.trackId, - ); - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (this.savedState) { - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } - } -} +import { Command, type CommandResult } from "@/lib/commands/base-command"; +import { EditorCore } from "@/core"; +import type { SceneTracks } from "@/lib/timeline"; + +export class RemoveTrackCommand extends Command { + private savedState: SceneTracks | null = null; + + constructor(private trackId: string) { + super(); + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + const updatedTracks: SceneTracks = { + ...this.savedState, + overlay: this.savedState.overlay.filter((track) => track.id !== this.trackId), + audio: this.savedState.audio.filter((track) => track.id !== this.trackId), + }; + editor.timeline.updateTracks(updatedTracks); + return undefined; + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } +} diff --git a/apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts b/apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts index 513fffc7..7ffe2fd7 100644 --- a/apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts +++ b/apps/web/src/lib/commands/timeline/track/toggle-track-mute.ts @@ -1,39 +1,41 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { TimelineTrack } from "@/lib/timeline"; -import { EditorCore } from "@/core"; -import { canTrackHaveAudio } from "@/lib/timeline"; - -export class ToggleTrackMuteCommand extends Command { - private savedState: TimelineTrack[] | null = null; - - constructor(private trackId: string) { - super(); - } - - execute(): CommandResult | undefined { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const targetTrack = this.savedState.find( - (track) => track.id === this.trackId, - ); - if (!targetTrack) { - return; - } - - const updatedTracks = this.savedState.map((track) => - track.id === this.trackId && canTrackHaveAudio(track) - ? { ...track, muted: !track.muted } - : track, - ); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (this.savedState) { - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } - } -} +import { Command, type CommandResult } from "@/lib/commands/base-command"; +import type { SceneTracks } from "@/lib/timeline"; +import { EditorCore } from "@/core"; +import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/lib/timeline"; + +export class ToggleTrackMuteCommand extends Command { + private savedState: SceneTracks | null = null; + + constructor(private trackId: string) { + super(); + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + + const targetTrack = findTrackInSceneTracks({ + tracks: this.savedState, + trackId: this.trackId, + }); + if (!targetTrack) { + return; + } + + const updatedTracks = updateTrackInSceneTracks({ + tracks: this.savedState, + trackId: this.trackId, + update: (track) => + canTrackHaveAudio(track) ? { ...track, muted: !track.muted } : track, + }); + + editor.timeline.updateTracks(updatedTracks); + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } +} diff --git a/apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts b/apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts index 077c21db..2292c83a 100644 --- a/apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts +++ b/apps/web/src/lib/commands/timeline/track/toggle-track-visibility.ts @@ -1,40 +1,45 @@ -import { Command, type CommandResult } from "@/lib/commands/base-command"; -import type { TimelineTrack } from "@/lib/timeline"; -import { EditorCore } from "@/core"; -import { canTrackBeHidden } from "@/lib/timeline"; - -export class ToggleTrackVisibilityCommand extends Command { - private savedState: TimelineTrack[] | null = null; - - constructor(private trackId: string) { - super(); - } - - execute(): CommandResult | undefined { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const targetTrack = this.savedState.find( - (track) => track.id === this.trackId, - ); - if (!targetTrack) { - return; - } - - const updatedTracks = this.savedState.map((track) => { - if (track.id === this.trackId && canTrackBeHidden(track)) { - return { ...track, hidden: !track.hidden }; - } - return track; - }); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (this.savedState) { - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } - } -} +import { Command, type CommandResult } from "@/lib/commands/base-command"; +import type { SceneTracks } from "@/lib/timeline"; +import { EditorCore } from "@/core"; +import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/lib/timeline"; + +export class ToggleTrackVisibilityCommand extends Command { + private savedState: SceneTracks | null = null; + + constructor(private trackId: string) { + super(); + } + + execute(): CommandResult | undefined { + const editor = EditorCore.getInstance(); + this.savedState = editor.scenes.getActiveScene().tracks; + + const targetTrack = findTrackInSceneTracks({ + tracks: this.savedState, + trackId: this.trackId, + }); + if (!targetTrack) { + return; + } + + const updatedTracks = updateTrackInSceneTracks({ + tracks: this.savedState, + trackId: this.trackId, + update: (track) => { + if (canTrackBeHidden(track)) { + return { ...track, hidden: !track.hidden }; + } + return track; + }, + }); + + editor.timeline.updateTracks(updatedTracks); + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } +} -- cgit v1.3.1