// Хранение миров в IndexedDB (локально в браузере). import type { World } from './types'; const DB = 'citygen'; const STORE = 'worlds'; function open(): Promise { return new Promise((resolve, reject) => { const req = indexedDB.open(DB, 1); req.onupgradeneeded = () => req.result.createObjectStore(STORE); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } async function tx(mode: IDBTransactionMode, fn: (s: IDBObjectStore) => IDBRequest): Promise { const db = await open(); return new Promise((resolve, reject) => { const t = db.transaction(STORE, mode); const req = fn(t.objectStore(STORE)); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } export const saveWorld = (w: World) => tx('readwrite', (s) => s.put(w, 'last')); export const loadWorld = () => tx('readonly', (s) => s.get('last')); export const hasWorld = async () => { try { return (await tx('readonly', (s) => s.count('last'))) > 0; } catch { return false; } }; /** Скачать мир файлом (.json.gz, если браузер умеет сжимать). */ export async function downloadWorld(w: World) { const json = JSON.stringify(w); let blob: Blob = new Blob([json], { type: 'application/json' }); let ext = 'json'; if ('CompressionStream' in window) { const cs = blob.stream().pipeThrough(new CompressionStream('gzip')); blob = await new Response(cs).blob(); ext = 'json.gz'; } const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${(w.name || 'world').replace(/[^\p{L}\p{N}_-]+/gu, '_').slice(0, 40)}.${ext}`; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 2000); } /** Прочитать файл мира или Overpass JSON (поддерживает .gz). */ export async function readJsonFile(file: File): Promise { let stream: ReadableStream = file.stream(); const head = new Uint8Array(await file.slice(0, 2).arrayBuffer()); if (head[0] === 0x1f && head[1] === 0x8b) stream = stream.pipeThrough(new DecompressionStream('gzip')); return JSON.parse(await new Response(stream).text()); }