One rule for countries too large for paper: countrySheet.js's LARGE_SHEETS; whole-India list, packet and cards on a desktop

CORRECTS the previous commit's "no prayer list, packet or cards for whole India": the current direction is that
whole India's poster, list, packet and cards (about a thousand pages) are offered DESKTOP-ONLY, and a phone shows
the existing poster.phoneLimit sentence instead of failing.
countrySheet.js now exports LARGE_SHEETS ({ IND: 60x96, desktopOnly }) and largeSheet(item), and makeCountrySheet
itself prints such a country in its large format (a packet page stays a page) with its "Administrative Areas"
cell from the shared state table ("36 states and union territories"). So every caller gets the same sheet: the
Regions list's India no longer has to disagree with Country Posters (regions-list's own lines follow, from regions).
The wall drops its India-only code and follows the same rule.
This commit is contained in:
Doxa Maps 2026-10-01 05:55:55 +00:00
parent 1c61cbb926
commit 455fa8ab7d
2 changed files with 36 additions and 22 deletions

View file

@ -72,6 +72,27 @@ export function seriesFor(countries, item) {
return { totalGroups: tier.reduce((a, x) => a + (x.count || 0), 0), totalLists: tier.length, index }
}
/**
* ★ COUNTRIES TOO LARGE FOR ANY PAPER SHEET print as ONE LARGE side-lane sheet instead (poster-render's 60x96 /
* 66x66 / 36x60: every group named beside the map, crossing-free leaders). ONE source of truth for every caller (the
* Country Posters wall, the Regions list), so one country never gets two answers. `desktopOnly`: its poster, list,
* packet and cards are offered on a desktop or laptop only (a phone cannot draw a sheet that big).
* The data (country-posters.json) still records why no PAPER sheet fits; this table is what prints instead.
*/
export const LARGE_SHEETS = { IND: { format: '60x96', desktopOnly: true } }
export function largeSheet(item) { return (item && LARGE_SHEETS[String(item.iso3 || '').toUpperCase()]) || null }
// The facts row's "Administrative Areas" cell for a large-sheet country whose divisions we know (India: its states and
// union territories, counted from the shared state table). English, like the rest of the sheet.
async function adminAreasFor(iso3, base) {
if (iso3 !== 'IND') return undefined
try {
const { loadIndiaStates } = await import('@map/data/indiaStates.js')
const n = ((await loadIndiaStates(base)).states || []).length
return n ? `${n} states and union territories` : undefined
} catch (_) { return undefined } // the cell falls back to the country's name
}
export function makeCountrySheet({ records, assetsBase, countries = null }) {
return async function countrySheet(item, format) {
const iso3 = item && item.iso3
@ -84,14 +105,16 @@ export function makeCountrySheet({ records, assetsBase, countries = null }) {
records({ type: 'country', values: [iso3] }),
])
if (!rows || !rows.length) return '' // nothing to pin: fall through rather than draw an empty country
const big = largeSheet(item)
const adminAreas = item.adminAreas || (big ? await adminAreasFor(String(iso3).toUpperCase(), assetsBase()) : undefined)
return generateCountrySheet({
// region travels: countryZone colours the sheet from it, and a country arriving without one
// silently falls back to the old flat teal for that poster alone.
country: { id: item.id, iso3, label: item.label, count: item.count, region: item.region, adminAreas: item.adminAreas },
country: { id: item.id, iso3, label: item.label, count: item.count, region: item.region, adminAreas },
records: rows,
assetsBase: assetsBase(),
ISO3_NAME,
format,
format: big && format !== 'packet' ? big.format : format, // a packet's page 1 stays a page
series: seriesFor(countries, item),
})
}

View file

