From 1bd9fac203f09736c1225bbbe19e5d7cc31c0b93 Mon Sep 17 00:00:00 2001 From: Maze Date: Sun, 5 Apr 2026 13:15:58 +0200 Subject: refactor: rewrite animation system to use bindings and scalar channels --- .../timeline/element/keyframes/remove-keyframe.ts | 38 ++++++++++++---------- .../keyframes/upsert-effect-param-keyframe.ts | 33 +++++++++++++++---- .../timeline/element/keyframes/upsert-keyframe.ts | 4 +-- .../element/toggle-source-audio-separation.ts | 4 +-- 4 files changed, 50 insertions(+), 29 deletions(-) (limited to 'apps/web/src/lib/commands/timeline') diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts b/apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts index b5c7550d..71b2ac21 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts +++ b/apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts @@ -1,8 +1,9 @@ import { EditorCore } from "@/core"; import { - getChannel, - getChannelValueAtTime, + hasKeyframesForPath, + getKeyframeById, removeElementKeyframe, + resolveAnimationPathValueAtTime, resolveAnimationTarget, } from "@/lib/animation"; import { Command, type CommandResult } from "@/lib/commands/base-command"; @@ -19,17 +20,6 @@ function sampleValueBeforeRemoval({ propertyPath: AnimationPath; keyframeId: string; }): AnimationValue | null { - const channel = getChannel({ - animations: element.animations, - propertyPath, - }); - const keyframe = channel?.keyframes.find( - (candidate) => candidate.id === keyframeId, - ); - if (!channel || !keyframe) { - return null; - } - const target = resolveAnimationTarget({ element, path: propertyPath }); if (!target) { return null; @@ -39,9 +29,19 @@ function sampleValueBeforeRemoval({ return null; } - return getChannelValueAtTime({ - channel, - time: keyframe.time, + const keyframe = getKeyframeById({ + animations: element.animations, + propertyPath, + keyframeId, + }); + if (!keyframe) { + return null; + } + + return resolveAnimationPathValueAtTime({ + animations: element.animations, + propertyPath, + localTime: keyframe.time, fallbackValue: baseValue, }); } @@ -72,8 +72,10 @@ function removeKeyframeAndPersist({ keyframeId, }); - const isChannelNowEmpty = - getChannel({ animations: nextAnimations, propertyPath }) === undefined; + const isChannelNowEmpty = !hasKeyframesForPath({ + animations: nextAnimations, + propertyPath, + }); const shouldPersistToBase = isChannelNowEmpty && valueBefore !== null; const baseElement = shouldPersistToBase diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts b/apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts index edbf5198..d0f01104 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts +++ b/apps/web/src/lib/commands/timeline/element/keyframes/upsert-effect-param-keyframe.ts @@ -1,8 +1,13 @@ import { EditorCore } from "@/core"; import { Command, type CommandResult } from "@/lib/commands/base-command"; -import { upsertEffectParamKeyframe } from "@/lib/animation/effect-param-channel"; +import { + buildEffectParamPath, + resolveAnimationTarget, + upsertPathKeyframe, +} from "@/lib/animation"; import { updateElementInTracks } from "@/lib/timeline"; import { isVisualElement } from "@/lib/timeline/element-utils"; +import type { AnimationInterpolation } from "@/lib/animation/types"; import type { TimelineTrack } from "@/lib/timeline"; export class UpsertEffectParamKeyframeCommand extends Command { @@ -12,8 +17,8 @@ export class UpsertEffectParamKeyframeCommand extends Command { private readonly effectId: string; private readonly paramKey: string; private readonly time: number; - private readonly value: number; - private readonly interpolation: "linear" | "hold" | undefined; + private readonly value: number | string | boolean; + private readonly interpolation: AnimationInterpolation | undefined; private readonly keyframeId: string | undefined; constructor({ @@ -31,8 +36,8 @@ export class UpsertEffectParamKeyframeCommand extends Command { effectId: string; paramKey: string; time: number; - value: number; - interpolation?: "linear" | "hold"; + value: number | string | boolean; + interpolation?: AnimationInterpolation; keyframeId?: string; }) { super(); @@ -57,14 +62,28 @@ export class UpsertEffectParamKeyframeCommand extends Command { elementPredicate: isVisualElement, update: (element) => { const boundedTime = Math.max(0, Math.min(this.time, element.duration)); - const animations = upsertEffectParamKeyframe({ - animations: element.animations, + const propertyPath = buildEffectParamPath({ effectId: this.effectId, paramKey: this.paramKey, + }); + const target = resolveAnimationTarget({ + element, + path: propertyPath, + }); + if (!target) { + return element; + } + + const animations = upsertPathKeyframe({ + animations: element.animations, + propertyPath, time: boundedTime, value: this.value, interpolation: this.interpolation, keyframeId: this.keyframeId, + kind: target.kind, + defaultInterpolation: target.defaultInterpolation, + coerceValue: target.coerceValue, }); return { ...element, animations }; }, diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts b/apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts index 98589987..bd56749a 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts +++ b/apps/web/src/lib/commands/timeline/element/keyframes/upsert-keyframe.ts @@ -73,9 +73,9 @@ export class UpsertKeyframeCommand extends Command { value: this.value, interpolation: this.interpolation, keyframeId: this.keyframeId, - valueKind: target.valueKind, + kind: target.kind, defaultInterpolation: target.defaultInterpolation, - numericRange: target.numericRange, + coerceValue: target.coerceValue, }), }; }, diff --git a/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts b/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts index c14a5a42..fc195cba 100644 --- a/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts +++ b/apps/web/src/lib/commands/timeline/element/toggle-source-audio-separation.ts @@ -47,7 +47,7 @@ export class ToggleSourceAudioSeparationCommand extends Command { return; } - if (canRecoverSourceAudio({ element: sourceElement })) { + if (canRecoverSourceAudio(sourceElement)) { editor.timeline.updateTracks( updateSourceAudioEnabled({ tracks: this.savedState, @@ -63,7 +63,7 @@ export class ToggleSourceAudioSeparationCommand extends Command { .media .getAssets() .find((asset) => asset.id === sourceElement.mediaId); - if (!canExtractSourceAudio({ element: sourceElement, mediaAsset })) { + if (!canExtractSourceAudio(sourceElement, mediaAsset)) { return; } if (sourceElement.duration <= 0) { -- cgit v1.3.1