From 3734cf388209fa8983d1734eebed81c7d66ba855 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sun, 27 Sep 2026 03:18:32 +0000 Subject: [PATCH] Key cities: the map honours provider=maplibre instead of always building Mapbox GL It loaded and constructed Mapbox GL whatever the page asked for, so under provider=maplibre Mapbox warned about missing CSS and an empty style. The engine now comes from the library's provider resolution (auto = Mapbox only with a token at hand, including the injected one), the engine's stylesheet goes into the shadow root, and the style is the registry pair connectivity-map uses: ink (dark-v11 / OpenFreeMap dark) or paper (light-v11 / positron). No silent fallback to Mapbox; the map stays gated. --- plugin-designs/key-cities/cities-map.vue | 29 +++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/plugin-designs/key-cities/cities-map.vue b/plugin-designs/key-cities/cities-map.vue index 827b0be7f..4eac00f21 100644 --- a/plugin-designs/key-cities/cities-map.vue +++ b/plugin-designs/key-cities/cities-map.vue @@ -36,7 +36,9 @@ import { labelFont } from '@map/maps/label-font.js' import { computed, inject, ref, shallowRef, markRaw, onMounted, onBeforeUnmount } from 'vue' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import { addMapAttribution } from '@map/composables/useMapAttribution' -import { ensureEngineScript, engineGlobal } from '@map/composables/useMapInstance.js' +import { ensureEngineScript, engineGlobal, ensureEngineCSS, engineStylesheetUrl } from '@map/composables/useMapInstance.js' +import { resolveMapProvider } from '@map/composables/useMapProvider.js' +import { resolveNamedStyle } from '@map/styles/registry.js' import { installRtlTextPlugin } from '@map/maps/rtl-text.js' import { resolveAssetsBase } from '@map/utils/embedBase.js' import { useI18n } from 'vue-i18n' @@ -47,6 +49,7 @@ try { t = useI18n().t } catch (_) { /* no i18n on this host */ } const profileConfig = inject('profileConfig', ref(null)) const isDark = computed(() => profileConfig.value?.isDark === true) +let unmounted = false // The token through the library's channel (documentation/reference-universal-props.md): the // profile-config tk, then ProfileLoader's injected mapboxToken (which carries the page-wide shared // token, fed by window.MAP_TOKEN), then window.MAP_TOKEN. This read window.DOXA_TOKEN, which only a @@ -145,18 +148,32 @@ function paint(map) { onMounted(async () => { // Load the engine rather than assume the page did: leaf s no longer preload mapbox-gl.js - // (it leaked every visitor's IP to Mapbox under provider=maplibre). This map is Mapbox-only. - try { await ensureEngineScript('mapbox') } catch (_) {} - const gl = engineGlobal('mapbox') + // (it leaked every visitor's IP to Mapbox under provider=maplibre). THE ENGINE THE PAGE ASKED FOR, + // OR NONE: no silent fallback to mapboxgl. Resolved by the library like every other map + // (?provider=, profile-config, window.MAP_PROVIDER; 'auto' = Mapbox only when a token is at hand, + // including the one ProfileLoader injected). This map used to hard-construct Mapbox GL under + // provider=maplibre. + const kcProvider = resolveMapProvider(profileConfig, { token: mapboxToken.value }) + try { await ensureEngineScript(kcProvider) } catch (_) {} + const gl = engineGlobal(kcProvider) if (!gl || !mapEl.value) { err.value = t('keyCities.map.engineMissing'); return } // RTL SHAPING before the map exists: a raw map is not covered by useMapInstance's install, and // the Mapbox basemap draws Middle-East places in Arabic/Persian/Hebrew script for every viewer. installRtlTextPlugin(gl) try { await load() + if (unmounted || !mapEl.value) return + // The engine's stylesheet must be INSIDE this shadow root; a document-level never reaches + // it (Mapbox's "missing CSS declarations" canary). + const css = engineStylesheetUrl(kcProvider) + if (css) await ensureEngineCSS(mapEl.value, css) + if (unmounted || !mapEl.value) return const map = new gl.Map({ container: mapEl.value, - style: isDark.value ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11', + // Paired through the registry: 'ink' = mapbox dark-v11 / OpenFreeMap dark, 'paper' = light-v11 / + // positron, the same pair connectivity-map uses. The literal is the fallback if a name stops resolving. + style: resolveNamedStyle(isDark.value ? 'ink' : 'paper', kcProvider).style + || (isDark.value ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11'), accessToken: mapboxToken.value, center: [42, 18], zoom: 1.3, projection: 'mercator', attributionControl: false, }) @@ -169,7 +186,7 @@ onMounted(async () => { map.once('load', () => paint(map)) } catch (e) { err.value = String(e.message || e) } }) -onBeforeUnmount(() => { try { mapRef.value?.remove() } catch (_) {} }) +onBeforeUnmount(() => { unmounted = true; try { mapRef.value?.remove() } catch (_) {} }) const fmt = (n) => (Number(n) || 0).toLocaleString()