Merge branch 'main' of https://git.nuxtbase.app/doxa-life/doxa-maps into bundler-1/work
Some checks are pending
Deploy to GitHub Pages / build (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Blocked by required conditions

This commit is contained in:
Doxa Maps 2026-09-26 00:19:52 +00:00
commit b0cd3140bd
11 changed files with 70 additions and 35 deletions

View file

@ -63,6 +63,29 @@ export function ensureGeocoder(provider = 'mapbox') {
// `new mapboxgl.Popup` throws on the first pin click. Now the library's engineFor (one copy).
export { engineFor as glFor } from '@map/composables/useMapProvider.js'
// Resolves once every GeoJSON source on the map has LOADED and a frame has RENDERED after that,
// or after `timeoutMs` (a safety net, never a hang). The views flipped `dataReady` (which hides the
// LoadingGlobe) right after addSource(); the engine then still tiles tens of thousands of points in
// its worker, so the globe disappeared and the map sat EMPTY. Measured on the DX-life gallery: 0
// dots for 20 s, then 19,376 features from 30 s. Not 'idle': an animating map may never go idle.
export function whenGeojsonDrawn(map, timeoutMs = 45000) {
return new Promise((resolve) => {
if (!map) return resolve(false)
let done = false
const finish = (drawn) => { if (done) return; done = true; clearTimeout(timer); map.off('sourcedata', check); resolve(drawn) }
const timer = setTimeout(() => finish(false), timeoutMs)
const check = () => {
try {
const srcs = (map.getStyle() || {}).sources || {}
const ids = Object.keys(srcs).filter((k) => srcs[k].type === 'geojson')
if (ids.every((id) => map.isSourceLoaded(id))) { map.once('render', () => finish(true)); map.triggerRepaint() }
} catch (_) { /* style mid-swap: wait for the next sourcedata */ }
}
map.on('sourcedata', check)
check()
})
}
// 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 }

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
/**
* pplr-coverage.vue — Country choropleth: avg # of locales filled per PG in that country.
*
@ -118,6 +118,8 @@ onMounted(async () => {
.setLngLat(f.geometry.coordinates).setHTML(popupForPG(f.properties)).addTo(map.value)
})
status.value = `Ready · ${countryStats.size} countries colored by avg translation locales / 17 (all ${[...countryStats.values()].reduce((n, c) => n + c.pgCount, 0).toLocaleString('en-US')} PG-in-country rows)`
// keep the LoadingGlobe up until the pins are actually drawn, not merely added
await whenGeojsonDrawn(map.value)
dataReady.value = true
} catch (e) {
status.value = `Error: ${e.message}`

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
/**
* pplr-language-families.vue — PG pins colored by PPLR ethnolinguistic family.
*
@ -490,6 +490,8 @@ onMounted(async () => {
const coverage = Math.round(100 * matched / fc.features.length)
status.value = `Ready · ${fc.features.length.toLocaleString()} pins · ${coverage}% family coverage · top-25 = ${Math.round(100 * inTop25 / fc.features.length)}%`
// keep the LoadingGlobe up until the pins are actually drawn, not merely added
await whenGeojsonDrawn(map.value)
dataReady.value = true
} catch (err) {
status.value = `Error: ${err.message}`

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 } from '../utils/pplrData.js'
import { glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { useMapInstance } from '@map/composables/useMapInstance.js'
import { addCountryPolygons, addPGPoints, popupForPG, POPUP_CSS } from '../utils/pplrMap.js'
import LoadingGlobe from '../components/LoadingGlobe.vue'
@ -50,6 +50,8 @@ onMounted(async () => {
.addTo(map.value)
})
status.value = `Ready · ${fc.features.length.toLocaleString('en-US')} PG-in-country pins · click any pin`
// keep the LoadingGlobe up until the pins are actually drawn, not merely added
await whenGeojsonDrawn(map.value)
dataReady.value = true
} catch (e) {
status.value = `Error: ${e.message}`

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
/**
* pplr-religion.vue — PG pins colored by primary religion.
*
@ -267,6 +267,8 @@ onMounted(async () => {
const dominant = RELIGION_GROUPS.reduce((a, b) => (counts[a.key] || 0) > (counts[b.key] || 0) ? a : b)
// Per user 2026-05-02: drop the "Ready" prefix — it was unclear and chewed up width.
status.value = `${pgData.value.features.length.toLocaleString()} pins · ${dominant.label} most common (${(counts[dominant.key]||0).toLocaleString()})`
// keep the LoadingGlobe up until the pins are actually drawn, not merely added
await whenGeojsonDrawn(map.value)
dataReady.value = true
} catch (err) {
status.value = `Error: ${err.message}`

View file

@ -13,7 +13,7 @@
* Click any pin → popup with full Rosetta table.
*/
import { inject, onMounted, onBeforeUnmount, ref } from 'vue'
import { glFor, exposeMap } from '../utils/pplrData.js'
import { glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
import { useMapInstance } from '@map/composables/useMapInstance.js'
import { addCountryPolygons, loadOnce, popupForPG, POPUP_CSS } from '../utils/pplrMap.js'
import LoadingGlobe from '../components/LoadingGlobe.vue'
@ -141,6 +141,8 @@ onMounted(async () => {
map.value.on('mouseleave', 'rosetta-pin', () => map.value.getCanvas().style.cursor = '')
status.value = `Ready · ${pgFc.features.length} pins · ${rosKeys.size} Rosetta samples (gold ★)`
// keep the LoadingGlobe up until the pins are actually drawn, not merely added
await whenGeojsonDrawn(map.value)
dataReady.value = true
} catch (e) {
status.value = `Error: ${e.message}`

View file

@ -1,5 +1,5 @@
<script setup>
import { dataUrl, glFor, exposeMap } from '../utils/pplrData.js'
import { dataUrl, glFor, exposeMap, whenGeojsonDrawn } from '../utils/pplrData.js'
/**
* pplr-translations.vue — PG name in all 17 locales.
*
@ -92,6 +92,8 @@ onMounted(async () => {
})
status.value = `${fc.features.length.toLocaleString()} pins · click any pin to see all 17 locale names`
// keep the LoadingGlobe up until the pins are actually drawn, not merely added
await whenGeojsonDrawn(map.value)
dataReady.value = true
} catch (err) {
status.value = `Error: ${err.message}`

View file

@ -837,7 +837,7 @@
"bundle": "pplr-data-maps",
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"output": "dx-maps/pplr-data-maps/pplr-data-maps.js",
"integrity": "sha384-nMEYoH17y92xbERsRYP3x9sroR//lz8NvipOQvUfV8zV40pJ7DmW0avOm7sARD82",
"integrity": "sha384-e6wVt+dC86mFb8TgX2jBn7wh/R9gA6rGxNgS6umNBeNrir3TstupaJKf4XElRP2T",
"elements": [
"pplr-data-maps"
],
@ -853,7 +853,7 @@
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"embed": {
"iframe": "<iframe src=\"./pplr-data-maps/pplr-data-maps/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-nMEYoH17y92xbERsRYP3x9sroR//lz8NvipOQvUfV8zV40pJ7DmW0avOm7sARD82\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-e6wVt+dC86mFb8TgX2jBn7wh/R9gA6rGxNgS6umNBeNrir3TstupaJKf4XElRP2T\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
},
"bundle": "pplr-data-maps"
}

View file

@ -837,7 +837,7 @@
"bundle": "pplr-data-maps",
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"output": "dx-maps/pplr-data-maps/pplr-data-maps.js",
"integrity": "sha384-nMEYoH17y92xbERsRYP3x9sroR//lz8NvipOQvUfV8zV40pJ7DmW0avOm7sARD82",
"integrity": "sha384-e6wVt+dC86mFb8TgX2jBn7wh/R9gA6rGxNgS6umNBeNrir3TstupaJKf4XElRP2T",
"elements": [
"pplr-data-maps"
],
@ -853,7 +853,7 @@
"scriptUrl": "./pplr-data-maps/pplr-data-maps.js",
"embed": {
"iframe": "<iframe src=\"./pplr-data-maps/pplr-data-maps/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-nMEYoH17y92xbERsRYP3x9sroR//lz8NvipOQvUfV8zV40pJ7DmW0avOm7sARD82\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./pplr-data-maps/pplr-data-maps.js\" integrity=\"sha384-e6wVt+dC86mFb8TgX2jBn7wh/R9gA6rGxNgS6umNBeNrir3TstupaJKf4XElRP2T\" crossorigin=\"anonymous\"></script>\n<pplr-data-maps profile-config='{\"profile\":\"pplr-data-maps\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></pplr-data-maps>"
},
"bundle": "pplr-data-maps"
}

File diff suppressed because one or more lines are too long

View file

@ -30,7 +30,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <pplr-data-maps> custom element. -->
<script src="../pplr-data-maps.js" integrity="sha384-nMEYoH17y92xbERsRYP3x9sroR//lz8NvipOQvUfV8zV40pJ7DmW0avOm7sARD82" crossorigin="anonymous"></script>
<script src="../pplr-data-maps.js" integrity="sha384-e6wVt+dC86mFb8TgX2jBn7wh/R9gA6rGxNgS6umNBeNrir3TstupaJKf4XElRP2T" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: