summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/services/storage')
-rw-r--r--apps/web/src/services/storage/migrations/__tests__/v30-to-v31.test.ts57
-rw-r--r--apps/web/src/services/storage/migrations/index.ts4
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v26-to-v27.ts12
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v30-to-v31.ts99
-rw-r--r--apps/web/src/services/storage/migrations/v30-to-v31.ts14
5 files changed, 179 insertions, 7 deletions
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 });
+ }
+}