diff options
Diffstat (limited to 'apps/web/src/services/storage/migrations/v1-to-v2.ts')
| -rw-r--r-- | apps/web/src/services/storage/migrations/v1-to-v2.ts | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/apps/web/src/services/storage/migrations/v1-to-v2.ts b/apps/web/src/services/storage/migrations/v1-to-v2.ts new file mode 100644 index 00000000..05d302ea --- /dev/null +++ b/apps/web/src/services/storage/migrations/v1-to-v2.ts @@ -0,0 +1,339 @@ +import { + DEFAULT_BLUR_INTENSITY, + DEFAULT_CANVAS_SIZE, + DEFAULT_COLOR, + DEFAULT_FPS, +} from "@/constants/project-constants"; +import { IndexedDBAdapter } from "@/services/storage/indexeddb-adapter"; +import { StorageMigration } from "./base"; + +type ProjectRecord = Record<string, unknown>; + +export class V1toV2Migration extends StorageMigration { + from = 1; + to = 2; + + async run(): Promise<void> { + const projectsAdapter = new IndexedDBAdapter<unknown>( + "video-editor-projects", + "projects", + 1, + ); + const projects = await projectsAdapter.getAll(); + + for (const project of projects) { + if (!isRecord(project)) { + continue; + } + + const projectId = getProjectId({ project }); + if (!projectId) { + continue; + } + + if (isV2Project({ project })) { + continue; + } + + const migratedProject = migrateProject({ project, projectId }); + await projectsAdapter.set(projectId, migratedProject); + } + } +} + +function migrateProject({ + project, + projectId, +}: { + project: ProjectRecord; + projectId: string; +}): ProjectRecord { + const createdAt = normalizeDateString({ value: project.createdAt }); + const updatedAt = normalizeDateString({ value: project.updatedAt }); + const metadataValue = project.metadata; + + const metadata = isRecord(metadataValue) + ? { + id: getStringValue({ value: metadataValue.id, fallback: projectId }), + name: getStringValue({ value: metadataValue.name, fallback: "" }), + thumbnail: getStringValue({ value: metadataValue.thumbnail }), + createdAt: normalizeDateString({ value: metadataValue.createdAt }), + updatedAt: normalizeDateString({ value: metadataValue.updatedAt }), + } + : { + id: projectId, + name: getStringValue({ value: project.name, fallback: "" }), + thumbnail: getStringValue({ value: project.thumbnail }), + createdAt, + updatedAt, + }; + + const scenesValue = project.scenes; + const scenes = Array.isArray(scenesValue) ? scenesValue : []; + const legacyBookmarks = Array.isArray(project.bookmarks) + ? project.bookmarks + : null; + const normalizedScenes = applyLegacyBookmarks({ + scenes, + legacyBookmarks, + }); + + const settingsValue = project.settings; + const settings = isRecord(settingsValue) + ? { + fps: getNumberValue({ + value: settingsValue.fps, + fallback: DEFAULT_FPS, + }), + canvasSize: getCanvasSizeValue({ + value: settingsValue.canvasSize, + fallback: DEFAULT_CANVAS_SIZE, + }), + background: getBackgroundValue({ + value: settingsValue.background, + }), + } + : { + fps: getNumberValue({ value: project.fps, fallback: DEFAULT_FPS }), + canvasSize: getCanvasSizeValue({ + value: project.canvasSize, + fallback: DEFAULT_CANVAS_SIZE, + }), + background: getBackgroundValue({ + value: project.background, + backgroundType: project.backgroundType, + backgroundColor: project.backgroundColor, + blurIntensity: project.blurIntensity, + }), + }; + + const currentSceneId = getCurrentSceneId({ + value: project.currentSceneId, + scenes: normalizedScenes, + }); + + return { + ...project, + metadata, + scenes: normalizedScenes, + currentSceneId, + settings, + version: 2, + }; +} + +function getProjectId({ project }: { project: ProjectRecord }): string | null { + const idValue = project.id; + if (typeof idValue === "string" && idValue.length > 0) { + return idValue; + } + + const metadataValue = project.metadata; + if (!isRecord(metadataValue)) { + return null; + } + + const metadataId = metadataValue.id; + if (typeof metadataId === "string" && metadataId.length > 0) { + return metadataId; + } + + return null; +} + +function getCurrentSceneId({ + value, + scenes, +}: { + value: unknown; + scenes: unknown[]; +}): string { + if (typeof value === "string" && value.length > 0) { + return value; + } + + const mainSceneId = findMainSceneId({ scenes }); + if (mainSceneId) { + return mainSceneId; + } + + return ""; +} + +function findMainSceneId({ scenes }: { scenes: unknown[] }): string | null { + for (const scene of scenes) { + if (!isRecord(scene)) { + continue; + } + + if (scene.isMain === true && typeof scene.id === "string") { + return scene.id; + } + } + + for (const scene of scenes) { + if (!isRecord(scene)) { + continue; + } + + if (typeof scene.id === "string") { + return scene.id; + } + } + + return null; +} + +function applyLegacyBookmarks({ + scenes, + legacyBookmarks, +}: { + scenes: unknown[]; + legacyBookmarks: unknown[] | null; +}): unknown[] { + if (!legacyBookmarks || legacyBookmarks.length === 0) { + return scenes; + } + + const mainSceneId = findMainSceneId({ scenes }); + + return scenes.map((scene) => { + if (!isRecord(scene)) { + return scene; + } + + if (mainSceneId && scene.id !== mainSceneId) { + return scene; + } + + if (Array.isArray(scene.bookmarks) && scene.bookmarks.length > 0) { + return scene; + } + + return { + ...scene, + bookmarks: legacyBookmarks, + }; + }); +} + +function getBackgroundValue({ + value, + backgroundType, + backgroundColor, + blurIntensity, +}: { + value: unknown; + backgroundType?: unknown; + backgroundColor?: unknown; + blurIntensity?: unknown; +}): { + type: "color" | "blur"; + color?: string; + blurIntensity?: number; +} { + if (isRecord(value)) { + const typeValue = value.type; + if (typeValue === "blur") { + return { + type: "blur", + blurIntensity: getNumberValue({ + value: value.blurIntensity, + fallback: DEFAULT_BLUR_INTENSITY, + }), + }; + } + + return { + type: "color", + color: getStringValue({ value: value.color, fallback: DEFAULT_COLOR }), + }; + } + + if (backgroundType === "blur") { + return { + type: "blur", + blurIntensity: getNumberValue({ + value: blurIntensity, + fallback: DEFAULT_BLUR_INTENSITY, + }), + }; + } + + return { + type: "color", + color: getStringValue({ value: backgroundColor, fallback: DEFAULT_COLOR }), + }; +} + +function getCanvasSizeValue({ + value, + fallback, +}: { + value: unknown; + fallback: { width: number; height: number }; +}): { width: number; height: number } { + if (isRecord(value)) { + const width = getNumberValue({ + value: value.width, + fallback: fallback.width, + }); + const height = getNumberValue({ + value: value.height, + fallback: fallback.height, + }); + + return { width, height }; + } + + return fallback; +} + +function getNumberValue({ + value, + fallback, +}: { + value: unknown; + fallback: number; +}): number { + return typeof value === "number" ? value : fallback; +} + +function getStringValue({ + value, + fallback, +}: { + value: unknown; + fallback?: string; +}): string | undefined { + if (typeof value === "string") { + return value; + } + + return fallback; +} + +function normalizeDateString({ value }: { value: unknown }): string { + if (value instanceof Date) { + return value.toISOString(); + } + + if (typeof value === "string") { + return value; + } + + return new Date().toISOString(); +} + +function isV2Project({ project }: { project: ProjectRecord }): boolean { + const versionValue = project.version; + if (typeof versionValue === "number" && versionValue >= 2) { + return true; + } + + return isRecord(project.metadata) && isRecord(project.settings); +} + +function isRecord(value: unknown): value is ProjectRecord { + return typeof value === "object" && value !== null; +} |
