UX: Wetter-Preset + robusteres Tile-Prefetch (gemeinsamer LastPos), SW by-v1091

Beide nutzen jetzt by_last_position aus localStorage:

- wetter.js: speichert jede erfolgreiche GPS-Position; bei GPS-Fehler
  (kein Netz, Permission verweigert) wird der letzte bekannte Ort
  als Fallback genutzt — kein Error-Banner mehr wenn man ihn schon
  einmal hatte
- offline-indicator.js: _prefetchTiles versucht erst GPS, fällt
  dann auf den gespeicherten letzten Ort zurück → Step 5 wird auch
  ohne aktive GPS-Permission grün, sobald Wetter (oder andere
  Module) einmal eine Position eingeloggt haben
- TILE_MIN von 50 auf 20 gesenkt — 5x4 Tiles reichen für eine
  brauchbare Offline-Karte im Nahbereich
This commit is contained in:
rene 2026-05-26 17:10:52 +02:00
parent 2876469e91
commit 0ba0de12b3
6 changed files with 65 additions and 24 deletions

View file

@ -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():

View file

@ -101,9 +101,9 @@
</script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=1090">
<link rel="stylesheet" href="/css/layout.css?v=1090">
<link rel="stylesheet" href="/css/components.css?v=1090">
<link rel="stylesheet" href="/css/design-system.css?v=1091">
<link rel="stylesheet" href="/css/layout.css?v=1091">
<link rel="stylesheet" href="/css/components.css?v=1091">
</head>
<body>
@ -625,11 +625,11 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=1090"></script>
<script src="/js/ui.js?v=1090"></script>
<script src="/js/app.js?v=1090"></script>
<script src="/js/worlds.js?v=1090"></script>
<script src="/js/offline-indicator.js?v=1090"></script>
<script src="/js/api.js?v=1091"></script>
<script src="/js/ui.js?v=1091"></script>
<script src="/js/app.js?v=1091"></script>
<script src="/js/worlds.js?v=1091"></script>
<script src="/js/offline-indicator.js?v=1091"></script>
<!-- Feature-Seiten werden lazy geladen -->

View file

@ -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;

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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