diff --git a/library/composables/useMapProvider.js b/library/composables/useMapProvider.js index 26790a2df..c499e1cfe 100644 --- a/library/composables/useMapProvider.js +++ b/library/composables/useMapProvider.js @@ -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 diff --git a/plugin-designs/all-people-groups/all-people-groups.vue b/plugin-designs/all-people-groups/all-people-groups.vue index 805464fd5..42b9c9ca8 100644 --- a/plugin-designs/all-people-groups/all-people-groups.vue +++ b/plugin-designs/all-people-groups/all-people-groups.vue @@ -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 ? `${esc(tr('pray'))}` : '' - 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(`
diff --git a/plugin-designs/doxa-research-map/research-map.vue b/plugin-designs/doxa-research-map/research-map.vue index 330dc6c89..cbcf3a13d 100644 --- a/plugin-designs/doxa-research-map/research-map.vue +++ b/plugin-designs/doxa-research-map/research-map.vue @@ -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') diff --git a/plugin-designs/pplr-data-maps/src/utils/pplrData.js b/plugin-designs/pplr-data-maps/src/utils/pplrData.js index f19741be7..c65c9a130 100644 --- a/plugin-designs/pplr-data-maps/src/utils/pplrData.js +++ b/plugin-designs/pplr-data-maps/src/utils/pplrData.js @@ -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,