Research page embeds the hosted Research Gallery as an iframe

The page frames NUXT_PUBLIC_DX_MAPS_GALLERY_URL (default: the gallery page on the maps container's /cdn),
sized below the header, in the site's language, keyless unless a Mapbox token is configured. It loads no
dx-maps script into its own document and needs no public/dx-maps copy.
This commit is contained in:
doxa-maps 2026-09-28 04:11:47 +00:00
parent d4daff02b9
commit 3c3f17144b
4 changed files with 30 additions and 49 deletions

View file

@ -37,9 +37,8 @@ MOBILE_APP_ANDROID_PACKAGE=life.doxa.pray
# Mapbox (used by the prayer map on /pray)
NUXT_PUBLIC_MAPBOX_TOKEN=your-mapbox-public-token
# Research page: load the dx-maps tree from a maps CDN instead of this site's public/dx-maps copy.
# Unset = the local copy. The CDN must send Access-Control-Allow-Origin for the tree.
# NUXT_PUBLIC_DX_MAPS_BASE=https://maps-dev.nuxtbase.app/cdn/dx-maps
# Research page: the Research Gallery page it iframes. Unset = the default below.
# NUXT_PUBLIC_DX_MAPS_GALLERY_URL=https://maps-dev.nuxtbase.app/cdn/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html
# Cloudflare Turnstile
NUXT_TURNSTILE_SITE_KEY=your-turnstile-site-key

View file

@ -9,7 +9,7 @@ on the site now loads from the same build tree.
| | |
|---|---|
| `public/dx-maps/` | **New.** The dx-maps build tree (the gallery plus every map it opens, their data and assets), copied unchanged from the doxa-maps repo's `main` (`public/dx-maps/`). DX-life serves it from its own origin, so this site is the maps' CDN; no third-party host is involved. |
| `app/pages/research/index.vue` | **The Research Gallery.** The page loads one script (`/dx-maps/doxa-maps-spa/doxa-maps-spa.js`) and adds one element (`<doxa-maps-spa>`), sized to the viewport below the header; its `tree-base` is built at runtime from the page's own origin. The previous content (the "By Region" link, the single research map and the UUPG list) is **kept but switched off**: set `SHOW_LEGACY_RESEARCH = true` in the page to bring it back. |
| `app/pages/research/index.vue` | **The Research Gallery, as an iframe.** The page frames the gallery page hosted by the maps container (`NUXT_PUBLIC_DX_MAPS_GALLERY_URL`, default `https://maps-dev.nuxtbase.app/cdn/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html`), sized to the viewport below the header, in the site's language. Nothing is loaded into the page's own document, and the page needs no `public/dx-maps/` copy. The previous content (the "By Region" link, the single research map and the UUPG list) is **kept but switched off**: set `SHOW_LEGACY_RESEARCH = true` in the page to bring it back. |
| `app/composables/useDoxaMap.ts` | The home (engagement), pray (prayer) and adopt (adoption) maps now load `/dx-maps/doxa-simple-map/doxa-simple-map.js`; the research-map key loads `/dx-maps/doxa-research-map/doxa-research-map.js`. Mapbox GL moves from v3.3.0 to v3.24.0, the version the dx-maps bundles are built and tested with. |
| `app/components/DoxaMapSlot.vue` | With no Mapbox token configured, it asks the map for the keyless MapLibre engine (see **Map engine**). With a token, the config passes through unchanged. |
| `public/embed/map/index.html`, `public/research/doxa-research-map.html` | The same: bundles from `/dx-maps/`, Mapbox GL v3.24.0. |
@ -52,13 +52,12 @@ or `.env` is needed for this page. Without `DATABASE_URL`, the dev server only l
`regions-dashboard`, `upg-100`, and so on).
- Share and embed links in the maps point at this site's own `/dx-maps/` pages.
## Maps from a CDN instead of the local copy
## The gallery page it frames
Set `NUXT_PUBLIC_DX_MAPS_BASE` to a dx-maps tree URL, for example
`NUXT_PUBLIC_DX_MAPS_BASE=https://maps-dev.nuxtbase.app/cdn/dx-maps`. The Research page then loads the
gallery script and every map from there. That host must send `Access-Control-Allow-Origin` for the tree.
Unset (the default), the page uses this site's own `public/dx-maps/` copy, as above. The home, pray and adopt
maps still load from the local copy.
Set `NUXT_PUBLIC_DX_MAPS_GALLERY_URL` to frame another gallery host (for example the maps subdomain once it
exists). The page adds `lang` and either `tk` (with a Mapbox token configured) or `provider=maplibre`.
The home, pray and adopt maps still load from this site's `public/dx-maps/` copy; only the Research page
is independent of it.
## Updating the maps

View file

