summaryrefslogtreecommitdiff
path: root/apps/web/src/commands/timeline/clipboard
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-26 02:50:04 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-26 02:50:04 +0200
commiteea6d43c82e1fd09d94d73001090dad205b1f1d2 (patch)
tree384c92e467effb708bcc96e25117c064b18464b8 /apps/web/src/commands/timeline/clipboard
parentf1b45b4328cff6a016720ce7b38b87ac2992dc2d (diff)
downloadopencut-eea6d43c82e1fd09d94d73001090dad205b1f1d2.tar.gz
fix: model editor time as wasm MediaTime ticks end-to-end
Diffstat (limited to 'apps/web/src/commands/timeline/clipboard')
-rw-r--r--apps/web/src/commands/timeline/clipboard/paste-keyframes.ts24
-rw-r--r--apps/web/src/commands/timeline/clipboard/paste.ts45
2 files changed, 51 insertions, 18 deletions
diff --git a/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts b/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts
index e05b49fa..c1127921 100644
--- a/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts
+++ b/apps/web/src/commands/timeline/clipboard/paste-keyframes.ts
@@ -10,6 +10,13 @@ import type { KeyframeClipboardItem } from "@/clipboard";
import type { SceneTracks, TimelineElement } from "@/timeline";
import { updateElementInSceneTracks } from "@/timeline";
import { generateUUID } from "@/utils/id";
+import {
+ addMediaTime,
+ type MediaTime,
+ maxMediaTime,
+ minMediaTime,
+ ZERO_MEDIA_TIME,
+} from "@/wasm";
function pasteKeyframesIntoElement({
element,
@@ -17,7 +24,7 @@ function pasteKeyframesIntoElement({
clipboardItems,
}: {
element: TimelineElement;
- time: number;
+ time: MediaTime;
clipboardItems: KeyframeClipboardItem[];
}): TimelineElement {
let nextElement = element;
@@ -31,10 +38,13 @@ function pasteKeyframesIntoElement({
continue;
}
- const keyframeTime = Math.max(
- 0,
- Math.min(time + item.timeOffset, nextElement.duration),
- );
+ const keyframeTime = maxMediaTime({
+ a: ZERO_MEDIA_TIME,
+ b: minMediaTime({
+ a: addMediaTime({ a: time, b: item.timeOffset }),
+ b: nextElement.duration,
+ }),
+ });
const nextAnimations = upsertPathKeyframe({
animations: nextElement.animations,
propertyPath: item.propertyPath,
@@ -79,7 +89,7 @@ export class PasteKeyframesCommand extends Command {
private savedState: SceneTracks | null = null;
private readonly trackId: string;
private readonly elementId: string;
- private readonly time: number;
+ private readonly time: MediaTime;
private readonly clipboardItems: KeyframeClipboardItem[];
constructor({
@@ -90,7 +100,7 @@ export class PasteKeyframesCommand extends Command {
}: {
trackId: string;
elementId: string;
- time: number;
+ time: MediaTime;
clipboardItems: KeyframeClipboardItem[];
}) {
super();
diff --git a/apps/web/src/commands/timeline/clipboard/paste.ts b/apps/web/src/commands/timeline/clipboard/paste.ts
index 4523566e..be0c2b91 100644
--- a/apps/web/src/commands/timeline/clipboard/paste.ts
+++ b/apps/web/src/commands/timeline/clipboard/paste.ts
@@ -13,18 +13,25 @@ import {
enforceMainTrackStart,
} from "@/timeline/placement";
import { cloneAnimations } from "@/animation";
+import {
+ addMediaTime,
+ type MediaTime,
+ maxMediaTime,
+ subMediaTime,
+ ZERO_MEDIA_TIME,
+} from "@/wasm";
export class PasteCommand extends Command {
private savedState: SceneTracks | null = null;
private pastedElements: { trackId: string; elementId: string }[] = [];
- private readonly time: number;
+ private readonly time: MediaTime;
private readonly clipboardItems: ElementClipboardItem[];
constructor({
time,
clipboardItems,
}: {
- time: number;
+ time: MediaTime;
clipboardItems: ElementClipboardItem[];
}) {
super();
@@ -39,8 +46,12 @@ export class PasteCommand extends Command {
this.savedState = editor.scenes.getActiveScene().tracks;
this.pastedElements = [];
- const minStart = Math.min(
- ...this.clipboardItems.map((item) => item.element.startTime),
+ const minStart = this.clipboardItems.reduce(
+ (earliestStartTime, item) =>
+ item.element.startTime < earliestStartTime
+ ? item.element.startTime
+ : earliestStartTime,
+ this.clipboardItems[0].element.startTime,
);
let updatedTracks = this.savedState;
@@ -97,12 +108,18 @@ export class PasteCommand extends Command {
targetTrackId: targetTrack.id,
requestedStartTime: earliestElement.startTime,
});
- const delta = adjustedEarliestStartTime - earliestElement.startTime;
+ const delta = subMediaTime({
+ a: adjustedEarliestStartTime,
+ b: earliestElement.startTime,
+ });
- if (delta !== 0) {
+ if (delta !== ZERO_MEDIA_TIME) {
elementsForPlacement = elementsToAdd.map((element) => ({
...element,
- startTime: Math.max(0, element.startTime + delta),
+ startTime: maxMediaTime({
+ a: ZERO_MEDIA_TIME,
+ b: addMediaTime({ a: element.startTime, b: delta }),
+ }),
}));
}
}
@@ -168,14 +185,20 @@ function buildPastedElements({
time,
}: {
items: ElementClipboardItem[];
- minStart: number;
- time: number;
+ minStart: MediaTime;
+ time: MediaTime;
}): TimelineElement[] {
const elementsToAdd: TimelineElement[] = [];
for (const item of items) {
- const relativeOffset = item.element.startTime - minStart;
- const startTime = Math.max(0, time + relativeOffset);
+ const relativeOffset = subMediaTime({
+ a: item.element.startTime,
+ b: minStart,
+ });
+ const startTime = maxMediaTime({
+ a: ZERO_MEDIA_TIME,
+ b: addMediaTime({ a: time, b: relativeOffset }),
+ });
const newElementId = generateUUID();
elementsToAdd.push({