From a9414823f190d5d09fa4124e07a196bcf021b702 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 9 Jul 2025 09:30:05 -0500 Subject: [PATCH] Fix spurious bounds_changed events and event loops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- assets/js/map.ts | 37 ++++++++++++++++++++++----- lib/aprsme_web/live/map_live/index.ex | 20 ++++++++++++++- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/assets/js/map.ts b/assets/js/map.ts index e2b3e5b..c7fc2df 100644 --- a/assets/js/map.ts +++ b/assets/js/map.ts @@ -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, diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index a962556..1733fd2 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -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