diff --git a/assets/js/features/trail_manager.ts b/assets/js/features/trail_manager.ts index 925d5b0..4810807 100644 --- a/assets/js/features/trail_manager.ts +++ b/assets/js/features/trail_manager.ts @@ -78,6 +78,24 @@ export class TrailManager { } } + setTrailDuration(durationHours: number) { + this.trailDuration = durationHours * 60 * 60 * 1000; // Convert hours to milliseconds + + // Clean up existing trails based on new duration + const cutoffTime = Date.now() - this.trailDuration; + + this.trails.forEach((trailState, baseCallsign) => { + // Filter positions based on new duration (skip historical dots) + trailState.positions = trailState.positions.filter((pos) => { + const posTimestamp = typeof pos.timestamp === "string" ? new Date(pos.timestamp).getTime() : pos.timestamp; + return posTimestamp >= cutoffTime; + }); + + // Update trail visualization + this.updateTrailVisualization(baseCallsign, trailState, true); + }); + } + addPosition( markerId: string, lat: number, diff --git a/assets/js/map.ts b/assets/js/map.ts index 73e1815..ea8d23a 100644 --- a/assets/js/map.ts +++ b/assets/js/map.ts @@ -663,6 +663,13 @@ let MapAPRSMap = { } }); + // Handle trail duration updates from LiveView + self.handleEvent("update_trail_duration", (data: { duration_hours: number }) => { + if (self.trailManager) { + self.trailManager.setTrailDuration(data.duration_hours); + } + }); + // Handle new packets from LiveView self.handleEvent("new_packet", (data: MarkerData) => { try { diff --git a/assets/js/map_helpers.ts b/assets/js/map_helpers.ts index 04b7232..1c690a0 100644 --- a/assets/js/map_helpers.ts +++ b/assets/js/map_helpers.ts @@ -35,7 +35,26 @@ export function parseTimestamp(timestamp: string | number | Date | undefined): n * Get trail ID from marker data */ export function getTrailId(data: { callsign_group?: string; callsign?: string; id: string }): string { - return data.callsign_group || data.callsign || data.id; + // Prioritize callsign_group and callsign over extracting from ID + if (data.callsign_group) { + return data.callsign_group; + } + + if (data.callsign) { + return data.callsign; + } + + // For historical markers like "hist_CALLSIGN_123", extract the base callsign + const id = String(data.id); + if (id.startsWith("hist_")) { + // Remove hist_ prefix and any trailing numeric index + const withoutPrefix = id.replace(/^hist_/, ""); + // Remove trailing _digits pattern + return withoutPrefix.replace(/_\d+$/, ""); + } + + // For regular IDs, return as-is + return data.id; } /** diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index d5e74e8..9fb3f9f 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -360,6 +360,9 @@ defmodule AprsmeWeb.MapLive.Index do socket = assign(socket, trail_duration: duration, packet_age_threshold: new_threshold) + # Update client-side TrailManager with new duration + socket = push_event(socket, "update_trail_duration", %{duration_hours: hours}) + # Trigger cleanup to remove packets that are now outside the new duration send(self(), :cleanup_old_packets) diff --git a/test/assets/js/map_helpers_test.ts b/test/assets/js/map_helpers_test.ts index 3eae93d..72e2a78 100644 --- a/test/assets/js/map_helpers_test.ts +++ b/test/assets/js/map_helpers_test.ts @@ -62,6 +62,36 @@ describe('map_helpers', () => { }; expect(getTrailId(data)).toBe('ID-1'); }); + + test('extracts base callsign from historical ID format', () => { + const data = { + id: 'hist_MYCALL_123' + }; + expect(getTrailId(data)).toBe('MYCALL'); + }); + + test('handles historical ID with complex callsign', () => { + const data = { + id: 'hist_KD8ABC-9_456' + }; + expect(getTrailId(data)).toBe('KD8ABC-9'); + }); + + test('prioritizes callsign_group over historical ID extraction', () => { + const data = { + callsign_group: 'GROUP-1', + id: 'hist_MYCALL_123' + }; + expect(getTrailId(data)).toBe('GROUP-1'); + }); + + test('prioritizes callsign over historical ID extraction', () => { + const data = { + callsign: 'REALCALL', + id: 'hist_MYCALL_123' + }; + expect(getTrailId(data)).toBe('REALCALL'); + }); }); describe('saveMapState', () => {