From c67b091df62af41ac096ebe80b67288ae5f4a832 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 05:55:52 +0000 Subject: [PATCH 01/14] Print on phones: print the page itself, and offer a Print button when the tap has expired Every poster, prayer list and card deck printed from a hidden iframe (openPrintFrame), and phone browsers do not honour a print() inside a subframe: no print dialog ever opened on a phone. printDocument() keeps that iframe path unchanged on desktop; on a phone or tablet it mounts the document into the page under a print-only stylesheet that hides everything else, and calls the page's own window.print(). When preparing the document outlasted the tap's activation, the wall's progress dialog shows a Print button so the print comes from a fresh tap. The poster wall (Regions, UPG-100, Country Posters) is the first user. --- library/components/poster/PosterWall.vue | 29 +- library/composables/printInPlace.js | 101 ++++++ library/composables/usePosterPrints.js | 43 ++- library/i18n/locales/ar/wall.json | 2 + library/i18n/locales/de/wall.json | 2 + library/i18n/locales/en/wall.json | 2 + library/i18n/locales/es/wall.json | 2 + library/i18n/locales/fr/wall.json | 2 + library/i18n/locales/hi/wall.json | 2 + library/i18n/locales/it/wall.json | 2 + library/i18n/locales/pt/wall.json | 2 + library/i18n/locales/ro/wall.json | 2 + library/i18n/locales/ru/wall.json | 2 + library/i18n/locales/zh/wall.json | 2 + .../country-posters/countries-map/index.html | 2 +- .../country-poster-wall/index.html | 2 +- .../country-posters-mfe/index.html | 2 +- .../country-posters/country-posters.js | 329 +++++++++--------- public/dx-maps/doxa-maps-spa/manifest.json | 36 +- public/dx-maps/manifest.json | 36 +- .../my-upg-100-list/my-upg-100-list.js | 227 ++++++------ .../my-upg-100-list/poster-wall/index.html | 2 +- .../my-upg-100-list/upg-100-list/index.html | 2 +- .../my-upg-100-list/upg-100-mfe/index.html | 2 +- .../region-dashboards/index.html | 2 +- .../regions-dashboard/regions-dashboard.js | 203 +++++------ .../regions-dashboard/regions-list/index.html | 2 +- .../regions-dashboard/regions-mfe/index.html | 2 +- 28 files changed, 602 insertions(+), 442 deletions(-) diff --git a/library/components/poster/PosterWall.vue b/library/components/poster/PosterWall.vue index b15fe3199..6f2b3e109 100644 --- a/library/components/poster/PosterWall.vue +++ b/library/components/poster/PosterWall.vue @@ -528,6 +528,8 @@ if (props.cardStocks) { const prints = usePosterPrints({ t: (key, params, n) => tCount(key, params, n), state: exportState, + // This wall's HUD renders phase 'ready' as a Print button (a phone whose tap expired while preparing). + tapToPrint: true, records: props.records ? (item) => props.records(item) : null, // useI18n()'s locale can be a ref or a plain string depending on how the host // installed it, so normalise rather than assume. @@ -639,13 +641,14 @@ function printPacketAs(item, variantId) { async function deliverPoster(blob, item) { if (props.posterAction === 'print') { exportState.value = { ...exportState.value, phase: 'save', message: hud('wall.hud.openingPrint', 'opening the print dialog'), pct: 80 } - const { openPrintFrame } = await import('@map/composables/printInPlace.js') + const { printDocument } = await import('@map/composables/printInPlace.js') const url = URL.createObjectURL(blob) posterObjectUrls.push(url) // A 3ft SQUARE on a PORTRAIT sheet must fit by its SHORTER axis: scaling to the // full page WIDTH makes it taller than the printable height and it overflows onto // a second page. Constrain both axes and let object-fit letterbox it. - openPrintFrame( + // printDocument: the in-place frame as before, or on a phone the PAGE itself (printInPlace.js says why). + const how = await printDocument( '' + esc(`${props.downloadPrefix} — ${item.label || item.id}`) + '' + '' + `${esc(item.label || item.id)}`, - { autoprint: true }, + { onNeedsTap: (printNow, cancel) => { + exportState.value = { ...exportState.value, phase: 'ready', message: hud('wall.hud.readyToPrint', 'ready to print'), pct: 100, + action: () => { exportState.value = null; printNow() }, cancel } + } }, ) + // The reader now owns the next step: the Print button in the HUD. + if (how === 'needs-tap') return } else { exportState.value = { ...exportState.value, phase: 'save', message: hud('wall.hud.saving', 'saving'), pct: 80 } download(blob, `${props.downloadPrefix}-${item.id}-poster.webp`) @@ -688,7 +696,13 @@ function download(blob, name) { setTimeout(() => URL.revokeObjectURL(a.href), 4000) } -function dismissExport() { exportState.value = null } +// A 'ready' print that the reader walks away from takes its document with it, so the browser's own Print +// menu prints the page again rather than a poster nobody asked for. +function dismissExport() { + const s = exportState.value + exportState.value = null + try { if (s && typeof s.cancel === 'function') s.cancel() } catch (_) { /* already gone */ } +} useShadowStyles( // --wall-pad-top is read back by .sec-head::before to cover the scroll-padding strip @@ -1275,7 +1289,7 @@ useShadowStyles( -