summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-13 04:40:38 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-13 05:01:51 +0200
commit108d3f4ecad4c0ead14941c2067a21546750fedc (patch)
tree8969775b3292ac2570b95e7848774958caadbc8b /apps/web/src/services/storage
parent8230faf082c405c9fd0fa640f4ce2284315782ae (diff)
downloadopencut-108d3f4ecad4c0ead14941c2067a21546750fedc.tar.gz
feat: position animated independently per axis
Made-with: Cursor
Diffstat (limited to 'apps/web/src/services/storage')
-rw-r--r--apps/web/src/services/storage/migrations/index.ts114
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v24-to-v25.ts140
-rw-r--r--apps/web/src/services/storage/migrations/v24-to-v25.ts16
3 files changed, 214 insertions, 56 deletions
diff --git a/apps/web/src/services/storage/migrations/index.ts b/apps/web/src/services/storage/migrations/index.ts
index b7ee37f5..bb2d9709 100644
--- a/apps/web/src/services/storage/migrations/index.ts
+++ b/apps/web/src/services/storage/migrations/index.ts
@@ -1,56 +1,58 @@
-export { StorageMigration } from "./base";
-import { V0toV1Migration } from "./v0-to-v1";
-import { V1toV2Migration } from "./v1-to-v2";
-import { V2toV3Migration } from "./v2-to-v3";
-import { V3toV4Migration } from "./v3-to-v4";
-import { V4toV5Migration } from "./v4-to-v5";
-import { V5toV6Migration } from "./v5-to-v6";
-import { V6toV7Migration } from "./v6-to-v7";
-import { V7toV8Migration } from "./v7-to-v8";
-import { V8toV9Migration } from "./v8-to-v9";
-import { V9toV10Migration } from "./v9-to-v10";
-import { V10toV11Migration } from "./v10-to-v11";
-import { V11toV12Migration } from "./v11-to-v12";
-import { V12toV13Migration } from "./v12-to-v13";
-import { V13toV14Migration } from "./v13-to-v14";
-import { V14toV15Migration } from "./v14-to-v15";
-import { V15toV16Migration } from "./v15-to-v16";
-import { V16toV17Migration } from "./v16-to-v17";
-import { V17toV18Migration } from "./v17-to-v18";
-import { V18toV19Migration } from "./v18-to-v19";
-import { V19toV20Migration } from "./v19-to-v20";
-import { V20toV21Migration } from "./v20-to-v21";
-import { V21toV22Migration } from "./v21-to-v22";
-import { V22toV23Migration } from "./v22-to-v23";
-import { V23toV24Migration } from "./v23-to-v24";
-export { runStorageMigrations } from "./runner";
-export type { MigrationProgress } from "./runner";
-
-export const CURRENT_PROJECT_VERSION = 24;
-
-export const migrations = [
- new V0toV1Migration(),
- new V1toV2Migration(),
- new V2toV3Migration(),
- new V3toV4Migration(),
- new V4toV5Migration(),
- new V5toV6Migration(),
- new V6toV7Migration(),
- new V7toV8Migration(),
- new V8toV9Migration(),
- new V9toV10Migration(),
- new V10toV11Migration(),
- new V11toV12Migration(),
- new V12toV13Migration(),
- new V13toV14Migration(),
- new V14toV15Migration(),
- new V15toV16Migration(),
- new V16toV17Migration(),
- new V17toV18Migration(),
- new V18toV19Migration(),
- new V19toV20Migration(),
- new V20toV21Migration(),
- new V21toV22Migration(),
- new V22toV23Migration(),
- new V23toV24Migration(),
-];
+export { StorageMigration } from "./base";
+import { V0toV1Migration } from "./v0-to-v1";
+import { V1toV2Migration } from "./v1-to-v2";
+import { V2toV3Migration } from "./v2-to-v3";
+import { V3toV4Migration } from "./v3-to-v4";
+import { V4toV5Migration } from "./v4-to-v5";
+import { V5toV6Migration } from "./v5-to-v6";
+import { V6toV7Migration } from "./v6-to-v7";
+import { V7toV8Migration } from "./v7-to-v8";
+import { V8toV9Migration } from "./v8-to-v9";
+import { V9toV10Migration } from "./v9-to-v10";
+import { V10toV11Migration } from "./v10-to-v11";
+import { V11toV12Migration } from "./v11-to-v12";
+import { V12toV13Migration } from "./v12-to-v13";
+import { V13toV14Migration } from "./v13-to-v14";
+import { V14toV15Migration } from "./v14-to-v15";
+import { V15toV16Migration } from "./v15-to-v16";
+import { V16toV17Migration } from "./v16-to-v17";
+import { V17toV18Migration } from "./v17-to-v18";
+import { V18toV19Migration } from "./v18-to-v19";
+import { V19toV20Migration } from "./v19-to-v20";
+import { V20toV21Migration } from "./v20-to-v21";
+import { V21toV22Migration } from "./v21-to-v22";
+import { V22toV23Migration } from "./v22-to-v23";
+import { V23toV24Migration } from "./v23-to-v24";
+import { V24toV25Migration } from "./v24-to-v25";
+export { runStorageMigrations } from "./runner";
+export type { MigrationProgress } from "./runner";
+
+export const CURRENT_PROJECT_VERSION = 25;
+
+export const migrations = [
+ new V0toV1Migration(),
+ new V1toV2Migration(),
+ new V2toV3Migration(),
+ new V3toV4Migration(),
+ new V4toV5Migration(),
+ new V5toV6Migration(),
+ new V6toV7Migration(),
+ new V7toV8Migration(),
+ new V8toV9Migration(),
+ new V9toV10Migration(),
+ new V10toV11Migration(),
+ new V11toV12Migration(),
+ new V12toV13Migration(),
+ new V13toV14Migration(),
+ new V14toV15Migration(),
+ new V15toV16Migration(),
+ new V16toV17Migration(),
+ new V17toV18Migration(),
+ new V18toV19Migration(),
+ new V19toV20Migration(),
+ new V20toV21Migration(),
+ new V21toV22Migration(),
+ new V22toV23Migration(),
+ new V23toV24Migration(),
+ new V24toV25Migration(),
+];
diff --git a/apps/web/src/services/storage/migrations/transformers/v24-to-v25.ts b/apps/web/src/services/storage/migrations/transformers/v24-to-v25.ts
new file mode 100644
index 00000000..b3b48f42
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/transformers/v24-to-v25.ts
@@ -0,0 +1,140 @@
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV24ToV25({
+ 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 >= 25) {
+ return { project, skipped: true, reason: "already v25" };
+ }
+ if (version !== 24) {
+ return { project, skipped: true, reason: "not v24" };
+ }
+
+ return {
+ project: {
+ ...migrateProject({ project }),
+ version: 25,
+ },
+ skipped: false,
+ };
+}
+
+function migrateProject({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ if (!Array.isArray(project.scenes)) {
+ return project;
+ }
+
+ return {
+ ...project,
+ scenes: project.scenes.map((scene) => migrateScene({ scene })),
+ };
+}
+
+function migrateScene({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene) || !isRecord(scene.tracks)) {
+ return scene;
+ }
+
+ const tracks = scene.tracks;
+ const nextTracks: ProjectRecord = { ...tracks };
+
+ if (isRecord(tracks.main)) {
+ nextTracks.main = migrateTrack({ track: tracks.main });
+ }
+
+ if (Array.isArray(tracks.overlay)) {
+ nextTracks.overlay = tracks.overlay.map((track) =>
+ migrateTrack({ track }),
+ );
+ }
+
+ if (Array.isArray(tracks.audio)) {
+ nextTracks.audio = tracks.audio.map((track) => migrateTrack({ track }));
+ }
+
+ return { ...scene, tracks: nextTracks };
+}
+
+function migrateTrack({ track }: { track: unknown }): unknown {
+ if (!isRecord(track) || !Array.isArray(track.elements)) {
+ return track;
+ }
+
+ return {
+ ...track,
+ elements: track.elements.map((element) => migrateElement({ element })),
+ };
+}
+
+function migrateElement({ element }: { element: unknown }): unknown {
+ if (!isRecord(element) || !isRecord(element.animations)) {
+ return element;
+ }
+
+ const nextAnimations = migrateAnimations({ animations: element.animations });
+ if (nextAnimations === element.animations) {
+ return element;
+ }
+
+ return { ...element, animations: nextAnimations };
+}
+
+function migrateAnimations({
+ animations,
+}: {
+ animations: ProjectRecord;
+}): ProjectRecord {
+ if (!isRecord(animations.bindings) || !isRecord(animations.channels)) {
+ return animations;
+ }
+
+ const positionBinding = animations.bindings["transform.position"];
+ if (!isRecord(positionBinding) || positionBinding.kind !== "vector2") {
+ return animations;
+ }
+
+ const xChannel = animations.channels["transform.position:x"];
+ const yChannel = animations.channels["transform.position:y"];
+
+ const nextBindings: ProjectRecord = { ...animations.bindings };
+ const nextChannels: ProjectRecord = { ...animations.channels };
+
+ delete nextBindings["transform.position"];
+ delete nextChannels["transform.position:x"];
+ delete nextChannels["transform.position:y"];
+
+ nextBindings["transform.positionX"] = {
+ path: "transform.positionX",
+ kind: "number",
+ components: [{ key: "value", channelId: "transform.positionX:value" }],
+ };
+ nextBindings["transform.positionY"] = {
+ path: "transform.positionY",
+ kind: "number",
+ components: [{ key: "value", channelId: "transform.positionY:value" }],
+ };
+
+ if (isRecord(xChannel)) {
+ nextChannels["transform.positionX:value"] = xChannel;
+ }
+ if (isRecord(yChannel)) {
+ nextChannels["transform.positionY:value"] = yChannel;
+ }
+
+ return { ...animations, bindings: nextBindings, channels: nextChannels };
+}
diff --git a/apps/web/src/services/storage/migrations/v24-to-v25.ts b/apps/web/src/services/storage/migrations/v24-to-v25.ts
new file mode 100644
index 00000000..2e2a6935
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/v24-to-v25.ts
@@ -0,0 +1,16 @@
+import { StorageMigration } from "./base";
+import type { ProjectRecord } from "./transformers/types";
+import { transformProjectV24ToV25 } from "./transformers/v24-to-v25";
+
+export class V24toV25Migration extends StorageMigration {
+ from = 24;
+ to = 25;
+
+ async transform(project: ProjectRecord): Promise<{
+ project: ProjectRecord;
+ skipped: boolean;
+ reason?: string;
+ }> {
+ return transformProjectV24ToV25({ project });
+ }
+}