fix(studio): harden keyframe editing semantics

This commit is contained in:
Miguel Angel Simon Sierra
2026-07-28 00:13:46 +02:00
parent 9fc0011703
commit d57039882f
41 changed files with 1294 additions and 435 deletions
@@ -11,6 +11,21 @@ import {
materializeIfDynamic,
} from "./gsapDragCommit";
export function buildTemporalArcKeyframes(
anim: GsapAnimation,
percentage: number,
properties: Record<string, number>,
) {
return [
...(anim.keyframes?.keyframes ?? []).map((keyframe) => ({
percentage: keyframe.percentage,
properties: { ...keyframe.properties },
...(keyframe.ease ? { ease: keyframe.ease } : {}),
})),
{ percentage, properties },
].sort((a, b) => a.percentage - b.percentage);
}
async function extendTweenAndAddKeyframe(
selection: DomEditSelection,
anim: GsapAnimation,
@@ -259,6 +274,47 @@ export async function commitGsapPositionFromDrag(
const backfillDefaults: Record<string, number> = { x: baseGsapX, y: baseGsapY };
const ct = usePlayerStore.getState().currentTime;
if (anim.arcPath?.enabled) {
const { activeKeyframePct, setActiveKeyframePct } = usePlayerStore.getState();
const pct = activeKeyframePct ?? computeCurrentPercentage(selection, anim);
const keyframes = anim.keyframes?.keyframes ?? [];
const pointIndex = keyframes.findIndex((kf) => Math.abs(kf.percentage - pct) < 0.05);
if (pointIndex >= 0) {
await callbacks.commitMutation(
selection,
{
type: "update-motion-path-point",
animationId: anim.id,
pointIndex,
x: newX,
y: newY,
},
{ label: "Move layer (waypoint)", softReload: true, beforeReload: restoreOffset },
);
setActiveKeyframePct(null);
parkPlayheadOnKeyframe(anim, pct);
return;
}
const tweenStart = resolveTweenStart(anim);
const tweenDuration = resolveTweenDuration(anim);
if (tweenStart === null || tweenDuration <= 0 || keyframes.length < 2) return;
const temporalKeyframes = buildTemporalArcKeyframes(anim, pct, { x: newX, y: newY });
await callbacks.commitMutation(
selection,
{
type: "replace-with-keyframes",
animationId: anim.id,
targetSelector: anim.targetSelector,
position: roundTo3(tweenStart),
duration: roundTo3(tweenDuration),
keyframes: temporalKeyframes,
ease: "none",
},
{ label: "Move layer (new keyframe)", softReload: true, beforeReload: restoreOffset },
);
return;
}
if (anim.keyframes) {
const newId = await materializeIfDynamic(anim, iframe, callbacks.commitMutation, selection);
const effectiveAnim = newId ? { ...anim, id: newId } : anim;