From cc6e7cb6c464931ec794448cef47a07846fa46ce Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 04:46:06 +0000 Subject: [PATCH] PosterDialog prints by default instead of downloading, like PosterWall The poster dialog had only a download path: its Render button saved a file, the same trap PosterWall's old default was (gospel's Driver-lens audit, card 7). A new action prop defaults to 'print': a PNG poster opens the print dialog in place (printImageBlob, sized to the chosen page), a PDF opens in the browser's viewer, and a blocked print frame or popup falls back to the file. 'download' stays an explicit opt-in. --- library/components/poster/PosterDialog.vue | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/library/components/poster/PosterDialog.vue b/library/components/poster/PosterDialog.vue index 96b94f865..e020401f2 100644 --- a/library/components/poster/PosterDialog.vue +++ b/library/components/poster/PosterDialog.vue @@ -119,6 +119,7 @@ import { defaultPosterSpec, posterDefaults } from '../../constants/posterDefault // shell (contain:layout on .vtl-content). See library/utils/viewportScrim.js. import { useViewportScrim } from '../../utils/viewportScrim.js'; import { tStatic } from '../../i18n/index.js'; +import { printImageBlob } from '../../composables/printInPlace.js'; // This dialog's chrome, read out of the catalogue. tStatic and not useI18n: these components mount // inside custom elements that install no i18n plugin, and an unguarded useI18n() throws on the way @@ -127,6 +128,13 @@ import { tStatic } from '../../i18n/index.js'; const T = (key, en) => tStatic(key) || en; const props = defineProps({ + /** + * What the Render button does. 'print' (default, Driver rule 2026-09-28: an export-poster button opens + * the PRINT DIALOG with a preview, never a direct download) prints the rendered poster in place; a PDF + * opens in the browser's viewer, where it can be printed or saved. 'download' saves the file, and a + * consumer that opts in must say why in a `posterAction-download:` comment (check:poster-action-print). + */ + action: { type: String, default: 'print' }, // 'print' | 'download' /** Boolean v-model: dialog visibility. */ modelValue: { type: Boolean, default: false }, /** Function returning the live Mapbox map instance. */ @@ -202,7 +210,17 @@ async function onRender() { const blob = await poster.render(spec.value, format.value); const ext = (format.value === 'pdf') ? 'pdf' : 'png'; const name = (titleText.value || 'poster').replace(/\W+/g, '-').toLowerCase(); - _download(blob, `${name}.${ext}`); + if (props.action === 'download') _download(blob, `${name}.${ext}`); + else if (ext === 'png') { + const s = getPosterSize(sizeId.value); + const opened = printImageBlob(blob, { widthIn: s?.inches?.w || 8.5, heightIn: s?.inches?.h || 11, title: titleText.value || 'poster', alt: titleText.value || '' }); + if (!opened) _download(blob, `${name}.${ext}`); // the host blocked the print frame: a file beats nothing + } else { + const url = URL.createObjectURL(blob); + const w = window.open(url, '_blank', 'noopener'); + if (!w) _download(blob, `${name}.${ext}`); + setTimeout(() => URL.revokeObjectURL(url), 60000); + } emit('rendered', { blob, format: format.value, spec: spec.value }); } catch (err) { // eslint-disable-next-line no-console