UX: Offline-Pfote — automatischer Tile-Prefetch + Step 5 umgebaut, SW by-v1086
- Step 5 misst jetzt 'Welt-Daten' (Streak + Wetter) statt Wiki/Übungen — die kommen automatisch beim Welten-Aufruf, kein zusätzliches Prefetch nötig (User-Wunsch: Wiki+Übungen NICHT preloaden) - Neuer _prefetchTiles(): beim App-Start werden 49+9 OSM-Tiles (Zoom 14 +13) im 3km-Umkreis automatisch gecacht — aber NUR wenn GPS-Permission schon erteilt ist (kein nerviger Popup beim Start). Damit wird Step 4 nach kurzer Zeit grün. - _fetchMissing für Step 5 lädt jetzt Streak + Wetter (statt Wiki)
This commit is contained in:
parent
94f02dbe3a
commit
307b4a5486
5 changed files with 65 additions and 18 deletions
|
|
@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request):
|
||||||
raise _HE(404, "Nicht gefunden.")
|
raise _HE(404, "Nicht gefunden.")
|
||||||
return _media_response(filepath)
|
return _media_response(filepath)
|
||||||
|
|
||||||
APP_VER = "1085" # muss mit APP_VER in app.js übereinstimmen
|
APP_VER = "1086" # muss mit APP_VER in app.js übereinstimmen
|
||||||
|
|
||||||
@app.get("/.well-known/assetlinks.json")
|
@app.get("/.well-known/assetlinks.json")
|
||||||
async def assetlinks():
|
async def assetlinks():
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css?v=1085">
|
<link rel="stylesheet" href="/css/design-system.css?v=1086">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=1085">
|
<link rel="stylesheet" href="/css/layout.css?v=1086">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=1085">
|
<link rel="stylesheet" href="/css/components.css?v=1086">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -625,11 +625,11 @@
|
||||||
<div id="modal-container"></div>
|
<div id="modal-container"></div>
|
||||||
|
|
||||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||||
<script src="/js/api.js?v=1085"></script>
|
<script src="/js/api.js?v=1086"></script>
|
||||||
<script src="/js/ui.js?v=1085"></script>
|
<script src="/js/ui.js?v=1086"></script>
|
||||||
<script src="/js/app.js?v=1085"></script>
|
<script src="/js/app.js?v=1086"></script>
|
||||||
<script src="/js/worlds.js?v=1085"></script>
|
<script src="/js/worlds.js?v=1086"></script>
|
||||||
<script src="/js/offline-indicator.js?v=1085"></script>
|
<script src="/js/offline-indicator.js?v=1086"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
Router, State-Management, Navigation, Initialisierung.
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const APP_VER = '1085'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
const APP_VER = '1086'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
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_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
|
||||||
window.APP_VERSION = APP_VERSION;
|
window.APP_VERSION = APP_VERSION;
|
||||||
|
|
|
||||||
|
|
@ -58,17 +58,23 @@ window.OfflineIndicator = (() => {
|
||||||
return (await c.keys()).length >= TILE_MIN;
|
return (await c.keys()).length >= TILE_MIN;
|
||||||
} },
|
} },
|
||||||
|
|
||||||
{ step: 5, title: 'Training & Wissen',
|
{ step: 5, title: 'Welt-Daten',
|
||||||
detail: 'Übungen, Wiki-Rassen, Wetter',
|
detail: 'Streak, Wetter, Hundepass — kommt beim Welten-Aufruf',
|
||||||
probe: async () => {
|
probe: async () => {
|
||||||
const c = await caches.open(CACHE_API).catch(() => null);
|
const c = await caches.open(CACHE_API).catch(() => null);
|
||||||
if (!c) return false;
|
if (!c) return false;
|
||||||
const urls = (await c.keys()).map(r => r.url);
|
const urls = (await c.keys()).map(r => r.url);
|
||||||
return urls.some(u => u.includes('/api/training/exercises'))
|
return urls.some(u => u.includes('/api/streak/'))
|
||||||
&& urls.some(u => u.includes('/api/wiki/rassen'));
|
&& urls.some(u => u.includes('/api/weather'));
|
||||||
} },
|
} },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Tile-Prefetch-Konfiguration
|
||||||
|
const TILE_PREFETCH = [
|
||||||
|
{ zoom: 14, radius: 3 }, // 7x7 = 49 Tiles im Nahbereich
|
||||||
|
{ zoom: 13, radius: 1 }, // 3x3 = 9 Tiles für Übersicht
|
||||||
|
];
|
||||||
|
|
||||||
let _fab = null;
|
let _fab = null;
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
|
|
@ -161,19 +167,60 @@ window.OfflineIndicator = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (m.step === 5) {
|
} else if (m.step === 5) {
|
||||||
tasks.push(fetch('/api/training/exercises').catch(() => {}));
|
// Welt-Daten: Streak braucht Hund-ID, Wetter braucht GPS
|
||||||
tasks.push(fetch('/api/wiki/rassen?limit=50').catch(() => {}));
|
|
||||||
tasks.push(fetch('/api/weather').catch(() => {}));
|
tasks.push(fetch('/api/weather').catch(() => {}));
|
||||||
|
const dogId = window._appState?.activeDog?.id;
|
||||||
|
if (dogId) tasks.push(fetch(`/api/streak/${dogId}`).catch(() => {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
// Tile-URL-Berechnung (OSM, Subdomain 'a')
|
||||||
|
// ----------------------------------------------------------
|
||||||
|
function _tile(lat, lon, z) {
|
||||||
|
const n = Math.pow(2, z);
|
||||||
|
const x = Math.floor((lon + 180) / 360 * n);
|
||||||
|
const latRad = lat * Math.PI / 180;
|
||||||
|
const y = Math.floor((1 - Math.log(Math.tan(latRad) + 1/Math.cos(latRad)) / Math.PI) / 2 * n);
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
function _tileUrls(lat, lon, zoom, radius) {
|
||||||
|
const center = _tile(lat, lon, zoom);
|
||||||
|
const out = [];
|
||||||
|
for (let dx = -radius; dx <= radius; dx++) {
|
||||||
|
for (let dy = -radius; dy <= radius; dy++) {
|
||||||
|
out.push(`https://a.tile.openstreetmap.org/${zoom}/${center.x + dx}/${center.y + dy}.png`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile-Prefetch im Umkreis der aktuellen GPS-Position (nur wenn Permission schon da)
|
||||||
|
async function _prefetchTiles() {
|
||||||
|
if (!navigator.serviceWorker?.controller) return;
|
||||||
|
if (!navigator.permissions || !navigator.geolocation) return;
|
||||||
|
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 });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
refresh();
|
refresh();
|
||||||
|
_prefetchTiles(); // im Hintergrund
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
navigator.serviceWorker.addEventListener('message', e => {
|
navigator.serviceWorker.addEventListener('message', e => {
|
||||||
if (e?.data?.type === 'CACHE_UPDATE') refresh();
|
if (e?.data?.type === 'CACHE_UPDATE') refresh();
|
||||||
|
if (e?.data?.type === 'CACHE_TILES_PROGRESS') refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setInterval(refresh, 60_000);
|
setInterval(refresh, 60_000);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||||
const VER = '1085';
|
const VER = '1086';
|
||||||
const CACHE_VERSION = `by-v${VER}`;
|
const CACHE_VERSION = `by-v${VER}`;
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue