aprs.me/lib/aprsme_web/live/info_live/show.html.heex

327 lines
14 KiB
Text

<% {symbol_table_id, symbol_code} = AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@packet || %{}) %>
<% symbol_table = if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% symbol_table_num =
case symbol_table do
"/" -> 0
"\\" -> 1
"]" -> 2
_ -> 0
end %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% _symbol_img = "/aprs-symbols/aprs-symbols-24-#{symbol_table_num}@2x.png" %>
<% _symbol_index = symbol_code_ord - 33 %>
<div class="min-h-screen bg-base-200">
<!-- Page header -->
<div class="bg-base-100 shadow-sm">
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-4 md:flex md:items-center md:justify-between">
<div class="min-w-0 flex-1">
<div class="flex items-center">
<h1 class="text-xl font-bold leading-7 sm:truncate sm:text-2xl">
APRS Station
</h1>
<div class="ml-3 flex items-center space-x-3">
<span class="badge badge-primary">
{@callsign}
</span>
<%= if @packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@packet) %>
<% symbol_table =
if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/",
do: "0",
else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
>
</div>
<% end %>
</div>
</div>
</div>
<div class="mt-3 flex md:ml-4 md:mt-0">
<div class="flex space-x-2">
<.link navigate={~p"/packets/#{@callsign}"} class="btn btn-outline btn-sm">
View packets
</.link>
<%= if @has_weather_packets do %>
<.link navigate={~p"/weather/#{@callsign}"} class="btn btn-primary btn-sm">
Weather charts
</.link>
<% end %>
</div>
</div>
</div>
</div>
</div>
<div class="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-4">
<%= if @packet do %>
<!-- Station details -->
<div class="grid grid-cols-1 gap-4 lg:grid-cols-2 mb-6">
<!-- Position information -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z"
/>
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Position Information</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium opacity-70">Location</dt>
<dd class="mt-1 text-sm font-semibold">
{@packet.lat || ""}, {@packet.lon || ""}
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Last Position</dt>
<dd class="mt-1 text-sm font-semibold">
{AprsmeWeb.MapLive.PacketUtils.get_timestamp(@packet)}
</dd>
</div>
<%= if @packet.altitude do %>
<div>
<dt class="text-xs font-medium opacity-70">Altitude</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.altitude} ft</dd>
</div>
<% end %>
<%= if @packet.course do %>
<div>
<dt class="text-xs font-medium opacity-70">Course</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.course}°</dd>
</div>
<% end %>
<%= if @packet.speed do %>
<div>
<dt class="text-xs font-medium opacity-70">Speed</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.speed} MPH</dd>
</div>
<% end %>
</dl>
</div>
</div>
<!-- Device information -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0020.75 3H3.75A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"
/>
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Device Information</h3>
</div>
</div>
<dl class="grid grid-cols-2 gap-3">
<div>
<dt class="text-xs font-medium opacity-70">Device</dt>
<dd class="mt-1 text-sm font-semibold">
<%= if @packet.device_model || @packet.device_vendor do %>
{[
@packet.device_model,
@packet.device_vendor,
@packet.device_contact
]
|> Enum.reject(&is_nil/1)
|> Enum.join(" ")}
<%= if @packet.device_class do %>
({@packet.device_class})
<% end %>
<% else %>
<span class="opacity-70 font-mono">
{@packet.device_identifier || "Unknown"}
</span>
<% end %>
</dd>
</div>
<div>
<dt class="text-xs font-medium opacity-70">Path</dt>
<dd class="mt-1 text-sm font-semibold">{@packet.path || ""}</dd>
</div>
<div class="col-span-2">
<dt class="text-xs font-medium opacity-70">Raw Packet</dt>
<dd class="mt-1 text-xs font-mono break-all">
<%= if is_binary(@packet.raw_packet) do %>
{Aprsme.EncodingUtils.sanitize_string(@packet.raw_packet)}
<% else %>
{@packet.raw_packet || ""}
<% end %>
</dd>
</div>
</dl>
</div>
</div>
</div>
<!-- Neighboring stations -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"
/>
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-medium">Stations Near Current Position</h3>
</div>
</div>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th class="text-xs font-medium uppercase tracking-wider">
Callsign
</th>
<th class="text-xs font-medium uppercase tracking-wider">
Distance & Course
</th>
<th class="text-xs font-medium uppercase tracking-wider">
Last Heard
</th>
</tr>
</thead>
<tbody>
<%= for neighbor <- @neighbors do %>
<tr>
<td class="font-medium">
<div class="flex items-center space-x-2">
<.link navigate={~p"/info/#{neighbor.callsign}"} class="link link-primary">
{neighbor.callsign}
</.link>
<%= if neighbor.packet do %>
<% {symbol_table_id, symbol_code} =
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(neighbor.packet) %>
<% symbol_table =
if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
<% symbol_code = symbol_code || ">" %>
<% table_id =
if symbol_table == "/",
do: "0",
else: if(symbol_table == "]", do: "2", else: "1") %>
<% symbol_code_ord =
symbol_code
|> String.to_charlist()
|> List.first()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() %>
<% index = symbol_code_ord - 33 %>
<% safe_index = max(0, min(index, 93)) %>
<% column = rem(safe_index, 16) %>
<% row = div(safe_index, 16) %>
<% x = -column * 128 %>
<% y = -row * 128 %>
<% symbol_img = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" %>
<div
style={"width: 32px; height: 32px; background-image: url(#{symbol_img}); background-position: #{x / 4}px #{y / 4}px; background-size: 512px 192px; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"}
title={"Symbol: #{symbol_code} (#{symbol_code_ord}) at row #{row}, col #{column}"}
>
</div>
<% end %>
</div>
</td>
<td class="opacity-70">
<%= if neighbor.course do %>
{neighbor.distance || ""} @ {Float.round(neighbor.course, 0)}°
<% else %>
{neighbor.distance || ""}
<% end %>
</td>
<td class="opacity-70">
{neighbor.last_heard || ""}
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<% else %>
<div class="text-center py-8">
<svg
class="mx-auto h-8 w-8 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423L16.5 15.75l.394 1.183a2.25 2.25 0 001.423 1.423L19.5 18.75l-1.183.394a2.25 2.25 0 00-1.423 1.423z"
/>
</svg>
<h3 class="mt-2 text-sm font-semibold">No data available</h3>
<p class="mt-1 text-sm opacity-70">
No recent packet data available for this callsign.
</p>
</div>
<% end %>
</div>
</div>