A print's progress moves as the work does: the poster reports each real step (people groups, map outlines, engine, then every drawing stage), yields a frame between them so the page repaints, and shows step n of N with the elapsed seconds instead of fixed percentages. The sheet's loads start together, and the leader-crossing count skips disjoint segment pairs and is no longer counted twice. Every sheet stays byte-identical (286/286).
This commit is contained in:
parent
45a5dd9557
commit
626b1df0f3
8 changed files with 111 additions and 18 deletions
|
|
@ -316,6 +316,31 @@ const countOf = (key, n) => tCount(key, { n: Number(n || 0).toLocaleString() },
|
|||
// The HUD's kinds and progress messages, and the poster image's alt text (what a screen reader announces
|
||||
// for a card), in the page's language; English only where a key is absent. The same translator goes to
|
||||
// usePosterPrints (opts.t) so the messages IT sets are localized too.
|
||||
// ★ A PRINT'S REAL STEPS (the Driver: a bar that moves as the work moves, never frozen, no invented percentages).
|
||||
// The sheet reports each step by name (countrySheet / posterSheets / renderPoster's onStep), the bar is the share of
|
||||
// the KNOWN steps done, and the elapsed seconds tick on their own whenever the page can paint.
|
||||
const PRINT_STEPS = ['records', 'boundaries', 'generator', 'map-frame', 'neighbours', 'seas', 'pins', 'side-lanes', 'cities', 'names', 'header', 'print']
|
||||
const STEP_EN = {
|
||||
records: 'loading the people groups', boundaries: 'loading the map outlines', generator: 'loading the poster engine',
|
||||
'map-frame': 'fitting the map', neighbours: 'drawing the neighbouring provinces', seas: 'naming the seas',
|
||||
pins: 'placing the pins', 'side-lanes': 'laying out the side lanes', cities: 'adding the cities',
|
||||
names: 'placing the names', header: 'writing the header', print: 'opening the print dialog',
|
||||
}
|
||||
const stepKey = (name) => name.replace(/-([a-z])/g, (m, c) => c.toUpperCase())
|
||||
const nowMs = ref(Date.now())
|
||||
let elapsedTick = null
|
||||
const elapsedS = computed(() => (exportState.value && exportState.value.startedAt ? Math.max(0, Math.floor((nowMs.value - exportState.value.startedAt) / 1000)) : 0))
|
||||
function printStepper() {
|
||||
let done = 0
|
||||
return (name) => {
|
||||
const i = PRINT_STEPS.indexOf(name)
|
||||
if (i < 0 || !exportState.value) return
|
||||
done = Math.max(done, i + 1)
|
||||
exportState.value = { ...exportState.value, phase: 'render', message: hud(`wall.hud.step.${stepKey(name)}`, STEP_EN[name]),
|
||||
stepN: done, stepM: PRINT_STEPS.length, pct: Math.round((100 * done) / PRINT_STEPS.length) }
|
||||
}
|
||||
}
|
||||
|
||||
const hud = (key, en, params = null, n = undefined) => {
|
||||
const v = tCount(key, params || {}, n)
|
||||
return v && v !== key ? v : String(en).replace(/\{(\w+)\}/g, (m, k) => (params && params[k] != null ? String(params[k]) : m))
|
||||
|
|
@ -556,6 +581,12 @@ function showOnMap(item) {
|
|||
// than by keeping two renderers in sync, and it works on Android where the Node
|
||||
// generator cannot run at all.
|
||||
const exportState = ref(null)
|
||||
// the elapsed seconds tick for the life of a print's HUD (declared here: the watch reads exportState at setup)
|
||||
watch(() => exportState.value && exportState.value.startedAt, (st) => {
|
||||
clearInterval(elapsedTick); elapsedTick = null
|
||||
if (st) { nowMs.value = Date.now(); elapsedTick = setInterval(() => { nowMs.value = Date.now() }, 250) }
|
||||
})
|
||||
onBeforeUnmount(() => { clearInterval(elapsedTick) })
|
||||
const { scrimRef, scrimStyle } = useViewportScrim(exportState)
|
||||
|
||||
async function exportPoster(item) {
|
||||
|
|
@ -566,7 +597,8 @@ async function exportPoster(item) {
|
|||
return
|
||||
}
|
||||
exportState.value = { label: item.label, kind: hud('wall.hud.kindPoster', 'Poster'), phase: 'fetch',
|
||||
message: hud('wall.hud.fetchingPoster', 'fetching the poster'), pct: 20 }
|
||||
message: hud('wall.hud.fetchingPoster', 'fetching the poster'), pct: 0, startedAt: Date.now() }
|
||||
const step = printStepper()
|
||||
try {
|
||||
// ★ GENERATED FIRST (the print standard): a host that can draw the sheet now (the `sheet` prop,
|
||||
// packet/posterSheets.js) prints or downloads THAT — no stored file involved, so a country, which has
|
||||
|
|
@ -574,15 +606,16 @@ async function exportPoster(item) {
|
|||
// for a home printer), the item's own poster size for a download. A generation that fails falls through
|
||||
// to the stored image and HTML below rather than to an error.
|
||||
if (props.sheet) {
|
||||
exportState.value = { ...exportState.value, phase: 'render', message: hud('wall.hud.generatingPoster', 'generating the poster'), pct: 35 }
|
||||
exportState.value = { ...exportState.value, phase: 'render', message: hud('wall.hud.generatingPoster', 'generating the poster') }
|
||||
const format = props.posterAction === 'print' ? 'letter' : (item.sheet === '3x3' ? '3x3' : '3x4')
|
||||
const html = await Promise.resolve().then(() => props.sheet(item, format)).catch(() => '')
|
||||
const html = await Promise.resolve().then(() => props.sheet(item, format, { onStep: step })).catch(() => '')
|
||||
if (html) {
|
||||
const { rasterPoster, looksRendered, hasExternalImages } = await import('@map/packet/poster-raster.js')
|
||||
// ★ A SHEET WITH PHOTOS PRINTS AS ITS OWN DOCUMENT (the large side-lane sheets): a raster cannot carry a
|
||||
// photo from its own host (poster-raster.js says why), and the sheet's own @page is its true size
|
||||
// (60in 96in), where an image of it would be shrunk onto letter. A download still rasterises, without them.
|
||||
if (props.posterAction === 'print' && hasExternalImages(html)) { await printSheetDocument(html); return }
|
||||
step('print')
|
||||
const blob = await rasterPoster(html, { scale: 2 })
|
||||
if (await looksRendered(blob)) { await deliverPoster(blob, item); return }
|
||||
}
|
||||
|
|
@ -1377,7 +1410,7 @@ provide(POSTER_WALL_CTX, {
|
|||
|
||||
<template v-else>
|
||||
<div class="bar"><div class="bar-fill" :style="{ width: exportState.pct + '%' }"></div></div>
|
||||
<div class="modal-phase">{{ exportState.message }}</div>
|
||||
<div class="modal-phase">{{ exportState.message }}<template v-if="exportState.stepN"> · {{ hud('wall.hud.stepOf', 'step {n} of {m}', { n: exportState.stepN, m: exportState.stepM }) }}</template><template v-if="elapsedS >= 1"> · {{ hud('wall.hud.elapsed', '{s} s', { s: elapsedS }) }}</template></div>
|
||||
<div class="modal-hint">
|
||||
{{ t('buttons.renderingLocal') }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -93,7 +93,23 @@
|
|||
"posterNotFound": "poster not found ({status})",
|
||||
"posterBlank": "the generated poster came out blank",
|
||||
"noApi": "no people-groups service is configured",
|
||||
"fetchFailed": "could not load the people groups ({status})"
|
||||
"fetchFailed": "could not load the people groups ({status})",
|
||||
"step": {
|
||||
"records": "loading the people groups",
|
||||
"boundaries": "loading the map outlines",
|
||||
"generator": "loading the poster engine",
|
||||
"mapFrame": "fitting the map",
|
||||
"neighbours": "drawing the neighbouring provinces",
|
||||
"seas": "naming the seas",
|
||||
"pins": "placing the pins",
|
||||
"sideLanes": "laying out the side lanes",
|
||||
"cities": "adding the cities",
|
||||
"names": "placing the names",
|
||||
"header": "writing the header",
|
||||
"print": "opening the print dialog"
|
||||
},
|
||||
"stepOf": "step {n} of {m}",
|
||||
"elapsed": "{s} s"
|
||||
},
|
||||
"posterAlt": "{label} — prayer poster",
|
||||
"posterAltAction": "{label} — prayer poster (click to preview and print)"
|
||||
|
|
|
|||
|
|
@ -134,16 +134,21 @@ async function adminAreasFor(iso3, base) {
|
|||
}
|
||||
|
||||
export function makeCountrySheet({ records, assetsBase, countries = null }) {
|
||||
return async function countrySheet(item, format) {
|
||||
// opts.onStep: the print HUD's real steps (PosterWall): 'records', then the drawing's own (posterSheets / renderPoster).
|
||||
return async function countrySheet(item, format, opts = {}) {
|
||||
const iso3 = item && item.iso3
|
||||
if (!iso3) return '' // a selection or a row without a country: no sheet
|
||||
if (posterWithheld(item)) return '' // a withheld poster (above): nothing is fetched or drawn
|
||||
// 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 onStep = (opts && typeof opts.onStep === 'function') ? opts.onStep : null
|
||||
if (onStep) { try { onStep('records') } catch (_) { /* the caller's display */ } }
|
||||
const { generateCountrySheet, warmSheet } = await import('@map/packet/posterSheets.js')
|
||||
const warm = warmSheet ? warmSheet(assetsBase()) : null // the boundaries + engine load WHILE the records do
|
||||
const [{ ISO3_NAME }, rows] = await Promise.all([
|
||||
import('@map/data/iso3-name.js'),
|
||||
records({ type: 'country', values: [iso3] }),
|
||||
warm,
|
||||
])
|
||||
if (!rows || !rows.length) return '' // nothing to pin: fall through rather than draw an empty country
|
||||
const big = largeSheet(item)
|
||||
|
|
@ -157,6 +162,7 @@ export function makeCountrySheet({ records, assetsBase, countries = null }) {
|
|||
ISO3_NAME,
|
||||
format: sheetFormatFor(item, format),
|
||||
series: seriesFor(countries, item),
|
||||
onStep,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
// The sha384 of library/packet/poster-render.js. The browser fetches the served copy of that file
|
||||
// (<assets>/lib/poster-render.js), hashes it, and runs it only when it matches this pin: code loaded at
|
||||
// print time is integrity-pinned like every embed, because it executes on partner pages.
|
||||
export const POSTER_RENDER_SHA384 = 'sha384-sR7a+BDeXAxY0eZS+crqAXtYtVySYAeDKSek4aHPFYzsUaq9pVi+v2RkmkwjadCV'
|
||||
export const POSTER_RENDER_SHA384 = 'sha384-AM43ejAFlUq5/1D5V/vIhQhohlg/xU6SmM3CRn53/AbULq+lVj+D/M9ylfd3+3ry'
|
||||
// The sha384 of library/scripts/posters/data/ne-places.json, served at <assets>/boundaries/ne-places.json.
|
||||
// Data, not code, but pinned the same way: a sheet uses the places only when they are the pinned ones.
|
||||
export const NE_PLACES_SHA384 = 'sha384-Qtz2q8mcnA9Ei8Kwa2MRMyX95hSbSnUXjXdtsy8rHTX++mgSCih4LO4Rdrb2MIQy'
|
||||
// The sha384 of the classic-script copy (<assets>/lib/poster-render.global.js, globalVariant of the file above),
|
||||
// loaded with <script integrity> where a page's policy forbids importing a blob: of the verified text.
|
||||
export const POSTER_RENDER_GLOBAL_SHA384 = 'sha384-bc9b4cbOEA6LG0PxZhSYvOQPd+f7wptSbTT2knIUx+U7U+4KTHgttWKGPQDkJaoJ'
|
||||
export const POSTER_RENDER_GLOBAL_SHA384 = 'sha384-3tatEkji15fqJiSe+XdnRxXotDEXRSnAq5rrwiqpx4UHd7vzJbidUvILn0YGe0rT'
|
||||
// The sha384 of library/scripts/posters/packet-strings.json, served at <assets>/lib/packet-strings.json. The
|
||||
// packet, list and card text in every language, fetched at print time (packet/packetStrings.js) and used only
|
||||
// when it matches; otherwise the print falls back to the bundled English block.
|
||||
|
|
|
|||
|
|
@ -30,10 +30,20 @@
|
|||
export function createPosterGenerator({
|
||||
env = {}, SERIES_DEF = {}, ISO3_NAME = {}, adm0 = null, adm1Ind = null, adm1Chn = null, nePlaces = [],
|
||||
fetchGrid = null, bestZoom = null, series = { totalGroups: 0, totalLists: 0, index: 1 },
|
||||
onStep = null,
|
||||
} = {}) {
|
||||
// The moved code reads process.env.X; this LOCAL process makes that read the caller's env object.
|
||||
const process = { env };
|
||||
const SERIES = series;
|
||||
// ★ PROGRESS THAT NEVER FREEZES (the print HUD): with onStep, renderPoster reports each stage by name and YIELDS a
|
||||
// frame between stages, so the page repaints (the step, the elapsed time) instead of one long frozen task. Without
|
||||
// onStep (Node, the proofs) a step is a no-op: `await undefined` changes no state, so every sheet stays byte-identical.
|
||||
const __step = onStep
|
||||
? (name) => {
|
||||
try { onStep(name); } catch (_) { /* the caller's own display */ }
|
||||
return new Promise((r) => (typeof requestAnimationFrame === 'function' ? requestAnimationFrame(() => setTimeout(r, 0)) : setTimeout(r, 0)));
|
||||
}
|
||||
: () => undefined;
|
||||
// ── PIN BBOX: antimeridian-aware, outlier-trimmed ───────────────────────────
|
||||
// Two independent failure modes made three posters render the WHOLE GLOBE:
|
||||
//
|
||||
|
|
@ -191,10 +201,14 @@ function segCross(p, q) {
|
|||
}
|
||||
function countPolyCrossings(polys) {
|
||||
const segs = [];
|
||||
polys.forEach((pts, li) => { for (let i = 0; i < pts.length - 1; i++) segs.push({ s: [pts[i], pts[i + 1]], li }); });
|
||||
// Each segment carries its bbox: a proper crossing needs the two bboxes to overlap, so a disjoint pair is never
|
||||
// counted by segCross either; skipping it gives the same count without the orientation tests (print speed).
|
||||
polys.forEach((pts, li) => { for (let i = 0; i < pts.length - 1; i++) { const a = pts[i], b = pts[i + 1];
|
||||
segs.push({ s: [a, b], li, x0: Math.min(a[0], b[0]), x1: Math.max(a[0], b[0]), y0: Math.min(a[1], b[1]), y1: Math.max(a[1], b[1]) }); } });
|
||||
let n = 0;
|
||||
for (let i = 0; i < segs.length; i++) for (let j = i + 1; j < segs.length; j++) {
|
||||
if (segs[i].li === segs[j].li) continue;
|
||||
if (segs[i].x1 < segs[j].x0 || segs[j].x1 < segs[i].x0 || segs[i].y1 < segs[j].y0 || segs[j].y1 < segs[i].y0) continue;
|
||||
if (segCross(segs[i].s, segs[j].s)) n++;
|
||||
}
|
||||
return n;
|
||||
|
|
@ -1085,6 +1099,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
: [_w * 0.22 + floorDeg, _h * 0.22 + floorDeg];
|
||||
const viewBbox = [bbox[0] - pad[0], bbox[1] - pad[1], bbox[2] + pad[0], bbox[3] + pad[1]];
|
||||
|
||||
await __step('map-frame');
|
||||
// ── FIT THE MAP FRAME TO THE REGION'S SHAPE ────────────────────────────────
|
||||
// The map frame was a fixed portrait rect (930x1580, aspect 0.59) but this
|
||||
// region is LANDSCAPE (aspect 1.26). makeProjection fits with Math.min(sx,sy),
|
||||
|
|
@ -1277,6 +1292,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
ctryNames += `<text x="${L.x.toFixed(1)}" y="${L.y.toFixed(1)}" text-anchor="middle" class="ctry-name-halo">${t}</text>`
|
||||
+ `<text x="${L.x.toFixed(1)}" y="${L.y.toFixed(1)}" text-anchor="middle" class="${cls}">${t}</text>`;
|
||||
}
|
||||
await __step('neighbours');
|
||||
// ── NEIGHBOURING PROVINCES (context ADM1) ─────────────────────────────────
|
||||
// Previously only the ZONE's provinces were drawn, so everything around the
|
||||
// highlighted cluster was one undifferentiated wash — a reader could not tell
|
||||
|
|
@ -1320,6 +1336,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
adm1Labels.push({ x: lx, y: ly, nm: String(nm), own: true });
|
||||
}
|
||||
}
|
||||
await __step('seas');
|
||||
// ── SEA LABELS ─────────────────────────────────────────────────────────────
|
||||
// Water rendered as an unlabelled pale field, so a reader could not tell ocean
|
||||
// from a province that failed to draw — the operator read the Arabian Sea as a
|
||||
|
|
@ -1470,6 +1487,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
// crossing. Moving pins AFTER the sort silently invalidated it — measured 618
|
||||
// crossings at f=0 where the ordering-preserving version measures 0.
|
||||
const prePins = pins.map((p) => { const [lx, ly] = proj(p.lng, p.lat); return { ...p, _px: lx, _py: ly }; });
|
||||
await __step('pins');
|
||||
// ── PIN DE-OVERLAP (relaxation, pixel space, AFTER projection) ────────────
|
||||
// The earlier version only caught EXACT pixel collisions, which missed the real
|
||||
// problem: pins that land 2-3px apart still overlap visually and still read as
|
||||
|
|
@ -1531,6 +1549,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
// Feed the settled pixel positions back as lng/lat-free overrides.
|
||||
const settled = prePins.map((p) => ({ ...p, _fixed: true }));
|
||||
|
||||
await __step('side-lanes');
|
||||
// ── SIDE LANES: a whole country's groups on one large sheet (FMT.sideLanes) ───────────────────────────
|
||||
// Every name HORIZONTAL, on the left and right of the map, in two STAGGERED columns per side: the rows alternate
|
||||
// inner/outer at half the line pitch, so each column keeps a full pitch and each row holds exactly ONE name.
|
||||
|
|
@ -1658,6 +1677,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
// What the city layer must keep clear of: the drawn pins (laid out, or true positions on a side-lane sheet).
|
||||
const avoidPins = sides ? sides.pins : laid;
|
||||
|
||||
await __step('cities');
|
||||
// ── CAPITALS + MAJOR CITIES ────────────────────────────────────────────────
|
||||
// Orientation marks, deliberately SUBORDINATE to the people-group pins. Imhof's
|
||||
// figure-ground again: this layer answers "where am I?" and must never compete
|
||||
|
|
@ -1808,6 +1828,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
let leaders = '', dots = '', labels = '', pinDots = '';
|
||||
let octiOK = true;
|
||||
|
||||
await __step('names');
|
||||
// ── NUMBERED-LEGEND MODE (dense posters) ──────────────────────────────────
|
||||
// A previous builder PROVED the leader design is geometrically contradictory,
|
||||
// not badly tuned: "labels evenly filling the margin" + "non-horizontal leaders"
|
||||
|
|
@ -2061,9 +2082,11 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
});
|
||||
if (sides) { leaders += sides.leaders; pinDots += sides.pinDots; labels += sides.labels; }
|
||||
if (!octiOK) console.warn(` ⚠ non-octilinear segment in ${zone.zoneKey}`);
|
||||
const crossings = sides ? sides.crossings : countPolyCrossings(routes);
|
||||
// routes = routesFor(bestD), whose crossings the sweep just counted (bestX): reuse it rather than count twice.
|
||||
const crossings = sides ? sides.crossings : (Number.isFinite(bestX) ? bestX : countPolyCrossings(routes));
|
||||
const diagFrac = bestD;
|
||||
|
||||
await __step('header');
|
||||
// ── TITLE vs SUBTITLE ──────────────────────────────────────────────────────
|
||||
// A TITLE IS NEVER A LIST. The India/China lists carry labels like "Rajasthan +
|
||||
// Haryana + Himachal Pradesh + Jammu and Kashmir + Punjab + Chandigarh + Ladakh"
|
||||
|
|
|
|||
|
|
@ -125,9 +125,11 @@ function loadPlaces(assetsBase) {
|
|||
export async function generateSheet({
|
||||
zone, pins = [], zoneFC = null, format = '3x4', seriesId = 'upg100', ISO3_NAME = {},
|
||||
adm0 = null, adm1Ind = null, adm1Chn = null, series = { totalGroups: 0, totalLists: 0, index: 1 }, date = null,
|
||||
assetsBase = null, nePlaces = null, sparse = format !== 'packet',
|
||||
assetsBase = null, nePlaces = null, sparse = format !== 'packet', onStep = null,
|
||||
} = {}) {
|
||||
if (!zone || !zone.zoneKey) return ''
|
||||
// the print HUD's steps (PosterWall): the engine loads, then each stage of the drawing reports itself (renderPoster)
|
||||
if (onStep) { try { onStep('generator') } catch (_) { /* the caller's display */ } }
|
||||
const [{ seriesAll }, places, createPosterGenerator] = await Promise.all([
|
||||
loadShared(), nePlaces ? Promise.resolve(nePlaces) : loadPlaces(assetsBase), loadGenerator(assetsBase),
|
||||
])
|
||||
|
|
@ -148,6 +150,7 @@ export async function generateSheet({
|
|||
// UPG-100 packet's page 1 is its full letter poster).
|
||||
env: { FORMAT: format, ...(sparse ? { SPARSE: '1' } : {}), ...(date ? { POSTER_DATE: date } : {}) },
|
||||
SERIES_DEF, ISO3_NAME, adm0, adm1Ind, adm1Chn, nePlaces: places, series: { totalGroups: 0, totalLists: 0, index: 1, ...series },
|
||||
onStep,
|
||||
})
|
||||
const fc = zoneFC || { type: 'FeatureCollection', features: [] }
|
||||
const out = await gen.renderPoster(zone, pins, fc)
|
||||
|
|
@ -283,11 +286,20 @@ export function pinsFrom(records) {
|
|||
* A country's sheet, generated now: its people groups (records, with coordinates) on its own outline.
|
||||
* @param country { id, iso3, label, count } — one entry of library/data/country-posters.json
|
||||
*/
|
||||
export async function generateCountrySheet({ country, records, assetsBase, ISO3_NAME, format = 'letter', series, date = null }) {
|
||||
// ★ START EVERYTHING A SHEET NEEDS AT ONCE (print speed): the boundaries, the engine, the places table and the
|
||||
// series text are independent, cached loads, and a print used to fetch them one after another (records, then the
|
||||
// boundaries, then the rest). A caller starts this beside its records; the later calls get the same cached results,
|
||||
// so the sheet is unchanged. Failures are left to the real call, which reports them.
|
||||
export function warmSheet(assetsBase) {
|
||||
return Promise.all([loadBoundaries(assetsBase), loadShared(), loadPlaces(assetsBase), loadGenerator(assetsBase)]).catch(() => {})
|
||||
}
|
||||
|
||||
export async function generateCountrySheet({ country, records, assetsBase, ISO3_NAME, format = 'letter', series, date = null, onStep = null }) {
|
||||
const zone = countryZone(country)
|
||||
if (onStep) { try { onStep('boundaries') } catch (_) { /* the caller's display */ } }
|
||||
// The FULL boundary set, as the Node driver passes it: a country beside India or China draws their
|
||||
// provinces too (outlines, names, sea-label land), and a sheet fed ADM0 alone differs from Node's.
|
||||
const { adm0, adm1Ind, adm1Chn } = await loadBoundaries(assetsBase)
|
||||
const zoneFC = countryGeometry(zone, adm0, (records || []).length > 0)
|
||||
return generateSheet({ zone, pins: pinsFrom(records), zoneFC, format, seriesId: 'country', ISO3_NAME, adm0, adm1Ind, adm1Chn, series, date, assetsBase })
|
||||
return generateSheet({ zone, pins: pinsFrom(records), zoneFC, format, seriesId: 'country', ISO3_NAME, adm0, adm1Ind, adm1Chn, series, date, assetsBase, onStep })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,12 +186,12 @@ const sheetForRecord = makeCountrySheet({
|
|||
assetsBase: () => assetsBase.value,
|
||||
countries: data.countries || [],
|
||||
})
|
||||
const countryOnlySheet = (card, format) => sheetForRecord(byId.get(card.id) || card, format)
|
||||
const countryOnlySheet = (card, format, opts) => sheetForRecord(byId.get(card.id) || card, format, opts)
|
||||
// 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,
|
||||
})
|
||||
const countrySheet = (card, format) => (card.indiaState ? stateSheet(card, format) : countryOnlySheet(card, format))
|
||||
const countrySheet = (card, format, opts) => (card.indiaState ? stateSheet(card, format, opts) : countryOnlySheet(card, format, opts))
|
||||
// The name table the generated sheet was labelled from, so a packet in another language relabels page 1's
|
||||
// country names (the same seam the UPG-100 wall passes).
|
||||
const iso3Names = () => import('@map/data/iso3-name.js').then((m) => m.ISO3_NAME)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ export function stateGeometry(zone, adm1Ind, shapeName) {
|
|||
* @returns {(item, format) => Promise<string>}
|
||||
*/
|
||||
export function makeIndiaStateSheet({ records, assetsBase, states, region = 'Asia' }) {
|
||||
return async function indiaStateSheet(item, format) {
|
||||
return async function indiaStateSheet(item, format, opts = {}) {
|
||||
const onStep = (opts && typeof opts.onStep === 'function') ? opts.onStep : null
|
||||
if (onStep) { try { onStep('records') } catch (_) { /* the caller's display */ } }
|
||||
const all = states() || []
|
||||
const state = all.find((s) => s.id === (item && item.id))
|
||||
if (!state || !state.count) return ''
|
||||
|
|
@ -73,6 +75,7 @@ export function makeIndiaStateSheet({ records, assetsBase, states, region = 'Asi
|
|||
records({ type: 'pg_slugs', values: state.slugs }),
|
||||
])
|
||||
if (!rows || !rows.length) return ''
|
||||
if (onStep) { try { onStep('boundaries') } catch (_) { /* the caller's display */ } }
|
||||
const bounds = await loadBoundaries(assetsBase())
|
||||
const zone = stateZone(state, countryRegionColor(region))
|
||||
const geo = stateGeometry(zone, bounds.adm1Ind, state.shapeName)
|
||||
|
|
@ -80,7 +83,7 @@ export function makeIndiaStateSheet({ records, assetsBase, states, region = 'Asi
|
|||
return generateSheet({
|
||||
zone, pins: pinsFrom(rows), zoneFC: geo.zoneFC, format, seriesId: 'indiaState', ISO3_NAME,
|
||||
adm0: bounds.adm0, adm1Ind: geo.adm1, adm1Chn: bounds.adm1Chn,
|
||||
series: stateSeries(all, state), assetsBase: assetsBase(),
|
||||
series: stateSeries(all, state), assetsBase: assetsBase(), onStep,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue