mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
refactor(core): switch to scene-based architecture
This commit is contained in:
@@ -152,7 +152,7 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
|
||||
|
||||
// Save to persistent storage in background
|
||||
try {
|
||||
await storageService.saveMediaFile(projectId, newItem);
|
||||
await storageService.saveMediaFile({ projectId, mediaItem: newItem });
|
||||
} catch (error) {
|
||||
console.error("Failed to save media item:", error);
|
||||
// Remove from local state if save failed
|
||||
@@ -217,7 +217,7 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
|
||||
|
||||
// 3) Remove from persistent storage
|
||||
try {
|
||||
await storageService.deleteMediaFile(projectId, id);
|
||||
await storageService.deleteMediaFile({ projectId, id });
|
||||
} catch (error) {
|
||||
console.error("Failed to delete media item:", error);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
|
||||
set({ isLoading: true });
|
||||
|
||||
try {
|
||||
const mediaItems = await storageService.loadAllMediaFiles(projectId);
|
||||
const mediaItems = await storageService.loadAllMediaFiles({ projectId });
|
||||
|
||||
// Regenerate thumbnails for video items
|
||||
const updatedMediaItems = await Promise.all(
|
||||
@@ -282,7 +282,7 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
|
||||
try {
|
||||
const mediaIds = state.mediaFiles.map((item) => item.id);
|
||||
await Promise.all(
|
||||
mediaIds.map((id) => storageService.deleteMediaFile(projectId, id))
|
||||
mediaIds.map((id) => storageService.deleteMediaFile({ projectId, id }))
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to clear media items from storage:", error);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
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,
|
||||
});
|
||||
},
|
||||
}));
|
||||
@@ -162,7 +162,7 @@ export const useSoundsStore = create<SoundsStore>((set, get) => ({
|
||||
|
||||
saveSoundEffect: async (soundEffect: SoundEffect) => {
|
||||
try {
|
||||
await storageService.saveSoundEffect(soundEffect);
|
||||
await storageService.saveSoundEffect({ soundEffect });
|
||||
|
||||
// Refresh saved sounds
|
||||
const savedSoundsData = await storageService.loadSavedSounds();
|
||||
@@ -178,7 +178,7 @@ export const useSoundsStore = create<SoundsStore>((set, get) => ({
|
||||
|
||||
removeSavedSound: async (soundId: number) => {
|
||||
try {
|
||||
await storageService.removeSavedSound(soundId);
|
||||
await storageService.removeSavedSound({ soundId });
|
||||
|
||||
// Update local state immediately
|
||||
set((state) => ({
|
||||
|
||||
@@ -15,6 +15,7 @@ import { MediaFile } from "@/types/media";
|
||||
import { findBestCanvasPreset } from "@/lib/editor-utils";
|
||||
import { storageService } from "@/lib/storage/storage-service";
|
||||
import { useProjectStore } from "./project-store";
|
||||
import { useSceneStore } from "./scene-store";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants";
|
||||
import { checkElementOverlaps, resolveElementOverlaps } from "@/lib/timeline";
|
||||
@@ -178,8 +179,20 @@ interface TimelineStore {
|
||||
pushHistory: () => void;
|
||||
|
||||
// Persistence actions
|
||||
loadProjectTimeline: (projectId: string) => Promise<void>;
|
||||
saveProjectTimeline: (projectId: string) => Promise<void>;
|
||||
loadProjectTimeline: ({
|
||||
projectId,
|
||||
sceneId,
|
||||
}: {
|
||||
projectId: string;
|
||||
sceneId?: string;
|
||||
}) => Promise<void>;
|
||||
saveProjectTimeline: ({
|
||||
projectId,
|
||||
sceneId,
|
||||
}: {
|
||||
projectId: string;
|
||||
sceneId?: string;
|
||||
}) => Promise<void>;
|
||||
clearTimeline: () => void;
|
||||
|
||||
// Clipboard actions
|
||||
@@ -235,9 +248,15 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
||||
// Helper to auto-save timeline changes
|
||||
const autoSaveTimeline = async () => {
|
||||
const activeProject = useProjectStore.getState().activeProject;
|
||||
if (activeProject) {
|
||||
const currentScene = useSceneStore.getState().currentScene;
|
||||
|
||||
if (activeProject && currentScene) {
|
||||
try {
|
||||
await storageService.saveTimeline(activeProject.id, get()._tracks);
|
||||
await storageService.saveTimeline({
|
||||
projectId: activeProject.id,
|
||||
tracks: get()._tracks,
|
||||
sceneId: currentScene.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to auto-save timeline:", error);
|
||||
}
|
||||
@@ -1229,8 +1248,24 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
||||
|
||||
getProjectThumbnail: async (projectId) => {
|
||||
try {
|
||||
const tracks = await storageService.loadTimeline(projectId);
|
||||
const mediaItems = await storageService.loadAllMediaFiles(projectId);
|
||||
const project = await storageService.loadProject({ id: projectId });
|
||||
if (!project) return null;
|
||||
|
||||
// For scene-based projects, use main scene timeline
|
||||
// For legacy projects, use legacy timeline format
|
||||
let sceneId: string | undefined;
|
||||
if (project.scenes && project.scenes.length > 0) {
|
||||
const mainScene = project.scenes.find((s) => s.isMain);
|
||||
sceneId = mainScene?.id;
|
||||
}
|
||||
|
||||
const tracks = await storageService.loadTimeline({
|
||||
projectId,
|
||||
sceneId,
|
||||
});
|
||||
const mediaItems = await storageService.loadAllMediaFiles({
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (!tracks || !mediaItems.length) return null;
|
||||
|
||||
@@ -1330,9 +1365,19 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
||||
});
|
||||
},
|
||||
|
||||
loadProjectTimeline: async (projectId) => {
|
||||
loadProjectTimeline: async ({
|
||||
projectId,
|
||||
sceneId,
|
||||
}: {
|
||||
projectId: string;
|
||||
sceneId?: string;
|
||||
}) => {
|
||||
try {
|
||||
const tracks = await storageService.loadTimeline(projectId);
|
||||
const tracks = await storageService.loadTimeline({
|
||||
projectId,
|
||||
sceneId,
|
||||
});
|
||||
|
||||
if (tracks) {
|
||||
updateTracks(tracks);
|
||||
} else {
|
||||
@@ -1348,9 +1393,20 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
||||
}
|
||||
},
|
||||
|
||||
saveProjectTimeline: async (projectId) => {
|
||||
saveProjectTimeline: async ({
|
||||
projectId,
|
||||
sceneId,
|
||||
}: {
|
||||
projectId: string;
|
||||
sceneId?: string;
|
||||
}) => {
|
||||
const { _tracks } = get();
|
||||
try {
|
||||
await storageService.saveTimeline(projectId, get()._tracks);
|
||||
await storageService.saveTimeline({
|
||||
projectId,
|
||||
tracks: _tracks,
|
||||
sceneId,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to save timeline:", error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user