map debugging
This commit is contained in:
parent
80a39b803e
commit
210f866374
6 changed files with 741 additions and 23 deletions
140
assets/js/app.js
140
assets/js/app.js
|
|
@ -27,9 +27,149 @@ let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("
|
||||||
// Import minimal APRS map hook
|
// Import minimal APRS map hook
|
||||||
import MinimalAPRSMap from "./minimal_map.js";
|
import MinimalAPRSMap from "./minimal_map.js";
|
||||||
|
|
||||||
|
// Debug Map Hook
|
||||||
|
let DebugMap = {
|
||||||
|
mounted() {
|
||||||
|
console.log("DebugMap hook mounted");
|
||||||
|
window.liveSocket.pushEvent("debug_info", { hook_mounted: true });
|
||||||
|
|
||||||
|
// Check if Leaflet is available
|
||||||
|
if (typeof L === "undefined") {
|
||||||
|
console.error("Leaflet library not loaded!");
|
||||||
|
window.liveSocket.pushEvent("debug_info", {
|
||||||
|
leaflet_loaded: false,
|
||||||
|
errors: ["Leaflet not loaded"],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.liveSocket.pushEvent("debug_info", { leaflet_loaded: true });
|
||||||
|
|
||||||
|
// Get initial center and zoom from server-provided data attributes
|
||||||
|
let initialCenter, initialZoom;
|
||||||
|
try {
|
||||||
|
initialCenter = JSON.parse(this.el.dataset.center);
|
||||||
|
initialZoom = parseInt(this.el.dataset.zoom);
|
||||||
|
console.log("Parsed data - center:", initialCenter, "zoom:", initialZoom);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing map data attributes:", error);
|
||||||
|
window.liveSocket.pushEvent("debug_info", {
|
||||||
|
errors: ["Error parsing map data: " + error.message],
|
||||||
|
});
|
||||||
|
// Fallback values
|
||||||
|
initialCenter = { lat: 39.8283, lng: -98.5795 };
|
||||||
|
initialZoom = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check element dimensions
|
||||||
|
const rect = this.el.getBoundingClientRect();
|
||||||
|
console.log("Map element dimensions:", rect);
|
||||||
|
|
||||||
|
// Initialize basic map
|
||||||
|
try {
|
||||||
|
this.map = L.map(this.el, {
|
||||||
|
zoomControl: true,
|
||||||
|
attributionControl: true,
|
||||||
|
closePopupOnClick: true,
|
||||||
|
}).setView([initialCenter.lat, initialCenter.lng], initialZoom);
|
||||||
|
|
||||||
|
console.log("Map initialized successfully");
|
||||||
|
window.liveSocket.pushEvent("debug_info", { map_initialized: true });
|
||||||
|
window.debugMapInstance = this.map;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing map:", error);
|
||||||
|
window.liveSocket.pushEvent("debug_info", {
|
||||||
|
map_initialized: false,
|
||||||
|
errors: ["Map init error: " + error.message],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add OpenStreetMap tile layer
|
||||||
|
try {
|
||||||
|
const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
attribution:
|
||||||
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors | APRS.me',
|
||||||
|
maxZoom: 19,
|
||||||
|
});
|
||||||
|
tileLayer.addTo(this.map);
|
||||||
|
console.log("Tile layer added successfully");
|
||||||
|
|
||||||
|
// Listen for tile layer events
|
||||||
|
tileLayer.on("loading", () => console.log("Tiles loading..."));
|
||||||
|
tileLayer.on("load", () => console.log("Tiles loaded"));
|
||||||
|
tileLayer.on("tileerror", (e) => console.error("Tile error:", e));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding tile layer:", error);
|
||||||
|
window.liveSocket.pushEvent("debug_info", {
|
||||||
|
errors: ["Tile layer error: " + error.message],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force initial size calculation
|
||||||
|
try {
|
||||||
|
this.map.invalidateSize();
|
||||||
|
console.log("Map size invalidated");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error invalidating map size:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track when map is ready
|
||||||
|
this.map.whenReady(() => {
|
||||||
|
console.log("Debug map is ready");
|
||||||
|
console.log("Map container size:", this.map.getSize());
|
||||||
|
console.log("Map zoom:", this.map.getZoom());
|
||||||
|
console.log("Map center:", this.map.getCenter());
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.pushEvent("map_ready", {});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in map ready callback:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle resize
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
console.log("Window resized, invalidating map size");
|
||||||
|
try {
|
||||||
|
this.map.invalidateSize();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error invalidating map size on resize:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a delayed size check
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("Delayed map size check");
|
||||||
|
const rect = this.el.getBoundingClientRect();
|
||||||
|
console.log("Map element dimensions after delay:", rect);
|
||||||
|
if (this.map) {
|
||||||
|
try {
|
||||||
|
this.map.invalidateSize();
|
||||||
|
console.log("Map size re-invalidated after delay");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error re-invalidating map size:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
console.log("DebugMap hook destroyed");
|
||||||
|
if (this.map) {
|
||||||
|
this.map.remove();
|
||||||
|
this.map = null;
|
||||||
|
}
|
||||||
|
if (window.debugMapInstance) {
|
||||||
|
window.debugMapInstance = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// APRS Map Hook
|
// APRS Map Hook
|
||||||
let Hooks = {};
|
let Hooks = {};
|
||||||
Hooks.APRSMap = MinimalAPRSMap;
|
Hooks.APRSMap = MinimalAPRSMap;
|
||||||
|
Hooks.DebugMap = DebugMap;
|
||||||
|
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
longPollFallbackMs: 2500,
|
longPollFallbackMs: 2500,
|
||||||
|
|
|
||||||
|
|
@ -4,39 +4,165 @@
|
||||||
let MinimalAPRSMap = {
|
let MinimalAPRSMap = {
|
||||||
mounted() {
|
mounted() {
|
||||||
console.log("MinimalAPRSMap hook mounted");
|
console.log("MinimalAPRSMap hook mounted");
|
||||||
|
console.log("Element:", this.el);
|
||||||
|
console.log("Element dataset:", this.el.dataset);
|
||||||
|
|
||||||
|
// Initialize error tracking
|
||||||
|
this.errors = [];
|
||||||
|
this.initializationAttempts = 0;
|
||||||
|
this.maxInitializationAttempts = 3;
|
||||||
|
|
||||||
|
this.attemptInitialization();
|
||||||
|
},
|
||||||
|
|
||||||
|
attemptInitialization() {
|
||||||
|
this.initializationAttempts++;
|
||||||
|
console.log(
|
||||||
|
`Initialization attempt ${this.initializationAttempts}/${this.maxInitializationAttempts}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if Leaflet is available
|
||||||
|
if (typeof L === "undefined") {
|
||||||
|
console.error("Leaflet library not loaded!");
|
||||||
|
this.errors.push("Leaflet library not available");
|
||||||
|
|
||||||
|
if (this.initializationAttempts < this.maxInitializationAttempts) {
|
||||||
|
console.log("Retrying initialization in 1 second...");
|
||||||
|
setTimeout(() => this.attemptInitialization(), 1000);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.handleFatalError("Leaflet library failed to load after multiple attempts");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get initial center and zoom from server-provided data attributes
|
// Get initial center and zoom from server-provided data attributes
|
||||||
const initialCenter = JSON.parse(this.el.dataset.center);
|
let initialCenter, initialZoom;
|
||||||
const initialZoom = parseInt(this.el.dataset.zoom);
|
try {
|
||||||
|
const centerData = this.el.dataset.center;
|
||||||
|
const zoomData = this.el.dataset.zoom;
|
||||||
|
|
||||||
|
if (!centerData || !zoomData) {
|
||||||
|
throw new Error("Missing map data attributes");
|
||||||
|
}
|
||||||
|
|
||||||
|
initialCenter = JSON.parse(centerData);
|
||||||
|
initialZoom = parseInt(zoomData);
|
||||||
|
|
||||||
|
// Validate parsed data
|
||||||
|
if (
|
||||||
|
!initialCenter ||
|
||||||
|
typeof initialCenter.lat !== "number" ||
|
||||||
|
typeof initialCenter.lng !== "number"
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid center data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(initialZoom) || initialZoom < 1 || initialZoom > 20) {
|
||||||
|
throw new Error("Invalid zoom data");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Parsed data - center:", initialCenter, "zoom:", initialZoom);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing map data attributes:", error);
|
||||||
|
console.log("Raw center data:", this.el.dataset.center);
|
||||||
|
console.log("Raw zoom data:", this.el.dataset.zoom);
|
||||||
|
|
||||||
|
// Fallback values
|
||||||
|
initialCenter = { lat: 39.8283, lng: -98.5795 };
|
||||||
|
initialZoom = 5;
|
||||||
|
console.log("Using fallback values:", initialCenter, initialZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initializeMap(initialCenter, initialZoom);
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeMap(initialCenter, initialZoom) {
|
||||||
console.log("Initializing minimal map with center:", initialCenter, "and zoom:", initialZoom);
|
console.log("Initializing minimal map with center:", initialCenter, "and zoom:", initialZoom);
|
||||||
|
|
||||||
|
// Check element dimensions
|
||||||
|
const rect = this.el.getBoundingClientRect();
|
||||||
|
console.log("Map element dimensions:", rect);
|
||||||
|
|
||||||
|
// Validate element has dimensions
|
||||||
|
if (rect.width === 0 || rect.height === 0) {
|
||||||
|
console.warn("Map element has no dimensions, retrying...");
|
||||||
|
this.errors.push("Map element has zero dimensions");
|
||||||
|
|
||||||
|
if (this.initializationAttempts < this.maxInitializationAttempts) {
|
||||||
|
setTimeout(() => this.attemptInitialization(), 500);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.handleFatalError("Map element never gained proper dimensions");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize basic map
|
// Initialize basic map
|
||||||
this.map = L.map(this.el, {
|
try {
|
||||||
zoomControl: true,
|
this.map = L.map(this.el, {
|
||||||
attributionControl: true,
|
zoomControl: true,
|
||||||
closePopupOnClick: true,
|
attributionControl: true,
|
||||||
}).setView([initialCenter.lat, initialCenter.lng], initialZoom);
|
closePopupOnClick: true,
|
||||||
|
}).setView([initialCenter.lat, initialCenter.lng], initialZoom);
|
||||||
|
console.log("Map initialized successfully");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing map:", error);
|
||||||
|
this.errors.push("Map initialization failed: " + error.message);
|
||||||
|
|
||||||
|
if (this.initializationAttempts < this.maxInitializationAttempts) {
|
||||||
|
setTimeout(() => this.attemptInitialization(), 1000);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.handleFatalError("Map initialization failed after multiple attempts");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add OpenStreetMap tile layer
|
// Add OpenStreetMap tile layer
|
||||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
try {
|
||||||
attribution:
|
const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors | APRS.me',
|
attribution:
|
||||||
maxZoom: 19,
|
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors | APRS.me',
|
||||||
}).addTo(this.map);
|
maxZoom: 19,
|
||||||
|
});
|
||||||
|
tileLayer.addTo(this.map);
|
||||||
|
console.log("Tile layer added successfully");
|
||||||
|
|
||||||
|
// Listen for tile layer events
|
||||||
|
tileLayer.on("loading", () => console.log("Tiles loading..."));
|
||||||
|
tileLayer.on("load", () => console.log("Tiles loaded"));
|
||||||
|
tileLayer.on("tileerror", (e) => console.error("Tile error:", e));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error adding tile layer:", error);
|
||||||
|
this.errors.push("Tile layer failed: " + error.message);
|
||||||
|
}
|
||||||
|
|
||||||
// Store markers for management
|
// Store markers for management
|
||||||
this.markers = new Map();
|
this.markers = new Map();
|
||||||
this.markerLayer = L.layerGroup().addTo(this.map);
|
this.markerLayer = L.layerGroup().addTo(this.map);
|
||||||
|
|
||||||
// Force initial size calculation
|
// Force initial size calculation
|
||||||
this.map.invalidateSize();
|
try {
|
||||||
|
this.map.invalidateSize();
|
||||||
|
console.log("Map size invalidated");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error invalidating map size:", error);
|
||||||
|
}
|
||||||
|
|
||||||
// Track when map is ready
|
// Track when map is ready
|
||||||
this.map.whenReady(() => {
|
this.map.whenReady(() => {
|
||||||
console.log("Minimal map is ready");
|
console.log("Minimal map is ready");
|
||||||
this.pushEvent("map_ready", {});
|
console.log("Map container size:", this.map.getSize());
|
||||||
this.sendBoundsToServer();
|
console.log("Map zoom:", this.map.getZoom());
|
||||||
|
console.log("Map center:", this.map.getCenter());
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.pushEvent("map_ready", {});
|
||||||
|
this.sendBoundsToServer();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in map ready callback:", error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send bounds to LiveView when map moves
|
// Send bounds to LiveView when map moves
|
||||||
|
|
@ -48,14 +174,67 @@ let MinimalAPRSMap = {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize
|
||||||
window.addEventListener("resize", () => {
|
this.resizeHandler = () => {
|
||||||
this.map.invalidateSize();
|
console.log("Window resized, invalidating map size");
|
||||||
});
|
try {
|
||||||
|
if (this.map) {
|
||||||
|
this.map.invalidateSize();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error invalidating map size on resize:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("resize", this.resizeHandler);
|
||||||
|
|
||||||
|
// Add a delayed size check
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log("Delayed map size check");
|
||||||
|
const rect = this.el.getBoundingClientRect();
|
||||||
|
console.log("Map element dimensions after delay:", rect);
|
||||||
|
if (this.map) {
|
||||||
|
try {
|
||||||
|
this.map.invalidateSize();
|
||||||
|
console.log("Map size re-invalidated after delay");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error re-invalidating map size:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
// LiveView event handlers
|
// LiveView event handlers
|
||||||
this.setupLiveViewHandlers();
|
this.setupLiveViewHandlers();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleFatalError(message) {
|
||||||
|
console.error("Fatal map error:", message);
|
||||||
|
console.error("All errors:", this.errors);
|
||||||
|
|
||||||
|
// Display error message to user
|
||||||
|
if (this.el) {
|
||||||
|
this.el.innerHTML = `
|
||||||
|
<div style="
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #721c24;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
">
|
||||||
|
<div>
|
||||||
|
<h3 style="margin: 0 0 10px 0;">Map Loading Error</h3>
|
||||||
|
<p style="margin: 0; font-size: 14px;">${message}</p>
|
||||||
|
<p style="margin: 10px 0 0 0; font-size: 12px; color: #856404;">
|
||||||
|
Please refresh the page or check the browser console for details.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setupLiveViewHandlers() {
|
setupLiveViewHandlers() {
|
||||||
// Add single marker
|
// Add single marker
|
||||||
this.handleEvent("add_marker", (data) => {
|
this.handleEvent("add_marker", (data) => {
|
||||||
|
|
@ -346,6 +525,11 @@ let MinimalAPRSMap = {
|
||||||
clearTimeout(this.boundsTimer);
|
clearTimeout(this.boundsTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up event listeners
|
||||||
|
if (this.resizeHandler) {
|
||||||
|
window.removeEventListener("resize", this.resizeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up markers
|
// Clean up markers
|
||||||
if (this.markerLayer) {
|
if (this.markerLayer) {
|
||||||
this.markerLayer.clearLayers();
|
this.markerLayer.clearLayers();
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,84 @@ defmodule AprsWeb.PageController do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_map(conn, _params) do
|
||||||
|
# Simple HTML test page for basic map functionality
|
||||||
|
html(conn, """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Map Test - APRS.me</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
|
||||||
|
#map { height: 100vh; width: 100%; }
|
||||||
|
.status { position: fixed; top: 10px; left: 10px; z-index: 1000; background: white; padding: 10px; border-radius: 5px; border: 1px solid #ccc; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="status">
|
||||||
|
<strong>Map Test</strong><br>
|
||||||
|
Status: <span id="status">Initializing...</span><br>
|
||||||
|
<button onclick="testMarker()">Add Test Marker</button><br>
|
||||||
|
<a href="/">← Back to Main Map</a>
|
||||||
|
</div>
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||||
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
||||||
|
<script>
|
||||||
|
let map;
|
||||||
|
let status = document.getElementById('status');
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.textContent = 'Loading Leaflet...';
|
||||||
|
|
||||||
|
if (typeof L === 'undefined') {
|
||||||
|
throw new Error('Leaflet not loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
status.textContent = 'Initializing map...';
|
||||||
|
|
||||||
|
map = L.map('map').setView([39.8283, -98.5795], 5);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© OpenStreetMap contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
status.textContent = 'Map loaded successfully!';
|
||||||
|
status.style.color = 'green';
|
||||||
|
|
||||||
|
// Add a default marker
|
||||||
|
L.marker([39.8283, -98.5795])
|
||||||
|
.addTo(map)
|
||||||
|
.bindPopup('Test marker - map is working!')
|
||||||
|
.openPopup();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
status.textContent = 'Error: ' + error.message;
|
||||||
|
status.style.color = 'red';
|
||||||
|
console.error('Map initialization error:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMarker() {
|
||||||
|
if (map) {
|
||||||
|
const lat = 39 + Math.random() * 10;
|
||||||
|
const lng = -100 + Math.random() * 10;
|
||||||
|
L.marker([lat, lng])
|
||||||
|
.addTo(map)
|
||||||
|
.bindPopup('Random test marker');
|
||||||
|
status.textContent = 'Added marker at ' + lat.toFixed(2) + ', ' + lng.toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
defp format_uptime(seconds) when seconds <= 0, do: "Not connected"
|
defp format_uptime(seconds) when seconds <= 0, do: "Not connected"
|
||||||
|
|
||||||
defp format_uptime(seconds) do
|
defp format_uptime(seconds) do
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
def mount(%{"callsign" => callsign}, _session, socket) do
|
def mount(%{"callsign" => callsign}, _session, socket) do
|
||||||
# Normalize callsign to uppercase
|
# Normalize callsign to uppercase
|
||||||
normalized_callsign = String.upcase(callsign)
|
normalized_callsign = String.upcase(callsign)
|
||||||
|
IO.puts("CallsignView: Mounting for callsign #{normalized_callsign}")
|
||||||
|
|
||||||
# Calculate one hour ago for packet age filtering
|
# Calculate one hour ago for packet age filtering
|
||||||
one_hour_ago = DateTime.add(DateTime.utc_now(), -3600, :second)
|
one_hour_ago = DateTime.add(DateTime.utc_now(), -3600, :second)
|
||||||
|
|
@ -33,6 +34,8 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
},
|
},
|
||||||
map_center: @default_center,
|
map_center: @default_center,
|
||||||
map_zoom: @default_zoom,
|
map_zoom: @default_zoom,
|
||||||
|
default_center: @default_center,
|
||||||
|
default_zoom: @default_zoom,
|
||||||
# Replay controls
|
# Replay controls
|
||||||
replay_active: false,
|
replay_active: false,
|
||||||
replay_speed: @default_replay_speed,
|
replay_speed: @default_replay_speed,
|
||||||
|
|
@ -57,10 +60,12 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
)
|
)
|
||||||
|
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
|
IO.puts("CallsignView: Socket connected, subscribing to messages")
|
||||||
Endpoint.subscribe("aprs_messages")
|
Endpoint.subscribe("aprs_messages")
|
||||||
|
|
||||||
# Load recent packets for this callsign
|
# Load recent packets for this callsign
|
||||||
socket = load_callsign_packets(socket, normalized_callsign)
|
socket = load_callsign_packets(socket, normalized_callsign)
|
||||||
|
IO.puts("CallsignView: Loaded packets, map_center: #{inspect(socket.assigns.map_center)}")
|
||||||
|
|
||||||
# Schedule regular cleanup of old packets from the map
|
# Schedule regular cleanup of old packets from the map
|
||||||
Process.send_after(self(), :cleanup_old_packets, 60_000)
|
Process.send_after(self(), :cleanup_old_packets, 60_000)
|
||||||
|
|
@ -69,6 +74,7 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
|
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
else
|
else
|
||||||
|
IO.puts("CallsignView: Socket not connected")
|
||||||
{:ok, socket}
|
{:ok, socket}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -137,13 +143,16 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("map_ready", _params, socket) do
|
def handle_event("map_ready", _params, socket) do
|
||||||
|
IO.puts("CallsignView: Map ready event received")
|
||||||
socket = assign(socket, map_ready: true)
|
socket = assign(socket, map_ready: true)
|
||||||
|
|
||||||
# Auto-start replay if it hasn't been started yet
|
# Auto-start replay if it hasn't been started yet
|
||||||
socket =
|
socket =
|
||||||
if socket.assigns.replay_started do
|
if socket.assigns.replay_started do
|
||||||
|
IO.puts("CallsignView: Replay already started")
|
||||||
socket
|
socket
|
||||||
else
|
else
|
||||||
|
IO.puts("CallsignView: Starting historical replay")
|
||||||
socket = start_historical_replay(socket)
|
socket = start_historical_replay(socket)
|
||||||
assign(socket, replay_started: true, replay_active: true)
|
assign(socket, replay_started: true, replay_active: true)
|
||||||
end
|
end
|
||||||
|
|
@ -152,13 +161,16 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
socket =
|
socket =
|
||||||
if socket.assigns.pending_geolocation do
|
if socket.assigns.pending_geolocation do
|
||||||
location = socket.assigns.pending_geolocation
|
location = socket.assigns.pending_geolocation
|
||||||
|
IO.puts("CallsignView: Zooming to pending geolocation: #{inspect(location)}")
|
||||||
push_event(socket, "zoom_to_location", %{lat: location.lat, lng: location.lng, zoom: 12})
|
push_event(socket, "zoom_to_location", %{lat: location.lat, lng: location.lng, zoom: 12})
|
||||||
else
|
else
|
||||||
# If we have a last known position, zoom to it
|
# If we have a last known position, zoom to it
|
||||||
if socket.assigns.last_known_position do
|
if socket.assigns.last_known_position do
|
||||||
pos = socket.assigns.last_known_position
|
pos = socket.assigns.last_known_position
|
||||||
|
IO.puts("CallsignView: Zooming to last known position: #{inspect(pos)}")
|
||||||
push_event(socket, "zoom_to_location", %{lat: pos.lat, lng: pos.lng, zoom: 12})
|
push_event(socket, "zoom_to_location", %{lat: pos.lat, lng: pos.lng, zoom: 12})
|
||||||
else
|
else
|
||||||
|
IO.puts("CallsignView: No position data to zoom to")
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -367,16 +379,31 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#aprs-map {
|
#aprs-map {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Ensure the map container has a defined height */
|
||||||
|
.phx-main {
|
||||||
|
position: relative;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix for potential layout issues */
|
||||||
|
body, html {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.callsign-header {
|
.callsign-header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
|
@ -607,8 +634,8 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
<div
|
<div
|
||||||
id="aprs-map"
|
id="aprs-map"
|
||||||
phx-hook="APRSMap"
|
phx-hook="APRSMap"
|
||||||
data-center={Jason.encode!(@map_center)}
|
data-center={Jason.encode!(@map_center || @default_center)}
|
||||||
data-zoom={@map_zoom}
|
data-zoom={@map_zoom || @default_zoom}
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
@ -813,6 +840,7 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
defp load_callsign_packets(socket, callsign) do
|
defp load_callsign_packets(socket, callsign) do
|
||||||
# Load recent packets (last hour) for this specific callsign
|
# Load recent packets (last hour) for this specific callsign
|
||||||
recent_packets = Packets.get_recent_packets(%{callsign: callsign})
|
recent_packets = Packets.get_recent_packets(%{callsign: callsign})
|
||||||
|
IO.puts("CallsignView: Found #{length(recent_packets)} recent packets for #{callsign}")
|
||||||
|
|
||||||
# Find the most recent packet with position data for auto-zoom
|
# Find the most recent packet with position data for auto-zoom
|
||||||
last_known_position =
|
last_known_position =
|
||||||
|
|
@ -822,11 +850,20 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
|> List.first()
|
|> List.first()
|
||||||
|> case do
|
|> case do
|
||||||
nil ->
|
nil ->
|
||||||
|
IO.puts("CallsignView: No packets with position data found")
|
||||||
nil
|
nil
|
||||||
|
|
||||||
packet ->
|
packet ->
|
||||||
{lat, lng} = get_coordinates(packet)
|
{lat, lng} = get_coordinates(packet)
|
||||||
if lat && lng, do: %{lat: lat, lng: lng}
|
|
||||||
|
if lat && lng do
|
||||||
|
position = %{lat: lat, lng: lng}
|
||||||
|
IO.puts("CallsignView: Last known position: #{inspect(position)}")
|
||||||
|
position
|
||||||
|
else
|
||||||
|
IO.puts("CallsignView: Could not extract coordinates from packet")
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert packets to client-friendly format and send to map
|
# Convert packets to client-friendly format and send to map
|
||||||
|
|
@ -837,11 +874,15 @@ defmodule AprsWeb.MapLive.CallsignView do
|
||||||
|> Stream.filter(&(&1 != nil))
|
|> Stream.filter(&(&1 != nil))
|
||||||
|> Enum.to_list()
|
|> Enum.to_list()
|
||||||
|
|
||||||
|
IO.puts("CallsignView: Converted #{length(packet_data_list)} packets to marker data")
|
||||||
|
|
||||||
# Send packets to map if any exist
|
# Send packets to map if any exist
|
||||||
socket =
|
socket =
|
||||||
if length(packet_data_list) > 0 do
|
if length(packet_data_list) > 0 do
|
||||||
|
IO.puts("CallsignView: Sending markers to map")
|
||||||
push_event(socket, "add_markers", %{markers: packet_data_list})
|
push_event(socket, "add_markers", %{markers: packet_data_list})
|
||||||
else
|
else
|
||||||
|
IO.puts("CallsignView: No markers to send to map")
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
273
lib/aprs_web/live/map_live/debug.ex
Normal file
273
lib/aprs_web/live/map_live/debug.ex
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
defmodule AprsWeb.MapLive.Debug do
|
||||||
|
@moduledoc false
|
||||||
|
use AprsWeb, :live_view
|
||||||
|
|
||||||
|
@default_center %{lat: 39.8283, lng: -98.5795}
|
||||||
|
@default_zoom 5
|
||||||
|
|
||||||
|
def mount(_params, _session, socket) do
|
||||||
|
socket =
|
||||||
|
assign(socket,
|
||||||
|
page_title: "Map Debug",
|
||||||
|
map_center: @default_center,
|
||||||
|
map_zoom: @default_zoom,
|
||||||
|
debug_info: %{
|
||||||
|
leaflet_loaded: false,
|
||||||
|
map_initialized: false,
|
||||||
|
hook_mounted: false,
|
||||||
|
map_ready: false,
|
||||||
|
element_dimensions: nil,
|
||||||
|
errors: []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("debug_info", params, socket) do
|
||||||
|
debug_info = Map.merge(socket.assigns.debug_info, params)
|
||||||
|
{:noreply, assign(socket, debug_info: debug_info)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("map_ready", _params, socket) do
|
||||||
|
debug_info = Map.put(socket.assigns.debug_info, :map_ready, true)
|
||||||
|
{:noreply, assign(socket, debug_info: debug_info)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||||
|
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||||
|
crossorigin=""
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||||
|
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||||
|
crossorigin=""
|
||||||
|
>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.debug-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-panel {
|
||||||
|
width: 400px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-right: 1px solid #dee2e6;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-ok {
|
||||||
|
background: #d4edda;
|
||||||
|
border-color: #c3e6cb;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-error {
|
||||||
|
background: #f8d7da;
|
||||||
|
border-color: #f5c6cb;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending {
|
||||||
|
background: #fff3cd;
|
||||||
|
border-color: #ffeaa7;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-button {
|
||||||
|
background: #007cba;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px 5px 5px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-button:hover {
|
||||||
|
background: #005a87;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="debug-container">
|
||||||
|
<div class="debug-panel">
|
||||||
|
<div class="debug-title">Map Debug Panel</div>
|
||||||
|
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>Map Dimensions:</strong> <br />
|
||||||
|
<span id="dimensions-info">Checking...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={"status-item #{if @debug_info.leaflet_loaded, do: "status-ok", else: "status-pending"}"}>
|
||||||
|
<strong>Leaflet Library:</strong> <br />
|
||||||
|
{if @debug_info.leaflet_loaded, do: "✓ Loaded", else: "⏳ Loading..."}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={"status-item #{if @debug_info.hook_mounted, do: "status-ok", else: "status-pending"}"}>
|
||||||
|
<strong>Phoenix Hook:</strong> <br />
|
||||||
|
{if @debug_info.hook_mounted, do: "✓ Mounted", else: "⏳ Waiting..."}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={"status-item #{if @debug_info.map_initialized, do: "status-ok", else: "status-pending"}"}>
|
||||||
|
<strong>Map Initialization:</strong> <br />
|
||||||
|
{if @debug_info.map_initialized, do: "✓ Initialized", else: "⏳ Waiting..."}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={"status-item #{if @debug_info.map_ready, do: "status-ok", else: "status-pending"}"}>
|
||||||
|
<strong>Map Ready:</strong> <br />
|
||||||
|
{if @debug_info.map_ready, do: "✓ Ready", else: "⏳ Waiting..."}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= if length(@debug_info.errors) > 0 do %>
|
||||||
|
<div class="status-item status-error">
|
||||||
|
<strong>Errors:</strong>
|
||||||
|
<br />
|
||||||
|
<%= for error <- @debug_info.errors do %>
|
||||||
|
• {error}<br />
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>Test Actions:</strong> <br />
|
||||||
|
<button class="test-button" onclick="testLeaflet()">Test Leaflet</button>
|
||||||
|
<button class="test-button" onclick="testMapSize()">Test Map Size</button>
|
||||||
|
<button class="test-button" onclick="addTestMarker()">Add Marker</button>
|
||||||
|
<button class="test-button" onclick="clearConsole()">Clear Console</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>Console Output:</strong>
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
id="console-output"
|
||||||
|
style="height: 200px; overflow-y: auto; background: white; padding: 10px; border: 1px solid #ccc; font-size: 10px;"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-item">
|
||||||
|
<strong>Instructions:</strong>
|
||||||
|
<br /> 1. Open browser dev tools (F12)<br /> 2. Check Console tab for errors<br />
|
||||||
|
3. Watch this panel for status updates<br /> 4. Use test buttons to debug issues
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="map-container">
|
||||||
|
<div
|
||||||
|
id="debug-map"
|
||||||
|
phx-hook="DebugMap"
|
||||||
|
data-center={Jason.encode!(@map_center)}
|
||||||
|
data-zoom={@map_zoom}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Capture console output
|
||||||
|
const originalLog = console.log;
|
||||||
|
const originalError = console.error;
|
||||||
|
const originalWarn = console.warn;
|
||||||
|
|
||||||
|
function addToConsoleOutput(message, type = 'log') {
|
||||||
|
const output = document.getElementById('console-output');
|
||||||
|
if (output) {
|
||||||
|
const timestamp = new Date().toLocaleTimeString();
|
||||||
|
const color = type === 'error' ? 'red' : type === 'warn' ? 'orange' : 'black';
|
||||||
|
output.innerHTML += `<div style="color: ${color};">[${timestamp}] ${message}</div>`;
|
||||||
|
output.scrollTop = output.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log = function(...args) {
|
||||||
|
originalLog.apply(console, args);
|
||||||
|
addToConsoleOutput(args.join(' '), 'log');
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error = function(...args) {
|
||||||
|
originalError.apply(console, args);
|
||||||
|
addToConsoleOutput(args.join(' '), 'error');
|
||||||
|
};
|
||||||
|
|
||||||
|
console.warn = function(...args) {
|
||||||
|
originalWarn.apply(console, args);
|
||||||
|
addToConsoleOutput(args.join(' '), 'warn');
|
||||||
|
};
|
||||||
|
|
||||||
|
function testLeaflet() {
|
||||||
|
if (typeof L !== 'undefined') {
|
||||||
|
console.log('✓ Leaflet is available, version:', L.version);
|
||||||
|
window.liveSocket.pushEvent('debug_info', {leaflet_loaded: true});
|
||||||
|
} else {
|
||||||
|
console.error('✗ Leaflet is not available');
|
||||||
|
window.liveSocket.pushEvent('debug_info', {leaflet_loaded: false, errors: ['Leaflet not loaded']});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testMapSize() {
|
||||||
|
const mapEl = document.getElementById('debug-map');
|
||||||
|
if (mapEl) {
|
||||||
|
const rect = mapEl.getBoundingClientRect();
|
||||||
|
console.log('Map element dimensions:', rect);
|
||||||
|
document.getElementById('dimensions-info').textContent =
|
||||||
|
`${rect.width}x${rect.height} (${rect.left},${rect.top})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTestMarker() {
|
||||||
|
if (window.debugMapInstance) {
|
||||||
|
const marker = L.marker([39.8283, -98.5795])
|
||||||
|
.addTo(window.debugMapInstance)
|
||||||
|
.bindPopup('Test Marker');
|
||||||
|
console.log('✓ Test marker added');
|
||||||
|
} else {
|
||||||
|
console.error('✗ No map instance available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearConsole() {
|
||||||
|
document.getElementById('console-output').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Leaflet availability when page loads
|
||||||
|
setTimeout(testLeaflet, 1000);
|
||||||
|
setTimeout(testMapSize, 1000);
|
||||||
|
</script>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -31,6 +31,8 @@ defmodule AprsWeb.Router do
|
||||||
live "/enhanced", MapLive.Enhanced, :index
|
live "/enhanced", MapLive.Enhanced, :index
|
||||||
get "/old", PageController, :map
|
get "/old", PageController, :map
|
||||||
live "/status", StatusLive.Index, :index
|
live "/status", StatusLive.Index, :index
|
||||||
|
live "/debug_map", MapLive.Debug, :index
|
||||||
|
get "/test_map", PageController, :test_map
|
||||||
|
|
||||||
live "/packets", PacketsLive.Index, :index
|
live "/packets", PacketsLive.Index, :index
|
||||||
live "/:callsign", MapLive.CallsignView, :index
|
live "/:callsign", MapLive.CallsignView, :index
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue