import L from 'leaflet'; import { getTile, putTile } from '@/lib/tileCache'; /** * A Leaflet tile layer that reads through an on-device IndexedDB cache. * * Behaviour per tile: * 1. cached blob → draw it immediately (works fully offline); * 2. else fetch → cache the bytes → draw them; * 3. offline and uncached → fall back to the plain tile URL, so Leaflet shows * its normal missing-tile state instead of hanging forever. * * Works on web and in the Capacitor WebView alike, unlike a service worker. */ export class CachedTileLayer extends L.TileLayer { protected createTile(coords: L.Coords, done: L.DoneCallback): HTMLElement { const tile = document.createElement('img'); tile.alt = ''; tile.setAttribute('role', 'presentation'); const key = `${coords.z}/${coords.x}/${coords.y}`; const url = this.getTileUrl(coords); const useSrc = (src: string, revoke: boolean) => { tile.onload = () => { if (revoke) URL.revokeObjectURL(src); done(undefined, tile); }; tile.onerror = (e) => done(e as unknown as Error, tile); tile.src = src; }; void (async () => { const cached = await getTile(key); if (cached) { useSrc(URL.createObjectURL(cached), true); return; } try { const res = await fetch(url, { mode: 'cors' }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const blob = await res.blob(); if (blob.size > 0) void putTile(key, blob); useSrc(URL.createObjectURL(blob), true); } catch { // No cache and no network: let the browser try the URL directly. useSrc(url, false); } })(); return tile; } } export function createCachedTileLayer( urlTemplate: string, options?: L.TileLayerOptions, ): CachedTileLayer { return new CachedTileLayer(urlTemplate, options); }