60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
// Хранение миров в IndexedDB (локально в браузере).
|
|
import type { World } from './types';
|
|
|
|
const DB = 'citygen';
|
|
const STORE = 'worlds';
|
|
|
|
function open(): Promise<IDBDatabase> {
|
|
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<T>(mode: IDBTransactionMode, fn: (s: IDBObjectStore) => IDBRequest<T>): Promise<T> {
|
|
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<World | undefined>('readonly', (s) => s.get('last'));
|
|
export const hasWorld = async () => {
|
|
try {
|
|
return (await tx<number>('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<any> {
|
|
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());
|
|
}
|