@ -37,7 +37,7 @@
*/
import { computed, inject, ref, watch } from 'vue'
import PosterWall from '@map/components/poster/PosterWall.vue'
import { useOptionalT, useOptionalI18n, pickPluralForm, fillParams } from '@map/composables/useOptionalT.js'
import { useOptionalT, useOptionalI18n } from '@map/composables/useOptionalT.js'
// A country's name in the reader's language (Intl, joined from our ISO-3 codes).
import { countryName } from '@map/utils/countryName.js'
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
@ -48,7 +48,8 @@ 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'
import { makeCountrySheet } from '@map/packet/countrySheet.js'
import { makeCountrySheet, largeSheet } from '@map/packet/countrySheet.js'
import { isPhonePrint } from '@map/composables/printInPlace.js'
// India's states and union territories: the shared table (fetched when the wall shows them) and their sheet.
import { loadIndiaStates } from '@map/data/indiaStates.js'
import { makeIndiaStateSheet, statesWithPosters } from './src/indiaStateSheet.js'
@ -149,10 +150,12 @@ const recordsFor = createPeopleGroupRecords({
lang: () => getRuntimeLocale() || detectLocale(),
profileConfig,
})
// ★ THE WHOLE-INDIA POSTER IS THE POSTER ONLY: no prayer list, packet or cards (855 groups would make a book), so its
// card's rows are empty. Its sheet still draws every group: makeCountrySheet fetches them itself.
const isWholeIndia = (c) => !c.indiaState && String(c.iso3 || '').toUpperCase() === 'IND'
const countryRecords = (c) => (isWholeIndia(c) ? Promise.resolve([]) : c.indiaState
// ★ A LARGE-SHEET COUNTRY (India: one 5x8 sheet naming all its groups; countrySheet.js's LARGE_SHEETS) is DESKTOP-ONLY:
// its poster, list, packet and cards (about a thousand pages) are offered on a desktop or laptop, and on a phone the
// card says so instead of failing. Same rule as the Regions list, from the same table.
const ON_PHONE = isPhonePrint()
const phoneBlocked = (c) => !c.indiaState && ON_PHONE && Boolean(largeSheet(c)?.desktopOnly)
const countryRecords = (c) => (phoneBlocked(c) ? Promise.resolve([]) : c.indiaState
? (c.slugs.length ? recordsFor({ type: 'pg_slugs', values: c.slugs }) : Promise.resolve([]))
: recordsFor({ type: 'country', values: [c.iso3] }))
// ★ GENERATED AT THE CLICK, through the ONE shared path (@map/packet/countrySheet.js, which the Regions
@ -168,19 +171,7 @@ const sheetForRecord = makeCountrySheet({
assetsBase: () => assetsBase.value,
countries: data.countries || [],
})
// ★ INDIA PRINTS AS ONE LARGE SIDE-LANE SHEET (60x96: every group named beside the map, one leader per cluster),
// whatever paper the wall asks for, until the shared size picker also offers 66x66 and 36x60. Its facts row names
// the true count of its divisions, in English like the rest of the sheet, from the same plural key as the wall head.
const INDIA_FORMAT = '60x96'
const indiaAdminAreas = () => {
const n = (indiaStates.value || []).length
return n ? fillParams(pickPluralForm(tStatic('countryPosters.indiaItemCount', 'en') || '', n, 'en'), { n }) : undefined
}
const countryOnlySheet = (card, format) => {
const rec = byId.get(card.id) || card
if (isWholeIndia(rec) && format !== 'packet') return sheetForRecord({ ...rec, adminAreas: indiaAdminAreas() }, INDIA_FORMAT)
return sheetForRecord(rec, format)
}
const countryOnlySheet = (card, format) => sheetForRecord(byId.get(card.id) || card, format)
// A state's sheet through its own ONE path (src/indiaStateSheet.js), in India's hue and the indiaState series.
const stateSheet = makeIndiaStateSheet({
records: recordsFor, assetsBase: () => assetsBase.value, states: () => indiaStates.value, region: INDIA_REGION,
@ -238,7 +229,7 @@ const collapsedBy = (sec) => sec.total < 60
// 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.
// A requested country with no poster at all says so in regions-list.vue's words (the regions catalogue).
const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : c.unsupportedReason && !isWholeIndia(c) ? t('messages.countryPosterTooMany') : null)
const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : phoneBlocked(c) ? t('poster.phoneLimit') : c.unsupportedReason && !largeSheet(c) ? 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)