summaryrefslogtreecommitdiff
path: root/apps/web/src
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-06 12:59:07 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-06 12:59:07 +0200
commitc411edb7c160c55e94fddf62fd60fa1e166351c0 (patch)
tree39495bfc8120590c93943a396cce2b477d3faa6c /apps/web/src
parent2776adaae1512bf925c5bb4564b311b37f46202d (diff)
downloadopencut-c411edb7c160c55e94fddf62fd60fa1e166351c0.tar.gz
fix: clean up
Diffstat (limited to 'apps/web/src')
-rw-r--r--apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts2
-rw-r--r--apps/web/src/lib/animation/bezier.ts4
-rw-r--r--apps/web/src/lib/animation/binding-values.ts11
-rw-r--r--apps/web/src/lib/animation/interpolation.ts9
-rw-r--r--apps/web/src/lib/animation/keyframes.ts6
-rw-r--r--apps/web/src/lib/animation/target-resolver.ts18
-rw-r--r--apps/web/src/lib/commands/timeline/element/keyframes/remove-keyframe.ts2
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v21-to-v22.ts36
8 files changed, 60 insertions, 28 deletions
diff --git a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts
index c044ff5f..29b290d9 100644
--- a/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts
+++ b/apps/web/src/components/editor/panels/properties/hooks/use-keyframed-param-property.ts
@@ -83,7 +83,7 @@ export function useKeyframedParamProperty({
defaultInterpolation: getParamDefaultInterpolation({
param,
}),
- coerceValue: (nextValue) =>
+ coerceValue: ({ value: nextValue }) =>
coerceAnimationValueForParam({
param,
value: nextValue,
diff --git a/apps/web/src/lib/animation/bezier.ts b/apps/web/src/lib/animation/bezier.ts
index 4dce027e..5a9758ed 100644
--- a/apps/web/src/lib/animation/bezier.ts
+++ b/apps/web/src/lib/animation/bezier.ts
@@ -1,5 +1,7 @@
import type { ScalarAnimationKey } from "@/lib/animation/types";
+const BEZIER_SOLVE_ITERATIONS = 20;
+
export function getBezierPoint({
progress,
p0,
@@ -68,7 +70,7 @@ export function solveBezierProgressForTime({
const leftHandle =
rightKey.leftHandle ?? getDefaultLeftHandle({ leftKey, rightKey });
- for (let iteration = 0; iteration < 20; iteration++) {
+ for (let iteration = 0; iteration < BEZIER_SOLVE_ITERATIONS; iteration++) {
const mid = (lower + upper) / 2;
const estimate = getBezierPoint({
progress: mid,
diff --git a/apps/web/src/lib/animation/binding-values.ts b/apps/web/src/lib/animation/binding-values.ts
index 6f7ce995..344fd776 100644
--- a/apps/web/src/lib/animation/binding-values.ts
+++ b/apps/web/src/lib/animation/binding-values.ts
@@ -13,6 +13,7 @@ import type {
Vector2AnimationBinding,
VectorValue,
} from "@/lib/animation/types";
+import { clamp } from "@/utils/math";
interface LinearRgba {
r: number;
@@ -25,10 +26,6 @@ export type AnimationComponentValue = number | DiscreteValue;
const toRgb = converter("rgb");
-function clamp01({ value }: { value: number }): number {
- return Math.max(0, Math.min(1, value));
-}
-
function srgbToLinear({ value }: { value: number }): number {
return value <= 0.04045
? value / 12.92
@@ -36,7 +33,7 @@ function srgbToLinear({ value }: { value: number }): number {
}
function linearToSrgb({ value }: { value: number }): number {
- const clamped = clamp01({ value });
+ const clamped = clamp({ value, min: 0, max: 1 });
return clamped <= 0.0031308
? clamped * 12.92
: 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
@@ -234,7 +231,7 @@ export function parseColorToLinearRgba({
r: srgbToLinear({ value: rgb.r ?? 0 }),
g: srgbToLinear({ value: rgb.g ?? 0 }),
b: srgbToLinear({ value: rgb.b ?? 0 }),
- a: clamp01({ value: rgb.alpha ?? 1 }),
+ a: clamp({ value: rgb.alpha ?? 1, min: 0, max: 1 }),
};
}
@@ -248,7 +245,7 @@ export function formatLinearRgba({
r: linearToSrgb({ value: color.r }),
g: linearToSrgb({ value: color.g }),
b: linearToSrgb({ value: color.b }),
- alpha: clamp01({ value: color.a }),
+ alpha: clamp({ value: color.a, min: 0, max: 1 }),
} as const;
return rgb.alpha < 1 ? formatHex8(rgb) : formatHex(rgb);
}
diff --git a/apps/web/src/lib/animation/interpolation.ts b/apps/web/src/lib/animation/interpolation.ts
index 9609441c..67456aa0 100644
--- a/apps/web/src/lib/animation/interpolation.ts
+++ b/apps/web/src/lib/animation/interpolation.ts
@@ -9,6 +9,7 @@ import type {
ScalarSegmentType,
} from "@/lib/animation/types";
import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants";
+import { clamp } from "@/utils/math";
import {
getBezierPoint,
getDefaultLeftHandle,
@@ -41,10 +42,6 @@ function isWithinTimePair({
);
}
-function clamp01({ value }: { value: number }): number {
- return Math.max(0, Math.min(1, value));
-}
-
function lerpNumber({
leftValue,
rightValue,
@@ -287,8 +284,10 @@ export function getScalarChannelValueAtTime({
return rightKey.value;
}
- const progress = clamp01({
+ const progress = clamp({
value: (time - leftKey.time) / span,
+ min: 0,
+ max: 1,
});
if (leftKey.segmentToNext === "linear") {
return lerpNumber({
diff --git a/apps/web/src/lib/animation/keyframes.ts b/apps/web/src/lib/animation/keyframes.ts
index 7eaa5ed6..d72f9e16 100644
--- a/apps/web/src/lib/animation/keyframes.ts
+++ b/apps/web/src/lib/animation/keyframes.ts
@@ -440,9 +440,9 @@ export function upsertPathKeyframe({
keyframeId?: string;
kind: AnimationBindingKind;
defaultInterpolation: AnimationInterpolation;
- coerceValue: (value: AnimationValue) => AnimationValue | null;
+ coerceValue: ({ value }: { value: AnimationValue }) => AnimationValue | null;
}): ElementAnimations | undefined {
- const coercedValue = coerceValue(value);
+ const coercedValue = coerceValue({ value });
if (coercedValue === null) {
return animations;
}
@@ -547,7 +547,7 @@ export function upsertElementKeyframe({
keyframeId,
kind: propertyDefinition.kind,
defaultInterpolation: propertyDefinition.defaultInterpolation,
- coerceValue: (nextValue) =>
+ coerceValue: ({ value: nextValue }) =>
coerceAnimationValueForProperty({
propertyPath,
value: nextValue,
diff --git a/apps/web/src/lib/animation/target-resolver.ts b/apps/web/src/lib/animation/target-resolver.ts
index 06c9e140..64bb8709 100644
--- a/apps/web/src/lib/animation/target-resolver.ts
+++ b/apps/web/src/lib/animation/target-resolver.ts
@@ -32,9 +32,9 @@ export interface AnimationPathDescriptor {
kind: AnimationBindingKind;
defaultInterpolation: AnimationInterpolation;
numericRanges?: Partial<Record<string, NumericSpec>>;
- coerceValue(value: AnimationValue): AnimationValue | null;
- getBaseValue(): AnimationValue | null;
- setBaseValue(value: AnimationValue): TimelineElement;
+ coerceValue: ({ value }: { value: AnimationValue }) => AnimationValue | null;
+ getBaseValue: () => AnimationValue | null;
+ setBaseValue: ({ value }: { value: AnimationValue }) => TimelineElement;
}
export function getParamValueKind({
@@ -135,9 +135,9 @@ function buildGraphicParamDescriptor({
kind: getParamValueKind({ param }),
defaultInterpolation: getParamDefaultInterpolation({ param }),
numericRanges: getParamNumericRange({ param }),
- coerceValue: (value) => coerceAnimationValueForParam({ param, value }),
+ coerceValue: ({ value }) => coerceAnimationValueForParam({ param, value }),
getBaseValue: () => element.params[param.key] ?? param.default,
- setBaseValue: (value) => {
+ setBaseValue: ({ value }) => {
const coercedValue = coerceAnimationValueForParam({ param, value });
if (coercedValue === null) {
return element;
@@ -183,9 +183,9 @@ function buildEffectParamDescriptor({
kind: getParamValueKind({ param }),
defaultInterpolation: getParamDefaultInterpolation({ param }),
numericRanges: getParamNumericRange({ param }),
- coerceValue: (value) => coerceAnimationValueForParam({ param, value }),
+ coerceValue: ({ value }) => coerceAnimationValueForParam({ param, value }),
getBaseValue: () => effect.params[param.key] ?? param.default,
- setBaseValue: (value) => {
+ setBaseValue: ({ value }) => {
const coercedValue = coerceAnimationValueForParam({ param, value });
if (coercedValue === null) {
return element;
@@ -239,7 +239,7 @@ export function resolveAnimationTarget({
kind: propertyDefinition.kind,
defaultInterpolation: propertyDefinition.defaultInterpolation,
numericRanges: propertyDefinition.numericRanges,
- coerceValue: (value) =>
+ coerceValue: ({ value }) =>
coerceAnimationValueForProperty({
propertyPath: path,
value,
@@ -249,7 +249,7 @@ export function resolveAnimationTarget({
element,
propertyPath: path,
}),
- setBaseValue: (value) => {
+ setBaseValue: ({ value }) => {
const coercedValue = propertyDefinition.coerceValue({ value });
if (coercedValue === null) {
return element;
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 71b2ac21..35aa0860 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
@@ -79,7 +79,7 @@ function removeKeyframeAndPersist({
const shouldPersistToBase = isChannelNowEmpty && valueBefore !== null;
const baseElement = shouldPersistToBase
- ? target.setBaseValue(valueBefore)
+ ? target.setBaseValue({ value: valueBefore })
: element;
return { ...baseElement, animations: nextAnimations };
diff --git a/apps/web/src/services/storage/migrations/transformers/v21-to-v22.ts b/apps/web/src/services/storage/migrations/transformers/v21-to-v22.ts
index 15fd9edf..5212caf6 100644
--- a/apps/web/src/services/storage/migrations/transformers/v21-to-v22.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v21-to-v22.ts
@@ -1,10 +1,44 @@
-import { parseColorToLinearRgba } from "@/lib/animation/binding-values";
+import { converter, parse } from "culori";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
const COLOR_COMPONENT_KEYS = ["r", "g", "b", "a"] as const;
type LegacyInterpolation = "linear" | "hold";
+const toRgb = converter("rgb");
+
+interface LinearRgba {
+ r: number;
+ g: number;
+ b: number;
+ a: number;
+}
+
+function srgbToLinear({ value }: { value: number }): number {
+ return value <= 0.04045
+ ? value / 12.92
+ : Math.pow((value + 0.055) / 1.055, 2.4);
+}
+
+function parseColorToLinearRgba({
+ color,
+}: {
+ color: string;
+}): LinearRgba | null {
+ const parsed = parse(color);
+ const rgb = parsed ? toRgb(parsed) : null;
+ if (!rgb) {
+ return null;
+ }
+
+ return {
+ r: srgbToLinear({ value: rgb.r ?? 0 }),
+ g: srgbToLinear({ value: rgb.g ?? 0 }),
+ b: srgbToLinear({ value: rgb.b ?? 0 }),
+ a: Math.max(0, Math.min(1, rgb.alpha ?? 1)),
+ };
+}
+
interface LegacyScalarKeyframe {
id: string;
time: number;