Country Posters: the wall shows a host's countries, or one region, flat, on request

profile-config gains two optional keys for the country-poster-wall profile: `countries` (ISO-3 codes, or
{code, name, count} records, in the host's order) and `region` (one WAGF region as the data names it).
With either, the wall is flat: one section, nothing collapsed, two columns unless `columns` says otherwise,
no heading unless `heading` is given, because it sits under the host's own region heading. A requested
country with no poster keeps its card and says so in the regions catalogue's words. With neither key,
every binding is what it was, so the plain wall is unchanged. PosterWall itself is untouched; only the
country-posters bundle rebuilds. The usage lines in index.js and the profile's meta note name the keys.
This commit is contained in:
Doxa Maps 2026-09-30 12:17:39 +00:00
parent d45b765bd6
commit 6c9b844b8e
3 changed files with 58 additions and 9 deletions

View file

@ -1,5 +1,5 @@
{
"type": "single",
"label": "Country Posters — poster wall",
"note": "The printable poster wall pane of the Country Posters element (country-posters-mfe composes it); also embeddable alone."
"note": "The printable poster wall pane of the Country Posters element (country-posters-mfe composes it); also embeddable alone. profile-config `countries` (ISO-3 list or {code,name,count} records) or `region` (a WAGF region label) shows only those, flat, under a host's own region heading (DX Life's /regions, 2026-09-30); without them the wall is unchanged."
}

View file

@ -48,6 +48,9 @@ import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
// library/. Generated by library/scripts/posters/emit-countries.mjs, which now writes there.
import data from '@map/data/country-posters.json'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
// The regions catalogue holds the not-yet sentence a host's country gets when no poster exists (all eleven locales);
// its keys are English sentences with dots, so it is read by literal key, the way regions-list.vue reads it.
import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js'
const profileConfig = inject('profileConfig', ref(null))
@ -62,10 +65,49 @@ const assetsBase = computed(() => resolveAssetsBase(profileConfig.value))
// The i18n locale REF is read so the list is recomputed on a runtime language switch.
const { locale: wallLocale } = useOptionalI18n('')
const byId = new Map((data.countries || []).map((c) => [c.id, c]))
// ★ A HOST MAY ASK FOR SOME COUNTRIES, OR ONE REGION (2026-09-30). DX Life keeps its own /regions
// page and wants "View country posters" under each region card there, and a host page can only speak
// profile-config. So the profile takes, from profile-config, the filter regions-list.vue builds in code:
// countries the countries to show, in the host's order: ISO-3 codes ("IND,PAK" or ["IND","PAK"]) or the
// host's own records ({ code, name, count }). A code with no poster is NOT dropped: it gets the
// same not-yet reason regions-list.vue gives one (its postersFor), so no country silently
// disappears from a region.
// region one WAGF region as the data names it ("Asia"): every poster in that region.
// Either makes the wall FLAT — one section, nothing collapsed, two columns unless the host says `columns`, no
// heading, tag or intro unless the host passes `heading` — because it already sits under the host's own region
// heading. With neither key every binding below is what it was, so the wall is unchanged.
const requested = computed(() => {
const v = profileConfig.value?.countries
const list = Array.isArray(v) ? v : String(v || '').split(/[\s,]+/)
return list
.map((x) => (x && typeof x === 'object' ? { ...x, code: String(x.code || x.iso3 || '').toUpperCase() } : { code: String(x || '').toUpperCase() }))
.filter((x) => x.code)
})
const requestedRegion = computed(() => String(profileConfig.value?.region || '').trim())
const filtered = computed(() => requested.value.length > 0 || requestedRegion.value !== '')
const byIso3 = new Map((data.countries || []).map((c) => [String(c.iso3 || '').toUpperCase(), c]))
const selected = computed(() => {
const all = data.countries || []
if (requested.value.length) {
return requested.value.map((r) => byIso3.get(r.code) || {
id: `country-${r.code.toLowerCase()}`, iso3: r.code, label: r.name || r.label || r.code, count: Number(r.count) || 0, noPoster: true,
})
}
if (requestedRegion.value) return all.filter((c) => (c.region || 'Other') === requestedRegion.value)
return all
})
const flatGroup = () => 'countries'
const neverCollapsed = () => false
const hostHeading = computed(() => String(profileConfig.value?.heading || ''))
const hostColumns = computed(() => Number(profileConfig.value?.columns) || 2)
let tRegions = (key) => key
try { tRegions = useRegionsI18n().t } catch (_) { /* a host with no i18n reads the English sentence, like every string here */ }
const items = computed(() => {
const l = wallLocale && (wallLocale.value ?? wallLocale)
const locale = (typeof l === 'string' && l) || getRuntimeLocale() || detectLocale() || 'en'
const countries = data.countries || []
const countries = selected.value
if (/^en\b/i.test(locale)) return countries
return countries.map((c) => {
const name = countryName(c.iso3, locale)
@ -137,7 +179,8 @@ const collapsedBy = (sec) => sec.total < 60
// The reason is shown on the card and in the print HUD, so it speaks the reader's language: the data's
// unsupportedReason is an English note (and a flag), not display text. The card already states the
// count above it, so the sentence needs no number and no plural agreement.
const unsupported = (c) => (c.unsupportedReason ? t('messages.countryPosterTooMany') : null)
// A requested country with no poster at all says so in regions-list.vue's words (the regions catalogue).
const unsupported = (c) => (c.noPoster ? tRegions('No poster has been generated for this country yet.') : c.unsupportedReason ? t('messages.countryPosterTooMany') : null)
// ★ THROUGH THE CATALOGUE, like the UPG-100 wall: the heading, the tag, the intro and the counts read
// in the page's language. The intro keeps its one emphasis as three plain keys (A, the emphasis, B)
@ -152,14 +195,14 @@ const soonMessage = computed(() => t('messages.countryWallSoon'))
<template>
<PosterWall
:items="items"
:group-by="groupBy"
:group-by="filtered ? flatGroup : groupBy"
:instance-id="instanceId"
:is-dark="isDark"
:assets-base="assetsBase"
event-name="doxa-country-posters:show-on-map"
:heading="heading"
:tag="wallTag"
:intro="intro"
:heading="filtered ? hostHeading : heading"
:tag="filtered ? '' : wallTag"
:intro="filtered ? '' : intro"
unit="UPG"
item-count-key="regions.countryCount"
:records="countryRecords"
@ -167,8 +210,11 @@ const soonMessage = computed(() => t('messages.countryWallSoon'))
:sheet="countrySheet"
:iso3-names="iso3Names"
:map-image="countryMap"
:collapsible="true"
:collapsed-by="collapsedBy"
:collapsible="!filtered"
:collapsed-by="filtered ? neverCollapsed : collapsedBy"
:section-heads="!filtered"
:embedded="filtered"
:columns="filtered ? hostColumns : 0"
:unsupported="unsupported"
:prayer-list-soon-message="soonMessage"
download-prefix="doxa-country"

View file

@ -11,6 +11,9 @@ import { defineMapElement } from '@map/elements/defineMapElement.js'
* <script src="/app/country-posters.js"></script>
* <country-posters-map profile-config='{"profile":"countries-map","tk":"pk.YOUR_MAPBOX_TOKEN"}'></country-posters-map>
* <country-posters-map profile-config='{"profile":"country-poster-wall"}'></country-posters-map>
* <country-posters-map profile-config='{"profile":"country-poster-wall","countries":"IND,PAK,BGD"}'></country-posters-map>
* only those countries (ISO-3, or {code,name,count} records), FLAT, for a host's own region section
* (DX Life's /regions, 2026-09-30); `region: "Asia"` asks for one WAGF region; `columns`, `heading` optional.
*
* TWO PROFILES, ONE BUNDLE — the same shape as my-upg-100-list:
* countries-map.vue the map; legend opens on the COUNTRY level