Fix spurious bounds_changed events and event loops

Key fixes:
- Combine bounds_changed and update_map_state into single event
- Remove duplicate sendBoundsToServer() calls
- Add programmaticMove flag to prevent event loops
- Only process bounds updates when bounds actually change
- Debounce events with 300ms timeout

This eliminates:
- Duplicate server events for same user action
- Event loops when server updates URL
- Unnecessary bounds processing
- Excessive server load

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Graham McIntire 2025-07-09 09:30:05 -05:00
parent c873614bf1
commit a9414823f1
No known key found for this signature in database
2 changed files with 50 additions and 7 deletions

View file

@ -25,6 +25,7 @@ type LiveViewHookContext = {
lastZoom?: number;
currentPopupMarkerId?: string | null;
oms?: any;
programmaticMove?: boolean;
[key: string]: any;
};
@ -287,9 +288,14 @@ let MapAPRSMap = {
// Send bounds to LiveView when map moves
self.map!.on("moveend", () => {
// Skip if this is a programmatic move from the server
if (self.programmaticMove) {
self.programmaticMove = false;
return;
}
if (self.boundsTimer) clearTimeout(self.boundsTimer);
self.boundsTimer = setTimeout(() => {
self.sendBoundsToServer();
// Save map state and update URL
const center = self.map.getCenter();
const zoom = self.map.getZoom();
@ -298,16 +304,28 @@ let MapAPRSMap = {
JSON.stringify({ lat: center.lat, lng: center.lng, zoom }),
);
// Send map state update to server for URL updating
// Send combined map state update to server for URL and bounds updating
self.pushEvent("update_map_state", {
center: { lat: center.lat, lng: center.lng },
zoom: zoom
zoom: zoom,
bounds: {
north: self.map.getBounds().getNorth(),
south: self.map.getBounds().getSouth(),
east: self.map.getBounds().getEast(),
west: self.map.getBounds().getWest(),
}
});
}, 300);
});
// Handle zoom changes with optimization for large zoom differences
self.map!.on("zoomend", () => {
// Skip if this is a programmatic move from the server
if (self.programmaticMove) {
self.programmaticMove = false;
return;
}
if (self.boundsTimer) clearTimeout(self.boundsTimer);
self.boundsTimer = setTimeout(() => {
const currentZoom = self.map!.getZoom();
@ -319,7 +337,6 @@ let MapAPRSMap = {
self.pushEvent("refresh_markers", {});
}
self.sendBoundsToServer();
self.lastZoom = currentZoom;
// Save map state and update URL
const center = self.map.getCenter();
@ -329,10 +346,16 @@ let MapAPRSMap = {
JSON.stringify({ lat: center.lat, lng: center.lng, zoom }),
);
// Send map state update to server for URL updating
// Send combined map state update to server for URL and bounds updating
self.pushEvent("update_map_state", {
center: { lat: center.lat, lng: center.lng },
zoom: zoom
zoom: zoom,
bounds: {
north: self.map.getBounds().getNorth(),
south: self.map.getBounds().getSouth(),
east: self.map.getBounds().getEast(),
west: self.map.getBounds().getWest(),
}
});
}, 300);
});
@ -464,6 +487,8 @@ let MapAPRSMap = {
// Use a slight delay to ensure map is ready
setTimeout(() => {
if (self.map) {
// Set flag to prevent event loop
self.programmaticMove = true;
self.map.setView([lat, lng], zoom, {
animate: true,
duration: 1,

View file

@ -317,7 +317,7 @@ defmodule AprsmeWeb.MapLive.Index do
end
@impl true
def handle_event("update_map_state", %{"center" => center, "zoom" => zoom}, socket) do
def handle_event("update_map_state", %{"center" => center, "zoom" => zoom} = params, socket) do
# Parse center coordinates
lat =
case center do
@ -345,6 +345,24 @@ defmodule AprsmeWeb.MapLive.Index do
new_path = "/?lat=#{lat}&lng=#{lng}&z=#{zoom}"
socket = push_patch(socket, to: new_path, replace: true)
# If bounds are included, also process bounds update
socket = case Map.get(params, "bounds") do
%{"north" => north, "south" => south, "east" => east, "west" => west} ->
map_bounds = %{
north: north,
south: south,
east: east,
west: west
}
# Only trigger bounds processing if bounds actually changed
if socket.assigns.map_bounds != map_bounds do
send(self(), {:process_bounds_update, map_bounds})
end
socket
_ ->
socket
end
{:noreply, socket}
end