fix(map): refresh overlay on reconnect and tab visibility change

When the tab was hidden or the LiveView socket dropped, the server-side
bounds/scores assigns reset while the hook's scoreGrid retained its old
viewport data. Tiles Leaflet created or regenerated outside that stale
extent painted blank, leaving the overlay visibly truncated.
This commit is contained in:
Graham McIntire 2026-04-18 13:23:34 -05:00
parent 738973456f
commit 53cc2c9121
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59

View file

@ -145,6 +145,7 @@ interface PropagationMapHook extends ViewHook {
selectedTime: string | null
forecastCache: Map<string, ScorePoint[]>
playbackTimer: ReturnType<typeof setInterval> | null
visibilityHandler: (() => void) | null
showDetailPanel(this: PropagationMapHook): void
hideDetailPanel(this: PropagationMapHook): void
@ -661,6 +662,7 @@ export const PropagationMap: Record<string, unknown> & {
startPlayback(this: PropagationMapHook): void
stopPlayback(this: PropagationMapHook): void
sendBounds(this: PropagationMapHook): void
reconnected(this: PropagationMapHook): void
destroyed(this: PropagationMapHook): void
} = {
mounted(this: PropagationMapHook) {
@ -975,6 +977,19 @@ export const PropagationMap: Record<string, unknown> & {
requestAnimationFrame(() => this.sendBounds())
// Re-check bounds after layout settles (sidebar, fonts, etc.)
setTimeout(() => { this.map.invalidateSize(); this.sendBounds() }, 500)
// When the tab is hidden, Leaflet may create or drop score tiles while the
// server-side assigns (bounds, scores) are gone. On return, the retained
// scoreGrid covers the old viewport and any freshly-created tiles outside
// it paint blank. Re-fetch bounds so update_scores repaints all tiles with
// fresh data. reconnected() handles the socket-drop case on top of this.
this.visibilityHandler = () => {
if (document.visibilityState === "visible") {
this.map.invalidateSize()
this.sendBounds()
}
}
document.addEventListener("visibilitychange", this.visibilityHandler)
this.map.on("moveend", () => {
this.sendBounds()
if (this.gridVisible) {
@ -1462,11 +1477,25 @@ export const PropagationMap: Record<string, unknown> & {
})
},
reconnected(this: PropagationMapHook) {
// Server assigns (bounds, scores) reset on reconnect. Re-send bounds so
// update_scores refreshes the client scoreGrid and repaints all tiles,
// fixing partial/blank overlay after the tab comes back.
if (this.map) {
this.map.invalidateSize()
this.sendBounds()
}
},
destroyed(this: PropagationMapHook) {
if (this.playbackTimer !== null) {
clearInterval(this.playbackTimer)
this.playbackTimer = null
}
if (this.visibilityHandler) {
document.removeEventListener("visibilitychange", this.visibilityHandler)
this.visibilityHandler = null
}
if (this.map) this.map.remove()
}
}