summaryrefslogtreecommitdiff
path: root/apps/web/src/core/managers/timeline-manager.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-13 04:41:33 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-13 05:08:29 +0200
commit7d4e166678c35d48b22b8d1d05639f981a956a8b (patch)
treeeb596d4f4609053524224169c247db24052db353 /apps/web/src/core/managers/timeline-manager.ts
parentcad88ee4d1c9e06e7529b88ef5e5d3fdf3f89149 (diff)
downloadopencut-7d4e166678c35d48b22b8d1d05639f981a956a8b.tar.gz
fix: text measurement, rotation handle, video cache races, playback sync
Made-with: Cursor
Diffstat (limited to 'apps/web/src/core/managers/timeline-manager.ts')
-rw-r--r--apps/web/src/core/managers/timeline-manager.ts60
1 files changed, 46 insertions, 14 deletions
diff --git a/apps/web/src/core/managers/timeline-manager.ts b/apps/web/src/core/managers/timeline-manager.ts
index 74870a95..70075b98 100644
--- a/apps/web/src/core/managers/timeline-manager.ts
+++ b/apps/web/src/core/managers/timeline-manager.ts
@@ -5,7 +5,6 @@ import type {
TrackType,
TimelineTrack,
TimelineElement,
- ClipboardItem,
RetimeConfig,
} from "@/lib/timeline";
import { calculateTotalDuration } from "@/lib/timeline";
@@ -23,6 +22,11 @@ import type {
AnimationValue,
ScalarCurveKeyframePatch,
} from "@/lib/animation/types";
+import {
+ getElementLocalTime,
+ resolveAnimationTarget,
+ resolveAnimationPathValueAtTime,
+} from "@/lib/animation";
import { lastFrameTime } from "opencut-wasm";
import { BatchCommand } from "@/lib/commands";
import {
@@ -35,7 +39,6 @@ import {
DuplicateElementsCommand,
UpdateElementsCommand,
SplitElementsCommand,
- PasteCommand,
MoveElementCommand,
TracksSnapshotCommand,
UpsertKeyframeCommand,
@@ -244,18 +247,6 @@ export class TimelineManager {
return result;
}
- pasteAtTime({
- time,
- clipboardItems,
- }: {
- time: number;
- clipboardItems: ClipboardItem[];
- }): { trackId: string; elementId: string }[] {
- const command = new PasteCommand({ time, clipboardItems });
- this.editor.command.execute({ command });
- return command.getPastedElements();
- }
-
deleteElements({
elements,
}: {
@@ -492,6 +483,45 @@ export class TimelineManager {
return;
}
+ // Pre-sample values at playhead for each (element, property) pair.
+ // This preserves "what you see is what you get" when all keyframes are deleted.
+ const playheadTime = this.editor.playback.getCurrentTime();
+ const valueAtPlayheadMap = new Map<string, AnimationValue | null>();
+
+ for (const { trackId, elementId, propertyPath } of keyframes) {
+ const key = `${elementId}:${propertyPath}`;
+ if (valueAtPlayheadMap.has(key)) {
+ continue;
+ }
+
+ const element = this.getElementByRef({ trackId, elementId });
+ if (!element) {
+ valueAtPlayheadMap.set(key, null);
+ continue;
+ }
+
+ const localTime = getElementLocalTime({
+ timelineTime: playheadTime,
+ elementStartTime: element.startTime,
+ elementDuration: element.duration,
+ });
+
+ const target = resolveAnimationTarget({ element, path: propertyPath });
+ const baseValue = target?.getBaseValue() ?? null;
+ if (baseValue === null) {
+ valueAtPlayheadMap.set(key, null);
+ continue;
+ }
+
+ const value = resolveAnimationPathValueAtTime({
+ animations: element.animations,
+ propertyPath,
+ localTime,
+ fallbackValue: baseValue,
+ });
+ valueAtPlayheadMap.set(key, value);
+ }
+
const commands = keyframes.map(
({ trackId, elementId, propertyPath, keyframeId }) =>
new RemoveKeyframeCommand({
@@ -499,6 +529,8 @@ export class TimelineManager {
elementId,
propertyPath,
keyframeId,
+ valueAtPlayhead:
+ valueAtPlayheadMap.get(`${elementId}:${propertyPath}`) ?? null,
}),
);
const command =