diff --git a/assets/js/contacts_map_hook.js b/assets/js/contacts_map_hook.js index c80288b3..71de8ce2 100644 --- a/assets/js/contacts_map_hook.js +++ b/assets/js/contacts_map_hook.js @@ -50,98 +50,19 @@ export const ContactsMap = { this.lineLayer.addTo(this.map) this.dotLayer.addTo(this.map) - // Discover all bands - const bandSet = new Set() - for (const c of this.allContacts) bandSet.add(c[4]) - this.allBands = [...bandSet].sort((a, b) => a - b) - for (const b of this.allBands) this.enabledBands.add(b) + // Enable all bands initially + for (const c of this.allContacts) this.enabledBands.add(c[4]) + + // Listen for filter changes from LiveView + this.handleEvent("apply_filter", ({enabled_bands, callsign}) => { + this.enabledBands = new Set(enabled_bands) + this.callsignFilter = (callsign || "").toUpperCase() + this.rebuildMap() + }) - this.buildControlPanel() this.rebuildMap() }, - buildControlPanel() { - const control = L.control({position: "bottomright"}) - const self = this - - control.onAdd = function() { - const div = L.DomUtil.create("div") - div.style.cssText = "background:rgba(30,30,40,0.95);color:#fff;padding:8px 12px;border-radius:8px;font-size:11px;line-height:1.8;max-height:60vh;overflow-y:auto;" - L.DomEvent.disableClickPropagation(div) - L.DomEvent.disableScrollPropagation(div) - - // Callsign filter - let html = `
- -
` - - // Band header with All/None - html += `
- Bands - - - - -
` - - // Band checkboxes - for (const band of self.allBands) { - const color = bandColor(band) - const label = bandLabel(band) - html += `` - } - - div.innerHTML = html - - // Wire callsign filter - const input = div.querySelector("#callsign-filter") - let debounceTimer - input.addEventListener("input", () => { - clearTimeout(debounceTimer) - debounceTimer = setTimeout(() => { - self.callsignFilter = input.value.trim().toUpperCase() - self.rebuildMap() - }, 300) - }) - - // Wire band checkboxes - div.querySelectorAll("input[data-band]").forEach(cb => { - cb.addEventListener("change", () => { - const band = parseInt(cb.dataset.band) - if (cb.checked) { - self.enabledBands.add(band) - } else { - self.enabledBands.delete(band) - } - self.rebuildMap() - }) - }) - - div.querySelector("#bands-all").addEventListener("click", () => { - div.querySelectorAll("input[data-band]").forEach(cb => { cb.checked = true }) - for (const b of self.allBands) self.enabledBands.add(b) - self.rebuildMap() - }) - - div.querySelector("#bands-none").addEventListener("click", () => { - div.querySelectorAll("input[data-band]").forEach(cb => { cb.checked = false }) - self.enabledBands.clear() - self.rebuildMap() - }) - - self.controlDiv = div - return div - } - - control.addTo(this.map) - }, - rebuildMap() { this.lineLayer.clearLayers() this.dotLayer.clearLayers() @@ -158,27 +79,11 @@ export const ContactsMap = { return true }) - // Count per band for display - const bandCounts = {} - for (const c of filtered) { - const band = c[4] - bandCounts[band] = (bandCounts[band] || 0) + 1 - } - - // Update band counts in control panel - if (this.controlDiv) { - this.controlDiv.querySelectorAll(".band-count").forEach(el => { - const band = parseInt(el.dataset.band) - const count = bandCounts[band] || 0 - el.textContent = `(${count.toLocaleString()})` - }) - } - - // Update header count + // Update count displays const countEl = document.getElementById("contact-count") - if (countEl) { - countEl.textContent = `${filtered.length.toLocaleString()} contacts` - } + if (countEl) countEl.textContent = `${filtered.length.toLocaleString()} contacts` + const countMobile = document.getElementById("contact-count-mobile") + if (countMobile) countMobile.textContent = `${filtered.length.toLocaleString()} contacts` // Draw lines const endpointCounts = {} diff --git a/lib/microwaveprop_web/live/contact_map_live.ex b/lib/microwaveprop_web/live/contact_map_live.ex index 2faf2feb..ebc28f02 100644 --- a/lib/microwaveprop_web/live/contact_map_live.ex +++ b/lib/microwaveprop_web/live/contact_map_live.ex @@ -7,18 +7,91 @@ defmodule MicrowavepropWeb.ContactMapLive do alias Microwaveprop.Radio.Contact alias Microwaveprop.Repo + @band_colors %{ + 1296 => "#475569", + 2304 => "#7c3aed", + 3456 => "#4f46e5", + 5760 => "#2563eb", + 10_000 => "#059669", + 24_000 => "#d97706", + 47_000 => "#ea580c", + 68_000 => "#dc2626", + 75_000 => "#c026d3", + 122_000 => "#db2777", + 134_000 => "#e11d48", + 241_000 => "#b91c1c" + } + @impl true def mount(_params, _session, socket) do contacts = load_contacts() + bands = + contacts + |> Enum.map(fn [_, _, _, _, band | _] -> band end) + |> Enum.uniq() + |> Enum.sort() + + enabled_bands = MapSet.new(bands) + {:ok, assign(socket, page_title: "Contact Map", contacts_json: Jason.encode!(contacts), - contact_count: length(contacts) + contact_count: length(contacts), + all_bands: bands, + enabled_bands: enabled_bands, + callsign_filter: "" )} end + @impl true + def handle_event("toggle_band", %{"band" => band_str}, socket) do + band = String.to_integer(band_str) + + enabled = + if MapSet.member?(socket.assigns.enabled_bands, band) do + MapSet.delete(socket.assigns.enabled_bands, band) + else + MapSet.put(socket.assigns.enabled_bands, band) + end + + socket = assign(socket, enabled_bands: enabled) + {:noreply, push_filter(socket)} + end + + def handle_event("bands_all", _params, socket) do + socket = assign(socket, enabled_bands: MapSet.new(socket.assigns.all_bands)) + {:noreply, push_filter(socket)} + end + + def handle_event("bands_none", _params, socket) do + socket = assign(socket, enabled_bands: MapSet.new()) + {:noreply, push_filter(socket)} + end + + def handle_event("filter_callsign", %{"callsign" => callsign}, socket) do + socket = assign(socket, callsign_filter: String.trim(callsign)) + {:noreply, push_filter(socket)} + end + + defp push_filter(socket) do + push_event(socket, "apply_filter", %{ + enabled_bands: MapSet.to_list(socket.assigns.enabled_bands), + callsign: socket.assigns.callsign_filter + }) + end + + defp band_label(mhz) when mhz >= 1000 do + ghz = mhz / 1000 + label = if ghz == trunc(ghz), do: "#{trunc(ghz)}", else: "#{Float.round(ghz / 1, 1)}" + "#{label} GHz" + end + + defp band_label(mhz), do: "#{mhz} MHz" + + defp band_color(mhz), do: Map.get(@band_colors, mhz, "#64748b") + defp load_contacts do from(c in Contact, where: not is_nil(c.pos1) and not is_nil(c.pos2), @@ -64,20 +137,208 @@ defmodule MicrowavepropWeb.ContactMapLive do @impl true def render(assigns) do ~H""" -
-
-
-
Contact Map
-
- {Integer.to_string(@contact_count)} contacts +
+
+
+
+ + <%!-- Mobile floating controls --%> +
+
+
+
+ Contact Map +
+ {Integer.to_string(@contact_count)} contacts +
+
+ +
+ + +
+
+
+ + <%!-- Desktop right sidebar --%> +
+ + """ end end