/** * 뷰어 옵션 영구 저장소 (IndexedDB). * * localStorage 대신 IndexedDB 를 쓰는 이유: 값이 문자열로 강제되지 않고(숫자/불린/ * 객체 그대로), 용량 제한이 사실상 없으며, 나중에 도면별 설정처럼 구조가 커져도 * 같은 저장소를 그대로 쓸 수 있다. * * DB 하나(`hmw-viewer`) 안에 key-value 스토어(`options`) 하나만 둔다. * 실패(사생활 보호 모드, 스토리지 차단 등)해도 앱이 죽지 않도록 모든 API 는 * 조용히 기본값으로 떨어진다. */ const DB_NAME = 'hmw-viewer'; const DB_VERSION = 1; const STORE = 'options'; let dbPromise: Promise | null = null; function openDb(): Promise { if (dbPromise) return dbPromise; dbPromise = new Promise((resolve) => { if (typeof indexedDB === 'undefined') return resolve(null); let req: IDBOpenDBRequest; try { req = indexedDB.open(DB_NAME, DB_VERSION); } catch { return resolve(null); } req.onupgradeneeded = () => { const db = req.result; if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE); }; req.onsuccess = () => resolve(req.result); req.onerror = () => resolve(null); req.onblocked = () => resolve(null); }); return dbPromise; } function tx(mode: IDBTransactionMode, run: (store: IDBObjectStore) => IDBRequest): Promise { return openDb().then( (db) => new Promise((resolve) => { if (!db) return resolve(null); try { const t = db.transaction(STORE, mode); const req = run(t.objectStore(STORE)); req.onsuccess = () => resolve(req.result as T); req.onerror = () => resolve(null); } catch { resolve(null); } }), ); } /** 저장된 옵션 하나. 없으면 `fallback`. */ export async function getOption(key: string, fallback: T): Promise { const v = await tx('readonly', (s) => s.get(key)); return v === null || v === undefined ? fallback : v; } /** 옵션 하나 저장. 저장이 막혀 있어도 예외를 던지지 않는다. */ export async function setOption(key: string, value: unknown): Promise { await tx('readwrite', (s) => s.put(value, key)); } /** 전체 옵션을 한 번에 읽는다 (기동 시 1회 호출용). */ export async function getAllOptions(): Promise> { const keys = (await tx('readonly', (s) => s.getAllKeys())) ?? []; const vals = (await tx('readonly', (s) => s.getAll())) ?? []; const out: Record = {}; keys.forEach((k, i) => { out[String(k)] = vals[i]; }); return out; }