From 8db3bead13441a5882d8689dfc6a9f09c82375d9 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 25 Mar 2026 16:47:22 +0100 Subject: feat: masks, properties refactor, shaders, storage migrations, and more --- .../lib/commands/timeline/element/retime/index.ts | 1 + .../element/retime/update-element-retime.ts | 114 +++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 apps/web/src/lib/commands/timeline/element/retime/index.ts create mode 100644 apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts (limited to 'apps/web/src/lib/commands/timeline/element/retime') diff --git a/apps/web/src/lib/commands/timeline/element/retime/index.ts b/apps/web/src/lib/commands/timeline/element/retime/index.ts new file mode 100644 index 00000000..5daa59bf --- /dev/null +++ b/apps/web/src/lib/commands/timeline/element/retime/index.ts @@ -0,0 +1 @@ +export { UpdateElementRetimeCommand } from "./update-element-retime"; diff --git a/apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts b/apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts new file mode 100644 index 00000000..d1f859e6 --- /dev/null +++ b/apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts @@ -0,0 +1,114 @@ +import { EditorCore } from "@/core"; +import { clampRetimeRate } from "@/constants/retime-constants"; +import { clampAnimationsToDuration } from "@/lib/animation"; +import { Command } from "@/lib/commands/base-command"; +import { getTimelineDurationForSourceSpan, getSourceSpanAtClipTime } from "@/lib/retime"; +import { isRetimableElement, updateElementInTracks } from "@/lib/timeline"; +import type { RetimeConfig, TimelineTrack } from "@/lib/timeline"; + +function getSourceDuration({ + trimStart, + trimEnd, + duration, + sourceDuration, + retime, +}: { + trimStart: number; + trimEnd: number; + duration: number; + sourceDuration?: number; + retime?: RetimeConfig; +}): number { + if (typeof sourceDuration === "number") { + return sourceDuration; + } + + return ( + trimStart + + getSourceSpanAtClipTime({ + clipTime: duration, + retime, + }) + + trimEnd + ); +} + +export class UpdateElementRetimeCommand extends Command { + private savedState: TimelineTrack[] | null = null; + private readonly trackId: string; + private readonly elementId: string; + private readonly retime: RetimeConfig | undefined; + + constructor({ + trackId, + elementId, + retime, + }: { + trackId: string; + elementId: string; + retime?: RetimeConfig; + }) { + super(); + this.trackId = trackId; + this.elementId = elementId; + this.retime = retime; + } + + execute(): void { + const editor = EditorCore.getInstance(); + this.savedState = editor.timeline.getTracks(); + + const updatedTracks = updateElementInTracks({ + tracks: this.savedState, + trackId: this.trackId, + elementId: this.elementId, + elementPredicate: isRetimableElement, + update: (element) => { + if (!isRetimableElement(element)) { + return element; + } + + const nextRetime = this.retime + ? { + ...this.retime, + rate: clampRetimeRate({ rate: this.retime.rate }), + } + : undefined; + const sourceDuration = getSourceDuration({ + trimStart: element.trimStart, + trimEnd: element.trimEnd, + duration: element.duration, + sourceDuration: element.sourceDuration, + retime: element.retime, + }); + const visibleSourceSpan = Math.max( + 0, + sourceDuration - element.trimStart - element.trimEnd, + ); + const nextDuration = getTimelineDurationForSourceSpan({ + sourceSpan: visibleSourceSpan, + retime: nextRetime, + }); + + return { + ...element, + retime: nextRetime, + duration: nextDuration, + animations: clampAnimationsToDuration({ + animations: element.animations, + duration: nextDuration, + }), + }; + }, + }); + + editor.timeline.updateTracks(updatedTracks); + } + + undo(): void { + if (this.savedState) { + const editor = EditorCore.getInstance(); + editor.timeline.updateTracks(this.savedState); + } + } +} -- cgit v1.3.1