From be1ae20d9089e81cb84a8294be76fe680a24b60f Mon Sep 17 00:00:00 2001 From: Hyteq Date: Sun, 20 Jul 2025 11:00:22 +0300 Subject: format --- apps/web/src/stores/project-store.ts | 722 +++++++++++++++++------------------ 1 file changed, 361 insertions(+), 361 deletions(-) (limited to 'apps/web/src/stores/project-store.ts') diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts index a8833982..36ab3503 100644 --- a/apps/web/src/stores/project-store.ts +++ b/apps/web/src/stores/project-store.ts @@ -1,361 +1,361 @@ -import { TProject } 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 { generateUUID } from "@/lib/utils"; - -interface ProjectStore { - activeProject: TProject | null; - savedProjects: TProject[]; - isLoading: boolean; - isInitialized: boolean; - - // Actions - createNewProject: (name: string) => Promise; - loadProject: (id: string) => Promise; - saveCurrentProject: () => Promise; - loadAllProjects: () => Promise; - deleteProject: (id: string) => Promise; - closeProject: () => void; - renameProject: (projectId: string, name: string) => Promise; - duplicateProject: (projectId: string) => Promise; - updateProjectBackground: (backgroundColor: string) => Promise; - updateBackgroundType: ( - type: "color" | "blur", - options?: { backgroundColor?: string; blurIntensity?: number } - ) => Promise; - updateProjectFps: (fps: number) => Promise; - - getFilteredAndSortedProjects: ( - searchQuery: string, - sortOption: string - ) => TProject[]; -} - -export const useProjectStore = create((set, get) => ({ - activeProject: null, - savedProjects: [], - isLoading: true, - isInitialized: false, - - createNewProject: async (name: string) => { - const newProject: TProject = { - id: generateUUID(), - name, - thumbnail: "", - createdAt: new Date(), - updatedAt: new Date(), - backgroundColor: "#000000", - backgroundType: "color", - blurIntensity: 8, - }; - - set({ activeProject: newProject }); - - try { - await storageService.saveProject(newProject); - // Reload all projects to update the list - await get().loadAllProjects(); - return newProject.id; - } catch (error) { - toast.error("Failed to save new project"); - throw error; - } - }, - - loadProject: async (id: string) => { - if (!get().isInitialized) { - set({ isLoading: true }); - } - - // Clear media and timeline immediately to prevent flickering when switching projects - const mediaStore = useMediaStore.getState(); - const timelineStore = useTimelineStore.getState(); - mediaStore.clearAllMedia(); - timelineStore.clearTimeline(); - - try { - const project = await storageService.loadProject(id); - if (project) { - set({ activeProject: project }); - - // Load project-specific data in parallel - await Promise.all([ - mediaStore.loadProjectMedia(id), - timelineStore.loadProjectTimeline(id), - ]); - } else { - throw new Error(`Project with id ${id} not found`); - } - } catch (error) { - console.error("Failed to load project:", error); - throw error; // Re-throw so the editor page can handle it - } finally { - set({ isLoading: false }); - } - }, - - saveCurrentProject: async () => { - const { activeProject } = get(); - if (!activeProject) return; - - try { - // Save project metadata and timeline data in parallel - const timelineStore = useTimelineStore.getState(); - await Promise.all([ - storageService.saveProject(activeProject), - timelineStore.saveProjectTimeline(activeProject.id), - ]); - await get().loadAllProjects(); // Refresh the list - } catch (error) { - console.error("Failed to save project:", error); - } - }, - - loadAllProjects: async () => { - if (!get().isInitialized) { - set({ isLoading: true }); - } - - try { - const projects = await storageService.loadAllProjects(); - set({ savedProjects: projects }); - } catch (error) { - console.error("Failed to load projects:", error); - } finally { - set({ isLoading: false, isInitialized: true }); - } - }, - - deleteProject: async (id: string) => { - try { - // Delete project data in parallel - await Promise.all([ - storageService.deleteProjectMedia(id), - storageService.deleteProjectTimeline(id), - storageService.deleteProject(id), - ]); - await get().loadAllProjects(); // Refresh the list - - // If we deleted the 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(); - mediaStore.clearAllMedia(); - timelineStore.clearTimeline(); - } - } catch (error) { - console.error("Failed to delete project:", error); - } - }, - - closeProject: () => { - set({ activeProject: null }); - - // Clear data from stores when closing project - const mediaStore = useMediaStore.getState(); - const timelineStore = useTimelineStore.getState(); - mediaStore.clearAllMedia(); - timelineStore.clearTimeline(); - }, - - renameProject: async (id: string, name: string) => { - const { savedProjects } = get(); - - // Find the project to rename - const projectToRename = savedProjects.find((p) => p.id === id); - if (!projectToRename) { - toast.error("Project not found", { - description: "Please try again", - }); - return; - } - - const updatedProject = { - ...projectToRename, - name, - updatedAt: new Date(), - }; - - try { - // Save to storage - await storageService.saveProject(updatedProject); - - await get().loadAllProjects(); - - // Update activeProject if it's the same project - const { activeProject } = get(); - if (activeProject?.id === id) { - set({ activeProject: updatedProject }); - } - } catch (error) { - console.error("Failed to rename project:", error); - toast.error("Failed to rename project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - } - }, - - duplicateProject: async (projectId: string) => { - try { - const project = await storageService.loadProject(projectId); - if (!project) { - toast.error("Project not found", { - description: "Please try again", - }); - throw new Error("Project not found"); - } - - const { savedProjects } = get(); - - // Extract the base name (remove any existing numbering) - const numberMatch = project.name.match(/^\((\d+)\)\s+(.+)$/); - const baseName = numberMatch ? numberMatch[2] : project.name; - const existingNumbers: number[] = []; - - // Check for pattern "(number) baseName" in existing projects - savedProjects.forEach((p) => { - const match = p.name.match(/^\((\d+)\)\s+(.+)$/); - if (match && match[2] === baseName) { - existingNumbers.push(parseInt(match[1], 10)); - } - }); - - const nextNumber = - existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1; - - const newProject: TProject = { - id: generateUUID(), - name: `(${nextNumber}) ${baseName}`, - thumbnail: project.thumbnail, - createdAt: new Date(), - updatedAt: new Date(), - }; - - await storageService.saveProject(newProject); - await get().loadAllProjects(); - return newProject.id; - } catch (error) { - console.error("Failed to duplicate project:", error); - toast.error("Failed to duplicate project", { - description: - error instanceof Error ? error.message : "Please try again", - }); - throw error; - } - }, - - updateProjectBackground: async (backgroundColor: string) => { - const { activeProject } = get(); - if (!activeProject) return; - - const updatedProject = { - ...activeProject, - backgroundColor, - updatedAt: new Date(), - }; - - try { - await storageService.saveProject(updatedProject); - set({ activeProject: updatedProject }); - await get().loadAllProjects(); // Refresh the list - } catch (error) { - console.error("Failed to update project background:", error); - toast.error("Failed to update background", { - description: "Please try again", - }); - } - }, - - updateBackgroundType: async ( - type: "color" | "blur", - options?: { backgroundColor?: string; blurIntensity?: number } - ) => { - const { activeProject } = get(); - if (!activeProject) return; - - const updatedProject = { - ...activeProject, - backgroundType: type, - ...(options?.backgroundColor && { - backgroundColor: options.backgroundColor, - }), - ...(options?.blurIntensity && { blurIntensity: options.blurIntensity }), - updatedAt: new Date(), - }; - - try { - await storageService.saveProject(updatedProject); - set({ activeProject: updatedProject }); - await get().loadAllProjects(); // Refresh the list - } catch (error) { - console.error("Failed to update background type:", error); - toast.error("Failed to update background", { - description: "Please try again", - }); - } - }, - - updateProjectFps: async (fps: number) => { - const { activeProject } = get(); - if (!activeProject) return; - - const updatedProject = { - ...activeProject, - fps, - updatedAt: new Date(), - }; - - try { - await storageService.saveProject(updatedProject); - set({ activeProject: updatedProject }); - await get().loadAllProjects(); // Refresh the list - } catch (error) { - console.error("Failed to update project FPS:", error); - toast.error("Failed to update project FPS", { - description: "Please try again", - }); - } - }, - - 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("-"); - - if (key !== "createdAt" && key !== "name") { - console.warn(`Invalid sort key: ${key}`); - return 0; - } - - const aValue = a[key]; - const bValue = b[key]; - - if (aValue === undefined || bValue === undefined) return 0; - - if (order === "asc") { - if (aValue < bValue) return -1; - if (aValue > bValue) return 1; - return 0; - } else { - if (aValue > bValue) return -1; - if (aValue < bValue) return 1; - return 0; - } - }); - - return sortedProjects; - }, -})); +import { TProject } 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 { generateUUID } from "@/lib/utils"; + +interface ProjectStore { + activeProject: TProject | null; + savedProjects: TProject[]; + isLoading: boolean; + isInitialized: boolean; + + // Actions + createNewProject: (name: string) => Promise; + loadProject: (id: string) => Promise; + saveCurrentProject: () => Promise; + loadAllProjects: () => Promise; + deleteProject: (id: string) => Promise; + closeProject: () => void; + renameProject: (projectId: string, name: string) => Promise; + duplicateProject: (projectId: string) => Promise; + updateProjectBackground: (backgroundColor: string) => Promise; + updateBackgroundType: ( + type: "color" | "blur", + options?: { backgroundColor?: string; blurIntensity?: number }, + ) => Promise; + updateProjectFps: (fps: number) => Promise; + + getFilteredAndSortedProjects: ( + searchQuery: string, + sortOption: string, + ) => TProject[]; +} + +export const useProjectStore = create((set, get) => ({ + activeProject: null, + savedProjects: [], + isLoading: true, + isInitialized: false, + + createNewProject: async (name: string) => { + const newProject: TProject = { + id: generateUUID(), + name, + thumbnail: "", + createdAt: new Date(), + updatedAt: new Date(), + backgroundColor: "#000000", + backgroundType: "color", + blurIntensity: 8, + }; + + set({ activeProject: newProject }); + + try { + await storageService.saveProject(newProject); + // Reload all projects to update the list + await get().loadAllProjects(); + return newProject.id; + } catch (error) { + toast.error("Failed to save new project"); + throw error; + } + }, + + loadProject: async (id: string) => { + if (!get().isInitialized) { + set({ isLoading: true }); + } + + // Clear media and timeline immediately to prevent flickering when switching projects + const mediaStore = useMediaStore.getState(); + const timelineStore = useTimelineStore.getState(); + mediaStore.clearAllMedia(); + timelineStore.clearTimeline(); + + try { + const project = await storageService.loadProject(id); + if (project) { + set({ activeProject: project }); + + // Load project-specific data in parallel + await Promise.all([ + mediaStore.loadProjectMedia(id), + timelineStore.loadProjectTimeline(id), + ]); + } else { + throw new Error(`Project with id ${id} not found`); + } + } catch (error) { + console.error("Failed to load project:", error); + throw error; // Re-throw so the editor page can handle it + } finally { + set({ isLoading: false }); + } + }, + + saveCurrentProject: async () => { + const { activeProject } = get(); + if (!activeProject) return; + + try { + // Save project metadata and timeline data in parallel + const timelineStore = useTimelineStore.getState(); + await Promise.all([ + storageService.saveProject(activeProject), + timelineStore.saveProjectTimeline(activeProject.id), + ]); + await get().loadAllProjects(); // Refresh the list + } catch (error) { + console.error("Failed to save project:", error); + } + }, + + loadAllProjects: async () => { + if (!get().isInitialized) { + set({ isLoading: true }); + } + + try { + const projects = await storageService.loadAllProjects(); + set({ savedProjects: projects }); + } catch (error) { + console.error("Failed to load projects:", error); + } finally { + set({ isLoading: false, isInitialized: true }); + } + }, + + deleteProject: async (id: string) => { + try { + // Delete project data in parallel + await Promise.all([ + storageService.deleteProjectMedia(id), + storageService.deleteProjectTimeline(id), + storageService.deleteProject(id), + ]); + await get().loadAllProjects(); // Refresh the list + + // If we deleted the 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(); + mediaStore.clearAllMedia(); + timelineStore.clearTimeline(); + } + } catch (error) { + console.error("Failed to delete project:", error); + } + }, + + closeProject: () => { + set({ activeProject: null }); + + // Clear data from stores when closing project + const mediaStore = useMediaStore.getState(); + const timelineStore = useTimelineStore.getState(); + mediaStore.clearAllMedia(); + timelineStore.clearTimeline(); + }, + + renameProject: async (id: string, name: string) => { + const { savedProjects } = get(); + + // Find the project to rename + const projectToRename = savedProjects.find((p) => p.id === id); + if (!projectToRename) { + toast.error("Project not found", { + description: "Please try again", + }); + return; + } + + const updatedProject = { + ...projectToRename, + name, + updatedAt: new Date(), + }; + + try { + // Save to storage + await storageService.saveProject(updatedProject); + + await get().loadAllProjects(); + + // Update activeProject if it's the same project + const { activeProject } = get(); + if (activeProject?.id === id) { + set({ activeProject: updatedProject }); + } + } catch (error) { + console.error("Failed to rename project:", error); + toast.error("Failed to rename project", { + description: + error instanceof Error ? error.message : "Please try again", + }); + } + }, + + duplicateProject: async (projectId: string) => { + try { + const project = await storageService.loadProject(projectId); + if (!project) { + toast.error("Project not found", { + description: "Please try again", + }); + throw new Error("Project not found"); + } + + const { savedProjects } = get(); + + // Extract the base name (remove any existing numbering) + const numberMatch = project.name.match(/^\((\d+)\)\s+(.+)$/); + const baseName = numberMatch ? numberMatch[2] : project.name; + const existingNumbers: number[] = []; + + // Check for pattern "(number) baseName" in existing projects + savedProjects.forEach((p) => { + const match = p.name.match(/^\((\d+)\)\s+(.+)$/); + if (match && match[2] === baseName) { + existingNumbers.push(parseInt(match[1], 10)); + } + }); + + const nextNumber = + existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1; + + const newProject: TProject = { + id: generateUUID(), + name: `(${nextNumber}) ${baseName}`, + thumbnail: project.thumbnail, + createdAt: new Date(), + updatedAt: new Date(), + }; + + await storageService.saveProject(newProject); + await get().loadAllProjects(); + return newProject.id; + } catch (error) { + console.error("Failed to duplicate project:", error); + toast.error("Failed to duplicate project", { + description: + error instanceof Error ? error.message : "Please try again", + }); + throw error; + } + }, + + updateProjectBackground: async (backgroundColor: string) => { + const { activeProject } = get(); + if (!activeProject) return; + + const updatedProject = { + ...activeProject, + backgroundColor, + updatedAt: new Date(), + }; + + try { + await storageService.saveProject(updatedProject); + set({ activeProject: updatedProject }); + await get().loadAllProjects(); // Refresh the list + } catch (error) { + console.error("Failed to update project background:", error); + toast.error("Failed to update background", { + description: "Please try again", + }); + } + }, + + updateBackgroundType: async ( + type: "color" | "blur", + options?: { backgroundColor?: string; blurIntensity?: number }, + ) => { + const { activeProject } = get(); + if (!activeProject) return; + + const updatedProject = { + ...activeProject, + backgroundType: type, + ...(options?.backgroundColor && { + backgroundColor: options.backgroundColor, + }), + ...(options?.blurIntensity && { blurIntensity: options.blurIntensity }), + updatedAt: new Date(), + }; + + try { + await storageService.saveProject(updatedProject); + set({ activeProject: updatedProject }); + await get().loadAllProjects(); // Refresh the list + } catch (error) { + console.error("Failed to update background type:", error); + toast.error("Failed to update background", { + description: "Please try again", + }); + } + }, + + updateProjectFps: async (fps: number) => { + const { activeProject } = get(); + if (!activeProject) return; + + const updatedProject = { + ...activeProject, + fps, + updatedAt: new Date(), + }; + + try { + await storageService.saveProject(updatedProject); + set({ activeProject: updatedProject }); + await get().loadAllProjects(); // Refresh the list + } catch (error) { + console.error("Failed to update project FPS:", error); + toast.error("Failed to update project FPS", { + description: "Please try again", + }); + } + }, + + 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("-"); + + if (key !== "createdAt" && key !== "name") { + console.warn(`Invalid sort key: ${key}`); + return 0; + } + + const aValue = a[key]; + const bValue = b[key]; + + if (aValue === undefined || bValue === undefined) return 0; + + if (order === "asc") { + if (aValue < bValue) return -1; + if (aValue > bValue) return 1; + return 0; + } else { + if (aValue > bValue) return -1; + if (aValue < bValue) return 1; + return 0; + } + }); + + return sortedProjects; + }, +})); -- cgit v1.3.1