diff --git a/assets/js/features/weather_charts.ts b/assets/js/features/weather_charts.ts index f82ea43..33c6ab2 100644 --- a/assets/js/features/weather_charts.ts +++ b/assets/js/features/weather_charts.ts @@ -1,5 +1,7 @@ // Import Chart.js types import type { Chart, ChartConfiguration, ChartType } from 'chart.js'; +import type { WeatherChartDataset, YAxisOptions } from '../types/chart-types'; +import type { HandleEventFunction } from '../types/events'; // Declare global Chart object from CDN declare global { @@ -15,7 +17,7 @@ interface Hook { updated?: () => void; destroyed?: () => void; el: HTMLElement; - handleEvent: (event: string, handler: (payload: any) => void) => void; + handleEvent: HandleEventFunction; } // Define chart hook context type @@ -106,10 +108,10 @@ const getLabels = (el: HTMLElement | null): Record => { // Chart configurations interface ChartConfig { type: ChartType; - datasets: (data: WeatherHistoryDatum[], labels: Record) => any[]; + datasets: (data: WeatherHistoryDatum[], labels: Record) => WeatherChartDataset[]; title: (labels: Record) => string; yAxisLabel?: (labels: Record) => string; - yAxisOptions?: any; + yAxisOptions?: YAxisOptions; } const chartConfigs: Record = { diff --git a/assets/js/map.ts b/assets/js/map.ts index a787b8b..4ff7a49 100644 --- a/assets/js/map.ts +++ b/assets/js/map.ts @@ -7,14 +7,20 @@ import type { MarkerState, MapEventData } from './types/map'; -import type { Map as LeafletMap, Marker, TileLayer, LayerGroup, DivIcon, LatLngBounds } from 'leaflet'; +import type { Map as LeafletMap, Marker, TileLayer, LayerGroup, DivIcon, LatLngBounds, Polyline } from 'leaflet'; +import type { HeatLayer, MarkerClusterGroup, OverlappingMarkerSpiderfier, MarkerClusterGroupOptions, HeatLayerOptions, MarkerCluster, HeatLatLng } from './types/leaflet-plugins'; +import type { APRSMarker } from './types/marker-extensions'; +import type { BaseEventPayload } from './types/events'; // Declare Leaflet as a global variable with proper typing declare const L: typeof import('leaflet') & { - heatLayer?: (latlngs: Array<[number, number, number?]>, options?: any) => any; - markerClusterGroup?: (options?: any) => any; + heatLayer?: (latlngs: HeatLatLng[], options?: HeatLayerOptions) => HeatLayer; + markerClusterGroup?: (options?: MarkerClusterGroupOptions) => MarkerClusterGroup; }; -declare const OverlappingMarkerSpiderfier: any; + +// Import OverlappingMarkerSpiderfier constructor +import { OverlappingMarkerSpiderfier as OMSConstructor } from './types/leaflet-plugins'; +declare const OverlappingMarkerSpiderfier: typeof OMSConstructor; // Import trail management functionality import { TrailManager } from "./features/trail_manager"; @@ -207,7 +213,7 @@ let MapAPRSMap = { } // Store markers for management - self.markers = new Map(); + self.markers = new Map(); // Create marker cluster group for medium zoom levels (9-14) if (L.markerClusterGroup) { @@ -218,7 +224,7 @@ let MapAPRSMap = { removeOutsideVisibleBounds: true, disableClusteringAtZoom: 11, // Show individual markers at zoom 11+ maxClusterRadius: 80, - iconCreateFunction: function(cluster: any) { + iconCreateFunction: function(cluster: MarkerCluster) { const count = cluster.getChildCount(); let size = 'small'; let className = 'marker-cluster-small'; @@ -300,7 +306,7 @@ let MapAPRSMap = { setTimeout(() => { if (self.map && self.pushEvent && !self.isDestroyed) { console.log("Sending initial update_map_state for historical loading"); - saveMapState(self.map, (event: string, payload: any) => self.pushEvent(event, payload)); + saveMapState(self.map, (event: string, payload: BaseEventPayload) => self.pushEvent(event, payload)); } }, 500); } else { @@ -314,7 +320,7 @@ let MapAPRSMap = { setTimeout(() => { if (self.map && self.pushEvent && !self.isDestroyed) { console.log("Sending initial update_map_state for historical loading (retry path)"); - saveMapState(self.map, (event: string, payload: any) => self.pushEvent(event, payload)); + saveMapState(self.map, (event: string, payload: BaseEventPayload) => self.pushEvent(event, payload)); } }, 500); } @@ -441,16 +447,16 @@ let MapAPRSMap = { }); // Add click handler for spiderfied markers - self.oms.addListener('click', function(marker: any) { + self.oms.addListener('click', function(marker: Marker) { if (marker.openPopup) marker.openPopup(); }); // Style the spider legs - self.oms.addListener('spiderfy', function(markers: any) { + self.oms.addListener('spiderfy', function(markers: Marker[]) { self.map.closePopup(); }); - self.oms.addListener('unspiderfy', function(markers: any) { + self.oms.addListener('unspiderfy', function(markers: Marker[]) { // Markers return to normal positions }); } @@ -726,7 +732,7 @@ let MapAPRSMap = { existingMarker.setIcon(self.createMarkerIcon(historicalIconData)); // Mark as historical - (existingMarker as any)._isHistorical = true; + (existingMarker as APRSMarker)._isHistorical = true; } }); } @@ -870,10 +876,10 @@ let MapAPRSMap = { console.log("Clearing historical packets"); // Remove only historical markers (preserve live markers and their trails) const markersToRemove: string[] = []; - self.markers!.forEach((marker: any, id: any) => { + self.markers!.forEach((marker: APRSMarker, id: string) => { const markerState = self.markerStates!.get(String(id)); // Only remove markers that are explicitly historical - if ((marker as any)._isHistorical || (markerState && markerState.historical)) { + if ((marker as APRSMarker)._isHistorical || (markerState && markerState.historical)) { markersToRemove.push(String(id)); } }); @@ -999,7 +1005,7 @@ let MapAPRSMap = { }); // Handle heat map data for low zoom levels - self.handleEvent("show_heat_map", (data: { heat_points: Array<{lat: number, lng: number, intensity: number}> }) => { + self.handleEvent("show_heat_map", (data: { heat_points: HeatLatLng[] }) => { try { console.log("Received heat map data with", data.heat_points?.length || 0, "points"); @@ -1272,7 +1278,7 @@ let MapAPRSMap = { // Mark historical markers for identification if (data.historical) { - (marker as any)._isHistorical = true; + (marker as APRSMarker)._isHistorical = true; } // Add to map and store reference @@ -1320,7 +1326,7 @@ let MapAPRSMap = { } }, - removeMarker(id: string | any) { + removeMarker(id: string) { const self = this as unknown as LiveViewHookContext; // Ensure id is a string const markerId = typeof id === "string" ? id : String(id); @@ -1400,7 +1406,7 @@ let MapAPRSMap = { // Identify historical markers and most recent markers to preserve self.markers!.forEach((marker, id) => { const markerState = self.markerStates!.get(String(id)); - const isHistorical = (marker as any)._isHistorical || (markerState && markerState.historical); + const isHistorical = (marker as APRSMarker)._isHistorical || (markerState && markerState.historical); const isMostRecent = markerState && markerState.is_most_recent_for_callsign; // Keep historical markers and current position markers @@ -1447,7 +1453,7 @@ let MapAPRSMap = { self.markers!.forEach((marker: L.Marker, id: string) => { // Check if this is a historical marker or the most recent marker for a callsign const markerState = self.markerStates!.get(String(id)); - const isHistorical = (marker as any)._isHistorical || (markerState && markerState.historical); + const isHistorical = (marker as APRSMarker)._isHistorical || (markerState && markerState.historical); const isMostRecent = markerState && markerState.is_most_recent_for_callsign; // Always preserve historical markers and the most recent marker for a callsign @@ -1482,7 +1488,7 @@ let MapAPRSMap = { markersToRemove.forEach((id) => self.removeMarkerWithoutTrail(String(id))); }, - removeMarkerWithoutTrail(id: string | any) { + removeMarkerWithoutTrail(id: string) { const self = this as unknown as LiveViewHookContext; // Ensure id is a string const markerId = typeof id === "string" ? id : String(id); @@ -1504,7 +1510,7 @@ let MapAPRSMap = { if (data.is_most_recent_for_callsign) { // Remove any historical dot at the same position for this callsign if (self.markers && self.markerStates) { - self.markers.forEach((marker: any, id: string) => { + self.markers.forEach((marker: APRSMarker, id: string) => { const state = self.markerStates!.get(String(id)); if ( state && @@ -1667,7 +1673,7 @@ let MapAPRSMap = { // Remove all event listeners from markers before clearing layers if (self.markers !== undefined) { - self.markers.forEach((marker: any) => { + self.markers.forEach((marker: APRSMarker) => { try { marker.off(); // Remove all event listeners if (marker.getPopup()) { diff --git a/assets/js/map_fixes.ts b/assets/js/map_fixes.ts index 1d1bea6..acfa40e 100644 --- a/assets/js/map_fixes.ts +++ b/assets/js/map_fixes.ts @@ -1,9 +1,14 @@ // 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: any): number { + parseTimestamp(timestamp: string | number | undefined | null): number { if (!timestamp) return Date.now(); if (typeof timestamp === "number") { @@ -20,7 +25,7 @@ export const MapHelpers = { }, // Centralized map state saving - saveMapState(map: any, pushEvent: Function) { + saveMapState(map: LeafletMap, pushEvent: PushEventFunction) { const center = map.getCenter(); const zoom = map.getZoom(); @@ -47,7 +52,7 @@ export const MapHelpers = { }, // Safe event pushing with connection check - safePushEvent(pushEvent: Function | undefined, event: string, payload: any) { + safePushEvent(pushEvent: PushEventFunction | undefined, event: string, payload: BaseEventPayload) { if (!pushEvent) return false; try { @@ -83,8 +88,8 @@ export const setupMapWithCleanup = (self: ImprovedLiveViewHookContext) => { }, 5 * 60 * 1000); // Create wrapped event handlers that check if destroyed - const createSafeHandler = (handler: Function) => { - return (...args: any[]) => { + const createSafeHandler = (handler: (...args: TArgs) => void) => { + return (...args: TArgs) => { if (!self.isDestroyed) { handler(...args); } @@ -165,7 +170,7 @@ export const improvedDestroyed = (self: ImprovedLiveViewHookContext) => { // Remove all event listeners from markers before clearing layers if (self.markers !== undefined) { - self.markers.forEach((marker: any, id: string) => { + self.markers.forEach((marker: APRSMarker, id: string) => { try { marker.off(); // Remove all event listeners if (marker.getPopup()) { diff --git a/assets/js/map_helpers.ts b/assets/js/map_helpers.ts index 9fd1951..6aeffb5 100644 --- a/assets/js/map_helpers.ts +++ b/assets/js/map_helpers.ts @@ -1,6 +1,8 @@ // Helper functions for map.ts to reduce code duplication import type * as L from 'leaflet'; +import type { BaseEventPayload, PushEventFunction } from './types/events'; +import type { LiveSocket } from './types/map'; export interface MapState { lat: number; @@ -39,8 +41,7 @@ export function getTrailId(data: { callsign_group?: string; callsign?: string; i /** * Save map state to localStorage and send to server */ -// Define the pushEvent function type -type PushEventFunction = (event: string, payload: Record) => void; +// PushEventFunction is now imported from types/events export function saveMapState(map: L.Map, pushEvent: PushEventFunction) { if (!map || !pushEvent) { @@ -89,7 +90,7 @@ export function saveMapState(map: L.Map, pushEvent: PushEventFunction) { /** * Safely push event to LiveView */ -export function safePushEvent(pushEvent: PushEventFunction | undefined, event: string, payload: Record): boolean { +export function safePushEvent(pushEvent: PushEventFunction | undefined, event: string, payload: T): boolean { if (!pushEvent || typeof pushEvent !== 'function') { console.debug(`pushEvent not available for ${event} event`); return false; @@ -108,16 +109,12 @@ export function safePushEvent(pushEvent: PushEventFunction | undefined, event: s * Check if LiveView socket is available */ export function isLiveViewConnected(): boolean { - return typeof window !== 'undefined' && !!(window as any).liveSocket; + return typeof window !== 'undefined' && !!window.liveSocket; } /** * Get LiveView socket */ -interface LiveSocket { - pushHistoryPatch(href: string, type: string, target: HTMLElement): void; -} - export function getLiveSocket(): LiveSocket | null { - return typeof window !== 'undefined' ? (window as any).liveSocket : null; + return typeof window !== 'undefined' ? window.liveSocket || null : null; } \ No newline at end of file diff --git a/assets/js/types/chart-types.d.ts b/assets/js/types/chart-types.d.ts new file mode 100644 index 0000000..cd21e47 --- /dev/null +++ b/assets/js/types/chart-types.d.ts @@ -0,0 +1,29 @@ +// Type definitions for Chart.js configurations used in APRS.me + +import type { ChartType, ChartDataset, ScaleOptions } from 'chart.js'; + +// Chart dataset types +export interface LineDataset extends ChartDataset<'line'> { + label: string; + data: (number | null | undefined)[]; + borderColor: string; + backgroundColor: string; + tension: number; + pointRadius: number; +} + +export interface BarDataset extends ChartDataset<'bar'> { + label: string; + data: (number | null | undefined)[]; + backgroundColor: string; + borderColor: string; + borderWidth: number; +} + +export type WeatherChartDataset = LineDataset | BarDataset; + +// Y-axis options +export interface YAxisOptions extends Partial> { + min?: number; + max?: number; +} \ No newline at end of file diff --git a/assets/js/types/events.d.ts b/assets/js/types/events.d.ts new file mode 100644 index 0000000..68b4d95 --- /dev/null +++ b/assets/js/types/events.d.ts @@ -0,0 +1,113 @@ +// Type definitions for event payloads used in APRS.me + +import type { BoundsData, CenterData, MarkerData, HeatMapPoint } from './map'; + +// Base event payload +export interface BaseEventPayload { + [key: string]: unknown; +} + +// Map navigation events +export interface MapNavigationPayload extends BaseEventPayload { + lat: number; + lng: number; + zoom?: number; +} + +// Marker interaction events +export interface MarkerClickPayload extends BaseEventPayload { + id: string; + lat: number; + lng: number; +} + +export interface MarkerPopupPayload extends BaseEventPayload { + id: string; +} + +// Map state events +export interface MapStatePayload extends BaseEventPayload { + lat: number; + lng: number; + zoom: number; +} + +export interface MapBoundsPayload extends BaseEventPayload { + bounds: BoundsData; +} + +// Data update events +export interface UpdateMarkersPayload extends BaseEventPayload { + markers: MarkerData[]; +} + +export interface UpdateHeatmapPayload extends BaseEventPayload { + heat_points: HeatMapPoint[]; +} + +export interface RemoveMarkerPayload extends BaseEventPayload { + id: string; +} + +export interface ClearMarkersPayload extends BaseEventPayload { + // No additional fields needed +} + +// RF path events +export interface RFPathPayload extends BaseEventPayload { + from_lat: number; + from_lng: number; + to_lat: number; + to_lng: number; + packet_count: number; + last_heard: string; +} + +export interface ClearRFPathsPayload extends BaseEventPayload { + // No additional fields needed +} + +// Trail events +export interface UpdateTrailPayload extends BaseEventPayload { + callsign: string; + positions: Array<{ + lat: number; + lng: number; + timestamp: string | number; + }>; +} + +export interface RemoveTrailPayload extends BaseEventPayload { + callsign: string; +} + +// Weather data events +export interface WeatherDataPayload extends BaseEventPayload { + callsign: string; + data: Array<{ + timestamp: string; + temperature?: number; + humidity?: number; + pressure?: number; + wind_speed?: number; + wind_direction?: number; + rain_1h?: number; + rain_24h?: number; + rain_midnight?: number; + }>; +} + +// Event handler callback types +export type EventCallback = (data: T) => void; + +// Push event function type +export type PushEventFunction = ( + event: string, + payload: T +) => void; + +// Handle event function type +export type HandleEventFunction = ( + event: string, + callback: EventCallback +) => void; \ No newline at end of file diff --git a/assets/js/types/leaflet-events.d.ts b/assets/js/types/leaflet-events.d.ts new file mode 100644 index 0000000..76d1dfb --- /dev/null +++ b/assets/js/types/leaflet-events.d.ts @@ -0,0 +1,43 @@ +// Type definitions for Leaflet event objects + +import type { LatLng, Point, LatLngBounds } from 'leaflet'; + +export interface LeafletEvent { + type: string; + target: any; +} + +export interface LeafletMouseEvent extends LeafletEvent { + latlng: LatLng; + layerPoint: Point; + containerPoint: Point; + originalEvent: MouseEvent; +} + +export interface LeafletLocationEvent extends LeafletEvent { + latlng: LatLng; + bounds: LatLngBounds; + accuracy: number; + altitude: number; + altitudeAccuracy: number; + heading: number; + speed: number; + timestamp: number; +} + +export interface LeafletResizeEvent extends LeafletEvent { + oldSize: Point; + newSize: Point; +} + +export interface LeafletLayerEvent extends LeafletEvent { + layer: L.Layer; +} + +export interface LeafletZoomAnimEvent extends LeafletEvent { + center: LatLng; + zoom: number; + noUpdate: boolean; +} + +export type LeafletEventHandlerFn = (event: LeafletEvent) => void; \ No newline at end of file diff --git a/assets/js/types/leaflet-plugins.d.ts b/assets/js/types/leaflet-plugins.d.ts new file mode 100644 index 0000000..2c8dbb2 --- /dev/null +++ b/assets/js/types/leaflet-plugins.d.ts @@ -0,0 +1,110 @@ +// Type definitions for Leaflet plugins used in APRS.me + +import type { LatLng, Layer, LayerOptions, Map, Marker } from 'leaflet'; + +// Leaflet.heat plugin types +export interface HeatLatLng extends Array { + 0: number; // latitude + 1: number; // longitude + 2?: number; // intensity +} + +export interface HeatLayerOptions { + minOpacity?: number; + maxZoom?: number; + max?: number; + radius?: number; + blur?: number; + gradient?: Record; +} + +export interface HeatLayer extends Layer { + setLatLngs(latlngs: HeatLatLng[]): this; + addLatLng(latlng: HeatLatLng): this; + setOptions(options: HeatLayerOptions): this; + redraw(): this; +} + +// Leaflet.markercluster plugin types +export interface MarkerClusterGroupOptions extends LayerOptions { + maxClusterRadius?: number | ((zoom: number) => number); + iconCreateFunction?: (cluster: MarkerCluster) => L.Icon | L.DivIcon; + clusterPane?: string; + spiderfyOnMaxZoom?: boolean; + showCoverageOnHover?: boolean; + zoomToBoundsOnClick?: boolean; + singleMarkerMode?: boolean; + disableClusteringAtZoom?: number; + removeOutsideVisibleBounds?: boolean; + animate?: boolean; + animateAddingMarkers?: boolean; + spiderfyDistanceMultiplier?: number; + spiderLegPolylineOptions?: L.PolylineOptions; + chunkedLoading?: boolean; + chunkInterval?: number; + chunkDelay?: number; + chunkProgress?: (processed: number, total: number, elapsed: number) => void; +} + +export interface MarkerCluster extends Marker { + getChildCount(): number; + getAllChildMarkers(): Marker[]; + spiderfy(): void; + unspiderfy(): void; +} + +export interface MarkerClusterGroup extends Layer { + addLayer(layer: Layer): this; + removeLayer(layer: Layer): this; + clearLayers(): this; + getVisibleParent(marker: Marker): Marker | MarkerCluster | null; + refreshClusters(layerOrLayers?: Layer | Layer[]): this; + getLayer(id: number): Layer | undefined; + getLayers(): Layer[]; + hasLayer(layer: Layer): boolean; + zoomToShowLayer(layer: Layer, callback?: () => void): void; +} + +// OverlappingMarkerSpiderfier types +export interface OMSOptions { + keepSpiderfied?: boolean; + nearbyDistance?: number; + circleSpiralSwitchover?: number; + circleFootSeparation?: number; + spiralFootSeparation?: number; + spiralLengthStart?: number; + spiralLengthFactor?: number; + legWeight?: number; + legColors?: { + usual?: string; + highlighted?: string; + }; +} + +export interface OverlappingMarkerSpiderfierStatic { + new(map: Map, options?: OMSOptions): OverlappingMarkerSpiderfier; +} + +export interface OverlappingMarkerSpiderfier { + addMarker(marker: Marker): void; + removeMarker(marker: Marker): void; + getMarkers(): Marker[]; + clearMarkers(): void; + addListener(event: 'click', handler: (marker: Marker) => void): void; + addListener(event: 'spiderfy', handler: (markers: Marker[]) => void): void; + addListener(event: 'unspiderfy', handler: (markers: Marker[]) => void): void; + removeListener(event: string, handler: Function): void; + clearListeners(event?: string): void; + unspiderfy(): void; +} + +// Extend the global L namespace +declare global { + namespace L { + function heatLayer(latlngs: HeatLatLng[], options?: HeatLayerOptions): HeatLayer; + function markerClusterGroup(options?: MarkerClusterGroupOptions): MarkerClusterGroup; + } +} + +// Export the constructor type for OverlappingMarkerSpiderfier +export const OverlappingMarkerSpiderfier: OverlappingMarkerSpiderfierStatic; \ No newline at end of file diff --git a/assets/js/types/leaflet.d.ts b/assets/js/types/leaflet.d.ts index 94c3e86..1febd5a 100644 --- a/assets/js/types/leaflet.d.ts +++ b/assets/js/types/leaflet.d.ts @@ -8,7 +8,7 @@ declare namespace L { remove(): void; invalidateSize(options?: { animate?: boolean; pan?: boolean }): this; whenReady(callback: () => void): this; - on(event: string, handler: (e: any) => void): this; + on(event: string, handler: (e: import('./leaflet-events').LeafletEvent) => void): this; } interface MapOptions { diff --git a/assets/js/types/map.d.ts b/assets/js/types/map.d.ts index edc2381..a6e2eab 100644 --- a/assets/js/types/map.d.ts +++ b/assets/js/types/map.d.ts @@ -1,17 +1,20 @@ // Type definitions for APRS Map application -import type { Map as LeafletMap, Marker, LatLng, LatLngBounds, DivIcon, LayerGroup, Popup } from 'leaflet'; +import type { Map as LeafletMap, Marker, LatLng, LatLngBounds, DivIcon, LayerGroup, Popup, Polyline } from 'leaflet'; +import type { HeatLayer, MarkerClusterGroup, OverlappingMarkerSpiderfier } from './leaflet-plugins'; +import type { PushEventFunction, HandleEventFunction } from './events'; +import type { TrailManager } from '../features/trail_manager'; export interface LiveViewHookContext { - el: HTMLElement & { _leaflet_id?: any }; - pushEvent: (event: string, payload: any) => void; - handleEvent: (event: string, callback: (data: any) => void) => void; + el: HTMLElement & { _leaflet_id?: number }; + pushEvent: PushEventFunction; + handleEvent: HandleEventFunction; map?: LeafletMap; markers?: Map; markerStates?: Map; markerLayer?: LayerGroup; - heatLayer?: any; // L.heatLayer type - trailManager?: any; // Import from trail_manager.ts when typed + heatLayer?: HeatLayer; + trailManager?: TrailManager; boundsTimer?: ReturnType; resizeHandler?: () => void; errors?: string[]; @@ -19,7 +22,7 @@ export interface LiveViewHookContext { maxInitializationAttempts?: number; lastZoom?: number; currentPopupMarkerId?: string | null; - oms?: any; // OverlappingMarkerSpiderfier type + oms?: OverlappingMarkerSpiderfier; programmaticMoveId?: string; programmaticMoveTimeout?: ReturnType; cleanupInterval?: ReturnType; @@ -28,10 +31,9 @@ export interface LiveViewHookContext { popupNavigationHandler?: (e: Event) => void; moveEndHandler?: () => void; zoomEndHandler?: () => void; - rfPathLines?: any[]; // Array of Leaflet polylines and markers for RF path visualization + rfPathLines?: Array; // Array of Leaflet polylines and markers for RF path visualization trailLayer?: LayerGroup; - markerClusterGroup?: any; - [key: string]: any; + markerClusterGroup?: MarkerClusterGroup; } export interface MarkerData { diff --git a/assets/js/types/marker-extensions.d.ts b/assets/js/types/marker-extensions.d.ts new file mode 100644 index 0000000..463ff04 --- /dev/null +++ b/assets/js/types/marker-extensions.d.ts @@ -0,0 +1,9 @@ +// Type definitions for extended marker properties used in APRS.me + +import type { Marker } from 'leaflet'; + +// Extended marker interface with APRS-specific properties +export interface APRSMarker extends Marker { + _isHistorical?: boolean; + _markerId?: string; +} \ No newline at end of file