summaryrefslogtreecommitdiff
path: root/apps/web/src/core/managers/timeline-manager.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/core/managers/timeline-manager.ts')
-rw-r--r--apps/web/src/core/managers/timeline-manager.ts275
1 files changed, 275 insertions, 0 deletions
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
new file mode 100644
index 00000000..867df3a8
--- /dev/null
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -0,0 +1,275 @@
+import type { EditorCore } from "@/core";
+import type {
+ TrackType,
+ TimelineTrack,
+ TextElement,
+ TimelineElement,
+ ClipboardItem,
+} from "@/types/timeline";
+import { calculateTotalDuration } from "@/lib/timeline";
+import {
+ AddTrackCommand,
+ RemoveTrackCommand,
+ ToggleTrackMuteCommand,
+ ToggleTrackVisibilityCommand,
+ InsertElementCommand,
+ UpdateElementTrimCommand,
+ UpdateElementDurationCommand,
+ DeleteElementsCommand,
+ DuplicateElementsCommand,
+ ToggleElementsVisibilityCommand,
+ ToggleElementsMutedCommand,
+ UpdateTextElementCommand,
+ SplitElementsCommand,
+ PasteCommand,
+ UpdateElementStartTimeCommand,
+ MoveElementCommand,
+} from "@/lib/commands/timeline";
+import type { InsertElementParams } from "@/lib/commands/timeline/element/insert-element";
+
+export class TimelineManager {
+ private listeners = new Set<() => void>();
+
+ constructor(private editor: EditorCore) {}
+
+ addTrack({ type, index }: { type: TrackType; index?: number }): string {
+ const command = new AddTrackCommand(type, index);
+ this.editor.command.execute({ command });
+ return command.getTrackId();
+ }
+
+ removeTrack({ trackId }: { trackId: string }): void {
+ const command = new RemoveTrackCommand(trackId);
+ this.editor.command.execute({ command });
+ }
+
+ insertElement({ element, placement }: InsertElementParams): void {
+ const command = new InsertElementCommand({ element, placement });
+ this.editor.command.execute({ command });
+ }
+
+ updateElementTrim({
+ elementId,
+ trimStart,
+ trimEnd,
+ pushHistory = true,
+ }: {
+ elementId: string;
+ trimStart: number;
+ trimEnd: number;
+ pushHistory?: boolean;
+ }): void {
+ const command = new UpdateElementTrimCommand(elementId, trimStart, trimEnd);
+ if (pushHistory) {
+ this.editor.command.execute({ command });
+ } else {
+ command.execute();
+ }
+ }
+
+ updateElementDuration({
+ trackId,
+ elementId,
+ duration,
+ pushHistory = true,
+ }: {
+ trackId: string;
+ elementId: string;
+ duration: number;
+ pushHistory?: boolean;
+ }): void {
+ const command = new UpdateElementDurationCommand(
+ trackId,
+ elementId,
+ duration,
+ );
+ if (pushHistory) {
+ this.editor.command.execute({ command });
+ } else {
+ command.execute();
+ }
+ }
+
+ updateElementStartTime({
+ elements,
+ startTime,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ startTime: number;
+ }): void {
+ const command = new UpdateElementStartTimeCommand(elements, startTime);
+ this.editor.command.execute({ command });
+ }
+
+ moveElement({
+ sourceTrackId,
+ targetTrackId,
+ elementId,
+ newStartTime,
+ createTrack,
+ }: {
+ sourceTrackId: string;
+ targetTrackId: string;
+ elementId: string;
+ newStartTime: number;
+ createTrack?: { type: TrackType; index: number };
+ }): void {
+ const command = new MoveElementCommand(
+ sourceTrackId,
+ targetTrackId,
+ elementId,
+ newStartTime,
+ createTrack,
+ );
+ this.editor.command.execute({ command });
+ }
+
+ toggleTrackMute({ trackId }: { trackId: string }): void {
+ const command = new ToggleTrackMuteCommand(trackId);
+ this.editor.command.execute({ command });
+ }
+
+ toggleTrackVisibility({ trackId }: { trackId: string }): void {
+ const command = new ToggleTrackVisibilityCommand(trackId);
+ this.editor.command.execute({ command });
+ }
+
+ splitElements({
+ elements,
+ splitTime,
+ retainSide = "both",
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ splitTime: number;
+ retainSide?: "both" | "left" | "right";
+ }): { trackId: string; elementId: string }[] {
+ const command = new SplitElementsCommand(elements, splitTime, retainSide);
+ this.editor.command.execute({ command });
+ return command.getRightSideElements();
+ }
+
+ getTotalDuration(): number {
+ return calculateTotalDuration({ tracks: this.getTracks() });
+ }
+
+ getTrackById({ trackId }: { trackId: string }): TimelineTrack | null {
+ return this.getTracks().find((track) => track.id === trackId) ?? null;
+ }
+
+ getElementsWithTracks({
+ elements,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ }): Array<{ track: TimelineTrack; element: TimelineElement }> {
+ const result: Array<{ track: TimelineTrack; element: TimelineElement }> =
+ [];
+
+ for (const { trackId, elementId } of elements) {
+ const track = this.getTrackById({ trackId });
+ const element = track?.elements.find(
+ (trackElement) => trackElement.id === elementId,
+ );
+
+ if (track && element) {
+ result.push({ track, element });
+ }
+ }
+
+ return result;
+ }
+
+ pasteAtTime({
+ time,
+ clipboardItems,
+ }: {
+ time: number;
+ clipboardItems: ClipboardItem[];
+ }): { trackId: string; elementId: string }[] {
+ const command = new PasteCommand(time, clipboardItems);
+ this.editor.command.execute({ command });
+ return command.getPastedElements();
+ }
+
+ deleteElements({
+ elements,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ }): void {
+ const command = new DeleteElementsCommand(elements);
+ this.editor.command.execute({ command });
+ }
+
+ updateTextElement({
+ trackId,
+ elementId,
+ updates,
+ }: {
+ trackId: string;
+ elementId: string;
+ updates: Partial<
+ Pick<
+ TextElement,
+ | "content"
+ | "fontSize"
+ | "fontFamily"
+ | "color"
+ | "backgroundColor"
+ | "textAlign"
+ | "fontWeight"
+ | "fontStyle"
+ | "textDecoration"
+ | "transform"
+ | "opacity"
+ >
+ >;
+ }): void {
+ const command = new UpdateTextElementCommand(trackId, elementId, updates);
+ this.editor.command.execute({ command });
+ }
+
+ duplicateElements({
+ elements,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ }): { trackId: string; elementId: string }[] {
+ const command = new DuplicateElementsCommand({ elements });
+ this.editor.command.execute({ command });
+ return command.getDuplicatedElements();
+ }
+
+ toggleElementsVisibility({
+ elements,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ }): void {
+ const command = new ToggleElementsVisibilityCommand(elements);
+ this.editor.command.execute({ command });
+ }
+
+ toggleElementsMuted({
+ elements,
+ }: {
+ elements: { trackId: string; elementId: string }[];
+ }): void {
+ const command = new ToggleElementsMutedCommand(elements);
+ this.editor.command.execute({ command });
+ }
+
+ getTracks(): TimelineTrack[] {
+ return this.editor.scenes.getActiveScene()?.tracks ?? [];
+ }
+
+ subscribe(listener: () => void): () => void {
+ this.listeners.add(listener);
+ return () => this.listeners.delete(listener);
+ }
+
+ private notify(): void {
+ this.listeners.forEach((fn) => fn());
+ }
+
+ updateTracks(newTracks: TimelineTrack[]): void {
+ this.editor.scenes.updateSceneTracks({ tracks: newTracks });
+ this.notify();
+ }
+}