diff options
Diffstat (limited to 'apps/web/src/services')
6 files changed, 181 insertions, 9 deletions
diff --git a/apps/web/src/services/renderer/compositor/frame-descriptor.ts b/apps/web/src/services/renderer/compositor/frame-descriptor.ts index eeb0633c..d01da1df 100644 --- a/apps/web/src/services/renderer/compositor/frame-descriptor.ts +++ b/apps/web/src/services/renderer/compositor/frame-descriptor.ts @@ -1,5 +1,5 @@ import { drawCssBackground } from "@/gradients"; -import { masksRegistry } from "@/masks"; +import { getMaskDefinition } from "@/masks"; import { incrementCounter } from "@/diagnostics/render-perf"; import type { AnyBaseNode } from "../nodes/base-node"; import type { CanvasRenderer } from "../canvas-renderer"; @@ -390,7 +390,7 @@ function buildMaskArtifacts({ return { mask: null, strokeLayer: null }; } - const definition = masksRegistry.get(mask.type); + const definition = getMaskDefinition(mask.type); if (definition.isActive?.(mask.params) === false) { return { mask: null, strokeLayer: null }; diff --git a/apps/web/src/services/storage/migrations/__tests__/v30-to-v31.test.ts b/apps/web/src/services/storage/migrations/__tests__/v30-to-v31.test.ts new file mode 100644 index 00000000..e5c82ae2 --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/v30-to-v31.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "bun:test"; +import { transformProjectV30ToV31 } from "../transformers/v30-to-v31"; +import { asRecord, asRecordArray } from "./helpers"; + +describe("V30 to V31 Migration", () => { + test("renames custom masks to freeform without changing params", () => { + const params = { + feather: 0, + inverted: false, + strokeColor: "#ffffff", + strokeWidth: 0, + strokeAlign: "center", + path: [{ id: "p1", x: 0, y: 0, inX: 0, inY: 0, outX: 0, outY: 0 }], + closed: true, + centerX: 0, + centerY: 0, + rotation: 0, + scale: 1, + }; + + const result = transformProjectV30ToV31({ + project: { + id: "project-v30-freeform", + version: 30, + scenes: [ + { + tracks: { + main: { + elements: [ + { + id: "image-1", + type: "image", + masks: [{ id: "mask-1", type: "custom", params }], + }, + ], + }, + }, + }, + ], + }, + }); + + expect(result.skipped).toBe(false); + expect(result.project.version).toBe(31); + const scene = asRecordArray(result.project.scenes)[0]; + const tracks = asRecord(scene.tracks); + const main = asRecord(tracks.main); + const element = asRecordArray(main.elements)[0]; + const mask = asRecordArray(asRecord(element).masks)[0]; + expect(mask).toMatchObject({ + id: "mask-1", + type: "freeform", + legacyType: "custom", + params, + }); + }); +}); diff --git a/apps/web/src/services/storage/migrations/index.ts b/apps/web/src/services/storage/migrations/index.ts index 03b5ed0c..cbc35bb3 100644 --- a/apps/web/src/services/storage/migrations/index.ts +++ b/apps/web/src/services/storage/migrations/index.ts @@ -29,10 +29,11 @@ import { V26toV27Migration } from "./v26-to-v27"; import { V27toV28Migration } from "./v27-to-v28"; import { V28toV29Migration } from "./v28-to-v29"; import { V29toV30Migration } from "./v29-to-v30"; +import { V30toV31Migration } from "./v30-to-v31"; export { runStorageMigrations } from "./runner"; export type { MigrationProgress } from "./runner"; -export const CURRENT_PROJECT_VERSION = 30; +export const CURRENT_PROJECT_VERSION = 31; export const migrations = [ new V0toV1Migration(), @@ -65,4 +66,5 @@ export const migrations = [ new V27toV28Migration(), new V28toV29Migration(), new V29toV30Migration(), + new V30toV31Migration(), ]; diff --git a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts index 4477d363..ccebb980 100644 --- a/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts +++ b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts @@ -1,7 +1,7 @@ import type { MigrationResult, ProjectRecord } from "./types"; import { getProjectId, isRecord } from "./utils"; -interface CustomMaskPathPoint { +interface FreeformPathPoint { id: string; x: number; y: number; @@ -11,7 +11,7 @@ interface CustomMaskPathPoint { outY: number; } -function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint { +function isFreeformPathPoint(value: unknown): value is FreeformPathPoint { if (!isRecord(value)) { return false; } @@ -27,18 +27,18 @@ function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint { ); } -function parseCustomMaskPath({ +function parseFreeformPath({ path, }: { path: string; -}): CustomMaskPathPoint[] { +}): FreeformPathPoint[] { if (!path) { return []; } try { const parsed = JSON.parse(path); - return Array.isArray(parsed) ? parsed.filter(isCustomMaskPathPoint) : []; + return Array.isArray(parsed) ? parsed.filter(isFreeformPathPoint) : []; } catch { return []; } @@ -150,7 +150,7 @@ function migrateMask({ mask }: { mask: unknown }): unknown { ...mask, params: { ...mask.params, - path: parseCustomMaskPath({ path }), + path: parseFreeformPath({ path }), }, }; } diff --git a/apps/web/src/services/storage/migrations/transformers/v30-to-v31.ts b/apps/web/src/services/storage/migrations/transformers/v30-to-v31.ts new file mode 100644 index 00000000..acdf4061 --- /dev/null +++ b/apps/web/src/services/storage/migrations/transformers/v30-to-v31.ts @@ -0,0 +1,99 @@ +import type { MigrationResult, ProjectRecord } from "./types"; +import { getProjectId, isRecord } from "./utils"; + +export function transformProjectV30ToV31({ + project, +}: { + project: ProjectRecord; +}): MigrationResult<ProjectRecord> { + if (!getProjectId({ project })) { + return { project, skipped: true, reason: "no project id" }; + } + + const version = project.version; + if (typeof version !== "number") { + return { project, skipped: true, reason: "invalid version" }; + } + if (version >= 31) { + return { project, skipped: true, reason: "already v31" }; + } + if (version !== 30) { + return { project, skipped: true, reason: "not v30" }; + } + + return { + project: { + ...migrateProject({ project }), + version: 31, + }, + skipped: false, + }; +} + +function migrateProject({ project }: { project: ProjectRecord }): ProjectRecord { + const nextProject = { ...project }; + if (Array.isArray(project.scenes)) { + nextProject.scenes = project.scenes.map((scene) => migrateScene({ scene })); + } + return nextProject; +} + +function migrateScene({ scene }: { scene: unknown }): unknown { + if (!isRecord(scene)) { + return scene; + } + + const nextScene = { ...scene }; + if (isRecord(scene.tracks)) { + nextScene.tracks = migrateTracks({ tracks: scene.tracks }); + } + return nextScene; +} + +function migrateTracks({ tracks }: { tracks: ProjectRecord }): ProjectRecord { + const nextTracks = { ...tracks }; + if (isRecord(tracks.main)) { + nextTracks.main = migrateTrack({ track: tracks.main }); + } + if (Array.isArray(tracks.overlay)) { + nextTracks.overlay = tracks.overlay.map((track) => migrateTrack({ track })); + } + if (Array.isArray(tracks.audio)) { + nextTracks.audio = tracks.audio.map((track) => migrateTrack({ track })); + } + return nextTracks; +} + +function migrateTrack({ track }: { track: unknown }): unknown { + if (!isRecord(track) || !Array.isArray(track.elements)) { + return track; + } + + return { + ...track, + elements: track.elements.map((element) => migrateElement({ element })), + }; +} + +function migrateElement({ element }: { element: unknown }): unknown { + if (!isRecord(element) || !Array.isArray(element.masks)) { + return element; + } + + return { + ...element, + masks: element.masks.map((mask) => migrateMask({ mask })), + }; +} + +function migrateMask({ mask }: { mask: unknown }): unknown { + if (!isRecord(mask) || mask.type !== "custom") { + return mask; + } + + return { + ...mask, + type: "freeform", + legacyType: "custom", + }; +} diff --git a/apps/web/src/services/storage/migrations/v30-to-v31.ts b/apps/web/src/services/storage/migrations/v30-to-v31.ts new file mode 100644 index 00000000..35bf0969 --- /dev/null +++ b/apps/web/src/services/storage/migrations/v30-to-v31.ts @@ -0,0 +1,14 @@ +import { StorageMigration, type StorageMigrationRunArgs } from "./base"; +import type { MigrationResult, ProjectRecord } from "./transformers/types"; +import { transformProjectV30ToV31 } from "./transformers/v30-to-v31"; + +export class V30toV31Migration extends StorageMigration { + from = 30; + to = 31; + + async run({ + project, + }: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> { + return transformProjectV30ToV31({ project }); + } +} |
