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;