summaryrefslogtreecommitdiff
path: root/apps/web/src/timeline/scenes.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/scenes.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/scenes.ts')
-rw-r--r--apps/web/src/timeline/scenes.ts113
1 files changed, 113 insertions, 0 deletions
diff --git a/apps/web/src/timeline/scenes.ts b/apps/web/src/timeline/scenes.ts
new file mode 100644
index 00000000..d48c943c
--- /dev/null
+++ b/apps/web/src/timeline/scenes.ts
@@ -0,0 +1,113 @@
+import type { TScene } from "@/timeline";
+import { generateUUID } from "@/utils/id";
+import { calculateTotalDuration } from "@/timeline";
+import { MAIN_TRACK_NAME } from "@/timeline/placement/main-track";
+
+export function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null {
+ return scenes.find((scene) => scene.isMain) || null;
+}
+
+export function ensureMainScene({ scenes }: { scenes: TScene[] }): TScene[] {
+ const hasMain = scenes.some((scene) => scene.isMain);
+ if (!hasMain) {
+ const mainScene = buildDefaultScene({ name: "Main scene", isMain: true });
+ return [mainScene, ...scenes];
+ }
+ return scenes;
+}
+
+export function buildDefaultScene({
+ name,
+ isMain,
+}: {
+ name: string;
+ isMain: boolean;
+}): TScene {
+ return {
+ id: generateUUID(),
+ name,
+ isMain,
+ tracks: {
+ overlay: [],
+ main: {
+ id: generateUUID(),
+ name: MAIN_TRACK_NAME,
+ type: "video",
+ elements: [],
+ muted: false,
+ hidden: false,
+ },
+ audio: [],
+ },
+ bookmarks: [],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+}
+
+export function canDeleteScene({ scene }: { scene: TScene }): {
+ canDelete: boolean;
+ reason?: string;
+} {
+ if (scene.isMain) {
+ return { canDelete: false, reason: "Cannot delete main scene" };
+ }
+ return { canDelete: true };
+}
+
+export function getFallbackSceneAfterDelete({
+ scenes,
+ deletedSceneId,
+ currentSceneId,
+}: {
+ scenes: TScene[];
+ deletedSceneId: string;
+ currentSceneId: string | null;
+}): TScene | null {
+ if (currentSceneId !== deletedSceneId) {
+ return scenes.find((s) => s.id === currentSceneId) || null;
+ }
+ return getMainScene({ scenes });
+}
+
+export function findCurrentScene({
+ scenes,
+ currentSceneId,
+}: {
+ scenes: TScene[];
+ currentSceneId: string;
+}): TScene | null {
+ return (
+ scenes.find((s) => s.id === currentSceneId) ||
+ getMainScene({ scenes }) ||
+ scenes[0] ||
+ null
+ );
+}
+
+export function getProjectDurationFromScenes({
+ scenes,
+}: {
+ scenes: TScene[];
+}): number {
+ const mainScene = getMainScene({ scenes }) ?? scenes[0] ?? null;
+ if (!mainScene?.tracks) {
+ return 0;
+ }
+
+ return calculateTotalDuration({ tracks: mainScene.tracks });
+}
+
+export function updateSceneInArray({
+ scenes,
+ sceneId,
+ updates,
+}: {
+ scenes: TScene[];
+ sceneId: string;
+ updates: Partial<TScene>;
+}): TScene[] {
+ return scenes.map((scene) =>
+ scene.id === sceneId ? { ...scene, ...updates } : scene,
+ );
+}