UX: Offline-Score direkt im FAB statt separater Pfote, SW by-v1083
User-Feedback: separater Indikator zu viel — die Pfote IM FAB selbst soll je nach Score grün eingefärbt werden. - Separater #offline-indicator Button entfernt (HTML + CSS) - Welten-FAB-Icon: <use phosphor.svg#paw-print> ersetzt durch Inline-SVG mit 5 einzelnen paw-elem-Pfaden (1 Ballen + 4 Zehen) - CSS: Default weiß (wie bisher), .filled wird leuchtendes Grün (#16a34a) — überzeichnet auf orangem FAB klar erkennbar - offline-indicator.js: zeigt jetzt nur noch die FAB-Pfade ein/aus, kein eigenes Element mehr; Klick-Status-Modal als window.OfflineIndicator.openStatus() weiter verfügbar (kann später bei Bedarf an Long-Press oder Menüpunkt gehängt werden)
This commit is contained in:
parent
53c80b9bf6
commit
b9fe5b5bc3
6 changed files with 61 additions and 152 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 = "1082" # muss mit APP_VER in app.js übereinstimmen
|
APP_VER = "1083" # 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():
|
||||||
|
|
|
||||||
|
|
@ -8867,44 +8867,17 @@ svg.empty-state-icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Offline-Bereitschafts-Indikator — schwebend über dem Welten-FAB
|
Offline-Bereitschafts-Anzeige IM Welten-FAB
|
||||||
Sichtbar NUR wenn Welten aktiv sind (Sibling-Selektor)
|
Die 5 Pfoten-Pfade werden je nach Score grün gefärbt
|
||||||
5 Pfade — Score 0 (grau) bis 5 (grün, gefüllt)
|
(Default = weiß auf orange, filled = grün auf orange)
|
||||||
============================================================ */
|
============================================================ */
|
||||||
#offline-indicator {
|
#worlds-fab .offline-paw .paw-elem {
|
||||||
display: flex; /* Default: sichtbar — JS blendet auf Detail-Seiten aus */
|
color: #fff;
|
||||||
position: fixed;
|
transition: stroke 0.4s ease, fill 0.4s ease;
|
||||||
right: 20px; /* gleicher right wie #worlds-fab */
|
|
||||||
bottom: calc(env(safe-area-inset-bottom, 16px) + 16px + 54px + 12px); /* FAB-Bottom + FAB-Höhe + 12px */
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(255,255,255,0.95);
|
|
||||||
border: 2px solid var(--c-border);
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.18);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 61; /* knapp über dem FAB (60), unter Modals */
|
|
||||||
transition: transform 0.12s, opacity 0.2s;
|
|
||||||
}
|
}
|
||||||
#offline-indicator.is-hidden { display: none; } /* JS-gesteuert: in Detail-Seiten */
|
#worlds-fab .offline-paw .paw-elem.filled {
|
||||||
|
color: #16a34a; /* leuchtendes Grün, klar sichtbar auf orange */
|
||||||
[data-theme="dark"] #offline-indicator {
|
fill: #16a34a;
|
||||||
background: rgba(31,41,55,0.85);
|
|
||||||
border-color: rgba(255,255,255,0.08);
|
|
||||||
}
|
|
||||||
#offline-indicator:active { transform: scale(0.92); }
|
|
||||||
#offline-indicator .offline-paw { width: 24px; height: 24px; }
|
|
||||||
|
|
||||||
.offline-paw .paw-elem {
|
|
||||||
color: var(--c-text-muted);
|
|
||||||
transition: stroke 0.5s ease, fill 0.5s ease;
|
|
||||||
}
|
|
||||||
.offline-paw .paw-elem.filled {
|
|
||||||
color: var(--c-success);
|
|
||||||
fill: var(--c-success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.offline-status-row {
|
.offline-status-row {
|
||||||
|
|
|
||||||
|
|
@ -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=1082">
|
<link rel="stylesheet" href="/css/design-system.css?v=1083">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=1082">
|
<link rel="stylesheet" href="/css/layout.css?v=1083">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=1082">
|
<link rel="stylesheet" href="/css/components.css?v=1083">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -602,27 +602,22 @@
|
||||||
<div class="world-panel" id="wp-welt"><div id="ww-content"></div></div>
|
<div class="world-panel" id="wp-welt"><div id="ww-content"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<button id="worlds-fab" aria-label="Hinzufügen">
|
<button id="worlds-fab" aria-label="Hinzufügen">
|
||||||
<svg class="ph-icon" style="width:22px;height:22px"><use href="/icons/phosphor.svg#paw-print"></use></svg>
|
<svg class="offline-paw" viewBox="0 0 256 256" aria-hidden="true" style="width:24px;height:24px">
|
||||||
|
<!-- 5 Sub-Pfade einzeln einfärbbar via .paw-elem; Default: weiß auf orange -->
|
||||||
|
<path class="paw-elem" data-step="1"
|
||||||
|
d="M128,104A36,36,0,0,0,93.43,130a43.49,43.49,0,0,1-20.67,25.9,32,32,0,0,0,27.73,57.62,72.49,72.49,0,0,1,55,0,32,32,0,0,0,27.73-57.62A43.46,43.46,0,0,1,162.57,130,36,36,0,0,0,128,104Z"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle class="paw-elem" data-step="2" cx="44" cy="108" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
||||||
|
<circle class="paw-elem" data-step="3" cx="92" cy="60" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
||||||
|
<circle class="paw-elem" data-step="4" cx="164" cy="60" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
||||||
|
<circle class="paw-elem" data-step="5" cx="212" cy="108" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="worlds-back" aria-label="Zurück zur Welten-Navigation">
|
<div id="worlds-back" aria-label="Zurück zur Welten-Navigation">
|
||||||
<svg class="ph-icon" style="width:22px;height:22px"><use href="/icons/phosphor.svg#arrow-left"></use></svg>
|
<svg class="ph-icon" style="width:22px;height:22px"><use href="/icons/phosphor.svg#arrow-left"></use></svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- OFFLINE-BEREITSCHAFTS-INDIKATOR — schwebend oben rechts, immer sichtbar -->
|
|
||||||
<button id="offline-indicator"
|
|
||||||
aria-label="Offline-Bereitschaft" title="Offline-Bereitschaft wird geprüft …">
|
|
||||||
<svg class="offline-paw" viewBox="0 0 256 256" aria-hidden="true">
|
|
||||||
<path class="paw-elem" data-step="1"
|
|
||||||
d="M128,104A36,36,0,0,0,93.43,130a43.49,43.49,0,0,1-20.67,25.9,32,32,0,0,0,27.73,57.62,72.49,72.49,0,0,1,55,0,32,32,0,0,0,27.73-57.62A43.46,43.46,0,0,1,162.57,130,36,36,0,0,0,128,104Z"
|
|
||||||
fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<circle class="paw-elem" data-step="2" cx="44" cy="108" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
||||||
<circle class="paw-elem" data-step="3" cx="92" cy="60" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
||||||
<circle class="paw-elem" data-step="4" cx="164" cy="60" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
||||||
<circle class="paw-elem" data-step="5" cx="212" cy="108" r="20" fill="none" stroke="currentColor" stroke-width="16"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- TOAST CONTAINER (außerhalb der App, immer sichtbar) -->
|
<!-- TOAST CONTAINER (außerhalb der App, immer sichtbar) -->
|
||||||
<div class="toast-container" id="toast-container" role="alert" aria-live="polite"></div>
|
<div class="toast-container" id="toast-container" role="alert" aria-live="polite"></div>
|
||||||
|
|
||||||
|
|
@ -630,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=1082"></script>
|
<script src="/js/api.js?v=1083"></script>
|
||||||
<script src="/js/ui.js?v=1082"></script>
|
<script src="/js/ui.js?v=1083"></script>
|
||||||
<script src="/js/app.js?v=1082"></script>
|
<script src="/js/app.js?v=1083"></script>
|
||||||
<script src="/js/worlds.js?v=1082"></script>
|
<script src="/js/worlds.js?v=1083"></script>
|
||||||
<script src="/js/offline-indicator.js?v=1082"></script>
|
<script src="/js/offline-indicator.js?v=1083"></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 = '1082'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
const APP_VER = '1083'; // ← 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
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen.
|
// Cache-Bust-Parameter nach Update-Reload sofort entfernen.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
BAN YARO — Offline-Bereitschafts-Indikator
|
BAN YARO — Offline-Bereitschafts-Anzeige IM Welten-FAB
|
||||||
5-stufige Pfote im Header, zeigt wie viel der App offline
|
Färbt die 5 Pfoten-Pfade je nach Cache-Stand grün:
|
||||||
verfügbar ist. Klick → Status-Modal mit Nachlade-Button.
|
1 = App-Shell · 2 = Wichtige Seiten · 3 = Hund-/Tagebuchdaten
|
||||||
|
4 = Karten-Tiles · 5 = Training & Wissen
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
window.OfflineIndicator = (() => {
|
window.OfflineIndicator = (() => {
|
||||||
|
|
@ -11,9 +12,8 @@ window.OfflineIndicator = (() => {
|
||||||
const CACHE_STATIC = `by-v${(window.APP_VER || '0')}-static`;
|
const CACHE_STATIC = `by-v${(window.APP_VER || '0')}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1';
|
const CACHE_TILES = 'ban-yaro-tiles-v1';
|
||||||
const CACHE_API = 'ban-yaro-api-v1';
|
const CACHE_API = 'ban-yaro-api-v1';
|
||||||
const TILE_MIN = 50; // Mindest-Tiles für Stufe 4
|
const TILE_MIN = 50;
|
||||||
|
|
||||||
// 5 Offline-Bereitschafts-Checks, in Reihenfolge der Pfoten-Stufen
|
|
||||||
const CHECKS = [
|
const CHECKS = [
|
||||||
{ step: 1, title: 'App-Grundgerüst',
|
{ step: 1, title: 'App-Grundgerüst',
|
||||||
detail: 'CSS, Layout und Hauptmodule — die Basis',
|
detail: 'CSS, Layout und Hauptmodule — die Basis',
|
||||||
|
|
@ -27,8 +27,8 @@ window.OfflineIndicator = (() => {
|
||||||
if (!c) return false;
|
if (!c) return false;
|
||||||
const must = ['diary.js','map.js','walks.js','erste-hilfe.js'];
|
const must = ['diary.js','map.js','walks.js','erste-hilfe.js'];
|
||||||
const keys = await c.keys();
|
const keys = await c.keys();
|
||||||
const have = new Set(keys.map(r => r.url));
|
const have = keys.map(r => r.url);
|
||||||
return must.every(name => [...have].some(u => u.includes('/js/pages/' + name)));
|
return must.every(name => have.some(u => u.includes('/js/pages/' + name)));
|
||||||
} },
|
} },
|
||||||
|
|
||||||
{ step: 3, title: 'Hund- und Tagebuchdaten',
|
{ step: 3, title: 'Hund- und Tagebuchdaten',
|
||||||
|
|
@ -36,8 +36,7 @@ window.OfflineIndicator = (() => {
|
||||||
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 keys = await c.keys();
|
const urls = (await c.keys()).map(r => r.url);
|
||||||
const urls = keys.map(r => r.url);
|
|
||||||
return urls.some(u => /\/api\/dogs\/\d+/.test(u))
|
return urls.some(u => /\/api\/dogs\/\d+/.test(u))
|
||||||
&& urls.some(u => /\/api\/dogs\/\d+\/diary/.test(u));
|
&& urls.some(u => /\/api\/dogs\/\d+\/diary/.test(u));
|
||||||
} },
|
} },
|
||||||
|
|
@ -47,12 +46,11 @@ window.OfflineIndicator = (() => {
|
||||||
probe: async () => {
|
probe: async () => {
|
||||||
const c = await caches.open(CACHE_TILES).catch(() => null);
|
const c = await caches.open(CACHE_TILES).catch(() => null);
|
||||||
if (!c) return false;
|
if (!c) return false;
|
||||||
const keys = await c.keys();
|
return (await c.keys()).length >= TILE_MIN;
|
||||||
return keys.length >= TILE_MIN;
|
|
||||||
} },
|
} },
|
||||||
|
|
||||||
{ step: 5, title: 'Training & Wissen',
|
{ step: 5, title: 'Training & Wissen',
|
||||||
detail: 'Übungen, Wiki-Rassen, Wetter — Welt-Inhalte',
|
detail: 'Übungen, Wiki-Rassen, Wetter',
|
||||||
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;
|
||||||
|
|
@ -62,47 +60,34 @@ window.OfflineIndicator = (() => {
|
||||||
} },
|
} },
|
||||||
];
|
];
|
||||||
|
|
||||||
let _btn = null;
|
let _fab = null;
|
||||||
let _svg = null;
|
|
||||||
let _lastScore = -1;
|
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
// Score berechnen + Pfote einfärben
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
if (!_btn) return;
|
_fab = document.getElementById('worlds-fab');
|
||||||
if (!('caches' in window)) { _btn.style.display = 'none'; return; }
|
if (!_fab || !('caches' in window)) return null;
|
||||||
|
|
||||||
const results = await Promise.all(CHECKS.map(async c => {
|
const results = await Promise.all(CHECKS.map(async c => {
|
||||||
try { return { ...c, ok: await c.probe() }; }
|
try { return { ...c, ok: await c.probe() }; }
|
||||||
catch { return { ...c, ok: false }; }
|
catch { return { ...c, ok: false }; }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const score = results.filter(r => r.ok).length;
|
_fab.querySelectorAll('.paw-elem').forEach(el => {
|
||||||
_applyScore(score, results);
|
|
||||||
_lastScore = score;
|
|
||||||
return { score, results };
|
|
||||||
}
|
|
||||||
|
|
||||||
function _applyScore(score, results) {
|
|
||||||
if (!_svg) return;
|
|
||||||
_svg.querySelectorAll('.paw-elem').forEach(el => {
|
|
||||||
const step = Number(el.dataset.step);
|
const step = Number(el.dataset.step);
|
||||||
const isOk = results.find(r => r.step === step)?.ok;
|
const isOk = results.find(r => r.step === step)?.ok;
|
||||||
el.classList.toggle('filled', !!isOk);
|
el.classList.toggle('filled', !!isOk);
|
||||||
});
|
});
|
||||||
_btn.title = `Offline-Bereitschaft: ${score} von 5`;
|
|
||||||
_btn.setAttribute('aria-label', `Offline-Bereitschaft: ${score} von 5`);
|
const score = results.filter(r => r.ok).length;
|
||||||
|
_fab.setAttribute('data-offline-score', `${score}/5`);
|
||||||
|
return { score, results };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// Optional aufrufbar: zeigt das Status-Modal mit Nachlade-Button
|
||||||
// Status-Modal beim Klick
|
async function openStatus() {
|
||||||
// ----------------------------------------------------------
|
|
||||||
async function _openModal() {
|
|
||||||
const data = await refresh();
|
const data = await refresh();
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
const { score, results } = data;
|
const { score, results } = data;
|
||||||
|
const missing = results.filter(r => !r.ok);
|
||||||
const rows = results.map(r => `
|
const rows = results.map(r => `
|
||||||
<div class="offline-status-row ${r.ok ? 'ok' : 'miss'}">
|
<div class="offline-status-row ${r.ok ? 'ok' : 'miss'}">
|
||||||
<div class="osr-check">${r.ok ? '✓' : '○'}</div>
|
<div class="osr-check">${r.ok ? '✓' : '○'}</div>
|
||||||
|
|
@ -113,25 +98,21 @@ window.OfflineIndicator = (() => {
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
const missing = results.filter(r => !r.ok);
|
|
||||||
const allOk = missing.length === 0;
|
|
||||||
|
|
||||||
UI.modal.open({
|
UI.modal.open({
|
||||||
title: `🐾 Offline-Bereitschaft ${score}/5`,
|
title: `🐾 Offline-Bereitschaft ${score}/5`,
|
||||||
body: `
|
body: `
|
||||||
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3)">
|
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3)">
|
||||||
${allOk
|
${missing.length === 0
|
||||||
? 'Deine App ist voll offline-fähig. Du kannst Tagebuch, Karte und Daten auch ohne Internet nutzen.'
|
? 'Voll offline-fähig — Tagebuch, Karte und Daten funktionieren auch ohne Internet.'
|
||||||
: 'Je grüner deine Pfote, desto besser klappt die App ohne Internet. Fehlende Inhalte werden beim nächsten Online-Aufruf automatisch geladen.'}
|
: 'Je grüner deine Pfote im FAB, desto mehr klappt offline. Fehlende Inhalte werden beim nächsten Online-Aufruf automatisch geladen.'}
|
||||||
</p>
|
</p>
|
||||||
${rows}
|
${rows}
|
||||||
`,
|
`,
|
||||||
footer: `
|
footer: `
|
||||||
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
|
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
|
||||||
${missing.length
|
${missing.length ? `<button class="btn btn-primary" id="offline-fill-btn" style="width:100%">
|
||||||
? `<button class="btn btn-primary" id="offline-fill-btn" style="width:100%">
|
${UI.icon('cloud-arrow-down')} Fehlende jetzt nachladen
|
||||||
${UI.icon('cloud-arrow-down')} Fehlende jetzt nachladen
|
</button>` : ''}
|
||||||
</button>` : ''}
|
|
||||||
<button class="btn btn-secondary" data-modal-close style="width:100%">Schließen</button>
|
<button class="btn btn-secondary" data-modal-close style="width:100%">Schließen</button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
@ -139,8 +120,7 @@ window.OfflineIndicator = (() => {
|
||||||
|
|
||||||
document.getElementById('offline-fill-btn')?.addEventListener('click', async () => {
|
document.getElementById('offline-fill-btn')?.addEventListener('click', async () => {
|
||||||
const btn = document.getElementById('offline-fill-btn');
|
const btn = document.getElementById('offline-fill-btn');
|
||||||
btn.disabled = true;
|
btn.disabled = true; btn.textContent = 'Lade …';
|
||||||
btn.textContent = 'Lade …';
|
|
||||||
await _fetchMissing(missing);
|
await _fetchMissing(missing);
|
||||||
UI.modal.close();
|
UI.modal.close();
|
||||||
UI.toast.success('Offline-Inhalte aktualisiert.');
|
UI.toast.success('Offline-Inhalte aktualisiert.');
|
||||||
|
|
@ -148,14 +128,10 @@ window.OfflineIndicator = (() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
// Fehlende Inhalte aktiv nachladen
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
async function _fetchMissing(missing) {
|
async function _fetchMissing(missing) {
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
for (const m of missing) {
|
for (const m of missing) {
|
||||||
if (m.step === 2) {
|
if (m.step === 2) {
|
||||||
// Page-Module fetchen → SW cached sie automatisch
|
|
||||||
['diary.js','map.js','walks.js','erste-hilfe.js'].forEach(p =>
|
['diary.js','map.js','walks.js','erste-hilfe.js'].forEach(p =>
|
||||||
tasks.push(fetch(`/js/pages/${p}?v=${window.APP_VER}`).catch(() => {})));
|
tasks.push(fetch(`/js/pages/${p}?v=${window.APP_VER}`).catch(() => {})));
|
||||||
} else if (m.step === 3) {
|
} else if (m.step === 3) {
|
||||||
|
|
@ -165,17 +141,13 @@ window.OfflineIndicator = (() => {
|
||||||
tasks.push(fetch(`/api/dogs/${dogId}/diary?limit=20`).catch(() => {}));
|
tasks.push(fetch(`/api/dogs/${dogId}/diary?limit=20`).catch(() => {}));
|
||||||
}
|
}
|
||||||
} else if (m.step === 4) {
|
} else if (m.step === 4) {
|
||||||
// Karten-Tiles: SW per Message anstoßen
|
|
||||||
if (navigator.serviceWorker?.controller) {
|
if (navigator.serviceWorker?.controller) {
|
||||||
const pos = await new Promise(res =>
|
const pos = await new Promise(res =>
|
||||||
navigator.geolocation?.getCurrentPosition(p => res(p), () => res(null), { timeout: 4000 }));
|
navigator.geolocation?.getCurrentPosition(p => res(p), () => res(null), { timeout: 4000 }));
|
||||||
if (pos) {
|
if (pos) {
|
||||||
navigator.serviceWorker.controller.postMessage({
|
navigator.serviceWorker.controller.postMessage({
|
||||||
type: 'CACHE_TILES',
|
type: 'CACHE_TILES', lat: pos.coords.latitude, lon: pos.coords.longitude,
|
||||||
lat: pos.coords.latitude,
|
zoom: 14, radius: 2,
|
||||||
lon: pos.coords.longitude,
|
|
||||||
zoom: 14,
|
|
||||||
radius: 2,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,39 +160,8 @@ window.OfflineIndicator = (() => {
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
// Sichtbarkeit an Welten-Overlay koppeln
|
|
||||||
// — default sichtbar; nur ausblenden wenn explizit auf Detail-Seite
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
function _syncVisibility() {
|
|
||||||
if (!_btn) return;
|
|
||||||
const ov = document.getElementById('worlds-overlay');
|
|
||||||
if (!ov) return; // ohne Welten-Overlay sichtbar lassen
|
|
||||||
const inWorlds = ov.classList.contains('worlds-visible')
|
|
||||||
|| ov.style.display === 'block'
|
|
||||||
|| ov.style.display === '';
|
|
||||||
_btn.classList.toggle('is-hidden', !inWorlds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
// Init
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
function init() {
|
function init() {
|
||||||
_btn = document.getElementById('offline-indicator');
|
|
||||||
if (!_btn) { console.warn('[OfflineIndicator] #offline-indicator nicht im DOM'); return; }
|
|
||||||
_svg = _btn.querySelector('.offline-paw');
|
|
||||||
_btn.addEventListener('click', _openModal);
|
|
||||||
|
|
||||||
// MutationObserver: Welten-Overlay Klassenänderung → Indikator zeigen/verstecken
|
|
||||||
const ov = document.getElementById('worlds-overlay');
|
|
||||||
if (ov) {
|
|
||||||
_syncVisibility();
|
|
||||||
new MutationObserver(_syncVisibility).observe(ov, { attributes: true, attributeFilter: ['class', 'style'] });
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
// bei SW-Updates und alle 60s neu prüfen
|
|
||||||
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();
|
||||||
|
|
@ -229,7 +170,7 @@ window.OfflineIndicator = (() => {
|
||||||
setInterval(refresh, 60_000);
|
setInterval(refresh, 60_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { init, refresh };
|
return { init, refresh, openStatus };
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (document.readyState !== 'loading') {
|
if (document.readyState !== 'loading') {
|
||||||
|
|
|
||||||
|
|
@ -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 = '1082';
|
const VER = '1083';
|
||||||
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