Three maps asked the window which engine drew them; now they ask the map
C3, buckets A and B. Each site was sniffing `window.maplibregl.Map` directly,
which breaks the moment a leaf stops loading a static engine peer: the global is
simply absent and the sniff answers about whatever else the page happened to load.
A needed a helper that did not exist. Both A sites want a provider NAME from a
live map. engineFor() returns the engine NAMESPACE, and resolveMapProvider()
reads a CONFIG rather than a map, so on a dual-engine page it answers about the
wrong thing. Hence providerFor(map) -> 'mapbox' | 'maplibre' | undefined, with two
named consumers on arrival.
★ WHY providerFor RETURNS undefined INSTEAD OF A DEFAULT. The obvious rewrite was
in terms of the existing isMapboxMap(). It is wrong in a way no per-engine test
catches: both originals test for MAPLIBRE and fall through to mapbox/false, so an
isMapboxMap() version inverts the fall-through to maplibre/true. On a stub, a
destroyed map, or a page where neither global loaded, that changes what a
diagnostic field reports AND which attribution a poster prints -- while agreeing
on both real engines. Returning undefined forces each call site to keep its own
default visible:
pplrData.js engine: providerFor(map) || 'mapbox'
research-map.vue const onMaplibre = providerFor(map.value) === 'maplibre'
Changing a default is not a curation migration.
B is engineGlobal(mapProvider), which already defaults to Mapbox for anything
unrecognised -- exactly what the ternary's else branch did.
All three files now carry zero raw window.mapboxgl|maplibregl references in code.
Verified by _qa-cycle/02-builder-cycle/_chk-c3-provider-helper-equivalence.mjs:
27 assertions, each old pattern against its replacement on both engines and on
{}, null and undefined, plus an executable proof of the isMapboxMap near-miss and
three negative controls.
This commit is contained in:
parent
50a29b463d
commit
5d82899d18
4 changed files with 42 additions and 6 deletions
|
|
@ -106,6 +106,36 @@ export function isMapboxMap(map) {
|
|||
* @param {object} map the live map instance
|
||||
* @returns {object|undefined} the engine namespace, or undefined when neither is loaded
|
||||
*/
|
||||
/**
|
||||
* The provider NAME that built THIS map: 'mapbox', 'maplibre', or undefined when neither global is
|
||||
* loaded or `map` is not a map at all.
|
||||
*
|
||||
* ★ WHY THIS EXISTS, given engineFor() is right above it. engineFor returns the engine NAMESPACE,
|
||||
* which is what you want for constructing things (Marker, Popup, a second Map). Two call sites want
|
||||
* the opposite: a NAME, to put in a diagnostic field and to choose an attribution string. They were
|
||||
* each doing their own `map instanceof window.maplibregl.Map` sniff, which is the raw-global coupling
|
||||
* C3 exists to remove — and `resolveMapProvider()` cannot serve them because it reads a CONFIG, not a
|
||||
* live map, so on a dual-engine page it answers about the wrong thing.
|
||||
*
|
||||
* ★ THE AMBIGUOUS CASE IS THE WHOLE REASON THIS RETURNS undefined RATHER THAN A DEFAULT. Both call
|
||||
* sites tested for MAPLIBRE and fell through to 'mapbox', so a stub, a destroyed map or a page where
|
||||
* neither global loaded read as 'mapbox'. A rewrite in terms of the existing isMapboxMap() would have
|
||||
* inverted that — falling through to 'maplibre' — and silently changed what a diagnostic reports and
|
||||
* which attribution a poster prints. Returning undefined forces each caller to keep its OWN default
|
||||
* visible at the call site, so the migration cannot quietly move a default. Changing a default is not
|
||||
* a curation migration.
|
||||
*
|
||||
* @param {object} map the live map instance
|
||||
* @returns {'mapbox'|'maplibre'|undefined}
|
||||
*/
|
||||
export function providerFor(map) {
|
||||
if (typeof window === 'undefined') return undefined
|
||||
const ml = window.maplibregl, mb = window.mapboxgl
|
||||
if (ml && ml.Map && map instanceof ml.Map) return 'maplibre'
|
||||
if (mb && mb.Map && map instanceof mb.Map) return 'mapbox'
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function engineFor(map) {
|
||||
if (typeof window === 'undefined') return undefined
|
||||
const ml = window.maplibregl, mb = window.mapboxgl
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
*/
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useMapInstance, engineGlobal } from '@map/composables/useMapInstance.js'
|
||||
import { resolveMapProvider, setProjectionOn } from '@map/composables/useMapProvider.js'
|
||||
import { useMapData } from '@map/composables/useMapData.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -295,7 +295,9 @@ function addLayer(fc) {
|
|||
const pray = doxaRec && doxaRec.slug
|
||||
? `<a href="https://pray.doxa.life/${encodeURIComponent(doxaRec.slug)}?source=doxalife&utm_source=${utmSource()}" target="_blank" rel="noopener">${esc(tr('pray'))}</a>`
|
||||
: ''
|
||||
const NS = mapProvider === 'maplibre' ? window.maplibregl : window.mapboxgl
|
||||
// C3: engineGlobal() resolves a provider NAME to its namespace and already defaults to Mapbox
|
||||
// for anything unrecognised, which is exactly what this ternary's else branch did.
|
||||
const NS = engineGlobal(mapProvider)
|
||||
new NS.Popup({ closeButton: true, maxWidth: '290px' })
|
||||
.setLngLat(e.lngLat)
|
||||
.setHTML(`<div class="apg-pop">
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import { retryableAsyncComponent } from '@map/composables/retryableAsyncComponen
|
|||
|
||||
// ─── Composables (framework) ─────────────────────────────────────────────────
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { resolveMapProvider, providerLabel, setProjectionOn } from '@map/composables/useMapProvider.js'
|
||||
import { resolveMapProvider, providerLabel, providerFor, setProjectionOn } from '@map/composables/useMapProvider.js'
|
||||
import { useMapLayers, PIN_STROKE_WIDTH_LIGHT, buildDarkRingWidth } from '@map/composables/useMapLayers.js'
|
||||
import { useMapTheme, THEME_TOGGLE, FIXED_BASEMAP } from '@map/composables/useMapTheme.js'
|
||||
import { useMapEvents } from '@map/composables/useMapEvents.js'
|
||||
|
|
@ -1319,7 +1319,9 @@ async function openPoster() {
|
|||
spec.slots.scaleBar = false
|
||||
spec.slots.north = false
|
||||
// Credit the tiles actually drawn: OpenFreeMap on MapLibre, Mapbox otherwise.
|
||||
const onMaplibre = !!(window.maplibregl && map.value instanceof window.maplibregl.Map)
|
||||
// C3: ask the library, not the globals. The explicit === 'maplibre' keeps the original FALSE for
|
||||
// a map that is neither, so the Mapbox attribution stays the fall-through it always was.
|
||||
const onMaplibre = providerFor(map.value) === 'maplibre'
|
||||
spec.slots.footer = { ...spec.slots.footer, left: 'pray.doxa.life', right: new Date().toISOString().slice(0, 10),
|
||||
attribution: onMaplibre ? '© OpenFreeMap © OpenMapTiles © OpenStreetMap' : '© Mapbox © OpenStreetMap' }
|
||||
const blob = await exporter.render(spec, 'png')
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
import { CDN, pinElement } from '@map/constants/cdn.js'
|
||||
import { loadPinnedUmd } from '@map/utils/loadPinnedUmd.js'
|
||||
import { setProjectionOn } from '@map/composables/useMapProvider.js'
|
||||
import { providerFor, setProjectionOn } from '@map/composables/useMapProvider.js'
|
||||
let base = './assets'
|
||||
export function setDataBase(b) { if (b) base = String(b).replace(/\/+$/, '') }
|
||||
export function dataUrl(file) { return base + '/pplr/' + file }
|
||||
|
|
@ -112,7 +112,9 @@ export function exposeMap(map) {
|
|||
let layers = []
|
||||
try { layers = map.getStyle()?.layers || [] } catch (_) {}
|
||||
return {
|
||||
engine: window.maplibregl && window.maplibregl.Map && map instanceof window.maplibregl.Map ? 'maplibre' : 'mapbox',
|
||||
// C3: ask the library, not the globals. `|| 'mapbox'` keeps the ORIGINAL fall-through for a map
|
||||
// that is neither (stub, destroyed, neither global loaded) -- see providerFor's own note.
|
||||
engine: providerFor(map) || 'mapbox',
|
||||
// 'load' may already have fired before this listener was attached; a loaded style says so too
|
||||
loaded: loaded || !!(map.isStyleLoaded && map.isStyleLoaded()),
|
||||
styleLayers: layers.length,
|
||||
|
|
|
|||
Loading…
Reference in a new issue