Studio and stack-demo hand the host's language down to what they mount
A host that embeds either composite with profile-config.locale was ignored (check:host-locale-contract: 0 Arabic characters with locale "ar", against 2121 / 2249 at ?lang=ar). - stack-demo mounts its children by tag and already threaded tk and provider; locale now rides the same way to the research dashboard, and the grid (which reads cfg.locale itself) gets it only when the host gave one. 0 -> 358: the grid half. The dashboard half waits on its own bundle providing appI18n. - plugin-studio seeded its language from localStorage / the browser and handed that to every pane. The ladder is now host profile-config locale/lang -> ?lang= / ?locale= -> saved choice -> browser; a host- or URL-given language is not persisted as the reader's. 0 -> 893 (its own chrome); the rest is the Research Gallery pane it opens on, which does not pass the locale on yet. - plugin-studio's language switch rebuilt a pane's profile-config by hand without provider, tokenUrl and treeBase, so switching language turned a maplibre pane back into a mapbox one. Mount and switch now share one paneConfig().
This commit is contained in:
parent
aed352f7b6
commit
2c83cce407
11 changed files with 73 additions and 42 deletions
|
|
@ -161,7 +161,7 @@ import { defineComponent, inject } from 'vue'
|
|||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import VerticalTabsLayout from '@map/layouts/VerticalTabsLayout.vue'
|
||||
import StudioTree from './src/StudioTree.vue'
|
||||
import { SWITCHER_LANGUAGES, loadLocale, saveLocale, getLanguage } from '@map/layouts/app-wrapper-material/languages.js'
|
||||
import { SWITCHER_LANGUAGES, loadLocale, saveLocale, getLanguage, normaliseLocale } from '@map/layouts/app-wrapper-material/languages.js'
|
||||
|
||||
// Only the studio's CONTENT-AREA styles remain here; the shell (top bar + rail + body) is now the
|
||||
// shared VerticalTabsLayout (which self-injects its own CSS). Kept as an injected string so the studio
|
||||
|
|
@ -409,7 +409,13 @@ export default defineComponent({
|
|||
activeKey: '',
|
||||
embedOpen: false,
|
||||
// i18n locale switcher (reuses app-wrapper-material/languages.js — persisted, offline, no dep).
|
||||
locale: loadLocale(),
|
||||
// ★ THE HOST'S LOCALE FIRST. A host that embeds the studio with profile-config.locale (the
|
||||
// library's contract, read by ProfileLoader) was ignored: the switcher seeded from localStorage /
|
||||
// the browser, and every pane was handed THAT — check:host-locale-contract measured 0 Arabic
|
||||
// characters with locale:"ar" against 2077 at ?lang=ar. Ladder, as in i18n/index.js detectLocale:
|
||||
// profile-config locale/lang → ?lang= / ?locale= → the reader's saved switcher choice → browser.
|
||||
// A host- or URL-given locale is NOT persisted: it is the host's choice, not the reader's.
|
||||
locale: this.initialLocale(),
|
||||
localeOpen: false,
|
||||
switcherLangs: SWITCHER_LANGUAGES,
|
||||
// rows keyed by "<bundle>/<profile>"; each carries its resolved nesting state.
|
||||
|
|
@ -584,6 +590,26 @@ export default defineComponent({
|
|||
// Switch preview language. languages.js's contract: switching = re-setting profile-config.locale
|
||||
// on the mounted element. Persist, then re-set it on the element this pane already holds. Every
|
||||
// pane now carries a real element, so there is ONE path here — no per-mode branch.
|
||||
initialLocale() {
|
||||
let pc = this.profileConfig
|
||||
if (typeof pc === 'string') { try { pc = pc ? JSON.parse(pc) : {} } catch { pc = {} } }
|
||||
let q = ''
|
||||
try { const u = new URLSearchParams(window.location.search); q = u.get('lang') || u.get('locale') || '' } catch { /* no window */ }
|
||||
const given = (pc && (pc.locale || pc.lang)) || q
|
||||
return given ? normaliseLocale(given) : loadLocale()
|
||||
},
|
||||
// ONE builder for a pane's profile-config, used by the mount AND by a language switch. The switch
|
||||
// used to rebuild it by hand with only profile/instanceId/locale/tk — dropping tokenUrl, provider
|
||||
// and treeBase, so switching language flipped a maplibre pane back to mapbox.
|
||||
paneConfig(row, locale) {
|
||||
const pc = { profile: row.profile, instanceId: 'ps-' + row.key.replace(/\//g, '-'), locale }
|
||||
if (this.tk || this.cfg.tk) pc.tk = this.tk || this.cfg.tk
|
||||
if (this.tokenUrl || this.cfg.tokenUrl) pc.tokenUrl = this.tokenUrl || this.cfg.tokenUrl
|
||||
const wantProvider = this.provider || this.cfg.provider
|
||||
if (wantProvider === 'maplibre' || wantProvider === 'mapbox') pc.provider = wantProvider
|
||||
if (this.base) pc.treeBase = this.base
|
||||
return pc
|
||||
},
|
||||
setLocale(code) {
|
||||
this.locale = code
|
||||
this.localeOpen = false
|
||||
|
|
@ -593,9 +619,7 @@ export default defineComponent({
|
|||
const slot = this._slots && this._slots[this.activeKey]
|
||||
const el = slot && slot.firstElementChild
|
||||
if (el && el.setAttribute && row) {
|
||||
const pc = { profile: row.profile, instanceId: 'ps-' + this.activeKey.replace(/\//g, '-'), locale: code }
|
||||
if (this.tk || this.cfg.tk) pc.tk = this.tk || this.cfg.tk
|
||||
el.setAttribute('profile-config', JSON.stringify(pc))
|
||||
el.setAttribute('profile-config', JSON.stringify(this.paneConfig(row, code)))
|
||||
}
|
||||
} catch { /* non-fatal: the switcher still persists + updates the UI */ }
|
||||
},
|
||||
|
|
@ -782,16 +806,11 @@ export default defineComponent({
|
|||
// renders in the studio's active language (ProfileLoader reads profile-config.locale).
|
||||
// tk/tokenUrl ride along here now that map rows mount directly — this is the token ladder
|
||||
// the removed iframe src used to carry as a query param.
|
||||
const pc = { profile: row.profile, instanceId: 'ps-' + row.key.replace(/\//g, '-'), locale: this.locale }
|
||||
if (this.tk || this.cfg.tk) pc.tk = this.tk || this.cfg.tk
|
||||
if (this.tokenUrl || this.cfg.tokenUrl) pc.tokenUrl = this.tokenUrl || this.cfg.tokenUrl
|
||||
// ★ AND THE ENGINE, for the same reason tk rides along. resolveMapProvider's ladder starts
|
||||
// at profile-config.provider, and a pane reads the config THIS file writes — so a studio
|
||||
// embedded with provider=maplibre showed mapbox panes and, on a keyless host, panes that
|
||||
// asked for a token. MEASURED on a foreign origin: 19 requests to api.mapbox.com for a
|
||||
// maplibre request. Validated to the two known engines so a typo cannot select a third.
|
||||
const wantProvider = this.provider || this.cfg.provider
|
||||
if (wantProvider === 'maplibre' || wantProvider === 'mapbox') pc.provider = wantProvider
|
||||
// PASS THE TREE BASE DOWN — a nested cap-5 shell cannot guess it.
|
||||
// doxa-maps-spa carries its OWN hardcoded, TREE-RELATIVE tab list (doxa-maps-spa.vue:265-312,
|
||||
// e.g. 'doxa-research-map/research-map/index.html') and resolves it against ITS treeBase,
|
||||
|
|
@ -804,8 +823,8 @@ export default defineComponent({
|
|||
// We know the correct base because it is the one WE resolved against, so hand it over rather
|
||||
// than letting the child re-derive it. doxa-maps-spa reads cfg.treeBase (:519) ahead of its
|
||||
// own guess, so this lands through the existing config path with no new prop.
|
||||
if (this.base) pc.treeBase = this.base
|
||||
el.setAttribute('profile-config', JSON.stringify(pc))
|
||||
// (All of the above is built in ONE place, paneConfig(), shared with setLocale.)
|
||||
el.setAttribute('profile-config', JSON.stringify(this.paneConfig(row, this.locale)))
|
||||
// …and as a plain `tree-base` ATTRIBUTE, for elements that take the base that way instead of
|
||||
// through profile-config (golden-path-check does, deliberately — it has no ProfileLoader).
|
||||
// Without it that check guessed the tree from the PAGE URL, which in the studio is the page
|
||||
|
|
|
|||
|
|
@ -76,6 +76,14 @@ const hostProvider = computed(() => {
|
|||
const p = hostConfig.value?.provider
|
||||
return p === 'maplibre' || p === 'mapbox' ? p : ''
|
||||
})
|
||||
// ★ AND THE HOST'S LANGUAGE, the third knob a host passes the same way. The children are mounted BY
|
||||
// TAG, each with its own ProfileLoader / config reader, so the only thing missing was handing the
|
||||
// locale down — check:host-locale-contract measured 0 Arabic characters with profile-config
|
||||
// locale:"ar" against 2205 at ?lang=ar. Passed through as given; each child validates it.
|
||||
const hostLocale = computed(() => {
|
||||
const l = hostConfig.value?.locale || hostConfig.value?.lang
|
||||
return typeof l === 'string' && l ? l : ''
|
||||
})
|
||||
|
||||
const sections = computed(() => [
|
||||
{
|
||||
|
|
@ -101,6 +109,7 @@ const sections = computed(() => [
|
|||
// what its standalone leaf asks for.
|
||||
...(hostToken.value ? { tk: hostToken.value } : {}),
|
||||
...(hostProvider.value ? { provider: hostProvider.value } : {}),
|
||||
...(hostLocale.value ? { locale: hostLocale.value } : {}),
|
||||
assetsBase: '../../assets'
|
||||
})
|
||||
}
|
||||
|
|
@ -110,7 +119,10 @@ const sections = computed(() => [
|
|||
title: 'People Groups',
|
||||
sub: 'the full searchable list (live)',
|
||||
element: 'people-groups-grid',
|
||||
src: bundleSrc('people-groups-grid', 'people-groups-grid')
|
||||
src: bundleSrc('people-groups-grid', 'people-groups-grid'),
|
||||
// The grid reads profile-config.locale itself (people-groups-grid.vue, cfg.locale). Only set
|
||||
// when the host gave one, so without it the grid mounts exactly as its standalone leaf does.
|
||||
...(hostLocale.value ? { attrs: { 'profile-config': JSON.stringify({ locale: hostLocale.value }) } } : {})
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
|
|
|||
8
public/doxa-maps/doxa-maps-spa/manifest.json
generated
8
public/doxa-maps/doxa-maps-spa/manifest.json
generated
|
|
@ -782,7 +782,7 @@
|
|||
"bundle": "plugin-studio",
|
||||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"output": "doxa-maps/plugin-studio/plugin-studio.js",
|
||||
"integrity": "sha384-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM",
|
||||
"integrity": "sha384-JkHKL07MX7U5S8CLVSqL5FoDxuS25rpJygq75ozwNJuq8w9TVeZCw5DCZw82CTTA",
|
||||
"elements": [
|
||||
"plugin-studio"
|
||||
],
|
||||
|
|
@ -810,7 +810,7 @@
|
|||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-JkHKL07MX7U5S8CLVSqL5FoDxuS25rpJygq75ozwNJuq8w9TVeZCw5DCZw82CTTA\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -1015,7 +1015,7 @@
|
|||
"bundle": "stack-demo",
|
||||
"scriptUrl": "./stack-demo/stack-demo.js",
|
||||
"output": "doxa-maps/stack-demo/stack-demo.js",
|
||||
"integrity": "sha384-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G",
|
||||
"integrity": "sha384-UkW6xuFr5wd1F6WJagiWzDoGxBXN6V89ESxbUu6AsRkcQOXTMGxBdRs84EpOAV6A",
|
||||
"elements": [
|
||||
"stack-demo"
|
||||
],
|
||||
|
|
@ -1035,7 +1035,7 @@
|
|||
"scriptUrl": "./stack-demo/stack-demo.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./stack-demo/stack-demo/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./stack-demo/stack-demo.js\" integrity=\"sha384-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G\" crossorigin=\"anonymous\"></script>\n<stack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></stack-demo>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./stack-demo/stack-demo.js\" integrity=\"sha384-UkW6xuFr5wd1F6WJagiWzDoGxBXN6V89ESxbUu6AsRkcQOXTMGxBdRs84EpOAV6A\" crossorigin=\"anonymous\"></script>\n<stack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></stack-demo>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -782,7 +782,7 @@
|
|||
"bundle": "plugin-studio",
|
||||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"output": "doxa-maps/plugin-studio/plugin-studio.js",
|
||||
"integrity": "sha384-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM",
|
||||
"integrity": "sha384-JkHKL07MX7U5S8CLVSqL5FoDxuS25rpJygq75ozwNJuq8w9TVeZCw5DCZw82CTTA",
|
||||
"elements": [
|
||||
"plugin-studio"
|
||||
],
|
||||
|
|
@ -810,7 +810,7 @@
|
|||
"scriptUrl": "./plugin-studio/plugin-studio.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./plugin-studio/plugin-studio/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./plugin-studio/plugin-studio.js\" integrity=\"sha384-JkHKL07MX7U5S8CLVSqL5FoDxuS25rpJygq75ozwNJuq8w9TVeZCw5DCZw82CTTA\" crossorigin=\"anonymous\"></script>\n<plugin-studio profile-config='{\"profile\":\"plugin-studio\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></plugin-studio>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -1015,7 +1015,7 @@
|
|||
"bundle": "stack-demo",
|
||||
"scriptUrl": "./stack-demo/stack-demo.js",
|
||||
"output": "doxa-maps/stack-demo/stack-demo.js",
|
||||
"integrity": "sha384-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G",
|
||||
"integrity": "sha384-UkW6xuFr5wd1F6WJagiWzDoGxBXN6V89ESxbUu6AsRkcQOXTMGxBdRs84EpOAV6A",
|
||||
"elements": [
|
||||
"stack-demo"
|
||||
],
|
||||
|
|
@ -1035,7 +1035,7 @@
|
|||
"scriptUrl": "./stack-demo/stack-demo.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./stack-demo/stack-demo/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./stack-demo/stack-demo.js\" integrity=\"sha384-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G\" crossorigin=\"anonymous\"></script>\n<stack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></stack-demo>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./stack-demo/stack-demo.js\" integrity=\"sha384-UkW6xuFr5wd1F6WJagiWzDoGxBXN6V89ESxbUu6AsRkcQOXTMGxBdRs84EpOAV6A\" crossorigin=\"anonymous\"></script>\n<stack-demo profile-config='{\"profile\":\"stack-demo\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></stack-demo>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
20
public/doxa-maps/plugin-studio/plugin-studio.js
generated
20
public/doxa-maps/plugin-studio/plugin-studio.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
|
||||
<script src="../plugin-studio.js" integrity="sha384-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM" crossorigin="anonymous"></script>
|
||||
<script src="../plugin-studio.js" integrity="sha384-JkHKL07MX7U5S8CLVSqL5FoDxuS25rpJygq75ozwNJuq8w9TVeZCw5DCZw82CTTA" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <plugin-studio> custom element. -->
|
||||
<script src="../../plugin-studio.js" integrity="sha384-JhqC9WgYSipRHC8JK60x+VOeRp4gg1USvIDUXWb9aLx3gHdOCeLgmtV6HkN8mfgM" crossorigin="anonymous"></script>
|
||||
<script src="../../plugin-studio.js" integrity="sha384-JkHKL07MX7U5S8CLVSqL5FoDxuS25rpJygq75ozwNJuq8w9TVeZCw5DCZw82CTTA" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
12
public/doxa-maps/stack-demo/stack-demo.js
generated
12
public/doxa-maps/stack-demo/stack-demo.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <stack-demo> custom element. -->
|
||||
<script src="../stack-demo.js" integrity="sha384-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G" crossorigin="anonymous"></script>
|
||||
<script src="../stack-demo.js" integrity="sha384-UkW6xuFr5wd1F6WJagiWzDoGxBXN6V89ESxbUu6AsRkcQOXTMGxBdRs84EpOAV6A" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <stack-demo> custom element. -->
|
||||
<script src="../../stack-demo.js" integrity="sha384-3Rjk4pZDcTABDfBIAbT2tu+D6J9/U3jx3UJDkiPynH+oeiwzCGFotSX7hbhdT48G" crossorigin="anonymous"></script>
|
||||
<script src="../../stack-demo.js" integrity="sha384-UkW6xuFr5wd1F6WJagiWzDoGxBXN6V89ESxbUu6AsRkcQOXTMGxBdRs84EpOAV6A" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue