Refactor: UI.loadLeaflet, leafletMarker, escape, emptyState, locationPicker zentralisiert
- Task 1: UI.loadLeaflet() in ui.js (mit Cluster-Option), lokale _loadLeaflet() in diary/walks/routes/places/poison/events.js entfernt - Task 2: UI.escape() ersetzt lokale _esc()/_escape() in allen 5 Seiten-Modulen - Task 3: UI.emptyState() ersetzt lokale _emptyState() in diary/routes/events.js - Task 4: _fmtDate/_fmtDateShort in walks/poison bewusst behalten (anderes Format), Kommentare ergänzt - Task 5: UI.locationPicker() eingebaut in places/poison/events (ersetzt manuelle GPS-Input-Blöcke) - Task 6: UI.leafletMarker() factory in ui.js, Kreis-divIcon-Blöcke in walks/places/ poison ersetzt; events.js behält Diamant-Marker (andere Form) - SW by-v207, APP_VER 175
This commit is contained in:
parent
066b722c5e
commit
e98ce0d232
9 changed files with 761 additions and 471 deletions
|
|
@ -27,8 +27,6 @@ window.Page_routes = (() => {
|
|||
|
||||
// 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: '🐾🐾🐾🐾' };
|
||||
|
|
@ -41,26 +39,13 @@ window.Page_routes = (() => {
|
|||
{ type: 'bank', icon: '🪑', label: 'Bank' },
|
||||
];
|
||||
|
||||
function _esc(s) {
|
||||
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
|
||||
function _emptyState(icon, title, text, cta = '') {
|
||||
return `<div class="empty-state">
|
||||
<svg class="ph-icon empty-state-icon" aria-hidden="true">
|
||||
<use href="/icons/phosphor.svg#${icon}"></use>
|
||||
</svg>
|
||||
<div class="empty-state-title">${title}</div>
|
||||
${text ? `<p class="empty-state-text">${text}</p>` : ''}
|
||||
${cta ? `<div class="empty-state-cta">${cta}</div>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
// _esc und _emptyState ersetzt durch UI.escape() / UI.emptyState()
|
||||
|
||||
async function init(container, appState) {
|
||||
_container = container;
|
||||
_appState = appState;
|
||||
_render();
|
||||
_loadLeaflet(); // fire & forget — bereit wenn Cards gerendert werden
|
||||
UI.loadLeaflet(); // fire & forget — bereit wenn Cards gerendert werden
|
||||
try { _userPos = await API.getLocation(); } catch {}
|
||||
await _loadData();
|
||||
|
||||
|
|
@ -72,21 +57,6 @@ window.Page_routes = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
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() {}
|
||||
|
||||
|
|
@ -362,7 +332,7 @@ window.Page_routes = (() => {
|
|||
}).addTo(_searchMap);
|
||||
|
||||
// Tooltip mit Namen und Distanz
|
||||
const tip = `<b>${_esc(route.name)}</b>${route.distanz_km ? ` · ${route.distanz_km.toFixed(1)} km` : ''}`;
|
||||
const tip = `<b>${UI.escape(route.name)}</b>${route.distanz_km ? ` · ${route.distanz_km.toFixed(1)} km` : ''}`;
|
||||
line.bindTooltip(tip, { sticky: true, className: 'rk-map-tooltip' });
|
||||
|
||||
// Hover-Highlight
|
||||
|
|
@ -409,7 +379,7 @@ window.Page_routes = (() => {
|
|||
_applyFilter();
|
||||
} catch (err) {
|
||||
document.getElementById('rk-grid').innerHTML =
|
||||
`<p style="color:var(--c-danger);padding:var(--space-6)">Fehler: ${_esc(err.message)}</p>`;
|
||||
`<p style="color:var(--c-danger);padding:var(--space-6)">Fehler: ${UI.escape(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -478,12 +448,12 @@ window.Page_routes = (() => {
|
|||
</div>`;
|
||||
} else {
|
||||
// Noch gar keine eigenen Routen
|
||||
grid.innerHTML = _emptyState(
|
||||
'map-trifold',
|
||||
'Noch keine Routen',
|
||||
'Zeichne Lieblingsrouten auf oder importiere GPX-Dateien. Teile Routen mit Freunden.',
|
||||
`<button class="btn btn-primary" id="rk-empty-rec">${UI.icon('path')} Route aufzeichnen</button>`
|
||||
);
|
||||
grid.innerHTML = UI.emptyState({
|
||||
icon: UI.icon('map-trifold'),
|
||||
title: 'Noch keine Routen',
|
||||
text: 'Zeichne Lieblingsrouten auf oder importiere GPX-Dateien. Teile Routen mit Freunden.',
|
||||
action: `<button class="btn btn-primary" id="rk-empty-rec">${UI.icon('path')} Route aufzeichnen</button>`,
|
||||
});
|
||||
document.getElementById('rk-empty-rec')?.addEventListener('click', () => {
|
||||
App.navigate('map');
|
||||
setTimeout(() => window.Page_map?.startRecording?.(), 600);
|
||||
|
|
@ -530,13 +500,13 @@ window.Page_routes = (() => {
|
|||
const dur = r.dauer_min ? _fmtDur(r.dauer_min) : '';
|
||||
const firstPhoto = (r.foto_urls || [])[0];
|
||||
const previewContent = firstPhoto
|
||||
? `<img src="${_esc(firstPhoto)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
? `<img src="${UI.escape(firstPhoto)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
: `<div class="rk-mini-map" data-id="${r.id}"
|
||||
data-track='${JSON.stringify(r.preview_track||[])}'
|
||||
style="width:100%;height:100%"></div>`;
|
||||
|
||||
const authorLine = isDiscover
|
||||
? `<div class="rk-card-creator">${UI.icon('user')} ${_esc(r.user_name||'Anonym')}</div>`
|
||||
? `<div class="rk-card-creator">${UI.icon('user')} ${UI.escape(r.user_name||'Anonym')}</div>`
|
||||
: '';
|
||||
|
||||
return `
|
||||
|
|
@ -544,7 +514,7 @@ window.Page_routes = (() => {
|
|||
<div class="rk-card-preview">${previewContent}</div>
|
||||
<div class="rk-card-body">
|
||||
${authorLine}
|
||||
<div class="rk-card-name">${_esc(r.name)}</div>
|
||||
<div class="rk-card-name">${UI.escape(r.name)}</div>
|
||||
<div class="rk-card-stats">
|
||||
${dist ? `<span>${UI.icon('map-trifold')} ${dist}</span>` : ''}
|
||||
${dur ? `<span>${UI.icon('timer')} ${dur}</span>` : ''}
|
||||
|
|
@ -560,7 +530,7 @@ window.Page_routes = (() => {
|
|||
<div class="rk-card-footer">
|
||||
<div class="rk-stars">${_starsHTML(r.id, r.bewertung||0, r.anz_bewertungen||0)}</div>
|
||||
<div class="rk-card-actions">
|
||||
${isDiscover ? '' : `<span class="rk-card-author">${_esc(r.user_name||'Anonym')}</span>`}
|
||||
${isDiscover ? '' : `<span class="rk-card-author">${UI.escape(r.user_name||'Anonym')}</span>`}
|
||||
<button class="rk-dl-btn" data-id="${r.id}">${UI.icon('download-simple')} GPX</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -661,7 +631,7 @@ window.Page_routes = (() => {
|
|||
|
||||
const photoGallery = photos.length ? `
|
||||
<div class="rk-photo-gallery">
|
||||
${photos.map(u => `<img src="${_esc(u)}" class="rk-photo-thumb" onclick="window.open('${_esc(u)}','_blank')">`).join('')}
|
||||
${photos.map(u => `<img src="${UI.escape(u)}" class="rk-photo-thumb" onclick="window.open('${UI.escape(u)}','_blank')">`).join('')}
|
||||
${isOwn ? `<label class="rk-photo-add" title="Foto hinzufügen">
|
||||
<span>+</span>
|
||||
<input type="file" id="rk-photo-input" accept="image/*" style="display:none">
|
||||
|
|
@ -686,13 +656,13 @@ window.Page_routes = (() => {
|
|||
${route.leine_empfohlen ? `<span class="rk-badge">${UI.icon('link')} Leine empfohlen</span>` : ''}
|
||||
${!route.is_public ? `<span class="rk-badge rk-badge--private">${UI.icon('lock')} Privat</span>` : ''}
|
||||
</div>
|
||||
${route.beschreibung ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)">${_esc(route.beschreibung)}</p>` : ''}
|
||||
${route.beschreibung ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)">${UI.escape(route.beschreibung)}</p>` : ''}
|
||||
<div id="rk-nearby" class="rk-nearby-section">
|
||||
<div style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt Orte entlang der Route…</div>
|
||||
</div>
|
||||
<div id="rk-rating-${route.id}"></div>
|
||||
<p style="color:var(--c-text-muted);font-size:0.75rem;margin-top:var(--space-2)">
|
||||
${track.length} GPS-Punkte · von ${_esc(route.user_name||'Anonym')}
|
||||
${route.bewertung ? ` · ${UI.icon('star')} ${route.bewertung.toFixed(1)} (${route.anz_bewertungen})` : ''}
|
||||
${track.length} GPS-Punkte · von ${UI.escape(route.user_name||'Anonym')}
|
||||
</p>
|
||||
`;
|
||||
|
||||
|
|
@ -707,7 +677,14 @@ window.Page_routes = (() => {
|
|||
<button type="button" class="btn btn-primary flex-1" id="rd-close">Schließen</button>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: `🥾 ${_esc(route.name)}`, body, footer });
|
||||
UI.modal.open({ title: `🥾 ${UI.escape(route.name)}`, body, footer });
|
||||
|
||||
UI.ratingStars({
|
||||
containerId: `rk-rating-${route.id}`,
|
||||
targetType: 'route',
|
||||
targetId: route.id,
|
||||
isLoggedIn: !!_appState.user,
|
||||
});
|
||||
|
||||
document.getElementById('rd-close')?.addEventListener('click', UI.modal.close);
|
||||
document.getElementById('rd-gpx')?.addEventListener('click', () => _downloadGpxDirect(route));
|
||||
|
|
@ -855,12 +832,12 @@ window.Page_routes = (() => {
|
|||
<div class="rk-nearby-title">${UI.icon('map-pin')} Entlang der Route</div>
|
||||
${Object.values(byType).map(group => `
|
||||
<div class="rk-nearby-group">
|
||||
<div class="rk-nearby-group-label">${group.icon} ${_esc(group.label)} (${group.items.length})</div>
|
||||
<div class="rk-nearby-group-label">${group.icon} ${UI.escape(group.label)} (${group.items.length})</div>
|
||||
${group.items.slice(0, 5).map(p => `
|
||||
<div class="rk-nearby-item">
|
||||
<span class="rk-nearby-name">${_esc(p.name || group.label)}</span>
|
||||
${p.opening_hours ? `<span class="rk-nearby-detail">${UI.icon('clock')} ${_esc(p.opening_hours)}</span>` : ''}
|
||||
${p.phone ? `<a href="tel:${_esc(p.phone)}" class="rk-nearby-detail rk-nearby-phone">${UI.icon('phone')} ${_esc(p.phone)}</a>` : ''}
|
||||
<span class="rk-nearby-name">${UI.escape(p.name || group.label)}</span>
|
||||
${p.opening_hours ? `<span class="rk-nearby-detail">${UI.icon('clock')} ${UI.escape(p.opening_hours)}</span>` : ''}
|
||||
${p.phone ? `<a href="tel:${UI.escape(p.phone)}" class="rk-nearby-detail rk-nearby-phone">${UI.icon('phone')} ${UI.escape(p.phone)}</a>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
${group.items.length > 5 ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 0">+${group.items.length-5} weitere</div>` : ''}
|
||||
|
|
@ -898,7 +875,7 @@ window.Page_routes = (() => {
|
|||
const pts = track.map(p => ` <trkpt lat="${p.lat}" lon="${p.lon}"></trkpt>`).join('\n');
|
||||
const gpx = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="Ban Yaro" xmlns="http://www.topografix.com/GPX/1/1">
|
||||
<trk><name>${_esc(route.name)}</name><trkseg>\n${pts}\n </trkseg></trk>
|
||||
<trk><name>${UI.escape(route.name)}</name><trkseg>\n${pts}\n </trkseg></trk>
|
||||
</gpx>`;
|
||||
const blob = new Blob([gpx], { type: 'application/gpx+xml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
|
@ -1093,7 +1070,7 @@ window.Page_routes = (() => {
|
|||
<form id="rk-import-form" style="display:flex;flex-direction:column;gap:var(--space-3);margin-top:var(--space-4)">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name *</label>
|
||||
<input class="form-input" id="ri-name" value="${_esc(name)}" required maxlength="120">
|
||||
<input class="form-input" id="ri-name" value="${UI.escape(name)}" required maxlength="120">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Beschreibung</label>
|
||||
|
|
@ -1225,15 +1202,15 @@ window.Page_routes = (() => {
|
|||
|
||||
const friendRows = friends.map(f => {
|
||||
const initial = (f.name || '?')[0].toUpperCase();
|
||||
return `<div class="rk-friend-row" data-id="${f.id}" data-name="${_esc(f.name || 'Anonym')}"
|
||||
return `<div class="rk-friend-row" data-id="${f.id}" data-name="${UI.escape(f.name || 'Anonym')}"
|
||||
style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);
|
||||
cursor:pointer;border-radius:var(--radius-md);transition:background .15s"
|
||||
onmouseover="this.style.background='var(--c-surface-2)'"
|
||||
onmouseout="this.style.background=''">
|
||||
<div style="width:36px;height:36px;border-radius:50%;background:var(--c-primary);
|
||||
color:#fff;display:flex;align-items:center;justify-content:center;
|
||||
font-weight:600;flex-shrink:0">${_esc(initial)}</div>
|
||||
<span>${_esc(f.name || 'Anonym')}</span>
|
||||
font-weight:600;flex-shrink:0">${UI.escape(initial)}</div>
|
||||
<span>${UI.escape(f.name || 'Anonym')}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue