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
|
|
@ -453,6 +453,10 @@ const UI = (() => {
|
|||
const isDark = document.documentElement.dataset.theme === 'dark';
|
||||
if (isDark) tiles.getContainer().style.filter = 'brightness(0.7) invert(1) contrast(0.9) hue-rotate(200deg)';
|
||||
}
|
||||
// Safety-Net: Container-Größe nach Layout neu vermessen. Verhindert
|
||||
// grau bleibende Bereiche wenn die Karte vor dem finalen Layout erstellt
|
||||
// wird (z.B. in frisch eingefügten Overlays mit flex:1).
|
||||
requestAnimationFrame(() => m.invalidateSize());
|
||||
return m;
|
||||
},
|
||||
|
||||
|
|
@ -873,12 +877,35 @@ const UI = (() => {
|
|||
coordsClear: `${p}-coords-clear`,
|
||||
suggestions: `${p}-suggestions`,
|
||||
pinHere: `${p}-pin-here`,
|
||||
geoInput: `${p}-geo-input`,
|
||||
geoClear: `${p}-geo-clear`,
|
||||
geoResults: `${p}-geo-results`,
|
||||
};
|
||||
|
||||
// HTML in den Container rendern
|
||||
function _render(container) {
|
||||
container.innerHTML = `
|
||||
<div style="position:relative">
|
||||
<!-- Geocoding-Suchfeld als Overlay oben — left:46px lässt Zoom-Control frei -->
|
||||
<div style="position:absolute;top:8px;left:46px;right:8px;z-index:1001">
|
||||
<div style="display:flex;align-items:center;gap:7px;background:rgba(255,255,255,0.96);
|
||||
border-radius:var(--radius-full);padding:6px 11px;
|
||||
box-shadow:0 2px 8px rgba(0,0,0,0.22);backdrop-filter:blur(4px)">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px;flex-shrink:0;color:#aaa"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
|
||||
<input type="search" id="${ids.geoInput}" placeholder="Ort oder Adresse suchen…"
|
||||
autocomplete="off" autocorrect="off" spellcheck="false"
|
||||
style="flex:1;border:none;outline:none;font-size:13px;background:transparent;
|
||||
font-family:inherit;color:var(--c-text);min-width:0">
|
||||
<button type="button" id="${ids.geoClear}" aria-label="Suche löschen"
|
||||
style="display:none;background:none;border:none;padding:2px;cursor:pointer;
|
||||
color:#bbb;line-height:1">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:13px;height:13px"><use href="/icons/phosphor.svg#x"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="${ids.geoResults}" style="display:none;background:rgba(255,255,255,0.98);
|
||||
border-radius:10px;box-shadow:0 4px 14px rgba(0,0,0,0.18);
|
||||
margin-top:5px;overflow:hidden;max-height:190px;overflow-y:auto"></div>
|
||||
</div>
|
||||
<div id="${ids.mapWrap}" style="border-radius:var(--radius-md);overflow:hidden;height:200px;background:var(--c-surface-2)"></div>
|
||||
<button type="button" id="${ids.pinHere}" style="
|
||||
position:absolute;bottom:10px;left:50%;transform:translateX(-50%);
|
||||
|
|
@ -1102,6 +1129,75 @@ const UI = (() => {
|
|||
}
|
||||
|
||||
_getEl(ids.locBtn)?.addEventListener('click', _showSuggestions);
|
||||
|
||||
// Geocoding-Suche
|
||||
let _geoTimer = null;
|
||||
const geoInput = _getEl(ids.geoInput);
|
||||
const geoClear = _getEl(ids.geoClear);
|
||||
const geoResults = _getEl(ids.geoResults);
|
||||
|
||||
geoInput?.addEventListener('input', () => {
|
||||
const q = geoInput.value.trim();
|
||||
if (geoClear) geoClear.style.display = q ? '' : 'none';
|
||||
clearTimeout(_geoTimer);
|
||||
if (q.length < 2) { if (geoResults) geoResults.style.display = 'none'; return; }
|
||||
_geoTimer = setTimeout(async () => {
|
||||
if (geoResults) {
|
||||
geoResults.innerHTML = '<div style="padding:9px 13px;font-size:12px;color:var(--c-text-secondary)">Suche…</div>';
|
||||
geoResults.style.display = '';
|
||||
}
|
||||
try {
|
||||
const data = await API.get(`/osm/geocode?q=${encodeURIComponent(q)}`);
|
||||
if (!geoResults) return;
|
||||
if (!data.length) {
|
||||
geoResults.innerHTML = '<div style="padding:9px 13px;font-size:12px;color:var(--c-text-secondary)">Keine Ergebnisse</div>';
|
||||
return;
|
||||
}
|
||||
geoResults.innerHTML = data.map((r, i) => `
|
||||
<div data-i="${i}" style="padding:9px 13px;cursor:pointer;border-bottom:1px solid rgba(0,0,0,0.05)">
|
||||
<div style="font-size:13px;font-weight:600;color:var(--c-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escape(r.name)}</div>
|
||||
${r.subtitle ? `<div style="font-size:11px;color:var(--c-text-secondary)">${escape(r.subtitle)}</div>` : ''}
|
||||
</div>`).join('');
|
||||
geoResults.querySelectorAll('[data-i]').forEach(el => {
|
||||
el.addEventListener('pointerdown', e => {
|
||||
e.preventDefault();
|
||||
const r = data[+el.dataset.i];
|
||||
_setCoords(r.lat, r.lon);
|
||||
_setName(r.name);
|
||||
if (_map) {
|
||||
_map.flyTo([r.lat, r.lon], 15, { duration: 0.8 });
|
||||
_placeMarker(r.lat, r.lon);
|
||||
}
|
||||
const lbl = _getEl(ids.locBtnLabel);
|
||||
if (lbl) lbl.textContent = 'POI suchen';
|
||||
geoInput.value = '';
|
||||
if (geoClear) geoClear.style.display = 'none';
|
||||
geoResults.style.display = 'none';
|
||||
onSelect?.(_lat, _lon, _name);
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
if (geoResults) geoResults.innerHTML = '<div style="padding:9px 13px;font-size:12px;color:var(--c-text-secondary)">Suche nicht verfügbar</div>';
|
||||
}
|
||||
}, 400);
|
||||
});
|
||||
|
||||
geoInput?.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') {
|
||||
geoInput.value = '';
|
||||
if (geoClear) geoClear.style.display = 'none';
|
||||
if (geoResults) geoResults.style.display = 'none';
|
||||
}
|
||||
});
|
||||
geoClear?.addEventListener('click', () => {
|
||||
geoInput.value = '';
|
||||
geoClear.style.display = 'none';
|
||||
if (geoResults) geoResults.style.display = 'none';
|
||||
});
|
||||
_getEl(ids.mapWrap)?.addEventListener('pointerdown', () => {
|
||||
if (geoResults) geoResults.style.display = 'none';
|
||||
geoInput?.blur();
|
||||
});
|
||||
}
|
||||
|
||||
// Container initialisieren
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue