summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/web/src/components/providers/migrators/scenes-migrator.tsx358
-rw-r--r--apps/web/src/lib/storage/storage-service.ts5
-rw-r--r--apps/web/src/stores/project-store.ts15
-rw-r--r--apps/web/src/stores/scene-store.ts486
-rw-r--r--apps/web/src/stores/timeline-store.ts9
-rw-r--r--apps/web/src/types/project.ts1
6 files changed, 479 insertions, 395 deletions
diff --git a/apps/web/src/components/providers/migrators/scenes-migrator.tsx b/apps/web/src/components/providers/migrators/scenes-migrator.tsx
index 3a21faa4..521e24af 100644
--- a/apps/web/src/components/providers/migrators/scenes-migrator.tsx
+++ b/apps/web/src/components/providers/migrators/scenes-migrator.tsx
@@ -1,171 +1,187 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { usePathname } from "next/navigation";
-import { storageService } from "@/lib/storage/storage-service";
-import { TProject, Scene } from "@/types/project";
-import { generateUUID } from "@/lib/utils";
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Progress } from "@/components/ui/progress";
-import { createMainScene } from "@/stores/project-store";
-
-interface MigrationProgress {
- current: number;
- total: number;
- currentProjectName: string;
-}
-
-export function ScenesMigrator({ children }: { children: React.ReactNode }) {
- const pathname = usePathname();
- const [isMigrating, setIsMigrating] = useState(false);
- const [progress, setProgress] = useState<MigrationProgress>({
- current: 0,
- total: 0,
- currentProjectName: "",
- });
-
- const shouldCheckMigration =
- pathname.startsWith("/editor") || pathname.startsWith("/projects");
-
- useEffect(() => {
- if (!shouldCheckMigration) return;
-
- checkAndMigrateProjects();
- }, [shouldCheckMigration]);
-
- const checkAndMigrateProjects = async () => {
- try {
- const projects = await storageService.loadAllProjects();
- const legacyProjects = projects.filter(
- (project) => !project.scenes || project.scenes.length === 0
- );
-
- if (legacyProjects.length === 0) {
- // No migration needed
- return;
- }
-
- setIsMigrating(true);
- setProgress({
- current: 0,
- total: legacyProjects.length,
- currentProjectName: "",
- });
-
- // Migrate each legacy project
- for (let i = 0; i < legacyProjects.length; i++) {
- const project = legacyProjects[i];
-
- setProgress({
- current: i,
- total: legacyProjects.length,
- currentProjectName: project.name,
- });
-
- await migrateLegacyProject(project);
- }
-
- setProgress({
- current: legacyProjects.length,
- total: legacyProjects.length,
- currentProjectName: "Complete!",
- });
-
- setTimeout(() => {
- setIsMigrating(false);
- }, 1000);
- } catch (error) {
- console.error("Migration failed:", error);
- setIsMigrating(false);
- }
- };
-
- const migrateLegacyProject = async (project: TProject) => {
- try {
- const mainScene = createMainScene();
-
- const migratedProject: TProject = {
- ...project,
- scenes: [mainScene],
- currentSceneId: mainScene.id,
- updatedAt: new Date(),
- };
-
- // Load existing timeline data (legacy format)
- const legacyTimeline = await storageService.loadTimeline({
- projectId: project.id,
- });
-
- await storageService.saveProject({ project: migratedProject });
-
- // If timeline data, migrate it to the main scene
- if (legacyTimeline && legacyTimeline.length > 0) {
- await storageService.saveTimeline({
- projectId: project.id,
- tracks: legacyTimeline,
- sceneId: mainScene.id,
- });
- }
-
- // Clean up legacy timeline storage
- await storageService.deleteProjectTimeline({ projectId: project.id });
- } catch (error) {
- console.error(`Failed to migrate project ${project.name}:`, error);
- throw error;
- }
- };
-
- if (!shouldCheckMigration) {
- return children;
- }
-
- if (isMigrating) {
- const progressPercent =
- progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
-
- return (
- <Dialog open={true}>
- <DialogContent
- className="sm:max-w-md"
- onPointerDownOutside={(e) => e.preventDefault()}
- >
- <DialogHeader>
- <DialogTitle>Updating Projects</DialogTitle>
- <DialogDescription>
- We're adding scene support to your projects. This will only take a
- moment.
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-4">
- <div className="space-y-2">
- <div className="flex justify-between text-sm">
- <span>Progress</span>
- <span>
- {progress.current} of {progress.total}
- </span>
- </div>
- <Progress value={progressPercent} className="w-full" />
- </div>
-
- {progress.currentProjectName && (
- <div className="text-sm text-muted-foreground">
- {progress.current < progress.total
- ? `Updating: ${progress.currentProjectName}`
- : progress.currentProjectName}
- </div>
- )}
- </div>
- </DialogContent>
- </Dialog>
- );
- }
-
- return children;
-}
+"use client";
+
+import { useEffect, useState } from "react";
+import { usePathname } from "next/navigation";
+import { storageService } from "@/lib/storage/storage-service";
+import { TProject, Scene } from "@/types/project";
+import { generateUUID } from "@/lib/utils";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Progress } from "@/components/ui/progress";
+import { createMainScene } from "@/stores/project-store";
+
+interface MigrationProgress {
+ current: number;
+ total: number;
+ currentProjectName: string;
+}
+
+export function ScenesMigrator({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname();
+ const [isMigrating, setIsMigrating] = useState(false);
+ const [progress, setProgress] = useState<MigrationProgress>({
+ current: 0,
+ total: 0,
+ currentProjectName: "",
+ });
+
+ const shouldCheckMigration =
+ pathname.startsWith("/editor") || pathname.startsWith("/projects");
+
+ useEffect(() => {
+ if (!shouldCheckMigration) return;
+
+ checkAndMigrateProjects();
+ }, [shouldCheckMigration]);
+
+ const checkAndMigrateProjects = async () => {
+ try {
+ const projects = await storageService.loadAllProjects();
+ const legacyProjects = projects.filter(
+ (project) => !project.scenes || project.scenes.length === 0
+ );
+
+ if (legacyProjects.length === 0) {
+ // No migration needed
+ return;
+ }
+
+ setIsMigrating(true);
+ setProgress({
+ current: 0,
+ total: legacyProjects.length,
+ currentProjectName: "",
+ });
+
+ // Migrate each legacy project
+ for (let i = 0; i < legacyProjects.length; i++) {
+ const project = legacyProjects[i];
+
+ setProgress({
+ current: i,
+ total: legacyProjects.length,
+ currentProjectName: project.name,
+ });
+
+ await migrateLegacyProject(project);
+ }
+
+ setProgress({
+ current: legacyProjects.length,
+ total: legacyProjects.length,
+ currentProjectName: "Complete!",
+ });
+
+ setTimeout(() => {
+ setIsMigrating(false);
+ }, 1000);
+ } catch (error) {
+ console.error("Migration failed:", error);
+ setIsMigrating(false);
+ }
+ };
+
+ const migrateLegacyProject = async (project: TProject) => {
+ try {
+ const mainScene: Scene = {
+ id: generateUUID(),
+ name: "Main Scene",
+ isMain: true,
+ isBackground: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+
+ const backgroundScene: Scene = {
+ id: generateUUID(),
+ name: "Background",
+ isMain: false,
+ isBackground: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+
+ const migratedProject: TProject = {
+ ...project,
+ scenes: [mainScene, backgroundScene],
+ currentSceneId: mainScene.id,
+ updatedAt: new Date(),
+ };
+
+ // Load existing timeline data (legacy format)
+ const legacyTimeline = await storageService.loadTimeline({
+ projectId: project.id,
+ });
+
+ await storageService.saveProject({ project: migratedProject });
+
+ // If timeline data, migrate it to the main scene
+ if (legacyTimeline && legacyTimeline.length > 0) {
+ await storageService.saveTimeline({
+ projectId: project.id,
+ tracks: legacyTimeline,
+ sceneId: mainScene.id,
+ });
+ }
+
+ // Clean up legacy timeline storage
+ await storageService.deleteProjectTimeline({ projectId: project.id });
+ } catch (error) {
+ console.error(`Failed to migrate project ${project.name}:`, error);
+ throw error;
+ }
+ };
+
+ if (!shouldCheckMigration) {
+ return children;
+ }
+
+ if (isMigrating) {
+ const progressPercent =
+ progress.total > 0 ? (progress.current / progress.total) * 100 : 0;
+
+ return (
+ <Dialog open={true}>
+ <DialogContent
+ className="sm:max-w-md"
+ onPointerDownOutside={(e) => e.preventDefault()}
+ >
+ <DialogHeader>
+ <DialogTitle>Updating Projects</DialogTitle>
+ <DialogDescription>
+ We're adding scene support to your projects. This will only take a
+ moment.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <div className="flex justify-between text-sm">
+ <span>Progress</span>
+ <span>
+ {progress.current} of {progress.total}
+ </span>
+ </div>
+ <Progress value={progressPercent} className="w-full" />
+ </div>
+
+ {progress.currentProjectName && (
+ <div className="text-sm text-muted-foreground">
+ {progress.current < progress.total
+ ? `Updating: ${progress.currentProjectName}`
+ : progress.currentProjectName}
+ </div>
+ )}
+ </div>
+ </DialogContent>
+ </Dialog>
+ );
+ }
+
+ return children;
+}
diff --git a/apps/web/src/lib/storage/storage-service.ts b/apps/web/src/lib/storage/storage-service.ts
index 80f10d3d..112d09b6 100644
--- a/apps/web/src/lib/storage/storage-service.ts
+++ b/apps/web/src/lib/storage/storage-service.ts
@@ -78,6 +78,7 @@ class StorageService {
id: scene.id,
name: scene.name,
isMain: scene.isMain,
+ isBackground: scene.isBackground,
createdAt: scene.createdAt.toISOString(),
updatedAt: scene.updatedAt.toISOString(),
}));
@@ -113,12 +114,13 @@ class StorageService {
id: scene.id,
name: scene.name,
isMain: scene.isMain,
+ isBackground: scene.isBackground || false, // Default for legacy scenes
createdAt: new Date(scene.createdAt),
updatedAt: new Date(scene.updatedAt),
})) || [];
// Convert back to TProject format
- return {
+ const project = {
id: serializedProject.id,
name: serializedProject.name,
thumbnail: serializedProject.thumbnail,
@@ -134,6 +136,7 @@ class StorageService {
canvasSize: serializedProject.canvasSize,
canvasMode: serializedProject.canvasMode,
};
+ return project;
}
async loadAllProjects(): Promise<TProject[]> {
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts
index 70c11b28..0c67be9f 100644
--- a/apps/web/src/stores/project-store.ts
+++ b/apps/web/src/stores/project-store.ts
@@ -4,7 +4,7 @@ import { storageService } from "@/lib/storage/storage-service";
import { toast } from "sonner";
import { useMediaStore } from "./media-store";
import { useTimelineStore } from "./timeline-store";
-import { useSceneStore } from "./scene-store";
+import { createBackgroundScene, useSceneStore } from "./scene-store";
import { generateUUID } from "@/lib/utils";
import { CanvasSize, CanvasMode } from "@/types/editor";
@@ -16,6 +16,7 @@ export function createMainScene(): Scene {
id: generateUUID(),
name: "Main scene",
isMain: true,
+ isBackground: false,
createdAt: new Date(),
updatedAt: new Date(),
};
@@ -23,13 +24,15 @@ export function createMainScene(): Scene {
const createDefaultProject = (name: string): TProject => {
const mainScene = createMainScene();
+ const backgroundScene = createBackgroundScene();
+
return {
id: generateUUID(),
name,
thumbnail: "",
createdAt: new Date(),
updatedAt: new Date(),
- scenes: [mainScene],
+ scenes: [mainScene, backgroundScene],
currentSceneId: mainScene.id,
backgroundColor: "#000000",
backgroundType: "color",
@@ -226,15 +229,19 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
if (project) {
set({ activeProject: project });
+ let currentScene = null;
if (project.scenes && project.scenes.length > 0) {
sceneStore.initializeScenes({
scenes: project.scenes,
currentSceneId: project.currentSceneId,
});
+ // Get current scene directly from project data (don't rely on store state)
+ currentScene =
+ project.scenes.find((s) => s.id === project.currentSceneId) ||
+ project.scenes.find((s) => s.isMain) ||
+ project.scenes[0];
}
- const currentScene = sceneStore.currentScene;
-
await Promise.all([
mediaStore.loadProjectMedia(id),
timelineStore.loadProjectTimeline({
diff --git a/apps/web/src/stores/scene-store.ts b/apps/web/src/stores/scene-store.ts
index e16185e9..1b789427 100644
--- a/apps/web/src/stores/scene-store.ts
+++ b/apps/web/src/stores/scene-store.ts
@@ -1,219 +1,267 @@
-import { create } from "zustand";
-import { Scene } from "@/types/project";
-import { useProjectStore } from "./project-store";
-import { useTimelineStore } from "./timeline-store";
-import { storageService } from "@/lib/storage/storage-service";
-import { generateUUID } from "@/lib/utils";
-
-export function getMainScene({ scenes }: { scenes: Scene[] }): Scene | null {
- return scenes.find((scene) => scene.isMain) || null;
-}
-
-interface SceneStore {
- // Current scene state
- currentScene: Scene | null;
- scenes: Scene[];
-
- // Scene management
- createScene: ({
- name,
- isMain,
- }: {
- name: string;
- isMain: boolean;
- }) => Promise<string>;
- renameScene: ({
- sceneId,
- name,
- }: {
- sceneId: string;
- name: string;
- }) => Promise<void>;
- switchToScene: ({ sceneId }: { sceneId: string }) => Promise<void>;
-
- // Scene utilities
- getMainScene: () => Scene | null;
- getCurrentScene: () => Scene | null;
-
- // Project integration
- loadProjectScenes: ({ projectId }: { projectId: string }) => Promise<void>;
- initializeScenes: ({
- scenes,
- currentSceneId,
- }: {
- scenes: Scene[];
- currentSceneId?: string;
- }) => void;
- clearScenes: () => void;
-}
-
-export const useSceneStore = create<SceneStore>((set, get) => ({
- currentScene: null,
- scenes: [],
-
- createScene: async ({ name, isMain = false }) => {
- const { scenes } = get();
-
- const newScene = {
- id: generateUUID(),
- name,
- isMain,
- createdAt: new Date(),
- updatedAt: new Date(),
- };
- const updatedScenes = [...scenes, newScene];
-
- const projectStore = useProjectStore.getState();
- const { activeProject } = projectStore;
-
- if (!activeProject) {
- throw new Error("No active project");
- }
-
- const updatedProject = {
- ...activeProject,
- scenes: updatedScenes,
- updatedAt: new Date(),
- };
-
- try {
- await storageService.saveProject({ project: updatedProject });
- useProjectStore.setState({ activeProject: updatedProject });
- set({ scenes: updatedScenes });
- return newScene.id;
- } catch (error) {
- console.error("Failed to create scene:", error);
- throw error;
- }
- },
-
- renameScene: async ({ sceneId, name }: { sceneId: string; name: string }) => {
- const { scenes } = get();
- const updatedScenes = scenes.map((scene) =>
- scene.id === sceneId ? { ...scene, name, updatedAt: new Date() } : scene
- );
-
- // Update project
- const projectStore = useProjectStore.getState();
- const { activeProject } = projectStore;
-
- if (!activeProject) {
- throw new Error("No active project");
- }
-
- const updatedProject = {
- ...activeProject,
- scenes: updatedScenes,
- updatedAt: new Date(),
- };
-
- try {
- await storageService.saveProject({ project: updatedProject });
- useProjectStore.setState({ activeProject: updatedProject });
- set({
- scenes: updatedScenes,
- currentScene: updatedScenes.find((s) => s.id === sceneId) || null,
- });
- } catch (error) {
- console.error("Failed to rename scene:", error);
- throw error;
- }
- },
-
- switchToScene: async ({ sceneId }: { sceneId: string }) => {
- const { scenes } = get();
- const targetScene = scenes.find((s) => s.id === sceneId);
-
- if (!targetScene) {
- throw new Error("Scene not found");
- }
-
- const timelineStore = useTimelineStore.getState();
- const projectStore = useProjectStore.getState();
- const { activeProject } = projectStore;
- const { currentScene } = get();
-
- if (activeProject && currentScene) {
- await timelineStore.saveProjectTimeline({
- projectId: activeProject.id,
- sceneId: currentScene.id,
- });
- }
-
- if (activeProject) {
- await timelineStore.loadProjectTimeline({
- projectId: activeProject.id,
- sceneId,
- });
-
- const updatedProject = {
- ...activeProject,
- currentSceneId: sceneId,
- updatedAt: new Date(),
- };
-
- await storageService.saveProject({ project: updatedProject });
- useProjectStore.setState({ activeProject: updatedProject });
- }
-
- set({ currentScene: targetScene });
- },
-
- getMainScene: () => {
- const { scenes } = get();
- return scenes.find((scene) => scene.isMain) || null;
- },
-
- getCurrentScene: () => {
- return get().currentScene;
- },
-
- loadProjectScenes: async ({ projectId }: { projectId: string }) => {
- try {
- const project = await storageService.loadProject({ id: projectId });
- if (project?.scenes) {
- const ensuredScenes = project.scenes.map((scene) => ({
- ...scene,
- isMain: scene.isMain || false,
- }));
- const currentScene =
- ensuredScenes.find((s) => s.id === project.currentSceneId) ||
- ensuredScenes[0];
-
- set({
- scenes: ensuredScenes,
- currentScene,
- });
- }
- } catch (error) {
- console.error("Failed to load project scenes:", error);
- set({ scenes: [], currentScene: null });
- }
- },
-
- initializeScenes: ({
- scenes,
- currentSceneId,
- }: {
- scenes: Scene[];
- currentSceneId?: string;
- }) => {
- const currentScene = currentSceneId
- ? scenes.find((s) => s.id === currentSceneId)
- : null;
-
- const fallbackScene = getMainScene({ scenes });
-
- set({
- scenes,
- currentScene: currentScene || fallbackScene,
- });
- },
-
- clearScenes: () => {
- set({
- scenes: [],
- currentScene: null,
- });
- },
-}));
+import { create } from "zustand";
+import { Scene } from "@/types/project";
+import { useProjectStore } from "./project-store";
+import { useTimelineStore } from "./timeline-store";
+import { storageService } from "@/lib/storage/storage-service";
+import { generateUUID } from "@/lib/utils";
+
+export function getMainScene({ scenes }: { scenes: Scene[] }): Scene | null {
+ return scenes.find((scene) => scene.isMain) || null;
+}
+
+export function getBackgroundScene({
+ scenes,
+}: {
+ scenes: Scene[];
+}): Scene | null {
+ return scenes.find((scene) => scene.isBackground) || null;
+}
+
+export function createBackgroundScene(): Scene {
+ return {
+ id: generateUUID(),
+ name: "Background",
+ isMain: false,
+ isBackground: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+}
+
+function ensureBackgroundScene({ scenes }: { scenes: Scene[] }): Scene[] {
+ const hasBackground = scenes.some((scene) => scene.isBackground);
+ if (!hasBackground) {
+ return [...scenes, createBackgroundScene()];
+ }
+ return scenes;
+}
+
+interface SceneStore {
+ // Current scene state
+ currentScene: Scene | null;
+ scenes: Scene[];
+
+ // Scene management
+ createScene: ({
+ name,
+ isMain,
+ }: {
+ name: string;
+ isMain: boolean;
+ }) => Promise<string>;
+ renameScene: ({
+ sceneId,
+ name,
+ }: {
+ sceneId: string;
+ name: string;
+ }) => Promise<void>;
+ switchToScene: ({ sceneId }: { sceneId: string }) => Promise<void>;
+
+ // Scene utilities
+ getMainScene: () => Scene | null;
+ getCurrentScene: () => Scene | null;
+
+ // Project integration
+ loadProjectScenes: ({ projectId }: { projectId: string }) => Promise<void>;
+ initializeScenes: ({
+ scenes,
+ currentSceneId,
+ }: {
+ scenes: Scene[];
+ currentSceneId?: string;
+ }) => void;
+ clearScenes: () => void;
+}
+
+export const useSceneStore = create<SceneStore>((set, get) => ({
+ currentScene: null,
+ scenes: [],
+
+ createScene: async ({ name, isMain = false }) => {
+ const { scenes } = get();
+
+ const newScene = {
+ id: generateUUID(),
+ name,
+ isMain,
+ isBackground: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+ const updatedScenes = [...scenes, newScene];
+
+ const projectStore = useProjectStore.getState();
+ const { activeProject } = projectStore;
+
+ if (!activeProject) {
+ throw new Error("No active project");
+ }
+
+ const updatedProject = {
+ ...activeProject,
+ scenes: updatedScenes,
+ updatedAt: new Date(),
+ };
+
+ try {
+ await storageService.saveProject({ project: updatedProject });
+ useProjectStore.setState({ activeProject: updatedProject });
+ set({ scenes: updatedScenes });
+ return newScene.id;
+ } catch (error) {
+ console.error("Failed to create scene:", error);
+ throw error;
+ }
+ },
+
+ renameScene: async ({ sceneId, name }: { sceneId: string; name: string }) => {
+ const { scenes } = get();
+ const updatedScenes = scenes.map((scene) =>
+ scene.id === sceneId ? { ...scene, name, updatedAt: new Date() } : scene
+ );
+
+ // Update project
+ const projectStore = useProjectStore.getState();
+ const { activeProject } = projectStore;
+
+ if (!activeProject) {
+ throw new Error("No active project");
+ }
+
+ const updatedProject = {
+ ...activeProject,
+ scenes: updatedScenes,
+ updatedAt: new Date(),
+ };
+
+ try {
+ await storageService.saveProject({ project: updatedProject });
+ useProjectStore.setState({ activeProject: updatedProject });
+ set({
+ scenes: updatedScenes,
+ currentScene: updatedScenes.find((s) => s.id === sceneId) || null,
+ });
+ } catch (error) {
+ console.error("Failed to rename scene:", error);
+ throw error;
+ }
+ },
+
+ switchToScene: async ({ sceneId }: { sceneId: string }) => {
+ const { scenes } = get();
+ const targetScene = scenes.find((s) => s.id === sceneId);
+
+ if (!targetScene) {
+ throw new Error("Scene not found");
+ }
+
+ const timelineStore = useTimelineStore.getState();
+ const projectStore = useProjectStore.getState();
+ const { activeProject } = projectStore;
+ const { currentScene } = get();
+
+ if (activeProject && currentScene) {
+ await timelineStore.saveProjectTimeline({
+ projectId: activeProject.id,
+ sceneId: currentScene.id,
+ });
+ }
+
+ if (activeProject) {
+ await timelineStore.loadProjectTimeline({
+ projectId: activeProject.id,
+ sceneId,
+ });
+
+ const updatedProject = {
+ ...activeProject,
+ currentSceneId: sceneId,
+ updatedAt: new Date(),
+ };
+
+ await storageService.saveProject({ project: updatedProject });
+ useProjectStore.setState({ activeProject: updatedProject });
+ }
+
+ set({ currentScene: targetScene });
+ },
+
+ getMainScene: () => {
+ const { scenes } = get();
+ return scenes.find((scene) => scene.isMain) || null;
+ },
+
+ getCurrentScene: () => {
+ return get().currentScene;
+ },
+
+ loadProjectScenes: async ({ projectId }: { projectId: string }) => {
+ try {
+ const project = await storageService.loadProject({ id: projectId });
+ if (project?.scenes) {
+ const ensuredScenes = project.scenes.map((scene) => ({
+ ...scene,
+ isMain: scene.isMain || false,
+ }));
+ const currentScene =
+ ensuredScenes.find((s) => s.id === project.currentSceneId) ||
+ ensuredScenes[0];
+
+ set({
+ scenes: ensuredScenes,
+ currentScene,
+ });
+ }
+ } catch (error) {
+ console.error("Failed to load project scenes:", error);
+ set({ scenes: [], currentScene: null });
+ }
+ },
+
+ initializeScenes: ({
+ scenes,
+ currentSceneId,
+ }: {
+ scenes: Scene[];
+ currentSceneId?: string;
+ }) => {
+ const ensuredScenes = ensureBackgroundScene({ scenes });
+ const currentScene = currentSceneId
+ ? ensuredScenes.find((s) => s.id === currentSceneId)
+ : null;
+
+ const fallbackScene = getMainScene({ scenes: ensuredScenes });
+
+ set({
+ scenes: ensuredScenes,
+ currentScene: currentScene || fallbackScene,
+ });
+
+ if (ensuredScenes.length > scenes.length) {
+ const projectStore = useProjectStore.getState();
+ const { activeProject } = projectStore;
+
+ if (activeProject) {
+ const updatedProject = {
+ ...activeProject,
+ scenes: ensuredScenes,
+ updatedAt: new Date(),
+ };
+
+ storageService.saveProject({ project: updatedProject }).then(() => {
+ useProjectStore.setState({ activeProject: updatedProject });
+ }).catch(error => {
+ console.error("Failed to save project with background scene:", error);
+ });
+ }
+ }
+ },
+
+ clearScenes: () => {
+ set({
+ scenes: [],
+ currentScene: null,
+ });
+ },
+}));
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
index 8caa71a7..24b6eca3 100644
--- a/apps/web/src/stores/timeline-store.ts
+++ b/apps/web/src/stores/timeline-store.ts
@@ -260,6 +260,15 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
} catch (error) {
console.error("Failed to auto-save timeline:", error);
}
+ } else {
+ console.warn(
+ "Auto-save skipped - missing activeProject or currentScene:",
+ {
+ hasProject: !!activeProject,
+ hasScene: !!currentScene,
+ sceneName: currentScene?.name,
+ }
+ );
}
};
diff --git a/apps/web/src/types/project.ts b/apps/web/src/types/project.ts
index f182653d..7fa53c69 100644
--- a/apps/web/src/types/project.ts
+++ b/apps/web/src/types/project.ts
@@ -6,6 +6,7 @@ export interface Scene {
id: string;
name: string;
isMain: boolean;
+ isBackground: boolean;
createdAt: Date;
updatedAt: Date;
}