Compare commits
No commits in common. "bf26e5faf4ff8b5cdfeafa2c34ace3ec2332223e" and "01081fcd75d34f35f03cf1c715da70b54c44bd07" have entirely different histories.
bf26e5faf4
...
01081fcd75
7 changed files with 49 additions and 138 deletions
|
|
@ -129,49 +129,6 @@ async def share_target(request: Request):
|
||||||
headers={"Cache-Control": "no-cache"}
|
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
|
# SPA Fallback — ALLE nicht-API-Routen gehen zur index.html
|
||||||
@app.get("/{full_path:path}")
|
@app.get("/{full_path:path}")
|
||||||
async def spa_fallback(full_path: str):
|
async def spa_fallback(full_path: str):
|
||||||
|
|
|
||||||
|
|
@ -1565,20 +1565,9 @@ textarea.form-control {
|
||||||
ZENTRALE KARTE (map.js)
|
ZENTRALE KARTE (map.js)
|
||||||
============================================================ */
|
============================================================ */
|
||||||
.map-full-layout {
|
.map-full-layout {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: calc(var(--header-height) + var(--safe-top));
|
inset: 0;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: calc(var(--nav-bottom-height) + var(--safe-bottom));
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.map-full-layout {
|
|
||||||
top: 0;
|
|
||||||
left: var(--nav-sidebar-width);
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.map-full {
|
.map-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
|
|
@ -118,15 +118,17 @@
|
||||||
.sidebar-backdrop { display: none !important; }
|
.sidebar-backdrop { display: none !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Karte: volle Höhe, kein Scrollen, kein Padding */
|
/* Mobile Sidebar als Drawer */
|
||||||
#page-map {
|
@media (max-width: 767px) {
|
||||||
height: 100%;
|
#sidebar {
|
||||||
overflow: hidden;
|
display: flex;
|
||||||
|
z-index: 500;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.28s ease;
|
||||||
|
}
|
||||||
|
#sidebar.open {
|
||||||
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
#page-map > .page-body {
|
|
||||||
padding: 0 !important;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------
|
/* ------------------------------------------------------------
|
||||||
|
|
@ -265,30 +267,6 @@
|
||||||
#sidebar { display: flex; }
|
#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 {
|
.sidebar-logo {
|
||||||
padding: var(--space-6) var(--space-5);
|
padding: var(--space-6) var(--space-5);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,32 @@
|
||||||
|
|
||||||
<title>Ban Yaro</title>
|
<title>Ban Yaro</title>
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css">
|
<link rel="stylesheet" href="/css/design-system.css">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=32">
|
<link rel="stylesheet" href="/css/layout.css">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=32">
|
<link rel="stylesheet" href="/css/components.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<!-- Backdrop + Sidebar direkt im body (kein Ancestor-Stacking-Context) -->
|
<!-- ============================================================
|
||||||
|
APP SHELL
|
||||||
|
============================================================ -->
|
||||||
|
<div id="app">
|
||||||
|
|
||||||
|
<!-- 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>
|
<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">
|
<div class="sidebar-logo" id="sidebar-dog-switcher">
|
||||||
<img class="sidebar-logo-img" src="/icons/icon-180.png" alt="Ban Yaro">
|
<img class="sidebar-logo-img" src="/icons/icon-180.png" alt="Ban Yaro">
|
||||||
|
|
@ -95,21 +111,6 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</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 -->
|
<!-- HAUPT-INHALTSBEREICH -->
|
||||||
<main id="page-content" role="main">
|
<main id="page-content" role="main">
|
||||||
|
|
||||||
|
|
@ -214,9 +215,9 @@
|
||||||
<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=32"></script>
|
<script src="/js/api.js"></script>
|
||||||
<script src="/js/ui.js?v=32"></script>
|
<script src="/js/ui.js"></script>
|
||||||
<script src="/js/app.js?v=32"></script>
|
<script src="/js/app.js"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,11 +193,7 @@ const App = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _openSidebar() {
|
function _openSidebar() {
|
||||||
const s = document.getElementById('sidebar');
|
document.getElementById('sidebar')?.classList.add('open');
|
||||||
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');
|
document.getElementById('sidebar-backdrop')?.classList.add('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -148,9 +148,8 @@ window.Page_map = (() => {
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 })
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 })
|
||||||
.addTo(_map);
|
.addTo(_map);
|
||||||
|
|
||||||
// invalidateSize zweimal: einmal früh, einmal nach möglichen Layout-Delays
|
// invalidateSize nach kurzer Verzögerung, damit der Browser das Layout abgeschlossen hat
|
||||||
setTimeout(() => _map.invalidateSize(), 100);
|
setTimeout(() => _map.invalidateSize(), 150);
|
||||||
setTimeout(() => _map.invalidateSize(), 600);
|
|
||||||
window.addEventListener('resize', () => _map.invalidateSize());
|
window.addEventListener('resize', () => _map.invalidateSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
Offline-Cache + Push Notifications
|
Offline-Cache + Push Notifications
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v33';
|
const CACHE_VERSION = 'by-v27';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
|
|
||||||
// index.html wird NICHT pre-gecacht (immer Network-First)
|
// Diese Dateien werden beim Install gecacht (App Shell)
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
|
'/',
|
||||||
'/css/design-system.css',
|
'/css/design-system.css',
|
||||||
'/css/layout.css',
|
'/css/layout.css',
|
||||||
'/css/components.css',
|
'/css/components.css',
|
||||||
|
|
@ -43,7 +44,7 @@ self.addEventListener('activate', event => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
// FETCH — Network-First für HTML, Cache-First für Assets, Network für API
|
// FETCH — Cache-First für statische Assets, Network-First für API
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
const url = new URL(event.request.url);
|
const url = new URL(event.request.url);
|
||||||
|
|
@ -59,25 +60,12 @@ self.addEventListener('fetch', event => {
|
||||||
return;
|
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
|
// Statische Assets: Cache-First
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request)
|
caches.match(event.request)
|
||||||
.then(cached => cached || fetch(event.request)
|
.then(cached => cached || fetch(event.request)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
// Erfolgreiche Responses für statische Assets cachen
|
||||||
if (response.ok && event.request.method === 'GET') {
|
if (response.ok && event.request.method === 'GET') {
|
||||||
const clone = response.clone();
|
const clone = response.clone();
|
||||||
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
|
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
|
||||||
|
|
@ -86,6 +74,7 @@ self.addEventListener('fetch', event => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
// Offline-Fallback: App Shell zurückgeben
|
||||||
if (event.request.mode === 'navigate') {
|
if (event.request.mode === 'navigate') {
|
||||||
return caches.match('/');
|
return caches.match('/');
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +136,7 @@ self.addEventListener('notificationclick', event => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
clients.matchAll({ type: 'window', includeUncontrolled: true })
|
clients.matchAll({ type: 'window', includeUncontrolled: true })
|
||||||
.then(windowClients => {
|
.then(windowClients => {
|
||||||
|
// Offenes Fenster fokussieren
|
||||||
for (const client of windowClients) {
|
for (const client of windowClients) {
|
||||||
if (client.url.includes(self.location.origin)) {
|
if (client.url.includes(self.location.origin)) {
|
||||||
client.focus();
|
client.focus();
|
||||||
|
|
@ -154,6 +144,7 @@ self.addEventListener('notificationclick', event => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Neues Fenster öffnen
|
||||||
return clients.openWindow(url);
|
return clients.openWindow(url);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue