From 2ee0a44735d2b930348e9774909638b2c1909738 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 29 Apr 2026 01:28:50 +0200 Subject: refactor: unify element params --- .../properties/components/element-params-tab.tsx | 154 ++++++++++++++++++ .../properties/components/property-param-field.tsx | 38 ++++- .../hooks/use-keyframed-color-property.ts | 96 ----------- .../hooks/use-keyframed-number-property.ts | 175 --------------------- .../hooks/use-keyframed-param-property.ts | 32 ++-- .../editor/panels/properties/registry.tsx | 67 +++++++- 6 files changed, 263 insertions(+), 299 deletions(-) create mode 100644 apps/web/src/components/editor/panels/properties/components/element-params-tab.tsx delete mode 100644 apps/web/src/components/editor/panels/properties/hooks/use-keyframed-color-property.ts delete mode 100644 apps/web/src/components/editor/panels/properties/hooks/use-keyframed-number-property.ts (limited to 'apps/web/src/components') diff --git a/apps/web/src/components/editor/panels/properties/components/element-params-tab.tsx b/apps/web/src/components/editor/panels/properties/components/element-params-tab.tsx new file mode 100644 index 00000000..2befcef4 --- /dev/null +++ b/apps/web/src/components/editor/panels/properties/components/element-params-tab.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { resolveAnimationPathValueAtTime } from "@/animation"; +import { Section, SectionContent, SectionFields } from "@/components/section"; +import { useElementPlayhead } from "@/components/editor/panels/properties/hooks/use-element-playhead"; +import { useKeyframedParamProperty } from "@/components/editor/panels/properties/hooks/use-keyframed-param-property"; +import { PropertyParamField } from "@/components/editor/panels/properties/components/property-param-field"; +import type { ParamValue, ParamValues } from "@/params"; +import { + getElementParams, + readElementParamValue, + writeElementParamValue, + type ElementParamDefinition, +} from "@/params/registry"; +import type { TimelineElement } from "@/timeline"; +import type { MediaTime } from "@/wasm"; + +export function ElementParamsTab({ + element, + trackId, + paramKeys, + sectionKey, +}: { + element: TimelineElement; + trackId: string; + paramKeys?: readonly string[]; + sectionKey: string; +}) { + const { localTime, isPlayheadWithinElementRange } = useElementPlayhead({ + startTime: element.startTime, + duration: element.duration, + }); + const params = getElementParams({ element }).filter( + (param) => !paramKeys || paramKeys.includes(param.key), + ); + const baseValues = buildValues({ element, params }); + + return ( +
+ + + {params + .filter((param) => isVisible({ param, values: baseValues })) + .map((param) => ( + + ))} + + +
+ ); +} + +function ElementParamField({ + element, + trackId, + param, + baseValue, + localTime, + isPlayheadWithinElementRange, +}: { + element: TimelineElement; + trackId: string; + param: ElementParamDefinition; + baseValue: ParamValue; + localTime: MediaTime; + isPlayheadWithinElementRange: boolean; +}) { + const resolvedValue = resolveAnimationPathValueAtTime({ + animations: element.animations, + propertyPath: param.key, + localTime, + fallbackValue: baseValue, + }); + const animatedParam = useKeyframedParamProperty({ + param, + trackId, + elementId: element.id, + animations: element.animations, + propertyPath: param.key, + localTime, + isPlayheadWithinElementRange, + resolvedValue, + buildBaseUpdates: ({ value }) => + writeElementParamValue({ element, param, value }), + }); + + return ( + + ); +} + +function buildValues({ + element, + params, +}: { + element: TimelineElement; + params: readonly ElementParamDefinition[]; +}): ParamValues { + const values: ParamValues = {}; + for (const param of params) { + const value = readElementParamValue({ element, param }); + if (value !== null) { + values[param.key] = value; + } + } + return values; +} + +function isVisible({ + param, + values, +}: { + param: ElementParamDefinition; + values: ParamValues; +}): boolean { + return (param.dependencies ?? []).every((dependency) => + areParamValuesEqual({ + left: values[dependency.param], + right: dependency.equals, + }), + ); +} + +function areParamValuesEqual({ + left, + right, +}: { + left: ParamValue | undefined; + right: ParamValue; +}): boolean { + return left === right; +} diff --git a/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx b/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx index c9a9c22e..66225903 100644 --- a/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx +++ b/apps/web/src/components/editor/panels/properties/components/property-param-field.tsx @@ -1,6 +1,10 @@ "use client"; -import type { ParamDefinition, NumberParamDefinition } from "@/params"; +import type { + ParamDefinition, + NumberParamDefinition, + ParamValue, +} from "@/params"; import { formatNumberForDisplay, getFractionDigitsForStep, @@ -19,6 +23,7 @@ import { } from "@/components/ui/select"; import { usePropertyDraft } from "../hooks/use-property-draft"; import { KeyframeToggle } from "./keyframe-toggle"; +import { Textarea } from "@/components/ui/textarea"; export function PropertyParamField({ param, @@ -28,8 +33,8 @@ export function PropertyParamField({ keyframe, }: { param: ParamDefinition; - value: number | string | boolean; - onPreview: (value: number | string | boolean) => void; + value: ParamValue; + onPreview: (value: ParamValue) => void; onCommit: () => void; keyframe?: { isActive: boolean; @@ -41,7 +46,7 @@ export function PropertyParamField({ void; + value: ParamValue; + onPreview: (value: ParamValue) => void; onCommit: () => void; }) { if (param.type === "number") { @@ -131,6 +136,27 @@ function ParamInput({ ); } + if (param.type === "text") { + return ( +