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>
76 lines
2.7 KiB
TypeScript
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;
|
|
}
|