Files
dwg-dxf-viewer-sample/src/optionsStore.ts
minsungandClaude Opus 5 1f80442f6c feat(ui): options panel with an IndexedDB-backed lineweight display scale
Set the default lineweight scale to 11 px/mm: the value the user matched
against AutoCAD on the actual screen. The 20 px/mm derived from their
screenshot was too heavy in practice - the capture and the monitor do
not share a DPI - so the constant is now only a starting point.

Move the lineweight controls off the toolbar into an Options panel:
display toggle, scale slider plus number box, a readout of what the
drawing's own weights come out to, grid toggle, zoom speed, and a reset.
Settings persist in IndexedDB (hmw-viewer/options) rather than
localStorage, so values keep their type and the store has room to grow
per-drawing later; every call degrades quietly if storage is blocked.

Scale changes still apply to the live materials - each fat batch now
remembers its weight in mm - so dragging the slider retunes a 37MB
drawing instantly.

Verified headless: 0.35mm renders 3-4px at scale 11 and 8-9px at 25,
matching the readout, and the value survives a reload via IndexedDB.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 09:00:37 +09:00

76 lines
2.7 KiB
TypeScript

/**
* 뷰어 옵션 영구 저장소 (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<IDBDatabase | null> | null = null;
function openDb(): Promise<IDBDatabase | null> {
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<T>(mode: IDBTransactionMode, run: (store: IDBObjectStore) => IDBRequest): Promise<T | null> {
return openDb().then(
(db) =>
new Promise<T | null>((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<T>(key: string, fallback: T): Promise<T> {
const v = await tx<T>('readonly', (s) => s.get(key));
return v === null || v === undefined ? fallback : v;
}
/** 옵션 하나 저장. 저장이 막혀 있어도 예외를 던지지 않는다. */
export async function setOption(key: string, value: unknown): Promise<void> {
await tx('readwrite', (s) => s.put(value, key));
}
/** 전체 옵션을 한 번에 읽는다 (기동 시 1회 호출용). */
export async function getAllOptions(): Promise<Record<string, unknown>> {
const keys = (await tx<IDBValidKey[]>('readonly', (s) => s.getAllKeys())) ?? [];
const vals = (await tx<unknown[]>('readonly', (s) => s.getAll())) ?? [];
const out: Record<string, unknown> = {};
keys.forEach((k, i) => { out[String(k)] = vals[i]; });
return out;
}