summaryrefslogtreecommitdiff
path: root/apps/web/src/timeline/components/expanded-layout.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/timeline/components/expanded-layout.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/timeline/components/expanded-layout.ts')
-rw-r--r--apps/web/src/timeline/components/expanded-layout.ts117
1 files changed, 117 insertions, 0 deletions
diff --git a/apps/web/src/timeline/components/expanded-layout.ts b/apps/web/src/timeline/components/expanded-layout.ts
new file mode 100644
index 00000000..fae0e50c
--- /dev/null
+++ b/apps/web/src/timeline/components/expanded-layout.ts
@@ -0,0 +1,117 @@
+import type {
+ AnimationPath,
+ ElementAnimations,
+} from "@/animation/types";
+import type { TimelineTrack } from "@/timeline";
+import { getElementKeyframes } from "@/animation";
+import { KEYFRAME_LANE_HEIGHT_PX } from "./layout";
+
+export interface ExpandedRow {
+ propertyPath: AnimationPath;
+ label: string;
+}
+
+interface PropertyGroupDefinition {
+ matchesPath: (path: AnimationPath) => boolean;
+}
+
+const PROPERTY_GROUPS: PropertyGroupDefinition[] = [
+ { matchesPath: (path) => path.startsWith("transform.") || path === "opacity" },
+ { matchesPath: (path) => path === "volume" || path === "color" },
+ { matchesPath: (path) => path.startsWith("background.") },
+ { matchesPath: (path) => path.startsWith("params.") },
+ { matchesPath: (path) => path.startsWith("effects.") },
+];
+
+const PROPERTY_LABELS: Partial<Record<string, string>> = {
+ "transform.positionX": "Position X",
+ "transform.positionY": "Position Y",
+ "transform.scaleX": "Scale X",
+ "transform.scaleY": "Scale Y",
+ "transform.rotate": "Rotation",
+ opacity: "Opacity",
+ volume: "Volume",
+ color: "Color",
+ "background.color": "BG Color",
+ "background.paddingX": "BG Pad X",
+ "background.paddingY": "BG Pad Y",
+ "background.offsetX": "BG Offset X",
+ "background.offsetY": "BG Offset Y",
+ "background.cornerRadius": "Corner Radius",
+};
+
+export function getPropertyLabel(path: AnimationPath): string {
+ if (PROPERTY_LABELS[path]) return PROPERTY_LABELS[path];
+ if (path.startsWith("params.")) return path.slice("params.".length);
+ if (path.startsWith("effects.")) {
+ const parts = path.split(".");
+ return parts[parts.length - 1];
+ }
+ return path;
+}
+
+export function getExpandedRows({
+ animations,
+}: {
+ animations: ElementAnimations | undefined;
+}): ExpandedRow[] {
+ const keyframes = getElementKeyframes({ animations });
+ const propertyPaths = [...new Set(keyframes.map((kf) => kf.propertyPath))];
+ if (propertyPaths.length === 0) return [];
+
+ const rows: ExpandedRow[] = [];
+
+ for (const group of PROPERTY_GROUPS) {
+ const groupPaths = propertyPaths.filter((path) =>
+ group.matchesPath(path),
+ );
+ for (const path of groupPaths) {
+ rows.push({ propertyPath: path, label: getPropertyLabel(path) });
+ }
+ }
+
+ return rows;
+}
+
+export function getExpansionHeight({ rows }: { rows: ExpandedRow[] }): number {
+ return rows.length * KEYFRAME_LANE_HEIGHT_PX;
+}
+
+export function computeTrackExpansionHeight({
+ track,
+ expandedElementIds,
+}: {
+ track: TimelineTrack;
+ expandedElementIds: Set<string>;
+}): number {
+ let maxHeight = 0;
+ for (const element of track.elements) {
+ if (!expandedElementIds.has(element.id)) continue;
+ const rows = getExpandedRows({ animations: element.animations });
+ maxHeight = Math.max(maxHeight, getExpansionHeight({ rows }));
+ }
+ return maxHeight;
+}
+
+export function getTrackExpandedRows({
+ track,
+ expandedElementIds,
+}: {
+ track: TimelineTrack;
+ expandedElementIds: Set<string>;
+}): ExpandedRow[] {
+ let maxHeight = 0;
+ let maxRows: ExpandedRow[] = [];
+
+ for (const element of track.elements) {
+ if (!expandedElementIds.has(element.id)) continue;
+ const rows = getExpandedRows({ animations: element.animations });
+ const height = getExpansionHeight({ rows });
+ if (height > maxHeight) {
+ maxHeight = height;
+ maxRows = rows;
+ }
+ }
+
+ return maxRows;
+}