summaryrefslogtreecommitdiff
path: root/apps/web/src/stores/project-store.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-09-01 16:17:06 +0200
committerMaze Winther <mazewinther@gmail.com>2025-09-01 16:17:06 +0200
commit350b55081cde3a4332c982ea5c53b7d359029f6b (patch)
treef63b66635d2a13a0a43c622a9bea25d9b9822960 /apps/web/src/stores/project-store.ts
parent2b700b7474ffc728f5318c178244e035c93716b5 (diff)
downloadopencut-350b55081cde3a4332c982ea5c53b7d359029f6b.tar.gz
refactor(core): switch to scene-based architecture
Diffstat (limited to 'apps/web/src/stores/project-store.ts')
-rw-r--r--apps/web/src/stores/project-store.ts145
1 files changed, 91 insertions, 54 deletions
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts
index ed5eda73..d04c23d5 100644
--- a/apps/web/src/stores/project-store.ts
+++ b/apps/web/src/stores/project-store.ts
@@ -1,28 +1,44 @@
-import { TProject, BlurIntensity } from "@/types/project";
+import { TProject, BlurIntensity, Scene } from "@/types/project";
import { create } from "zustand";
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 { generateUUID } from "@/lib/utils";
import { CanvasSize, CanvasMode } from "@/types/editor";
export const DEFAULT_CANVAS_SIZE: CanvasSize = { width: 1920, height: 1080 };
export const DEFAULT_FPS = 30;
-const DEFAULT_PROJECT: TProject = {
- id: generateUUID(),
- name: "Untitled",
- thumbnail: "",
- createdAt: new Date(),
- updatedAt: new Date(),
- backgroundColor: "#000000",
- backgroundType: "color",
- blurIntensity: 8,
- bookmarks: [],
- fps: DEFAULT_FPS,
- canvasSize: DEFAULT_CANVAS_SIZE,
- canvasMode: "preset",
+function createMainScene(): Scene {
+ return {
+ id: generateUUID(),
+ name: "Main Scene",
+ isMain: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+}
+
+const createDefaultProject = (name: string): TProject => {
+ const mainScene = createMainScene();
+ return {
+ id: generateUUID(),
+ name,
+ thumbnail: "",
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ scenes: [mainScene],
+ currentSceneId: mainScene.id,
+ backgroundColor: "#000000",
+ backgroundType: "color",
+ blurIntensity: 8,
+ bookmarks: [],
+ fps: DEFAULT_FPS,
+ canvasSize: DEFAULT_CANVAS_SIZE,
+ canvasMode: "preset",
+ };
};
interface ProjectStore {
@@ -104,7 +120,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
set({ activeProject: updatedProject });
await get().loadAllProjects(); // Refresh the list
} catch (error) {
@@ -152,7 +168,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
set({ activeProject: updatedProject });
await get().loadAllProjects(); // Refresh the list
} catch (error) {
@@ -164,23 +180,24 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
},
createNewProject: async (name: string) => {
- const newProject: TProject = {
- ...DEFAULT_PROJECT,
- id: generateUUID(),
- name,
- createdAt: new Date(),
- updatedAt: new Date(),
- };
+ const newProject = createDefaultProject(name);
set({ activeProject: newProject });
const mediaStore = useMediaStore.getState();
const timelineStore = useTimelineStore.getState();
+ const sceneStore = useSceneStore.getState();
+
mediaStore.clearAllMedia();
timelineStore.clearTimeline();
+ sceneStore.initializeScenes({
+ scenes: newProject.scenes,
+ currentSceneId: newProject.currentSceneId,
+ });
+
try {
- await storageService.saveProject(newProject);
+ await storageService.saveProject({ project: newProject });
// Reload all projects to update the list
await get().loadAllProjects();
return newProject.id;
@@ -195,21 +212,35 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
set({ isLoading: true });
}
- // Clear media and timeline immediately to prevent flickering when switching projects
+ // Prevent flicker when switching projects - clear all stores
const mediaStore = useMediaStore.getState();
const timelineStore = useTimelineStore.getState();
+ const sceneStore = useSceneStore.getState();
+
mediaStore.clearAllMedia();
timelineStore.clearTimeline();
+ sceneStore.clearScenes();
try {
- const project = await storageService.loadProject(id);
+ const project = await storageService.loadProject({ id });
if (project) {
set({ activeProject: project });
- // Load project-specific data in parallel
+ if (project.scenes && project.scenes.length > 0) {
+ sceneStore.initializeScenes({
+ scenes: project.scenes,
+ currentSceneId: project.currentSceneId,
+ });
+ }
+
+ const currentScene = sceneStore.currentScene;
+
await Promise.all([
mediaStore.loadProjectMedia(id),
- timelineStore.loadProjectTimeline(id),
+ timelineStore.loadProjectTimeline({
+ projectId: id,
+ sceneId: currentScene?.id,
+ }),
]);
} else {
throw new Error(`Project with id ${id} not found`);
@@ -227,11 +258,16 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
if (!activeProject) return;
try {
- // Save project metadata and timeline data in parallel
const timelineStore = useTimelineStore.getState();
+ const sceneStore = useSceneStore.getState();
+ const currentScene = sceneStore.currentScene;
+
await Promise.all([
- storageService.saveProject(activeProject),
- timelineStore.saveProjectTimeline(activeProject.id),
+ storageService.saveProject({ project: activeProject }),
+ timelineStore.saveProjectTimeline({
+ projectId: activeProject.id,
+ sceneId: currentScene?.id,
+ }),
]);
await get().loadAllProjects(); // Refresh the list
} catch (error) {
@@ -256,22 +292,24 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
deleteProject: async (id: string) => {
try {
- // Delete project data in parallel
await Promise.all([
- storageService.deleteProjectMedia(id),
- storageService.deleteProjectTimeline(id),
- storageService.deleteProject(id),
+ storageService.deleteProjectMedia({ projectId: id }),
+ storageService.deleteProjectTimeline({ projectId: id }),
+ storageService.deleteProject({ id }),
]);
await get().loadAllProjects(); // Refresh the list
- // If we deleted the active project, close it and clear data
+ // If deleted active project, close it and clear data
const { activeProject } = get();
if (activeProject?.id === id) {
set({ activeProject: null });
const mediaStore = useMediaStore.getState();
const timelineStore = useTimelineStore.getState();
+ const sceneStore = useSceneStore.getState();
+
mediaStore.clearAllMedia();
timelineStore.clearTimeline();
+ sceneStore.clearScenes();
}
} catch (error) {
console.error("Failed to delete project:", error);
@@ -281,11 +319,13 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
closeProject: () => {
set({ activeProject: null });
- // Clear data from stores when closing project
const mediaStore = useMediaStore.getState();
const timelineStore = useTimelineStore.getState();
+ const sceneStore = useSceneStore.getState();
+
mediaStore.clearAllMedia();
timelineStore.clearTimeline();
+ sceneStore.clearScenes();
},
renameProject: async (id: string, name: string) => {
@@ -307,12 +347,11 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- // Save to storage
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
await get().loadAllProjects();
- // Update activeProject if it's the same project
+ // Update activeProject if same project
const { activeProject } = get();
if (activeProject?.id === id) {
set({ activeProject: updatedProject });
@@ -328,7 +367,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
duplicateProject: async (projectId: string) => {
try {
- const project = await storageService.loadProject(projectId);
+ const project = await storageService.loadProject({ id: projectId });
if (!project) {
toast.error("Project not found", {
description: "Please try again",
@@ -355,14 +394,14 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
const newProject: TProject = {
- ...project, // Copy all properties from the original project
+ ...project,
id: generateUUID(),
name: `(${nextNumber}) ${baseName}`,
createdAt: new Date(),
updatedAt: new Date(),
};
- await storageService.saveProject(newProject);
+ await storageService.saveProject({ project: newProject });
await get().loadAllProjects();
return newProject.id;
} catch (error) {
@@ -386,9 +425,9 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
set({ activeProject: updatedProject });
- await get().loadAllProjects(); // Refresh the list
+ await get().loadAllProjects();
} catch (error) {
console.error("Failed to update project background:", error);
toast.error("Failed to update background", {
@@ -417,9 +456,9 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
set({ activeProject: updatedProject });
- await get().loadAllProjects(); // Refresh the list
+ await get().loadAllProjects();
} catch (error) {
console.error("Failed to update background type:", error);
toast.error("Failed to update background", {
@@ -439,9 +478,9 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
set({ activeProject: updatedProject });
- await get().loadAllProjects(); // Refresh the list
+ await get().loadAllProjects();
} catch (error) {
console.error("Failed to update project FPS:", error);
toast.error("Failed to update project FPS", {
@@ -462,9 +501,9 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
};
try {
- await storageService.saveProject(updatedProject);
+ await storageService.saveProject({ project: updatedProject });
set({ activeProject: updatedProject });
- await get().loadAllProjects(); // Refresh the list
+ await get().loadAllProjects();
} catch (error) {
console.error("Failed to update canvas size:", error);
toast.error("Failed to update canvas size", {
@@ -476,12 +515,10 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
getFilteredAndSortedProjects: (searchQuery: string, sortOption: string) => {
const { savedProjects } = get();
- // Filter projects by search query
const filteredProjects = savedProjects.filter((project) =>
project.name.toLowerCase().includes(searchQuery.toLowerCase())
);
- // Sort filtered projects
const sortedProjects = [...filteredProjects].sort((a, b) => {
const [key, order] = sortOption.split("-");
@@ -508,7 +545,7 @@ export const useProjectStore = create<ProjectStore>((set, get) => ({
return sortedProjects;
},
- // Global invalid project ID tracking implementation
+ // Global invalid project ID tracking
isInvalidProjectId: (id: string) => {
const invalidIds = get().invalidProjectIds || new Set();
return invalidIds.has(id);