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/lib/storage/storage-service.ts | 530 ++++++++++++++-------------- 1 file changed, 265 insertions(+), 265 deletions(-) (limited to 'apps/web/src/lib/storage/storage-service.ts') diff --git a/apps/web/src/lib/storage/storage-service.ts b/apps/web/src/lib/storage/storage-service.ts index 0037dbdb..044f2dff 100644 --- a/apps/web/src/lib/storage/storage-service.ts +++ b/apps/web/src/lib/storage/storage-service.ts @@ -3,275 +3,275 @@ import { MediaItem } from "@/stores/media-store"; import { IndexedDBAdapter } from "./indexeddb-adapter"; import { OPFSAdapter } from "./opfs-adapter"; import { - MediaFileData, - StorageConfig, - SerializedProject, - TimelineData, + MediaFileData, + StorageConfig, + SerializedProject, + TimelineData, } from "./types"; import { TimelineTrack } from "@/types/timeline"; class StorageService { - private projectsAdapter: IndexedDBAdapter; - private config: StorageConfig; - - constructor() { - this.config = { - projectsDb: "video-editor-projects", - mediaDb: "video-editor-media", - timelineDb: "video-editor-timelines", - version: 1, - }; - - this.projectsAdapter = new IndexedDBAdapter( - this.config.projectsDb, - "projects", - this.config.version - ); - } - - // Helper to get project-specific media adapters - private getProjectMediaAdapters(projectId: string) { - const mediaMetadataAdapter = new IndexedDBAdapter( - `${this.config.mediaDb}-${projectId}`, - "media-metadata", - this.config.version - ); - - const mediaFilesAdapter = new OPFSAdapter(`media-files-${projectId}`); - - return { mediaMetadataAdapter, mediaFilesAdapter }; - } - - // Helper to get project-specific timeline adapter - private getProjectTimelineAdapter(projectId: string) { - return new IndexedDBAdapter( - `${this.config.timelineDb}-${projectId}`, - "timeline", - this.config.version - ); - } - - // Project operations - async saveProject(project: TProject): Promise { - // Convert TProject to serializable format - const serializedProject: SerializedProject = { - id: project.id, - name: project.name, - thumbnail: project.thumbnail, - createdAt: project.createdAt.toISOString(), - updatedAt: project.updatedAt.toISOString(), - backgroundColor: project.backgroundColor, - backgroundType: project.backgroundType, - blurIntensity: project.blurIntensity, - }; - - await this.projectsAdapter.set(project.id, serializedProject); - } - - async loadProject(id: string): Promise { - const serializedProject = await this.projectsAdapter.get(id); - - if (!serializedProject) return null; - - // Convert back to TProject format - return { - id: serializedProject.id, - name: serializedProject.name, - thumbnail: serializedProject.thumbnail, - createdAt: new Date(serializedProject.createdAt), - updatedAt: new Date(serializedProject.updatedAt), - backgroundColor: serializedProject.backgroundColor, - backgroundType: serializedProject.backgroundType, - blurIntensity: serializedProject.blurIntensity, - }; - } - - async loadAllProjects(): Promise { - const projectIds = await this.projectsAdapter.list(); - const projects: TProject[] = []; - - for (const id of projectIds) { - const project = await this.loadProject(id); - if (project) { - projects.push(project); - } - } - - // Sort by last updated (most recent first) - return projects.sort( - (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() - ); - } - - async deleteProject(id: string): Promise { - await this.projectsAdapter.remove(id); - } - - // Media operations - now project-specific - async saveMediaItem(projectId: string, mediaItem: MediaItem): Promise { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); - - // Save file to project-specific OPFS - await mediaFilesAdapter.set(mediaItem.id, mediaItem.file); - - // Save metadata to project-specific IndexedDB - const metadata: MediaFileData = { - id: mediaItem.id, - name: mediaItem.name, - type: mediaItem.type, - size: mediaItem.file.size, - lastModified: mediaItem.file.lastModified, - width: mediaItem.width, - height: mediaItem.height, - duration: mediaItem.duration, - }; - - await mediaMetadataAdapter.set(mediaItem.id, metadata); - } - - async loadMediaItem( - projectId: string, - id: string - ): Promise { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); - - const [file, metadata] = await Promise.all([ - mediaFilesAdapter.get(id), - mediaMetadataAdapter.get(id), - ]); - - if (!file || !metadata) return null; - - // Create new object URL for the file - const url = URL.createObjectURL(file); - - return { - id: metadata.id, - name: metadata.name, - type: metadata.type, - file, - url, - width: metadata.width, - height: metadata.height, - duration: metadata.duration, - // thumbnailUrl would need to be regenerated or cached separately - }; - } - - async loadAllMediaItems(projectId: string): Promise { - const { mediaMetadataAdapter } = this.getProjectMediaAdapters(projectId); - - const mediaIds = await mediaMetadataAdapter.list(); - const mediaItems: MediaItem[] = []; - - for (const id of mediaIds) { - const item = await this.loadMediaItem(projectId, id); - if (item) { - mediaItems.push(item); - } - } - - return mediaItems; - } - - async deleteMediaItem(projectId: string, id: string): Promise { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); - - await Promise.all([ - mediaFilesAdapter.remove(id), - mediaMetadataAdapter.remove(id), - ]); - } - - async deleteProjectMedia(projectId: string): Promise { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); - - await Promise.all([ - mediaMetadataAdapter.clear(), - mediaFilesAdapter.clear(), - ]); - } - - // Timeline operations - now project-specific - async saveTimeline( - projectId: string, - tracks: TimelineTrack[] - ): Promise { - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - const timelineData: TimelineData = { - tracks, - lastModified: new Date().toISOString(), - }; - await timelineAdapter.set("timeline", timelineData); - } - - async loadTimeline(projectId: string): Promise { - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - const timelineData = await timelineAdapter.get("timeline"); - return timelineData ? timelineData.tracks : null; - } - - async deleteProjectTimeline(projectId: string): Promise { - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - await timelineAdapter.remove("timeline"); - } - - // Utility methods - async clearAllData(): Promise { - // Clear all projects - await this.projectsAdapter.clear(); - - // Note: Project-specific media and timelines will be cleaned up when projects are deleted - } - - async getStorageInfo(): Promise<{ - projects: number; - isOPFSSupported: boolean; - isIndexedDBSupported: boolean; - }> { - const projectIds = await this.projectsAdapter.list(); - - return { - projects: projectIds.length, - isOPFSSupported: this.isOPFSSupported(), - isIndexedDBSupported: this.isIndexedDBSupported(), - }; - } - - async getProjectStorageInfo(projectId: string): Promise<{ - mediaItems: number; - hasTimeline: boolean; - }> { - const { mediaMetadataAdapter } = this.getProjectMediaAdapters(projectId); - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - - const [mediaIds, timelineData] = await Promise.all([ - mediaMetadataAdapter.list(), - timelineAdapter.get("timeline"), - ]); - - return { - mediaItems: mediaIds.length, - hasTimeline: !!timelineData, - }; - } - - // Check browser support - isOPFSSupported(): boolean { - return OPFSAdapter.isSupported(); - } - - isIndexedDBSupported(): boolean { - return "indexedDB" in window; - } - - isFullySupported(): boolean { - return this.isIndexedDBSupported() && this.isOPFSSupported(); - } + private projectsAdapter: IndexedDBAdapter; + private config: StorageConfig; + + constructor() { + this.config = { + projectsDb: "video-editor-projects", + mediaDb: "video-editor-media", + timelineDb: "video-editor-timelines", + version: 1, + }; + + this.projectsAdapter = new IndexedDBAdapter( + this.config.projectsDb, + "projects", + this.config.version, + ); + } + + // Helper to get project-specific media adapters + private getProjectMediaAdapters(projectId: string) { + const mediaMetadataAdapter = new IndexedDBAdapter( + `${this.config.mediaDb}-${projectId}`, + "media-metadata", + this.config.version, + ); + + const mediaFilesAdapter = new OPFSAdapter(`media-files-${projectId}`); + + return { mediaMetadataAdapter, mediaFilesAdapter }; + } + + // Helper to get project-specific timeline adapter + private getProjectTimelineAdapter(projectId: string) { + return new IndexedDBAdapter( + `${this.config.timelineDb}-${projectId}`, + "timeline", + this.config.version, + ); + } + + // Project operations + async saveProject(project: TProject): Promise { + // Convert TProject to serializable format + const serializedProject: SerializedProject = { + id: project.id, + name: project.name, + thumbnail: project.thumbnail, + createdAt: project.createdAt.toISOString(), + updatedAt: project.updatedAt.toISOString(), + backgroundColor: project.backgroundColor, + backgroundType: project.backgroundType, + blurIntensity: project.blurIntensity, + }; + + await this.projectsAdapter.set(project.id, serializedProject); + } + + async loadProject(id: string): Promise { + const serializedProject = await this.projectsAdapter.get(id); + + if (!serializedProject) return null; + + // Convert back to TProject format + return { + id: serializedProject.id, + name: serializedProject.name, + thumbnail: serializedProject.thumbnail, + createdAt: new Date(serializedProject.createdAt), + updatedAt: new Date(serializedProject.updatedAt), + backgroundColor: serializedProject.backgroundColor, + backgroundType: serializedProject.backgroundType, + blurIntensity: serializedProject.blurIntensity, + }; + } + + async loadAllProjects(): Promise { + const projectIds = await this.projectsAdapter.list(); + const projects: TProject[] = []; + + for (const id of projectIds) { + const project = await this.loadProject(id); + if (project) { + projects.push(project); + } + } + + // Sort by last updated (most recent first) + return projects.sort( + (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(), + ); + } + + async deleteProject(id: string): Promise { + await this.projectsAdapter.remove(id); + } + + // Media operations - now project-specific + async saveMediaItem(projectId: string, mediaItem: MediaItem): Promise { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); + + // Save file to project-specific OPFS + await mediaFilesAdapter.set(mediaItem.id, mediaItem.file); + + // Save metadata to project-specific IndexedDB + const metadata: MediaFileData = { + id: mediaItem.id, + name: mediaItem.name, + type: mediaItem.type, + size: mediaItem.file.size, + lastModified: mediaItem.file.lastModified, + width: mediaItem.width, + height: mediaItem.height, + duration: mediaItem.duration, + }; + + await mediaMetadataAdapter.set(mediaItem.id, metadata); + } + + async loadMediaItem( + projectId: string, + id: string, + ): Promise { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); + + const [file, metadata] = await Promise.all([ + mediaFilesAdapter.get(id), + mediaMetadataAdapter.get(id), + ]); + + if (!file || !metadata) return null; + + // Create new object URL for the file + const url = URL.createObjectURL(file); + + return { + id: metadata.id, + name: metadata.name, + type: metadata.type, + file, + url, + width: metadata.width, + height: metadata.height, + duration: metadata.duration, + // thumbnailUrl would need to be regenerated or cached separately + }; + } + + async loadAllMediaItems(projectId: string): Promise { + const { mediaMetadataAdapter } = this.getProjectMediaAdapters(projectId); + + const mediaIds = await mediaMetadataAdapter.list(); + const mediaItems: MediaItem[] = []; + + for (const id of mediaIds) { + const item = await this.loadMediaItem(projectId, id); + if (item) { + mediaItems.push(item); + } + } + + return mediaItems; + } + + async deleteMediaItem(projectId: string, id: string): Promise { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); + + await Promise.all([ + mediaFilesAdapter.remove(id), + mediaMetadataAdapter.remove(id), + ]); + } + + async deleteProjectMedia(projectId: string): Promise { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); + + await Promise.all([ + mediaMetadataAdapter.clear(), + mediaFilesAdapter.clear(), + ]); + } + + // Timeline operations - now project-specific + async saveTimeline( + projectId: string, + tracks: TimelineTrack[], + ): Promise { + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + const timelineData: TimelineData = { + tracks, + lastModified: new Date().toISOString(), + }; + await timelineAdapter.set("timeline", timelineData); + } + + async loadTimeline(projectId: string): Promise { + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + const timelineData = await timelineAdapter.get("timeline"); + return timelineData ? timelineData.tracks : null; + } + + async deleteProjectTimeline(projectId: string): Promise { + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + await timelineAdapter.remove("timeline"); + } + + // Utility methods + async clearAllData(): Promise { + // Clear all projects + await this.projectsAdapter.clear(); + + // Note: Project-specific media and timelines will be cleaned up when projects are deleted + } + + async getStorageInfo(): Promise<{ + projects: number; + isOPFSSupported: boolean; + isIndexedDBSupported: boolean; + }> { + const projectIds = await this.projectsAdapter.list(); + + return { + projects: projectIds.length, + isOPFSSupported: this.isOPFSSupported(), + isIndexedDBSupported: this.isIndexedDBSupported(), + }; + } + + async getProjectStorageInfo(projectId: string): Promise<{ + mediaItems: number; + hasTimeline: boolean; + }> { + const { mediaMetadataAdapter } = this.getProjectMediaAdapters(projectId); + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + + const [mediaIds, timelineData] = await Promise.all([ + mediaMetadataAdapter.list(), + timelineAdapter.get("timeline"), + ]); + + return { + mediaItems: mediaIds.length, + hasTimeline: !!timelineData, + }; + } + + // Check browser support + isOPFSSupported(): boolean { + return OPFSAdapter.isSupported(); + } + + isIndexedDBSupported(): boolean { + return "indexedDB" in window; + } + + isFullySupported(): boolean { + return this.isIndexedDBSupported() && this.isOPFSSupported(); + } } // Export singleton instance -- cgit v1.3.1