@ -4,14 +4,10 @@
// "By Region" link that were here. Those three are NOT deleted: they are kept below, switched off by
// SHOW_LEGACY_RESEARCH, so the previous page returns by flipping one constant.
//
// The gallery is a web component served from DX-life's OWN origin (public/dx-maps/, a copy of
// the dx-maps build tree), so this site is its CDN: one script, one element, no third-party host.
// It reads its manifest and every child map from the same /dx-maps/ tree. See
// DX-MAPS-GALLERY.md at the repo root.
//
// The element is created in onMounted rather than written in the template: it is a custom element
// that only exists in the browser, so this keeps it out of SSR and out of Vue's component resolver
// without any nuxt.config change.
// On this branch the gallery is an IFRAME of the gallery page hosted by the maps container
// (NUXT_PUBLIC_DX_MAPS_GALLERY_URL, default the Plugin Studio container's /cdn). Nothing is loaded
// into this page's own document, and the page needs no public/dx-maps copy. See DX-MAPS-GALLERY.md
// at the repo root.
import { buildUupgListTranslations } from '~/utils/uupgListTranslations'
@ -35,18 +31,21 @@ const researchProfileConfig = computed(() => JSON.stringify({
}))
if (SHOW_LEGACY_RESEARCH) useTextHighlight()
// The dx-maps tree: NUXT_PUBLIC_DX_MAPS_BASE when set (a maps CDN), otherwise this site's own
// /dx-maps/ copy. The script and every embed URL come from the same tree.
const DX_MAPS_BASE = ((runtimeConfig.public as { dxMapsBase?: string }).dxMapsBase || '').replace(/\/+$/, '')
const GALLERY_SCRIPT = `${DX_MAPS_BASE || '/dx-maps'}/doxa-maps-spa/doxa-maps-spa.js`
useHead({ title: () => t('Research') })
useHead({
title: () => t('Research'),
script: [{ src: GALLERY_SCRIPT, defer: true, key: 'research-gallery' }]
// The gallery page follows the site's language (and the language switcher: a new language reloads the
// frame). With a Mapbox token configured, the maps use it; without one they use the keyless MapLibre
// engine, so the page works with no secrets at all.
const galleryUrl = computed(() => {
const pub = runtimeConfig.public as { dxMapsGalleryUrl?: string; mapboxToken?: string }
const url = new URL(pub.dxMapsGalleryUrl || 'https://maps-dev.nuxtbase.app/cdn/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html')
url.searchParams.set('lang', locale.value)
if (pub.mapboxToken) url.searchParams.set('tk', pub.mapboxToken)
else url.searchParams.set('provider', 'maplibre')
return url.toString()
})
const host = ref<HTMLElement | null>(null)
let gallery: HTMLElement | null = null
let resizeObs: ResizeObserver | null = null
// The gallery fills the viewport below the header. The header's height differs between desktop
@ -64,28 +63,10 @@ onMounted(() => {
resizeObs = new ResizeObserver(fitBelowHeader)
resizeObs.observe(header)
}
gallery = document.createElement('doxa-maps-spa')
// Every embed URL is built at RUNTIME: from NUXT_PUBLIC_DX_MAPS_BASE when set, otherwise from the
// origin serving this page, so the same build works on any host (laptop, staging, production) with
// nothing hardcoded.
gallery.setAttribute('tree-base', DX_MAPS_BASE || `${window.location.origin}/dx-maps`)
// The site's language drives the gallery's (and every map's); it follows the language switcher.
gallery.setAttribute('language', locale.value)
// With a Mapbox token configured, the maps use it. Without one (a fresh laptop), they use the
// keyless MapLibre engine, so the page works with no secrets at all.
const token = (runtimeConfig.public as { mapboxToken?: string }).mapboxToken || ''
if (token) gallery.setAttribute('profile-config', JSON.stringify({ tk: token }))
else gallery.setAttribute('provider', 'maplibre')
host.value?.appendChild(gallery)
})
watch(locale, (lang) => { gallery?.setAttribute('language', lang) })
onBeforeUnmount(() => {
resizeObs?.disconnect()
gallery?.remove()
gallery = null
})
</script>
@ -93,7 +74,9 @@ onBeforeUnmount(() => {
<div>
<div class="research-gallery-page">
<h1 class="visually-hidden">{{ t('Research') }}</h1>
<div ref="host" class="research-gallery-host" />
<div ref="host" class="research-gallery-host">
<iframe :src="galleryUrl" :title="t('Research')" allow="fullscreen; clipboard-write" />
</div>
</div>
<!-- The previous Research page, switched off (SHOW_LEGACY_RESEARCH): kept, not deleted. -->
@ -126,10 +109,11 @@ onBeforeUnmount(() => {
min-height: 480px;
}
.research-gallery-host,
.research-gallery-host :deep(doxa-maps-spa) {
.research-gallery-host iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
.visually-hidden {
position: absolute;

View file

@ -286,9 +286,8 @@ export default defineNuxtConfig({
siteUrl: process.env.NUXT_PUBLIC_SITE_URL || '',
prayBaseUrl: process.env.NUXT_PUBLIC_PRAY_BASE_URL || 'https://pray.doxa.life',
mapboxToken: process.env.NUXT_PUBLIC_MAPBOX_TOKEN || '',
// Where the Research page loads the dx-maps tree from (e.g. https://maps-dev.nuxtbase.app/cdn/dx-maps).
// Unset: this site's own /dx-maps/ copy (public/dx-maps). A foreign host must send CORS for the tree.
dxMapsBase: process.env.NUXT_PUBLIC_DX_MAPS_BASE || '',
// The Research Gallery page the Research page iframes (the maps container's hosted gallery).
dxMapsGalleryUrl: process.env.NUXT_PUBLIC_DX_MAPS_GALLERY_URL || 'https://maps-dev.nuxtbase.app/cdn/dx-maps/doxa-maps-spa/doxa-maps-spa/index.html',
statinatorUrl: process.env.NUXT_PUBLIC_STATINATOR_URL || 'https://statinator.doxa.life',
statinatorProjectId: process.env.NUXT_PUBLIC_STATINATOR_PROJECT_ID || 'doxa',
statinatorEnabled: process.env.NUXT_PUBLIC_STATINATOR_ENABLED === 'true',