Compare commits
9 commits
01081fcd75
...
bf26e5faf4
| Author | SHA1 | Date | |
|---|---|---|---|
| bf26e5faf4 | |||
| d399cb84cf | |||
| 3b79efb82b | |||
| 1c6ec6f17e | |||
| 619ff559e6 | |||
| b6fae96334 | |||
| 40b802ae86 | |||
| 1c8ed88dac | |||
| e5bf841d45 |
7 changed files with 138 additions and 49 deletions
|
|
@ -129,6 +129,49 @@ async def share_target(request: Request):
|
|||
headers={"Cache-Control": "no-cache"}
|
||||
)
|
||||
|
||||
# Cache-Reset-Seite — löscht SW + Caches, leitet zur App weiter
|
||||
@app.get("/update")
|
||||
async def force_update():
|
||||
from fastapi.responses import HTMLResponse
|
||||
html = """<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Ban Yaro — Aktualisieren</title>
|
||||
<style>
|
||||
body{font-family:-apple-system,sans-serif;display:flex;align-items:center;
|
||||
justify-content:center;min-height:100vh;margin:0;background:#faf8f5}
|
||||
.box{text-align:center;padding:2rem}
|
||||
h1{color:#C4843A;margin-bottom:.5rem}
|
||||
p{color:#666;margin-bottom:1.5rem}
|
||||
.sp{width:44px;height:44px;border:4px solid #eee;border-top-color:#C4843A;
|
||||
border-radius:50%;animation:s .8s linear infinite;margin:0 auto}
|
||||
@keyframes s{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>Ban Yaro</h1>
|
||||
<p>App wird aktualisiert…</p>
|
||||
<div class="sp"></div>
|
||||
</div>
|
||||
<script>
|
||||
(async () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const regs = await navigator.serviceWorker.getRegistrations();
|
||||
await Promise.all(regs.map(r => r.unregister()));
|
||||
}
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map(k => caches.delete(k)));
|
||||
window.location.replace('/');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
return HTMLResponse(html, headers={"Cache-Control": "no-store"})
|
||||
|
||||
|
||||
# SPA Fallback — ALLE nicht-API-Routen gehen zur index.html
|
||||
@app.get("/{full_path:path}")
|
||||
async def spa_fallback(full_path: str):
|
||||
|
|
|
|||
|
|
@ -1565,9 +1565,20 @@ textarea.form-control {
|
|||
ZENTRALE KARTE (map.js)
|
||||
============================================================ */
|
||||
.map-full-layout {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
position: fixed;
|
||||
top: calc(var(--header-height) + var(--safe-top));
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: calc(var(--nav-bottom-height) + var(--safe-bottom));
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.map-full-layout {
|
||||
top: 0;
|
||||
left: var(--nav-sidebar-width);
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.map-full {
|
||||
width: 100%;
|
||||
|
|
|
|||
|
|
@ -118,17 +118,15 @@
|
|||
.sidebar-backdrop { display: none !important; }
|
||||
}
|
||||
|
||||
/* Mobile Sidebar als Drawer */
|
||||
@media (max-width: 767px) {
|
||||
#sidebar {
|
||||
display: flex;
|
||||
z-index: 500;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.28s ease;
|
||||
}
|
||||
#sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
/* Karte: volle Höhe, kein Scrollen, kein Padding */
|
||||
#page-map {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#page-map > .page-body {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------
|
||||
|
|
@ -267,6 +265,30 @@
|
|||
#sidebar { display: flex; }
|
||||
}
|
||||
|
||||
/* Mobile Sidebar als Drawer */
|
||||
@media (max-width: 767px) {
|
||||
#sidebar {
|
||||
display: flex !important; /* überschreibt display:none aus Base */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: var(--nav-sidebar-width);
|
||||
z-index: 1000; /* klar über Backdrop (999) */
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.28s ease;
|
||||
background: var(--c-surface);
|
||||
box-shadow: none;
|
||||
}
|
||||
#sidebar.open {
|
||||
transform: translateX(0);
|
||||
box-shadow: 4px 0 24px rgba(0,0,0,0.18);
|
||||
}
|
||||
.sidebar-backdrop {
|
||||
z-index: 999; /* unter Sidebar, über Seiteninhalt */
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
padding: var(--space-6) var(--space-5);
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -20,33 +20,17 @@
|
|||
|
||||
<title>Ban Yaro</title>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig -->
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<link rel="stylesheet" href="/css/design-system.css">
|
||||
<link rel="stylesheet" href="/css/layout.css">
|
||||
<link rel="stylesheet" href="/css/components.css">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=32">
|
||||
<link rel="stylesheet" href="/css/components.css?v=32">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============================================================
|
||||
APP SHELL
|
||||
============================================================ -->
|
||||
<div id="app">
|
||||
<!-- Backdrop + Sidebar direkt im body (kein Ancestor-Stacking-Context) -->
|
||||
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
|
||||
|
||||
<!-- MOBILE HEADER (wird per JS mit Seitentitel befüllt) -->
|
||||
<header id="app-header">
|
||||
<button class="header-back hidden" id="header-back" aria-label="Zurück">←</button>
|
||||
<div id="header-dog-switcher" class="dog-switcher">
|
||||
<span class="header-title" id="header-title">Ban Yaro</span>
|
||||
</div>
|
||||
<div id="header-actions"></div>
|
||||
<button class="header-menu-btn" id="header-menu-btn" aria-label="Menü">☰</button>
|
||||
</header>
|
||||
|
||||
<!-- Backdrop für mobile Sidebar-Drawer -->
|
||||
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
|
||||
|
||||
<!-- DESKTOP SIDEBAR -->
|
||||
<nav id="sidebar" role="navigation" aria-label="Hauptnavigation">
|
||||
<nav id="sidebar" role="navigation" aria-label="Hauptnavigation">
|
||||
<div class="sidebar-logo" id="sidebar-dog-switcher">
|
||||
<img class="sidebar-logo-img" src="/icons/icon-180.png" alt="Ban Yaro">
|
||||
<span class="sidebar-logo-text">Ban Yaro</span>
|
||||
|
|
@ -111,6 +95,21 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ============================================================
|
||||
APP SHELL
|
||||
============================================================ -->
|
||||
<div id="app">
|
||||
|
||||
<!-- MOBILE HEADER -->
|
||||
<header id="app-header">
|
||||
<button class="header-back hidden" id="header-back" aria-label="Zurück">←</button>
|
||||
<div id="header-dog-switcher" class="dog-switcher">
|
||||
<span class="header-title" id="header-title">Ban Yaro</span>
|
||||
</div>
|
||||
<div id="header-actions"></div>
|
||||
<button class="header-menu-btn" id="header-menu-btn" aria-label="Menü">☰</button>
|
||||
</header>
|
||||
|
||||
<!-- HAUPT-INHALTSBEREICH -->
|
||||
<main id="page-content" role="main">
|
||||
|
||||
|
|
@ -215,9 +214,9 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/ui.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
<script src="/js/api.js?v=32"></script>
|
||||
<script src="/js/ui.js?v=32"></script>
|
||||
<script src="/js/app.js?v=32"></script>
|
||||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
|
|
|
|||
|
|
@ -193,7 +193,11 @@ const App = (() => {
|
|||
}
|
||||
|
||||
function _openSidebar() {
|
||||
document.getElementById('sidebar')?.classList.add('open');
|
||||
const s = document.getElementById('sidebar');
|
||||
if (!s) { UI.toast.error('sidebar: NULL'); return; }
|
||||
s.classList.add('open');
|
||||
const cs = getComputedStyle(s);
|
||||
UI.toast.info(`display:${cs.display} | transform:${cs.transform} | z:${cs.zIndex}`);
|
||||
document.getElementById('sidebar-backdrop')?.classList.add('visible');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -148,8 +148,9 @@ window.Page_map = (() => {
|
|||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 })
|
||||
.addTo(_map);
|
||||
|
||||
// invalidateSize nach kurzer Verzögerung, damit der Browser das Layout abgeschlossen hat
|
||||
setTimeout(() => _map.invalidateSize(), 150);
|
||||
// invalidateSize zweimal: einmal früh, einmal nach möglichen Layout-Delays
|
||||
setTimeout(() => _map.invalidateSize(), 100);
|
||||
setTimeout(() => _map.invalidateSize(), 600);
|
||||
window.addEventListener('resize', () => _map.invalidateSize());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@
|
|||
Offline-Cache + Push Notifications
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v27';
|
||||
const CACHE_VERSION = 'by-v33';
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
|
||||
// Diese Dateien werden beim Install gecacht (App Shell)
|
||||
// index.html wird NICHT pre-gecacht (immer Network-First)
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/css/design-system.css',
|
||||
'/css/layout.css',
|
||||
'/css/components.css',
|
||||
|
|
@ -44,7 +43,7 @@ self.addEventListener('activate', event => {
|
|||
});
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// FETCH — Cache-First für statische Assets, Network-First für API
|
||||
// FETCH — Network-First für HTML, Cache-First für Assets, Network für API
|
||||
// ----------------------------------------------------------
|
||||
self.addEventListener('fetch', event => {
|
||||
const url = new URL(event.request.url);
|
||||
|
|
@ -60,12 +59,25 @@ self.addEventListener('fetch', event => {
|
|||
return;
|
||||
}
|
||||
|
||||
// Navigation (index.html): immer Network-First
|
||||
if (event.request.mode === 'navigate') {
|
||||
event.respondWith(
|
||||
fetch(event.request)
|
||||
.then(response => {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
|
||||
return response;
|
||||
})
|
||||
.catch(() => caches.match('/'))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Statische Assets: Cache-First
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then(cached => cached || fetch(event.request)
|
||||
.then(response => {
|
||||
// Erfolgreiche Responses für statische Assets cachen
|
||||
if (response.ok && event.request.method === 'GET') {
|
||||
const clone = response.clone();
|
||||
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
|
||||
|
|
@ -74,7 +86,6 @@ self.addEventListener('fetch', event => {
|
|||
})
|
||||
)
|
||||
.catch(() => {
|
||||
// Offline-Fallback: App Shell zurückgeben
|
||||
if (event.request.mode === 'navigate') {
|
||||
return caches.match('/');
|
||||
}
|
||||
|
|
@ -136,7 +147,6 @@ self.addEventListener('notificationclick', event => {
|
|||
event.waitUntil(
|
||||
clients.matchAll({ type: 'window', includeUncontrolled: true })
|
||||
.then(windowClients => {
|
||||
// Offenes Fenster fokussieren
|
||||
for (const client of windowClients) {
|
||||
if (client.url.includes(self.location.origin)) {
|
||||
client.focus();
|
||||
|
|
@ -144,7 +154,6 @@ self.addEventListener('notificationclick', event => {
|
|||
return;
|
||||
}
|
||||
}
|
||||
// Neues Fenster öffnen
|
||||
return clients.openWindow(url);
|
||||
})
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue