// Map used by /beacons — renders every approved beacon as a circle // marker with a popup linking to the detail page. interface BeaconPoint { id: string callsign: string lat: number lon: number frequency_mhz: number grid: string keying: string on_the_air: boolean } interface BeaconsListMapHook extends ViewHook { mounted(this: BeaconsListMapHook): void } function formatFreq(freq: number): string { // Mirror Microwaveprop.Beacons.Beacon.format_freq/1 — integers get // comma separators, floats keep up to three decimals trimmed. if (Number.isInteger(freq)) return freq.toLocaleString("en-US") const trimmed = freq.toFixed(3).replace(/\.?0+$/, "") const [whole, frac] = trimmed.split(".") const wholeFormatted = parseInt(whole, 10).toLocaleString("en-US") return frac ? `${wholeFormatted}.${frac}` : wholeFormatted } function escapeHtml(str: string): string { return str.replace(/[&<>"']/g, ch => { switch (ch) { case "&": return "&" case "<": return "<" case ">": return ">" case "\"": return """ case "'": return "'" default: return ch } }) } export const BeaconsListMap = { mounted(this: BeaconsListMapHook) { const beacons: BeaconPoint[] = JSON.parse(this.el.dataset.beacons || "[]") // DFW fallback so the map still renders when no beacons exist yet. const defaultCenter: [number, number] = [32.897, -97.038] const map = L.map(this.el, { zoomControl: true, scrollWheelZoom: false, attributionControl: true }).setView(defaultCenter, 6) L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors", maxZoom: 19 }).addTo(map) const bounds: [number, number][] = [] for (const b of beacons) { if (!Number.isFinite(b.lat) || !Number.isFinite(b.lon)) continue const color = b.on_the_air ? "#16a34a" : "#94a3b8" const fill = b.on_the_air ? "#22c55e" : "#cbd5e1" const popupHtml = `
` + `${escapeHtml(b.callsign)}` + (b.on_the_air ? "" : ` · off-air`) + `
` + `${formatFreq(b.frequency_mhz)} MHz
` + `Grid ${escapeHtml(b.grid)}
` + `Keying: ${escapeHtml(b.keying)}
` + `View details →` + `
` L.circleMarker([b.lat, b.lon], { radius: 7, color, fillColor: fill, fillOpacity: 0.9, weight: 2 }) .bindPopup(popupHtml, { closeButton: true }) .bindTooltip(b.callsign, { direction: "top", offset: [0, -8] }) .addTo(map) bounds.push([b.lat, b.lon]) } if (bounds.length > 0) { map.fitBounds(L.latLngBounds(bounds), { padding: [30, 30], maxZoom: 10 }) } // Invalidate once after mount in case the parent was hidden. setTimeout(() => map.invalidateSize(), 50) } }