diff --git a/backend/static/css/components.css b/backend/static/css/components.css index b9ae47e..5d0369d 100644 --- a/backend/static/css/components.css +++ b/backend/static/css/components.css @@ -2036,6 +2036,66 @@ textarea.form-control { .map-place-btns .btn { flex: 1; } /* Statusleiste: nur Info, unten links */ +/* Scan-Fortschrittsbalken — oben im Kartenfenster */ +.map-scan-bar { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1001; + height: 40px; + background: rgba(255,255,255,0.92); + backdrop-filter: blur(6px); + border-bottom: 1px solid var(--c-border-light); + display: flex; + align-items: center; + gap: var(--space-3); + padding: 0 var(--space-4); + opacity: 0; + pointer-events: none; + transition: opacity 0.25s; + overflow: hidden; +} +.map-scan-bar.active { + opacity: 1; +} +.map-scan-bar__track { + flex: 1; + height: 6px; + background: var(--c-border-light); + border-radius: var(--radius-full); + overflow: hidden; + position: relative; +} +.map-scan-bar__fill { + height: 6px; + background: var(--c-primary); + border-radius: var(--radius-full); + width: 0%; + transition: width 0.4s ease; + position: relative; +} +.map-scan-bar__fill::after { + content: ''; + position: absolute; + top: 0; right: 0; bottom: 0; + width: 60px; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.5), transparent); + animation: scan-shimmer 1.2s ease-in-out infinite; +} +@keyframes scan-shimmer { + 0% { transform: translateX(-60px); opacity: 0; } + 50% { opacity: 1; } + 100% { transform: translateX(60px); opacity: 0; } +} +.map-scan-bar__label { + font-size: 12px; + font-weight: 600; + color: var(--c-primary); + min-width: 80px; + white-space: nowrap; +} + .map-statusbar { position: absolute; bottom: var(--space-3); diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 597a2ba..3dfcd92 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '96'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '97'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/map.js b/backend/static/js/pages/map.js index d25b4fe..8ac88b1 100644 --- a/backend/static/js/pages/map.js +++ b/backend/static/js/pages/map.js @@ -183,6 +183,13 @@ window.Page_map = (() => { +
+ +
+
+
+
+
@@ -402,9 +409,33 @@ window.Page_map = (() => { else { el.textContent = `Zoom ${z}`; el.style.opacity = '1'; } } - function _setOsmStatus(text) { + function _setOsmStatus(text, pct = null) { const el = document.getElementById('map-osm-status'); if (el) el.textContent = text; + + const bar = document.getElementById('map-scan-bar'); + const fill = document.getElementById('map-scan-fill'); + const label = document.getElementById('map-scan-label'); + if (!bar || !fill || !label) return; + + if (!text) { + bar.classList.remove('active'); + return; + } + bar.classList.add('active'); + if (pct !== null) { + fill.style.width = `${pct}%`; + label.textContent = pct < 100 ? `Scanne ${pct}\u202f%` : ''; + } else { + fill.style.width = '30%'; + fill.classList.add('indeterminate'); + label.textContent = text; + } + if (pct === 100) { + fill.style.width = '100%'; + label.textContent = ''; + setTimeout(() => bar.classList.remove('active'), 600); + } } // ---------------------------------------------------------- @@ -464,7 +495,7 @@ window.Page_map = (() => { } // Phase 1: sofort DB-Daten zeigen (fast=true) - _setOsmStatus('Lade\u2026'); + _setOsmStatus('Lade…'); const fastTasks = activeLayers.map(async ([layerKey, osmType]) => { const params = new URLSearchParams({ type: osmType, fast: 'true', ...bbox }); try { @@ -475,12 +506,12 @@ window.Page_map = (() => { }); const fastCounts = await Promise.all(fastTasks); const fastTotal = fastCounts.reduce((a, b) => a + b, 0); - if (fastTotal > 0) _setOsmStatus(`${fastTotal} aus Datenbank`); + if (fastTotal > 0) _setOsmStatus(`${fastTotal} aus Datenbank`, 20); // Phase 2: Overpass für fehlende Tiles — mit %-Fortschritt let _done = 0; const _total = activeLayers.length; - _setOsmStatus(fastTotal > 0 ? `${fastTotal} gefunden \u00b7 Scanne 0\u202f%` : 'Scanne 0\u202f%'); + _setOsmStatus('Scanne…', 20); const freshTasks = activeLayers.map(async ([layerKey, osmType]) => { const params = new URLSearchParams({ type: osmType, ...bbox }); @@ -489,12 +520,9 @@ window.Page_map = (() => { const osmCount = _layers[layerKey].filter(m => !m._ownPlace).length; if (pois.length !== osmCount) _replaceOsmMarkers(layerKey, pois); _done++; - const pct = Math.round(_done / _total * 100); + const pct = Math.round(20 + _done / _total * 80); const total = Object.values(_layers).flat().filter(m => !m._ownPlace).length; - _setOsmStatus(pct < 100 - ? `${total} gefunden \u00b7 Scanne ${pct}\u202f%` - : `${total} Marker` - ); + _setOsmStatus(pct < 100 ? `Scanne…` : `${total} Marker`, pct); return pois.length; } catch { _done++; diff --git a/backend/static/sw.js b/backend/static/sw.js index 224d462..ff80e19 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v121'; +const CACHE_VERSION = 'by-v122'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten