From b8d08df244b97b1d3131cb2d076166838189eeb0 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 29 Apr 2026 01:59:46 +0200 Subject: [PATCH] chore: cleanup --- apps/web/src/animation/property-registry.ts | 200 ------------------ .../migrations/__tests__/v28-to-v29.test.ts | 24 ++- .../migrations/transformers/v28-to-v29.ts | 17 -- .../subtitles/build-subtitle-text-element.ts | 3 +- apps/web/src/subtitles/types.ts | 2 +- apps/web/src/text/background.ts | 10 + apps/web/src/text/layout.ts | 2 +- apps/web/src/text/measure-element.ts | 3 +- apps/web/src/timeline/animation-targets.ts | 40 +--- apps/web/src/timeline/types.ts | 12 -- 10 files changed, 41 insertions(+), 272 deletions(-) delete mode 100644 apps/web/src/animation/property-registry.ts diff --git a/apps/web/src/animation/property-registry.ts b/apps/web/src/animation/property-registry.ts deleted file mode 100644 index 5ba45204..00000000 --- a/apps/web/src/animation/property-registry.ts +++ /dev/null @@ -1,200 +0,0 @@ -import type { - AnimationBindingKind, - AnimationInterpolation, - AnimationPropertyPath, - AnimationValue, - NumericSpec, -} from "@/animation/types"; -import { - coerceParamValue, - getParamDefaultInterpolation, - getParamNumericRange, - getParamValueKind, - type ParamDefinition, -} from "@/params"; -import { - getBuiltInElementParams, - getElementParam, - readElementParamValue, - writeElementParamValue, -} from "@/params/registry"; -import type { ElementType, TimelineElement } from "@/timeline"; - -export interface AnimationPropertyDefinition { - kind: AnimationBindingKind; - defaultInterpolation: AnimationInterpolation; - numericRanges?: Partial>; - supportsElement: ({ element }: { element: TimelineElement }) => boolean; - getValue: ({ element }: { element: TimelineElement }) => AnimationValue | null; - coerceValue: ({ value }: { value: AnimationValue }) => AnimationValue | null; - applyValue: ({ - element, - value, - }: { - element: TimelineElement; - value: AnimationValue; - }) => TimelineElement; -} - -function getFallbackParam({ - propertyPath, -}: { - propertyPath: AnimationPropertyPath; -}): ParamDefinition | null { - const elementTypes: ElementType[] = [ - "video", - "image", - "text", - "sticker", - "graphic", - "audio", - ]; - for (const type of elementTypes) { - const param = - getBuiltInElementParams({ type }).find( - (candidate) => candidate.key === propertyPath, - ) ?? null; - if (param) { - return param; - } - } - return null; -} - -function buildDefinition({ - propertyPath, - element, -}: { - propertyPath: AnimationPropertyPath; - element?: TimelineElement; -}): AnimationPropertyDefinition | null { - const param = element - ? getElementParam({ element, key: propertyPath }) - : getFallbackParam({ propertyPath }); - if (!param || param.keyframable === false) { - return null; - } - - const range = getParamNumericRange({ param }); - - return { - kind: getParamValueKind({ param }), - defaultInterpolation: getParamDefaultInterpolation({ param }), - numericRanges: range ? { value: range } : undefined, - supportsElement: ({ element: candidate }) => - getElementParam({ element: candidate, key: propertyPath }) !== null, - getValue: ({ element: candidate }) => - readElementParamValue({ element: candidate, param }), - coerceValue: ({ value }) => coerceParamValue({ param, value }), - applyValue: ({ element: candidate, value }) => { - const targetParam = getElementParam({ - element: candidate, - key: propertyPath, - }); - if (!targetParam) { - return candidate; - } - const coercedValue = coerceParamValue({ - param: targetParam, - value, - }); - if (coercedValue === null) { - return candidate; - } - return writeElementParamValue({ - element: candidate, - param: targetParam, - value: coercedValue, - }); - }, - }; -} - -export function isAnimationPropertyPath( - propertyPath: string, -): propertyPath is AnimationPropertyPath { - return !propertyPath.startsWith("params.") && !propertyPath.startsWith("effects."); -} - -export function getAnimationPropertyDefinition({ - propertyPath, - element, -}: { - propertyPath: AnimationPropertyPath; - element?: TimelineElement; -}): AnimationPropertyDefinition { - const definition = buildDefinition({ propertyPath, element }); - if (!definition) { - throw new Error(`Unknown animation property for element: ${propertyPath}`); - } - return definition; -} - -export function supportsAnimationProperty({ - element, - propertyPath, -}: { - element: TimelineElement; - propertyPath: AnimationPropertyPath; -}): boolean { - return getElementParam({ element, key: propertyPath }) !== null; -} - -export function getElementBaseValueForProperty({ - element, - propertyPath, -}: { - element: TimelineElement; - propertyPath: AnimationPropertyPath; -}): AnimationValue | null { - const param = getElementParam({ element, key: propertyPath }); - if (!param) { - return null; - } - return readElementParamValue({ element, param }); -} - -export function withElementBaseValueForProperty({ - element, - propertyPath, - value, -}: { - element: TimelineElement; - propertyPath: AnimationPropertyPath; - value: AnimationValue; -}): TimelineElement { - const param = getElementParam({ element, key: propertyPath }); - if (!param) { - return element; - } - const coercedValue = coerceParamValue({ param, value }); - if (coercedValue === null) { - return element; - } - return writeElementParamValue({ element, param, value: coercedValue }); -} - -export function getDefaultInterpolationForProperty({ - propertyPath, - element, -}: { - propertyPath: AnimationPropertyPath; - element?: TimelineElement; -}): AnimationInterpolation { - return getAnimationPropertyDefinition({ propertyPath, element }) - .defaultInterpolation; -} - -export function coerceAnimationValueForProperty({ - propertyPath, - value, - element, -}: { - propertyPath: AnimationPropertyPath; - value: AnimationValue; - element?: TimelineElement; -}): AnimationValue | null { - return getAnimationPropertyDefinition({ propertyPath, element }).coerceValue({ - value, - }); -} diff --git a/apps/web/src/services/storage/migrations/__tests__/v28-to-v29.test.ts b/apps/web/src/services/storage/migrations/__tests__/v28-to-v29.test.ts index dc76b6fd..38a806e1 100644 --- a/apps/web/src/services/storage/migrations/__tests__/v28-to-v29.test.ts +++ b/apps/web/src/services/storage/migrations/__tests__/v28-to-v29.test.ts @@ -3,7 +3,7 @@ import { transformProjectV28ToV29 } from "../transformers/v28-to-v29"; import { asRecord, asRecordArray } from "./helpers"; describe("V28 to V29 Migration", () => { - test("moves built-in element fields into params", () => { + test("copies built-in element fields into params without deleting source fields", () => { const result = transformProjectV28ToV29({ project: { id: "project-v28-builtins", @@ -83,10 +83,15 @@ describe("V28 to V29 Migration", () => { const tracks = asRecord(scene.tracks); const main = asRecord(tracks.main); const video = asRecordArray(main.elements)[0]; - expect(video.transform).toBeUndefined(); - expect(video.opacity).toBeUndefined(); - expect(video.volume).toBeUndefined(); - expect(video.muted).toBeUndefined(); + expect(video.transform).toEqual({ + position: { x: 12, y: -4 }, + scaleX: 1.25, + scaleY: 0.75, + rotate: 9, + }); + expect(video.opacity).toBe(0.5); + expect(video.volume).toBe(-6); + expect(video.muted).toBe(true); expect(video.params).toEqual({ "transform.positionX": 12, "transform.positionY": -4, @@ -101,8 +106,13 @@ describe("V28 to V29 Migration", () => { const overlay = asRecordArray(tracks.overlay)[0]; const text = asRecordArray(asRecord(overlay).elements)[0]; - expect(text.content).toBeUndefined(); - expect(text.background).toBeUndefined(); + expect(text.content).toBe("Hello"); + expect(text.background).toEqual({ + enabled: true, + color: "#111111", + paddingX: 10, + paddingY: 12, + }); expect(text.params).toMatchObject({ content: "Hello", fontSize: 20, diff --git a/apps/web/src/services/storage/migrations/transformers/v28-to-v29.ts b/apps/web/src/services/storage/migrations/transformers/v28-to-v29.ts index c7f68a34..4ce048f6 100644 --- a/apps/web/src/services/storage/migrations/transformers/v28-to-v29.ts +++ b/apps/web/src/services/storage/migrations/transformers/v28-to-v29.ts @@ -114,23 +114,6 @@ function migrateElement({ element }: { element: unknown }): unknown { } nextElement.params = params; - delete nextElement.transform; - delete nextElement.opacity; - delete nextElement.blendMode; - delete nextElement.volume; - delete nextElement.muted; - delete nextElement.content; - delete nextElement.fontSize; - delete nextElement.fontFamily; - delete nextElement.color; - delete nextElement.background; - delete nextElement.textAlign; - delete nextElement.fontWeight; - delete nextElement.fontStyle; - delete nextElement.textDecoration; - delete nextElement.letterSpacing; - delete nextElement.lineHeight; - return nextElement; } diff --git a/apps/web/src/subtitles/build-subtitle-text-element.ts b/apps/web/src/subtitles/build-subtitle-text-element.ts index c2d59f08..87cc6a03 100644 --- a/apps/web/src/subtitles/build-subtitle-text-element.ts +++ b/apps/web/src/subtitles/build-subtitle-text-element.ts @@ -6,7 +6,8 @@ import { } from "@/text/layout"; import { DEFAULTS } from "@/timeline/defaults"; import { mediaTimeFromSeconds } from "@/wasm"; -import type { CreateTextElement, TextBackground } from "@/timeline"; +import type { CreateTextElement } from "@/timeline"; +import type { TextBackground } from "@/text/background"; import type { TextAlign, TextDecoration, diff --git a/apps/web/src/subtitles/types.ts b/apps/web/src/subtitles/types.ts index a2de9ab7..ec485f22 100644 --- a/apps/web/src/subtitles/types.ts +++ b/apps/web/src/subtitles/types.ts @@ -1,4 +1,4 @@ -import type { TextBackground } from "@/timeline"; +import type { TextBackground } from "@/text/background"; import type { TextAlign, TextDecoration, diff --git a/apps/web/src/text/background.ts b/apps/web/src/text/background.ts index be5ab6f5..baa67a52 100644 --- a/apps/web/src/text/background.ts +++ b/apps/web/src/text/background.ts @@ -1,2 +1,12 @@ export const CORNER_RADIUS_MIN = 0; export const CORNER_RADIUS_MAX = 100; + +export interface TextBackground { + enabled: boolean; + color: string; + cornerRadius?: number; + paddingX?: number; + paddingY?: number; + offsetX?: number; + offsetY?: number; +} diff --git a/apps/web/src/text/layout.ts b/apps/web/src/text/layout.ts index 65e17291..c52abc30 100644 --- a/apps/web/src/text/layout.ts +++ b/apps/web/src/text/layout.ts @@ -1,4 +1,4 @@ -import type { TextBackground } from "@/timeline"; +import type { TextBackground } from "@/text/background"; import { DEFAULTS } from "@/timeline/defaults"; import type { TextAlign } from "@/text/primitives"; diff --git a/apps/web/src/text/measure-element.ts b/apps/web/src/text/measure-element.ts index d89cd2f8..28e97eac 100644 --- a/apps/web/src/text/measure-element.ts +++ b/apps/web/src/text/measure-element.ts @@ -1,6 +1,7 @@ import { CORNER_RADIUS_MIN } from "@/text/background"; import { DEFAULTS } from "@/timeline/defaults"; -import type { TextBackground, TextElement } from "@/timeline"; +import type { TextElement } from "@/timeline"; +import type { TextBackground } from "@/text/background"; import { resolveNumberAtTime } from "@/animation/values"; import { getTextVisualRect, diff --git a/apps/web/src/timeline/animation-targets.ts b/apps/web/src/timeline/animation-targets.ts index 8cccc78f..fcdb2915 100644 --- a/apps/web/src/timeline/animation-targets.ts +++ b/apps/web/src/timeline/animation-targets.ts @@ -23,9 +23,6 @@ import { } from "@/params"; import { getElementParam, - readElementParamValue, - writeElementParamValue, - type ElementParamDefinition, } from "@/params/registry"; import type { TimelineElement } from "@/timeline"; import { isVisualElement } from "@/timeline/element-utils"; @@ -96,35 +93,14 @@ function buildElementParamDescriptor({ return null; } - return buildTimelineElementParamDescriptor({ element, param }); -} - -function buildTimelineElementParamDescriptor({ - element, - param, -}: { - element: TimelineElement; - param: ElementParamDefinition; -}): AnimationPathDescriptor | null { - if (param.keyframable === false) { - return null; - } - - return { - kind: getParamValueKind({ param }), - defaultInterpolation: getParamDefaultInterpolation({ param }), - numericRanges: paramNumericRanges({ param }), - coerceValue: ({ value }) => coerceParamValue({ param, value }), - getBaseValue: () => readElementParamValue({ element, param }), - setBaseValue: ({ value }) => { - const coercedValue = coerceParamValue({ param, value }); - if (coercedValue === null) { - return element; - } - - return writeElementParamValue({ element, param, value: coercedValue }); - }, - }; + return buildParamDescriptor({ + param, + baseParams: element.params, + setParams: (params) => ({ + ...element, + params, + }), + }); } function buildGraphicParamDescriptor({ diff --git a/apps/web/src/timeline/types.ts b/apps/web/src/timeline/types.ts index c009bc16..5b3e4d29 100644 --- a/apps/web/src/timeline/types.ts +++ b/apps/web/src/timeline/types.ts @@ -117,8 +117,6 @@ interface BaseTimelineElement { export interface VideoElement extends BaseTimelineElement { type: "video"; mediaId: string; - volume?: number; - muted?: boolean; isSourceAudioEnabled?: boolean; hidden?: boolean; retime?: RetimeConfig; @@ -134,16 +132,6 @@ export interface ImageElement extends BaseTimelineElement { masks?: Mask[]; } -export interface TextBackground { - enabled: boolean; - color: string; - cornerRadius?: number; - paddingX?: number; - paddingY?: number; - offsetX?: number; - offsetY?: number; -} - export interface TextElement extends BaseTimelineElement { type: "text"; hidden?: boolean;