Pplr Data Maps: its verification hooks leave window when a view closes, so a closed view no longer keeps the whole app in memory

This commit is contained in:
Doxa Maps 2026-09-28 19:53:45 +00:00
parent 401b5d28d0
commit c9c2280241
8 changed files with 31 additions and 15 deletions

View file

@ -104,8 +104,10 @@ export function setProjectionFor(map, name) {
// Verification handle (the gospel-resources __grState contract): what the ACTIVE view's map
// actually did, so a check can assert "loaded" rather than inferring it from missing requests.
// window.__pplrMap() -> { engine, loaded, styleLayers, pplrLayers }
let _exposedMap = null // whose hook is on window, so releaseMap only clears its own
export function exposeMap(map) {
if (typeof window === 'undefined' || !map) return
_exposedMap = map
let loaded = false
map.once('load', () => { loaded = true })
window.__pplrMap = () => {
@ -123,3 +125,11 @@ export function exposeMap(map) {
}
}
}
// Release the hook on unmount (a closure on window pins the whole app after the view is gone; rega
// measured ~10 MB per mount). Only if it is still THIS map's: another view may have exposed its own since.
export function releaseMap(map) {
if (typeof window === 'undefined' || !map || _exposedMap !== map) return
delete window.__pplrMap
_exposedMap = null
}

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, releaseMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { formatCount } from '@map/utils/formatCount.js' // counts stay fixed en-US: numerals are held for the Driver
import { tStatic } from '@map/i18n/index.js'
@ -79,7 +79,7 @@ onMounted(async () => {
map.value.getSource(sourceId).setData(fc)
// Verification handle (the gospel-resources __grState contract): how many country
// polygons found a coverage row. A key mismatch paints every country the "no data" grey.
if (typeof window !== 'undefined') window.__pplrCoverage = () => ({
if (typeof window !== 'undefined') window.__pplrCoverage = _coverageHook = () => ({
polygons: fc.features.length,
matched: fc.features.filter(f => f.properties.avgFilled != null).length,
sampleUnmatched: fc.features.filter(f => f.properties.avgFilled == null).slice(0, 5).map(f => f.properties.GENCalpha3),
@ -136,7 +136,13 @@ onMounted(async () => {
}
})
})
onBeforeUnmount(() => destroy())
let _coverageHook = null
onBeforeUnmount(() => {
releaseMap(map.value)
// Release this view's own hook too (only if it is still ours).
if (typeof window !== 'undefined' && window.__pplrCoverage === _coverageHook) delete window.__pplrCoverage
destroy()
})
const SCALE = [
{ stop: '0', color: '#7f1d1d', label: '0 locales' },

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn, setProjectionFor } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, releaseMap, whenGeojsonDrawn, setProjectionFor } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { useOptionalT } from '@map/composables/useOptionalT.js'
import { tStatic } from '@map/i18n/index.js'
@ -515,7 +515,7 @@ onMounted(async () => {
}
})
})
onBeforeUnmount(() => { pinIndicator.destroy(); destroy() })
onBeforeUnmount(() => { releaseMap(map.value); pinIndicator.destroy(); destroy() })
const LF_CSS = POPUP_CSS + `
.pplr-popup h3 small { display: block; font-weight: 400; color: #6b7280; font-size: 11px; margin-top: 2px; }

View file

@ -5,7 +5,7 @@
* Individual pins at all zoom levels; click pin for full Rosetta popup; hover country for highlight.
*/
import { inject, onMounted, onBeforeUnmount, ref } from 'vue'
import { glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { glFor, exposeMap, releaseMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { formatCount } from '@map/utils/formatCount.js' // counts stay fixed en-US: numerals are held for the Driver
import { tStatic } from '@map/i18n/index.js'
@ -68,7 +68,7 @@ onMounted(async () => {
}
})
})
onBeforeUnmount(() => destroy())
onBeforeUnmount(() => { releaseMap(map.value); destroy() })
</script>
<template>

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn, setProjectionFor } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, releaseMap, whenGeojsonDrawn, setProjectionFor } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { religionFamilyLabel } from '@map/colors/religion.js'
import { tStatic } from '@map/i18n/index.js'
@ -296,7 +296,7 @@ onMounted(async () => {
}
})
})
onBeforeUnmount(() => { pinIndicator.destroy(); destroy() })
onBeforeUnmount(() => { releaseMap(map.value); pinIndicator.destroy(); destroy() })
const REL_CSS = POPUP_CSS + `
.pplr-popup h3 small { display: block; font-weight: 400; color: #6b7280; font-size: 11px; margin-top: 2px; }

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap, setProjectionFor } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, releaseMap, setProjectionFor } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { useOptionalT } from '@map/composables/useOptionalT.js'
import { tStatic } from '@map/i18n/index.js'
@ -482,7 +482,7 @@ onMounted(async () => {
}
})
})
onBeforeUnmount(() => { pinIndicator.destroy(); destroy() })
onBeforeUnmount(() => { releaseMap(map.value); pinIndicator.destroy(); destroy() })
const TREE_POPUP_CSS = POPUP_CSS + `
.pplr-popup h3 small { display: block; color: #6b7280; font-weight: 400; font-size: 11px; margin-top: 2px; }

View file

@ -13,7 +13,7 @@
* Click any pin → popup with full Rosetta table.
*/
import { inject, onMounted, onBeforeUnmount, ref, computed } from 'vue'
import { glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { glFor, exposeMap, releaseMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { tStatic } from '@map/i18n/index.js'
import { useMapInstance } from '@map/composables/useMapInstance.js'
@ -158,7 +158,7 @@ onMounted(async () => {
}
})
})
onBeforeUnmount(() => destroy())
onBeforeUnmount(() => { releaseMap(map.value); destroy() })
const LEGEND = computed(() => [
{ color: '#fbbf24', label: t('pplr.rosetta.legend.all') },

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, releaseMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { usePplrT } from '../composables/usePplrT.js'
import { tStatic } from '@map/i18n/index.js'
/**
@ -110,7 +110,7 @@ onMounted(async () => {
})
})
onBeforeUnmount(() => { pinIndicator.destroy(); destroy() })
onBeforeUnmount(() => { releaseMap(map.value); pinIndicator.destroy(); destroy() })
</script>
<template>