fix: scenes now load properly
This commit is contained in:
parent
e2bd8d1e09
commit
af1964cf11
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[]> {
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export interface Scene {
|
|||
id: string;
|
||||
name: string;
|
||||
isMain: boolean;
|
||||
isBackground: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue