defmodule MicrowavepropWeb.BeaconLive.Show do
@moduledoc false
use MicrowavepropWeb, :live_view
alias Microwaveprop.Beacons
alias Microwaveprop.Beacons.Beacon
alias Microwaveprop.Beacons.RangeEstimate
@impl true
def render(assigns) do
~H"""
<.header>
{@beacon.callsign}
<:subtitle>
{Beacon.format_freq(@beacon.frequency_mhz)} MHz · {@beacon.grid}
Pending approval
<:actions>
<.button
:if={admin?(@current_scope) and not @beacon.approved}
variant="primary"
phx-click="approve"
>
<.icon name="hero-check" /> Approve
<.button navigate={~p"/beacons"}>
<.icon name="hero-arrow-left" />
<.button
:if={admin?(@current_scope)}
variant="primary"
navigate={~p"/beacons/#{@beacon}/edit?return_to=show"}
>
<.icon name="hero-pencil-square" /> Edit
@beacon.id}
phx-hook="BeaconMap"
phx-update="ignore"
class="h-96 w-full rounded-lg border border-base-300 mb-2"
data-lat={@beacon.lat}
data-lon={@beacon.lon}
data-label={"#{@beacon.callsign} · #{Beacon.format_freq(@beacon.frequency_mhz)} MHz"}
data-on-the-air={to_string(@beacon.on_the_air)}
data-grid-step={@estimate.grid_step}
data-cells={Jason.encode!(@estimate.cells)}
data-show-coverage={to_string(@show_coverage)}
>
Show estimated current coverage
Band {@estimate.band_label} ·
EIRP {@estimate.eirp_dbm} dBm ·
Atm loss {@estimate.atm_per_km} dB/km
Beacon grid score {@estimate.center_score}
<%= if @estimate.valid_time do %>
at {Calendar.strftime(@estimate.valid_time, "%Y-%m-%d %H:%M UTC")}
<% else %>
(no HRRR data — defaulting cells to 50)
<% end %>
· {length(@estimate.cells)}
cells rendered
<%= for tier <- @estimate.tiers do %>
{tier.label} (≥ {tier.min_dbm} dBm)
<% end %>
<.stat_field label="Frequency">{Beacon.format_freq(@beacon.frequency_mhz)} MHz
<.stat_field label="Callsign">{@beacon.callsign}
<.stat_field label="Keying">{Beacon.keying_label(@beacon.keying)}
<.stat_field label="On the air">
{if @beacon.on_the_air, do: "Yes", else: "No"}
<.stat_field label="Grid">{@beacon.grid}
<.stat_field label="Latitude">{format_coord(@beacon.lat)}
<.stat_field label="Longitude">{format_coord(@beacon.lon)}
<.stat_field label="EIRP">{Beacon.format_mw(@beacon.power_mw)} mW
<.stat_field label="Height AGL">{@beacon.height_ft} ft
<.stat_field label="Bearing">{bearing_label(@beacon.bearing)}
<.stat_field :if={@beacon.beamwidth_deg} label="Beamwidth">
{Beacon.format_mw(@beacon.beamwidth_deg)}°
Submitted by
{@beacon.user.callsign}
"""
end
@impl true
def mount(%{"id" => id}, _session, socket) do
if connected?(socket), do: Beacons.subscribe_beacons()
beacon = Beacons.get_beacon!(id)
{:ok,
socket
|> assign(:page_title, "Beacon")
|> assign(:beacon, beacon)
|> assign(:show_coverage, false)
|> assign(:estimate, RangeEstimate.estimate(beacon))}
end
@impl true
def handle_event("toggle_coverage", _params, socket) do
show = not socket.assigns.show_coverage
{:noreply,
socket
|> assign(:show_coverage, show)
|> push_event("toggle_coverage", %{show: show})}
end
def handle_event("approve", _params, socket) do
if admin?(socket.assigns.current_scope) do
{:ok, approved} = Beacons.approve_beacon(socket.assigns.beacon)
{:noreply,
socket
|> assign(:beacon, approved)
|> put_flash(:info, "Beacon approved.")}
else
{:noreply, put_flash(socket, :error, "Admins only.")}
end
end
@impl true
def handle_info({:updated, %Beacon{id: id} = beacon}, %{assigns: %{beacon: %{id: id}}} = socket) do
{:noreply,
socket
|> assign(:beacon, beacon)
|> assign(:estimate, RangeEstimate.estimate(beacon))}
end
def handle_info({:deleted, %Beacon{id: id}}, %{assigns: %{beacon: %{id: id}}} = socket) do
{:noreply,
socket
|> put_flash(:error, "This beacon was deleted.")
|> push_navigate(to: ~p"/beacons")}
end
def handle_info({type, %Beacon{}}, socket) when type in [:created, :updated, :deleted] do
{:noreply, socket}
end
defp admin?(%{user: %{is_admin: true}}), do: true
defp admin?(_), do: false
defp bearing_label(nil), do: "Omni"
defp bearing_label("omni"), do: "Omni"
defp bearing_label(value), do: "#{value}°"
defp format_coord(value) when is_float(value), do: :erlang.float_to_binary(value, decimals: 6)
defp format_coord(value), do: to_string(value)
attr :label, :string, required: true
slot :inner_block, required: true
defp stat_field(assigns) do
~H"""
{@label}
{render_slot(@inner_block)}
"""
end
end