diff options
Diffstat (limited to 'apps/web/src/core/managers/timeline-manager.ts')
| -rw-r--r-- | apps/web/src/core/managers/timeline-manager.ts | 155 |
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(); } |
