diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-15 15:45:50 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-15 15:45:50 +0200 |
| commit | c46d28891fee62ca2d3132da876af6411a41d9da (patch) | |
| tree | f856f71d04c69628f26b904088d675dd298a5080 /apps/web/src/services/storage/migrations | |
| parent | 66e4367bc4727f1d0bcf8f869529c7518812277e (diff) | |
| download | opencut-c46d28891fee62ca2d3132da876af6411a41d9da.tar.gz | |
feat: text / custom masks, fix preview pan, refactor selection
Diffstat (limited to 'apps/web/src/services/storage/migrations')
4 files changed, 216 insertions, 1 deletions
diff --git a/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts b/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts new file mode 100644 index 00000000..730d958d --- /dev/null +++ b/apps/web/src/services/storage/migrations/__tests__/v26-to-v27.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, test } from "bun:test"; +import { transformProjectV26ToV27 } from "../transformers/v26-to-v27"; + +describe("V26 to V27 Migration", () => { + test("converts custom mask paths from JSON strings to typed point arrays", () => { + const result = transformProjectV26ToV27({ + project: { + id: "project-v26-custom-mask", + version: 26, + scenes: [ + { + id: "scene-1", + tracks: { + main: { + id: "track-1", + type: "video", + elements: [ + { + id: "element-1", + type: "image", + masks: [ + { + id: "mask-custom", + type: "custom", + params: { + path: JSON.stringify([ + { + id: "point-1", + x: 0, + y: 0, + inX: 0, + inY: 0, + outX: 0.1, + outY: 0, + }, + ]), + closed: false, + }, + }, + { + id: "mask-rectangle", + type: "rectangle", + params: { + width: 0.5, + }, + }, + ], + }, + ], + }, + overlay: [], + audio: [], + }, + }, + ], + }, + }); + + expect(result.skipped).toBe(false); + expect(result.project.version).toBe(27); + + const scenes = result.project.scenes as Array<Record<string, unknown>>; + const tracks = scenes[0].tracks as Record<string, unknown>; + const mainTrack = tracks.main as Record<string, unknown>; + const elements = mainTrack.elements as Array<Record<string, unknown>>; + const masks = elements[0].masks as Array<Record<string, unknown>>; + const customParams = masks[0].params as Record<string, unknown>; + const rectangleParams = masks[1].params as Record<string, unknown>; + + expect(customParams.path).toEqual([ + { + id: "point-1", + x: 0, + y: 0, + inX: 0, + inY: 0, + outX: 0.1, + outY: 0, + }, + ]); + expect(rectangleParams.width).toBe(0.5); + }); +}); diff --git a/apps/web/src/services/storage/migrations/index.ts b/apps/web/src/services/storage/migrations/index.ts index c5a2e237..b290b713 100644 --- a/apps/web/src/services/storage/migrations/index.ts +++ b/apps/web/src/services/storage/migrations/index.ts @@ -25,10 +25,11 @@ import { V22toV23Migration } from "./v22-to-v23"; import { V23toV24Migration } from "./v23-to-v24"; import { V24toV25Migration } from "./v24-to-v25"; import { V25toV26Migration } from "./v25-to-v26"; +import { V26toV27Migration } from "./v26-to-v27"; export { runStorageMigrations } from "./runner"; export type { MigrationProgress } from "./runner"; -export const CURRENT_PROJECT_VERSION = 26; +export const CURRENT_PROJECT_VERSION = 27; export const migrations = [ new V0toV1Migration(), @@ -57,4 +58,5 @@ export const migrations = [ new V23toV24Migration(), new V24toV25Migration(), new V25toV26Migration(), + new V26toV27Migration(), ]; 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 new file mode 100644 index 00000000..186ae1e9 --- /dev/null +++ b/apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts @@ -0,0 +1,114 @@ +import { parseCustomMaskPath } from "@/lib/masks/custom-path"; +import type { MigrationResult, ProjectRecord } from "./types"; +import { getProjectId, isRecord } from "./utils"; + +export function transformProjectV26ToV27({ + 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 >= 27) { + return { project, skipped: true, reason: "already v27" }; + } + if (version !== 26) { + return { project, skipped: true, reason: "not v26" }; + } + + return { + project: { + ...migrateProject({ project }), + version: 27, + }, + skipped: false, + }; +} + +function migrateProject({ + project, +}: { + project: ProjectRecord; +}): ProjectRecord { + if (!Array.isArray(project.scenes)) { + return project; + } + + return { + ...project, + scenes: project.scenes.map((scene) => migrateScene({ scene })), + }; +} + +function migrateScene({ scene }: { scene: unknown }): unknown { + if (!isRecord(scene) || !isRecord(scene.tracks)) { + return scene; + } + + const tracks = scene.tracks; + const nextTracks: ProjectRecord = { ...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 { + ...scene, + tracks: 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" || !isRecord(mask.params)) { + return mask; + } + + const path = mask.params.path; + if (typeof path !== "string") { + return mask; + } + + return { + ...mask, + params: { + ...mask.params, + path: parseCustomMaskPath({ path }), + }, + }; +} diff --git a/apps/web/src/services/storage/migrations/v26-to-v27.ts b/apps/web/src/services/storage/migrations/v26-to-v27.ts new file mode 100644 index 00000000..2df5329d --- /dev/null +++ b/apps/web/src/services/storage/migrations/v26-to-v27.ts @@ -0,0 +1,16 @@ +import { StorageMigration } from "./base"; +import type { ProjectRecord } from "./transformers/types"; +import { transformProjectV26ToV27 } from "./transformers/v26-to-v27"; + +export class V26toV27Migration extends StorageMigration { + from = 26; + to = 27; + + async transform(project: ProjectRecord): Promise<{ + project: ProjectRecord; + skipped: boolean; + reason?: string; + }> { + return transformProjectV26ToV27({ project }); + } +} |
