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.
This commit is contained in:
Doxa Maps 2026-09-27 03:18:32 +00:00
parent 54d04fc388
commit 3734cf3882

View file

@ -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 <head>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 <link> 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()