diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-13 04:40:38 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-13 05:01:51 +0200 |
| commit | 108d3f4ecad4c0ead14941c2067a21546750fedc (patch) | |
| tree | 8969775b3292ac2570b95e7848774958caadbc8b /apps/web/src/services | |
| parent | 8230faf082c405c9fd0fa640f4ce2284315782ae (diff) | |
| download | opencut-108d3f4ecad4c0ead14941c2067a21546750fedc.tar.gz | |
feat: position animated independently per axis
Made-with: Cursor
Diffstat (limited to 'apps/web/src/services')
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 }); + } +} |
