diff options
| author | Simon Orzel <simonorzel@gmail.com> | 2025-07-20 18:35:06 +0200 |
|---|---|---|
| committer | Simon Orzel <simonorzel@gmail.com> | 2025-07-20 18:35:06 +0200 |
| commit | b03287df8d6fa14153af264c6a3e3da5b2a06153 (patch) | |
| tree | 100b2c2a729295419dce61655bca418e74195ec4 /apps/web/src/lib/storage | |
| parent | 061624ab588afff18073ae62fbac425c1aa5cf3a (diff) | |
| parent | ba3da43728bc051aa033dc7b45042b0f0f788e63 (diff) | |
| download | opencut-b03287df8d6fa14153af264c6a3e3da5b2a06153.tar.gz | |
Merge branch 'main' into feat/project-preview-images
Diffstat (limited to 'apps/web/src/lib/storage')
| -rw-r--r-- | apps/web/src/lib/storage/indexeddb-adapter.ts | 178 | ||||
| -rw-r--r-- | apps/web/src/lib/storage/opfs-adapter.ts | 114 | ||||
| -rw-r--r-- | apps/web/src/lib/storage/storage-service.ts | 440 | ||||
| -rw-r--r-- | apps/web/src/lib/storage/types.ts | 54 |
4 files changed, 393 insertions, 393 deletions
diff --git a/apps/web/src/lib/storage/indexeddb-adapter.ts b/apps/web/src/lib/storage/indexeddb-adapter.ts index b599e2df..f43a8798 100644 --- a/apps/web/src/lib/storage/indexeddb-adapter.ts +++ b/apps/web/src/lib/storage/indexeddb-adapter.ts @@ -1,89 +1,89 @@ -import { StorageAdapter } from "./types"; - -export class IndexedDBAdapter<T> implements StorageAdapter<T> { - private dbName: string; - private storeName: string; - private version: number; - - constructor(dbName: string, storeName: string, version: number = 1) { - this.dbName = dbName; - this.storeName = storeName; - this.version = version; - } - - private async getDB(): Promise<IDBDatabase> { - return new Promise((resolve, reject) => { - const request = indexedDB.open(this.dbName, this.version); - - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result); - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result; - if (!db.objectStoreNames.contains(this.storeName)) { - db.createObjectStore(this.storeName, { keyPath: "id" }); - } - }; - }); - } - - async get(key: string): Promise<T | null> { - const db = await this.getDB(); - const transaction = db.transaction([this.storeName], "readonly"); - const store = transaction.objectStore(this.storeName); - - return new Promise((resolve, reject) => { - const request = store.get(key); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result || null); - }); - } - - async set(key: string, value: T): Promise<void> { - const db = await this.getDB(); - const transaction = db.transaction([this.storeName], "readwrite"); - const store = transaction.objectStore(this.storeName); - - return new Promise((resolve, reject) => { - const request = store.put({ id: key, ...value }); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(); - }); - } - - async remove(key: string): Promise<void> { - const db = await this.getDB(); - const transaction = db.transaction([this.storeName], "readwrite"); - const store = transaction.objectStore(this.storeName); - - return new Promise((resolve, reject) => { - const request = store.delete(key); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(); - }); - } - - async list(): Promise<string[]> { - const db = await this.getDB(); - const transaction = db.transaction([this.storeName], "readonly"); - const store = transaction.objectStore(this.storeName); - - return new Promise((resolve, reject) => { - const request = store.getAllKeys(); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result as string[]); - }); - } - - async clear(): Promise<void> { - const db = await this.getDB(); - const transaction = db.transaction([this.storeName], "readwrite"); - const store = transaction.objectStore(this.storeName); - - return new Promise((resolve, reject) => { - const request = store.clear(); - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(); - }); - } -} +import { StorageAdapter } from "./types";
+
+export class IndexedDBAdapter<T> implements StorageAdapter<T> {
+ private dbName: string;
+ private storeName: string;
+ private version: number;
+
+ constructor(dbName: string, storeName: string, version: number = 1) {
+ this.dbName = dbName;
+ this.storeName = storeName;
+ this.version = version;
+ }
+
+ private async getDB(): Promise<IDBDatabase> {
+ return new Promise((resolve, reject) => {
+ const request = indexedDB.open(this.dbName, this.version);
+
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve(request.result);
+
+ request.onupgradeneeded = (event) => {
+ const db = (event.target as IDBOpenDBRequest).result;
+ if (!db.objectStoreNames.contains(this.storeName)) {
+ db.createObjectStore(this.storeName, { keyPath: "id" });
+ }
+ };
+ });
+ }
+
+ async get(key: string): Promise<T | null> {
+ const db = await this.getDB();
+ const transaction = db.transaction([this.storeName], "readonly");
+ const store = transaction.objectStore(this.storeName);
+
+ return new Promise((resolve, reject) => {
+ const request = store.get(key);
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve(request.result || null);
+ });
+ }
+
+ async set(key: string, value: T): Promise<void> {
+ const db = await this.getDB();
+ const transaction = db.transaction([this.storeName], "readwrite");
+ const store = transaction.objectStore(this.storeName);
+
+ return new Promise((resolve, reject) => {
+ const request = store.put({ id: key, ...value });
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve();
+ });
+ }
+
+ async remove(key: string): Promise<void> {
+ const db = await this.getDB();
+ const transaction = db.transaction([this.storeName], "readwrite");
+ const store = transaction.objectStore(this.storeName);
+
+ return new Promise((resolve, reject) => {
+ const request = store.delete(key);
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve();
+ });
+ }
+
+ async list(): Promise<string[]> {
+ const db = await this.getDB();
+ const transaction = db.transaction([this.storeName], "readonly");
+ const store = transaction.objectStore(this.storeName);
+
+ return new Promise((resolve, reject) => {
+ const request = store.getAllKeys();
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve(request.result as string[]);
+ });
+ }
+
+ async clear(): Promise<void> {
+ const db = await this.getDB();
+ const transaction = db.transaction([this.storeName], "readwrite");
+ const store = transaction.objectStore(this.storeName);
+
+ return new Promise((resolve, reject) => {
+ const request = store.clear();
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve();
+ });
+ }
+}
diff --git a/apps/web/src/lib/storage/opfs-adapter.ts b/apps/web/src/lib/storage/opfs-adapter.ts index ccd9735d..c01954e5 100644 --- a/apps/web/src/lib/storage/opfs-adapter.ts +++ b/apps/web/src/lib/storage/opfs-adapter.ts @@ -1,73 +1,73 @@ import { StorageAdapter } from "./types"; export class OPFSAdapter implements StorageAdapter<File> { - private directoryName: string; + private directoryName: string; - constructor(directoryName: string = "media") { - this.directoryName = directoryName; - } + constructor(directoryName: string = "media") { + this.directoryName = directoryName; + } - private async getDirectory(): Promise<FileSystemDirectoryHandle> { - const opfsRoot = await navigator.storage.getDirectory(); - return await opfsRoot.getDirectoryHandle(this.directoryName, { - create: true, - }); - } + private async getDirectory(): Promise<FileSystemDirectoryHandle> { + const opfsRoot = await navigator.storage.getDirectory(); + return await opfsRoot.getDirectoryHandle(this.directoryName, { + create: true, + }); + } - async get(key: string): Promise<File | null> { - try { - const directory = await this.getDirectory(); - const fileHandle = await directory.getFileHandle(key); - return await fileHandle.getFile(); - } catch (error) { - if ((error as Error).name === "NotFoundError") { - return null; - } - throw error; - } - } + async get(key: string): Promise<File | null> { + try { + const directory = await this.getDirectory(); + const fileHandle = await directory.getFileHandle(key); + return await fileHandle.getFile(); + } catch (error) { + if ((error as Error).name === "NotFoundError") { + return null; + } + throw error; + } + } - async set(key: string, file: File): Promise<void> { - const directory = await this.getDirectory(); - const fileHandle = await directory.getFileHandle(key, { create: true }); - const writable = await fileHandle.createWritable(); + async set(key: string, file: File): Promise<void> { + const directory = await this.getDirectory(); + const fileHandle = await directory.getFileHandle(key, { create: true }); + const writable = await fileHandle.createWritable(); - await writable.write(file); - await writable.close(); - } + await writable.write(file); + await writable.close(); + } - async remove(key: string): Promise<void> { - try { - const directory = await this.getDirectory(); - await directory.removeEntry(key); - } catch (error) { - if ((error as Error).name !== "NotFoundError") { - throw error; - } - } - } + async remove(key: string): Promise<void> { + try { + const directory = await this.getDirectory(); + await directory.removeEntry(key); + } catch (error) { + if ((error as Error).name !== "NotFoundError") { + throw error; + } + } + } - async list(): Promise<string[]> { - const directory = await this.getDirectory(); - const keys: string[] = []; + async list(): Promise<string[]> { + const directory = await this.getDirectory(); + const keys: string[] = []; - for await (const name of directory.keys()) { - keys.push(name); - } + for await (const name of directory.keys()) { + keys.push(name); + } - return keys; - } + return keys; + } - async clear(): Promise<void> { - const directory = await this.getDirectory(); + async clear(): Promise<void> { + const directory = await this.getDirectory(); - for await (const name of directory.keys()) { - await directory.removeEntry(name); - } - } + for await (const name of directory.keys()) { + await directory.removeEntry(name); + } + } - // Helper method to check OPFS support - static isSupported(): boolean { - return "storage" in navigator && "getDirectory" in navigator.storage; - } + // Helper method to check OPFS support + static isSupported(): boolean { + return "storage" in navigator && "getDirectory" in navigator.storage; + } } diff --git a/apps/web/src/lib/storage/storage-service.ts b/apps/web/src/lib/storage/storage-service.ts index 044f2dff..0037dbdb 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<SerializedProject>; - private config: StorageConfig; + private projectsAdapter: IndexedDBAdapter<SerializedProject>; + private config: StorageConfig; - constructor() { - this.config = { - projectsDb: "video-editor-projects", - mediaDb: "video-editor-media", - timelineDb: "video-editor-timelines", - version: 1, - }; + constructor() { + this.config = { + projectsDb: "video-editor-projects", + mediaDb: "video-editor-media", + timelineDb: "video-editor-timelines", + version: 1, + }; - this.projectsAdapter = new IndexedDBAdapter<SerializedProject>( - this.config.projectsDb, - "projects", - this.config.version, - ); - } + this.projectsAdapter = new IndexedDBAdapter<SerializedProject>( + this.config.projectsDb, + "projects", + this.config.version + ); + } - // Helper to get project-specific media adapters - private getProjectMediaAdapters(projectId: string) { - const mediaMetadataAdapter = new IndexedDBAdapter<MediaFileData>( - `${this.config.mediaDb}-${projectId}`, - "media-metadata", - this.config.version, - ); + // Helper to get project-specific media adapters + private getProjectMediaAdapters(projectId: string) { + const mediaMetadataAdapter = new IndexedDBAdapter<MediaFileData>( + `${this.config.mediaDb}-${projectId}`, + "media-metadata", + this.config.version + ); - const mediaFilesAdapter = new OPFSAdapter(`media-files-${projectId}`); + const mediaFilesAdapter = new OPFSAdapter(`media-files-${projectId}`); - return { mediaMetadataAdapter, mediaFilesAdapter }; - } + return { mediaMetadataAdapter, mediaFilesAdapter }; + } - // Helper to get project-specific timeline adapter - private getProjectTimelineAdapter(projectId: string) { - return new IndexedDBAdapter<TimelineData>( - `${this.config.timelineDb}-${projectId}`, - "timeline", - this.config.version, - ); - } + // Helper to get project-specific timeline adapter + private getProjectTimelineAdapter(projectId: string) { + return new IndexedDBAdapter<TimelineData>( + `${this.config.timelineDb}-${projectId}`, + "timeline", + this.config.version + ); + } - // Project operations - async saveProject(project: TProject): Promise<void> { - // 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, - }; + // Project operations + async saveProject(project: TProject): Promise<void> { + // 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); - } + await this.projectsAdapter.set(project.id, serializedProject); + } - async loadProject(id: string): Promise<TProject | null> { - const serializedProject = await this.projectsAdapter.get(id); + async loadProject(id: string): Promise<TProject | null> { + const serializedProject = await this.projectsAdapter.get(id); - if (!serializedProject) return null; + 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, - }; - } + // 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<TProject[]> { - const projectIds = await this.projectsAdapter.list(); - const projects: TProject[] = []; + async loadAllProjects(): Promise<TProject[]> { + 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); - } - } + 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(), - ); - } + // Sort by last updated (most recent first) + return projects.sort( + (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() + ); + } - async deleteProject(id: string): Promise<void> { - await this.projectsAdapter.remove(id); - } + async deleteProject(id: string): Promise<void> { + await this.projectsAdapter.remove(id); + } - // Media operations - now project-specific - async saveMediaItem(projectId: string, mediaItem: MediaItem): Promise<void> { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); + // Media operations - now project-specific + async saveMediaItem(projectId: string, mediaItem: MediaItem): Promise<void> { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); - // Save file to project-specific OPFS - await mediaFilesAdapter.set(mediaItem.id, mediaItem.file); + // 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, - }; + // 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); - } + await mediaMetadataAdapter.set(mediaItem.id, metadata); + } - async loadMediaItem( - projectId: string, - id: string, - ): Promise<MediaItem | null> { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); + async loadMediaItem( + projectId: string, + id: string + ): Promise<MediaItem | null> { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); - const [file, metadata] = await Promise.all([ - mediaFilesAdapter.get(id), - mediaMetadataAdapter.get(id), - ]); + const [file, metadata] = await Promise.all([ + mediaFilesAdapter.get(id), + mediaMetadataAdapter.get(id), + ]); - if (!file || !metadata) return null; + if (!file || !metadata) return null; - // Create new object URL for the file - const url = URL.createObjectURL(file); + // 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 - }; - } + 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<MediaItem[]> { - const { mediaMetadataAdapter } = this.getProjectMediaAdapters(projectId); + async loadAllMediaItems(projectId: string): Promise<MediaItem[]> { + const { mediaMetadataAdapter } = this.getProjectMediaAdapters(projectId); - const mediaIds = await mediaMetadataAdapter.list(); - const mediaItems: MediaItem[] = []; + 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); - } - } + for (const id of mediaIds) { + const item = await this.loadMediaItem(projectId, id); + if (item) { + mediaItems.push(item); + } + } - return mediaItems; - } + return mediaItems; + } - async deleteMediaItem(projectId: string, id: string): Promise<void> { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); + async deleteMediaItem(projectId: string, id: string): Promise<void> { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); - await Promise.all([ - mediaFilesAdapter.remove(id), - mediaMetadataAdapter.remove(id), - ]); - } + await Promise.all([ + mediaFilesAdapter.remove(id), + mediaMetadataAdapter.remove(id), + ]); + } - async deleteProjectMedia(projectId: string): Promise<void> { - const { mediaMetadataAdapter, mediaFilesAdapter } = - this.getProjectMediaAdapters(projectId); + async deleteProjectMedia(projectId: string): Promise<void> { + const { mediaMetadataAdapter, mediaFilesAdapter } = + this.getProjectMediaAdapters(projectId); - await Promise.all([ - mediaMetadataAdapter.clear(), - mediaFilesAdapter.clear(), - ]); - } + await Promise.all([ + mediaMetadataAdapter.clear(), + mediaFilesAdapter.clear(), + ]); + } - // Timeline operations - now project-specific - async saveTimeline( - projectId: string, - tracks: TimelineTrack[], - ): Promise<void> { - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - const timelineData: TimelineData = { - tracks, - lastModified: new Date().toISOString(), - }; - await timelineAdapter.set("timeline", timelineData); - } + // Timeline operations - now project-specific + async saveTimeline( + projectId: string, + tracks: TimelineTrack[] + ): Promise<void> { + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + const timelineData: TimelineData = { + tracks, + lastModified: new Date().toISOString(), + }; + await timelineAdapter.set("timeline", timelineData); + } - async loadTimeline(projectId: string): Promise<TimelineTrack[] | null> { - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - const timelineData = await timelineAdapter.get("timeline"); - return timelineData ? timelineData.tracks : null; - } + async loadTimeline(projectId: string): Promise<TimelineTrack[] | null> { + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + const timelineData = await timelineAdapter.get("timeline"); + return timelineData ? timelineData.tracks : null; + } - async deleteProjectTimeline(projectId: string): Promise<void> { - const timelineAdapter = this.getProjectTimelineAdapter(projectId); - await timelineAdapter.remove("timeline"); - } + async deleteProjectTimeline(projectId: string): Promise<void> { + const timelineAdapter = this.getProjectTimelineAdapter(projectId); + await timelineAdapter.remove("timeline"); + } - // Utility methods - async clearAllData(): Promise<void> { - // Clear all projects - await this.projectsAdapter.clear(); + // Utility methods + async clearAllData(): Promise<void> { + // Clear all projects + await this.projectsAdapter.clear(); - // Note: Project-specific media and timelines will be cleaned up when projects are deleted - } + // 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(); + 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(), - }; - } + 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); + 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"), - ]); + const [mediaIds, timelineData] = await Promise.all([ + mediaMetadataAdapter.list(), + timelineAdapter.get("timeline"), + ]); - return { - mediaItems: mediaIds.length, - hasTimeline: !!timelineData, - }; - } + return { + mediaItems: mediaIds.length, + hasTimeline: !!timelineData, + }; + } - // Check browser support - isOPFSSupported(): boolean { - return OPFSAdapter.isSupported(); - } + // Check browser support + isOPFSSupported(): boolean { + return OPFSAdapter.isSupported(); + } - isIndexedDBSupported(): boolean { - return "indexedDB" in window; - } + isIndexedDBSupported(): boolean { + return "indexedDB" in window; + } - isFullySupported(): boolean { - return this.isIndexedDBSupported() && this.isOPFSSupported(); - } + isFullySupported(): boolean { + return this.isIndexedDBSupported() && this.isOPFSSupported(); + } } // Export singleton instance diff --git a/apps/web/src/lib/storage/types.ts b/apps/web/src/lib/storage/types.ts index 1adbb3b8..b662c164 100644 --- a/apps/web/src/lib/storage/types.ts +++ b/apps/web/src/lib/storage/types.ts @@ -2,48 +2,48 @@ import { TProject } from "@/types/project"; import { TimelineTrack } from "@/types/timeline"; export interface StorageAdapter<T> { - get(key: string): Promise<T | null>; - set(key: string, value: T): Promise<void>; - remove(key: string): Promise<void>; - list(): Promise<string[]>; - clear(): Promise<void>; + get(key: string): Promise<T | null>; + set(key: string, value: T): Promise<void>; + remove(key: string): Promise<void>; + list(): Promise<string[]>; + clear(): Promise<void>; } export interface MediaFileData { - id: string; - name: string; - type: "image" | "video" | "audio"; - size: number; - lastModified: number; - width?: number; - height?: number; - duration?: number; - // File will be stored separately in OPFS + id: string; + name: string; + type: "image" | "video" | "audio"; + size: number; + lastModified: number; + width?: number; + height?: number; + duration?: number; + // File will be stored separately in OPFS } export interface TimelineData { - tracks: TimelineTrack[]; - lastModified: string; + tracks: TimelineTrack[]; + lastModified: string; } export interface StorageConfig { - projectsDb: string; - mediaDb: string; - timelineDb: string; - version: number; + projectsDb: string; + mediaDb: string; + timelineDb: string; + version: number; } // Helper type for serialization - converts Date objects to strings export type SerializedProject = Omit<TProject, "createdAt" | "updatedAt"> & { - createdAt: string; - updatedAt: string; + createdAt: string; + updatedAt: string; }; // Extend FileSystemDirectoryHandle with missing async iterator methods declare global { - interface FileSystemDirectoryHandle { - keys(): AsyncIterableIterator<string>; - values(): AsyncIterableIterator<FileSystemHandle>; - entries(): AsyncIterableIterator<[string, FileSystemHandle]>; - } + interface FileSystemDirectoryHandle { + keys(): AsyncIterableIterator<string>; + values(): AsyncIterableIterator<FileSystemHandle>; + entries(): AsyncIterableIterator<[string, FileSystemHandle]>; + } } |
