summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/animation/effect-param-channel.ts
blob: 07efe4d893465e4d8850816675f41f032fe474a3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import type { Effect, EffectParamValues } from "@/types/effects";
import type {
	ElementAnimations,
	NumberAnimationChannel,
} from "@/types/animation";
import {
	getChannel,
	removeKeyframe,
	setChannel,
	upsertKeyframe,
} from "./keyframes";
import { getChannelValueAtTime } from "./interpolation";

const EFFECT_PARAM_PATH_PREFIX = "effects.";
const EFFECT_PARAM_PATH_SUFFIX = ".params.";

function buildEffectParamPath({
	effectId,
	paramKey,
}: {
	effectId: string;
	paramKey: string;
}): string {
	return `${EFFECT_PARAM_PATH_PREFIX}${effectId}${EFFECT_PARAM_PATH_SUFFIX}${paramKey}`;
}

export function resolveEffectParamsAtTime({
	effect,
	animations,
	localTime,
}: {
	effect: Effect;
	animations: ElementAnimations | undefined;
	localTime: number;
}): EffectParamValues {
	const resolved: EffectParamValues = {};

	for (const [paramKey, staticValue] of Object.entries(effect.params)) {
		const path = buildEffectParamPath({ effectId: effect.id, paramKey });
		const channel = getChannel({ animations, propertyPath: path });
		if (channel && channel.keyframes.length > 0) {
			resolved[paramKey] = getChannelValueAtTime({
				channel,
				time: localTime,
				fallbackValue: staticValue,
			}) as number | string | boolean;
		} else {
			resolved[paramKey] = staticValue;
		}
	}

	return resolved;
}

const EMPTY_NUMBER_CHANNEL: NumberAnimationChannel = {
	valueKind: "number",
	keyframes: [],
};

export function upsertEffectParamKeyframe({
	animations,
	effectId,
	paramKey,
	time,
	value,
	interpolation,
	keyframeId,
}: {
	animations: ElementAnimations | undefined;
	effectId: string;
	paramKey: string;
	time: number;
	value: number;
	interpolation?: "linear" | "hold";
	keyframeId?: string;
}): ElementAnimations | undefined {
	const path = buildEffectParamPath({ effectId, paramKey });
	const channel = getChannel({ animations, propertyPath: path });
	const targetChannel =
		channel && channel.valueKind === "number" ? channel : EMPTY_NUMBER_CHANNEL;
	const updatedChannel = upsertKeyframe({
		channel: targetChannel,
		time,
		value,
		interpolation: interpolation ?? "linear",
		keyframeId,
	});

	return (
		setChannel({
			animations,
			propertyPath: path,
			channel: updatedChannel,
		}) ?? { channels: {} }
	);
}

export function removeEffectParamKeyframe({
	animations,
	effectId,
	paramKey,
	keyframeId,
}: {
	animations: ElementAnimations | undefined;
	effectId: string;
	paramKey: string;
	keyframeId: string;
}): ElementAnimations | undefined {
	const path = buildEffectParamPath({ effectId, paramKey });
	const channel = getChannel({ animations, propertyPath: path });
	const updatedChannel = removeKeyframe({ channel, keyframeId });
	return setChannel({
		animations,
		propertyPath: path,
		channel: updatedChannel,
	});
}