UPG-100 batch selection: on a phone, a selection too big to print asks for a computer
Building a selection's Prayer list or Card deck is never the problem (the whole selection is ready in under 30 s on a 4×-throttled phone). PRINTING it is: in a browser capped at 2.5 GB, the print of 1,857 groups (101 sheets, a 138 MB PDF) finished, and 2,209 groups (113 sheets) crashed the page at 1× and at 4×. A phone tab has roughly half that memory, so above 1,000 groups (PHONE_GROUP_CEILING, with the measurement beside it) a phone or tablet gets the house text "This needs a desktop or laptop computer." (poster.phoneLimitAll, as Country Posters shows for whole India) and nothing starts. India alone (905) still prints on a phone. A desktop is never blocked. Copy and CSV stay allowed. The device test is isPhonePrint(), the same platform test the print path uses. check:batch-selection-export arm A7 tests the ceiling both ways. Arm C checks that Prayer list, Card deck and Poster each ask BEFORE they print, and arm D checks the string ×11. Four mutants each fail.
This commit is contained in:
parent
1cd2870941
commit
1fde892d4b
3 changed files with 58 additions and 3 deletions
|
|
@ -183,6 +183,21 @@ ok(!disagree.length, `Copy's regroup warning disagrees with apply-grouping: ${di
|
|||
`a state-only selection is titled "Gujarāt"; a state + a country names both; a province with no table is not named; Mizoram alone reads "${empty}"`)
|
||||
}
|
||||
|
||||
// ── A7. A selection too big for a PHONE'S PRINT starts nothing there ────────────────────────────────────
|
||||
// Printing the whole selection (2,209 groups, 113 sheets) crashed the page in a 2.5 GB browser; 1,857 finished. The
|
||||
// ceiling (half of that, for a phone tab's memory) is recorded beside its measurement in data/selection-export.js.
|
||||
// Self-test: a phone over the ceiling is blocked; a desktop over it is not; a phone at or under it (India alone, 905)
|
||||
// is not. The wiring (arm C) checks each of Prayer list, Card deck and Poster asks BEFORE it prints.
|
||||
{
|
||||
const c = X.PHONE_GROUP_CEILING
|
||||
const src = readFileSync(P('plugin-designs/my-upg-100-list/data/selection-export.js'), 'utf8')
|
||||
const cases = [[2209, true, true], [1001, true, true], [2209, false, false], [c, true, false], [905, true, false]]
|
||||
const wrong = cases.filter(([n, phone, want]) => X.overPhoneCeiling(n, phone) !== want).map(([n, phone]) => `${n} on a ${phone ? 'phone' : 'desktop'}`)
|
||||
ok(c === 1000 && !wrong.length && /2,209 groups +113 sheets +the page CRASHED/.test(src) && /1,857 groups/.test(src),
|
||||
`phone ceiling ${c}: wrong for ${wrong.join(', ') || '(none)'}; the measurement beside it is ${/2,209 groups/.test(src) ? 'present' : 'MISSING'}`,
|
||||
`a phone over ${c} groups prints nothing (2,209 → blocked); a desktop is never blocked; India alone (905) still prints on a phone`)
|
||||
}
|
||||
|
||||
// ── B. the CSV ───────────────────────────────────────────────────────────────────────────────────
|
||||
function parseCsv(s) { // RFC 4180: quoted fields, doubled quotes, CRLF records
|
||||
const rows = []; let row = [], cell = '', q = false
|
||||
|
|
@ -236,6 +251,14 @@ const wired = {
|
|||
const body = vue.slice(at, vue.indexOf('\n })', at))
|
||||
return !/toggleTerritory|toggleByProps|toggleZoneSelection/.test(body)
|
||||
})(),
|
||||
// Each print action asks the phone ceiling BEFORE it prints, and says so in the house words.
|
||||
'a big selection on a phone prints nothing': (() => {
|
||||
const body = (name) => { const at = vue.indexOf(`async function ${name}() {`); return at < 0 ? '' : vue.slice(at, vue.indexOf('\n}\n', at)) }
|
||||
const before = (name, call) => { const b = body(name); const g = b.indexOf('phoneTooBig('), p = b.indexOf(call); return g > 0 && p > g }
|
||||
return before('makeSelectionPacket', 'packetPrints.printPacket(') && before('makeSelectionCards', 'packetPrints.printCardDeck(')
|
||||
&& before('makeSelectionPoster', 'packetPrints.printPoster(') && /flashStatus\(t\('poster\.phoneLimitAll'\)/.test(vue)
|
||||
&& /const ON_PHONE = isPhonePrint\(\);/.test(vue)
|
||||
})(),
|
||||
'the regroup warning on Copy': /const warn = regroupable\(sel\) \? '' : t\('upgMap\.copyNotRegroupable'\)/.test(vue),
|
||||
// The packet / card-deck title goes through selectionTitleNames (arm A6 proves it), with the keyed empty case.
|
||||
'the title names come from selectionTitleNames': /const names = selectionTitleNames\(zoneNames, selectedTerritories\.value, from,\s*\n\s*\(\) => keyedError\('wall\.hud\.noRecords'/.test(vue)
|
||||
|
|
@ -249,6 +272,7 @@ ok(!missingWires.length, `the HUD is missing: ${missingWires.join(', ')}`, 'the
|
|||
// ── D. the strings, ×11 ──────────────────────────────────────────────────────────────────────────
|
||||
const LANGS = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']
|
||||
const KEYS = {
|
||||
poster: ['phoneLimitAll'],
|
||||
upgMap: ['copySelection', 'exportCsv', 'copyNotRegroupable', 'peopleStillLoading', 'selectionColourGroup', 'selectionColour', 'customColour', 'copiedSelection', 'copiedTerritoriesOnly', 'csvSaved', 'csvFailed'] }
|
||||
const cat = (l, f) => JSON.parse(readFileSync(P('library/i18n/locales', l, `${f}.json`), 'utf8'))
|
||||
const bad = []
|
||||
|
|
|
|||
|
|
@ -112,6 +112,23 @@ export function selectionTitleNames(zoneNames, territories, from, noRecords) {
|
|||
return [...zoneNames, ...countries, ...states];
|
||||
}
|
||||
|
||||
/**
|
||||
* ★ A SELECTION TOO BIG FOR A PHONE'S PRINT. Building the document is never the problem: on a 4×-throttled phone
|
||||
* context the WHOLE selection (all 28 territories, 2,209 groups) is ready in 19.5 s as a Prayer list and 27 s as a
|
||||
* card deck. Printing it is. The print/preview path, measured as page.pdf of the same document in a browser capped
|
||||
* at 2.5 GB:
|
||||
* 905 groups (India) 54 sheets 50 MB PDF 45 s at 4×
|
||||
* 1,324 / 1,497 / 1,857 groups 75 / 83 / 101 sheets 98 / 127 / 138 MB 54-62 s at 1×
|
||||
* 2,209 groups 113 sheets the page CRASHED (at 1× and at 4×; ~1.8 GB resident when it died)
|
||||
* A phone tab gets roughly half that memory, so the ceiling is about half the largest that finished: 1,000. India
|
||||
* alone (905) still prints on a phone; anything bigger asks for a computer, in the words Country Posters uses for
|
||||
* whole India (poster.phoneLimitAll). Copy and CSV are cheap and stay allowed.
|
||||
*/
|
||||
export const PHONE_GROUP_CEILING = 1000;
|
||||
export function overPhoneCeiling(count, onPhone, ceiling = PHONE_GROUP_CEILING) {
|
||||
return Boolean(onPhone) && Number(count) > ceiling;
|
||||
}
|
||||
|
||||
/** A file name a partner can recognise: the selection's label, reduced to safe characters. */
|
||||
export function selectionCsvFileName(label) {
|
||||
const base = String(label || 'selection').normalize('NFKD').replace(/[\u0300-\u036f]/g, '')
|
||||
|
|
@ -119,4 +136,4 @@ export function selectionCsvFileName(label) {
|
|||
return `upg-100-${base}.csv`;
|
||||
}
|
||||
|
||||
export default { SELECTION_CSV_COLUMNS, csvCell, payloadName, regroupKind, regroupable, selectionCsv, selectionClipboard, settleWithin, asReaderError, selectionTitleNames, selectionCsvFileName };
|
||||
export default { SELECTION_CSV_COLUMNS, csvCell, payloadName, regroupKind, regroupable, selectionCsv, selectionClipboard, settleWithin, asReaderError, selectionTitleNames, PHONE_GROUP_CEILING, overPhoneCeiling, selectionCsvFileName };
|
||||
|
|
|
|||
|
|
@ -128,7 +128,8 @@ import { colorForZone } from './data/zone-colors.js'
|
|||
// clicked tapestry feature carries shapeName (ADM1) OR iso3 (ADM0) and this maps
|
||||
// either to { key, name, count } from admin-counts/membership (NEVER edits them).
|
||||
import { resolveTerritory, buildCountryNameMap, formatClipboard, ADM1_COUNTS, ISO3_NAME } from './data/territory-counts.js'
|
||||
import { selectionCsv, selectionClipboard, selectionCsvFileName, regroupable, settleWithin, asReaderError, selectionTitleNames } from './data/selection-export.js'
|
||||
import { selectionCsv, selectionClipboard, selectionCsvFileName, regroupable, settleWithin, asReaderError, selectionTitleNames, overPhoneCeiling } from './data/selection-export.js'
|
||||
import { isPhonePrint } from '@map/composables/printInPlace.js'
|
||||
import visionFunnel from '../../data/people-groups/vision-funnel.json'
|
||||
import { upgTotal, countInSentence } from './data/upg-total.js'
|
||||
// 09b-search-bar §1(a-c): unified search index (PG + zone + territory) + the
|
||||
|
|
@ -1998,12 +1999,22 @@ async function exportSelectionCsv() {
|
|||
} finally { batchBusy.value = false; busyAction.value = ''; }
|
||||
}
|
||||
|
||||
// A selection too big for a phone's print (data/selection-export.js has the measurement) starts nothing and says so.
|
||||
// The same platform test the print path uses (isPhonePrint: a phone or tablet prints the page, with a phone's memory).
|
||||
const ON_PHONE = isPhonePrint();
|
||||
function phoneTooBig(count) {
|
||||
if (!overPhoneCeiling(count, ON_PHONE)) return false;
|
||||
flashStatus(t('poster.phoneLimitAll'), 6000);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function makeSelectionPacket() {
|
||||
if (batchBusy.value) return;
|
||||
batchBusy.value = true;
|
||||
busyAction.value = 'packet';
|
||||
try {
|
||||
const sel = await selectionAsList();
|
||||
if (phoneTooBig(sel.count)) return;
|
||||
// The packet's map page is per-zone artwork; a multi-zone selection has no single poster to
|
||||
// lift it from, so it prints a cover WITHOUT the map rather than one zone's map standing in
|
||||
// for all of them. A packet with no sheet says so on page 1.
|
||||
|
|
@ -2031,7 +2042,9 @@ async function makeSelectionCards() {
|
|||
batchBusy.value = true;
|
||||
busyAction.value = 'cards';
|
||||
try {
|
||||
await packetPrints.printCardDeck(await selectionAsList());
|
||||
const sel = await selectionAsList();
|
||||
if (phoneTooBig(sel.count)) return;
|
||||
await packetPrints.printCardDeck(sel);
|
||||
} catch (e) {
|
||||
flashStatus(t('upgMap.selectionCardsFailed', { reason: errorText(e, t).slice(0, 90) }), 4000);
|
||||
} finally {
|
||||
|
|
@ -2064,6 +2077,7 @@ async function makeSelectionPoster() {
|
|||
flashStatus(t('messages.posterOneTerritory', { count: lists.length }), 5000);
|
||||
return;
|
||||
}
|
||||
if (phoneTooBig(lists[0].count)) return;
|
||||
await packetPrints.printPoster(lists[0]);
|
||||
} catch (e) {
|
||||
flashStatus(t('messages.selectionPosterFailed', { reason: errorText(e, t).slice(0, 90) }), 4000);
|
||||
|
|
|
|||
Loading…
Reference in a new issue