From 96bd57f0adc475b017da4f298e0d8b35c5ee4b99 Mon Sep 17 00:00:00 2001 From: rene Date: Wed, 15 Apr 2026 16:37:26 +0200 Subject: [PATCH] Fix: Routen-Cards mit OSM-Mini-Karte statt SVG, Username ohne Prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Jede Route-Card zeigt echte OSM-Tiles als Kartenvorschau (Leaflet lazy-laden + IntersectionObserver → tiles erst wenn sichtbar) - Track (orange), Start (grün), Ziel (rot) als Overlay - Interaktion komplett deaktiviert (drag/zoom/click off) - Username ohne "von " — kürzer, kein redundanter Text - _svgPreview() bleibt als interner Fallback erhalten --- backend/static/js/pages/routes.js | 85 ++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/backend/static/js/pages/routes.js b/backend/static/js/pages/routes.js index 82c0423..f08a87a 100644 --- a/backend/static/js/pages/routes.js +++ b/backend/static/js/pages/routes.js @@ -17,6 +17,10 @@ window.Page_routes = (() => { let _sortBy = 'newest'; let _onlyMine = false; + // Mini-Karten auf den Route-Cards + let _miniMaps = new Map(); // routeId → L.map + let _leafletReady = false; + const DIFFICULTY_LABEL = { leicht: '🟢 Leicht', mittel: '🟡 Mittel', anspruchsvoll: '🔴 Anspruchsvoll' }; const TERRAIN_LABEL = { wald: '🌲 Wald', asphalt: '🛣️ Asphalt', wiese: '🌿 Wiese', mix: '🔀 Mix' }; const HUNDE_LABEL = { eingeschränkt: '🐾', gut: '🐾🐾', sehr_gut: '🐾🐾🐾', premium: '🐾🐾🐾🐾' }; @@ -37,10 +41,26 @@ window.Page_routes = (() => { _container = container; _appState = appState; _render(); + _loadLeaflet(); // fire & forget — bereit wenn Cards gerendert werden try { _userPos = await API.getLocation(); } catch {} _loadData(); } + async function _loadLeaflet() { + if (_leafletReady || window.L) { _leafletReady = true; return; } + if (!document.querySelector('link[href="/css/leaflet.css"]')) { + const l = document.createElement('link'); + l.rel = 'stylesheet'; l.href = '/css/leaflet.css'; + document.head.appendChild(l); + } + await new Promise((res, rej) => { + const s = document.createElement('script'); + s.src = '/js/leaflet.js'; s.onload = res; s.onerror = rej; + document.head.appendChild(s); + }); + _leafletReady = true; + } + function refresh() { _loadData(); } function onDogChange() {} @@ -205,7 +225,12 @@ window.Page_routes = (() => { } return; } + // Alte Mini-Maps zerstören bevor DOM neu geschrieben wird + _miniMaps.forEach(m => m.remove()); + _miniMaps.clear(); + grid.innerHTML = _filtered.map(r => _cardHTML(r)).join(''); + _initMiniMaps(); grid.querySelectorAll('.rk-card').forEach(card => { card.addEventListener('click', e => { if (e.target.closest('.rk-stars,.rk-dl-btn')) return; @@ -236,16 +261,16 @@ window.Page_routes = (() => { const paws = HUNDE_LABEL[r.hunde_tauglichkeit] || ''; const dist = r.distanz_km ? `${r.distanz_km.toFixed(1)} km` : ''; const dur = r.dauer_min ? _fmtDur(r.dauer_min) : ''; - const preview = _svgPreview(r.preview_track || []); const firstPhoto = (r.foto_urls || [])[0]; + const previewContent = firstPhoto + ? `` + : `
`; return `
-
- ${firstPhoto - ? `` - : preview} -
+
${previewContent}
${_esc(r.name)}
@@ -263,7 +288,7 @@ window.Page_routes = (() => { @@ -279,7 +304,51 @@ window.Page_routes = (() => { } // ---------------------------------------------------------- - // SVG-Vorschau + // Mini-Karten (OSM-Tiles via Leaflet, lazy per IntersectionObserver) + // ---------------------------------------------------------- + function _initMiniMaps() { + const init = () => { + const obs = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + obs.unobserve(entry.target); + _buildMiniMap(entry.target); + }); + }, { rootMargin: '150px' }); + document.querySelectorAll('.rk-mini-map').forEach(el => obs.observe(el)); + }; + + if (window.L) { init(); return; } + // Leaflet noch am Laden — kurz pollen + let tries = 0; + const poll = setInterval(() => { + if (window.L || ++tries > 30) { clearInterval(poll); if (window.L) init(); } + }, 100); + } + + function _buildMiniMap(el) { + const track = JSON.parse(el.dataset.track || '[]'); + const routeId = parseInt(el.dataset.id); + if (track.length < 2) { + el.innerHTML = '
🗺️
'; + return; + } + const lls = track.map(p => [p.lat, p.lon]); + const m = L.map(el, { + zoomControl: false, attributionControl: false, + dragging: false, touchZoom: false, scrollWheelZoom: false, + doubleClickZoom: false, keyboard: false, boxZoom: false, + }); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 17 }).addTo(m); + const poly = L.polyline(lls, { color: '#C4843A', weight: 3, opacity: 0.9 }).addTo(m); + L.circleMarker(lls[0], { radius: 5, color: '#22C55E', fillColor: '#22C55E', fillOpacity: 1, weight: 1.5 }).addTo(m); + L.circleMarker(lls.at(-1), { radius: 5, color: '#EF4444', fillColor: '#EF4444', fillOpacity: 1, weight: 1.5 }).addTo(m); + m.fitBounds(poly.getBounds(), { padding: [8, 8] }); + _miniMaps.set(routeId, m); + } + + // ---------------------------------------------------------- + // SVG-Vorschau (Fallback, wird nicht mehr direkt genutzt) // ---------------------------------------------------------- function _svgPreview(track) { if (!track || track.length < 2)