feat: remove default background scene and add scene management

This commit is contained in:
Maze Winther 2025-09-01 22:15:32 +02:00
parent b84e699e9c
commit d8f446580c
13 changed files with 364 additions and 144 deletions

View File

@ -14,6 +14,7 @@ import { cn } from "@/lib/utils";
import { formatTimeCode } from "@/lib/time";
import { EditableTimecode } from "@/components/ui/editable-timecode";
import { useFrameCache } from "@/hooks/use-frame-cache";
import { useSceneStore } from "@/stores/scene-store";
import {
DEFAULT_CANVAS_SIZE,
DEFAULT_FPS,
@ -43,6 +44,7 @@ export function PreviewPanel() {
const { currentTime, toggle, setCurrentTime } = usePlaybackStore();
const { isPlaying, volume, muted } = usePlaybackStore();
const { activeProject } = useProjectStore();
const { currentScene } = useSceneStore();
const previewRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const { getCachedFrame, cacheFrame, invalidateCache, preRenderNearbyFrames } =
@ -219,11 +221,10 @@ export function PreviewPanel() {
};
}, [dragState, previewDimensions, canvasSize, updateTextElement]);
// Invalidate cache when timeline changes
// Clear the frame cache when background settings change since they affect rendering
useEffect(() => {
invalidateCache();
}, [
tracks,
mediaFiles,
activeProject?.backgroundColor,
activeProject?.backgroundType,
@ -494,7 +495,8 @@ export function PreviewPanel() {
currentTime,
tracks,
mediaFiles,
activeProject
activeProject,
currentScene?.id
);
if (cachedFrame) {
mainCtx.putImageData(cachedFrame, 0, 0);
@ -532,7 +534,9 @@ export function PreviewPanel() {
});
return tempCtx.getImageData(0, 0, displayWidth, displayHeight);
}
},
currentScene?.id,
3
);
} else {
// Small lookahead while playing
@ -567,6 +571,7 @@ export function PreviewPanel() {
return tempCtx.getImageData(0, 0, displayWidth, displayHeight);
},
currentScene?.id,
1
);
}
@ -644,7 +649,14 @@ export function PreviewPanel() {
displayWidth,
displayHeight
);
cacheFrame(currentTime, imageData, tracks, mediaFiles, activeProject);
cacheFrame(
currentTime,
imageData,
tracks,
mediaFiles,
activeProject,
currentScene?.id
);
// Blit offscreen to visible canvas
mainCtx.clearRect(0, 0, displayWidth, displayHeight);

View File

@ -1,64 +1,198 @@
"use client";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { useSceneStore } from "@/stores/scene-store";
import { Check } from "lucide-react";
export function ScenesView({ children }: { children: React.ReactNode }) {
const { scenes, currentScene, switchToScene } = useSceneStore();
const handleSceneSwitch = async (sceneId: string) => {
try {
await switchToScene({ sceneId });
} catch (error) {
console.error("Failed to switch scene:", error);
}
};
return (
<Sheet>
<SheetTrigger asChild>{children}</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Scenes</SheetTitle>
<SheetDescription>
Switch between scenes in your project
</SheetDescription>
</SheetHeader>
<div className="py-4">
{scenes.length === 0 ? (
<div className="text-sm text-muted-foreground">
No scenes available
</div>
) : (
<div className="space-y-2">
{scenes.map((scene) => (
<Button
key={scene.id}
variant={
currentScene?.id === scene.id ? "default" : "outline"
}
className="w-full justify-between"
onClick={() => handleSceneSwitch(scene.id)}
>
<span>{scene.name}</span>
{currentScene?.id === scene.id && (
<Check className="h-4 w-4" />
)}
</Button>
))}
</div>
)}
</div>
</SheetContent>
</Sheet>
);
}
"use client";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { useSceneStore } from "@/stores/scene-store";
import { Check, ListCheck, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
DialogTrigger,
} from "@/components/ui/dialog";
export function ScenesView({ children }: { children: React.ReactNode }) {
const { scenes, currentScene, switchToScene, deleteScene } = useSceneStore();
const [isSelectMode, setIsSelectMode] = useState(false);
const [selectedScenes, setSelectedScenes] = useState<Set<string>>(new Set());
const handleSceneSwitch = async (sceneId: string) => {
if (isSelectMode) {
toggleSceneSelection(sceneId);
return;
}
try {
await switchToScene({ sceneId });
} catch (error) {
console.error("Failed to switch scene:", error);
}
};
const toggleSceneSelection = (sceneId: string) => {
setSelectedScenes((prev) => {
const newSet = new Set(prev);
if (newSet.has(sceneId)) {
newSet.delete(sceneId);
} else {
newSet.add(sceneId);
}
return newSet;
});
};
const handleSelectMode = () => {
setIsSelectMode(!isSelectMode);
setSelectedScenes(new Set());
};
const handleDeleteSelected = async () => {
for (const sceneId of selectedScenes) {
const scene = scenes.find((s) => s.id === sceneId);
if (scene && !scene.isMain) {
try {
await deleteScene({ sceneId });
} catch (error) {
console.error("Failed to delete scene:", error);
}
}
}
setSelectedScenes(new Set());
setIsSelectMode(false);
};
return (
<Sheet>
<SheetTrigger asChild>{children}</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>
{isSelectMode ? `Select scenes (${selectedScenes.size})` : "Scenes"}
</SheetTitle>
<SheetDescription>
{isSelectMode
? "Select scenes to delete"
: "Switch between scenes in your project"}
</SheetDescription>
</SheetHeader>
<div className="py-4 flex flex-col gap-4">
<div className="flex items-center gap-2">
<Button
className="rounded-md"
variant={isSelectMode ? "default" : "outline"}
size="sm"
onClick={handleSelectMode}
>
<ListCheck />
{isSelectMode ? "Cancel" : "Select"}
</Button>
{isSelectMode && (
<DeleteDialog
count={selectedScenes.size}
onDelete={handleDeleteSelected}
disabled={Array.from(selectedScenes).some(
(id) => scenes.find((s) => s.id === id)?.isMain
)}
>
<Button className="rounded-md" variant="destructive" size="sm">
<Trash2 />
Delete ({selectedScenes.size})
</Button>
</DeleteDialog>
)}
</div>
{scenes.length === 0 ? (
<div className="text-sm text-muted-foreground">
No scenes available
</div>
) : (
<div className="space-y-2">
{scenes.map((scene) => (
<Button
key={scene.id}
variant="outline"
className={cn(
"w-full justify-between font-normal",
currentScene?.id === scene.id &&
!isSelectMode &&
"border-primary !text-primary",
isSelectMode &&
selectedScenes.has(scene.id) &&
"bg-accent border-foreground/30"
)}
onClick={() => handleSceneSwitch(scene.id)}
>
<span>{scene.name}</span>
<div className="flex items-center gap-2">
{((isSelectMode && selectedScenes.has(scene.id)) ||
(!isSelectMode && currentScene?.id === scene.id)) && (
<Check className="h-4 w-4" />
)}
</div>
</Button>
))}
</div>
)}
</div>
</SheetContent>
</Sheet>
);
}
function DeleteDialog({
count,
onDelete,
disabled,
children,
}: {
count: number;
onDelete: () => void;
disabled?: boolean;
children: React.ReactNode;
}) {
const [open, setOpen] = useState(false);
const handleDelete = () => {
onDelete();
setOpen(false);
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete Scenes</DialogTitle>
<DialogDescription>
Are you sure you want to delete {count} scene
{count === 1 ? "" : "s"}? This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
variant="destructive"
onClick={handleDelete}
disabled={disabled}
>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -5,6 +5,7 @@ import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
import { TimelineTrack } from "@/types/timeline";
import { MediaFile } from "@/types/media";
import { TProject } from "@/types/project";
import { useSceneStore } from "@/stores/scene-store";
interface CacheSegment {
startTime: number;
@ -22,7 +23,8 @@ interface TimelineCacheIndicatorProps {
time: number,
tracks: TimelineTrack[],
mediaFiles: MediaFile[],
activeProject: TProject | null
activeProject: TProject | null,
sceneId?: string
) => "cached" | "not-cached";
}
@ -34,6 +36,8 @@ export function TimelineCacheIndicator({
activeProject,
getRenderStatus,
}: TimelineCacheIndicatorProps) {
const { currentScene } = useSceneStore();
// Calculate cache segments by sampling the timeline
const calculateCacheSegments = (): CacheSegment[] => {
const segments: CacheSegment[] = [];
@ -49,7 +53,13 @@ export function TimelineCacheIndicator({
for (let i = 0; i <= totalSamples; i++) {
const time = i / sampleRate;
const cached =
getRenderStatus(time, tracks, mediaFiles, activeProject) === "cached";
getRenderStatus(
time,
tracks,
mediaFiles,
activeProject,
currentScene?.id
) === "cached";
if (!currentSegment) {
// Start first segment

View File

@ -67,7 +67,7 @@ export function TimelineToolbar({
} = useTimelineStore();
const { currentTime, duration, isPlaying, toggle, seek } = usePlaybackStore();
const { toggleBookmark, isBookmarked, activeProject } = useProjectStore();
const { currentScene } = useSceneStore();
const { scenes, currentScene } = useSceneStore();
const handleSplitSelected = () => {
if (selectedElements.length === 0) return;
@ -359,11 +359,11 @@ export function TimelineToolbar({
</TooltipProvider>
</div>
<div>
<SplitButton>
<SplitButton className="border border-foreground/10">
<SplitButtonLeft>{currentScene?.name || "No Scene"}</SplitButtonLeft>
<SplitButtonSeparator />
<ScenesView>
<SplitButtonRight onClick={() => {}}>
<SplitButtonRight disabled={scenes.length === 1} onClick={() => {}}>
<LayersIcon />
</SplitButtonRight>
</ScenesView>

View File

@ -92,23 +92,13 @@ export function ScenesMigrator({ children }: { children: React.ReactNode }) {
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],
scenes: [mainScene],
currentSceneId: mainScene.id,
updatedAt: new Date(),
};

View File

@ -16,7 +16,7 @@ const buttonVariants = cva(
"primary-gradient":
"bg-gradient-to-r from-cyan-400 to-blue-500 text-white hover:opacity-85 transition-opacity",
destructive:
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
"bg-destructive/0 border border-destructive/25 text-destructive shadow-xs hover:bg-destructive hover:text-destructive-foreground",
outline:
"border border-input bg-transparent shadow-xs hover:opacity-75 transition-opacity hover:text-accent-foreground",
secondary:

View File

@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-150 bg-black/20 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-250 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] p-6 z-150 grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-popover shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 rounded-lg",
"fixed left-[50%] top-[50%] p-6 z-250 grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-popover shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 rounded-lg",
className
)}
onCloseAutoFocus={(e) => {

View File

@ -39,7 +39,7 @@ const SplitButtonSide = forwardRef<
ref={ref}
variant="text"
className={cn(
"h-full rounded-none bg-panel-accent !opacity-100 border-0 gap-0 font-normal transition-colors",
"h-full rounded-none bg-panel-accent !opacity-100 border-0 gap-0 font-normal transition-colors disabled:text-muted-foreground",
onClick
? "hover:bg-foreground/10 hover:opacity-100 cursor-pointer"
: "cursor-default select-text",

View File

@ -37,7 +37,8 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
time: number,
tracks: TimelineTrack[],
mediaFiles: MediaFile[],
activeProject: TProject | null
activeProject: TProject | null,
sceneId?: string
): string => {
// Get elements that are active at this time
const activeElements: Array<{
@ -117,11 +118,13 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
canvasSize: activeProject?.canvasSize,
};
return JSON.stringify({
const hash = {
activeElements,
projectState,
time: Math.floor(time * cacheResolution) / cacheResolution, // Quantize time
});
sceneId,
time: Math.floor(time * cacheResolution) / cacheResolution,
};
return JSON.stringify(hash);
},
[cacheResolution]
);
@ -132,7 +135,8 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
time: number,
tracks: TimelineTrack[],
mediaFiles: MediaFile[],
activeProject: TProject | null
activeProject: TProject | null,
sceneId?: string
): boolean => {
const frameKey = Math.floor(time * cacheResolution);
const cached = frameCacheRef.current.get(frameKey);
@ -143,7 +147,8 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
time,
tracks,
mediaFiles,
activeProject
activeProject,
sceneId
);
return cached.timelineHash === currentHash;
},
@ -156,21 +161,33 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
time: number,
tracks: TimelineTrack[],
mediaFiles: MediaFile[],
activeProject: TProject | null
activeProject: TProject | null,
sceneId?: string
): ImageData | null => {
const frameKey = Math.floor(time * cacheResolution);
const cached = frameCacheRef.current.get(frameKey);
if (!cached) return null;
if (!cached) {
return null;
}
const currentHash = getTimelineHash(
time,
tracks,
mediaFiles,
activeProject
activeProject,
sceneId
);
console.log(cached.timelineHash === currentHash);
if (cached.timelineHash !== currentHash) {
// Cache is stale, remove it
console.log(
"Cache miss - hash mismatch:",
JSON.stringify({
cachedHash: cached.timelineHash.slice(0, 100),
currentHash: currentHash.slice(0, 100),
})
);
frameCacheRef.current.delete(frameKey);
return null;
}
@ -187,14 +204,16 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
imageData: ImageData,
tracks: TimelineTrack[],
mediaFiles: MediaFile[],
activeProject: TProject | null
activeProject: TProject | null,
sceneId?: string
): void => {
const frameKey = Math.floor(time * cacheResolution);
const timelineHash = getTimelineHash(
time,
tracks,
mediaFiles,
activeProject
activeProject,
sceneId
);
// Enforce cache size limit (LRU eviction)
@ -230,9 +249,10 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
time: number,
tracks: TimelineTrack[],
mediaFiles: MediaFile[],
activeProject: TProject | null
activeProject: TProject | null,
sceneId?: string
): "cached" | "not-cached" => {
return isFrameCached(time, tracks, mediaFiles, activeProject)
return isFrameCached(time, tracks, mediaFiles, activeProject, sceneId)
? "cached"
: "not-cached";
},
@ -247,6 +267,7 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
mediaFiles: MediaFile[],
activeProject: TProject | null,
renderFunction: (time: number) => Promise<ImageData>,
sceneId?: string,
range: number = 3 // seconds
) => {
const framesToPreRender: number[] = [];
@ -260,7 +281,7 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
const time = currentTime + offset;
if (time < 0) continue;
if (!isFrameCached(time, tracks, mediaFiles, activeProject)) {
if (!isFrameCached(time, tracks, mediaFiles, activeProject, sceneId)) {
framesToPreRender.push(time);
}
}
@ -276,7 +297,7 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
for (let k = 0; k < cacheResolution; k++) {
const t = s + k / cacheResolution;
if (t < 0) continue;
if (!isFrameCached(t, tracks, mediaFiles, activeProject)) {
if (!isFrameCached(t, tracks, mediaFiles, activeProject, sceneId)) {
expandedTimes.push(t);
}
}
@ -298,7 +319,14 @@ export function useFrameCache(options: FrameCacheOptions = {}) {
requestIdleCallback(async () => {
try {
const imageData = await renderFunction(time);
cacheFrame(time, imageData, tracks, mediaFiles, activeProject);
cacheFrame(
time,
imageData,
tracks,
mediaFiles,
activeProject,
sceneId
);
} catch (error) {
console.warn(`Pre-render failed for time ${time}:`, error);
}

View File

@ -78,7 +78,6 @@ class StorageService {
id: scene.id,
name: scene.name,
isMain: scene.isMain,
isBackground: scene.isBackground,
createdAt: scene.createdAt.toISOString(),
updatedAt: scene.updatedAt.toISOString(),
}));
@ -114,7 +113,6 @@ 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),
})) || [];

View File

@ -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 { createBackgroundScene, useSceneStore } from "./scene-store";
import { useSceneStore } from "./scene-store";
import { generateUUID } from "@/lib/utils";
import { CanvasSize, CanvasMode } from "@/types/editor";
@ -14,9 +14,8 @@ export const DEFAULT_FPS = 30;
export function createMainScene(): Scene {
return {
id: generateUUID(),
name: "Main scene",
name: "Main Scene",
isMain: true,
isBackground: false,
createdAt: new Date(),
updatedAt: new Date(),
};
@ -24,7 +23,6 @@ export function createMainScene(): Scene {
const createDefaultProject = (name: string): TProject => {
const mainScene = createMainScene();
const backgroundScene = createBackgroundScene();
return {
id: generateUUID(),
@ -32,7 +30,7 @@ const createDefaultProject = (name: string): TProject => {
thumbnail: "",
createdAt: new Date(),
updatedAt: new Date(),
scenes: [mainScene, backgroundScene],
scenes: [mainScene],
currentSceneId: mainScene.id,
backgroundColor: "#000000",
backgroundType: "color",

View File

@ -9,29 +9,17 @@ 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()];
function ensureMainScene(scenes: Scene[]): Scene[] {
const hasMain = scenes.some((scene) => scene.isMain);
if (!hasMain) {
const mainScene: Scene = {
id: generateUUID(),
name: "Main scene",
isMain: true,
createdAt: new Date(),
updatedAt: new Date(),
};
return [mainScene, ...scenes];
}
return scenes;
}
@ -49,6 +37,7 @@ interface SceneStore {
name: string;
isMain: boolean;
}) => Promise<string>;
deleteScene: ({ sceneId }: { sceneId: string }) => Promise<void>;
renameScene: ({
sceneId,
name,
@ -115,6 +104,62 @@ export const useSceneStore = create<SceneStore>((set, get) => ({
}
},
deleteScene: async ({ sceneId }: { sceneId: string }) => {
const { scenes, currentScene } = get();
const sceneToDelete = scenes.find((s) => s.id === sceneId);
if (!sceneToDelete) {
throw new Error("Scene not found");
}
if (sceneToDelete.isMain) {
throw new Error("Cannot delete main scene");
}
const updatedScenes = scenes.filter((s) => s.id !== sceneId);
// Determine new current scene if we're deleting the current one
let newCurrentScene = currentScene;
if (currentScene?.id === sceneId) {
newCurrentScene = getMainScene({ scenes: updatedScenes });
}
// 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: newCurrentScene,
});
// If we switched scenes, load the new scene's timeline
if (newCurrentScene && newCurrentScene.id !== currentScene?.id) {
const timelineStore = useTimelineStore.getState();
await timelineStore.loadProjectTimeline({
projectId: activeProject.id,
sceneId: newCurrentScene.id,
});
}
} catch (error) {
console.error("Failed to delete scene:", error);
throw error;
}
},
renameScene: async ({ sceneId, name }: { sceneId: string; name: string }) => {
const { scenes } = get();
const updatedScenes = scenes.map((scene) =>
@ -226,7 +271,7 @@ export const useSceneStore = create<SceneStore>((set, get) => ({
scenes: Scene[];
currentSceneId?: string;
}) => {
const ensuredScenes = ensureBackgroundScene({ scenes });
const ensuredScenes = ensureMainScene(scenes);
const currentScene = currentSceneId
? ensuredScenes.find((s) => s.id === currentSceneId)
: null;
@ -241,19 +286,25 @@ export const useSceneStore = create<SceneStore>((set, get) => ({
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);
});
storageService
.saveProject({ project: updatedProject })
.then(() => {
useProjectStore.setState({ activeProject: updatedProject });
})
.catch((error) => {
console.error(
"Failed to save project with background scene:",
error
);
});
}
}
},

View File

@ -6,7 +6,6 @@ export interface Scene {
id: string;
name: string;
isMain: boolean;
isBackground: boolean;
createdAt: Date;
updatedAt: Date;
}