summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/migrations/transformers/v25-to-v26.ts
blob: f00383e6c654f4e4e4afb4d4b420467ca5b73881 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";

export function transformProjectV25ToV26({
	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 >= 26) {
		return { project, skipped: true, reason: "already v26" };
	}
	if (version !== 25) {
		return { project, skipped: true, reason: "not v25" };
	}

	return {
		project: {
			...migrateProject({ project }),
			version: 26,
		},
		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)) {
		return scene;
	}

	const tracks = scene.tracks;

	// Already in the correct shape — nothing to repair
	if (isRecord(tracks) && Array.isArray(tracks.overlay) && Array.isArray(tracks.audio)) {
		return scene;
	}

	// Reconstruct the flat track array from whatever broken state it's in.
	// v24-to-v25 spread a flat array into a numeric-keyed object, so
	// Object.values recovers the original elements. A true flat array is
	// also handled for safety.
	let trackArray: unknown[];
	if (Array.isArray(tracks)) {
		trackArray = tracks;
	} else if (isRecord(tracks)) {
		trackArray = Object.values(tracks);
	} else {
		trackArray = [];
	}

	const mainTrack = findMainTrack({ tracks: trackArray });
	const finalMainTrack = mainTrack ?? buildEmptyMainTrack();

	return {
		...scene,
		tracks: {
			main: migrateTrack({ track: finalMainTrack }),
			overlay: trackArray
				.filter((track) => track !== mainTrack)
				.map((track) => migrateTrack({ track }))
				.filter(
					(track): track is ProjectRecord =>
						isRecord(track) && track.type !== "audio",
				),
			audio: trackArray
				.map((track) => migrateTrack({ track }))
				.filter(
					(track): track is ProjectRecord =>
						isRecord(track) && track.type === "audio",
				),
		},
	};
}

function findMainTrack({
	tracks,
}: {
	tracks: unknown[];
}): ProjectRecord | null {
	for (const track of tracks) {
		if (!isRecord(track)) continue;
		if (track.type === "video" && track.isMain === true) return track;
	}
	for (const track of tracks) {
		if (!isRecord(track)) continue;
		if (track.type === "video") return track;
	}
	return null;
}

function migrateTrack({ track }: { track: unknown }): unknown {
	if (!isRecord(track)) return track;
	const nextTrack = { ...track };
	delete nextTrack.isMain;
	return nextTrack;
}

function buildEmptyMainTrack(): ProjectRecord {
	return {
		id: crypto.randomUUID(),
		name: "Main",
		type: "video",
		elements: [],
		muted: false,
		hidden: false,
	};
}