aprs.me/lib/aprsme_web/live/map_live/state.ex
Graham McIntire cc88fd4a10
Some checks failed
Build and Push / Build and Push Docker Image (push) Failing after 2s
fix: dialyzer errors, pubsub failure handling, cache TTL, stream trim perf, and clearer warnings
2026-07-26 16:19:42 -05:00

176 lines
5.5 KiB
Elixir

defmodule AprsmeWeb.MapLive.State do
@moduledoc """
Manages initial LiveView socket state assignment for the map view.
Extracted from Index to reduce module size.
"""
import Phoenix.Component, only: [assign: 2]
import Phoenix.LiveView, only: [connected?: 1, get_connect_params: 1]
alias Aprsme.Packets
alias AprsmeWeb.Live.Shared.BoundsUtils
alias AprsmeWeb.MapLive.PacketBatcher
alias AprsmeWeb.MapLive.UrlParams
alias Phoenix.LiveView.Socket
@doc """
Assign default values to a fresh socket.
"""
@spec default(Socket.t(), DateTime.t()) :: Socket.t()
def default(socket, one_day_ago) do
assign(socket,
packets: [],
visible_packets: %{},
historical_packets: %{},
page_title: "APRS Map",
station_popup_open: false,
map_bounds: %{
north: 49.0,
south: 24.0,
east: -66.0,
west: -125.0
},
map_center: UrlParams.default_center(),
map_zoom: UrlParams.default_zoom(),
packet_age_threshold: one_day_ago,
map_ready: false,
historical_loaded: false,
bounds_update_timer: nil,
pending_bounds: nil,
initial_bounds_loaded: false,
# Loading state management
historical_loading: false,
loading_generation: 0,
pending_batch_tasks: [],
# Overlay controls
overlay_callsign: "",
slideover_open: true,
# Track when last update occurred for real-time display in map sidebar
# Updated when packets are processed or map bounds change
last_update_at: DateTime.utc_now(),
# Add missing assigns for components
loading: false,
connection_status: connection_status(),
show_all_packets: true
)
end
@doc """
Finalize mount assigns with computed values.
Takes the socket after basic setup and an opts map.
"""
@spec finalize(Socket.t(), map()) :: Socket.t()
def finalize(socket, %{
initial_bounds: initial_bounds,
final_map_center: final_map_center,
final_map_zoom: final_map_zoom,
should_skip_initial_url_update: should_skip_initial_url_update,
tracked_callsign: tracked_callsign,
deployed_at: deployed_at,
one_day_ago: one_day_ago
}) do
# Don't override trail_duration and historical_hours if they're already set
trail_duration = Map.get(socket.assigns, :trail_duration, "1")
historical_hours = Map.get(socket.assigns, :historical_hours, "1")
packet_age_threshold = Map.get(socket.assigns, :packet_age_threshold, one_day_ago)
# If tracking a specific callsign, fetch their latest packet and other SSIDs
{tracked_callsign_latest_packet, other_ssids} =
if tracked_callsign == "" do
{nil, []}
else
try do
packet = Packets.get_latest_packet_for_callsign(tracked_callsign)
ssids = Packets.get_other_ssids(tracked_callsign)
{packet, ssids}
rescue
# Handle database connection errors gracefully (especially in tests)
DBConnection.OwnershipError ->
{nil, []}
_ ->
{nil, []}
end
end
# Start packet batcher for efficient updates
{:ok, batcher_pid} = PacketBatcher.start_link(self())
Process.monitor(batcher_pid)
# Determine initial slideover state from client viewport width
slideover_open = slideover_open?(socket)
assign(socket,
map_ready: false,
map_bounds: initial_bounds,
map_center: final_map_center,
map_zoom: final_map_zoom,
should_skip_initial_url_update: should_skip_initial_url_update,
overlay_callsign: "",
tracked_callsign: tracked_callsign,
tracked_callsign_latest_packet: tracked_callsign_latest_packet,
other_ssids: other_ssids,
trail_duration: trail_duration,
historical_hours: historical_hours,
packet_age_threshold: packet_age_threshold,
slideover_open: slideover_open,
deployed_at: deployed_at,
buffer_timer: nil,
batcher_pid: batcher_pid,
station_popup_open: false,
initial_bounds_loaded: false,
# Always load historical data on initial page load
needs_initial_historical_load: true,
# Loading state management
historical_loading: false,
loading_generation: 0,
pending_batch_tasks: [],
# Add missing assigns for components
loading: false,
connection_status: connection_status(),
show_all_packets: true
)
end
@doc """
Determine whether the slideover should be open on initial render.
"""
@spec slideover_open?(Socket.t()) :: boolean()
def slideover_open?(socket) do
if connected?(socket) do
viewport_width = get_connect_params(socket)["viewport_width"] || 1024
viewport_width >= 1024
else
true
end
end
@doc """
Get the initial APRS connection status.
"""
@spec connection_status() :: String.t()
def connection_status do
# Check if APRS connection is disabled
connection_disabled = Application.get_env(:aprsme, :disable_aprs_connection, false)
if connection_disabled, do: "disconnected", else: "connected"
end
@doc """
Default map center used by UrlParams.
"""
@spec default_center() :: %{lat: float(), lng: float()}
def default_center, do: UrlParams.default_center()
@doc """
Default map zoom used by UrlParams.
"""
def default_zoom, do: UrlParams.default_zoom()
@doc """
Calculate initial bounds from center and zoom.
"""
@spec initial_bounds(%{lat: float(), lng: float()}, integer()) :: map()
def initial_bounds(center, zoom) do
BoundsUtils.calculate_bounds_from_center_and_zoom(center, zoom)
end
end