import type { AnimationPath, ElementAnimations, ChannelData, ScalarAnimationChannel, ScalarGraphChannel, ScalarGraphKeyframeContext, } from "@/animation/types"; import type { ChannelEasingMode } from "@/params"; import { isCompositeChannelData, isLeafChannelData } from "./channel-data"; import { isScalarChannel } from "./interpolation"; export interface EditableScalarChannels { easingMode: ChannelEasingMode; channels: ScalarGraphChannel[]; } function isScalarAnimationChannel( channel: ChannelData | undefined, ): channel is ScalarAnimationChannel { return isLeafChannelData(channel) && isScalarChannel(channel); } function getEasingModeForChannelData({ data, }: { data: ChannelData | undefined; }): ChannelEasingMode { return isCompositeChannelData(data) && ["r", "g", "b", "a"].every((componentKey) => componentKey in data) ? "shared" : "independent"; } export function getEditableScalarChannels({ animations, propertyPath, }: { animations: ElementAnimations | undefined; propertyPath: AnimationPath; }): EditableScalarChannels | null { const data = animations?.[propertyPath]; if (!data) { return null; } const channelEntries = isLeafChannelData(data) ? [["value", data] as const] : Object.entries(data); const channels = channelEntries.flatMap(([componentKey, channel]) => { if (!isScalarAnimationChannel(channel)) { return []; } return [ { propertyPath, componentKey, channel, } satisfies ScalarGraphChannel, ]; }); return { easingMode: getEasingModeForChannelData({ data }), channels }; } export function getEditableScalarChannel({ animations, propertyPath, componentKey, }: { animations: ElementAnimations | undefined; propertyPath: AnimationPath; componentKey: string; }): ScalarGraphChannel | null { const result = getEditableScalarChannels({ animations, propertyPath }); return result?.channels.find((channel) => channel.componentKey === componentKey) ?? null; } export function getScalarKeyframeContext({ animations, propertyPath, componentKey, keyframeId, }: { animations: ElementAnimations | undefined; propertyPath: AnimationPath; componentKey: string; keyframeId: string; }): ScalarGraphKeyframeContext | null { const scalarChannel = getEditableScalarChannel({ animations, propertyPath, componentKey, }); if (!scalarChannel) { return null; } const keyframeIndex = scalarChannel.channel.keys.findIndex( (keyframe) => keyframe.id === keyframeId, ); if (keyframeIndex < 0) { return null; } return { ...scalarChannel, keyframe: scalarChannel.channel.keys[keyframeIndex], keyframeIndex, previousKey: scalarChannel.channel.keys[keyframeIndex - 1] ?? null, nextKey: scalarChannel.channel.keys[keyframeIndex + 1] ?? null, }; }