diff options
Diffstat (limited to 'apps/web/src/lib/commands/timeline/element/keyframes')
6 files changed, 464 insertions, 464 deletions
diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/index.ts b/apps/web/src/lib/commands/timeline/element/keyframes/index.ts index c03b14e9..ea19286c 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/index.ts +++ b/apps/web/src/lib/commands/timeline/element/keyframes/index.ts @@ -1,5 +1,5 @@ -export * from "./remove-effect-param-keyframe"; -export * from "./remove-keyframe"; -export * from "./retime-keyframe"; -export * from "./upsert-effect-param-keyframe"; -export * from "./upsert-keyframe"; +export * from "./remove-effect-param-keyframe";
+export * from "./remove-keyframe";
+export * from "./retime-keyframe";
+export * from "./upsert-effect-param-keyframe";
+export * from "./upsert-keyframe";
diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts b/apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts index 984a5ed7..aafa3c22 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts +++ b/apps/web/src/lib/commands/timeline/element/keyframes/remove-effect-param-keyframe.ts @@ -1,68 +1,68 @@ -import { EditorCore } from "@/core"; -import { Command } from "@/lib/commands/base-command"; -import { removeEffectParamKeyframe } from "@/lib/animation/effect-param-channel"; -import { updateElementInTracks } from "@/lib/timeline"; -import { isVisualElement } from "@/lib/timeline/element-utils"; -import type { TimelineTrack } from "@/lib/timeline"; - -export class RemoveEffectParamKeyframeCommand extends Command { - private savedState: TimelineTrack[] | null = null; - private readonly trackId: string; - private readonly elementId: string; - private readonly effectId: string; - private readonly paramKey: string; - private readonly keyframeId: string; - - constructor({ - trackId, - elementId, - effectId, - paramKey, - keyframeId, - }: { - trackId: string; - elementId: string; - effectId: string; - paramKey: string; - keyframeId: string; - }) { - super(); - this.trackId = trackId; - this.elementId = elementId; - this.effectId = effectId; - this.paramKey = paramKey; - this.keyframeId = keyframeId; - } - - execute(): void { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const updatedTracks = updateElementInTracks({ - tracks: this.savedState, - trackId: this.trackId, - elementId: this.elementId, - elementPredicate: isVisualElement, - update: (element) => { - const animations = removeEffectParamKeyframe({ - animations: element.animations, - effectId: this.effectId, - paramKey: this.paramKey, - keyframeId: this.keyframeId, - }); - return { ...element, animations }; - }, - }); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (!this.savedState) { - return; - } - - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } -} +import { EditorCore } from "@/core";
+import { Command } from "@/lib/commands/base-command";
+import { removeEffectParamKeyframe } from "@/lib/animation/effect-param-channel";
+import { updateElementInTracks } from "@/lib/timeline";
+import { isVisualElement } from "@/lib/timeline/element-utils";
+import type { TimelineTrack } from "@/lib/timeline";
+
+export class RemoveEffectParamKeyframeCommand extends Command {
+ private savedState: TimelineTrack[] | null = null;
+ private readonly trackId: string;
+ private readonly elementId: string;
+ private readonly effectId: string;
+ private readonly paramKey: string;
+ private readonly keyframeId: string;
+
+ constructor({
+ trackId,
+ elementId,
+ effectId,
+ paramKey,
+ keyframeId,
+ }: {
+ trackId: string;
+ elementId: string;
+ effectId: string;
+ paramKey: string;
+ keyframeId: string;
+ }) {
+ super();
+ this.trackId = trackId;
+ this.elementId = elementId;
+ this.effectId = effectId;
+ this.paramKey = paramKey;
+ this.keyframeId = keyframeId;
+ }
+
+ execute(): void {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.timeline.getTracks();
+
+ const updatedTracks = updateElementInTracks({
+ tracks: this.savedState,
+ trackId: this.trackId,
+ elementId: this.elementId,
+ elementPredicate: isVisualElement,
+ update: (element) => {
+ const animations = removeEffectParamKeyframe({
+ animations: element.animations,
+ effectId: this.effectId,
+ paramKey: this.paramKey,
+ keyframeId: this.keyframeId,
+ });
+ return { ...element, animations };
+ },
+ });
+
+ editor.timeline.updateTracks(updatedTracks);
+ }
+
+ undo(): void {
+ if (!this.savedState) {
+ return;
+ }
+
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+}
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 eb5cf742..56676aa5 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,138 +1,138 @@ -import { EditorCore } from "@/core"; -import { - getChannel, - getChannelValueAtTime, - removeElementKeyframe, - resolveAnimationTarget, -} from "@/lib/animation"; -import { Command } from "@/lib/commands/base-command"; -import { updateElementInTracks } from "@/lib/timeline"; -import type { AnimationPath, AnimationValue } from "@/lib/animation/types"; -import type { TimelineElement, TimelineTrack } from "@/lib/timeline"; - -function sampleValueBeforeRemoval({ - element, - propertyPath, - keyframeId, -}: { - element: TimelineElement; - 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; - } - const baseValue = target.getBaseValue(); - if (baseValue === null) { - return null; - } - - return getChannelValueAtTime({ - channel, - time: keyframe.time, - fallbackValue: baseValue, - }); -} - -function removeKeyframeAndPersist({ - element, - propertyPath, - keyframeId, -}: { - element: TimelineElement; - propertyPath: AnimationPath; - keyframeId: string; -}): TimelineElement { - const target = resolveAnimationTarget({ element, path: propertyPath }); - if (!target) { - return element; - } - - const valueBefore = sampleValueBeforeRemoval({ - element, - propertyPath, - keyframeId, - }); - - const nextAnimations = removeElementKeyframe({ - animations: element.animations, - propertyPath, - keyframeId, - }); - - const isChannelNowEmpty = - getChannel({ animations: nextAnimations, propertyPath }) === undefined; - const shouldPersistToBase = isChannelNowEmpty && valueBefore !== null; - - const baseElement = shouldPersistToBase - ? target.setBaseValue(valueBefore) - : element; - - return { ...baseElement, animations: nextAnimations }; -} - -export class RemoveKeyframeCommand extends Command { - private savedState: TimelineTrack[] | null = null; - private readonly trackId: string; - private readonly elementId: string; - private readonly propertyPath: AnimationPath; - private readonly keyframeId: string; - - constructor({ - trackId, - elementId, - propertyPath, - keyframeId, - }: { - trackId: string; - elementId: string; - propertyPath: AnimationPath; - keyframeId: string; - }) { - super(); - this.trackId = trackId; - this.elementId = elementId; - this.propertyPath = propertyPath; - this.keyframeId = keyframeId; - } - - execute(): void { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const updatedTracks = updateElementInTracks({ - tracks: this.savedState, - trackId: this.trackId, - elementId: this.elementId, - update: (element) => - removeKeyframeAndPersist({ - element, - propertyPath: this.propertyPath, - keyframeId: this.keyframeId, - }), - }); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (!this.savedState) { - return; - } - - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } -} +import { EditorCore } from "@/core";
+import {
+ getChannel,
+ getChannelValueAtTime,
+ removeElementKeyframe,
+ resolveAnimationTarget,
+} from "@/lib/animation";
+import { Command } from "@/lib/commands/base-command";
+import { updateElementInTracks } from "@/lib/timeline";
+import type { AnimationPath, AnimationValue } from "@/lib/animation/types";
+import type { TimelineElement, TimelineTrack } from "@/lib/timeline";
+
+function sampleValueBeforeRemoval({
+ element,
+ propertyPath,
+ keyframeId,
+}: {
+ element: TimelineElement;
+ 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;
+ }
+ const baseValue = target.getBaseValue();
+ if (baseValue === null) {
+ return null;
+ }
+
+ return getChannelValueAtTime({
+ channel,
+ time: keyframe.time,
+ fallbackValue: baseValue,
+ });
+}
+
+function removeKeyframeAndPersist({
+ element,
+ propertyPath,
+ keyframeId,
+}: {
+ element: TimelineElement;
+ propertyPath: AnimationPath;
+ keyframeId: string;
+}): TimelineElement {
+ const target = resolveAnimationTarget({ element, path: propertyPath });
+ if (!target) {
+ return element;
+ }
+
+ const valueBefore = sampleValueBeforeRemoval({
+ element,
+ propertyPath,
+ keyframeId,
+ });
+
+ const nextAnimations = removeElementKeyframe({
+ animations: element.animations,
+ propertyPath,
+ keyframeId,
+ });
+
+ const isChannelNowEmpty =
+ getChannel({ animations: nextAnimations, propertyPath }) === undefined;
+ const shouldPersistToBase = isChannelNowEmpty && valueBefore !== null;
+
+ const baseElement = shouldPersistToBase
+ ? target.setBaseValue(valueBefore)
+ : element;
+
+ return { ...baseElement, animations: nextAnimations };
+}
+
+export class RemoveKeyframeCommand extends Command {
+ private savedState: TimelineTrack[] | null = null;
+ private readonly trackId: string;
+ private readonly elementId: string;
+ private readonly propertyPath: AnimationPath;
+ private readonly keyframeId: string;
+
+ constructor({
+ trackId,
+ elementId,
+ propertyPath,
+ keyframeId,
+ }: {
+ trackId: string;
+ elementId: string;
+ propertyPath: AnimationPath;
+ keyframeId: string;
+ }) {
+ super();
+ this.trackId = trackId;
+ this.elementId = elementId;
+ this.propertyPath = propertyPath;
+ this.keyframeId = keyframeId;
+ }
+
+ execute(): void {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.timeline.getTracks();
+
+ const updatedTracks = updateElementInTracks({
+ tracks: this.savedState,
+ trackId: this.trackId,
+ elementId: this.elementId,
+ update: (element) =>
+ removeKeyframeAndPersist({
+ element,
+ propertyPath: this.propertyPath,
+ keyframeId: this.keyframeId,
+ }),
+ });
+
+ editor.timeline.updateTracks(updatedTracks);
+ }
+
+ undo(): void {
+ if (!this.savedState) {
+ return;
+ }
+
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+}
diff --git a/apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts b/apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts index 9e912f27..7818ed0d 100644 --- a/apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts +++ b/apps/web/src/lib/commands/timeline/element/keyframes/retime-keyframe.ts @@ -1,74 +1,74 @@ -import { EditorCore } from "@/core"; -import { resolveAnimationTarget, retimeElementKeyframe } from "@/lib/animation"; -import { Command } from "@/lib/commands/base-command"; -import { updateElementInTracks } from "@/lib/timeline"; -import type { AnimationPath } from "@/lib/animation/types"; -import type { TimelineTrack } from "@/lib/timeline"; - -export class RetimeKeyframeCommand extends Command { - private savedState: TimelineTrack[] | null = null; - private readonly trackId: string; - private readonly elementId: string; - private readonly propertyPath: AnimationPath; - private readonly keyframeId: string; - private readonly nextTime: number; - - constructor({ - trackId, - elementId, - propertyPath, - keyframeId, - nextTime, - }: { - trackId: string; - elementId: string; - propertyPath: AnimationPath; - keyframeId: string; - nextTime: number; - }) { - super(); - this.trackId = trackId; - this.elementId = elementId; - this.propertyPath = propertyPath; - this.keyframeId = keyframeId; - this.nextTime = nextTime; - } - - execute(): void { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const updatedTracks = updateElementInTracks({ - tracks: this.savedState, - trackId: this.trackId, - elementId: this.elementId, - update: (element) => { - if (!resolveAnimationTarget({ element, path: this.propertyPath })) { - return element; - } - - const boundedTime = Math.max(0, Math.min(this.nextTime, element.duration)); - return { - ...element, - animations: retimeElementKeyframe({ - animations: element.animations, - propertyPath: this.propertyPath, - keyframeId: this.keyframeId, - time: boundedTime, - }), - }; - }, - }); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (!this.savedState) { - return; - } - - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } -} +import { EditorCore } from "@/core";
+import { resolveAnimationTarget, retimeElementKeyframe } from "@/lib/animation";
+import { Command } from "@/lib/commands/base-command";
+import { updateElementInTracks } from "@/lib/timeline";
+import type { AnimationPath } from "@/lib/animation/types";
+import type { TimelineTrack } from "@/lib/timeline";
+
+export class RetimeKeyframeCommand extends Command {
+ private savedState: TimelineTrack[] | null = null;
+ private readonly trackId: string;
+ private readonly elementId: string;
+ private readonly propertyPath: AnimationPath;
+ private readonly keyframeId: string;
+ private readonly nextTime: number;
+
+ constructor({
+ trackId,
+ elementId,
+ propertyPath,
+ keyframeId,
+ nextTime,
+ }: {
+ trackId: string;
+ elementId: string;
+ propertyPath: AnimationPath;
+ keyframeId: string;
+ nextTime: number;
+ }) {
+ super();
+ this.trackId = trackId;
+ this.elementId = elementId;
+ this.propertyPath = propertyPath;
+ this.keyframeId = keyframeId;
+ this.nextTime = nextTime;
+ }
+
+ execute(): void {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.timeline.getTracks();
+
+ const updatedTracks = updateElementInTracks({
+ tracks: this.savedState,
+ trackId: this.trackId,
+ elementId: this.elementId,
+ update: (element) => {
+ if (!resolveAnimationTarget({ element, path: this.propertyPath })) {
+ return element;
+ }
+
+ const boundedTime = Math.max(0, Math.min(this.nextTime, element.duration));
+ return {
+ ...element,
+ animations: retimeElementKeyframe({
+ animations: element.animations,
+ propertyPath: this.propertyPath,
+ keyframeId: this.keyframeId,
+ time: boundedTime,
+ }),
+ };
+ },
+ });
+
+ editor.timeline.updateTracks(updatedTracks);
+ }
+
+ undo(): void {
+ if (!this.savedState) {
+ return;
+ }
+
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+}
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 285bd80c..8e541b72 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,84 +1,84 @@ -import { EditorCore } from "@/core"; -import { Command } from "@/lib/commands/base-command"; -import { upsertEffectParamKeyframe } from "@/lib/animation/effect-param-channel"; -import { updateElementInTracks } from "@/lib/timeline"; -import { isVisualElement } from "@/lib/timeline/element-utils"; -import type { TimelineTrack } from "@/lib/timeline"; - -export class UpsertEffectParamKeyframeCommand extends Command { - private savedState: TimelineTrack[] | null = null; - private readonly trackId: string; - private readonly elementId: string; - private readonly effectId: string; - private readonly paramKey: string; - private readonly time: number; - private readonly value: number; - private readonly interpolation: "linear" | "hold" | undefined; - private readonly keyframeId: string | undefined; - - constructor({ - trackId, - elementId, - effectId, - paramKey, - time, - value, - interpolation, - keyframeId, - }: { - trackId: string; - elementId: string; - effectId: string; - paramKey: string; - time: number; - value: number; - interpolation?: "linear" | "hold"; - keyframeId?: string; - }) { - super(); - this.trackId = trackId; - this.elementId = elementId; - this.effectId = effectId; - this.paramKey = paramKey; - this.time = time; - this.value = value; - this.interpolation = interpolation; - this.keyframeId = keyframeId; - } - - execute(): void { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const updatedTracks = updateElementInTracks({ - tracks: this.savedState, - trackId: this.trackId, - elementId: this.elementId, - elementPredicate: isVisualElement, - update: (element) => { - const boundedTime = Math.max(0, Math.min(this.time, element.duration)); - const animations = upsertEffectParamKeyframe({ - animations: element.animations, - effectId: this.effectId, - paramKey: this.paramKey, - time: boundedTime, - value: this.value, - interpolation: this.interpolation, - keyframeId: this.keyframeId, - }); - return { ...element, animations }; - }, - }); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (!this.savedState) { - return; - } - - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } -} +import { EditorCore } from "@/core";
+import { Command } from "@/lib/commands/base-command";
+import { upsertEffectParamKeyframe } from "@/lib/animation/effect-param-channel";
+import { updateElementInTracks } from "@/lib/timeline";
+import { isVisualElement } from "@/lib/timeline/element-utils";
+import type { TimelineTrack } from "@/lib/timeline";
+
+export class UpsertEffectParamKeyframeCommand extends Command {
+ private savedState: TimelineTrack[] | null = null;
+ private readonly trackId: string;
+ private readonly elementId: string;
+ private readonly effectId: string;
+ private readonly paramKey: string;
+ private readonly time: number;
+ private readonly value: number;
+ private readonly interpolation: "linear" | "hold" | undefined;
+ private readonly keyframeId: string | undefined;
+
+ constructor({
+ trackId,
+ elementId,
+ effectId,
+ paramKey,
+ time,
+ value,
+ interpolation,
+ keyframeId,
+ }: {
+ trackId: string;
+ elementId: string;
+ effectId: string;
+ paramKey: string;
+ time: number;
+ value: number;
+ interpolation?: "linear" | "hold";
+ keyframeId?: string;
+ }) {
+ super();
+ this.trackId = trackId;
+ this.elementId = elementId;
+ this.effectId = effectId;
+ this.paramKey = paramKey;
+ this.time = time;
+ this.value = value;
+ this.interpolation = interpolation;
+ this.keyframeId = keyframeId;
+ }
+
+ execute(): void {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.timeline.getTracks();
+
+ const updatedTracks = updateElementInTracks({
+ tracks: this.savedState,
+ trackId: this.trackId,
+ elementId: this.elementId,
+ elementPredicate: isVisualElement,
+ update: (element) => {
+ const boundedTime = Math.max(0, Math.min(this.time, element.duration));
+ const animations = upsertEffectParamKeyframe({
+ animations: element.animations,
+ effectId: this.effectId,
+ paramKey: this.paramKey,
+ time: boundedTime,
+ value: this.value,
+ interpolation: this.interpolation,
+ keyframeId: this.keyframeId,
+ });
+ return { ...element, animations };
+ },
+ });
+
+ editor.timeline.updateTracks(updatedTracks);
+ }
+
+ undo(): void {
+ if (!this.savedState) {
+ return;
+ }
+
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+}
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 28e7feff..4b697705 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 @@ -1,95 +1,95 @@ -import { EditorCore } from "@/core"; -import { Command } from "@/lib/commands/base-command"; -import { resolveAnimationTarget, upsertPathKeyframe } from "@/lib/animation"; -import { updateElementInTracks } from "@/lib/timeline"; -import type { TimelineTrack } from "@/lib/timeline"; -import type { - AnimationPath, - AnimationInterpolation, - AnimationValue, -} from "@/lib/animation/types"; - -export class UpsertKeyframeCommand extends Command { - private savedState: TimelineTrack[] | null = null; - private readonly trackId: string; - private readonly elementId: string; - private readonly propertyPath: AnimationPath; - private readonly time: number; - private readonly value: AnimationValue; - private readonly interpolation: AnimationInterpolation | undefined; - private readonly keyframeId: string | undefined; - - constructor({ - trackId, - elementId, - propertyPath, - time, - value, - interpolation, - keyframeId, - }: { - trackId: string; - elementId: string; - propertyPath: AnimationPath; - time: number; - value: AnimationValue; - interpolation?: AnimationInterpolation; - keyframeId?: string; - }) { - super(); - this.trackId = trackId; - this.elementId = elementId; - this.propertyPath = propertyPath; - this.time = time; - this.value = value; - this.interpolation = interpolation; - this.keyframeId = keyframeId; - } - - execute(): void { - const editor = EditorCore.getInstance(); - this.savedState = editor.timeline.getTracks(); - - const updatedTracks = updateElementInTracks({ - tracks: this.savedState, - trackId: this.trackId, - elementId: this.elementId, - update: (element) => { - const target = resolveAnimationTarget({ - element, - path: this.propertyPath, - }); - if (!target) { - return element; - } - - const boundedTime = Math.max(0, Math.min(this.time, element.duration)); - return { - ...element, - animations: upsertPathKeyframe({ - animations: element.animations, - propertyPath: this.propertyPath, - time: boundedTime, - value: this.value, - interpolation: this.interpolation, - keyframeId: this.keyframeId, - valueKind: target.valueKind, - defaultInterpolation: target.defaultInterpolation, - numericRange: target.numericRange, - }), - }; - }, - }); - - editor.timeline.updateTracks(updatedTracks); - } - - undo(): void { - if (!this.savedState) { - return; - } - - const editor = EditorCore.getInstance(); - editor.timeline.updateTracks(this.savedState); - } -} +import { EditorCore } from "@/core";
+import { Command } from "@/lib/commands/base-command";
+import { resolveAnimationTarget, upsertPathKeyframe } from "@/lib/animation";
+import { updateElementInTracks } from "@/lib/timeline";
+import type { TimelineTrack } from "@/lib/timeline";
+import type {
+ AnimationPath,
+ AnimationInterpolation,
+ AnimationValue,
+} from "@/lib/animation/types";
+
+export class UpsertKeyframeCommand extends Command {
+ private savedState: TimelineTrack[] | null = null;
+ private readonly trackId: string;
+ private readonly elementId: string;
+ private readonly propertyPath: AnimationPath;
+ private readonly time: number;
+ private readonly value: AnimationValue;
+ private readonly interpolation: AnimationInterpolation | undefined;
+ private readonly keyframeId: string | undefined;
+
+ constructor({
+ trackId,
+ elementId,
+ propertyPath,
+ time,
+ value,
+ interpolation,
+ keyframeId,
+ }: {
+ trackId: string;
+ elementId: string;
+ propertyPath: AnimationPath;
+ time: number;
+ value: AnimationValue;
+ interpolation?: AnimationInterpolation;
+ keyframeId?: string;
+ }) {
+ super();
+ this.trackId = trackId;
+ this.elementId = elementId;
+ this.propertyPath = propertyPath;
+ this.time = time;
+ this.value = value;
+ this.interpolation = interpolation;
+ this.keyframeId = keyframeId;
+ }
+
+ execute(): void {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.timeline.getTracks();
+
+ const updatedTracks = updateElementInTracks({
+ tracks: this.savedState,
+ trackId: this.trackId,
+ elementId: this.elementId,
+ update: (element) => {
+ const target = resolveAnimationTarget({
+ element,
+ path: this.propertyPath,
+ });
+ if (!target) {
+ return element;
+ }
+
+ const boundedTime = Math.max(0, Math.min(this.time, element.duration));
+ return {
+ ...element,
+ animations: upsertPathKeyframe({
+ animations: element.animations,
+ propertyPath: this.propertyPath,
+ time: boundedTime,
+ value: this.value,
+ interpolation: this.interpolation,
+ keyframeId: this.keyframeId,
+ valueKind: target.valueKind,
+ defaultInterpolation: target.defaultInterpolation,
+ numericRange: target.numericRange,
+ }),
+ };
+ },
+ });
+
+ editor.timeline.updateTracks(updatedTracks);
+ }
+
+ undo(): void {
+ if (!this.savedState) {
+ return;
+ }
+
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+}
|
