dx-base's three commits from research-gallery-plugin-studio (the runtime loader, check:origins and its lint pass) apply here unchanged: the code is byte-identical to that branch. What differs is what the two settings point at on this route. The Research Gallery is the research site the team's dx-maps container serves, and the maps load from the container's published tree, which the research subdomain serves at /dx-maps (the Studio's host serves the same tree at /cdn/dx-maps; both send CORS). The comments, .env.example and DX-MAPS-GALLERY.md now say so, with this route's laptop values (research.localhost:4615, as compose.laptop.yaml already sets). DX-MAPS-GALLERY.md's "Run it" said no .env is needed for /research. That stopped being true when the default maps host was removed, so it now names the one setting the maps need. check:origins: 15 problems on research-gallery-subdomain before, PASS (306 files) after.
169 lines
6.5 KiB
Vue
169 lines
6.5 KiB
Vue
<script setup lang="ts">
|
|
// The Research page IS the Research Gallery: every research map, dashboard and list in one app,
|
|
// filling the page below the header. It replaces the single research map, the UUPG list and the
|
|
// "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 the deployment's dx-maps tree (NUXT_PUBLIC_DX_MAPS_BASE,
|
|
// the team container's published tree), asked from this site's server at runtime: this page is prerendered,
|
|
// and a prerendered page cannot carry a deployment's address. It reads its manifest and every child map
|
|
// from the same tree. The menu's Research item goes to the gallery's own subdomain instead; this page
|
|
// is kept, unlinked. 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.
|
|
|
|
import { mapboxTokenIfChosen } from '~/utils/mapProvider'
|
|
import { buildUupgListTranslations } from '~/utils/uupgListTranslations'
|
|
|
|
// ONE scrollbar: no footer here, and the page itself never scrolls; the gallery's own content does.
|
|
definePageMeta({ hideFooter: true })
|
|
|
|
const { t, locale } = useI18n()
|
|
const runtimeConfig = useRuntimeConfig()
|
|
|
|
// The previous Research page (the "By Region" link, the single research map and the UUPG list).
|
|
// Off on this branch: the Research Gallery replaces all three. Set true to bring them back.
|
|
const SHOW_LEGACY_RESEARCH = false
|
|
const localePath = useLocalePath()
|
|
const route = useRoute()
|
|
const initialSearchTerm = computed(() =>
|
|
(Array.isArray(route.query.uupg_search) ? route.query.uupg_search[0] : route.query.uupg_search) ?? ''
|
|
)
|
|
const translations = computed(() => buildUupgListTranslations(t))
|
|
const researchProfileConfig = computed(() => JSON.stringify({
|
|
profile: 'research-map',
|
|
tk: (runtimeConfig.public as { mapboxToken?: string }).mapboxToken || '',
|
|
dataSource: 'pray-tools',
|
|
instanceId: 'research-page'
|
|
}))
|
|
if (SHOW_LEGACY_RESEARCH) useTextHighlight()
|
|
|
|
useHead({ title: () => t('Research') })
|
|
|
|
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
|
|
// and phone (and with the language), so it is measured, not assumed.
|
|
function fitBelowHeader() {
|
|
const header = document.getElementById('masthead')
|
|
const h = header ? Math.round(header.getBoundingClientRect().height) : 0
|
|
document.documentElement.style.setProperty('--rg-header-h', h + 'px')
|
|
}
|
|
|
|
onMounted(() => {
|
|
fitBelowHeader()
|
|
// The gallery is sized to the viewport below the header; a sub-pixel header height must not add a
|
|
// page scrollbar, so the page does not scroll while the gallery is shown.
|
|
document.documentElement.classList.add('rg-no-page-scroll')
|
|
const header = document.getElementById('masthead')
|
|
if (header && 'ResizeObserver' in window) {
|
|
resizeObs = new ResizeObserver(fitBelowHeader)
|
|
resizeObs.observe(header)
|
|
}
|
|
|
|
gallery = document.createElement('doxa-maps-spa')
|
|
// The site's language drives the gallery's (and every map's); it follows the language switcher.
|
|
gallery.setAttribute('language', locale.value)
|
|
// MapLibre (keyless) by default; Mapbox only when NUXT_PUBLIC_MAP_PROVIDER=mapbox and a token is set.
|
|
const token = mapboxTokenIfChosen(runtimeConfig.public)
|
|
if (token) gallery.setAttribute('profile-config', JSON.stringify({ tk: token }))
|
|
else gallery.setAttribute('provider', 'maplibre')
|
|
// The tree, its script and every embed URL come from the deployment's address, asked at RUNTIME, so
|
|
// the same build works on any host (laptop, staging, production) with nothing hardcoded.
|
|
dxMapsBaseOrThrow().then((tree) => {
|
|
if (!gallery) return
|
|
gallery.setAttribute('tree-base', tree)
|
|
host.value?.appendChild(gallery)
|
|
if (!document.querySelector('script[data-research-gallery]')) {
|
|
const s = document.createElement('script')
|
|
s.src = `${tree}/doxa-maps-spa/doxa-maps-spa.js`
|
|
s.dataset.researchGallery = '1'
|
|
document.head.appendChild(s)
|
|
}
|
|
}).catch((err) => {
|
|
const p = document.createElement('p')
|
|
p.className = 'research-gallery-error'
|
|
p.setAttribute('role', 'status')
|
|
p.textContent = String((err as Error)?.message || err)
|
|
host.value?.replaceChildren(p)
|
|
})
|
|
})
|
|
|
|
watch(locale, (lang) => { gallery?.setAttribute('language', lang) })
|
|
|
|
onBeforeUnmount(() => {
|
|
document.documentElement.classList.remove('rg-no-page-scroll')
|
|
resizeObs?.disconnect()
|
|
gallery?.remove()
|
|
gallery = null
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="research-gallery-page">
|
|
<h1 class="visually-hidden">{{ t('Research') }}</h1>
|
|
<div ref="host" class="research-gallery-host" />
|
|
</div>
|
|
|
|
<!-- The previous Research page, switched off (SHOW_LEGACY_RESEARCH): kept, not deleted. -->
|
|
<div v-if="SHOW_LEGACY_RESEARCH" class="container page-content uupgs-page stack stack--3xl">
|
|
<div class="stack stack--xs text-center">
|
|
<h1 class="highlight" data-highlight-last>{{ t('Find a UUPG') }}</h1>
|
|
<p><NuxtLink class="light-link" :to="localePath('/regions')">{{ t('By Region') }} →</NuxtLink></p>
|
|
</div>
|
|
<DoxaMapSlot
|
|
map-id="research-page-map"
|
|
bundle="research-map"
|
|
:profile-config="researchProfileConfig"
|
|
class="rounded-xlg"
|
|
/>
|
|
<UupgsList
|
|
:language-code="locale"
|
|
:research-url="localePath('/research') + '/'"
|
|
:t="translations"
|
|
:initial-search-term="initialSearchTerm"
|
|
hide-see-all-link
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.research-gallery-page {
|
|
/* 100svh: the phone's SMALL viewport, so the browser's own toolbars never cover the gallery. */
|
|
height: calc(100svh - var(--rg-header-h, 0px));
|
|
}
|
|
.research-gallery-host,
|
|
.research-gallery-host :deep(doxa-maps-spa) {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.research-gallery-host :deep(.research-gallery-error) {
|
|
max-width: 32rem;
|
|
margin: 2rem auto;
|
|
padding: 0 1rem;
|
|
text-align: center;
|
|
}
|
|
.visually-hidden {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0 0 0 0);
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
/* Not scoped: set on <html> by this page only while the gallery is mounted. */
|
|
html.rg-no-page-scroll,
|
|
html.rg-no-page-scroll body {
|
|
overflow: hidden;
|
|
}
|
|
</style>
|