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.ts203
1 files changed, 114 insertions, 89 deletions
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
index 6f262e57..a6f2462a 100644
--- a/apps/web/src/core/managers/timeline-manager.ts
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -7,32 +7,31 @@ import type {
ClipboardItem,
RetimeConfig,
} from "@/lib/timeline";
+import { calculateTotalDuration } from "@/lib/timeline";
+import {
+ canElementBeHidden,
+ canElementHaveAudio,
+} from "@/lib/timeline/element-utils";
import type {
AnimationPath,
AnimationInterpolation,
AnimationValue,
} from "@/lib/animation/types";
-import { calculateTotalDuration } from "@/lib/timeline";
import { getLastFrameTime } from "opencut-wasm";
+import { BatchCommand } from "@/lib/commands";
import {
AddTrackCommand,
RemoveTrackCommand,
ToggleTrackMuteCommand,
ToggleTrackVisibilityCommand,
InsertElementCommand,
- UpdateElementTrimCommand,
- UpdateElementDurationCommand,
DeleteElementsCommand,
DuplicateElementsCommand,
- ToggleElementsVisibilityCommand,
- ToggleElementsMutedCommand,
- UpdateElementCommand,
+ UpdateElementsCommand,
SplitElementsCommand,
PasteCommand,
- UpdateElementStartTimeCommand,
MoveElementCommand,
TracksSnapshotCommand,
- UpdateElementRetimeCommand,
UpsertKeyframeCommand,
RemoveKeyframeCommand,
RetimeKeyframeCommand,
@@ -47,7 +46,6 @@ import {
RemoveEffectParamKeyframeCommand,
ToggleSourceAudioSeparationCommand,
} from "@/lib/commands/timeline";
-import { BatchCommand } from "@/lib/commands";
import type { InsertElementParams } from "@/lib/commands/timeline/element/insert-element";
export class TimelineManager {
@@ -80,7 +78,6 @@ export class TimelineManager {
startTime,
duration,
pushHistory = true,
- rippleEnabled = false,
}: {
elementId: string;
trimStart: number;
@@ -88,44 +85,33 @@ export class TimelineManager {
startTime?: number;
duration?: number;
pushHistory?: boolean;
- rippleEnabled?: boolean;
}): void {
- const command = new UpdateElementTrimCommand({
- elementId,
+ const trackId = this.findTrackIdForElement({ elementId });
+ if (!trackId) {
+ return;
+ }
+
+ const nextUpdates: Partial<TimelineElement> = {
trimStart,
trimEnd,
- startTime,
- duration,
- rippleEnabled,
- });
- if (pushHistory) {
- this.editor.command.execute({ command });
- } else {
- command.execute();
+ };
+ if (startTime !== undefined) {
+ nextUpdates.startTime = startTime;
+ }
+ if (duration !== undefined) {
+ nextUpdates.duration = duration;
}
- }
- updateElementDuration({
- trackId,
- elementId,
- duration,
- pushHistory = true,
- }: {
- trackId: string;
- elementId: string;
- duration: number;
- pushHistory?: boolean;
- }): void {
- const command = new UpdateElementDurationCommand({
- trackId,
- elementId,
- duration,
+ this.updateElements({
+ updates: [
+ {
+ trackId,
+ elementId,
+ patch: nextUpdates,
+ },
+ ],
+ pushHistory,
});
- if (pushHistory) {
- this.editor.command.execute({ command });
- } else {
- command.execute();
- }
}
updateElementRetime({
@@ -139,30 +125,18 @@ export class TimelineManager {
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,
- }: {
- elements: { trackId: string; elementId: string }[];
- startTime: number;
- }): void {
- const command = new UpdateElementStartTimeCommand({
- elements,
- startTime,
+ this.updateElements({
+ updates: [
+ {
+ trackId,
+ elementId,
+ patch: {
+ retime,
+ },
+ },
+ ],
+ pushHistory,
});
- this.editor.command.execute({ command });
}
moveElement({
@@ -171,14 +145,12 @@ export class TimelineManager {
elementId,
newStartTime,
createTrack,
- rippleEnabled = false,
}: {
sourceTrackId: string;
targetTrackId: string;
elementId: string;
newStartTime: number;
createTrack?: { type: TrackType; index: number };
- rippleEnabled?: boolean;
}): void {
const command = new MoveElementCommand({
sourceTrackId,
@@ -186,7 +158,6 @@ export class TimelineManager {
elementId,
newStartTime,
createTrack,
- rippleEnabled,
});
this.editor.command.execute({ command });
}
@@ -205,18 +176,15 @@ export class TimelineManager {
elements,
splitTime,
retainSide = "both",
- rippleEnabled = false,
}: {
elements: { trackId: string; elementId: string }[];
splitTime: number;
retainSide?: "both" | "left" | "right";
- rippleEnabled?: boolean;
}): { trackId: string; elementId: string }[] {
const command = new SplitElementsCommand({
elements,
splitTime,
retainSide,
- rippleEnabled,
});
this.editor.command.execute({ command });
return command.getRightSideElements();
@@ -273,12 +241,10 @@ export class TimelineManager {
deleteElements({
elements,
- rippleEnabled = false,
}: {
elements: { trackId: string; elementId: string }[];
- rippleEnabled?: boolean;
}): void {
- const command = new DeleteElementsCommand({ elements, rippleEnabled });
+ const command = new DeleteElementsCommand({ elements });
this.editor.command.execute({ command });
}
@@ -303,20 +269,17 @@ export class TimelineManager {
updates: Array<{
trackId: string;
elementId: string;
- updates: Partial<TimelineElement>;
+ patch: Partial<TimelineElement>;
}>;
pushHistory?: boolean;
}): void {
- const commands = updates.map(
- ({ trackId, elementId, updates: elementUpdates }) =>
- new UpdateElementCommand({
- trackId,
- elementId,
- updates: elementUpdates,
- }),
- );
- const command =
- commands.length === 1 ? commands[0] : new BatchCommand(commands);
+ if (updates.length === 0) {
+ return;
+ }
+
+ const command = new UpdateElementsCommand({
+ updates,
+ });
if (pushHistory) {
this.editor.command.execute({ command });
} else {
@@ -679,8 +642,27 @@ export class TimelineManager {
}: {
elements: { trackId: string; elementId: string }[];
}): void {
- const command = new ToggleElementsVisibilityCommand(elements);
- this.editor.command.execute({ command });
+ const shouldHide = elements.some(({ trackId, elementId }) => {
+ const element = this.getElementByRef({ trackId, elementId });
+ return element && canElementBeHidden(element) && !element.hidden;
+ });
+
+ const nextUpdates = elements.flatMap(({ trackId, elementId }) => {
+ const element = this.getElementByRef({ trackId, elementId });
+ if (!element || !canElementBeHidden(element)) {
+ return [];
+ }
+
+ return [
+ {
+ trackId,
+ elementId,
+ patch: { hidden: shouldHide },
+ },
+ ];
+ });
+
+ this.updateElements({ updates: nextUpdates });
}
toggleElementsMuted({
@@ -688,8 +670,27 @@ export class TimelineManager {
}: {
elements: { trackId: string; elementId: string }[];
}): void {
- const command = new ToggleElementsMutedCommand(elements);
- this.editor.command.execute({ command });
+ const shouldMute = elements.some(({ trackId, elementId }) => {
+ const element = this.getElementByRef({ trackId, elementId });
+ return element && canElementHaveAudio(element) && !element.muted;
+ });
+
+ const nextUpdates = elements.flatMap(({ trackId, elementId }) => {
+ const element = this.getElementByRef({ trackId, elementId });
+ if (!element || !canElementHaveAudio(element)) {
+ return [];
+ }
+
+ return [
+ {
+ trackId,
+ elementId,
+ patch: { muted: shouldMute },
+ },
+ ];
+ });
+
+ this.updateElements({ updates: nextUpdates });
}
getTracks(): TimelineTrack[] {
@@ -712,6 +713,30 @@ export class TimelineManager {
});
}
+ private getElementByRef({
+ trackId,
+ elementId,
+ }: {
+ trackId: string;
+ elementId: string;
+ }): TimelineElement | undefined {
+ return this.getTrackById({ trackId })?.elements.find(
+ (element) => element.id === elementId,
+ );
+ }
+
+ private findTrackIdForElement({
+ elementId,
+ }: {
+ elementId: string;
+ }): string | null {
+ return (
+ this.getTracks().find((track) =>
+ track.elements.some((element) => element.id === elementId),
+ )?.id ?? null
+ );
+ }
+
updateTracks(newTracks: TimelineTrack[]): void {
this.previewOverlay.clear();
this.previewTracks = null;