From 315ef9def0e9c6ba4259140182f656931a164079 Mon Sep 17 00:00:00 2001 From: lashman Date: Wed, 31 Dec 2025 20:08:59 +0200 Subject: [PATCH] offline puzzle cache --- frontend/src/offline.ts | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 frontend/src/offline.ts diff --git a/frontend/src/offline.ts b/frontend/src/offline.ts new file mode 100644 index 0000000..b4da499 --- /dev/null +++ b/frontend/src/offline.ts @@ -0,0 +1,73 @@ +import type { GenerateResponse } from './types' + +const DB_NAME = 'cruciverb-offline' +const DB_VERSION = 1 + +function openDB(): Promise { + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, DB_VERSION) + req.onupgradeneeded = () => { + const db = req.result + if (!db.objectStoreNames.contains('puzzles')) { + db.createObjectStore('puzzles', { keyPath: 'id' }) + } + if (!db.objectStoreNames.contains('progress')) { + db.createObjectStore('progress', { keyPath: 'puzzleId' }) + } + if (!db.objectStoreNames.contains('pending')) { + db.createObjectStore('pending', { keyPath: 'id', autoIncrement: true }) + } + } + req.onsuccess = () => resolve(req.result) + req.onerror = () => reject(req.error) + }) +} + +export async function cachePuzzle(id: string, data: GenerateResponse): Promise { + const db = await openDB() + const tx = db.transaction('puzzles', 'readwrite') + tx.objectStore('puzzles').put({ id, data, cached_at: Date.now() }) + return new Promise((resolve, reject) => { tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error) }) +} + +export async function getCachedPuzzle(id: string): Promise { + const db = await openDB() + return new Promise((resolve, reject) => { + const tx = db.transaction('puzzles', 'readonly') + const req = tx.objectStore('puzzles').get(id) + req.onsuccess = () => resolve(req.result?.data || null) + req.onerror = () => reject(req.error) + }) +} + +export async function saveProgress(puzzleId: string, entries: string[][]): Promise { + const db = await openDB() + const tx = db.transaction('progress', 'readwrite') + tx.objectStore('progress').put({ puzzleId, entries, saved_at: Date.now() }) + return new Promise((resolve, reject) => { tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error) }) +} + +export async function getProgress(puzzleId: string): Promise { + const db = await openDB() + return new Promise((resolve, reject) => { + const tx = db.transaction('progress', 'readonly') + const req = tx.objectStore('progress').get(puzzleId) + req.onsuccess = () => resolve(req.result?.entries || null) + req.onerror = () => reject(req.error) + }) +} + +export function isOnline(): boolean { + return navigator.onLine +} + +export function onOnlineChange(callback: (online: boolean) => void): () => void { + const onOnline = () => callback(true) + const onOffline = () => callback(false) + window.addEventListener('online', onOnline) + window.addEventListener('offline', onOffline) + return () => { + window.removeEventListener('online', onOnline) + window.removeEventListener('offline', onOffline) + } +}