From 5053d69ade209c6b28ffb8d257c241c359cae4f7 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 9 Mar 2026 21:54:05 -0700 Subject: [PATCH] fix(rcal): map shows only events from visible calendar period Previously the map rendered ALL events regardless of which time period the calendar was displaying. Now markers, transit lines, and map bounds are filtered to the visible date range (day/week/month/season/year). The map auto-fits to the bounds of visible located events when zoom coupling is active. Co-Authored-By: Claude Opus 4.6 --- modules/rcal/components/folk-calendar-view.ts | 75 ++++++++++++++++--- 1 file changed, 66 insertions(+), 9 deletions(-) diff --git a/modules/rcal/components/folk-calendar-view.ts b/modules/rcal/components/folk-calendar-view.ts index b9960b0..832eb7f 100644 --- a/modules/rcal/components/folk-calendar-view.ts +++ b/modules/rcal/components/folk-calendar-view.ts @@ -310,16 +310,65 @@ class FolkCalendarView extends HTMLElement { return S_TO_ZOOM[this.getEffectiveSpatialIndex()]; } + /** Returns [start, end] epoch-ms bounds for the currently visible calendar period. */ + private getVisibleDateRange(): [number, number] { + const d = this.currentDate; + const y = d.getFullYear(), m = d.getMonth(), dt = d.getDate(); + switch (this.viewMode) { + case "day": + return [new Date(y, m, dt).getTime(), new Date(y, m, dt + 1).getTime()]; + case "week": { + const ws = new Date(y, m, dt - d.getDay()); + return [ws.getTime(), new Date(ws.getFullYear(), ws.getMonth(), ws.getDate() + 7).getTime()]; + } + case "month": + return [new Date(y, m, 1).getTime(), new Date(y, m + 1, 1).getTime()]; + case "season": { + const q = Math.floor(m / 3); + return [new Date(y, q * 3, 1).getTime(), new Date(y, q * 3 + 3, 1).getTime()]; + } + case "year": + return [new Date(y, 0, 1).getTime(), new Date(y + 1, 0, 1).getTime()]; + case "multi-year": { + const sy = y - 4; + return [new Date(sy, 0, 1).getTime(), new Date(sy + 9, 0, 1).getTime()]; + } + default: + return [new Date(y, m, 1).getTime(), new Date(y, m + 1, 1).getTime()]; + } + } + + /** Events with coordinates that fall within the visible calendar period. */ + private getVisibleLocatedEvents(): any[] { + const [start, end] = this.getVisibleDateRange(); + return this.events.filter(e => { + if (e.latitude == null || e.longitude == null) return false; + if (this.filteredSources.has(e.source_name)) return false; + const t = new Date(e.start_time).getTime(); + return t >= start && t < end; + }); + } + private syncMapToSpatial() { if (!this.leafletMap) return; - const zoom = this.getEffectiveLeafletZoom(); - const center = this.computeMapCenter(); - this.leafletMap.flyTo(center, zoom, { duration: 0.8 }); + const L = (window as any).L; + if (!L) return; + const located = this.getVisibleLocatedEvents(); + if (located.length === 0) { + // No events in view — fly to coupled zoom at default center + this.leafletMap.flyTo([52.52, 13.405], this.getEffectiveLeafletZoom(), { duration: 0.8 }); + return; + } + if (located.length === 1) { + this.leafletMap.flyTo([located[0].latitude, located[0].longitude], this.getEffectiveLeafletZoom(), { duration: 0.8 }); + return; + } + const bounds = L.latLngBounds(located.map((e: any) => [e.latitude, e.longitude])); + this.leafletMap.flyToBounds(bounds, { padding: [40, 40], maxZoom: 16, duration: 0.8 }); } private computeMapCenter(): [number, number] { - const located = this.events.filter(e => - e.latitude != null && e.longitude != null && !this.filteredSources.has(e.source_name)); + const located = this.getVisibleLocatedEvents(); if (located.length === 0) return [52.52, 13.405]; let sumLat = 0, sumLng = 0; for (const e of located) { sumLat += e.latitude; sumLng += e.longitude; } @@ -1868,8 +1917,7 @@ class FolkCalendarView extends HTMLElement { if (!L || !this.mapMarkerLayer) return; this.mapMarkerLayer.clearLayers(); - const located = this.events.filter(e => - e.latitude != null && e.longitude != null && !this.filteredSources.has(e.source_name)); + const located = this.getVisibleLocatedEvents(); for (const ev of located) { const marker = L.circleMarker([ev.latitude, ev.longitude], { @@ -1883,6 +1931,16 @@ class FolkCalendarView extends HTMLElement { `); this.mapMarkerLayer.addLayer(marker); } + + // Auto-fit map to visible event bounds + if (this.zoomCoupled && located.length > 0) { + if (located.length === 1) { + this.leafletMap?.flyTo([located[0].latitude, located[0].longitude], this.getEffectiveLeafletZoom(), { duration: 0.8 }); + } else { + const bounds = L.latLngBounds(located.map((e: any) => [e.latitude, e.longitude])); + this.leafletMap?.flyToBounds(bounds, { padding: [40, 40], maxZoom: 16, duration: 0.8 }); + } + } } private updateTransitLines() { @@ -1890,8 +1948,7 @@ class FolkCalendarView extends HTMLElement { if (!L || !this.transitLineLayer) return; this.transitLineLayer.clearLayers(); - const sorted = this.events - .filter(e => e.latitude != null && e.longitude != null && !this.filteredSources.has(e.source_name)) + const sorted = this.getVisibleLocatedEvents() .sort((a, b) => new Date(a.start_time).getTime() - new Date(b.start_time).getTime()); if (sorted.length < 2) return;