diff --git a/backend/main.py b/backend/main.py index 8378aee..9281580 100644 --- a/backend/main.py +++ b/backend/main.py @@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "1090" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "1091" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/static/index.html b/backend/static/index.html index 37c7fee..9494c1a 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -101,9 +101,9 @@ - - - + + + @@ -625,11 +625,11 @@ - - - - - + + + + + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 16cdbbe..098edb5 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 = '1090'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1091'; // ← 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 20951b3..b5cb25f 100644 --- a/backend/static/js/offline-indicator.js +++ b/backend/static/js/offline-indicator.js @@ -11,7 +11,8 @@ window.OfflineIndicator = (() => { // Cache-Namen dynamisch finden — robust gegen Versions-Updates const CACHE_TILES = 'ban-yaro-tiles-v1'; const CACHE_API = 'ban-yaro-api-v1'; - const TILE_MIN = 50; + const TILE_MIN = 20; // niedriger Schwellwert: 5x4 Tiles reichen für Nahbereich + const LS_LAST_POS = 'by_last_position'; // teilt sich Storage mit wetter.js async function _staticCache() { const names = await caches.keys(); @@ -195,21 +196,42 @@ window.OfflineIndicator = (() => { return out; } - // Tile-Prefetch im Umkreis der aktuellen GPS-Position (nur wenn Permission schon da) + // Tile-Prefetch — versucht aktuelle GPS-Position, sonst fällt auf gespeicherte zurück async function _prefetchTiles() { if (!navigator.serviceWorker?.controller) return; - if (!navigator.permissions || !navigator.geolocation) return; + + let lat = null, lon = null; + + // 1. Versuch: GPS wenn Permission schon erteilt (kein Popup) try { - const perm = await navigator.permissions.query({ name: 'geolocation' }); - if (perm.state !== 'granted') return; // kein Popup wenn nicht schon erlaubt - const pos = await new Promise(res => - navigator.geolocation.getCurrentPosition(p => res(p), () => res(null), { timeout: 5000 })); - if (!pos) return; - const urls = []; - TILE_PREFETCH.forEach(({ zoom, radius }) => - urls.push(..._tileUrls(pos.coords.latitude, pos.coords.longitude, zoom, radius))); - navigator.serviceWorker.controller.postMessage({ type: 'CACHE_TILES', urls }); + if (navigator.permissions && navigator.geolocation) { + const perm = await navigator.permissions.query({ name: 'geolocation' }); + if (perm.state === 'granted') { + const pos = await new Promise(res => + navigator.geolocation.getCurrentPosition(p => res(p), () => res(null), { timeout: 5000 })); + if (pos) { lat = pos.coords.latitude; lon = pos.coords.longitude; } + } + } } catch {} + + // 2. Fallback: zuletzt bekannte Position aus localStorage (gesetzt von wetter.js u.a.) + if (lat == null) { + try { + const raw = localStorage.getItem(LS_LAST_POS); + if (raw) { + const stored = JSON.parse(raw); + if (stored?.lat != null && stored?.lon != null) { + lat = stored.lat; lon = stored.lon; + } + } + } catch {} + } + + if (lat == null) return; + const urls = []; + TILE_PREFETCH.forEach(({ zoom, radius }) => + urls.push(..._tileUrls(lat, lon, zoom, radius))); + navigator.serviceWorker.controller.postMessage({ type: 'CACHE_TILES', urls }); } // Page-Module proaktiv fetchen — falls SW-Install sie noch nicht alle hatte diff --git a/backend/static/js/pages/wetter.js b/backend/static/js/pages/wetter.js index 2dceccd..77f974e 100644 --- a/backend/static/js/pages/wetter.js +++ b/backend/static/js/pages/wetter.js @@ -91,12 +91,31 @@ window.Page_wetter = (() => { `; } + const LS_LAST_POS = 'by_last_position'; + async function _tryAutoLocate() { + // Letzte bekannte Position als Fallback einlesen (für offline / GPS-verweigert) + let cached = null; + try { + const raw = localStorage.getItem(LS_LAST_POS); + if (raw) cached = JSON.parse(raw); + } catch {} + try { const pos = await API.getLocation({ timeout: 8000, maximumAge: 300_000 }); + try { + localStorage.setItem(LS_LAST_POS, JSON.stringify({ + lat: pos.lat, lon: pos.lon, ts: Date.now(), + })); + } catch {} await _loadData(pos.lat, pos.lon); } catch (err) { - _showLocationError(err?.code); + // GPS fehlgeschlagen → letzte bekannte Position nutzen (statt Error-Banner) + if (cached?.lat != null && cached?.lon != null) { + await _loadData(cached.lat, cached.lon); + } else { + _showLocationError(err?.code); + } } } diff --git a/backend/static/sw.js b/backend/static/sw.js index 6fce525..1c81ced 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 = '1090'; +const VER = '1091'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten