diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx index 9d6acc8..b44c9fe 100644 --- a/app/[lang]/page.tsx +++ b/app/[lang]/page.tsx @@ -12,7 +12,6 @@ import { import { useDiagram } from "@/contexts/diagram-context" import { type DrawioTheme, isDrawioTheme } from "@/lib/drawio-themes" import { i18n, type Locale } from "@/lib/i18n/config" -import { isIndexedDBUsable } from "@/lib/session-storage" export default function Home() { const { @@ -33,8 +32,6 @@ export default function Home() { const [isLoaded, setIsLoaded] = useState(false) const [isDrawioReady, setIsDrawioReady] = useState(false) const [isElectron, setIsElectron] = useState(false) - const [canPersist, setCanPersist] = useState(false) - const [canPersistChecked, setCanPersistChecked] = useState(false) const [drawioBaseUrl, setDrawioBaseUrl] = useState( process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net", ) @@ -85,11 +82,6 @@ export default function Home() { setDrawioBaseUrl(`${window.location.origin}/drawio/index.html`) } - void (async () => { - const usable = await isIndexedDBUsable() - setCanPersist(usable) - setCanPersistChecked(true) - })() setIsLoaded(true) }, [pathname, router]) @@ -98,13 +90,6 @@ export default function Home() { onDrawioLoad() }, [onDrawioLoad]) - const handleDrawioAutoSave = useCallback( - (data: { xml?: string }) => { - handleDiagramAutoSave(data) - }, - [handleDiagramAutoSave], - ) - const handleDarkModeChange = () => { const newValue = !darkMode setDarkMode(newValue) @@ -187,7 +172,7 @@ export default function Home() { }`} >
- {isLoaded && canPersistChecked && ( + {isLoaded && (
@@ -195,24 +180,14 @@ export default function Home() { key={`${drawioUi}-${darkMode}-${currentLang}-${isElectron}`} ref={drawioRef} autosave - onAutoSave={handleDrawioAutoSave} + onAutoSave={handleDiagramAutoSave} onExport={handleDiagramExport} onLoad={handleDrawioLoad} baseUrl={drawioBaseUrl} - configuration={ - canPersist - ? { confirmExit: false } - : undefined - } urlParameters={{ ui: drawioUi, spin: false, libraries: false, - // Disable modified tracking only when persistence is available - ...(canPersist && { - modified: false, - keepmodified: false, - }), saveAndExit: false, noSaveBtn: true, noExitBtn: true, diff --git a/lib/session-storage.ts b/lib/session-storage.ts index 4a51d66..8450375 100644 --- a/lib/session-storage.ts +++ b/lib/session-storage.ts @@ -58,33 +58,6 @@ interface ChatSessionDB extends DBSchema { // Database singleton let dbPromise: Promise> | null = null -const resetDBPromise = () => { - dbPromise = null -} - -const isClosingError = (error: unknown): boolean => { - return ( - error instanceof DOMException && - error.name === "InvalidStateError" && - /closing/i.test(error.message) - ) -} - -const withDB = async ( - action: (db: IDBPDatabase) => Promise, -): Promise => { - try { - const db = await getDB() - return await action(db) - } catch (error) { - if (isClosingError(error)) { - resetDBPromise() - const db = await getDB() - return await action(db) - } - throw error - } -} async function getDB(): Promise> { if (!dbPromise) { @@ -115,23 +88,7 @@ async function getDB(): Promise> { } } }, - terminated() { - resetDBPromise() - }, }) - dbPromise - .then((db) => { - db.onversionchange = () => { - db.close() - resetDBPromise() - } - db.onclose = () => { - resetDBPromise() - } - }) - .catch(() => { - resetDBPromise() - }) } return dbPromise } @@ -146,46 +103,31 @@ export function isIndexedDBAvailable(): boolean { } } -// Check if IndexedDB is actually usable (not just present). -// Note: Do NOT close the db here - getDB() returns a shared singleton connection -// that other code depends on. -export async function isIndexedDBUsable(): Promise { - if (!isIndexedDBAvailable()) return false - try { - await getDB() - return true - } catch { - return false - } -} - // CRUD Operations export async function getAllSessionMetadata(): Promise { if (!isIndexedDBAvailable()) return [] try { - return await withDB(async (db) => { - const tx = db.transaction(STORE_NAME, "readonly") - const index = tx.store.index("by-updated") - const metadata: SessionMetadata[] = [] + const db = await getDB() + const tx = db.transaction(STORE_NAME, "readonly") + const index = tx.store.index("by-updated") + const metadata: SessionMetadata[] = [] - // Use cursor to read only metadata fields (avoids loading full messages/XML) - let cursor = await index.openCursor(null, "prev") // newest first - while (cursor) { - const s = cursor.value - metadata.push({ - id: s.id, - title: s.title, - createdAt: s.createdAt, - updatedAt: s.updatedAt, - messageCount: s.messages.length, - hasDiagram: - !!s.diagramXml && s.diagramXml.trim().length > 0, - thumbnailDataUrl: s.thumbnailDataUrl, - }) - cursor = await cursor.continue() - } - return metadata - }) + // Use cursor to read only metadata fields (avoids loading full messages/XML) + let cursor = await index.openCursor(null, "prev") // newest first + while (cursor) { + const s = cursor.value + metadata.push({ + id: s.id, + title: s.title, + createdAt: s.createdAt, + updatedAt: s.updatedAt, + messageCount: s.messages.length, + hasDiagram: !!s.diagramXml && s.diagramXml.trim().length > 0, + thumbnailDataUrl: s.thumbnailDataUrl, + }) + cursor = await cursor.continue() + } + return metadata } catch (error) { console.error("Failed to get session metadata:", error) return [] @@ -195,9 +137,8 @@ export async function getAllSessionMetadata(): Promise { export async function getSession(id: string): Promise { if (!isIndexedDBAvailable()) return null try { - return await withDB(async (db) => { - return (await db.get(STORE_NAME, id)) || null - }) + const db = await getDB() + return (await db.get(STORE_NAME, id)) || null } catch (error) { console.error("Failed to get session:", error) return null @@ -207,9 +148,8 @@ export async function getSession(id: string): Promise { export async function saveSession(session: ChatSession): Promise { if (!isIndexedDBAvailable()) return false try { - await withDB(async (db) => { - await db.put(STORE_NAME, session) - }) + const db = await getDB() + await db.put(STORE_NAME, session) return true } catch (error) { // Handle quota exceeded @@ -221,9 +161,8 @@ export async function saveSession(session: ChatSession): Promise { await deleteOldestSession() // Retry once try { - await withDB(async (db) => { - await db.put(STORE_NAME, session) - }) + const db = await getDB() + await db.put(STORE_NAME, session) return true } catch (retryError) { console.error( @@ -242,9 +181,8 @@ export async function saveSession(session: ChatSession): Promise { export async function deleteSession(id: string): Promise { if (!isIndexedDBAvailable()) return try { - await withDB(async (db) => { - await db.delete(STORE_NAME, id) - }) + const db = await getDB() + await db.delete(STORE_NAME, id) } catch (error) { console.error("Failed to delete session:", error) } @@ -253,9 +191,8 @@ export async function deleteSession(id: string): Promise { export async function getSessionCount(): Promise { if (!isIndexedDBAvailable()) return 0 try { - return await withDB(async (db) => { - return await db.count(STORE_NAME) - }) + const db = await getDB() + return await db.count(STORE_NAME) } catch (error) { console.error("Failed to get session count:", error) return 0 @@ -265,15 +202,14 @@ export async function getSessionCount(): Promise { export async function deleteOldestSession(): Promise { if (!isIndexedDBAvailable()) return try { - await withDB(async (db) => { - const tx = db.transaction(STORE_NAME, "readwrite") - const index = tx.store.index("by-updated") - const cursor = await index.openCursor() - if (cursor) { - await cursor.delete() - } - await tx.done - }) + const db = await getDB() + const tx = db.transaction(STORE_NAME, "readwrite") + const index = tx.store.index("by-updated") + const cursor = await index.openCursor() + if (cursor) { + await cursor.delete() + } + await tx.done } catch (error) { console.error("Failed to delete oldest session:", error) } diff --git a/lib/template-storage.ts b/lib/template-storage.ts index 65c533a..8b0e451 100644 --- a/lib/template-storage.ts +++ b/lib/template-storage.ts @@ -57,33 +57,6 @@ export function generateDefaultTitle(prompt: string): string { // Database singleton let dbPromise: Promise> | null = null -const resetDBPromise = () => { - dbPromise = null -} - -const isClosingError = (error: unknown): boolean => { - return ( - error instanceof DOMException && - error.name === "InvalidStateError" && - /closing/i.test(error.message) - ) -} - -const withDB = async ( - action: (db: IDBPDatabase) => Promise, -): Promise => { - try { - const db = await getDB() - return await action(db) - } catch (error) { - if (isClosingError(error)) { - resetDBPromise() - const db = await getDB() - return await action(db) - } - throw error - } -} async function getDB(): Promise> { if (!dbPromise) { @@ -101,23 +74,7 @@ async function getDB(): Promise> { } } }, - terminated() { - resetDBPromise() - }, }) - dbPromise - .then((db) => { - db.onversionchange = () => { - db.close() - resetDBPromise() - } - db.onclose = () => { - resetDBPromise() - } - }) - .catch(() => { - resetDBPromise() - }) } return dbPromise } @@ -137,10 +94,9 @@ export function isIndexedDBAvailable(): boolean { export async function getAllTemplates(): Promise { if (!isIndexedDBAvailable()) return [] try { - return await withDB(async (db) => { - const templates = await db.getAll(STORE_NAME) - return sortTemplates(templates) - }) + const db = await getDB() + const templates = await db.getAll(STORE_NAME) + return sortTemplates(templates) } catch (error) { console.error("Failed to get templates:", error) return [] @@ -150,9 +106,8 @@ export async function getAllTemplates(): Promise { export async function getTemplate(id: string): Promise