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.ts155
1 files changed, 120 insertions, 35 deletions
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
index 173f0744..455222d6 100644
--- a/apps/web/src/core/managers/timeline-manager.ts
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -1,16 +1,17 @@
import type { EditorCore } from "@/core";
-import type { EffectParamValues } from "@/types/effects";
+import type { ParamValues } from "@/lib/params";
import type {
TrackType,
TimelineTrack,
TimelineElement,
ClipboardItem,
-} from "@/types/timeline";
+ RetimeConfig,
+} from "@/lib/timeline";
import type {
+ AnimationPath,
AnimationInterpolation,
- AnimationPropertyPath,
AnimationValue,
-} from "@/types/animation";
+} from "@/lib/animation/types";
import { calculateTotalDuration } from "@/lib/timeline";
import {
AddTrackCommand,
@@ -30,6 +31,7 @@ import {
UpdateElementStartTimeCommand,
MoveElementCommand,
TracksSnapshotCommand,
+ UpdateElementRetimeCommand,
UpsertKeyframeCommand,
RemoveKeyframeCommand,
RetimeKeyframeCommand,
@@ -38,15 +40,18 @@ import {
UpdateClipEffectParamsCommand,
ToggleClipEffectCommand,
ReorderClipEffectsCommand,
+ RemoveMaskCommand,
+ ToggleMaskInvertedCommand,
UpsertEffectParamKeyframeCommand,
RemoveEffectParamKeyframeCommand,
} from "@/lib/commands/timeline";
-import { BatchCommand, PreviewTracker } from "@/lib/commands";
+import { BatchCommand } from "@/lib/commands";
import type { InsertElementParams } from "@/lib/commands/timeline/element/insert-element";
export class TimelineManager {
private listeners = new Set<() => void>();
- private previewTracker = new PreviewTracker<TimelineTrack[]>();
+ private previewOverlay = new Map<string, Partial<TimelineElement>>();
+ private previewTracks: TimelineTrack[] | null = null;
constructor(private editor: EditorCore) {}
@@ -121,6 +126,29 @@ export class TimelineManager {
}
}
+ updateElementRetime({
+ trackId,
+ elementId,
+ retime,
+ pushHistory = true,
+ }: {
+ trackId: string;
+ elementId: string;
+ retime?: RetimeConfig;
+ pushHistory?: boolean;
+ }): void {
+ const command = new UpdateElementRetimeCommand({
+ trackId,
+ elementId,
+ retime,
+ });
+ if (pushHistory) {
+ this.editor.command.execute({ command });
+ } else {
+ command.execute();
+ }
+ }
+
updateElementStartTime({
elements,
startTime,
@@ -308,6 +336,23 @@ export class TimelineManager {
this.editor.command.execute({ command });
}
+ removeMask({
+ trackId,
+ elementId,
+ maskId,
+ }: {
+ trackId: string;
+ elementId: string;
+ maskId: string;
+ }): void {
+ const command = new RemoveMaskCommand({
+ trackId,
+ elementId,
+ maskId,
+ });
+ this.editor.command.execute({ command });
+ }
+
updateClipEffectParams({
trackId,
elementId,
@@ -318,7 +363,7 @@ export class TimelineManager {
trackId: string;
elementId: string;
effectId: string;
- params: Partial<EffectParamValues>;
+ params: Partial<ParamValues>;
pushHistory?: boolean;
}): void {
const command = new UpdateClipEffectParamsCommand({
@@ -351,6 +396,23 @@ export class TimelineManager {
this.editor.command.execute({ command });
}
+ toggleMaskInverted({
+ trackId,
+ elementId,
+ maskId,
+ }: {
+ trackId: string;
+ elementId: string;
+ maskId: string;
+ }): void {
+ const command = new ToggleMaskInvertedCommand({
+ trackId,
+ elementId,
+ maskId,
+ });
+ this.editor.command.execute({ command });
+ }
+
reorderClipEffects({
trackId,
elementId,
@@ -377,7 +439,7 @@ export class TimelineManager {
keyframes: Array<{
trackId: string;
elementId: string;
- propertyPath: AnimationPropertyPath;
+ propertyPath: AnimationPath;
time: number;
value: AnimationValue;
interpolation?: AnimationInterpolation;
@@ -419,7 +481,7 @@ export class TimelineManager {
keyframes: Array<{
trackId: string;
elementId: string;
- propertyPath: AnimationPropertyPath;
+ propertyPath: AnimationPath;
keyframeId: string;
}>;
}): void {
@@ -450,7 +512,7 @@ export class TimelineManager {
}: {
trackId: string;
elementId: string;
- propertyPath: AnimationPropertyPath;
+ propertyPath: AnimationPath;
keyframeId: string;
time: number;
}): void {
@@ -520,7 +582,7 @@ export class TimelineManager {
}
isPreviewActive(): boolean {
- return this.previewTracker.isActive();
+ return this.previewOverlay.size > 0;
}
previewElements({
@@ -532,37 +594,51 @@ export class TimelineManager {
updates: Partial<TimelineElement>;
}>;
}): void {
- const tracks = this.getTracks();
- this.previewTracker.begin({ state: tracks });
-
- let updatedTracks = tracks;
- for (const { trackId, elementId, updates: elementUpdates } of updates) {
- updatedTracks = updatedTracks.map((track) => {
- if (track.id !== trackId) return track;
- const newElements = track.elements.map((element) =>
- element.id === elementId
- ? { ...element, ...elementUpdates }
- : element,
- );
- return { ...track, elements: newElements } as TimelineTrack;
- });
+ for (const { elementId, updates: elementUpdates } of updates) {
+ const existingOverlay = this.previewOverlay.get(elementId);
+ const mergedOverlay = {
+ ...existingOverlay,
+ ...elementUpdates,
+ } as Partial<TimelineElement>;
+ this.previewOverlay.set(elementId, mergedOverlay);
}
- this.updateTracks(updatedTracks);
+ const committedTracks = this.editor.scenes.getActiveScene()?.tracks ?? [];
+ this.previewTracks = this.applyPreviewOverlay(committedTracks);
+ this.notify();
}
commitPreview(): void {
- const snapshot = this.previewTracker.end();
- if (snapshot === null) return;
- const currentTracks = this.getTracks();
- const command = new TracksSnapshotCommand(snapshot, currentTracks);
+ if (this.previewOverlay.size === 0) return;
+ const committedTracks = this.editor.scenes.getActiveScene()?.tracks ?? [];
+ const afterTracks =
+ this.previewTracks ?? this.applyPreviewOverlay(committedTracks);
+ const command = new TracksSnapshotCommand(committedTracks, afterTracks);
this.editor.command.push({ command });
+ this.previewOverlay.clear();
+ this.previewTracks = null;
+ this.updateTracks(afterTracks);
}
discardPreview(): void {
- const snapshot = this.previewTracker.end();
- if (snapshot !== null) {
- this.updateTracks(snapshot);
- }
+ if (this.previewOverlay.size === 0) return;
+ this.previewOverlay.clear();
+ this.previewTracks = null;
+ this.notify();
+ }
+
+ private applyPreviewOverlay(tracks: TimelineTrack[]): TimelineTrack[] {
+ if (this.previewOverlay.size === 0) return tracks;
+ return tracks.map((track) => {
+ const hasOverlay = track.elements.some((el) =>
+ this.previewOverlay.has(el.id),
+ );
+ if (!hasOverlay) return track;
+ const newElements = track.elements.map((el) => {
+ const overlay = this.previewOverlay.get(el.id);
+ return overlay ? ({ ...el, ...overlay } as TimelineElement) : el;
+ });
+ return { ...track, elements: newElements } as TimelineTrack;
+ });
}
duplicateElements({
@@ -597,16 +673,25 @@ export class TimelineManager {
return this.editor.scenes.getActiveScene()?.tracks ?? [];
}
+ getRenderTracks(): TimelineTrack[] {
+ if (this.previewTracks !== null) return this.previewTracks;
+ return this.getTracks();
+ }
+
subscribe(listener: () => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notify(): void {
- this.listeners.forEach((fn) => fn());
+ this.listeners.forEach((fn) => {
+ fn();
+ });
}
updateTracks(newTracks: TimelineTrack[]): void {
+ this.previewOverlay.clear();
+ this.previewTracks = null;
this.editor.scenes.updateSceneTracks({ tracks: newTracks });
this.notify();
}