feat: position animated independently per axis

Made-with: Cursor
This commit is contained in:
Maze Winther
2026-04-13 05:01:51 +02:00
parent 8230faf082
commit 108d3f4eca
12 changed files with 775 additions and 720 deletions
@@ -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(),
];
@@ -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 };
}
@@ -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 });
}
}