summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/web/src/animation/property-registry.ts200
-rw-r--r--apps/web/src/services/storage/migrations/__tests__/v28-to-v29.test.ts24
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v28-to-v29.ts17
-rw-r--r--apps/web/src/subtitles/build-subtitle-text-element.ts3
-rw-r--r--apps/web/src/subtitles/types.ts2
-rw-r--r--apps/web/src/text/background.ts10
-rw-r--r--apps/web/src/text/layout.ts2
-rw-r--r--apps/web/src/text/measure-element.ts3
-rw-r--r--apps/web/src/timeline/animation-targets.ts40
-rw-r--r--apps/web/src/timeline/types.ts12
10 files changed, 41 insertions, 272 deletions
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<Record<string, NumericSpec>>;
- 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;