diff --git a/src/lib/recentFiles.ts b/src/lib/recentFiles.ts new file mode 100644 index 0000000..3f1b92e --- /dev/null +++ b/src/lib/recentFiles.ts @@ -0,0 +1,122 @@ +// Armazena os arquivos GLB recentes (após conversão) para reuso rápido, +// sem necessidade de recarregar da nuvem ou do disco local. +// +// Estratégia: +// - Metadados (lista JSON ordenada) em localStorage → leitura síncrona / rápida +// - Blob real do GLB em IndexedDB → suporta arquivos grandes + +const META_KEY = 'tsxr_recent_files_v1'; +const DB_NAME = 'tsxr-recent'; +const STORE = 'glb'; +const MAX_RECENTS = 6; + +export interface RecentFileMeta { + id: string; // chave no IndexedDB + fileName: string; // nome do arquivo GLB + fileSize: number; // bytes + addedAt: number; // timestamp ms +} + +// ── IndexedDB helpers ───────────────────────────────────────────── +function openDB(): Promise { + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, 1); + req.onupgradeneeded = () => { + const db = req.result; + if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); +} + +async function idbPut(id: string, blob: Blob): Promise { + const db = await openDB(); + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE, 'readwrite'); + tx.objectStore(STORE).put(blob, id); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); + db.close(); +} + +async function idbGet(id: string): Promise { + const db = await openDB(); + const blob = await new Promise((resolve, reject) => { + const tx = db.transaction(STORE, 'readonly'); + const req = tx.objectStore(STORE).get(id); + req.onsuccess = () => resolve(req.result as Blob | undefined); + req.onerror = () => reject(req.error); + }); + db.close(); + return blob; +} + +async function idbDelete(id: string): Promise { + const db = await openDB(); + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE, 'readwrite'); + tx.objectStore(STORE).delete(id); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); + db.close(); +} + +// ── Metadata helpers ───────────────────────────────────────────── +export function listRecentFiles(): RecentFileMeta[] { + try { + const raw = localStorage.getItem(META_KEY); + if (!raw) return []; + const parsed = JSON.parse(raw) as RecentFileMeta[]; + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } +} + +function saveMeta(list: RecentFileMeta[]) { + try { localStorage.setItem(META_KEY, JSON.stringify(list)); } catch {} +} + +/** Registra um GLB nos recentes (substitui se mesmo fileName) e mantém apenas MAX_RECENTS. */ +export async function addRecentFile(fileName: string, blob: Blob): Promise { + try { + const list = listRecentFiles(); + const existing = list.find(r => r.fileName === fileName); + const id = existing?.id ?? `rf_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; + await idbPut(id, blob); + const filtered = list.filter(r => r.fileName !== fileName); + const next: RecentFileMeta[] = [ + { id, fileName, fileSize: blob.size, addedAt: Date.now() }, + ...filtered, + ].slice(0, MAX_RECENTS); + // Limpa blobs removidos + for (const r of filtered.slice(MAX_RECENTS - 1)) { + try { await idbDelete(r.id); } catch {} + } + saveMeta(next); + } catch (e) { + console.warn('[recentFiles] addRecent failed', e); + } +} + +/** Recupera o blob de um recente e devolve um object URL pronto para uso. */ +export async function loadRecentFile(id: string): Promise<{ url: string; blob: Blob } | null> { + const blob = await idbGet(id); + if (!blob) return null; + return { url: URL.createObjectURL(blob), blob }; +} + +export async function removeRecentFile(id: string): Promise { + const list = listRecentFiles().filter(r => r.id !== id); + saveMeta(list); + try { await idbDelete(id); } catch {} +} + +export async function clearRecentFiles(): Promise { + const list = listRecentFiles(); + for (const r of list) { try { await idbDelete(r.id); } catch {} } + saveMeta([]); +}