prop/lib/microwaveprop_web/live/map_live.ex
Graham McIntire 5a0113aac2
Add Leaflet map integration with propagation score display
Vendor Leaflet 1.9.4 (JS, CSS, marker images) and wire it into
the esbuild/Tailwind asset pipeline. Create MapLive with band
selector buttons, auto-refresh, and a colocated JS hook that
renders propagation scores as color-coded circle markers with a
legend. Stub Propagation context and BandConfig modules provide
the data interface for the scoring pipeline.

Add nav bar with links to Map, QSOs, and Submit pages.
2026-03-30 17:11:00 -05:00

96 lines
2.6 KiB
Elixir

defmodule MicrowavepropWeb.MapLive do
@moduledoc false
use MicrowavepropWeb, :live_view
alias Microwaveprop.Propagation
alias Microwaveprop.Propagation.BandConfig
@default_band 10_000
@refresh_interval_ms to_timeout(minute: 5)
@impl true
def mount(_params, _session, socket) do
bands = BandConfig.all_bands()
scores = Propagation.latest_scores(@default_band)
valid_time = Propagation.latest_valid_time()
if connected?(socket) do
Process.send_after(self(), :refresh, @refresh_interval_ms)
end
{:ok,
assign(socket,
page_title: "Propagation Map",
bands: bands,
selected_band: @default_band,
scores: scores,
valid_time: valid_time
)}
end
@impl true
def handle_event("select_band", %{"value" => band_str}, socket) do
band = String.to_integer(band_str)
scores = Propagation.latest_scores(band)
socket =
socket
|> assign(:selected_band, band)
|> assign(:scores, scores)
|> push_event("update_scores", %{scores: scores})
{:noreply, socket}
end
@impl true
def handle_info(:refresh, socket) do
scores = Propagation.latest_scores(socket.assigns.selected_band)
valid_time = Propagation.latest_valid_time()
Process.send_after(self(), :refresh, @refresh_interval_ms)
socket =
socket
|> assign(:scores, scores)
|> assign(:valid_time, valid_time)
|> push_event("update_scores", %{scores: scores})
{:noreply, socket}
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<div class="flex flex-col h-[calc(100vh-4rem)]">
<div class="flex items-center gap-4 p-3 bg-base-200 rounded-t-lg flex-wrap">
<h2 class="text-lg font-bold">Propagation Map</h2>
<div class="flex gap-1 flex-wrap">
<button
:for={band <- @bands}
phx-click="select_band"
value={band.freq_mhz}
class={[
"btn btn-sm",
if(@selected_band == band.freq_mhz, do: "btn-primary", else: "btn-ghost")
]}
>
{band.label}
</button>
</div>
<div :if={@valid_time} class="ml-auto text-sm text-base-content/60">
Updated: {Calendar.strftime(@valid_time, "%Y-%m-%d %H:%M UTC")}
</div>
</div>
<div
id="propagation-map"
phx-hook="PropagationMap"
phx-update="ignore"
data-scores={Jason.encode!(@scores)}
class="flex-1 rounded-b-lg z-0"
>
</div>
</div>
</Layouts.app>
"""
end
end