The country wall passed no records, so each of its 175 cards offered only the poster and said the prayer list was "coming soon", while the people groups were one request away. library/lists/ peopleGroupRecords.js is the records half of the print standard: one people-groups fetch per language (normalised the way the prints read rows, list membership applied), then a SELECTOR picks the set — a country by ISO3, explicit slugs, any field, or a host's own selector language. A selector nobody understands selects nothing rather than every group on earth, and a failed fetch is not cached. The country wall uses it by ISO3, so every country now prints the list, the deck (all five stocks) and the full or lightweight packet. The packet's page 1 is, for now, the country's stored poster when it is really there; the in-browser map generator will replace that. check:people-group-records asserts the selection two-sided with an injected fetch; registered in gates:pre.
132 lines
6.9 KiB
Vue
132 lines
6.9 KiB
Vue
<script setup>
|
|
/**
|
|
* country-poster-wall.vue — the COUNTRY poster wall (pane 2 of country-posters).
|
|
*
|
|
* @element country-posters-map
|
|
* @description One printable card per country, grouped into COLLAPSIBLE regional sections so the
|
|
* wall stays navigable at real data volume (Asia alone is 28 countries / 1,309 groups). Rendering
|
|
* lives in the shared PosterWall component; this profile supplies only its data and taxonomy.
|
|
*
|
|
* See the note in countries-map.vue: the generator reads the `@description` TAG, not the prose
|
|
* below it, and an absent tag makes the cap-5 SPA fall back to labelling the tab "experimental
|
|
* bundle".
|
|
*
|
|
* One card per country, grouped into COLLAPSIBLE regional sections. All of the
|
|
* rendering lives in the shared @map/components/poster/PosterWall.vue — this
|
|
* profile supplies only its data and its taxonomy.
|
|
*
|
|
* > design note 2026-08-01: "the poster wall should be semantically organized by the
|
|
* > regions… there's going to be way too many countries, so we'll need to be able
|
|
* > to collapse the regions and see how many countries there are in each of the
|
|
* > regions, because people don't want to just look at, like, scroll through
|
|
* > hundreds of countries."
|
|
*
|
|
* WHY COLLAPSE IS NOT A NICETY — measured from the live data:
|
|
* Asia 28 countries / 1309 groups
|
|
* Africa 41 / 314 Middle East 21 / 223
|
|
* LatAm 9 / 151 Europe 27 / 55
|
|
* Oceania 17 / 17 N.America 15 / 16 Other 18 / 21
|
|
* 92 of 175 countries have exactly ONE people group and 15 have two. Without
|
|
* collapsing, the wall is a scroll of mostly single-entry cards.
|
|
*
|
|
* SORTING IS THE ANSWER TO SPARSE SHEETS, not a template variant.
|
|
* > Brief: "if it has a one-people-group on it, we still want a one-people-group
|
|
* > map generated… those get sorted to the bottom of the list."
|
|
* The shared wall always sorts sections AND cards by descending count, so the
|
|
* dense countries lead and the long tail sinks. There is no floor.
|
|
*/
|
|
import { computed, inject, ref } from 'vue'
|
|
import PosterWall from '@map/components/poster/PosterWall.vue'
|
|
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
|
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
|
|
import { getApiBaseUrl } from '@map/api/apiBaseUrl.js'
|
|
import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
|
|
// MOVED to the shared library 2026-09-24: the regions-dashboard poster dropdown is a second
|
|
// consumer of this same dataset, and RULE 2 puts a dataset with a named second consumer in
|
|
// 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'
|
|
|
|
const profileConfig = inject('profileConfig', ref(null))
|
|
|
|
const instanceId = computed(() => profileConfig.value?.instanceId || 'country-posters-wall')
|
|
const isDark = computed(() => profileConfig.value?.isDark === true)
|
|
const assetsBase = computed(() => resolveAssetsBase(profileConfig.value))
|
|
|
|
const items = computed(() => data.countries || [])
|
|
|
|
// ★ EVERY COUNTRY PRINTS ITS PRAYER RESOURCES — the list, the card deck and the packet — through the
|
|
// library's print standard. The wall used to pass no records, so each card said "coming soon" while the
|
|
// people groups were one request away. Records come from the shared provider (one fetch per language
|
|
// for all 175 countries), selected by the country's ISO3.
|
|
const recordsFor = createPeopleGroupRecords({
|
|
apiBase: () => profileConfig.value?.apiBaseUrl || getApiBaseUrl(),
|
|
lang: () => getRuntimeLocale() || detectLocale(),
|
|
profileConfig,
|
|
})
|
|
const countryRecords = (c) => recordsFor({ type: 'country', values: [c.iso3] })
|
|
// The packet's page 1 is the country's own poster (posters/<id>.webp, 174 of them), absolute because
|
|
// the print frame is its own document, and only when it is really there: a missing image falls through
|
|
// to the packet's "map not included" line rather than printing a broken picture.
|
|
async function countryMap(c) {
|
|
const url = new URL(`${assetsBase.value}/posters/${c.id}.webp`, document.baseURI).href
|
|
const ok = await fetch(url, { method: 'HEAD', cache: 'force-cache' }).then((r) => r.ok).catch(() => false)
|
|
return ok ? url : null
|
|
}
|
|
|
|
// The region is already ON each record (emit-countries.mjs resolves it from
|
|
// wagf_region_label), so the taxonomy is not duplicated here the way the 100-list's
|
|
// REGION_OF map has to be — the 100-list's zones carry no region field, countries do.
|
|
// The data carries the WAGF region as its ENGLISH label; the section heading is that region in the
|
|
// page's language, from the dashboard's own WAGF table (dashboard.wagfRegion.<label as a slug>), so the
|
|
// wall and the Regions dashboard cannot name one region two ways. A label with no key stays as drawn.
|
|
const t = useOptionalT('')
|
|
const regionSlug = (label) => String(label).toLowerCase().replace(/\s+/g, '_')
|
|
const groupBy = (c) => {
|
|
if (!c.region || c.region === 'Other') return t('dashboard.other')
|
|
const key = `dashboard.wagfRegion.${regionSlug(c.region)}`
|
|
const v = t(key)
|
|
return v && v !== key ? v : c.region
|
|
}
|
|
|
|
// Low-density regions open COLLAPSED. Threshold is on the region's total, not its
|
|
// country count: Europe is 27 countries but only 55 groups, so it is long and thin —
|
|
// exactly what the design does not want to scroll through first.
|
|
const collapsedBy = (sec) => sec.total < 60
|
|
|
|
// The not-yet reason travels WITH the record, so the exact number the wall prints
|
|
// can never drift from the data. Brief: "it should say the exact number in our data."
|
|
const unsupported = (c) => c.unsupportedReason || 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)
|
|
// so no translation carries markup; the emphasis is the same phrase the UPG-100 wall uses.
|
|
const escHtml = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
const heading = computed(() => t('messages.countryWallHeading'))
|
|
const wallTag = computed(() => t('messages.upg100WallTag'))
|
|
const intro = computed(() => `${escHtml(t('messages.countryWallIntroA'))}<strong>${escHtml(t('messages.upg100WallIntroEmph'))}</strong>${escHtml(t('messages.countryWallIntroB'))}`)
|
|
const soonMessage = computed(() => t('messages.countryWallSoon'))
|
|
</script>
|
|
|
|
<template>
|
|
<PosterWall
|
|
:items="items"
|
|
:group-by="groupBy"
|
|
:instance-id="instanceId"
|
|
:is-dark="isDark"
|
|
:assets-base="assetsBase"
|
|
event-name="doxa-country-posters:show-on-map"
|
|
:heading="heading"
|
|
:tag="wallTag"
|
|
:intro="intro"
|
|
unit="UPG"
|
|
item-count-key="regions.countryCount"
|
|
:records="countryRecords"
|
|
:map-image="countryMap"
|
|
:collapsible="true"
|
|
:collapsed-by="collapsedBy"
|
|
:unsupported="unsupported"
|
|
:prayer-list-soon-message="soonMessage"
|
|
download-prefix="doxa-country"
|
|
/>
|
|
</template>
|