India can print at 60x96 (the default), 66x66 or 36x60. makeCountrySheet used to map any non-packet request to the default, so a 66x66 or 36x60 request drew the 60x96 sheet. sheetFormatFor(item, format) (exported, pure) now keeps a requested size the country can print at, falls back to its default otherwise, keeps a packet page a page, and returns exactly what was asked for every other country. check:country-sheet-one-path asserts that table (India 66x66/36x60/60x96/letter/3x4/packet; Nepal 3x3/letter; China 3x4), with two poisons: India ignoring a requested 66x66, and a packet page turned into a large sheet.
128 lines
8 KiB
JavaScript
128 lines
8 KiB
JavaScript
/**
|
|
* countrySheet.js — a country's map sheet, GENERATED in the browser at the click.
|
|
*
|
|
* SHARED (@map/packet/countrySheet.js). Its callers are the regions dashboard's regions-list and the
|
|
* Country Posters wall (country-poster-wall.vue), which used to define its own inline copy.
|
|
*
|
|
* ⚠ THE MOVE HERE WAS NEVER FINISHED, and it cost a real defect. This module's comment used to claim
|
|
* two consumers -- the country-poster wall and <doxa-country-map> -- and a CENSUS of the tree found
|
|
* it had NONE: nothing imported it at all. regions-list kept importing a byte-identical plugin-local
|
|
* copy (src/country-sheet-print.js), and country-poster-wall.vue defines its own inline countrySheet
|
|
* that calls generateCountrySheet directly. So this file was an ORPHAN wearing a comment that said
|
|
* otherwise.
|
|
* That mattered the moment the country poster's hue became data-driven: the region key was added
|
|
* HERE, where nothing read it, while the copy regions-list actually imported went on building
|
|
* `country` without a region -- so countryRegionColor(undefined) returned the old flat teal and
|
|
* warned. The same country then printed HUED from Country Posters and TEAL from the Regions tab,
|
|
* which is exactly the "two different posters for one country" the hue change exists to refuse.
|
|
* The copy is deleted, and regions-list and the wall both import this (check:country-sheet-one-path
|
|
* refuses a caller that draws a country sheet any other way). Do not re-copy it: a duplicate of this file is
|
|
* indistinguishable from it until one of them is edited, and then it is a silent divergence.
|
|
*
|
|
* 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
|
|
*/
|
|
/**
|
|
* The sheet's SERIES facts, computed exactly as the Country Posters wall computes them
|
|
* (country-poster-wall.vue): the tier is every country printed at the same sheet size, the total is
|
|
* that tier's length, and the index is the country's position in it by id. So a country's printed
|
|
* "map N of M" and "one of a ~M-map set" agree with the wall's print and with its own thumbnail.
|
|
*
|
|
* ★ WHY THIS EXISTS: without a series, generateCountrySheet falls back to totalLists 0, and every
|
|
* Regions-tab print read "map 1 of 0" and "one of a 0-map set called the Country Maps" -- nonsense
|
|
* on a printed poster, on the demo's showcase flow (Regions -> a country -> Poster Map). This was
|
|
* not introduced by the one-definition change: the plugin-local copy regions-list used before it
|
|
* passed no series either. The wall was never affected because it computes its own.
|
|
*
|
|
* Returns undefined when the item is not in the list (a host's country with no poster), so that row
|
|
* keeps the renderer's own default rather than a fabricated position.
|
|
*
|
|
* A null sheet is a tier too: India (sheet null, too many groups for any paper size) is its own
|
|
* 1-of-1 series. Skipping it gave India's sheet "map 1 of 0" on the Regions path and "map 1 of 1" on the
|
|
* wall's old copy. Neither was visible then (the map-only packet page prints no series line, and India's
|
|
* poster is gated), but any India sheet with a header would have shown it.
|
|
* Every null-sheet country shares that ONE oversized series: a second would make India "map 1 of 2".
|
|
* check:country-sheet-one-path fails when that happens, so it is decided, not drifted into.
|
|
*/
|
|
export function seriesFor(countries, item) {
|
|
if (!item || !Array.isArray(countries)) return undefined
|
|
const tier = countries.filter((x) => (x.sheet ?? null) === (item.sheet ?? null))
|
|
const index = tier.findIndex((x) => x.id === item.id) + 1
|
|
if (!index) return undefined
|
|
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', formats: ['60x96', '66x66', '36x60'], desktopOnly: true } }
|
|
export function largeSheet(item) { return (item && LARGE_SHEETS[String(item.iso3 || '').toUpperCase()]) || null }
|
|
// The sheet format a request gets: a large-sheet country prints at the large size ASKED FOR when it has one (66x66,
|
|
// 36x60), else at its default (60x96); a packet's page 1 stays a page; every other country gets exactly what it asked.
|
|
export function sheetFormatFor(item, format) {
|
|
const big = largeSheet(item)
|
|
if (!big || format === 'packet') return format
|
|
return big.formats.includes(format) ? format : big.format
|
|
}
|
|
|
|
// 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
|
|
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
|
|
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 },
|
|
records: rows,
|
|
assetsBase: assetsBase(),
|
|
ISO3_NAME,
|
|
format: sheetFormatFor(item, format),
|
|
series: seriesFor(countries, item),
|
|
})
|
|
}
|
|
}
|