mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
refactor: migration run() and pure v1-to-v2 transform
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur";
|
import {
|
||||||
import { DEFAULT_BACKGROUND_COLOR } from "@/background/color";
|
getProjectId,
|
||||||
import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes";
|
transformProjectV1ToV2,
|
||||||
const DEFAULT_FPS = 30;
|
type V1ToV2Context,
|
||||||
import type { MediaAssetData } from "@/services/storage/types";
|
} from "../transformers/v1-to-v2";
|
||||||
import { getProjectId, transformProjectV1ToV2 } from "../transformers/v1-to-v2";
|
|
||||||
import {
|
import {
|
||||||
projectWithNoId,
|
projectWithNoId,
|
||||||
projectWithNullValues,
|
projectWithNullValues,
|
||||||
@@ -13,10 +12,15 @@ import {
|
|||||||
v2Project,
|
v2Project,
|
||||||
} from "./fixtures";
|
} from "./fixtures";
|
||||||
|
|
||||||
|
const DEFAULT_FPS = 30;
|
||||||
|
const DEFAULT_BACKGROUND_BLUR_INTENSITY = 10;
|
||||||
|
const DEFAULT_BACKGROUND_COLOR = "#000000";
|
||||||
|
const DEFAULT_CANVAS_SIZE = { width: 1920, height: 1080 };
|
||||||
|
|
||||||
describe("V1 to V2 Migration", () => {
|
describe("V1 to V2 Migration", () => {
|
||||||
describe("transformProjectV1ToV2", () => {
|
describe("transformProjectV1ToV2", () => {
|
||||||
test("creates metadata object from flat properties", async () => {
|
test("creates metadata object from flat properties", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: v1Project });
|
const result = transformProjectV1ToV2({ project: v1Project });
|
||||||
|
|
||||||
expect(result.skipped).toBe(false);
|
expect(result.skipped).toBe(false);
|
||||||
expect(result.project.version).toBe(2);
|
expect(result.project.version).toBe(2);
|
||||||
@@ -28,8 +32,8 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(typeof metadata.updatedAt).toBe("string");
|
expect(typeof metadata.updatedAt).toBe("string");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("creates settings object from flat properties", async () => {
|
test("creates settings object from flat properties", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: v1Project });
|
const result = transformProjectV1ToV2({ project: v1Project });
|
||||||
|
|
||||||
const settings = result.project.settings as Record<string, unknown>;
|
const settings = result.project.settings as Record<string, unknown>;
|
||||||
expect(settings.fps).toBe(v1Project.fps);
|
expect(settings.fps).toBe(v1Project.fps);
|
||||||
@@ -37,8 +41,8 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(settings.originalCanvasSize).toBe(null);
|
expect(settings.originalCanvasSize).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("converts color background correctly", async () => {
|
test("converts color background correctly", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: v1Project });
|
const result = transformProjectV1ToV2({ project: v1Project });
|
||||||
|
|
||||||
const settings = result.project.settings as Record<string, unknown>;
|
const settings = result.project.settings as Record<string, unknown>;
|
||||||
const background = settings.background as Record<string, unknown>;
|
const background = settings.background as Record<string, unknown>;
|
||||||
@@ -46,13 +50,13 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(background.color).toBe(v1Project.backgroundColor);
|
expect(background.color).toBe(v1Project.backgroundColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("converts blur background correctly", async () => {
|
test("converts blur background correctly", () => {
|
||||||
const projectWithBlur = {
|
const projectWithBlur = {
|
||||||
...v1Project,
|
...v1Project,
|
||||||
backgroundType: "blur",
|
backgroundType: "blur",
|
||||||
blurIntensity: 30,
|
blurIntensity: 30,
|
||||||
};
|
};
|
||||||
const result = await transformProjectV1ToV2({ project: projectWithBlur });
|
const result = transformProjectV1ToV2({ project: projectWithBlur });
|
||||||
|
|
||||||
const settings = result.project.settings as Record<string, unknown>;
|
const settings = result.project.settings as Record<string, unknown>;
|
||||||
const background = settings.background as Record<string, unknown>;
|
const background = settings.background as Record<string, unknown>;
|
||||||
@@ -60,16 +64,16 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(background.blurIntensity).toBe(30);
|
expect(background.blurIntensity).toBe(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("applies legacy bookmarks to main scene", async () => {
|
test("applies legacy bookmarks to main scene", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: v1Project });
|
const result = transformProjectV1ToV2({ project: v1Project });
|
||||||
|
|
||||||
const scenes = result.project.scenes as Array<Record<string, unknown>>;
|
const scenes = result.project.scenes as Array<Record<string, unknown>>;
|
||||||
const mainScene = scenes.find((s) => s.isMain === true);
|
const mainScene = scenes.find((s) => s.isMain === true);
|
||||||
expect(mainScene?.bookmarks).toEqual(v1Project.bookmarks);
|
expect(mainScene?.bookmarks).toEqual(v1Project.bookmarks);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("preserves existing scene bookmarks", async () => {
|
test("preserves existing scene bookmarks", () => {
|
||||||
const result = await transformProjectV1ToV2({
|
const result = transformProjectV1ToV2({
|
||||||
project: v1ProjectWithMultipleScenes,
|
project: v1ProjectWithMultipleScenes,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,22 +82,22 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(introScene?.bookmarks).toEqual([1.0]);
|
expect(introScene?.bookmarks).toEqual([1.0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("skips project that already has v2 structure", async () => {
|
test("skips project that already has v2 structure", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: v2Project });
|
const result = transformProjectV1ToV2({ project: v2Project });
|
||||||
|
|
||||||
expect(result.skipped).toBe(true);
|
expect(result.skipped).toBe(true);
|
||||||
expect(result.reason).toBe("already v2");
|
expect(result.reason).toBe("already v2");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("skips project with no id", async () => {
|
test("skips project with no id", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: projectWithNoId });
|
const result = transformProjectV1ToV2({ project: projectWithNoId });
|
||||||
|
|
||||||
expect(result.skipped).toBe(true);
|
expect(result.skipped).toBe(true);
|
||||||
expect(result.reason).toBe("no project id");
|
expect(result.reason).toBe("no project id");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("handles null values gracefully", async () => {
|
test("handles null values gracefully", () => {
|
||||||
const result = await transformProjectV1ToV2({
|
const result = transformProjectV1ToV2({
|
||||||
project: projectWithNullValues,
|
project: projectWithNullValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,13 +107,13 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE);
|
expect(settings.canvasSize).toEqual(DEFAULT_CANVAS_SIZE);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses default values for missing properties", async () => {
|
test("uses default values for missing properties", () => {
|
||||||
const minimalProject = {
|
const minimalProject = {
|
||||||
id: "minimal",
|
id: "minimal",
|
||||||
version: 1,
|
version: 1,
|
||||||
scenes: [],
|
scenes: [],
|
||||||
};
|
};
|
||||||
const result = await transformProjectV1ToV2({ project: minimalProject });
|
const result = transformProjectV1ToV2({ project: minimalProject });
|
||||||
|
|
||||||
const settings = result.project.settings as Record<string, unknown>;
|
const settings = result.project.settings as Record<string, unknown>;
|
||||||
expect(settings.fps).toBe(DEFAULT_FPS);
|
expect(settings.fps).toBe(DEFAULT_FPS);
|
||||||
@@ -120,14 +124,14 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(background.color).toBe(DEFAULT_BACKGROUND_COLOR);
|
expect(background.color).toBe(DEFAULT_BACKGROUND_COLOR);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses default blur intensity when missing", async () => {
|
test("uses default blur intensity when missing", () => {
|
||||||
const projectWithBlurNoIntensity = {
|
const projectWithBlurNoIntensity = {
|
||||||
id: "blur-no-intensity",
|
id: "blur-no-intensity",
|
||||||
version: 1,
|
version: 1,
|
||||||
backgroundType: "blur",
|
backgroundType: "blur",
|
||||||
scenes: [],
|
scenes: [],
|
||||||
};
|
};
|
||||||
const result = await transformProjectV1ToV2({
|
const result = transformProjectV1ToV2({
|
||||||
project: projectWithBlurNoIntensity,
|
project: projectWithBlurNoIntensity,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,23 +140,23 @@ describe("V1 to V2 Migration", () => {
|
|||||||
expect(background.blurIntensity).toBe(DEFAULT_BACKGROUND_BLUR_INTENSITY);
|
expect(background.blurIntensity).toBe(DEFAULT_BACKGROUND_BLUR_INTENSITY);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("preserves currentSceneId", async () => {
|
test("preserves currentSceneId", () => {
|
||||||
const result = await transformProjectV1ToV2({ project: v1Project });
|
const result = transformProjectV1ToV2({ project: v1Project });
|
||||||
expect(result.project.currentSceneId).toBe(v1Project.currentSceneId);
|
expect(result.project.currentSceneId).toBe(v1Project.currentSceneId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("finds main scene id when currentSceneId missing", async () => {
|
test("finds main scene id when currentSceneId missing", () => {
|
||||||
const projectWithoutCurrentScene = {
|
const projectWithoutCurrentScene = {
|
||||||
...v1Project,
|
...v1Project,
|
||||||
currentSceneId: undefined,
|
currentSceneId: undefined,
|
||||||
};
|
};
|
||||||
const result = await transformProjectV1ToV2({
|
const result = transformProjectV1ToV2({
|
||||||
project: projectWithoutCurrentScene,
|
project: projectWithoutCurrentScene,
|
||||||
});
|
});
|
||||||
expect(result.project.currentSceneId).toBe("scene-main");
|
expect(result.project.currentSceneId).toBe("scene-main");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("skips loading tracks if scene already has tracks", async () => {
|
test("skips loading tracks if scene already has tracks", () => {
|
||||||
const projectWithTracks = {
|
const projectWithTracks = {
|
||||||
...v1Project,
|
...v1Project,
|
||||||
scenes: [
|
scenes: [
|
||||||
@@ -175,7 +179,7 @@ describe("V1 to V2 Migration", () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await transformProjectV1ToV2({
|
const result = transformProjectV1ToV2({
|
||||||
project: projectWithTracks,
|
project: projectWithTracks,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,22 +192,32 @@ describe("V1 to V2 Migration", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Track Loading and Transformation", () => {
|
describe("Track Loading and Transformation", () => {
|
||||||
test("loads tracks from legacy DB and transforms media track to video track", async () => {
|
test("loads tracks from legacy DB and transforms media track to video track", () => {
|
||||||
const mockLoadMediaAsset = async ({
|
const context: V1ToV2Context = {
|
||||||
mediaId,
|
legacyTracksBySceneId: {
|
||||||
}: {
|
"scene-main": [
|
||||||
mediaId: string;
|
{
|
||||||
}): Promise<MediaAssetData | null> => {
|
id: "legacy-track-1",
|
||||||
if (mediaId === "media-1") {
|
type: "media",
|
||||||
return {
|
name: "Legacy media track",
|
||||||
id: "media-1",
|
elements: [
|
||||||
name: "Test Video",
|
{
|
||||||
type: "video",
|
id: "media-element-1",
|
||||||
size: 1000,
|
name: "Test video clip",
|
||||||
lastModified: Date.now(),
|
type: "media",
|
||||||
};
|
mediaId: "media-1",
|
||||||
}
|
duration: 120,
|
||||||
return null;
|
startTime: 0,
|
||||||
|
trimStart: 0,
|
||||||
|
trimEnd: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mediaTypesById: {
|
||||||
|
"media-1": "video",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectWithLegacyTracks = {
|
const projectWithLegacyTracks = {
|
||||||
@@ -221,19 +235,50 @@ describe("V1 to V2 Migration", () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// mock IndexedDB for this test would require setting up a test environment
|
const result = transformProjectV1ToV2({
|
||||||
// for now, we test that the transformer handles empty tracks gracefully
|
|
||||||
const result = await transformProjectV1ToV2({
|
|
||||||
project: projectWithLegacyTracks,
|
project: projectWithLegacyTracks,
|
||||||
options: { loadMediaAsset: mockLoadMediaAsset },
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
const scenes = result.project.scenes as Array<Record<string, unknown>>;
|
const scenes = result.project.scenes as Array<Record<string, unknown>>;
|
||||||
const mainScene = scenes[0];
|
const mainScene = scenes[0];
|
||||||
expect(Array.isArray(mainScene.tracks)).toBe(true);
|
const tracks = mainScene.tracks as Array<Record<string, unknown>>;
|
||||||
|
expect(Array.isArray(tracks)).toBe(true);
|
||||||
|
expect(tracks).toHaveLength(1);
|
||||||
|
expect(tracks[0].type).toBe("video");
|
||||||
|
expect(tracks[0].isMain).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("transforms text element preserving opacity and migrating position", async () => {
|
test("transforms text element preserving opacity and migrating position", () => {
|
||||||
|
const context: V1ToV2Context = {
|
||||||
|
legacyTracksBySceneId: {
|
||||||
|
"scene-1": [
|
||||||
|
{
|
||||||
|
id: "legacy-text-track",
|
||||||
|
type: "text",
|
||||||
|
name: "Text",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
id: "text-element-1",
|
||||||
|
name: "Title",
|
||||||
|
type: "text",
|
||||||
|
content: "Hello",
|
||||||
|
x: 120,
|
||||||
|
y: 240,
|
||||||
|
rotation: 15,
|
||||||
|
opacity: 0.5,
|
||||||
|
duration: 90,
|
||||||
|
startTime: 10,
|
||||||
|
trimStart: 0,
|
||||||
|
trimEnd: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mediaTypesById: {},
|
||||||
|
};
|
||||||
|
|
||||||
const projectWithTextTrack = {
|
const projectWithTextTrack = {
|
||||||
id: "project-text",
|
id: "project-text",
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -251,15 +296,22 @@ describe("V1 to V2 Migration", () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// since tracks are empty, transformation won't happen
|
const result = transformProjectV1ToV2({
|
||||||
// but we verify the structure is correct
|
|
||||||
const result = await transformProjectV1ToV2({
|
|
||||||
project: projectWithTextTrack,
|
project: projectWithTextTrack,
|
||||||
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.skipped).toBe(false);
|
expect(result.skipped).toBe(false);
|
||||||
const scenes = result.project.scenes as Array<Record<string, unknown>>;
|
const scenes = result.project.scenes as Array<Record<string, unknown>>;
|
||||||
expect(scenes.length).toBe(1);
|
const tracks = scenes[0].tracks as Array<Record<string, unknown>>;
|
||||||
|
const elements = tracks[0].elements as Array<Record<string, unknown>>;
|
||||||
|
const textElement = elements[0];
|
||||||
|
expect(textElement.opacity).toBe(0.5);
|
||||||
|
expect(textElement.transform).toEqual({
|
||||||
|
scale: 1,
|
||||||
|
position: { x: 120, y: 240 },
|
||||||
|
rotate: 15,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
|
|
||||||
|
export interface StorageMigrationRunArgs {
|
||||||
|
projectId: string;
|
||||||
|
project: ProjectRecord;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class StorageMigration {
|
export abstract class StorageMigration {
|
||||||
abstract from: number;
|
abstract from: number;
|
||||||
abstract to: number;
|
abstract to: number;
|
||||||
abstract transform(
|
|
||||||
project: ProjectRecord,
|
abstract run({
|
||||||
): Promise<MigrationResult<ProjectRecord>>;
|
projectId,
|
||||||
|
project,
|
||||||
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ export async function runStorageMigrations({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let projectRecord = project as ProjectRecord;
|
let projectRecord = project as ProjectRecord;
|
||||||
|
const projectId = getProjectId({ project: projectRecord });
|
||||||
|
if (!projectId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let currentVersion = getProjectVersion({ project: projectRecord });
|
let currentVersion = getProjectVersion({ project: projectRecord });
|
||||||
const targetVersion = orderedMigrations.at(-1)?.to ?? currentVersion;
|
const targetVersion = orderedMigrations.at(-1)?.to ?? currentVersion;
|
||||||
|
|
||||||
@@ -81,17 +86,15 @@ export async function runStorageMigrations({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await migration.transform(projectRecord);
|
const result = await migration.run({
|
||||||
|
projectId,
|
||||||
|
project: projectRecord,
|
||||||
|
});
|
||||||
|
|
||||||
if (result.skipped) {
|
if (result.skipped) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectId = getProjectId({ project: result.project });
|
|
||||||
if (!projectId) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await projectsAdapter.set(projectId, result.project);
|
await projectsAdapter.set(projectId, result.project);
|
||||||
migratedCount++;
|
migratedCount++;
|
||||||
currentVersion = migration.to;
|
currentVersion = migration.to;
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/background/blur";
|
|
||||||
import { DEFAULT_BACKGROUND_COLOR } from "@/background/color";
|
|
||||||
import { DEFAULT_CANVAS_SIZE } from "@/canvas/sizes";
|
|
||||||
const DEFAULT_FPS = 30;
|
|
||||||
import { IndexedDBAdapter } from "@/services/storage/indexeddb-adapter";
|
|
||||||
import type { MediaAssetData } from "@/services/storage/types";
|
|
||||||
import type { MigrationResult, ProjectRecord } from "./types";
|
import type { MigrationResult, ProjectRecord } from "./types";
|
||||||
import { getProjectId, isRecord } from "./utils";
|
import { getProjectId, isRecord } from "./utils";
|
||||||
|
|
||||||
interface LegacyTimelineData {
|
// Frozen snapshots of v2-era defaults. See ./README.md.
|
||||||
tracks: unknown[];
|
const DEFAULT_BACKGROUND_BLUR_INTENSITY = 10;
|
||||||
lastModified: string;
|
const DEFAULT_BACKGROUND_COLOR = "#000000";
|
||||||
|
const DEFAULT_CANVAS_SIZE = { width: 1920, height: 1080 };
|
||||||
|
const DEFAULT_FPS = 30;
|
||||||
|
|
||||||
|
type LegacyMediaType = "image" | "video" | "audio";
|
||||||
|
|
||||||
|
export interface V1ToV2Context {
|
||||||
|
legacyTracksBySceneId: Record<string, unknown[]>;
|
||||||
|
mediaTypesById: Record<string, LegacyMediaType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMPTY_V1_TO_V2_CONTEXT: V1ToV2Context = {
|
||||||
|
legacyTracksBySceneId: {},
|
||||||
|
mediaTypesById: {},
|
||||||
|
};
|
||||||
|
|
||||||
interface LegacyMediaElement {
|
interface LegacyMediaElement {
|
||||||
type: "media";
|
type: "media";
|
||||||
mediaId: string;
|
mediaId: string;
|
||||||
@@ -146,21 +153,13 @@ interface V2AudioTrack {
|
|||||||
|
|
||||||
type V2TimelineTrack = V2VideoTrack | V2TextTrack | V2AudioTrack;
|
type V2TimelineTrack = V2VideoTrack | V2TextTrack | V2AudioTrack;
|
||||||
|
|
||||||
export interface TransformV1ToV2Options {
|
export function transformProjectV1ToV2({
|
||||||
loadMediaAsset?: ({
|
|
||||||
mediaId,
|
|
||||||
}: {
|
|
||||||
mediaId: string;
|
|
||||||
}) => Promise<MediaAssetData | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function transformProjectV1ToV2({
|
|
||||||
project,
|
project,
|
||||||
options = {},
|
context = EMPTY_V1_TO_V2_CONTEXT,
|
||||||
}: {
|
}: {
|
||||||
project: ProjectRecord;
|
project: ProjectRecord;
|
||||||
options?: TransformV1ToV2Options;
|
context?: V1ToV2Context;
|
||||||
}): Promise<MigrationResult<ProjectRecord>> {
|
}): MigrationResult<ProjectRecord> {
|
||||||
const projectId = getProjectId({ project });
|
const projectId = getProjectId({ project });
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
return { project, skipped: true, reason: "no project id" };
|
return { project, skipped: true, reason: "no project id" };
|
||||||
@@ -170,27 +169,23 @@ export async function transformProjectV1ToV2({
|
|||||||
return { project, skipped: true, reason: "already v2" };
|
return { project, skipped: true, reason: "already v2" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const migratedProject = await migrateProject({
|
const migratedProject = migrateProject({
|
||||||
project,
|
project,
|
||||||
projectId,
|
projectId,
|
||||||
loadMediaAsset: options.loadMediaAsset,
|
context,
|
||||||
});
|
});
|
||||||
return { project: migratedProject, skipped: false };
|
return { project: migratedProject, skipped: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function migrateProject({
|
function migrateProject({
|
||||||
project,
|
project,
|
||||||
projectId,
|
projectId,
|
||||||
loadMediaAsset,
|
context,
|
||||||
}: {
|
}: {
|
||||||
project: ProjectRecord;
|
project: ProjectRecord;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
loadMediaAsset?: ({
|
context: V1ToV2Context;
|
||||||
mediaId,
|
}): ProjectRecord {
|
||||||
}: {
|
|
||||||
mediaId: string;
|
|
||||||
}) => Promise<MediaAssetData | null>;
|
|
||||||
}): Promise<ProjectRecord> {
|
|
||||||
const createdAt = normalizeDateString({ value: project.createdAt });
|
const createdAt = normalizeDateString({ value: project.createdAt });
|
||||||
const updatedAt = normalizeDateString({ value: project.updatedAt });
|
const updatedAt = normalizeDateString({ value: project.updatedAt });
|
||||||
const metadataValue = project.metadata;
|
const metadataValue = project.metadata;
|
||||||
@@ -217,8 +212,7 @@ async function migrateProject({
|
|||||||
? project.bookmarks
|
? project.bookmarks
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const migratedScenes = await Promise.all(
|
const migratedScenes = scenes.map((scene) => {
|
||||||
scenes.map(async (scene) => {
|
|
||||||
if (!isRecord(scene)) {
|
if (!isRecord(scene)) {
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
@@ -236,23 +230,17 @@ async function migrateProject({
|
|||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tracks = await loadTracksFromLegacyDB({
|
const tracks = context.legacyTracksBySceneId[sceneId] ?? [];
|
||||||
projectId,
|
const transformedTracks = transformTracks({
|
||||||
sceneId,
|
|
||||||
isMain: scene.isMain === true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const transformedTracks = await transformTracks({
|
|
||||||
tracks,
|
tracks,
|
||||||
loadMediaAsset,
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...scene,
|
...scene,
|
||||||
tracks: transformedTracks,
|
tracks: transformedTracks,
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const normalizedScenes = applyLegacyBookmarks({
|
const normalizedScenes = applyLegacyBookmarks({
|
||||||
scenes: migratedScenes,
|
scenes: migratedScenes,
|
||||||
@@ -305,117 +293,63 @@ async function migrateProject({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadTracksFromLegacyDB({
|
function transformTracks({
|
||||||
projectId,
|
|
||||||
sceneId,
|
|
||||||
isMain,
|
|
||||||
}: {
|
|
||||||
projectId: string;
|
|
||||||
sceneId: string;
|
|
||||||
isMain: boolean;
|
|
||||||
}): Promise<unknown[]> {
|
|
||||||
if (typeof indexedDB === "undefined") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
|
|
||||||
const projectDbName = `video-editor-timelines-${projectId}`;
|
|
||||||
|
|
||||||
const adapter = new IndexedDBAdapter<LegacyTimelineData>(
|
|
||||||
sceneDbName,
|
|
||||||
"timeline",
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = await adapter.get("timeline");
|
|
||||||
|
|
||||||
if (!data && isMain) {
|
|
||||||
const projectAdapter = new IndexedDBAdapter<LegacyTimelineData>(
|
|
||||||
projectDbName,
|
|
||||||
"timeline",
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
data = await projectAdapter.get("timeline");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || !Array.isArray(data.tracks)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.tracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function transformTracks({
|
|
||||||
tracks,
|
tracks,
|
||||||
loadMediaAsset,
|
context,
|
||||||
}: {
|
}: {
|
||||||
tracks: unknown[];
|
tracks: unknown[];
|
||||||
loadMediaAsset?: ({
|
context: V1ToV2Context;
|
||||||
mediaId,
|
}): V2TimelineTrack[] {
|
||||||
}: {
|
|
||||||
mediaId: string;
|
|
||||||
}) => Promise<MediaAssetData | null>;
|
|
||||||
}): Promise<V2TimelineTrack[]> {
|
|
||||||
if (!Array.isArray(tracks)) {
|
if (!Array.isArray(tracks)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFirstVideoTrackFound = false;
|
let isFirstVideoTrackFound = false;
|
||||||
const transformedTracks: (V2TimelineTrack | null)[] = [];
|
const transformedTracks = tracks.map((track): V2TimelineTrack | null => {
|
||||||
|
|
||||||
for (const track of tracks) {
|
|
||||||
if (!isRecord(track)) {
|
if (!isRecord(track)) {
|
||||||
transformedTracks.push(null);
|
return null;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackType = track.type;
|
const trackType = track.type;
|
||||||
if (trackType === "media") {
|
if (trackType === "media") {
|
||||||
const videoTrack = await transformMediaTrack({
|
const isMain = !isFirstVideoTrackFound;
|
||||||
track: track as LegacyMediaTrack,
|
|
||||||
loadMediaAsset,
|
|
||||||
isMain: !isFirstVideoTrackFound,
|
|
||||||
});
|
|
||||||
isFirstVideoTrackFound = true;
|
isFirstVideoTrackFound = true;
|
||||||
transformedTracks.push(videoTrack);
|
const videoTrack = transformMediaTrack({
|
||||||
continue;
|
track: track as LegacyMediaTrack,
|
||||||
|
context,
|
||||||
|
isMain,
|
||||||
|
});
|
||||||
|
return videoTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackType === "text") {
|
if (trackType === "text") {
|
||||||
transformedTracks.push(transformTextTrack({ track }));
|
return transformTextTrack({ track });
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackType === "audio") {
|
if (trackType === "audio") {
|
||||||
transformedTracks.push(transformAudioTrack({ track }));
|
return transformAudioTrack({ track });
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transformedTracks.push(null);
|
return null;
|
||||||
}
|
});
|
||||||
|
|
||||||
return transformedTracks.filter(
|
return transformedTracks.filter(
|
||||||
(track): track is V2TimelineTrack => track !== null,
|
(track): track is V2TimelineTrack => track !== null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function transformMediaTrack({
|
function transformMediaTrack({
|
||||||
track,
|
track,
|
||||||
loadMediaAsset,
|
context,
|
||||||
isMain,
|
isMain,
|
||||||
}: {
|
}: {
|
||||||
track: LegacyMediaTrack;
|
track: LegacyMediaTrack;
|
||||||
loadMediaAsset?: ({
|
context: V1ToV2Context;
|
||||||
mediaId,
|
|
||||||
}: {
|
|
||||||
mediaId: string;
|
|
||||||
}) => Promise<MediaAssetData | null>;
|
|
||||||
isMain: boolean;
|
isMain: boolean;
|
||||||
}): Promise<V2VideoTrack> {
|
}): V2VideoTrack {
|
||||||
const elements = Array.isArray(track.elements) ? track.elements : [];
|
const elements = Array.isArray(track.elements) ? track.elements : [];
|
||||||
|
|
||||||
const transformedElements = await Promise.all(
|
const transformedElements = elements.map((element) => {
|
||||||
elements.map(async (element) => {
|
|
||||||
if (!isRecord(element) || element.type !== "media") {
|
if (!isRecord(element) || element.type !== "media") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -427,11 +361,9 @@ async function transformMediaTrack({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mediaType: "video" | "image" = "video";
|
let mediaType: "video" | "image" = "video";
|
||||||
if (loadMediaAsset) {
|
const storedMediaType = context.mediaTypesById[mediaId];
|
||||||
const mediaAsset = await loadMediaAsset({ mediaId });
|
if (storedMediaType) {
|
||||||
if (mediaAsset) {
|
mediaType = storedMediaType === "image" ? "image" : "video";
|
||||||
mediaType = mediaAsset.type === "image" ? "image" : "video";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTransform: V2Transform = {
|
const defaultTransform: V2Transform = {
|
||||||
@@ -480,8 +412,7 @@ async function transformMediaTrack({
|
|||||||
trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
|
trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
|
||||||
};
|
};
|
||||||
return videoElement;
|
return videoElement;
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const validElements = transformedElements.filter(
|
const validElements = transformedElements.filter(
|
||||||
(element): element is V2VideoElement | V2ImageElement => element !== null,
|
(element): element is V2VideoElement | V2ImageElement => element !== null,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/stickers/intrinsic-size";
|
|
||||||
import type { MigrationResult, ProjectRecord } from "./types";
|
import type { MigrationResult, ProjectRecord } from "./types";
|
||||||
import { getProjectId, isRecord } from "./utils";
|
import { getProjectId, isRecord } from "./utils";
|
||||||
|
|
||||||
|
// Frozen snapshot of the v15-era fallback. See ./README.md.
|
||||||
|
const STICKER_INTRINSIC_SIZE_FALLBACK = 200;
|
||||||
|
|
||||||
export function transformProjectV14ToV15({
|
export function transformProjectV14ToV15({
|
||||||
project,
|
project,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import type { MigrationResult, ProjectRecord } from "./types";
|
import type { MigrationResult, ProjectRecord } from "./types";
|
||||||
import { VOLUME_DB_MIN } from "@/timeline/audio-constants";
|
|
||||||
import { clampDb } from "@/timeline/audio-state";
|
|
||||||
import { getProjectId, isRecord } from "./utils";
|
import { getProjectId, isRecord } from "./utils";
|
||||||
|
|
||||||
|
// Frozen snapshots of v18-era volume bounds. See ./README.md.
|
||||||
|
const VOLUME_DB_MIN = -60;
|
||||||
|
const VOLUME_DB_MAX = 20;
|
||||||
|
|
||||||
export function transformProjectV17ToV18({
|
export function transformProjectV17ToV18({
|
||||||
project,
|
project,
|
||||||
}: {
|
}: {
|
||||||
@@ -68,9 +70,7 @@ function migrateElementVolumes({
|
|||||||
return {
|
return {
|
||||||
...element,
|
...element,
|
||||||
volume:
|
volume:
|
||||||
typeof legacyVolume === "number"
|
typeof legacyVolume === "number" ? linearGainToDb(legacyVolume) : 0,
|
||||||
? linearGainToDb(legacyVolume)
|
|
||||||
: 0,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,3 +103,11 @@ function linearGainToDb(gain: number): number {
|
|||||||
|
|
||||||
return clampDb(20 * Math.log10(gain));
|
return clampDb(20 * Math.log10(gain));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampDb(value: number): number {
|
||||||
|
if (!Number.isFinite(value)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(VOLUME_DB_MAX, Math.max(VOLUME_DB_MIN, value));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { INTENSITY_TO_SIGMA_DIVISOR } from "@/effects/definitions/blur";
|
|
||||||
import type { MigrationResult, ProjectRecord } from "./types";
|
import type { MigrationResult, ProjectRecord } from "./types";
|
||||||
import { getProjectId, isRecord } from "./utils";
|
import { getProjectId, isRecord } from "./utils";
|
||||||
|
|
||||||
|
// Frozen snapshot of the v21-era divisor. See ./README.md.
|
||||||
|
const INTENSITY_TO_SIGMA_DIVISOR = 5;
|
||||||
const LEGACY_DEFAULT_BACKGROUND_BLUR_INTENSITY = 50;
|
const LEGACY_DEFAULT_BACKGROUND_BLUR_INTENSITY = 50;
|
||||||
|
|
||||||
export function transformProjectV20ToV21({
|
export function transformProjectV20ToV21({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { MigrationResult, ProjectRecord } from "./types";
|
import type { MigrationResult, ProjectRecord } from "./types";
|
||||||
import { getProjectId, isRecord } from "./utils";
|
import { getProjectId, isRecord } from "./utils";
|
||||||
|
|
||||||
import { TICKS_PER_SECOND } from "@/wasm";
|
// Frozen snapshot of the v23-era tick rate. See ./README.md.
|
||||||
|
const TICKS_PER_SECOND = 120_000;
|
||||||
const ARBITRARY_FPS_DENOMINATOR = 1_000_000;
|
const ARBITRARY_FPS_DENOMINATOR = 1_000_000;
|
||||||
const STANDARD_FRAME_RATES = [
|
const STANDARD_FRAME_RATES = [
|
||||||
{ value: 24_000 / 1_001, numerator: 24_000, denominator: 1_001 },
|
{ value: 24_000 / 1_001, numerator: 24_000, denominator: 1_001 },
|
||||||
@@ -193,7 +194,9 @@ function migrateAnimationChannel({ channel }: { channel: unknown }): unknown {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...channel,
|
...channel,
|
||||||
keys: channel.keys.map((keyframe) => migrateAnimationKeyframe({ keyframe })),
|
keys: channel.keys.map((keyframe) =>
|
||||||
|
migrateAnimationKeyframe({ keyframe }),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +293,8 @@ function migrateFrameRate({ fps }: { fps: unknown }): unknown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const standardFrameRate = STANDARD_FRAME_RATES.find(
|
const standardFrameRate = STANDARD_FRAME_RATES.find(
|
||||||
(candidate) => Math.abs(fps - candidate.value) <= STANDARD_FRAME_RATE_TOLERANCE,
|
(candidate) =>
|
||||||
|
Math.abs(fps - candidate.value) <= STANDARD_FRAME_RATE_TOLERANCE,
|
||||||
);
|
);
|
||||||
if (standardFrameRate) {
|
if (standardFrameRate) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,50 @@
|
|||||||
import { parseCustomMaskPath } from "@/masks/custom-path";
|
|
||||||
import type { MigrationResult, ProjectRecord } from "./types";
|
import type { MigrationResult, ProjectRecord } from "./types";
|
||||||
import { getProjectId, isRecord } from "./utils";
|
import { getProjectId, isRecord } from "./utils";
|
||||||
|
|
||||||
|
interface CustomMaskPathPoint {
|
||||||
|
id: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
inX: number;
|
||||||
|
inY: number;
|
||||||
|
outX: number;
|
||||||
|
outY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint {
|
||||||
|
if (!value || typeof value !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidate = value as Record<string, unknown>;
|
||||||
|
return (
|
||||||
|
typeof candidate.id === "string" &&
|
||||||
|
typeof candidate.x === "number" &&
|
||||||
|
typeof candidate.y === "number" &&
|
||||||
|
typeof candidate.inX === "number" &&
|
||||||
|
typeof candidate.inY === "number" &&
|
||||||
|
typeof candidate.outX === "number" &&
|
||||||
|
typeof candidate.outY === "number"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCustomMaskPath({
|
||||||
|
path,
|
||||||
|
}: {
|
||||||
|
path: string;
|
||||||
|
}): CustomMaskPathPoint[] {
|
||||||
|
if (!path) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(path);
|
||||||
|
return Array.isArray(parsed) ? parsed.filter(isCustomMaskPathPoint) : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function transformProjectV26ToV27({
|
export function transformProjectV26ToV27({
|
||||||
project,
|
project,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV0ToV1 } from "./transformers/v0-to-v1";
|
import { transformProjectV0ToV1 } from "./transformers/v0-to-v1";
|
||||||
|
|
||||||
export class V0toV1Migration extends StorageMigration {
|
export class V0toV1Migration extends StorageMigration {
|
||||||
from = 0;
|
from = 0;
|
||||||
to = 1;
|
to = 1;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV0ToV1({ project });
|
return transformProjectV0ToV1({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,97 +3,260 @@ import {
|
|||||||
deleteDatabase,
|
deleteDatabase,
|
||||||
} from "@/services/storage/indexeddb-adapter";
|
} from "@/services/storage/indexeddb-adapter";
|
||||||
import type { MediaAssetData } from "@/services/storage/types";
|
import type { MediaAssetData } from "@/services/storage/types";
|
||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import {
|
import {
|
||||||
getProjectId,
|
|
||||||
transformProjectV1ToV2,
|
transformProjectV1ToV2,
|
||||||
type TransformV1ToV2Options,
|
type V1ToV2Context,
|
||||||
} from "./transformers/v1-to-v2";
|
} from "./transformers/v1-to-v2";
|
||||||
|
import { isRecord } from "./transformers/utils";
|
||||||
|
|
||||||
|
interface LegacyTimelineData {
|
||||||
|
tracks: unknown[];
|
||||||
|
lastModified: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class V1toV2Migration extends StorageMigration {
|
export class V1toV2Migration extends StorageMigration {
|
||||||
from = 1;
|
from = 1;
|
||||||
to = 2;
|
to = 2;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
projectId,
|
||||||
skipped: boolean;
|
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
const projectId = getProjectId({ project });
|
|
||||||
if (!projectId) {
|
|
||||||
return { project, skipped: true, reason: "no project id" };
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadMediaAsset = createMediaAssetLoader({ projectId });
|
|
||||||
|
|
||||||
const result = await transformProjectV1ToV2({
|
|
||||||
project,
|
project,
|
||||||
options: { loadMediaAsset },
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
});
|
const context = await loadV1ToV2Context({ projectId, project });
|
||||||
|
const result = transformProjectV1ToV2({ project, context });
|
||||||
|
|
||||||
if (!result.skipped) {
|
if (!result.skipped) {
|
||||||
void cleanupLegacyTimelineDBs({
|
try {
|
||||||
projectId,
|
await deleteLegacyTimelineDbs({ projectId, project: result.project });
|
||||||
project: result.project,
|
} catch (error) {
|
||||||
});
|
console.error(
|
||||||
|
`V1→V2 migration cleanup failed for project ${projectId}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMediaAssetLoader({
|
async function loadV1ToV2Context({
|
||||||
projectId,
|
projectId,
|
||||||
|
project,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}): TransformV1ToV2Options["loadMediaAsset"] {
|
project: ProjectRecord;
|
||||||
return async ({ mediaId }: { mediaId: string }) => {
|
}): Promise<V1ToV2Context> {
|
||||||
|
const legacyTracksBySceneId = await loadLegacyTracksBySceneId({
|
||||||
|
projectId,
|
||||||
|
project,
|
||||||
|
});
|
||||||
|
const mediaTypesById = await loadMediaTypesById({
|
||||||
|
projectId,
|
||||||
|
legacyTracksBySceneId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
legacyTracksBySceneId,
|
||||||
|
mediaTypesById,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLegacyTracksBySceneId({
|
||||||
|
projectId,
|
||||||
|
project,
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
project: ProjectRecord;
|
||||||
|
}): Promise<V1ToV2Context["legacyTracksBySceneId"]> {
|
||||||
|
const scenes = project.scenes;
|
||||||
|
if (!Array.isArray(scenes)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const sceneEntries = await Promise.all(
|
||||||
|
scenes.map(async (scene) => {
|
||||||
|
if (!isRecord(scene)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sceneId = scene.id;
|
||||||
|
if (typeof sceneId !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tracks = await loadLegacyTracksForScene({
|
||||||
|
projectId,
|
||||||
|
sceneId,
|
||||||
|
isMain: scene.isMain === true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return [sceneId, tracks] as const;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
sceneEntries.filter(
|
||||||
|
(
|
||||||
|
sceneEntry,
|
||||||
|
): sceneEntry is readonly [
|
||||||
|
string,
|
||||||
|
V1ToV2Context["legacyTracksBySceneId"][string],
|
||||||
|
] => sceneEntry !== null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadLegacyTracksForScene({
|
||||||
|
projectId,
|
||||||
|
sceneId,
|
||||||
|
isMain,
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
sceneId: string;
|
||||||
|
isMain: boolean;
|
||||||
|
}): Promise<unknown[]> {
|
||||||
|
const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
|
||||||
|
const projectDbName = `video-editor-timelines-${projectId}`;
|
||||||
|
|
||||||
|
const adapter = new IndexedDBAdapter<LegacyTimelineData>(
|
||||||
|
sceneDbName,
|
||||||
|
"timeline",
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = await adapter.get("timeline");
|
||||||
|
|
||||||
|
if (!data && isMain) {
|
||||||
|
const projectAdapter = new IndexedDBAdapter<LegacyTimelineData>(
|
||||||
|
projectDbName,
|
||||||
|
"timeline",
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
data = await projectAdapter.get("timeline");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || !Array.isArray(data.tracks)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadMediaTypesById({
|
||||||
|
projectId,
|
||||||
|
legacyTracksBySceneId,
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
legacyTracksBySceneId: V1ToV2Context["legacyTracksBySceneId"];
|
||||||
|
}): Promise<V1ToV2Context["mediaTypesById"]> {
|
||||||
|
const mediaIds = collectLegacyMediaIds({ legacyTracksBySceneId });
|
||||||
|
if (mediaIds.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(
|
const mediaMetadataAdapter = new IndexedDBAdapter<MediaAssetData>(
|
||||||
`video-editor-media-${projectId}`,
|
`video-editor-media-${projectId}`,
|
||||||
"media-metadata",
|
"media-metadata",
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
return mediaMetadataAdapter.get(mediaId);
|
const mediaEntries = await Promise.all(
|
||||||
};
|
mediaIds.map(async (mediaId) => {
|
||||||
|
const mediaMetadata = await mediaMetadataAdapter.get(mediaId);
|
||||||
|
if (!mediaMetadata) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupLegacyTimelineDBs({
|
return [mediaId, mediaMetadata.type] as const;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
mediaEntries.filter(
|
||||||
|
(
|
||||||
|
mediaEntry,
|
||||||
|
): mediaEntry is readonly [
|
||||||
|
string,
|
||||||
|
V1ToV2Context["mediaTypesById"][string],
|
||||||
|
] => mediaEntry !== null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectLegacyMediaIds({
|
||||||
|
legacyTracksBySceneId,
|
||||||
|
}: {
|
||||||
|
legacyTracksBySceneId: V1ToV2Context["legacyTracksBySceneId"];
|
||||||
|
}): string[] {
|
||||||
|
const mediaIds = new Set<string>();
|
||||||
|
|
||||||
|
for (const tracks of Object.values(legacyTracksBySceneId)) {
|
||||||
|
if (!Array.isArray(tracks)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
if (!isRecord(track) || track.type !== "media") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = track.elements;
|
||||||
|
if (!Array.isArray(elements)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of elements) {
|
||||||
|
if (!isRecord(element) || element.type !== "media") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof element.mediaId !== "string") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaIds.add(element.mediaId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(mediaIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteLegacyTimelineDbs({
|
||||||
projectId,
|
projectId,
|
||||||
project,
|
project,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
project: ProjectRecord;
|
project: ProjectRecord;
|
||||||
}): void {
|
}): Promise<void> {
|
||||||
|
const dbNames = getLegacyTimelineDbNames({ projectId, project });
|
||||||
|
await Promise.all(dbNames.map((dbName) => deleteDatabase({ dbName })));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLegacyTimelineDbNames({
|
||||||
|
projectId,
|
||||||
|
project,
|
||||||
|
}: {
|
||||||
|
projectId: string;
|
||||||
|
project: ProjectRecord;
|
||||||
|
}): string[] {
|
||||||
const scenes = project.scenes;
|
const scenes = project.scenes;
|
||||||
if (!Array.isArray(scenes)) {
|
if (!Array.isArray(scenes)) {
|
||||||
return;
|
return [`video-editor-timelines-${projectId}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbNamesToDelete: string[] = [];
|
const sceneDbNames = scenes.flatMap((scene) => {
|
||||||
|
if (!isRecord(scene)) {
|
||||||
for (const scene of scenes) {
|
return [];
|
||||||
if (typeof scene !== "object" || scene === null) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sceneId = scene.id;
|
return typeof scene.id === "string"
|
||||||
if (typeof sceneId === "string") {
|
? [`video-editor-timelines-${projectId}-${scene.id}`]
|
||||||
const sceneDbName = `video-editor-timelines-${projectId}-${sceneId}`;
|
: [];
|
||||||
dbNamesToDelete.push(sceneDbName);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectDbName = `video-editor-timelines-${projectId}`;
|
return [...sceneDbNames, `video-editor-timelines-${projectId}`];
|
||||||
dbNamesToDelete.push(projectDbName);
|
|
||||||
|
|
||||||
// Fire-and-forget: delete in parallel, don't block migration
|
|
||||||
void Promise.all(
|
|
||||||
dbNamesToDelete.map((dbName) =>
|
|
||||||
deleteDatabase({ dbName }).catch(() => {
|
|
||||||
// ignore errors, DB might not exist or already deleted
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV10ToV11 } from "./transformers/v10-to-v11";
|
import { transformProjectV10ToV11 } from "./transformers/v10-to-v11";
|
||||||
|
|
||||||
export class V10toV11Migration extends StorageMigration {
|
export class V10toV11Migration extends StorageMigration {
|
||||||
from = 10;
|
from = 10;
|
||||||
to = 11;
|
to = 11;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV10ToV11({ project });
|
return transformProjectV10ToV11({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV11ToV12 } from "./transformers/v11-to-v12";
|
import { transformProjectV11ToV12 } from "./transformers/v11-to-v12";
|
||||||
|
|
||||||
export class V11toV12Migration extends StorageMigration {
|
export class V11toV12Migration extends StorageMigration {
|
||||||
from = 11;
|
from = 11;
|
||||||
to = 12;
|
to = 12;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV11ToV12({ project });
|
return transformProjectV11ToV12({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV12ToV13 } from "./transformers/v12-to-v13";
|
import { transformProjectV12ToV13 } from "./transformers/v12-to-v13";
|
||||||
|
|
||||||
export class V12toV13Migration extends StorageMigration {
|
export class V12toV13Migration extends StorageMigration {
|
||||||
from = 12;
|
from = 12;
|
||||||
to = 13;
|
to = 13;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV12ToV13({ project });
|
return transformProjectV12ToV13({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV13ToV14 } from "./transformers/v13-to-v14";
|
import { transformProjectV13ToV14 } from "./transformers/v13-to-v14";
|
||||||
|
|
||||||
export class V13toV14Migration extends StorageMigration {
|
export class V13toV14Migration extends StorageMigration {
|
||||||
from = 13;
|
from = 13;
|
||||||
to = 14;
|
to = 14;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV13ToV14({ project });
|
return transformProjectV13ToV14({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV14ToV15 } from "./transformers/v14-to-v15";
|
import { transformProjectV14ToV15 } from "./transformers/v14-to-v15";
|
||||||
|
|
||||||
export class V14toV15Migration extends StorageMigration {
|
export class V14toV15Migration extends StorageMigration {
|
||||||
from = 14;
|
from = 14;
|
||||||
to = 15;
|
to = 15;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV14ToV15({ project });
|
return transformProjectV14ToV15({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV15ToV16 } from "./transformers/v15-to-v16";
|
import { transformProjectV15ToV16 } from "./transformers/v15-to-v16";
|
||||||
|
|
||||||
export class V15toV16Migration extends StorageMigration {
|
export class V15toV16Migration extends StorageMigration {
|
||||||
from = 15;
|
from = 15;
|
||||||
to = 16;
|
to = 16;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV15ToV16({ project });
|
return transformProjectV15ToV16({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV16ToV17 } from "./transformers/v16-to-v17";
|
import { transformProjectV16ToV17 } from "./transformers/v16-to-v17";
|
||||||
|
|
||||||
export class V16toV17Migration extends StorageMigration {
|
export class V16toV17Migration extends StorageMigration {
|
||||||
from = 16;
|
from = 16;
|
||||||
to = 17;
|
to = 17;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV16ToV17({ project });
|
return transformProjectV16ToV17({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV17ToV18 } from "./transformers/v17-to-v18";
|
import { transformProjectV17ToV18 } from "./transformers/v17-to-v18";
|
||||||
|
|
||||||
export class V17toV18Migration extends StorageMigration {
|
export class V17toV18Migration extends StorageMigration {
|
||||||
from = 17;
|
from = 17;
|
||||||
to = 18;
|
to = 18;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV17ToV18({ project });
|
return transformProjectV17ToV18({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV18ToV19 } from "./transformers/v18-to-v19";
|
import { transformProjectV18ToV19 } from "./transformers/v18-to-v19";
|
||||||
|
|
||||||
export class V18toV19Migration extends StorageMigration {
|
export class V18toV19Migration extends StorageMigration {
|
||||||
from = 18;
|
from = 18;
|
||||||
to = 19;
|
to = 19;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV18ToV19({ project });
|
return transformProjectV18ToV19({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV19ToV20 } from "./transformers/v19-to-v20";
|
import { transformProjectV19ToV20 } from "./transformers/v19-to-v20";
|
||||||
|
|
||||||
export class V19toV20Migration extends StorageMigration {
|
export class V19toV20Migration extends StorageMigration {
|
||||||
from = 19;
|
from = 19;
|
||||||
to = 20;
|
to = 20;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV19ToV20({ project });
|
return transformProjectV19ToV20({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV2ToV3 } from "./transformers/v2-to-v3";
|
import { transformProjectV2ToV3 } from "./transformers/v2-to-v3";
|
||||||
|
|
||||||
export class V2toV3Migration extends StorageMigration {
|
export class V2toV3Migration extends StorageMigration {
|
||||||
from = 2;
|
from = 2;
|
||||||
to = 3;
|
to = 3;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV2ToV3({ project });
|
return transformProjectV2ToV3({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV20ToV21 } from "./transformers/v20-to-v21";
|
import { transformProjectV20ToV21 } from "./transformers/v20-to-v21";
|
||||||
|
|
||||||
export class V20toV21Migration extends StorageMigration {
|
export class V20toV21Migration extends StorageMigration {
|
||||||
from = 20;
|
from = 20;
|
||||||
to = 21;
|
to = 21;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV20ToV21({ project });
|
return transformProjectV20ToV21({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV21ToV22 } from "./transformers/v21-to-v22";
|
import { transformProjectV21ToV22 } from "./transformers/v21-to-v22";
|
||||||
|
|
||||||
export class V21toV22Migration extends StorageMigration {
|
export class V21toV22Migration extends StorageMigration {
|
||||||
from = 21;
|
from = 21;
|
||||||
to = 22;
|
to = 22;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV21ToV22({ project });
|
return transformProjectV21ToV22({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV22ToV23 } from "./transformers/v22-to-v23";
|
import { transformProjectV22ToV23 } from "./transformers/v22-to-v23";
|
||||||
|
|
||||||
export class V22toV23Migration extends StorageMigration {
|
export class V22toV23Migration extends StorageMigration {
|
||||||
from = 22;
|
from = 22;
|
||||||
to = 23;
|
to = 23;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV22ToV23({ project });
|
return transformProjectV22ToV23({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV23ToV24 } from "./transformers/v23-to-v24";
|
import { transformProjectV23ToV24 } from "./transformers/v23-to-v24";
|
||||||
|
|
||||||
export class V23toV24Migration extends StorageMigration {
|
export class V23toV24Migration extends StorageMigration {
|
||||||
from = 23;
|
from = 23;
|
||||||
to = 24;
|
to = 24;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV23ToV24({ project });
|
return transformProjectV23ToV24({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV24ToV25 } from "./transformers/v24-to-v25";
|
import { transformProjectV24ToV25 } from "./transformers/v24-to-v25";
|
||||||
|
|
||||||
export class V24toV25Migration extends StorageMigration {
|
export class V24toV25Migration extends StorageMigration {
|
||||||
from = 24;
|
from = 24;
|
||||||
to = 25;
|
to = 25;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV24ToV25({ project });
|
return transformProjectV24ToV25({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV25ToV26 } from "./transformers/v25-to-v26";
|
import { transformProjectV25ToV26 } from "./transformers/v25-to-v26";
|
||||||
|
|
||||||
export class V25toV26Migration extends StorageMigration {
|
export class V25toV26Migration extends StorageMigration {
|
||||||
from = 25;
|
from = 25;
|
||||||
to = 26;
|
to = 26;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV25ToV26({ project });
|
return transformProjectV25ToV26({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV26ToV27 } from "./transformers/v26-to-v27";
|
import { transformProjectV26ToV27 } from "./transformers/v26-to-v27";
|
||||||
|
|
||||||
export class V26toV27Migration extends StorageMigration {
|
export class V26toV27Migration extends StorageMigration {
|
||||||
from = 26;
|
from = 26;
|
||||||
to = 27;
|
to = 27;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV26ToV27({ project });
|
return transformProjectV26ToV27({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV3ToV4 } from "./transformers/v3-to-v4";
|
import { transformProjectV3ToV4 } from "./transformers/v3-to-v4";
|
||||||
|
|
||||||
export class V3toV4Migration extends StorageMigration {
|
export class V3toV4Migration extends StorageMigration {
|
||||||
from = 3;
|
from = 3;
|
||||||
to = 4;
|
to = 4;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV3ToV4({ project });
|
return transformProjectV3ToV4({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV4ToV5 } from "./transformers/v4-to-v5";
|
import { transformProjectV4ToV5 } from "./transformers/v4-to-v5";
|
||||||
|
|
||||||
export class V4toV5Migration extends StorageMigration {
|
export class V4toV5Migration extends StorageMigration {
|
||||||
from = 4;
|
from = 4;
|
||||||
to = 5;
|
to = 5;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV4ToV5({ project });
|
return transformProjectV4ToV5({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV5ToV6 } from "./transformers/v5-to-v6";
|
import { transformProjectV5ToV6 } from "./transformers/v5-to-v6";
|
||||||
|
|
||||||
export class V5toV6Migration extends StorageMigration {
|
export class V5toV6Migration extends StorageMigration {
|
||||||
from = 5;
|
from = 5;
|
||||||
to = 6;
|
to = 6;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV5ToV6({ project });
|
return transformProjectV5ToV6({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV6ToV7 } from "./transformers/v6-to-v7";
|
import { transformProjectV6ToV7 } from "./transformers/v6-to-v7";
|
||||||
|
|
||||||
export class V6toV7Migration extends StorageMigration {
|
export class V6toV7Migration extends StorageMigration {
|
||||||
from = 6;
|
from = 6;
|
||||||
to = 7;
|
to = 7;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV6ToV7({ project });
|
return transformProjectV6ToV7({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV7ToV8 } from "./transformers/v7-to-v8";
|
import { transformProjectV7ToV8 } from "./transformers/v7-to-v8";
|
||||||
|
|
||||||
export class V7toV8Migration extends StorageMigration {
|
export class V7toV8Migration extends StorageMigration {
|
||||||
from = 7;
|
from = 7;
|
||||||
to = 8;
|
to = 8;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV7ToV8({ project });
|
return transformProjectV7ToV8({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV8ToV9 } from "./transformers/v8-to-v9";
|
import { transformProjectV8ToV9 } from "./transformers/v8-to-v9";
|
||||||
|
|
||||||
export class V8toV9Migration extends StorageMigration {
|
export class V8toV9Migration extends StorageMigration {
|
||||||
from = 8;
|
from = 8;
|
||||||
to = 9;
|
to = 9;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV8ToV9({ project });
|
return transformProjectV8ToV9({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { StorageMigration } from "./base";
|
import { StorageMigration, type StorageMigrationRunArgs } from "./base";
|
||||||
import type { ProjectRecord } from "./transformers/types";
|
import type { MigrationResult, ProjectRecord } from "./transformers/types";
|
||||||
import { transformProjectV9ToV10 } from "./transformers/v9-to-v10";
|
import { transformProjectV9ToV10 } from "./transformers/v9-to-v10";
|
||||||
|
|
||||||
export class V9toV10Migration extends StorageMigration {
|
export class V9toV10Migration extends StorageMigration {
|
||||||
from = 9;
|
from = 9;
|
||||||
to = 10;
|
to = 10;
|
||||||
|
|
||||||
async transform(project: ProjectRecord): Promise<{
|
async run({
|
||||||
project: ProjectRecord;
|
project,
|
||||||
skipped: boolean;
|
}: StorageMigrationRunArgs): Promise<MigrationResult<ProjectRecord>> {
|
||||||
reason?: string;
|
|
||||||
}> {
|
|
||||||
return transformProjectV9ToV10({ project });
|
return transformProjectV9ToV10({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user