summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/storage
diff options
context:
space:
mode:
authorHyteq <DerpsDer.es@gmail.com>2025-07-20 11:00:22 +0300
committerHyteq <DerpsDer.es@gmail.com>2025-07-20 11:00:22 +0300
commitbe1ae20d9089e81cb84a8294be76fe680a24b60f (patch)
tree48472c1122d35f45f802bbd1c3cf2c15f653f9cf /apps/web/src/lib/storage
parentede08dac416b47eac2db151969c164bdf0f06002 (diff)
downloadopencut-be1ae20d9089e81cb84a8294be76fe680a24b60f.tar.gz
format
Diffstat (limited to 'apps/web/src/lib/storage')
-rw-r--r--apps/web/src/lib/storage/indexeddb-adapter.ts178
-rw-r--r--apps/web/src/lib/storage/opfs-adapter.ts114
-rw-r--r--apps/web/src/lib/storage/storage-service.ts440
-rw-r--r--apps/web/src/lib/storage/types.ts54
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 f43a8798..b599e2df 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 c01954e5..ccd9735d 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 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<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 b662c164..1adbb3b8 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]>;
+ }
}