summaryrefslogtreecommitdiff
path: root/apps/web/src/commands/timeline/track
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/commands/timeline/track')
-rw-r--r--apps/web/src/commands/timeline/track/add-track.ts115
-rw-r--r--apps/web/src/commands/timeline/track/index.ts4
-rw-r--r--apps/web/src/commands/timeline/track/remove-track.ts30
-rw-r--r--apps/web/src/commands/timeline/track/toggle-track-mute.ts41
-rw-r--r--apps/web/src/commands/timeline/track/toggle-track-visibility.ts45
5 files changed, 235 insertions, 0 deletions
diff --git a/apps/web/src/commands/timeline/track/add-track.ts b/apps/web/src/commands/timeline/track/add-track.ts
new file mode 100644
index 00000000..6b58b687
--- /dev/null
+++ b/apps/web/src/commands/timeline/track/add-track.ts
@@ -0,0 +1,115 @@
+import { Command, type CommandResult } from "@/commands/base-command";
+import type { SceneTracks, TrackType } from "@/timeline";
+import { generateUUID } from "@/utils/id";
+import { EditorCore } from "@/core";
+import {
+ buildEmptyTrack,
+ getDefaultInsertIndexForTrack,
+} from "@/timeline/placement";
+
+export class AddTrackCommand extends Command {
+ private trackId: string;
+ private savedState: SceneTracks | null = null;
+
+ constructor(
+ private type: TrackType,
+ private index?: number,
+ ) {
+ super();
+ this.trackId = generateUUID();
+ }
+
+ execute(): CommandResult | undefined {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.scenes.getActiveScene().tracks;
+
+ const insertIndex =
+ this.index ??
+ getDefaultInsertIndexForTrack({
+ tracks: this.savedState,
+ trackType: this.type,
+ });
+
+ 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;
+ }
+
+ undo(): void {
+ if (this.savedState) {
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+ }
+
+ getTrackId(): string {
+ 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<TrackType, "audio">;
+}): 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/commands/timeline/track/index.ts b/apps/web/src/commands/timeline/track/index.ts
new file mode 100644
index 00000000..9d4bd9c3
--- /dev/null
+++ b/apps/web/src/commands/timeline/track/index.ts
@@ -0,0 +1,4 @@
+export { AddTrackCommand } from "./add-track";
+export { RemoveTrackCommand } from "./remove-track";
+export { ToggleTrackMuteCommand } from "./toggle-track-mute";
+export { ToggleTrackVisibilityCommand } from "./toggle-track-visibility";
diff --git a/apps/web/src/commands/timeline/track/remove-track.ts b/apps/web/src/commands/timeline/track/remove-track.ts
new file mode 100644
index 00000000..13a9ff4e
--- /dev/null
+++ b/apps/web/src/commands/timeline/track/remove-track.ts
@@ -0,0 +1,30 @@
+import { Command, type CommandResult } from "@/commands/base-command";
+import { EditorCore } from "@/core";
+import type { SceneTracks } from "@/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/commands/timeline/track/toggle-track-mute.ts b/apps/web/src/commands/timeline/track/toggle-track-mute.ts
new file mode 100644
index 00000000..0d1f41cd
--- /dev/null
+++ b/apps/web/src/commands/timeline/track/toggle-track-mute.ts
@@ -0,0 +1,41 @@
+import { Command, type CommandResult } from "@/commands/base-command";
+import type { SceneTracks } from "@/timeline";
+import { EditorCore } from "@/core";
+import { canTrackHaveAudio, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/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/commands/timeline/track/toggle-track-visibility.ts b/apps/web/src/commands/timeline/track/toggle-track-visibility.ts
new file mode 100644
index 00000000..f4160e3f
--- /dev/null
+++ b/apps/web/src/commands/timeline/track/toggle-track-visibility.ts
@@ -0,0 +1,45 @@
+import { Command, type CommandResult } from "@/commands/base-command";
+import type { SceneTracks } from "@/timeline";
+import { EditorCore } from "@/core";
+import { canTrackBeHidden, findTrackInSceneTracks, updateTrackInSceneTracks } from "@/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);
+ }
+ }
+}