Sprint D: Karten-Familie auf UI.map.create+svgMarker konsolidiert, SW by-v1107

Neue zentrale Helper (in Sprint B vorbereitet) jetzt von 5 Seiten genutzt:

walks.js (1 Karten-Init):
- L.map+L.tileLayer → await UI.map.create('walks-map', {...})
- _initMap zu async, Aufrufer in _switchView und _loadData angepasst
- Mini-Karte im Walk-Formular (Modal) bleibt unverändert
  (braucht eigene dragging/scrollWheelZoom-Options)
- view-toggle nicht migriert (responsive CSS-Konflikt mit Desktop)

poison.js (1 Karten-Init):
- L.map+L.tileLayer → await UI.map.create('poison-map', {...})
- _initMap zu async, manueller UI.loadLeaflet entfernt
- DangerCircle + User-Marker unverändert

events.js (1 Karten-Init + Diamant-Marker):
- await UI.map.create('ev-map', {...})
- Rotierter Diamant: L.divIcon+L.marker → UI.map.svgMarker
  (HTML 1:1 erhalten)

lost.js (1 Karten-Init + Puls-Marker):
- Eigene async _loadLeaflet() Funktion komplett entfernt — UI.map.create
  übernimmt das jetzt zentral
- await UI.map.create('lost-map', {...})
- Puls-Animation 🐕: L.divIcon+L.marker → UI.map.svgMarker
- _initMap zu async

routes.js (6 von 7 Karten-Inits):
- _suggestMap, _recMap, _searchMap, _navMap, trimMap, _buildDetailMap
  alle auf UI.map.create umgestellt + zu async
- _buildMiniMap (Route-Card-Preview) bleibt unverändert
  (braucht 6 spezifische Interaction-Disable Options)
- View-Toggle auf neue .map-list-toggle Klasse umgestellt
  (Border-Inline-Styles raus)

NEUE CSS-KLASSE in components.css:
- .map-list-toggle (vereinheitlichter Karten/Listen-Umschalter)
- Verwendet von routes.js; walks/events können später folgen

Tests 19/19 grün. GPS-Tracking-Logik (Polylines, Recording, Trim)
komplett unangetastet. Marker-Cluster-Logik unverändert.
This commit is contained in:
rene 2026-05-27 08:17:06 +02:00
parent c8ef4939f1
commit 73872e2c21
11 changed files with 130 additions and 152 deletions

View file

