diff --git a/VERSION b/VERSION index bcdab7e..b0ba9ce 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1187 \ No newline at end of file +1188 \ No newline at end of file diff --git a/backend/static/index.html b/backend/static/index.html index c285f9b..686e0c9 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,14 +86,14 @@ Ban Yaro - + - - - - - + + + + + @@ -617,11 +617,11 @@ - - - - - + + + + + @@ -631,7 +631,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index a8447a4..ea58fb5 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 = '1187'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1188'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen 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_VERSION = APP_VERSION; diff --git a/backend/static/js/pages/map.js b/backend/static/js/pages/map.js index 8dc7f9d..584b4f7 100644 --- a/backend/static/js/pages/map.js +++ b/backend/static/js/pages/map.js @@ -426,7 +426,6 @@ window.Page_map = (() => { let _tempDebounce = null; async function _toggleRadar() { - if (_engineGL) { UI.toast.info('Regenradar im neuen Karten-Modus in Kürze.'); return; } if (!App.hasPro(_appState?.user)) { UI.toast.info('Regenradar ist ein Pro-Feature.'); return; @@ -434,7 +433,7 @@ window.Page_map = (() => { const btn = document.getElementById('map-radar-btn'); if (_radarActive) { _radarActive = false; - if (_radarLayer) { _map.removeLayer(_radarLayer); _radarLayer = null; } + if (_radarLayer) { _wxRemoveRaster(_radarLayer); _radarLayer = null; } clearInterval(_radarTimer); btn?.classList.remove('active'); return; @@ -447,7 +446,6 @@ window.Page_map = (() => { } async function _toggleTemp() { - if (_engineGL) { UI.toast.info('Temperatur-Layer im neuen Karten-Modus in Kürze.'); return; } if (!App.hasPro(_appState?.user)) { UI.toast.info('Temperatur-Layer ist ein Pro-Feature.'); return; @@ -455,11 +453,11 @@ window.Page_map = (() => { const btn = document.getElementById('map-temp-btn'); if (_tempActive) { _tempActive = false; - if (_tempLayer) { _map.removeLayer(_tempLayer); _tempLayer = null; } - _tempMarkers.forEach(m => _map.removeLayer(m)); + if (_tempLayer) { _wxRemoveRaster(_tempLayer); _tempLayer = null; } + _tempMarkers.forEach(m => m.remove()); _tempMarkers = []; clearTimeout(_tempDebounce); - _map.off('moveend zoomend', _debounceTempLabels); + _mapOffMove(_debounceTempLabels); document.getElementById('map-temp-legend')?.remove(); btn?.classList.remove('active'); return; @@ -468,16 +466,9 @@ window.Page_map = (() => { btn?.classList.add('active'); try { const cfg = await API.get('/weather/layer-tiles?layer=temp_new'); - _tempLayer = window.L.tileLayer(cfg.url, { - opacity: 1.0, - tileSize: 256, - zIndex: 290, - maxNativeZoom: cfg.maxNativeZoom ?? 18, - maxZoom: 18, - attribution: 'Temp © OpenWeatherMap', - }).addTo(_map); + _tempLayer = _wxAddRaster('temp', cfg.url, 1.0, cfg.maxNativeZoom ?? 18); _showTempLegend(); - _map.on('moveend zoomend', _debounceTempLabels); + _mapOnMove(_debounceTempLabels); await _loadTempLabels(); } catch { _tempActive = false; @@ -527,24 +518,17 @@ window.Page_map = (() => { )); // Alte Marker entfernen - _tempMarkers.forEach(m => _map.removeLayer(m)); + _tempMarkers.forEach(m => m.remove()); _tempMarkers = []; results.filter(Boolean).forEach(({ lat, lon, t }) => { if (t == null) return; const temp = Math.round(t); const color = _tempColor(temp); - const icon = window.L.divIcon({ - className: '', - html: `
${temp}°
`, - iconSize: null, - iconAnchor: [20, 10], - }); - const m = window.L.marker([lat, lon], { icon, zIndexOffset: 500, interactive: false }); - m.addTo(_map); - _tempMarkers.push(m); + box-shadow:0 1px 4px rgba(0,0,0,0.5);text-shadow:0 1px 2px rgba(0,0,0,0.4)">${temp}°`; + _tempMarkers.push(_wxAddTempMarker(lat, lon, html)); }); } @@ -582,15 +566,8 @@ window.Page_map = (() => { if (!frames.length) return; const latest = frames[frames.length - 1].path; const url = `https://tilecache.rainviewer.com${latest}/256/{z}/{x}/{y}/4/1_1.png`; - if (_radarLayer) _map.removeLayer(_radarLayer); - _radarLayer = window.L.tileLayer(url, { - opacity: 0.7, - tileSize: 256, - zIndex: 300, - maxNativeZoom: 7, - maxZoom: 18, - attribution: 'Radar © RainViewer', - }).addTo(_map); + if (_radarLayer) _wxRemoveRaster(_radarLayer); + _radarLayer = _wxAddRaster('radar', url, 0.7, 7); } catch { /* still */ } } @@ -737,6 +714,45 @@ window.Page_map = (() => { return { north: n + dLat, south: s - dLat, east: e + dLon, west: w - dLon }; } + // ---- Engine-neutrale Wetter-Helfer (Raster-Overlay + Move-Listener + Temp-Marker) ---- + // Raster-Overlay (Radar/Temp). handle = Leaflet-Layer | GL-Layer-ID-String. + function _wxAddRaster(key, url, opacity, maxNativeZoom) { + if (_engineGL) { + const id = 'wx-' + key; + _wxRemoveRaster(id); + _map.addSource(id, { type: 'raster', tiles: [url], tileSize: 256, maxzoom: maxNativeZoom || 18 }); + // Unter die POI-Marker/Cluster einfügen (sonst verdeckt das Overlay die Marker). + let beforeId; + const _ls = (_map.getStyle().layers || []); + for (let i = 0; i < _ls.length; i++) { if (/^(cl-|clsym-|pt-|danger)/.test(_ls[i].id)) { beforeId = _ls[i].id; break; } } + _map.addLayer({ id: id, type: 'raster', source: id, paint: { 'raster-opacity': opacity } }, beforeId); + return id; + } + return window.L.tileLayer(url, { opacity: opacity, tileSize: 256, maxNativeZoom: maxNativeZoom || 18, maxZoom: 18 }).addTo(_map); + } + function _wxRemoveRaster(handle) { + if (!handle || !_map) return; + if (typeof handle === 'string') { + if (_map.getLayer(handle)) _map.removeLayer(handle); + if (_map.getSource(handle)) _map.removeSource(handle); + } else if (handle.remove) { + handle.remove(); + } + } + function _mapOnMove(fn) { if (_engineGL) _map.on('moveend', fn); else _map.on('moveend zoomend', fn); } + function _mapOffMove(fn) { if (_engineGL) _map.off('moveend', fn); else _map.off('moveend zoomend', fn); } + // Temp-Pill an lat/lon. html = der innere Pill-
. Beide Engines: .remove() vorhanden. + function _wxAddTempMarker(lat, lon, html) { + if (_engineGL) { + const wrap = document.createElement('div'); + wrap.innerHTML = html; wrap.style.pointerEvents = 'none'; + return new maplibregl.Marker({ element: wrap.firstElementChild || wrap, anchor: 'center' }) + .setLngLat([lon, lat]).addTo(_map); + } + const icon = window.L.divIcon({ className: '', html: html, iconSize: null, iconAnchor: [20, 10] }); + return window.L.marker([lat, lon], { icon: icon, zIndexOffset: 500, interactive: false }).addTo(_map); + } + function _initMapGL() { const el = document.getElementById('central-map'); if (!el || !window.maplibregl || _map) return; @@ -2123,7 +2139,6 @@ window.Page_map = (() => { // GPS-Aufzeichnung // ---------------------------------------------------------- function _toggleRecording() { - if (_engineGL && !_recActive) { UI.toast.info('GPS-Aufzeichnung im neuen Karten-Modus in Kürze.'); return; } if (!_recActive) _startRecording(); else _stopRecording(); } @@ -2167,7 +2182,6 @@ window.Page_map = (() => { } async function _startRecording(resume) { - if (_engineGL) { if (!resume) UI.toast.info('GPS-Aufzeichnung im neuen Karten-Modus in Kürze.'); return; } if (!_appState.user) { UI.toast.warning('Bitte zuerst anmelden.'); App.navigate('settings'); @@ -2226,11 +2240,17 @@ window.Page_map = (() => { ); // Fortgesetzte Aufzeichnung: bestehenden Track sofort einzeichnen - if (resume && _recTrack.length && _map && window.L) { - _recPolyline = L.polyline(_recTrack.map(p => [p.lat, p.lon]), { color: '#EF4444', weight: 5, opacity: 0.9 }).addTo(_map); + if (resume && _recTrack.length && _map) { const last = _recTrack[_recTrack.length - 1]; - _recMarker = L.circleMarker([last.lat, last.lon], { radius: 8, color: '#EF4444', fillColor: '#fff', fillOpacity: 1, weight: 3 }).addTo(_map); - _map.panTo([last.lat, last.lon]); + if (_engineGL) { + _recTrackGL(); + _updateRecMarker(last.lat, last.lon); + _map.panTo([last.lon, last.lat]); + } else if (window.L) { + _recPolyline = L.polyline(_recTrack.map(p => [p.lat, p.lon]), { color: '#EF4444', weight: 5, opacity: 0.9 }).addTo(_map); + _updateRecMarker(last.lat, last.lon); + _map.panTo([last.lat, last.lon]); + } _updateRecStatus(); } _persistRec(true); @@ -2282,21 +2302,57 @@ window.Page_map = (() => { return 2 * R * Math.asin(Math.sqrt(a)); } + // GL: Track-Linie aus dem vollen _recTrack (geojson line source) setzen. + function _recTrackGL() { + const geo = { type: 'Feature', geometry: { type: 'LineString', coordinates: _recTrack.map(p => [p.lon, p.lat]) } }; + if (!_map.getSource('rectrack')) { + _map.addSource('rectrack', { type: 'geojson', data: geo }); + _map.addLayer({ id: 'rectrack', type: 'line', source: 'rectrack', + layout: { 'line-cap': 'round', 'line-join': 'round' }, + paint: { 'line-color': '#EF4444', 'line-width': 5, 'line-opacity': 0.9 } }); + } else { + _map.getSource('rectrack').setData(geo); + } + } + function _updateRecMarker(lat, lon) { + if (_engineGL) { + if (!_recMarker) { + const d = document.createElement('div'); + d.style.cssText = 'width:16px;height:16px;border-radius:50%;background:#fff;border:3px solid #EF4444;box-shadow:0 1px 4px rgba(0,0,0,.4)'; + _recMarker = new maplibregl.Marker({ element: d, anchor: 'center' }).setLngLat([lon, lat]).addTo(_map); + } else { _recMarker.setLngLat([lon, lat]); } + } else { + if (!_recMarker) { + _recMarker = L.circleMarker([lat, lon], { radius: 8, color: '#EF4444', fillColor: '#fff', fillOpacity: 1, weight: 3 }).addTo(_map); + } else { _recMarker.setLatLng([lat, lon]); } + } + } + // Track + Marker entfernen (engine-neutral). + function _recCleanupMap() { + if (_engineGL && _map) { + if (_map.getLayer && _map.getLayer('rectrack')) _map.removeLayer('rectrack'); + if (_map.getSource && _map.getSource('rectrack')) _map.removeSource('rectrack'); + } else if (_recPolyline) { _recPolyline.remove(); } + _recPolyline = null; + if (_recMarker) { _recMarker.remove(); _recMarker = null; } + } + function _updateRecMap(lat, lon) { - if (!_map || !window.L) return; + if (!_map) return; + if (_engineGL) { + _recTrackGL(); + _updateRecMarker(lat, lon); + _map.panTo([lon, lat]); // MapLibre: [lng,lat] + return; + } + if (!window.L) return; const ll = [lat, lon]; if (!_recPolyline) { _recPolyline = L.polyline([ll], { color: '#EF4444', weight: 5, opacity: 0.9 }).addTo(_map); } else { _recPolyline.addLatLng(ll); } - if (!_recMarker) { - _recMarker = L.circleMarker(ll, { - radius: 8, color: '#EF4444', fillColor: '#fff', fillOpacity: 1, weight: 3, - }).addTo(_map); - } else { - _recMarker.setLatLng(ll); - } + _updateRecMarker(lat, lon); _map.panTo(ll); } @@ -2333,8 +2389,7 @@ window.Page_map = (() => { if (_recTrack.length < 2) { UI.toast.warning('Zu wenige GPS-Punkte — bitte etwas länger laufen.'); - if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; } - if (_recMarker) { _recMarker.remove(); _recMarker = null; } + _recCleanupMap(); _recDone(); return; } @@ -2468,8 +2523,7 @@ window.Page_map = (() => { document.getElementById('rms-discard')?.addEventListener('click', () => { UI.modal.close(); - if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; } - if (_recMarker) { _recMarker.remove(); _recMarker = null; } + _recCleanupMap(); _recDone(); }); @@ -2506,8 +2560,7 @@ window.Page_map = (() => { dog_ids: dogIds.length ? dogIds : null, }); UI.modal.close(); - if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; } - if (_recMarker) { _recMarker.remove(); _recMarker = null; } + _recCleanupMap(); _recDone(); if (saved.is_valid === false) { UI.toast.warning(`Route „${saved.name}" gespeichert — wird nicht für Statistiken gewertet (Geschwindigkeit zu hoch).`); diff --git a/backend/static/landing.html b/backend/static/landing.html index 59f327e..382560f 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -4,7 +4,7 @@ - + Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz diff --git a/backend/static/sw.js b/backend/static/sw.js index 66e353a..558ccf0 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -4,7 +4,7 @@ ============================================================ */ // ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab -const VER = '1187'; +const VER = '1188'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten