Merge branch 'regions/country-map-posters' into regions/handoff-integration
# Conflicts: # plugin-designs/regions-dashboard/regions-list.vue # public/dx-maps/doxa-maps-spa/manifest.json # public/dx-maps/manifest.json # public/dx-maps/regions-dashboard/region-dashboards/index.html # public/dx-maps/regions-dashboard/regions-dashboard.js # public/dx-maps/regions-dashboard/regions-list/index.html # public/dx-maps/regions-dashboard/regions-mfe/index.html
This commit is contained in:
commit
bf07e0eb61
2 changed files with 133 additions and 0 deletions
51
library/packet/countrySheet.js
Normal file
51
library/packet/countrySheet.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* countrySheet.js — a country's map sheet, GENERATED in the browser at the click.
|
||||
*
|
||||
* SHARED (@map/packet/countrySheet.js). Two consumers: the regions dashboard's country-poster wall
|
||||
* and the standalone <doxa-country-map> element. It was plugin-local while the regions list was its
|
||||
* only caller; it moved here when the element became the second, rather than being copied.
|
||||
*
|
||||
* The print standard is generate, store only previews: no country sheet is shipped, and none should
|
||||
* be. MEASURED before this existed: data/posters holds 28 .html sheets and all 28 are ZONES, while
|
||||
* emit-countries.mjs writes only country-posters.json, whose `sheet` field is a PAPER SIZE ('3x3')
|
||||
* rather than a filename. So a packet's page-1 lift for a country asked for posters/country-<iso3>.html
|
||||
* and got a 404 for all 175 of them. This draws it instead.
|
||||
*
|
||||
* Same shape as my-upg-100-list's zone sheet (data/zone-sheet-print.js), deliberately, so the two
|
||||
* surfaces cannot draw the "same" sheet two ways:
|
||||
*
|
||||
* const sheet = makeCountrySheet({ records, assetsBase: () => base })
|
||||
* usePosterPrints({ …, sheet }) // (item, format) -> html
|
||||
* <PosterWall :sheet="sheet" />
|
||||
*
|
||||
* The country case is simpler than the zone case: generateCountrySheet resolves the zone and its
|
||||
* geometry from the country entry itself, so there is no assignment step to keep in sync.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {object} o
|
||||
* records (selector) => Promise<rows[]> the shared provider; this asks it for ONE country
|
||||
* assetsBase () => string where boundaries/ is served from
|
||||
* @returns {(item, format) => Promise<string>} the sheet's HTML, or '' when it cannot be drawn
|
||||
*/
|
||||
export function makeCountrySheet({ records, assetsBase }) {
|
||||
return async function countrySheet(item, format) {
|
||||
const iso3 = item && item.iso3
|
||||
if (!iso3) return '' // a selection or a row without a country: no sheet
|
||||
// Lazy, like the zone sheet: the generator and the boundary loader are only pulled when a
|
||||
// reader actually prints, so they stay out of every wall's initial download.
|
||||
const { generateCountrySheet } = await import('@map/packet/posterSheets.js')
|
||||
const [{ ISO3_NAME }, rows] = await Promise.all([
|
||||
import('@map/data/iso3-name.js'),
|
||||
records({ type: 'country', values: [iso3] }),
|
||||
])
|
||||
if (!rows || !rows.length) return '' // nothing to pin: fall through rather than draw an empty country
|
||||
return generateCountrySheet({
|
||||
country: { id: item.id, iso3, label: item.label, count: item.count },
|
||||
records: rows,
|
||||
assetsBase: assetsBase(),
|
||||
ISO3_NAME,
|
||||
format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,26 @@ import { toNumber, PEOPLE_GROUP_LIST_FIELDS } from '@map/utils/countries-meta.js
|
|||
import { peopleGroupPoints, PEOPLE_GROUP_POINT_FIELDS } from '@map/utils/peopleGroupPoints.js'
|
||||
import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
|
||||
import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js'
|
||||
import { retryableAsyncComponent } from '@map/composables/retryableAsyncComponent.js'
|
||||
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
|
||||
import { makeCountrySheet } from '@map/packet/countrySheet.js'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import posterData from '@map/data/country-posters.json'
|
||||
|
||||
// ★ THE PRINT SURFACE IS OPT-IN AND LAZY, AND BOTH HALVES MATTER.
|
||||
//
|
||||
// OPT-IN, because other sites embed this element. A page that does not set showPosters must render
|
||||
// exactly what it renders today; the surface is additive, never a change to the existing contract.
|
||||
//
|
||||
// LAZY, because PosterWall STATICALLY imports usePosterPrints, which pulls the whole printing
|
||||
// subsystem. A plain `import PosterWall` would therefore land that weight in every embed including
|
||||
// the ones that never turn the flag on, which is the opposite of opt-in. An async component keeps
|
||||
// it out until a page asks for it.
|
||||
//
|
||||
// retryableAsyncComponent, not raw defineAsyncComponent: Vue CACHES a rejected loader promise, so
|
||||
// one dropped request would disable the surface for the life of the page. Same reason
|
||||
// upg-100-list.vue uses it for its detail panel.
|
||||
const PosterWall = retryableAsyncComponent(() => import('@map/components/poster/PosterWall.vue'))
|
||||
|
||||
const props = defineProps({
|
||||
profileConfig: { type: String, default: '' },
|
||||
|
|
@ -81,6 +101,43 @@ const researchUrl = computed(() => cfg.value.researchUrl || 'https://doxa.life/r
|
|||
const prayBaseUrl = computed(() => cfg.value.prayBaseUrl || 'https://pray.doxa.life')
|
||||
const countryUrl = computed(() => cfg.value.countryUrl || 'https://doxa.life/regions/')
|
||||
const languageCode = computed(() => cfg.value.languageCode || cfg.value.locale || cfg.value.lang || 'en')
|
||||
|
||||
// OFF unless the host says otherwise, and only true for a literal true / 'true': an absent key, a
|
||||
// typo'd value or a stray string must not switch a public page's UI on by accident.
|
||||
const showPosters = computed(() => cfg.value.showPosters === true || cfg.value.showPosters === 'true')
|
||||
|
||||
// The wall shows THIS element's country, not a region's list, so its single item is the entry for
|
||||
// the first country code. An unknown code still yields a row, carrying the reason it cannot print,
|
||||
// exactly as the regions list does for a country with no poster.
|
||||
const POSTER_BY_ISO3 = new Map((posterData.countries || []).map((c) => [String(c.iso3 || '').toUpperCase(), c]))
|
||||
const posterItems = computed(() => {
|
||||
const iso3 = countryCodes.value[0]
|
||||
if (!iso3) return []
|
||||
const rec = POSTER_BY_ISO3.get(iso3)
|
||||
if (rec) return [rec]
|
||||
return [{
|
||||
id: `country-${iso3.toLowerCase()}`,
|
||||
iso3,
|
||||
label: iso3,
|
||||
count: 0,
|
||||
unsupportedReason: t('No poster has been generated for this country yet.'),
|
||||
}]
|
||||
})
|
||||
|
||||
const instanceId = computed(() => cfg.value.instanceId || 'country-map')
|
||||
const posterAssetsBase = computed(() => resolveAssetsBase(cfg.value))
|
||||
// The same shared provider the regions wall uses, not a copy of its fetch: one cached request per
|
||||
// language, and the same rows feed the sheet, so the printed map and the printed list cannot
|
||||
// disagree about which groups are in this country.
|
||||
const countryRecords = createPeopleGroupRecords({
|
||||
apiBase: () => prayBaseUrl.value,
|
||||
lang: () => languageCode.value,
|
||||
profileConfig: injectedCfg,
|
||||
})
|
||||
const countrySheet = makeCountrySheet({
|
||||
records: countryRecords,
|
||||
assetsBase: () => posterAssetsBase.value,
|
||||
})
|
||||
const { t } = useRegionsI18n()
|
||||
|
||||
// ★ THE ELEMENT FETCHES ITS OWN PINS. prayBaseUrl is in the contract, and it is the API base the
|
||||
|
|
@ -167,4 +224,29 @@ watch(() => [languageCode.value, countryCodes.value.join(',')], loadPoints)
|
|||
"Regionsdaten" all say regions. The people-groups sentence is already in the catalogue and
|
||||
already translated in all eleven, so this costs no new string. -->
|
||||
<p v-if="loadFailed" class="cm-status" role="status">{{ t('People groups could not be loaded. This is a connection problem, not a filter.') }}</p>
|
||||
<!-- OPT-IN. v-if, not v-show: an embed that never sets showPosters must not carry this markup at
|
||||
all, and the async component behind it is never even requested. Turning it on is a change to
|
||||
a live page, so no host is switched on here. -->
|
||||
<PosterWall
|
||||
v-if="showPosters && posterItems.length"
|
||||
:items="posterItems"
|
||||
:instance-id="instanceId"
|
||||
:assets-base="posterAssetsBase"
|
||||
:heading="''"
|
||||
:intro="''"
|
||||
unit="UPG"
|
||||
:item-noun="t('Country')"
|
||||
:item-noun-plural="t('Countries')"
|
||||
item-count-key="regions.countryCount"
|
||||
:records="(item) => countryRecords({ type: 'country', values: [item.iso3] })"
|
||||
:sheet="countrySheet"
|
||||
:card-stocks="true"
|
||||
:collapsible="false"
|
||||
:embedded="true"
|
||||
:section-heads="false"
|
||||
:columns="1"
|
||||
:unsupported="(c) => c.unsupportedReason || null"
|
||||
download-prefix="doxa-country"
|
||||
poster-action="print"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in a new issue