Files
hyperframes/packages/studio/src/player/components/timelineKeyframeIdentity.ts
T

70 lines
2.1 KiB
TypeScript

import type { AnimationKeyframeTarget } from "../../hooks/gsapTweenSynth";
export interface TimelineKeyframeTarget {
percentage: number;
tweenPercentage?: number;
propertyGroup?: string;
animationId?: string;
collidingAnimationTargets?: AnimationKeyframeTarget[];
}
/**
* Note the asymmetry: `tweenPercentage` is optional here and defaults to
* `percentage`, but the reader treats both slots as authoritative. Callers must
* therefore keep the pair consistent — writing a new `tweenPercentage` without
* the matching `percentage` hashes two logically-equal selections to different
* keys.
*/
export function timelineKeyframeSelectionKey(
elementId: string,
target: TimelineKeyframeTarget,
): string {
if (!target.propertyGroup) return `${elementId}:${target.percentage}`;
return JSON.stringify([
elementId,
target.propertyGroup,
target.animationId ?? "",
target.percentage,
target.tweenPercentage ?? target.percentage,
]);
}
export function timelineKeyframeTargetFromSelectionKey(
elementId: string,
key: string,
): TimelineKeyframeTarget | null {
if (key.startsWith("[")) {
let decoded: unknown;
try {
decoded = JSON.parse(key);
} catch {
return null;
}
if (!Array.isArray(decoded) || decoded.length !== 5) return null;
const [selectedElementId, propertyGroup, animationId, percentage, tweenPercentage] = decoded;
if (
selectedElementId !== elementId ||
typeof propertyGroup !== "string" ||
propertyGroup.length === 0 ||
typeof animationId !== "string" ||
typeof percentage !== "number" ||
!Number.isFinite(percentage) ||
typeof tweenPercentage !== "number" ||
!Number.isFinite(tweenPercentage)
) {
return null;
}
return {
propertyGroup,
animationId: animationId || undefined,
percentage,
tweenPercentage,
};
}
const separator = key.lastIndexOf(":");
if (separator < 0 || key.slice(0, separator) !== elementId) return null;
const percentage = Number(key.slice(separator + 1));
return Number.isFinite(percentage) ? { percentage } : null;
}