feat(view): project save/open, Ctrl+S, save status, IndexedDB auto-backup (plan B2–B5)
- 저장 (Ctrl+S) downloads <교량명>_교각설계.pzr.json; 프로젝트 열기 reads it back and regenerates every pier without the source workbook. - Save status in the header: 저장 전 / 변경 있음 · 미저장 / 저장됨. The snapshot excludes the history so a save is not counted as a change. - Auto-backup to IndexedDB 0.8 s after changes while unsaved; on start a banner offers recovery or discard. Storage failures are reported, not fatal. - beforeunload warns while there are unsaved changes. - B1 (default-vs-changed tracking) is not added yet: inputs are not editable until the 2단계 input cards exist. - Not verified in a browser (no debuggable Chrome in this session). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5ce6d0e2f1
commit
f5303e4c2b
3 files changed
+205
No files matched your search
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Browser auto-backup in IndexedDB (spec §8, plan B4). Only the latest project text is kept.
|
||||
* Every call can fail (private window, blocked storage); callers catch and show the failure.
|
||||
*/
|
||||
const DB_NAME = 'pierzainer';
|
||||
const STORE = 'backup';
|
||||
const KEY = 'current';
|
||||
|
||||
export interface Backup {
|
||||
text: string;
|
||||
at: string;
|
||||
}
|
||||
|
||||
function openDb(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(DB_NAME, 1);
|
||||
req.onupgradeneeded = () => req.result.createObjectStore(STORE);
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
}
|
||||
|
||||
function run<T>(mode: IDBTransactionMode, action: (store: IDBObjectStore) => IDBRequest<T>): Promise<T> {
|
||||
return openDb().then(
|
||||
(db) =>
|
||||
new Promise<T>((resolve, reject) => {
|
||||
const tx = db.transaction(STORE, mode);
|
||||
const req = action(tx.objectStore(STORE));
|
||||
tx.oncomplete = () => {
|
||||
db.close();
|
||||
resolve(req.result);
|
||||
};
|
||||
tx.onerror = () => {
|
||||
db.close();
|
||||
reject(tx.error);
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function writeBackup(text: string): Promise<unknown> {
|
||||
const value: Backup = { text, at: new Date().toISOString() };
|
||||
return run('readwrite', (store) => store.put(value, KEY));
|
||||
}
|
||||
|
||||
export async function readBackup(): Promise<Backup | null> {
|
||||
const value = await run<Backup | undefined>('readonly', (store) => store.get(KEY));
|
||||
return value ?? null;
|
||||
}
|
||||
|
||||
export function clearBackup(): Promise<unknown> {
|
||||
return run('readwrite', (store) => store.delete(KEY));
|
||||
}
|
||||
Reference in new issue
Block a user