- Removed all console.log debugging statements from app.js (theme changes, chart rendering) - Removed all console.log statements from map.ts (map initialization, data processing) - Removed all console.debug statements from map_helpers.ts - Removed all console.debug statements from map_fixes.ts - Kept essential console.error and console.warn statements for error handling - Reduced bundle size by ~1.4kb This cleanup removes unnecessary debug output while maintaining error reporting for production debugging via console.error and Sentry. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
305 lines
No EOL
8.4 KiB
TypeScript
305 lines
No EOL
8.4 KiB
TypeScript
// Proposed fixes for map.ts issues
|
|
|
|
import type { Map as LeafletMap } from 'leaflet';
|
|
import type { LiveViewHookContext } from './types/map';
|
|
import type { BaseEventPayload, PushEventFunction } from './types/events';
|
|
import type { APRSMarker } from './types/marker-extensions';
|
|
|
|
// 1. Extract duplicated functions
|
|
export const MapHelpers = {
|
|
// Centralized timestamp parsing
|
|
parseTimestamp(timestamp: string | number | undefined | null): number {
|
|
if (!timestamp) return Date.now();
|
|
|
|
if (typeof timestamp === "number") {
|
|
return timestamp;
|
|
} else if (typeof timestamp === "string") {
|
|
return new Date(timestamp).getTime();
|
|
}
|
|
return Date.now();
|
|
},
|
|
|
|
// Centralized trail ID calculation
|
|
getTrailId(data: { callsign_group?: string; callsign?: string; id: string }): string {
|
|
return data.callsign_group || data.callsign || data.id;
|
|
},
|
|
|
|
// Centralized map state saving
|
|
saveMapState(map: LeafletMap, pushEvent: PushEventFunction) {
|
|
const center = map.getCenter();
|
|
const zoom = map.getZoom();
|
|
|
|
// Truncate lat/lng to 5 decimal places for URL
|
|
const truncatedLat = Math.round(center.lat * 100000) / 100000;
|
|
const truncatedLng = Math.round(center.lng * 100000) / 100000;
|
|
|
|
localStorage.setItem(
|
|
"aprs_map_state",
|
|
JSON.stringify({ lat: truncatedLat, lng: truncatedLng, zoom }),
|
|
);
|
|
|
|
// Send combined map state update to server for URL and bounds updating
|
|
pushEvent("update_map_state", {
|
|
center: { lat: truncatedLat, lng: truncatedLng },
|
|
zoom: zoom,
|
|
bounds: {
|
|
north: map.getBounds().getNorth(),
|
|
south: map.getBounds().getSouth(),
|
|
east: map.getBounds().getEast(),
|
|
west: map.getBounds().getWest(),
|
|
}
|
|
});
|
|
},
|
|
|
|
// Safe event pushing with connection check
|
|
safePushEvent(pushEvent: PushEventFunction | undefined, event: string, payload: BaseEventPayload) {
|
|
if (!pushEvent) return false;
|
|
|
|
try {
|
|
pushEvent(event, payload);
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 2. Improved LiveViewHookContext with proper cleanup tracking
|
|
interface ImprovedLiveViewHookContext extends LiveViewHookContext {
|
|
cleanupInterval?: ReturnType<typeof setInterval>;
|
|
mapEventHandlers?: Map<string, Function>;
|
|
isDestroyed?: boolean;
|
|
}
|
|
|
|
// 3. Example of fixed initialization with proper cleanup tracking
|
|
export const setupMapWithCleanup = (self: ImprovedLiveViewHookContext) => {
|
|
// Track if component is destroyed
|
|
self.isDestroyed = false;
|
|
|
|
// Store event handlers for cleanup
|
|
self.mapEventHandlers = new Map();
|
|
|
|
// Store interval for cleanup
|
|
self.cleanupInterval = setInterval(() => {
|
|
if (!self.isDestroyed && self.trailManager) {
|
|
self.trailManager.cleanupOldPositions();
|
|
}
|
|
}, 5 * 60 * 1000);
|
|
|
|
// Create wrapped event handlers that check if destroyed
|
|
const createSafeHandler = <TArgs extends unknown[]>(handler: (...args: TArgs) => void) => {
|
|
return (...args: TArgs) => {
|
|
if (!self.isDestroyed) {
|
|
handler(...args);
|
|
}
|
|
};
|
|
};
|
|
|
|
// Example of safe map event handler
|
|
const moveEndHandler = createSafeHandler(() => {
|
|
if (self.programmaticMoveCounter && self.programmaticMoveCounter > 0) {
|
|
self.programmaticMoveCounter--;
|
|
return;
|
|
}
|
|
|
|
if (self.boundsTimer) clearTimeout(self.boundsTimer);
|
|
self.boundsTimer = setTimeout(() => {
|
|
if (!self.isDestroyed) {
|
|
MapHelpers.saveMapState(self.map, self.pushEvent);
|
|
}
|
|
}, 300);
|
|
});
|
|
|
|
self.map.on("moveend", moveEndHandler);
|
|
self.mapEventHandlers.set("moveend", moveEndHandler);
|
|
};
|
|
|
|
// 4. Improved cleanup function
|
|
export const improvedDestroyed = (self: ImprovedLiveViewHookContext) => {
|
|
// Mark as destroyed immediately
|
|
self.isDestroyed = true;
|
|
|
|
// Disable pushEvent to prevent any events from being sent during cleanup
|
|
const originalPushEvent = self.pushEvent;
|
|
self.pushEvent = () => {}; // No-op function
|
|
|
|
// Clear interval timer
|
|
if (self.cleanupInterval !== undefined) {
|
|
clearInterval(self.cleanupInterval);
|
|
self.cleanupInterval = undefined;
|
|
}
|
|
|
|
// Remove popup navigation event listener
|
|
if (self.popupNavigationHandler) {
|
|
document.removeEventListener('click', self.popupNavigationHandler);
|
|
self.popupNavigationHandler = undefined;
|
|
}
|
|
|
|
// Close any open popups before cleanup
|
|
if (self.map !== undefined) {
|
|
try {
|
|
self.map.closePopup();
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
// Clear timers
|
|
if (self.boundsTimer !== undefined) {
|
|
clearTimeout(self.boundsTimer);
|
|
self.boundsTimer = undefined;
|
|
}
|
|
|
|
if (self.resizeHandler !== undefined) {
|
|
window.removeEventListener("resize", self.resizeHandler);
|
|
self.resizeHandler = undefined;
|
|
}
|
|
|
|
// Remove map event handlers
|
|
if (self.map !== undefined && self.mapEventHandlers !== undefined) {
|
|
self.mapEventHandlers.forEach((handler, event) => {
|
|
try {
|
|
self.map.off(event, handler);
|
|
} catch (e) {
|
|
}
|
|
});
|
|
self.mapEventHandlers.clear();
|
|
}
|
|
|
|
// Remove all event listeners from markers before clearing layers
|
|
if (self.markers !== undefined) {
|
|
self.markers.forEach((marker: APRSMarker, id: string) => {
|
|
try {
|
|
marker.off(); // Remove all event listeners
|
|
if (marker.getPopup()) {
|
|
marker.unbindPopup(); // Unbind popup to prevent events
|
|
}
|
|
} catch (e) {
|
|
}
|
|
});
|
|
}
|
|
|
|
// Clear layers and data
|
|
if (self.markerLayer !== undefined) {
|
|
try {
|
|
self.markerLayer!.clearLayers();
|
|
} catch (e) {
|
|
}
|
|
}
|
|
|
|
if (self.markers !== undefined) {
|
|
self.markers!.clear();
|
|
}
|
|
|
|
if (self.markerStates !== undefined) {
|
|
self.markerStates!.clear();
|
|
}
|
|
|
|
// Remove map
|
|
if (self.map !== undefined) {
|
|
try {
|
|
self.map!.remove();
|
|
} catch (e) {
|
|
}
|
|
self.map = undefined;
|
|
}
|
|
|
|
// Clear OMS if present
|
|
if (self.oms !== undefined) {
|
|
self.oms = undefined;
|
|
}
|
|
|
|
// Restore original pushEvent (though it won't be used since we're destroyed)
|
|
self.pushEvent = originalPushEvent;
|
|
};
|
|
|
|
// 5. Optimized marker position lookup using spatial index
|
|
export class MarkerSpatialIndex {
|
|
private grid: Map<string, Set<string>> = new Map();
|
|
private cellSize: number = 0.0001; // ~11 meters at equator
|
|
|
|
private getGridKey(lat: number, lng: number): string {
|
|
const latCell = Math.floor(lat / this.cellSize);
|
|
const lngCell = Math.floor(lng / this.cellSize);
|
|
return `${latCell},${lngCell}`;
|
|
}
|
|
|
|
add(id: string, lat: number, lng: number) {
|
|
const key = this.getGridKey(lat, lng);
|
|
if (!this.grid.has(key)) {
|
|
this.grid.set(key, new Set());
|
|
}
|
|
this.grid.get(key)!.add(id);
|
|
}
|
|
|
|
remove(id: string, lat: number, lng: number) {
|
|
const key = this.getGridKey(lat, lng);
|
|
const cell = this.grid.get(key);
|
|
if (cell) {
|
|
cell.delete(id);
|
|
if (cell.size === 0) {
|
|
this.grid.delete(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
findNearby(lat: number, lng: number, radius: number = 0.00001): Set<string> {
|
|
const results = new Set<string>();
|
|
const cellRadius = Math.ceil(radius / this.cellSize);
|
|
const centerLatCell = Math.floor(lat / this.cellSize);
|
|
const centerLngCell = Math.floor(lng / this.cellSize);
|
|
|
|
for (let latOffset = -cellRadius; latOffset <= cellRadius; latOffset++) {
|
|
for (let lngOffset = -cellRadius; lngOffset <= cellRadius; lngOffset++) {
|
|
const key = `${centerLatCell + latOffset},${centerLngCell + lngOffset}`;
|
|
const cell = this.grid.get(key);
|
|
if (cell) {
|
|
cell.forEach(id => results.add(id));
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
clear() {
|
|
this.grid.clear();
|
|
}
|
|
}
|
|
|
|
// 6. Improved programmatic move counter with proper state management
|
|
export class ProgrammaticMoveTracker {
|
|
private moveCount: number = 0;
|
|
private timeoutId?: ReturnType<typeof setTimeout>;
|
|
|
|
expectMoves(count: number) {
|
|
this.moveCount += count;
|
|
|
|
// Clear existing timeout
|
|
if (this.timeoutId) {
|
|
clearTimeout(this.timeoutId);
|
|
}
|
|
|
|
// Set safety timeout
|
|
this.timeoutId = setTimeout(() => {
|
|
if (this.moveCount > 0) {
|
|
this.moveCount = 0;
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
handleMove(): boolean {
|
|
if (this.moveCount > 0) {
|
|
this.moveCount--;
|
|
return true; // This was a programmatic move
|
|
}
|
|
return false; // This was a user-initiated move
|
|
}
|
|
|
|
reset() {
|
|
this.moveCount = 0;
|
|
if (this.timeoutId) {
|
|
clearTimeout(this.timeoutId);
|
|
this.timeoutId = undefined;
|
|
}
|
|
}
|
|
} |