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
|
|
@ -15,7 +15,6 @@ window.Page_poison = (() => {
|
|||
let _userMarker = null;
|
||||
let _reports = [];
|
||||
let _userPos = null;
|
||||
let _leafletLoaded = false;
|
||||
|
||||
const TYPEN = {
|
||||
unbekannt: { label: 'Unbekannt', icon: '❓', color: '#e67e22' },
|
||||
|
|
@ -97,43 +96,13 @@ window.Page_poison = (() => {
|
|||
document.getElementById('poison-btn-report')
|
||||
?.addEventListener('click', _showReportForm);
|
||||
|
||||
await _loadLeaflet();
|
||||
await UI.loadLeaflet();
|
||||
_initMap();
|
||||
// Leaflet muss nach CSS-Load die Container-Größe neu berechnen
|
||||
setTimeout(() => _map?.invalidateSize(), 100);
|
||||
await _locateAndLoad();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// LEAFLET DYNAMISCH LADEN
|
||||
// ----------------------------------------------------------
|
||||
async function _loadLeaflet() {
|
||||
if (_leafletLoaded || window.L) { _leafletLoaded = true; return; }
|
||||
|
||||
// CSS lokal (kein CDN)
|
||||
await new Promise(resolve => {
|
||||
if (document.querySelector('link[href*="leaflet"]')) { resolve(); return; }
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = '/css/leaflet.css';
|
||||
link.onload = resolve;
|
||||
link.onerror = resolve;
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
|
||||
// JS lokal (kein CDN)
|
||||
await new Promise((resolve, reject) => {
|
||||
if (document.querySelector('script[src*="leaflet"]')) { resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = '/js/leaflet.js';
|
||||
s.onload = resolve;
|
||||
s.onerror = reject;
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
|
||||
_leafletLoaded = true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// KARTE INITIALISIEREN
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -229,27 +198,16 @@ window.Page_poison = (() => {
|
|||
|
||||
_reports.forEach(r => {
|
||||
const typ = TYPEN[r.typ] || TYPEN.unbekannt;
|
||||
const icon = L.divIcon({
|
||||
className : '',
|
||||
html : `<div style="
|
||||
background:${typ.color};color:#fff;border-radius:50%;
|
||||
width:34px;height:34px;
|
||||
display:flex;align-items:center;justify-content:center;
|
||||
font-size:17px;box-shadow:0 2px 6px rgba(0,0,0,.35);
|
||||
border:2px solid #fff">${typ.icon}</div>`,
|
||||
iconSize : [34, 34],
|
||||
iconAnchor : [17, 17],
|
||||
});
|
||||
|
||||
const distStr = r.distanz_m < 1000
|
||||
? `${r.distanz_m} m`
|
||||
: `${(r.distanz_m / 1000).toFixed(1)} km`;
|
||||
|
||||
const marker = L.marker([r.lat, r.lon], { icon })
|
||||
const marker = UI.leafletMarker({ lat: r.lat, lon: r.lon, color: typ.color, icon: typ.icon, size: 34 })
|
||||
.addTo(_map)
|
||||
.bindPopup(`
|
||||
<b>${typ.icon} ${typ.label}</b><br>
|
||||
${r.beschreibung ? _escape(r.beschreibung.slice(0, 80)) + '<br>' : ''}
|
||||
${r.beschreibung ? UI.escape(r.beschreibung.slice(0, 80)) + '<br>' : ''}
|
||||
<small>📍 ${distStr} entfernt</small><br>
|
||||
<small>📅 ${_fmtDate(r.created_at)}</small>
|
||||
${r.bestaetigt ? '<br><small>✅ Bestätigt</small>' : ''}
|
||||
|
|
@ -316,7 +274,7 @@ window.Page_poison = (() => {
|
|||
${r.beschreibung
|
||||
? `<p style="margin:0 0 var(--space-1);font-size:var(--text-sm);
|
||||
color:var(--c-text)">
|
||||
${_escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
|
||||
${UI.escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
|
||||
</p>`
|
||||
: ''}
|
||||
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||||
|
|
@ -357,7 +315,7 @@ window.Page_poison = (() => {
|
|||
</div>
|
||||
|
||||
${r.beschreibung
|
||||
? `<p style="white-space:pre-wrap;margin-bottom:var(--space-3)">${_escape(r.beschreibung)}</p>`
|
||||
? `<p style="white-space:pre-wrap;margin-bottom:var(--space-3)">${UI.escape(r.beschreibung)}</p>`
|
||||
: ''}
|
||||
|
||||
<div style="font-size:var(--text-sm);color:var(--c-text-secondary);
|
||||
|
|
@ -365,7 +323,7 @@ window.Page_poison = (() => {
|
|||
<div>📍 ${r.lat.toFixed(5)}, ${r.lon.toFixed(5)}</div>
|
||||
<div>📅 Gemeldet: ${_fmtDate(r.created_at)}</div>
|
||||
<div>⏰ Läuft ab: ${_fmtDate(r.expires_at)}</div>
|
||||
${r.melder_name ? `<div>👤 Gemeldet von: ${_escape(r.melder_name)}</div>` : ''}
|
||||
${r.melder_name ? `<div>👤 Gemeldet von: ${UI.escape(r.melder_name)}</div>` : ''}
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
|
||||
|
|
@ -482,21 +440,7 @@ window.Page_poison = (() => {
|
|||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Standort</label>
|
||||
<div style="display:flex;gap:var(--space-2);align-items:center">
|
||||
<input class="form-control" type="text" id="pf-lat-disp"
|
||||
placeholder="Breite" readonly style="flex:1">
|
||||
<input class="form-control" type="text" id="pf-lon-disp"
|
||||
placeholder="Länge" readonly style="flex:1">
|
||||
<button type="button" class="btn btn-secondary" id="pf-gps-btn"
|
||||
title="GPS-Standort ermitteln">📍</button>
|
||||
</div>
|
||||
<input type="hidden" name="lat" id="pf-lat">
|
||||
<input type="hidden" name="lon" id="pf-lon">
|
||||
<small id="pf-gps-hint" style="color:var(--c-text-secondary)">
|
||||
${_userPos
|
||||
? '✅ Aktueller Standort vorausgefüllt'
|
||||
: 'GPS-Button drücken um Standort zu ermitteln'}
|
||||
</small>
|
||||
<div id="poison-location-picker"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -532,32 +476,12 @@ window.Page_poison = (() => {
|
|||
|
||||
UI.modal.open({ title: '⚠️ Giftköder melden', body, footer });
|
||||
|
||||
// Standort vorausfüllen wenn bekannt
|
||||
// Location-Picker initialisieren + ggf. bekannten Standort vorausfüllen
|
||||
const _picker = UI.locationPicker({ containerId: 'poison-location-picker' });
|
||||
if (_userPos) {
|
||||
document.getElementById('pf-lat').value = _userPos.lat;
|
||||
document.getElementById('pf-lon').value = _userPos.lon;
|
||||
document.getElementById('pf-lat-disp').value = _userPos.lat.toFixed(6);
|
||||
document.getElementById('pf-lon-disp').value = _userPos.lon.toFixed(6);
|
||||
_picker.setValue(_userPos.lat, _userPos.lon, null);
|
||||
}
|
||||
|
||||
// GPS-Button
|
||||
document.getElementById('pf-gps-btn')?.addEventListener('click', async () => {
|
||||
const btn = document.getElementById('pf-gps-btn');
|
||||
UI.setLoading(btn, true);
|
||||
try {
|
||||
const pos = await API.getLocation({ timeout: 10000, enableHighAccuracy: true });
|
||||
document.getElementById('pf-lat').value = pos.lat;
|
||||
document.getElementById('pf-lon').value = pos.lon;
|
||||
document.getElementById('pf-lat-disp').value = pos.lat.toFixed(6);
|
||||
document.getElementById('pf-lon-disp').value = pos.lon.toFixed(6);
|
||||
document.getElementById('pf-gps-hint').textContent = '✅ Standort aktualisiert';
|
||||
_userPos = pos;
|
||||
} catch {
|
||||
UI.toast.error('GPS-Standort konnte nicht ermittelt werden.');
|
||||
}
|
||||
UI.setLoading(btn, false);
|
||||
});
|
||||
|
||||
// Foto-Vorschau
|
||||
const photoInput = document.querySelector('#poison-form [name="photo"]');
|
||||
const photoPreview = document.getElementById('pf-photo-preview');
|
||||
|
|
@ -576,16 +500,17 @@ window.Page_poison = (() => {
|
|||
e.preventDefault();
|
||||
const submitBtn = document.querySelector('[form="poison-form"][type="submit"]') || e.target.querySelector('[type="submit"]');
|
||||
const fd = UI.formData(e.target);
|
||||
const loc = _picker.getValue();
|
||||
|
||||
if (!fd.lat || !fd.lon) {
|
||||
if (!loc.lat || !loc.lon) {
|
||||
UI.toast.warning('Bitte zuerst den GPS-Standort ermitteln (📍).');
|
||||
return;
|
||||
}
|
||||
|
||||
await UI.asyncButton(submitBtn, async () => {
|
||||
const payload = {
|
||||
lat : parseFloat(fd.lat),
|
||||
lon : parseFloat(fd.lon),
|
||||
lat : loc.lat,
|
||||
lon : loc.lon,
|
||||
typ : fd.typ,
|
||||
beschreibung : fd.beschreibung || null,
|
||||
};
|
||||
|
|
@ -605,6 +530,8 @@ window.Page_poison = (() => {
|
|||
}
|
||||
|
||||
// Distanz client-seitig berechnen (für sofortige Anzeige)
|
||||
// _userPos aktualisieren falls Picker neuen Standort geliefert hat
|
||||
if (loc.lat && loc.lon) _userPos = { lat: loc.lat, lon: loc.lon };
|
||||
created.distanz_m = _userPos
|
||||
? Math.round(_haversine(_userPos.lat, _userPos.lon, created.lat, created.lon))
|
||||
: 0;
|
||||
|
|
@ -644,6 +571,8 @@ window.Page_poison = (() => {
|
|||
return 2 * R * Math.asin(Math.sqrt(a));
|
||||
}
|
||||
|
||||
// _fmtDate: bewusst lokal behalten — UI.time.format() liefert langen Monats-Namen
|
||||
// und behandelt kein SQLite-Leerzeichen-Format ("2026-04-12 00:00:00")
|
||||
function _fmtDate(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
// SQLite speichert "2026-04-12T00:00:00" oder "2026-04-12 00:00:00"
|
||||
|
|
@ -653,15 +582,6 @@ window.Page_poison = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function _escape(str) {
|
||||
if (!str) return '';
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// PUBLIC
|
||||
// ----------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue