fix(ui): consolidate POI info into single context dialog (remove duplicate native Leaflet popup)
All checks were successful
CI / test-and-build (push) Successful in 43s
All checks were successful
CI / test-and-build (push) Successful in 43s
This commit is contained in:
parent
b0147940af
commit
a7955c0636
@ -50,7 +50,6 @@
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
const layerGroups: Record<string, L.LayerGroup> = {};
|
||||
const featureLayers: Record<string, L.Layer & { bindPopup: (s: string) => void }> = {};
|
||||
// Which layers are currently displayed on the map.
|
||||
let visible = $state<Record<string, boolean>>({});
|
||||
let totalFeatures = $state(0);
|
||||
@ -96,19 +95,17 @@
|
||||
const results = $derived(query.trim().length >= 2 ? search(query, searchables) : []);
|
||||
let showResults = $state(false);
|
||||
|
||||
function focusOn(item: Searchable, openPopup = true) {
|
||||
function focusOn(item: Searchable, openDialog = true) {
|
||||
if (!map) return;
|
||||
if (item.kind === 'zone' && item.bounds) {
|
||||
map.fitBounds(item.bounds as L.LatLngBoundsExpression, { padding: [60, 60] });
|
||||
} else {
|
||||
map.setView([item.lat, item.lon], Math.max(map.getZoom(), 15));
|
||||
}
|
||||
if (openPopup) {
|
||||
const fl = featureLayers[item.id];
|
||||
if (fl && typeof fl.bindPopup === 'function') {
|
||||
// openPopup is available on markers / path layers
|
||||
(fl as unknown as { openPopup: () => void }).openPopup?.();
|
||||
}
|
||||
// Selecting a search result opens the same context dialog used for a
|
||||
// direct map click, so there is a single info/actions bubble everywhere.
|
||||
if (openDialog) {
|
||||
openContextAt({ lat: item.lat, lng: item.lon } as L.LatLng, item.feature ? featureInfoFor(item.feature) : null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,8 +265,6 @@
|
||||
iconSize: [0, 0]
|
||||
})
|
||||
}).addTo(savedPointLayer);
|
||||
const nm = p.label || `${p.lat.toFixed(4)}, ${p.lon.toFixed(4)}`;
|
||||
mk.bindPopup(`<strong>${nm.replace(/</g, '<')}</strong><br><em class="popup-desc">Saved point</em>`);
|
||||
mk.on('click', (e: L.LeafletMouseEvent) => {
|
||||
L.DomEvent.stopPropagation(e.originalEvent);
|
||||
openContextAt(e.latlng, null);
|
||||
@ -392,37 +387,6 @@
|
||||
return { color, weight: 2, fillColor: color, fillOpacity: 0.25 };
|
||||
}
|
||||
|
||||
const COMPASS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
|
||||
function compassPoint(deg: number): string {
|
||||
const idx = Math.round((((deg % 360) + 360) % 360) / 22.5) % 16;
|
||||
return COMPASS[idx];
|
||||
}
|
||||
|
||||
function popupHtml(f: GeoJSON.Feature): string {
|
||||
const props = (f.properties ?? {}) as Record<string, unknown>;
|
||||
const name = (props.name as string | undefined) ?? String(f.id ?? '');
|
||||
|
||||
let extra = '';
|
||||
if (props.address) extra += `<tr><th>Address</th><td>${String(props.address)}</td></tr>`;
|
||||
if (props.bearing !== undefined) {
|
||||
const b = Number(props.bearing);
|
||||
extra += `<tr><th>Facing</th><td>${compassPoint(b)} (${b}°)</td></tr>`;
|
||||
}
|
||||
if (props.fov !== undefined) {
|
||||
extra += `<tr><th>Field of view</th><td>${String(props.fov)}°</td></tr>`;
|
||||
}
|
||||
|
||||
const rows = Object.entries(props)
|
||||
.filter(([k]) => !['name', 'address', 'bearing', 'fov', 'description'].includes(k) && !k.startsWith('_'))
|
||||
.map(([k, v]) => `<tr><th>${k}</th><td>${String(v)}</td></tr>`)
|
||||
.join('');
|
||||
|
||||
let desc = '';
|
||||
if (props.description) desc = `<div class="popup-desc">${String(props.description)}</div>`;
|
||||
|
||||
return `<strong>${name}</strong>${desc}<table class="popup">${extra}${rows}</table>`;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
let L;
|
||||
try {
|
||||
@ -493,14 +457,12 @@
|
||||
onEachFeature: (f, lay) => {
|
||||
totalFeatures++;
|
||||
const props = (f.properties ?? {}) as Record<string, unknown>;
|
||||
lay.bindPopup(popupHtml(f));
|
||||
// Clicking a feature opens the context dialog with its info and
|
||||
// stops propagation so the map's empty-click handler doesn't fire.
|
||||
lay.on('click', (e: L.LeafletMouseEvent) => {
|
||||
L.DomEvent.stopPropagation(e.originalEvent);
|
||||
openContextAt(e.latlng, featureInfoFor(f));
|
||||
});
|
||||
featureLayers[f.id as string] = lay;
|
||||
|
||||
// Build a searchable record. A single malformed feature
|
||||
// must not break the whole layer load.
|
||||
@ -783,10 +745,6 @@
|
||||
.swatch { display: inline-block; width: 12px; height: 12px; border-radius: 3px; border: 1px solid #cbd5e1; }
|
||||
.lbl { flex: 1; }
|
||||
.count { color: #94a3b8; font-size: 0.7rem; }
|
||||
:global(.popup) { border-collapse: collapse; margin-top: 4px; font-size: 0.75rem; }
|
||||
:global(.popup th) { text-align: left; padding-right: 10px; color: #64748b; font-weight: 600; }
|
||||
:global(.popup td) { padding: 1px 0; }
|
||||
:global(.popup-desc) { color: #475569; margin-top: 2px; font-size: 0.75rem; }
|
||||
:global(.dir-marker-wrap) { background: none; border: none; }
|
||||
:global(.dir-arrow) {
|
||||
width: 0; height: 0;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user