Karten-Ausbau (OSM), Forum-Erweiterung, UI-Komponenten, Refactor Tagebuch/Gassi (DRY), Landing/SEO — APP_VER 1155
This commit is contained in:
parent
2d907f6370
commit
10e39ed135
18 changed files with 871 additions and 405 deletions
|
|
@ -1306,37 +1306,7 @@ window.Page_diary = (() => {
|
|||
</div>
|
||||
<div class="form-group" id="diary-location-group">
|
||||
<label class="form-label">Ort <span class="text-secondary">(optional)</span></label>
|
||||
|
||||
<!-- Karte (Lesemodus, Edit per Button aktivierbar) -->
|
||||
<div style="position:relative">
|
||||
<div id="diary-map-wrap" style="border-radius:var(--radius-md);overflow:hidden;height:220px;background:var(--c-surface-2)"></div>
|
||||
<button type="button" id="diary-map-edit-btn" class="btn btn-secondary btn-sm"
|
||||
style="position:absolute;bottom:var(--space-2);right:var(--space-2);z-index:500">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#pencil-simple"></use></svg>
|
||||
<span id="diary-map-edit-label">Position ändern</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- POI-Name + Aktionen -->
|
||||
<div class="mt-2">
|
||||
<div id="diary-location-chip-wrap" style="${entry?.location_name ? '' : 'display:none'}">
|
||||
<div class="diary-location-chip">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
|
||||
<span id="diary-location-label">${UI.escape(entry?.location_name || '')}</span>
|
||||
<button type="button" id="diary-location-clear" aria-label="Name entfernen">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-2)">
|
||||
<button type="button" class="btn btn-danger" id="diary-coords-clear">Ort entfernen</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" id="diary-location-btn">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
|
||||
<span id="diary-location-btn-label">POI suchen</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="diary-location-suggestions" style="display:none;margin-top:var(--space-2)"></div>
|
||||
</div>
|
||||
<div id="diary-location-picker"></div>
|
||||
</div>
|
||||
${dogPickerHtml}
|
||||
<div class="form-group" style="margin-top:var(--space-5)">
|
||||
|
|
@ -1538,140 +1508,15 @@ window.Page_diary = (() => {
|
|||
let _locLat = (entry?.gps_lat != null) ? entry.gps_lat : null;
|
||||
let _locLon = (entry?.gps_lon != null) ? entry.gps_lon : null;
|
||||
let _locName = entry?.location_name || null;
|
||||
let _miniMap = null, _miniMarker = null;
|
||||
|
||||
const _pinSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="40" viewBox="0 0 32 40"><path d="M16 0C7.163 0 0 7.163 0 16c0 10 16 24 16 24S32 26 32 16C32 7.163 24.837 0 16 0z" fill="#C4843A"/><circle cx="16" cy="16" r="7" fill="white"/></svg>';
|
||||
const _mkIcon = () => L.divIcon({ html: _pinSvg, className: '', iconSize: [32,40], iconAnchor: [16,40] });
|
||||
|
||||
function _setName(name) {
|
||||
_locName = name;
|
||||
document.getElementById('diary-location-label').textContent = name;
|
||||
document.getElementById('diary-location-chip-wrap').style.display = '';
|
||||
document.getElementById('diary-location-suggestions').style.display = 'none';
|
||||
}
|
||||
|
||||
function _placeMarker(lat, lon) {
|
||||
if (_miniMarker) { _miniMarker.setLatLng([lat, lon]); return; }
|
||||
_miniMarker = L.marker([lat, lon], { draggable: false, icon: _mkIcon() }).addTo(_miniMap);
|
||||
_miniMarker.on('dragend', () => {
|
||||
const p = _miniMarker.getLatLng(); _locLat = p.lat; _locLon = p.lng;
|
||||
document.getElementById('diary-location-btn-label').textContent = 'POI suchen';
|
||||
// Location Picker (gemeinsame UI-Komponente)
|
||||
setTimeout(() => {
|
||||
const _diaryPicker = UI.locationPicker({
|
||||
containerId: 'diary-location-picker',
|
||||
onSelect: (lat, lon, name) => { _locLat = lat; _locLon = lon; _locName = name; },
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('diary-location-clear')?.addEventListener('click', () => {
|
||||
_locName = null;
|
||||
document.getElementById('diary-location-chip-wrap').style.display = 'none';
|
||||
});
|
||||
const _clearBtn = document.getElementById('diary-coords-clear');
|
||||
let _clearPending = false;
|
||||
_clearBtn?.addEventListener('click', () => {
|
||||
if (!_clearPending) {
|
||||
_clearPending = true;
|
||||
_clearBtn.textContent = 'Wirklich entfernen?';
|
||||
_clearBtn.style.color = 'var(--c-danger)';
|
||||
setTimeout(() => {
|
||||
if (_clearPending) {
|
||||
_clearPending = false;
|
||||
_clearBtn.textContent = 'Ort entfernen';
|
||||
_clearBtn.style.color = 'var(--c-text-muted)';
|
||||
}
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
_clearPending = false;
|
||||
_clearBtn.textContent = 'Ort entfernen';
|
||||
_clearBtn.style.color = 'var(--c-text-muted)';
|
||||
_locLat = null; _locLon = null; _locName = null;
|
||||
document.getElementById('diary-location-chip-wrap').style.display = 'none';
|
||||
document.getElementById('diary-location-suggestions').style.display = 'none';
|
||||
document.getElementById('diary-location-btn-label').textContent = 'POI suchen';
|
||||
if (_miniMarker) { _miniMarker.remove(); _miniMarker = null; }
|
||||
if (_miniMap) { _miniMap.setView([48.0, 11.9], 7); _setMapEditing(false); }
|
||||
});
|
||||
|
||||
let _mapEditing = false;
|
||||
|
||||
function _setMapEditing(on) {
|
||||
_mapEditing = on;
|
||||
const lbl = document.getElementById('diary-map-edit-label');
|
||||
if (lbl) lbl.textContent = on ? 'Fertig' : 'Position ändern';
|
||||
if (!_miniMap) return;
|
||||
if (on) {
|
||||
if (_miniMarker) _miniMarker.dragging.enable();
|
||||
} else {
|
||||
if (_miniMarker) _miniMarker.dragging.disable();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('diary-map-edit-btn')?.addEventListener('click', () => {
|
||||
_setMapEditing(!_mapEditing);
|
||||
});
|
||||
|
||||
// Karte beim Formular-Open automatisch laden
|
||||
UI.loadLeaflet().then(() => {
|
||||
setTimeout(() => {
|
||||
const lat = _locLat || 48.0, lon = _locLon || 11.9, zoom = _locLat ? 15 : 7;
|
||||
_miniMap = L.map('diary-map-wrap', {
|
||||
zoomControl: true, attributionControl: false,
|
||||
dragging: true, scrollWheelZoom: false,
|
||||
}).setView([lat, lon], zoom);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 })
|
||||
.addTo(_miniMap);
|
||||
_miniMap.invalidateSize();
|
||||
if (_locLat) {
|
||||
_placeMarker(lat, lon);
|
||||
_miniMarker.dragging.disable(); // Lesemodus: kein Drag
|
||||
}
|
||||
// Klick nur im Edit-Modus
|
||||
_miniMap.on('click', e => {
|
||||
if (!_mapEditing) return;
|
||||
_locLat = e.latlng.lat; _locLon = e.latlng.lng;
|
||||
_placeMarker(_locLat, _locLon);
|
||||
if (!_mapEditing) _miniMarker.dragging.disable();
|
||||
document.getElementById('diary-location-btn-label').textContent = 'POI suchen';
|
||||
});
|
||||
}, 150);
|
||||
});
|
||||
|
||||
async function _showSuggestions() {
|
||||
const btn = document.getElementById('diary-location-btn');
|
||||
UI.setLoading(btn, true);
|
||||
try {
|
||||
let lat = _locLat, lon = _locLon;
|
||||
if (lat == null || lon == null) {
|
||||
const pos = await API.getLocation();
|
||||
lat = pos.lat; lon = pos.lon;
|
||||
_locLat = lat; _locLon = lon;
|
||||
if (_miniMap) { _miniMap.setView([lat, lon], 15); _placeMarker(lat, lon); }
|
||||
document.getElementById('diary-location-btn-label').textContent = 'POI suchen';
|
||||
}
|
||||
const suggestions = await API.diary.nearby(_appState.activeDog.id, lat, lon);
|
||||
const sugEl = document.getElementById('diary-location-suggestions');
|
||||
if (suggestions.length === 0) {
|
||||
sugEl.innerHTML = '<p style="font-size:var(--text-sm);color:var(--c-text-secondary);padding:var(--space-2) 0">Keine Orte in der Nähe gefunden.</p>';
|
||||
} else {
|
||||
sugEl.innerHTML = suggestions.map(s => `
|
||||
<button type="button" class="diary-location-suggestion"
|
||||
data-name="${UI.escape(s.name)}" data-lat="${s.lat}" data-lon="${s.lon}">
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${_sourceIcon(s.source)}"></use></svg>
|
||||
<span>${UI.escape(s.name)}</span>
|
||||
<small>${s.distance_m < 1000 ? s.distance_m + ' m' : (s.distance_m / 1000).toFixed(1) + ' km'}</small>
|
||||
</button>`).join('');
|
||||
sugEl.querySelectorAll('.diary-location-suggestion').forEach(el => {
|
||||
el.addEventListener('click', () => _setName(el.dataset.name));
|
||||
});
|
||||
}
|
||||
sugEl.style.display = '';
|
||||
} catch (err) {
|
||||
UI.toast.error(err?.message?.includes('GPS') || lat == null
|
||||
? 'GPS nicht verfügbar.' : 'Ortssuche fehlgeschlagen.');
|
||||
} finally {
|
||||
UI.setLoading(btn, false);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('diary-location-btn')?.addEventListener('click', _showSuggestions);
|
||||
if (_locLat != null) _diaryPicker.setValue(_locLat, _locLon, _locName);
|
||||
}, 50);
|
||||
|
||||
document.getElementById('diary-form-delete')?.addEventListener('click', async () => {
|
||||
const ok = await UI.modal.confirm({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue