diff --git a/VERSION b/VERSION index 02be51a..847dd18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1211 \ No newline at end of file +1213 \ No newline at end of file diff --git a/backend/static/index.html b/backend/static/index.html index 5987a83..77b269b 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,14 +86,14 @@ Ban Yaro - + - - - - - + + + + + @@ -617,11 +617,11 @@ - - - - - + + + + + @@ -631,7 +631,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 55a367c..00af0f7 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '1211'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1213'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator) window.APP_VERSION = APP_VERSION; diff --git a/backend/static/js/offline-indicator.js b/backend/static/js/offline-indicator.js index 234fca0..55e93b1 100644 --- a/backend/static/js/offline-indicator.js +++ b/backend/static/js/offline-indicator.js @@ -20,6 +20,34 @@ window.OfflineIndicator = (() => { return found ? await caches.open(found) : null; } + // GL-Offline-Tiles-Modus (byt://-Vektorkacheln in IndexedDB) statt OSM-Raster. + function _offlineTilesMode() { + try { return localStorage.getItem('by_offline_tiles') === '1'; } catch (e) { return false; } + } + // Ist eine Offline-Region (Vektorkacheln) in IndexedDB gespeichert? (ohne MapOffline zu laden) + // WICHTIG: dasselbe Schema/Version wie map-offline.js anlegen — sonst legt ein versionsloses open() + // die DB leer an und MapOffline kann seine Stores nicht mehr erstellen. + function _offlineRegionStored() { + return new Promise(res => { + try { + const r = indexedDB.open('by-offline-tiles', 1); + r.onupgradeneeded = () => { + const d = r.result; + if (!d.objectStoreNames.contains('tiles')) d.createObjectStore('tiles'); + if (!d.objectStoreNames.contains('meta')) d.createObjectStore('meta'); + }; + r.onsuccess = () => { + const db = r.result; + if (!db.objectStoreNames.contains('tiles')) { db.close(); return res(false); } + const cnt = db.transaction('tiles', 'readonly').objectStore('tiles').count(); + cnt.onsuccess = () => { res(cnt.result > 0); db.close(); }; + cnt.onerror = () => { res(false); db.close(); }; + }; + r.onerror = () => res(false); + } catch (e) { res(false); } + }); + } + const CHECKS = [ { step: 1, title: 'App-Grundgerüst', detail: 'CSS, Layout und Hauptmodule — die Basis', @@ -69,8 +97,10 @@ window.OfflineIndicator = (() => { } }, { step: 5, title: 'Karten-Kacheln', - detail: `Mindestens ${TILE_MIN} OSM-Tiles im Umkreis`, + detail: 'Karten für deine Gegend offline verfügbar', probe: async () => { + // GL-Modus: gespeicherte Vektor-Region in IndexedDB (das alte OSM-Raster nutzt die GL-Karte nicht). + if (_offlineTilesMode()) return _offlineRegionStored(); const c = await caches.open(CACHE_TILES).catch(() => null); if (!c) return false; return (await c.keys()).length >= TILE_MIN; @@ -169,12 +199,30 @@ window.OfflineIndicator = (() => { tasks.push(fetch('/api/routes').catch(() => {})); tasks.push(fetch('/api/notes').catch(() => {})); } else if (m.step === 5) { - await _prefetchTiles(); + if (_offlineTilesMode()) await _downloadOfflineRegion(); + else await _prefetchTiles(); } } await Promise.all(tasks); } + // GL-Offline: Vektor-Region (~5 km) um den aktuellen Standort in IndexedDB laden. + async function _downloadOfflineRegion() { + let pos = null; + try { pos = await API.getLocation(); } catch (e) {} + if (!pos) { + try { + const raw = localStorage.getItem(LS_LAST_POS); + if (raw) { const p = JSON.parse(raw); pos = { lat: p.lat, lon: p.lon }; } + } catch (e) {} + } + if (!pos) { UI.toast.warning('Standort nötig, um die Gegend offline zu speichern.'); return; } + try { + await UI.loadMapLibreUI(); + if (window.MapOffline) await MapOffline.downloadAround(pos.lat, pos.lon, 5); + } catch (e) { console.warn('Offline-Region-Download fehlgeschlagen:', e); } + } + // ---------------------------------------------------------- // Tile-URL-Berechnung (OSM, Subdomain 'a') // ---------------------------------------------------------- diff --git a/backend/static/js/ui.js b/backend/static/js/ui.js index 23a71d5..b3d66a8 100644 --- a/backend/static/js/ui.js +++ b/backend/static/js/ui.js @@ -1800,6 +1800,7 @@ const UI = (() => { escape, escHtml, help, pageInfo, saveToAlbum, loadLeaflet, + loadMapLibreUI, leafletMarker, locationPicker, map, diff --git a/backend/static/landing.html b/backend/static/landing.html index 2cbc2a4..5f3d168 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -4,7 +4,7 @@ - + Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz diff --git a/backend/static/sw.js b/backend/static/sw.js index b9fa861..8a9b471 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -4,7 +4,7 @@ ============================================================ */ // ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab -const VER = '1211'; +const VER = '1213'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten diff --git a/docs/OFFLINE_MAPS_PLAN.md b/docs/OFFLINE_MAPS_PLAN.md index c4277b2..1e6ca0d 100644 --- a/docs/OFFLINE_MAPS_PLAN.md +++ b/docs/OFFLINE_MAPS_PLAN.md @@ -1,8 +1,29 @@ # Offline-Karten (GL/Vektor) — Feature-Plan -**Status:** geplant (Umsetzung nach Tile-Build/Produktions-Rollout der GL-Karten). +**Status:** KERN UMGESETZT + headless verifiziert (2026-06-05, v1213), **flag-gated `by_offline_tiles` (Default AUS)** bis Gerätetest. **Stand:** 2026-06-05. Autor: René + Claude (Design). +## Umsetzungsstand (2026-06-05) +**✅ Fertig + headless bewiesen:** +- `map-offline.js` (`window.MapOffline`): Region-Download (`downloadAround(lat,lon,radiusKm)`) → Vektorkacheln + z0–14 via `pmtiles.getZxy` (liefert bereits dekomprimierte MVT) + Glyphs in **IndexedDB** (`by-offline-tiles`). + `byt://`-MapLibre-Protokoll (IndexedDB-first, remote-Fallback). ~15 MB / 5 km (dekomprimiert). +- `map-gl-style.js` `build({offline})`: `byt`-Source statt `pmtiles://`. Flag `by_offline_tiles` (Default AUS). +- ui.js/map.js laden map-offline + registrieren `byt`. `UI.loadMapLibreUI` exportiert. +- Welten-FAB Segment 5: prüft im GL-Modus gespeicherte Region (nicht mehr OSM-Raster); „Fehlende nachladen" + stößt `MapOffline.downloadAround(GPS, 5km)` an. +- **Beweis:** Download 97 Tiles (5 km München) → Netz AUS → **1903 Features gerendert**, nicht geladene + Gegend (Paris) leer; Glyphs nötig (sonst lässt MapLibre offline die ganze Kachel fallen). + +**🔲 Offen (Follow-ups):** +- **Gerätetest (iOS-PWA offline/IndexedDB)** → dann Flag-Default auf Staging-AN (analog `by_map_gl`). +- Download-Button auf der **Karte** (`map-offline-btn`) im GL-Modus auf `downloadAround(Karten-Center)` umbiegen + (bisher OSM-Raster-Prefetch). +- **Adaptives Lernen** (rollendes Vorausladen beim Aufzeichnen + Funkloch-Gedächtnis). +- **Bereichsauswahl / Routen-Korridor** (inkl. „Route offline speichern" aus routes.js `_openDetail`). +- **Glyph-Persistenz** über App-Updates (aktuell SW-Cache, wird bei Update gepurged) → in IndexedDB ablegen + via `byt://f/` servieren. +- Alten OSM-Raster-Prefetch (`offline-indicator.js _prefetchTiles`) entfernen, wenn Flag dauerhaft AN. + ## Ziel GL-Vektorkarten offline-tauglich machen — Kernszenario **Gassi/Wandern im Funkloch**. Selbst-zielend (cacht wo nötig, nicht überall), speichersparsam, ohne Nutzeraufwand.