defmodule MicrowavepropWeb.ContactMapLive do @moduledoc "`/contacts/map` plots QSO endpoints + great-circle paths on a Leaflet map." use MicrowavepropWeb, :live_view alias Microwaveprop.Radio @band_colors %{ 1296 => "#475569", 2304 => "#7c3aed", 3400 => "#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 # Shell renders immediately with filter chrome (count + bands list). The # JS hook fetches the ~5 MB contacts payload from `/api/contacts/map` # after mount, which streams it as pre-gzipped bytes via the browser's # native `Content-Encoding: gzip` handling. This gets the heavy payload # off the initial HTML entirely. # # Band toggles are handled entirely client-side in the ContactsMap hook # (per-band Leaflet LayerGroups) so there's no enabled_bands state on # the server. Callsign filter still roundtrips (debounced) to push the # filter string to the hook. %{count: count, bands: bands} = Radio.contact_map_payload() {:ok, assign(socket, page_title: "Contact Map", contact_count: count, all_bands: bands, callsign_filter: "" )} end @impl true def handle_event("filter_callsign", %{"callsign" => callsign}, socket) do trimmed = String.trim(callsign) socket = socket |> assign(callsign_filter: trimmed) |> push_event("apply_filter", %{callsign: trimmed}) {:noreply, socket} 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") @impl true def render(assigns) do ~H"""
<%!-- Mobile floating controls --%>
Contact Map
{Integer.to_string(@contact_count)} contacts
<%!-- Desktop right sidebar --%>
""" end end