@ -193,20 +193,15 @@ window.Page_routes = (() => {
background:var(--c-surface);color:var(--c-text);outline:none;
box-sizing:border-box;">
</div>
<div style="display:flex;border:1.5px solid var(--c-border);border-radius:10px;
overflow:hidden;flex-shrink:0;height:46px;box-sizing:border-box">
<div class="map-list-toggle" style="flex-shrink:0;width:auto">
<button id="rk-view-list" title="Liste"
style="width:44px;height:100%;border:none;cursor:pointer;
display:flex;align-items:center;justify-content:center;
background:${_viewMode==='list' ? 'var(--c-primary)' : 'var(--c-surface)'};
color:${_viewMode==='list' ? '#fff' : 'var(--c-text-secondary)'}">
class="${_viewMode==='list' ? 'active' : ''}"
style="flex:0 0 auto;width:44px">
${UI.icon('list')}
</button>
<button id="rk-view-map" title="Karte"
style="width:44px;height:100%;border:none;border-left:1.5px solid var(--c-border);
cursor:pointer;display:flex;align-items:center;justify-content:center;
background:${_viewMode==='map' ? 'var(--c-primary)' : 'var(--c-surface)'};
color:${_viewMode==='map' ? '#fff' : 'var(--c-text-secondary)'}">
class="${_viewMode==='map' ? 'active' : ''}"
style="flex:0 0 auto;width:44px">
${UI.icon('map-trifold')}
</button>
</div>
@ -602,16 +597,18 @@ window.Page_routes = (() => {
</button>
</div>`;
const _initMap = () => {
const _initMap = async () => {
const mapEl = document.getElementById('rks-map');
if (!mapEl || !window.L) return;
if (!mapEl) return;
if (_suggestMap) { _suggestMap.remove(); _suggestMap = null; }
const track = result.gps_track || [];
if (track.length < 2) return;
const lls = track.map(p => [p.lat, p.lon]);
_suggestMap = L.map(mapEl, { zoomControl: false, attributionControl: false,
dragging: true, touchZoom: true, scrollWheelZoom: false });
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_suggestMap);
_suggestMap = await UI.map.create(mapEl, {
center: lls[0], zoom: 14,
zoomControl: false, attributionControl: false,
});
_suggestMap.scrollWheelZoom.disable();
const poly = L.polyline(lls, { color: '#C4843A', weight: 4, opacity: 0.9 }).addTo(_suggestMap);
L.circleMarker(lls[0], { radius:7, color:'#22C55E', fillColor:'#22C55E', fillOpacity:1, weight:2 }).addTo(_suggestMap);
L.circleMarker(lls.at(-1), { radius:7, color:'#EF4444', fillColor:'#EF4444', fillOpacity:1, weight:2 }).addTo(_suggestMap);
@ -619,12 +616,7 @@ window.Page_routes = (() => {
_suggestMap.fitBounds(poly.getBounds(), { padding: [16, 16] });
setTimeout(() => _suggestMap?.invalidateSize(), 120);
};
if (window.L) { _initMap(); } else {
let tries = 0;
const poll = setInterval(() => {
if (window.L || ++tries > 40) { clearInterval(poll); if (window.L) _initMap(); }
}, 100);
}
_initMap();
document.getElementById('rks-nav-btn')?.addEventListener('click', () => {
_openNavOverlay({ id: 'suggest-' + Date.now(), name: result.name,
@ -668,8 +660,6 @@ window.Page_routes = (() => {
if (!_appState.user) { UI.toast.warning('Bitte anmelden.'); return; }
if (_recOvl) return;
try { await (UI.loadLeaflet?.() ?? Promise.resolve()); }
catch { UI.toast.warning('Karte offline nicht verfügbar — GPS-Aufzeichnung läuft trotzdem.'); }
const ovl = document.createElement('div');
ovl.id = 'rk-rec-ovl';
@ -733,10 +723,10 @@ window.Page_routes = (() => {
// Map-Setup: Leaflet könnte offline fehlen → alles in try/catch
const pos = _userPos || { lat: 48.1, lon: 11.5 };
try {
if (!window.L) throw new Error('Leaflet not loaded');
_recMap = L.map(ovl.querySelector('#rk-rec-map-wrap'), { zoomControl: false, attributionControl: false })
.setView([pos.lat, pos.lon], 15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_recMap);
_recMap = await UI.map.create(ovl.querySelector('#rk-rec-map-wrap'), {
center: [pos.lat, pos.lon], zoom: 15,
zoomControl: false, attributionControl: false,
});
_recLocMarker = L.circleMarker([pos.lat, pos.lon], {
radius: 8, color: '#fff', weight: 2.5, fillColor: '#3b82f6', fillOpacity: 1
}).addTo(_recMap);
@ -1127,8 +1117,7 @@ window.Page_routes = (() => {
document.body.appendChild(sec);
document.getElementById('rk-map-back')?.addEventListener('click', () => _switchView('list'));
// Wie _initMiniMaps: pollen bis window.L bereit ist
_pollAndInitSearchMap();
_initSearchMap();
} else {
document.getElementById('rk-map-section')?.remove();
@ -1140,26 +1129,16 @@ window.Page_routes = (() => {
// ----------------------------------------------------------
// Suchkarte
// ----------------------------------------------------------
function _pollAndInitSearchMap() {
if (window.L) { _initSearchMap(); return; }
let tries = 0;
const poll = setInterval(() => {
if (window.L || ++tries > 40) {
clearInterval(poll);
if (window.L) _initSearchMap();
}
}, 100);
}
function _initSearchMap() {
async function _initSearchMap() {
if (!document.getElementById('rk-search-map')) return;
const center = _userPos ? [_userPos.lat, _userPos.lon] : [51.1, 10.4];
const zoom = _userPos ? 13 : 6;
_searchMap = L.map('rk-search-map', { zoomControl: true, attributionControl: false })
.setView(center, zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_searchMap);
_searchMap = await UI.map.create('rk-search-map', {
center, zoom,
zoomControl: true, attributionControl: false,
});
setTimeout(() => _searchMap?.invalidateSize(), 100);
setTimeout(() => _searchMap?.invalidateSize(), 600);
_renderRoutesOnMap();
@ -1586,8 +1565,6 @@ window.Page_routes = (() => {
try { await DeviceOrientationEvent.requestPermission(); } catch {}
}
await UI.loadLeaflet?.() ?? Promise.resolve();
const ovl = document.createElement('div');
ovl.id = 'rk-nav-ovl';
ovl.style.cssText = 'position:fixed;inset:0;z-index:850;display:flex;flex-direction:column;background:var(--c-bg)';
@ -1700,9 +1677,10 @@ window.Page_routes = (() => {
// Karte initialisieren
const mapEl = document.getElementById('rk-nav-map');
const mid = track[Math.floor(track.length / 2)];
_navMap = L.map(mapEl, { zoomControl: false, attributionControl: false })
.setView([mid.lat, mid.lon], 15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(_navMap);
_navMap = await UI.map.create(mapEl, {
center: [mid.lat, mid.lon], zoom: 15,
zoomControl: false, attributionControl: false,
});
// Route-Polylines: erledigt (grün) + ausstehend (orange)
const doneLine = L.polyline([], { color: '#22c55e', weight: 5, opacity: 0.85 }).addTo(_navMap);
@ -2106,12 +2084,12 @@ window.Page_routes = (() => {
document.body.appendChild(ovl);
// Map initialisieren
await UI.loadLeaflet?.() ?? Promise.resolve();
const mapEl = document.getElementById('rk-trim-map');
const center = fullTrack[Math.floor(fullTrack.length/2)];
const trimMap = L.map(mapEl, { zoomControl: false, attributionControl: false })
.setView([center.lat, center.lon], 14);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(trimMap);
const trimMap = await UI.map.create(mapEl, {
center: [center.lat, center.lon], zoom: 14,
zoomControl: false, attributionControl: false,
});
// Marker & Polylines
let greyBefore = L.polyline([], { color: '#9ca3af', weight: 3, opacity: 0.5 }).addTo(trimMap);
@ -2368,9 +2346,9 @@ window.Page_routes = (() => {
route.gps_track = [...route.gps_track].reverse();
// Karte neu aufbauen mit umgekehrtem Track
const el = document.getElementById('rk-detail-map');
if (el && window.L) {
if (el) {
if (_detailMap) { _detailMap.remove(); _detailMap = null; }
_detailMap = _buildDetailMap(el, route.gps_track);
_detailMap = await _buildDetailMap(el, route.gps_track);
}
UI.toast.success('Route dauerhaft umgekehrt');
} catch (err) { UI.toast.error(err.message); }
@ -2424,10 +2402,10 @@ window.Page_routes = (() => {
// Mini-Map
let _detailMap = null;
setTimeout(() => {
setTimeout(async () => {
const el = document.getElementById('rk-detail-map');
if (!el || !track.length) return;
if (window.L) _detailMap = _buildDetailMap(el, track);
_detailMap = await _buildDetailMap(el, track);
}, 80);
// Nearby POIs laden
@ -2546,10 +2524,12 @@ window.Page_routes = (() => {
}
}
function _buildDetailMap(el, track) {
const m = L.map(el, { zoomControl: false, attributionControl: false });
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 }).addTo(m);
async function _buildDetailMap(el, track) {
const lls = track.map(p => [p.lat, p.lon]);
const m = await UI.map.create(el, {
center: lls[0], zoom: 14,
zoomControl: false, attributionControl: false,
});
const poly = L.polyline(lls, { color: '#C4843A', weight: 4, opacity: 0.85 }).addTo(m);
_addRouteArrows(m, track, '#3b82f6');
L.circleMarker(lls[0], { radius:7, color:'#22C55E', fillColor:'#22C55E', fillOpacity:1 }).addTo(m);