From 6dbdd71826fb0258d6414fa5bb62f489ed3e41cf Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 3 Oct 2026 05:57:09 +0000 Subject: [PATCH] Poster Design: stored previews show at once, and one card for each poster kind Styles drew every preview live: 27.7 s to the last of 16 cards on the pilot, ~9 s per UPG100 zone row. A configuration whose exact thumbnail is already stored (same format, same sample, made by the library's generator) now shows it at once: the country 3 x 3 (Nepal), the UPG100 3 x 3 zone poster and the UPG100 packet's page 1. The live render stays one click away. Three poster kinds that exist only as stored posters get a card each, from their stored poster: the city poster (key-cities, Hyderabad), the India state poster (Uttar Pradesh) and the UPG100 prayer cards. Each names its source plugin, its renderer and its stored file; its print is that stored poster on a page. check:poster-design-catalogue arm 9: every preview is a file a plugin ships (poisoned with a missing one). Keys in 11 locales; three ledger names. --- library/i18n/locales/ar/posterDesign.json | 11 +++++-- library/i18n/locales/de/posterDesign.json | 11 +++++-- library/i18n/locales/en/posterDesign.json | 11 +++++-- library/i18n/locales/es/posterDesign.json | 11 +++++-- library/i18n/locales/fr/posterDesign.json | 11 +++++-- library/i18n/locales/hi/posterDesign.json | 11 +++++-- library/i18n/locales/it/posterDesign.json | 11 +++++-- library/i18n/locales/pt/posterDesign.json | 11 +++++-- library/i18n/locales/ro/posterDesign.json | 11 +++++-- library/i18n/locales/ru/posterDesign.json | 11 +++++-- library/i18n/locales/zh/posterDesign.json | 11 +++++-- package.json | 4 +-- .../poster-design/poster-styles.vue | 11 +++++++ .../poster-design/scripts/check-catalogue.mjs | 14 +++++++++ .../poster-design/src/FullDetailsDialog.vue | 6 +++- .../poster-design/src/catalogue-names.json | 5 +++- plugin-designs/poster-design/src/templates.js | 30 ++++++++++++++++++- 17 files changed, 153 insertions(+), 38 deletions(-) diff --git a/library/i18n/locales/ar/posterDesign.json b/library/i18n/locales/ar/posterDesign.json index 02816daec..c6becb6e2 100644 --- a/library/i18n/locales/ar/posterDesign.json +++ b/library/i18n/locales/ar/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "طباعة الخريطة", "plugins": "من إضافات أخرى", "experimental": "تجريبية (قيد العمل)", - "upgZone": "ملصقات مناطق UPG100" + "upgZone": "ملصقات مناطق UPG100", + "cityPoster": "ملصقات المدن" }, "tpl": { "mapSheetCountry": "صفحة خريطة الدولة", @@ -53,7 +54,10 @@ "blessPanels": "ألواح جدار البركة", "mapPrint": "طباعة الخريطة", "upgZonePoster": "ملصق منطقة UPG100", - "upgZonePacket": "حزمة صلاة UPG100، الصفحة 1" + "upgZonePacket": "حزمة صلاة UPG100، الصفحة 1", + "cityPoster": "ملصق مدينة", + "indiaStatePoster": "ملصق ولاية هندية", + "upgZoneCards": "بطاقات صلاة UPG100" }, "missing": { "bless": "الأوراق وشرائط اللصق ومواضع صفوف البطاقات مأخوذة من هندسة الألواح المشتركة؛ وتُعرض البطاقات كتخطيط، لأن جدار البركة يرسم بطاقاته في إضافته الخاصة.", @@ -71,7 +75,8 @@ "renderer": "المولّد", "legacy": "مقاس قديم: ما زال يُطبع، لكن أداة اختيار الطباعة لم تعد تعرضه.", "forAgents": "لاستخدام هذا القالب، قل للوكيل: استخدم الإعداد {name}.", - "openPage": "افتح صفحته" + "openPage": "افتح صفحته", + "storedPreview": "المعاينة المحفوظة" }, "templateCount": "لا قوالب | قالب واحد | قالبان | {n} قوالب | {n} قالبًا | {n} قالب", "fromPlugin": "تصنعه إضافته الخاصة. اعرض كل التفاصيل لتراه مباشرة." diff --git a/library/i18n/locales/de/posterDesign.json b/library/i18n/locales/de/posterDesign.json index f99745d86..cd3d5fa67 100644 --- a/library/i18n/locales/de/posterDesign.json +++ b/library/i18n/locales/de/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Kartendruck", "plugins": "Aus anderen Plugins", "experimental": "Experimentell (in Arbeit)", - "upgZone": "UPG100-Gebietsplakate" + "upgZone": "UPG100-Gebietsplakate", + "cityPoster": "Stadtplakate" }, "tpl": { "mapSheetCountry": "Länder-Kartenblatt", @@ -53,7 +54,10 @@ "blessPanels": "Tafeln der Segnen-Wand", "mapPrint": "Kartendruck", "upgZonePoster": "UPG100-Gebietsplakat", - "upgZonePacket": "UPG100-Gebetsmappe, Seite 1" + "upgZonePacket": "UPG100-Gebetsmappe, Seite 1", + "cityPoster": "Stadtplakat", + "indiaStatePoster": "Plakat eines indischen Bundesstaats", + "upgZoneCards": "UPG100-Gebetskarten" }, "missing": { "bless": "Die Bögen, die Klebestreifen und die Lage der Kartenreihen stammen aus der gemeinsamen Tafelgeometrie; die Karten sind als Layout dargestellt, weil die Segnen-Wand ihre Karten in ihrem eigenen Plugin zeichnet.", @@ -71,7 +75,8 @@ "renderer": "Generator", "legacy": "Alte Größe: Sie wird noch gedruckt, aber die Druckauswahl bietet sie nicht mehr an.", "forAgents": "Um diese Vorlage zu verwenden, sagen Sie einem Agenten: verwende die Konfiguration {name}.", - "openPage": "Seite öffnen" + "openPage": "Seite öffnen", + "storedPreview": "Gespeicherte Vorschau" }, "templateCount": "{n} Vorlage | {n} Vorlagen", "fromPlugin": "Wird von seinem eigenen Plugin erstellt. „Alle Details anzeigen“ zeigt es live." diff --git a/library/i18n/locales/en/posterDesign.json b/library/i18n/locales/en/posterDesign.json index 960706f52..386e00640 100644 --- a/library/i18n/locales/en/posterDesign.json +++ b/library/i18n/locales/en/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Map print", "plugins": "From other plugins", "experimental": "Experimental (in progress)", - "upgZone": "UPG100 zone posters" + "upgZone": "UPG100 zone posters", + "cityPoster": "City posters" }, "tpl": { "mapSheetCountry": "Country map sheet", @@ -53,7 +54,10 @@ "blessPanels": "Bless wall panels", "mapPrint": "Map print", "upgZonePoster": "UPG100 zone poster", - "upgZonePacket": "UPG100 prayer packet, page 1" + "upgZonePacket": "UPG100 prayer packet, page 1", + "cityPoster": "City poster", + "indiaStatePoster": "India state poster", + "upgZoneCards": "UPG100 prayer cards" }, "missing": { "bless": "The sheets, the glue strips and where the card rows go come from the shared panel geometry; the cards are shown as a layout, because the Bless wall draws its cards in its own plugin.", @@ -71,7 +75,8 @@ "renderer": "Renderer", "legacy": "Legacy size: it still prints, but the print picker no longer offers it.", "forAgents": "To use this template, tell an agent: use configuration {name}.", - "openPage": "Open its page" + "openPage": "Open its page", + "storedPreview": "Stored preview" }, "templateCount": "{n} templates | {n} template | {n} templates", "fromPlugin": "Made by its own plugin. Show full details shows it live." diff --git a/library/i18n/locales/es/posterDesign.json b/library/i18n/locales/es/posterDesign.json index 55147e63c..36d9f440a 100644 --- a/library/i18n/locales/es/posterDesign.json +++ b/library/i18n/locales/es/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Impresión del mapa", "plugins": "De otros complementos", "experimental": "Experimentales (en desarrollo)", - "upgZone": "Carteles de zonas UPG100" + "upgZone": "Carteles de zonas UPG100", + "cityPoster": "Carteles de ciudades" }, "tpl": { "mapSheetCountry": "Hoja de mapa del país", @@ -53,7 +54,10 @@ "blessPanels": "Paneles del muro Bendecir", "mapPrint": "Impresión del mapa", "upgZonePoster": "Cartel de zona UPG100", - "upgZonePacket": "Paquete de oración UPG100, página 1" + "upgZonePacket": "Paquete de oración UPG100, página 1", + "cityPoster": "Cartel de ciudad", + "indiaStatePoster": "Cartel de un estado de la India", + "upgZoneCards": "Tarjetas de oración UPG100" }, "missing": { "bless": "Las hojas, las franjas de pegado y el lugar de las filas de tarjetas vienen de la geometría de paneles compartida; las tarjetas se muestran como un diseño, porque el muro Bendecir dibuja sus tarjetas en su propio complemento.", @@ -71,7 +75,8 @@ "renderer": "Generador", "legacy": "Tamaño heredado: todavía se imprime, pero el selector de impresión ya no lo ofrece.", "forAgents": "Para usar esta plantilla, dile a un agente: usa la configuración {name}.", - "openPage": "Abrir su página" + "openPage": "Abrir su página", + "storedPreview": "Vista previa guardada" }, "templateCount": "{n} plantilla | {n} de plantillas | {n} plantillas", "fromPlugin": "Lo crea su propio complemento. Mostrar todos los detalles lo muestra en vivo." diff --git a/library/i18n/locales/fr/posterDesign.json b/library/i18n/locales/fr/posterDesign.json index 545cd8c51..ee764d0df 100644 --- a/library/i18n/locales/fr/posterDesign.json +++ b/library/i18n/locales/fr/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Impression de la carte", "plugins": "D’autres plugins", "experimental": "Expérimentaux (en cours)", - "upgZone": "Affiches de zones UPG100" + "upgZone": "Affiches de zones UPG100", + "cityPoster": "Affiches de villes" }, "tpl": { "mapSheetCountry": "Feuille de carte du pays", @@ -53,7 +54,10 @@ "blessPanels": "Panneaux du mur Bénir", "mapPrint": "Impression de la carte", "upgZonePoster": "Affiche de zone UPG100", - "upgZonePacket": "Dossier de prière UPG100, page 1" + "upgZonePacket": "Dossier de prière UPG100, page 1", + "cityPoster": "Affiche de ville", + "indiaStatePoster": "Affiche d’un État indien", + "upgZoneCards": "Cartes de prière UPG100" }, "missing": { "bless": "Les feuilles, les bandes de collage et l'emplacement des rangées de cartes viennent de la géométrie de panneaux partagée ; les cartes sont montrées comme une mise en page, car le mur Bénir dessine ses cartes dans son propre module.", @@ -71,7 +75,8 @@ "renderer": "Générateur", "legacy": "Format ancien : il s'imprime encore, mais le sélecteur d'impression ne le propose plus.", "forAgents": "Pour utiliser ce modèle, dites à un agent : utilise la configuration {name}.", - "openPage": "Ouvrir sa page" + "openPage": "Ouvrir sa page", + "storedPreview": "Aperçu enregistré" }, "templateCount": "{n} modèle | {n} de modèles | {n} modèles", "fromPlugin": "Créé par son propre plugin. Afficher tous les détails le montre en direct." diff --git a/library/i18n/locales/hi/posterDesign.json b/library/i18n/locales/hi/posterDesign.json index 38bdc75af..a0f14ea5d 100644 --- a/library/i18n/locales/hi/posterDesign.json +++ b/library/i18n/locales/hi/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "मानचित्र प्रिंट", "plugins": "अन्य प्लगइन से", "experimental": "प्रयोगात्मक (काम जारी)", - "upgZone": "UPG100 क्षेत्र पोस्टर" + "upgZone": "UPG100 क्षेत्र पोस्टर", + "cityPoster": "शहर के पोस्टर" }, "tpl": { "mapSheetCountry": "देश का मानचित्र पृष्ठ", @@ -53,7 +54,10 @@ "blessPanels": "आशीष दीवार के पैनल", "mapPrint": "मानचित्र प्रिंट", "upgZonePoster": "UPG100 क्षेत्र का पोस्टर", - "upgZonePacket": "UPG100 प्रार्थना पैकेट, पेज 1" + "upgZonePacket": "UPG100 प्रार्थना पैकेट, पेज 1", + "cityPoster": "शहर का पोस्टर", + "indiaStatePoster": "भारतीय राज्य का पोस्टर", + "upgZoneCards": "UPG100 प्रार्थना कार्ड" }, "missing": { "bless": "शीटें, चिपकाने की पट्टियाँ और कार्ड-पंक्तियों की जगह साझा पैनल ज्यामिति से आती हैं; कार्ड लेआउट के रूप में दिखाए गए हैं, क्योंकि आशीष दीवार अपने कार्ड अपने प्लगइन में बनाती है।", @@ -71,7 +75,8 @@ "renderer": "जनरेटर", "legacy": "पुराना आकार: यह अब भी प्रिंट होता है, पर प्रिंट चयनकर्ता अब इसे नहीं दिखाता।", "forAgents": "यह टेम्पलेट इस्तेमाल करने के लिए किसी एजेंट से कहें: कॉन्फ़िगरेशन {name} इस्तेमाल करो।", - "openPage": "इसका पेज खोलें" + "openPage": "इसका पेज खोलें", + "storedPreview": "सहेजा गया पूर्वावलोकन" }, "templateCount": "{n} टेम्पलेट | {n} टेम्पलेट", "fromPlugin": "यह अपने ही प्लगइन से बनता है। पूरा विवरण दिखाएँ इसे लाइव दिखाता है।" diff --git a/library/i18n/locales/it/posterDesign.json b/library/i18n/locales/it/posterDesign.json index 0e42f6f12..bcdb95c7f 100644 --- a/library/i18n/locales/it/posterDesign.json +++ b/library/i18n/locales/it/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Stampa della mappa", "plugins": "Da altri plugin", "experimental": "Sperimentali (in corso)", - "upgZone": "Poster delle zone UPG100" + "upgZone": "Poster delle zone UPG100", + "cityPoster": "Poster delle città" }, "tpl": { "mapSheetCountry": "Foglio mappa del paese", @@ -53,7 +54,10 @@ "blessPanels": "Pannelli del muro Benedici", "mapPrint": "Stampa della mappa", "upgZonePoster": "Poster di zona UPG100", - "upgZonePacket": "Fascicolo di preghiera UPG100, pagina 1" + "upgZonePacket": "Fascicolo di preghiera UPG100, pagina 1", + "cityPoster": "Poster di città", + "indiaStatePoster": "Poster di uno stato indiano", + "upgZoneCards": "Schede di preghiera UPG100" }, "missing": { "bless": "I fogli, le strisce da incollare e la posizione delle file di schede vengono dalla geometria dei pannelli condivisa; le schede sono mostrate come impaginazione, perché il muro Benedici disegna le sue schede nel proprio plugin.", @@ -71,7 +75,8 @@ "renderer": "Generatore", "legacy": "Formato precedente: si stampa ancora, ma il selettore di stampa non lo offre più.", "forAgents": "Per usare questo modello, di' a un agente: usa la configurazione {name}.", - "openPage": "Apri la sua pagina" + "openPage": "Apri la sua pagina", + "storedPreview": "Anteprima salvata" }, "templateCount": "{n} modello | {n} di modelli | {n} modelli", "fromPlugin": "Creato dal suo plugin. Mostra tutti i dettagli lo mostra dal vivo." diff --git a/library/i18n/locales/pt/posterDesign.json b/library/i18n/locales/pt/posterDesign.json index 24f86ca1d..507d7472b 100644 --- a/library/i18n/locales/pt/posterDesign.json +++ b/library/i18n/locales/pt/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Impressão do mapa", "plugins": "De outros plugins", "experimental": "Experimentais (em andamento)", - "upgZone": "Cartazes de zonas UPG100" + "upgZone": "Cartazes de zonas UPG100", + "cityPoster": "Cartazes de cidades" }, "tpl": { "mapSheetCountry": "Folha de mapa do país", @@ -53,7 +54,10 @@ "blessPanels": "Painéis do mural Abençoar", "mapPrint": "Impressão do mapa", "upgZonePoster": "Cartaz de zona UPG100", - "upgZonePacket": "Pacote de oração UPG100, página 1" + "upgZonePacket": "Pacote de oração UPG100, página 1", + "cityPoster": "Cartaz de cidade", + "indiaStatePoster": "Cartaz de um estado da Índia", + "upgZoneCards": "Cartões de oração UPG100" }, "missing": { "bless": "As folhas, as faixas de colagem e a posição das fileiras de cartões vêm da geometria de painéis compartilhada; os cartões aparecem como um layout, porque o mural Abençoar desenha os seus cartões no seu próprio plugin.", @@ -71,7 +75,8 @@ "renderer": "Gerador", "legacy": "Tamanho antigo: ainda imprime, mas o seletor de impressão já não o oferece.", "forAgents": "Para usar este modelo, diga a um agente: use a configuração {name}.", - "openPage": "Abrir a página" + "openPage": "Abrir a página", + "storedPreview": "Pré-visualização guardada" }, "templateCount": "{n} modelo | {n} de modelos | {n} modelos", "fromPlugin": "Feito pelo próprio plugin. Mostrar todos os detalhes o exibe ao vivo." diff --git a/library/i18n/locales/ro/posterDesign.json b/library/i18n/locales/ro/posterDesign.json index fc9a39279..d3c3a6c1d 100644 --- a/library/i18n/locales/ro/posterDesign.json +++ b/library/i18n/locales/ro/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Tipărirea hărții", "plugins": "Din alte pluginuri", "experimental": "Experimentale (în lucru)", - "upgZone": "Postere de zone UPG100" + "upgZone": "Postere de zone UPG100", + "cityPoster": "Postere de orașe" }, "tpl": { "mapSheetCountry": "Foaie de hartă a țării", @@ -53,7 +54,10 @@ "blessPanels": "Panourile peretelui Binecuvântează", "mapPrint": "Tipărirea hărții", "upgZonePoster": "Poster de zonă UPG100", - "upgZonePacket": "Pachet de rugăciune UPG100, pagina 1" + "upgZonePacket": "Pachet de rugăciune UPG100, pagina 1", + "cityPoster": "Poster de oraș", + "indiaStatePoster": "Poster al unui stat indian", + "upgZoneCards": "Cartonașe de rugăciune UPG100" }, "missing": { "bless": "Colile, benzile de lipire și locul rândurilor de cartonașe vin din geometria comună a panourilor; cartonașele sunt arătate ca machetă, pentru că peretele Binecuvântează își desenează cartonașele în propriul modul.", @@ -71,7 +75,8 @@ "renderer": "Generator", "legacy": "Dimensiune veche: încă se tipărește, dar selectorul de tipărire nu o mai oferă.", "forAgents": "Ca să folosești acest șablon, spune-i unui agent: folosește configurația {name}.", - "openPage": "Deschide pagina" + "openPage": "Deschide pagina", + "storedPreview": "Previzualizare salvată" }, "templateCount": "{n} șablon | {n} șabloane | {n} de șabloane", "fromPlugin": "Este realizat de propriul plugin. Afișează toate detaliile îl arată în direct." diff --git a/library/i18n/locales/ru/posterDesign.json b/library/i18n/locales/ru/posterDesign.json index 2652a74bb..6445cb585 100644 --- a/library/i18n/locales/ru/posterDesign.json +++ b/library/i18n/locales/ru/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "Печать карты", "plugins": "Из других плагинов", "experimental": "Экспериментальные (в работе)", - "upgZone": "Плакаты зон UPG100" + "upgZone": "Плакаты зон UPG100", + "cityPoster": "Плакаты городов" }, "tpl": { "mapSheetCountry": "Лист карты страны", @@ -53,7 +54,10 @@ "blessPanels": "Панели стены «Благословить»", "mapPrint": "Печать карты", "upgZonePoster": "Плакат зоны UPG100", - "upgZonePacket": "Молитвенный пакет UPG100, страница 1" + "upgZonePacket": "Молитвенный пакет UPG100, страница 1", + "cityPoster": "Плакат города", + "indiaStatePoster": "Плакат штата Индии", + "upgZoneCards": "Молитвенные карточки UPG100" }, "missing": { "bless": "Листы, полосы для склейки и места рядов карточек взяты из общей геометрии панелей; карточки показаны как макет, потому что стена «Благословить» рисует свои карточки в собственном плагине.", @@ -71,7 +75,8 @@ "renderer": "Генератор", "legacy": "Устаревший размер: он ещё печатается, но в выборе печати его больше нет.", "forAgents": "Чтобы использовать этот шаблон, скажите агенту: используй конфигурацию {name}.", - "openPage": "Открыть страницу" + "openPage": "Открыть страницу", + "storedPreview": "Сохранённый эскиз" }, "templateCount": "{n} шаблон | {n} шаблона | {n} шаблонов | {n} шаблонов", "fromPlugin": "Создаётся собственным плагином. «Показать все подробности» покажет его вживую." diff --git a/library/i18n/locales/zh/posterDesign.json b/library/i18n/locales/zh/posterDesign.json index bc5890019..2626bb0c7 100644 --- a/library/i18n/locales/zh/posterDesign.json +++ b/library/i18n/locales/zh/posterDesign.json @@ -42,7 +42,8 @@ "mapPrint": "地图打印", "plugins": "来自其他插件", "experimental": "实验性(进行中)", - "upgZone": "UPG100 区域海报" + "upgZone": "UPG100 区域海报", + "cityPoster": "城市海报" }, "tpl": { "mapSheetCountry": "国家地图页", @@ -53,7 +54,10 @@ "blessPanels": "祝福墙展板", "mapPrint": "地图打印", "upgZonePoster": "UPG100 区域海报(单张)", - "upgZonePacket": "UPG100 祷告资料包,第 1 页" + "upgZonePacket": "UPG100 祷告资料包,第 1 页", + "cityPoster": "城市海报(单张)", + "indiaStatePoster": "印度邦海报", + "upgZoneCards": "UPG100 祷告卡" }, "missing": { "bless": "纸张、粘贴条以及卡片行的位置都来自共享的拼板几何;卡片以版式示意,因为祝福墙在其自己的插件中绘制卡片。", @@ -71,7 +75,8 @@ "renderer": "生成器", "legacy": "旧尺寸:仍可打印,但打印选择器已不再提供。", "forAgents": "要使用此模板,请告诉代理:使用配置 {name}。", - "openPage": "打开其页面" + "openPage": "打开其页面", + "storedPreview": "已保存的预览" }, "templateCount": "{n} 个模板", "fromPlugin": "由其自身插件生成。点击“显示全部详情”可实时查看。" diff --git a/package.json b/package.json index 9ddde7c9d..beac0276a 100644 --- a/package.json +++ b/package.json @@ -620,8 +620,8 @@ "check:print-frame-reclaim": "a print frame is kept until it has printed: openPrintFrame reclaims it 60 s after its load (not after its creation), at afterprint, or at a 10-minute ceiling, and printImageBlob revokes its object URL on the same clock. Fake timers; two-sided (the pre-fix copy fails). The Wall poster loads 132 MB of photos, and the old 60 s-from-creation clock removed the frame before it could print.", "check:poster-card-fits": "a poster card's print-options row must stay inside its own card. Reported by the repo agent off a 1440px screenshot: 27/27 cards overflowing by 110px, 36 neighbour intrusions. .pfs already had width:82%, so SOURCE READING SAID IT WAS FINE — flex items default to min-width:auto and refuse to shrink. Asserts BOTH no-overlap AND that the controls keep a usable box, because a row whose buttons collapsed to nothing would also \"not overflow\". Sweeps 390/768/1024/1440/1920px and asserts the row sits BELOW the preview too — measuring only left/right edges is how it passed a driver-visible regression. CARD_FIT_WIDTH overrides the list. SKIPs when nothing is served.", "check:poster-degrades": "with NO stored poster, NO source and NO generator (forced in the browser: every posters/*.webp and *.html answers 404, lib/poster-render.js is aborted), nothing must print and the reader must be told why. Measures any served tree; NOT MEASURED without a wall url.", - "check:poster-design-catalogue": "Poster Design's configuration catalogue (plugin-designs/poster-design/src/templates.js) says only true things about the library: its sheet formats are exactly poster-render.js FORMATS at the same pixel sizes (the generator is a pinned asset, so the table cannot be imported); every configuration name is unique and well formed; the card entries are exactly card-render STOCK; every catalogue key it names exists in en/posterDesign.json; the sample country can be drawn. Six mutants (px drift, a missing format, a duplicate name, a bad key, an undrawable sample, a dropped stock) each FAIL. It also validates every plugin's posterDesign block (plugin-designs//.meta.json: family, name, size, mobileFriendly, optional labelKey and instance), keeps names unique across templates.js and all blocks, and fails when a name in the names ledger (src/catalogue-names.json) was renamed or removed.", - "check:poster-design-catalogue:self-test": "an invalid posterDesign block, a name that clashes with templates.js, a renamed ledger name a block naming a preset that does not exist, and a sample that exists but cannot be drawn (India with withholding off) must each make check:poster-design-catalogue fail, and the documented example block must pass.", + "check:poster-design-catalogue": "Poster Design's configuration catalogue (plugin-designs/poster-design/src/templates.js) says only true things about the library: its sheet formats are exactly poster-render.js FORMATS at the same pixel sizes (the generator is a pinned asset, so the table cannot be imported); every configuration name is unique and well formed; the card entries are exactly card-render STOCK; every catalogue key it names exists in en/posterDesign.json; the sample country can be drawn. Six mutants (px drift, a missing format, a duplicate name, a bad key, an undrawable sample, a dropped stock) each FAIL. It also validates every plugin's posterDesign block (plugin-designs//.meta.json: family, name, size, mobileFriendly, optional labelKey and instance), keeps names unique across templates.js and all blocks, and fails when a name in the names ledger (src/catalogue-names.json) was renamed or removed. Every template's stored preview (templates.js `preview`) must be a file some plugin ships under assets/previews (read from the git index).", + "check:poster-design-catalogue:self-test": "an invalid posterDesign block, a name that clashes with templates.js, a renamed ledger name a block naming a preset that does not exist, a sample that exists but cannot be drawn (India with withholding off), and a preview no plugin ships must each make check:poster-design-catalogue fail, and the documented example block must pass.", "check:upg100-zone-copy": "library/packet/upg100Zones/ is UPG100's zone-poster code copied into the library (so Poster Design draws UPG100 zone posters). COPY mode while UPG100's originals exist: each file equals its original beyond its import specifiers, clusters.json byte for byte. ONE-PATH mode once UPG100 switches and deletes its originals: no plugin file defines the zone code again. Half switched fails.", "check:upg100-zone-copy:self-test": "a one-character edit in a function body, a changed clusters.json, a missing copy, a half switch, and (one-path) a plugin re-defining makeZoneSheet must each fail; an import-specifier-only change and a clean one-path tree must pass.", "check:poster-fetch-guard": "a host's 200 fallback page is never taken for a poster. On a drop-in dev host a missing posters/.webp answered 200 text/html, and export handed that page to the print dialog as the image. Asserts library/packet/poster-fetch.js accepts a raster only if it is an image (by type or by its first bytes) and a source only if it is a poster document (class=\"stage\"), and that neither usePosterPrints nor PosterWall fetches posters/ directly. Node only, no server.", diff --git a/plugin-designs/poster-design/poster-styles.vue b/plugin-designs/poster-design/poster-styles.vue index 2c195b2be..c535beeed 100644 --- a/plugin-designs/poster-design/poster-styles.vue +++ b/plugin-designs/poster-design/poster-styles.vue @@ -180,7 +180,12 @@ async function renderDocument(name) { // previewSrc: the thumbnail. A map sheet is rasterised at a quarter scale (a 1500px sheet becomes ~375px, // the card's size), and a render that comes out blank is refused, so the card shows "preview unavailable" // rather than a black rectangle. A document's first page is drawn as it prints (src/pageThumb.js). +// A configuration with a STORED thumbnail of itself (templates.js `preview`) shows it at once: one small image +// instead of a live draw (the zone posters took ~9 s each, measured). The live render is still one click away. +const storedUrl = (rel) => `${assetsBase.value}/${rel}` async function previewSrc(item) { + const stored = byName.get(item.id)?.preview + if (stored) return storedUrl(stored) const out = await renderDocument(item.id) if (!out) return null if (out.image) return out.image @@ -196,6 +201,12 @@ async function previewSrc(item) { // built here only as far as its page 1, so it is NOT offered for printing from this card: printing a // packet missing its later pages would misrepresent the template. async function sheet(item) { + // A kind that exists only as STORED posters (no live renderer here): print its stored poster on a page. + const c0 = byName.get(item.id) + if (c0 && !c0.render && c0.preview) { + const src = new URL(storedUrl(c0.preview), document.baseURI).href + return `${escHtml(labelOf(c0))}` + } const out = await renderDocument(item.id) if (!out || out.image || (typeof out === 'object' && out.firstPageOnly)) return '' return typeof out === 'string' ? out : out.html diff --git a/plugin-designs/poster-design/scripts/check-catalogue.mjs b/plugin-designs/poster-design/scripts/check-catalogue.mjs index b020ec949..0d11e0692 100644 --- a/plugin-designs/poster-design/scripts/check-catalogue.mjs +++ b/plugin-designs/poster-design/scripts/check-catalogue.mjs @@ -18,6 +18,8 @@ * (src/pluginDesigns.js blockProblems: family, name, size, mobileFriendly, optional labelKey / instance), its * labelKey exists in the English catalogue, and its instance is a preset of that profile. * 7. Names are unique across templates.js AND every plugin's blocks. + * 9. STORED PREVIEWS: every template's `preview` (a path under the tree's assets/) is a stored file some plugin + * ships (plugin-designs//assets/), read from the git index so a sparse checkout works too. * 8. THE NAMES LEDGER (src/catalogue-names.json): every name ever shipped. A ledger name that no longer exists * (renamed or removed) FAILS: a name is what people hand an agent, so it must keep working (a retired size stays * as a legacy entry, as templates.js does). A new name passes and is reported, to be added to the ledger. @@ -26,6 +28,7 @@ * off) must each FAIL, and the documented example block must PASS. */ import { readFileSync, readdirSync, existsSync } from 'node:fs' +import { execFileSync } from 'node:child_process' import { resolve, dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' @@ -158,6 +161,14 @@ function designArms({ names, blocks, ledger, drawnFor = new Map() }) { for (const n of ledger) if (!seen.has(n)) fs.push(`8: '${n}' is in the names ledger and no longer exists: a shipped name is never renamed or removed (keep it, as a legacy entry if retired)`) return { fails: fs, fresh: [...seen].filter((n) => !ledger.includes(n)) } } +// 9. every `preview` names a stored file a plugin ships +const SHIPPED = (() => { try { return new Set(execFileSync('git', ['-C', ROOT, 'ls-files', 'plugin-designs'], { encoding: 'utf8' }).split('\n').filter((f) => /\/assets\/previews\//.test(f)).map((f) => f.replace(/^plugin-designs\/[^/]+\/assets\//, ''))) } catch { return null } })() +function previewArm(list, shipped) { + if (!shipped) return ['9: could not read the git index to find the stored previews'] + return list.filter((c) => c.preview && !shipped.has(c.preview)).map((c) => `9: '${c.name}' previews assets/${c.preview}, which no plugin ships`) +} +previewArm(configs, SHIPPED).forEach(fail) + const LEDGER_FILE = join(ROOT, 'plugin-designs/poster-design/src/catalogue-names.json') const ledger = existsSync(LEDGER_FILE) ? JSON.parse(readFileSync(LEDGER_FILE, 'utf8')).names || [] : [] if (!ledger.length) fail('8: the names ledger (src/catalogue-names.json) is missing or empty') @@ -184,6 +195,9 @@ if (process.argv.includes('--self-test')) { const und = sampleArm(samples, data.countries || [], () => false).find((f) => /cannot be drawn/.test(f)) console.log(und ? ` ✓ caught: a sample that exists but cannot be drawn (withholding off) -> ${und}` : ' ✗ MISSED: a sample that exists but cannot be drawn') if (!und) ok = false + const pv = previewArm([...configs, { name: 'fixture.missing-preview', preview: 'previews/no-such-poster.webp' }], SHIPPED).find((f) => /no-such-poster/.test(f)) + console.log(pv ? ` ✓ caught: a preview no plugin ships -> ${pv}` : ' ✗ MISSED: a preview no plugin ships') + if (!pv) ok = false const good = designArms({ ...real, blocks: [...blocks, B(example)] }) console.log(good.fails.length ? ` ✗ the documented example block FAILS: ${good.fails[0]}` : ' ✓ the documented example block passes') if (good.fails.length) ok = false diff --git a/plugin-designs/poster-design/src/FullDetailsDialog.vue b/plugin-designs/poster-design/src/FullDetailsDialog.vue index cdf6d3e42..c83081b38 100644 --- a/plugin-designs/poster-design/src/FullDetailsDialog.vue +++ b/plugin-designs/poster-design/src/FullDetailsDialog.vue @@ -71,7 +71,11 @@ async function copyName() {
{{ t('posterDesign.details.settings') }}
{{ settings }}
{{ t('posterDesign.details.sample') }}
-
{{ config.sample || '—' }}
+
{{ config.sample || config.sampleLabel || '—' }}
+
{{ t('posterDesign.details.renderer') }}
{{ config.renderer }}
diff --git a/plugin-designs/poster-design/src/catalogue-names.json b/plugin-designs/poster-design/src/catalogue-names.json index 0e94236c0..8b678b200 100644 --- a/plugin-designs/poster-design/src/catalogue-names.json +++ b/plugin-designs/poster-design/src/catalogue-names.json @@ -26,6 +26,9 @@ "wall.bless-60x96", "bible-poster.36x60", "upg100-territory-packet.page-1", - "bless-india.range-badge-60x96" + "bless-india.range-badge-60x96", + "city-poster.3x3", + "india-state-poster.3x3", + "upg100-territory-cards.poker" ] } diff --git a/plugin-designs/poster-design/src/templates.js b/plugin-designs/poster-design/src/templates.js index f68009740..a1dd1d223 100644 --- a/plugin-designs/poster-design/src/templates.js +++ b/plugin-designs/poster-design/src/templates.js @@ -33,7 +33,10 @@ export const SAMPLE_COUNTRY_ID = 'country-npl' // show the template, and their details say so (missing: noCountryAtSize). Names never change (src/catalogue-names.json). export const SHEET_FORMATS = { // `size` is the printed size as data (src/sizeText.js names it in the reader's language): inches, side lanes, paper. - '3x3': { size: { dims: [36, 36] }, px: [1500, 1500] }, + // `preview`: a STORED thumbnail of this exact configuration (same format, same sample), generated by the real + // generator (library/scripts/posters), path under the tree's assets/. Styles shows it instantly; the live render + // stays one click away (Show full details, print). check:poster-design-catalogue proves the file exists. + '3x3': { size: { dims: [36, 36] }, px: [1500, 1500], preview: 'previews/country-npl.webp' }, // experimental: still being designed, or no drawable preview; Styles lists these in a collapsed Experimental group. '60x96': { size: { dims: [60, 96], lanes: true }, px: [2500, 4000], sample: 'country-ind', experimental: true }, '66x66': { size: { dims: [66, 66], lanes: true }, px: [2750, 2750], sample: 'country-chn', missing: 'posterDesign.missing.noCountryAtSize', experimental: true }, @@ -47,6 +50,7 @@ export const SHEET_FORMATS = { // assembled, so the catalogue checks and the i18n area build can see each one. export const FAMILY_KEYS = { mapSheet: 'posterDesign.family.mapSheet', + cityPoster: 'posterDesign.family.cityPoster', upgZone: 'posterDesign.family.upgZone', packet: 'posterDesign.family.packet', cards: 'posterDesign.family.cards', @@ -64,6 +68,7 @@ function sheetEntry(format) { px: f.px, legacy: !!f.legacy, ...(f.experimental ? { experimental: true } : {}), + ...(f.preview && !f.sample ? { preview: f.preview } : {}), ...(f.missing ? { missing: f.missing } : {}), renderer: 'library/packet/posterSheets.js generateCountrySheet → library/packet/poster-render.js', settings: { format }, @@ -90,15 +95,38 @@ function zoneEntry(format) { renderer: ZONE_RENDERER, settings: { format }, sample: SAMPLE_ZONE_ID, + ...(ZONE_PREVIEWS[format] ? { preview: ZONE_PREVIEWS[format] } : {}), render: (ctx) => ctx.zoneSheet(format, SAMPLE_ZONE_ID), } } +// The stored thumbnails of middle-east-core that are exactly these configurations (the 3x3 zone poster and the +// packet's page 1); the letter and 3x4 zone posters have none yet, so they render live. +const ZONE_PREVIEWS = { '3x3': 'previews/middle-east-core.webp', packet: 'previews/examples/middle-east-core/packet-p01.webp' } + +// ONE OF EACH KIND that exists only as STORED posters today (their generator is the batch one, library/scripts/ +// posters): the card shows the stored poster; the live render is the source plugin's own page. Each carries its +// source plugin (`plugin`), so that plugin's own posterDesign block may share the name. +const STORED_KINDS = [ + { name: 'city-poster.3x3', family: 'cityPoster', labelKey: 'posterDesign.tpl.cityPoster', plugin: 'key-cities', + size: { dims: [36, 36] }, px: [560, 560], sampleLabel: 'Hyderabad, Telangana, India', + preview: 'previews/city-ind-17.4284-78.4492.webp', + renderer: 'library/scripts/posters/generate.mjs (key-cities) → library/packet/poster-render.js' }, + { name: 'india-state-poster.3x3', family: 'mapSheet', labelKey: 'posterDesign.tpl.indiaStatePoster', plugin: 'country-posters', + size: { dims: [36, 36] }, px: [560, 560], sampleLabel: 'Uttar Pradesh', + preview: 'previews/india-up.webp', + renderer: 'library/packet/countrySheet.js makeCountrySheet (indiaState) → library/packet/poster-render.js' }, + { name: 'upg100-territory-cards.poker', family: 'upgZone', labelKey: 'posterDesign.tpl.upgZoneCards', plugin: 'my-upg-100-list', + size: { dims: [2.5, 3.5] }, px: [480, 672], sampleLabel: 'middle-east-core, card 1', + preview: 'previews/examples/middle-east-core/card-01.webp', + renderer: 'library/packet/card-render.js createCardRenderer (STOCK poker), via library/scripts/posters/packet.mjs' }, +].map((k) => ({ ...k, settings: {}, sample: null, render: null })) /** @returns {Array} every configuration, in the gallery's order. */ export function listConfigurations() { return [ ...Object.keys(SHEET_FORMATS).map(sheetEntry), ...['letter', '3x3', '3x4', 'packet'].map(zoneEntry), + ...STORED_KINDS, ...['full', 'lightweight'].map((variant) => ({ name: `packet.${variant}`, family: 'packet',