From 108d3f4ecad4c0ead14941c2067a21546750fedc Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 13 Apr 2026 04:40:38 +0200 Subject: feat: position animated independently per axis Made-with: Cursor --- apps/web/src/services/storage/migrations/index.ts | 114 ++++++++--------- .../storage/migrations/transformers/v24-to-v25.ts | 140 +++++++++++++++++++++ .../src/services/storage/migrations/v24-to-v25.ts | 16 +++ 3 files changed, 214 insertions(+), 56 deletions(-) create mode 100644 apps/web/src/services/storage/migrations/transformers/v24-to-v25.ts create mode 100644 apps/web/src/services/storage/migrations/v24-to-v25.ts (limited to 'apps/web/src/services') 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 { + 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 }); + } +} -- cgit v1.3.1