diff --git a/src/lib/components/MapView.svelte b/src/lib/components/MapView.svelte index 3f82823..c11cae0 100644 --- a/src/lib/components/MapView.svelte +++ b/src/lib/components/MapView.svelte @@ -50,7 +50,6 @@ let loading = $state(true); let error = $state(null); const layerGroups: Record = {}; - const featureLayers: Record void }> = {}; // Which layers are currently displayed on the map. let visible = $state>({}); 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(`${nm.replace(/
Saved point`); 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; - const name = (props.name as string | undefined) ?? String(f.id ?? ''); - - let extra = ''; - if (props.address) extra += `Address${String(props.address)}`; - if (props.bearing !== undefined) { - const b = Number(props.bearing); - extra += `Facing${compassPoint(b)} (${b}°)`; - } - if (props.fov !== undefined) { - extra += `Field of view${String(props.fov)}°`; - } - - const rows = Object.entries(props) - .filter(([k]) => !['name', 'address', 'bearing', 'fov', 'description'].includes(k) && !k.startsWith('_')) - .map(([k, v]) => `${k}${String(v)}`) - .join(''); - - let desc = ''; - if (props.description) desc = ``; - - return `${name}${desc}${extra}${rows}`; - } - onMount(async () => { let L; try { @@ -493,14 +457,12 @@ onEachFeature: (f, lay) => { totalFeatures++; const props = (f.properties ?? {}) as Record; - 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;