"use strict"; /* * Offline map tiles via IndexedDB. * * A custom Leaflet tile layer that serves map tiles from IndexedDB when present * — so areas you've viewed once work fully offline afterwards. This works in the * Android WebView too, where a service worker can't run from a file:// page (the * service worker handles the same job for the hosted PWA). * * It degrades safely: on any IndexedDB/network/CORS error it falls back to the * plain tile URL, so the map can never be worse than an ordinary tile layer. * Tiles are only stored as you pan/zoom while online (never bulk pre-fetched), * in line with OpenStreetMap's tile usage policy. */ (function () { const DB_NAME = "mobis-tiles", STORE = "tiles"; let dbp = null; function db() { if (dbp) return dbp; dbp = new Promise((resolve, reject) => { let req; try { req = indexedDB.open(DB_NAME, 1); } catch (e) { return reject(e); } req.onupgradeneeded = () => req.result.createObjectStore(STORE); req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }).catch(() => null); return dbp; } async function idbGet(key) { const d = await db(); if (!d) return null; return new Promise((res) => { try { const r = d.transaction(STORE, "readonly").objectStore(STORE).get(key); r.onsuccess = () => res(r.result || null); r.onerror = () => res(null); } catch (e) { res(null); } }); } async function idbPut(key, blob) { const d = await db(); if (!d) return; try { d.transaction(STORE, "readwrite").objectStore(STORE).put(blob, key); } catch (e) {} } if (!window.L || !L.TileLayer) return; const CachedTileLayer = L.TileLayer.extend({ createTile(coords, done) { const tile = document.createElement("img"); tile.setAttribute("role", "presentation"); tile.alt = ""; const url = this.getTileUrl(coords); let settled = false; const finish = () => { if (!settled) { settled = true; done(null, tile); } }; tile.addEventListener("load", finish); tile.addEventListener("error", finish); const useBlob = (blob) => { const obj = URL.createObjectURL(blob); tile.addEventListener("load", () => { try { URL.revokeObjectURL(obj); } catch (e) {} }, { once: true }); tile.src = obj; }; idbGet(url).then((blob) => { if (blob) { useBlob(blob); return; } if (!navigator.onLine) { finish(); return; } // offline + uncached → blank fetch(url, { mode: "cors" }) .then((r) => (r && r.ok) ? r.blob() : Promise.reject()) .then((b) => { idbPut(url, b); useBlob(b); }) .catch(() => { tile.src = url; }); // CORS/network hiccup → direct }).catch(() => { tile.src = url; }); return tile; }, }); // Drop-in factory: returns the caching layer, or a plain tile layer if the // browser lacks IndexedDB. window.makeTileLayer = function (template, opts) { if (!("indexedDB" in window)) return L.tileLayer(template, opts); return new CachedTileLayer(template, opts); }; })();