diff --git a/package.json b/package.json index 28614cc0a..a89caba27 100644 --- a/package.json +++ b/package.json @@ -251,6 +251,7 @@ "check:window-globals-released": "node internal/check-window-globals-released.mjs", "check:poster-card-fits": "node internal/check-poster-card-fits.mjs", "check:poster-degrades": "node internal/check-poster-degrades-honestly.mjs", + "check:poster-design-catalogue": "node plugin-designs/poster-design/scripts/check-catalogue.mjs", "check:poster-fetch-guard": "node internal/check-poster-fetch-guard.mjs", "check:poster-print-fallback": "node internal/check-poster-print-fallback.mjs", "check:posters-exportable": "node internal/check-posters-exportable.mjs", @@ -347,7 +348,7 @@ "fix:sri": "node internal/fix-sri.mjs", "gallery": "node internal/generate-gallery.mjs", "gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight", - "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams", + "gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-design-catalogue", "gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs", "internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs", "library-index": "node internal/generate-library-index.mjs", @@ -579,6 +580,7 @@ "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.", "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.", "check:poster-layout-optin": "the composed poster sheet (header, map, legend beside it, footer) is OPT-IN: spec.layout === 'poster' asks for it, and every caller that does not gets the full-page map exactly as before, asserted by object identity, not just by value. Drives the real layout decisions with real spec objects. Headless, no browser.", "check:poster-print-fallback": "clicks the REAL poster print control with the served poster generator and every posters/*.webp aborted, and asserts the preview RASTERISES from posters/.html instead of opening on \"Poster not available yet\". Needs a served tree built with posterSources:true; NOT MEASURED otherwise.", diff --git a/plugin-designs/poster-design/index.html b/plugin-designs/poster-design/index.html new file mode 100644 index 000000000..41fc10755 --- /dev/null +++ b/plugin-designs/poster-design/index.html @@ -0,0 +1,38 @@ + + + + + + doxa-maps · poster-design staging + + + + +
+

poster-design · staging

+

One bundle, three flat profiles: the two-tab element and each of its tabs alone. + All compile into poster-design.js.

+
+
profile-config.profile = "poster-design-mfe"
+
+
+
+
profile-config.profile = "poster-styles"
+
+
+
+ + + diff --git a/plugin-designs/poster-design/index.js b/plugin-designs/poster-design/index.js new file mode 100644 index 000000000..8f93ff349 --- /dev/null +++ b/plugin-designs/poster-design/index.js @@ -0,0 +1,23 @@ +/** + * plugin-designs/poster-design/index.js: the Poster Design bundle entry. + * + * One tag, , and three profiles in this folder: + * poster-design-mfe the two-tab element (Styles + Sizes), the one a gallery mounts; + * poster-styles every poster template, one card per configuration; + * poster-sizes every poster size. + * + * + * + */ +import { defineMapElement } from '@map/elements/defineMapElement.js' + +// Flat: './*.vue' finds the profiles beside this file (src/ holds private components, not profiles). +const profileModules = import.meta.glob('./*.vue') + +const PosterDesignElement = defineMapElement({ profileModules }) + +if (typeof customElements !== 'undefined' && !customElements.get('poster-design')) { + customElements.define('poster-design', PosterDesignElement) +} + +export default PosterDesignElement diff --git a/plugin-designs/poster-design/poster-design-mfe.meta.json b/plugin-designs/poster-design/poster-design-mfe.meta.json new file mode 100644 index 000000000..79e0cda58 --- /dev/null +++ b/plugin-designs/poster-design/poster-design-mfe.meta.json @@ -0,0 +1,9 @@ +{ + "type": "nested-elements-mfe", + "label": "Poster Design — Styles + Sizes", + "composes": [ + "poster-design/poster-styles", + "poster-design/poster-sizes" + ], + "note": "The two-tab Poster Design element, the same shape as country-posters-mfe: the shared ViewSelect over Styles at [0,0] and Sizes at [1,0], each pane its own element. Styles renders the library's PosterWall with two host seams (headerAction for Show full details, previewSrc for thumbnails drawn from the library). Sizes reads the one shared size table." +} diff --git a/plugin-designs/poster-design/poster-design-mfe.vue b/plugin-designs/poster-design/poster-design-mfe.vue new file mode 100644 index 000000000..c00cb7f85 --- /dev/null +++ b/plugin-designs/poster-design/poster-design-mfe.vue @@ -0,0 +1,124 @@ + + + diff --git a/plugin-designs/poster-design/poster-sizes.meta.json b/plugin-designs/poster-design/poster-sizes.meta.json new file mode 100644 index 000000000..6342c9523 --- /dev/null +++ b/plugin-designs/poster-design/poster-sizes.meta.json @@ -0,0 +1,5 @@ +{ + "type": "single", + "label": "Poster Design — Sizes", + "note": "Every poster size from the shared size table (library/constants/posterSizes.js), each as a print preview; also embeddable alone." +} diff --git a/plugin-designs/poster-design/poster-sizes.vue b/plugin-designs/poster-design/poster-sizes.vue new file mode 100644 index 000000000..78f86f458 --- /dev/null +++ b/plugin-designs/poster-design/poster-sizes.vue @@ -0,0 +1,35 @@ + + + diff --git a/plugin-designs/poster-design/poster-styles.meta.json b/plugin-designs/poster-design/poster-styles.meta.json new file mode 100644 index 000000000..97801f4e9 --- /dev/null +++ b/plugin-designs/poster-design/poster-styles.meta.json @@ -0,0 +1,5 @@ +{ + "type": "single", + "label": "Poster Design — Styles", + "note": "Every poster template, one card per configuration (src/templates.js), drawn from the library with sample data; also embeddable alone." +} diff --git a/plugin-designs/poster-design/poster-styles.vue b/plugin-designs/poster-design/poster-styles.vue new file mode 100644 index 000000000..c1ccc6bf2 --- /dev/null +++ b/plugin-designs/poster-design/poster-styles.vue @@ -0,0 +1,151 @@ + + + diff --git a/plugin-designs/poster-design/scripts/check-catalogue.mjs b/plugin-designs/poster-design/scripts/check-catalogue.mjs new file mode 100644 index 000000000..73ab72861 --- /dev/null +++ b/plugin-designs/poster-design/scripts/check-catalogue.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/** + * check-catalogue.mjs (check:poster-design-catalogue): Poster Design's configuration catalogue + * (plugin-designs/poster-design/src/templates.js) says only true things about the library. + * + * 1. SHEET_FORMATS has exactly the formats poster-render.js FORMATS defines, at the same pixel size. + * The generator is served as a pinned asset, not bundled, so the catalogue cannot import the table; + * this is what keeps the two from drifting. + * 2. Every configuration name is unique and shaped like a name (lowercase words, dots, dashes, digits): + * a name is what a person hands an agent, so two cards must never share one. + * 3. The card entries are exactly card-render.js STOCK, one per stock. + * 4. Every catalogue key an entry names (labelKey, missing, the family keys) exists in the English + * posterDesign catalogue. + * 5. The sample country exists in the country data and can be drawn (has geometry, a sheet tier). + * A catalogue that lists nothing FAILS (vacuous guard). + */ +import { readFileSync } from 'node:fs' +import { resolve, dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..') +const failures = [] +const fail = (m) => failures.push(m) + +const { createServer } = await import('vite') +const server = await createServer({ + root: ROOT, configFile: false, logLevel: 'error', appType: 'custom', + server: { middlewareMode: true, hmr: false, ws: false, watch: null }, + resolve: { alias: { '@map': join(ROOT, 'library') } }, +}) +let cat, STOCK +try { + cat = await server.ssrLoadModule('/plugin-designs/poster-design/src/templates.js') + ;({ STOCK } = await server.ssrLoadModule('/library/packet/card-render.js')) +} finally { await server.close() } + +const configs = cat.listConfigurations() +if (!configs.length) fail('vacuous: listConfigurations() returned nothing') + +// 1. formats against the generator's own table +const src = readFileSync(join(ROOT, 'library/packet/poster-render.js'), 'utf8') +const block = (src.match(/const FORMATS = \{([\s\S]*?)\n\};?/) || [])[1] +if (!block) fail('poster-render.js: could not find "const FORMATS = {"; update this check') +else { + const found = {} + for (const m of block.matchAll(/^\s*'?([\w]+)'?\s*:\s*\{\s*w:\s*(\d+),\s*h:\s*(\d+)/gm)) found[m[1]] = [Number(m[2]), Number(m[3])] + const names = Object.keys(found) + if (names.length < 3) fail(`poster-render.js FORMATS: parsed only ${names.length} format(s) (${names.join(', ')}); the parser no longer matches`) + for (const k of names) { + const mine = cat.SHEET_FORMATS[k] + if (!mine) fail(`FORMATS has '${k}' and the catalogue does not`) + else if (mine.px[0] !== found[k][0] || mine.px[1] !== found[k][1]) fail(`'${k}': catalogue says ${mine.px.join('×')}, FORMATS says ${found[k].join('×')}`) + } + for (const k of Object.keys(cat.SHEET_FORMATS)) if (!found[k]) fail(`the catalogue has format '${k}', which FORMATS does not`) +} + +// 2. names +const seen = new Set() +for (const c of configs) { + if (!/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/i.test(c.name)) fail(`'${c.name}' is not a configuration name (words joined by . or -)`) + if (seen.has(c.name)) fail(`two configurations are named '${c.name}'`) + seen.add(c.name) +} + +// 3. stocks +const stockNames = configs.filter((c) => c.family === 'cards').map((c) => c.settings.stock).sort() +const want = Object.keys(STOCK).sort() +if (JSON.stringify(stockNames) !== JSON.stringify(want)) fail(`card configurations ${JSON.stringify(stockNames)} are not card-render STOCK ${JSON.stringify(want)}`) + +// 4. keys exist in English +const en = JSON.parse(readFileSync(join(ROOT, 'library/i18n/locales/en/posterDesign.json'), 'utf8')) +const has = (key) => { + const parts = String(key).split('.') + if (parts.shift() !== 'posterDesign') return false + let o = en + for (const p of parts) { if (!o || typeof o !== 'object' || !(p in o)) return false; o = o[p] } + return typeof o === 'string' +} +const keys = [...configs.flatMap((c) => [c.labelKey, c.missing].filter(Boolean)), ...Object.values(cat.FAMILY_KEYS)] +for (const k of keys) if (!has(k)) fail(`catalogue key '${k}' is not in en/posterDesign.json`) +for (const c of configs) if (!cat.FAMILY_KEYS[c.family]) fail(`'${c.name}' has family '${c.family}', which has no heading key`) + +// 5. the sample +const data = JSON.parse(readFileSync(join(ROOT, 'library/data/country-posters.json'), 'utf8')) +const s = (data.countries || []).find((c) => c.id === cat.SAMPLE_COUNTRY_ID) +if (!s) fail(`sample '${cat.SAMPLE_COUNTRY_ID}' is not in library/data/country-posters.json`) +else if (!s.hasGeometry || !s.sheet || s.unsupportedReason) fail(`sample '${s.id}' cannot be drawn (hasGeometry ${s.hasGeometry}, sheet ${s.sheet})`) + +if (failures.length) { + console.error(`POSTER-DESIGN-CATALOGUE: FAIL (${failures.length})\n - ${failures.join('\n - ')}`) + process.exitCode = 1 +} else { + console.log(`POSTER-DESIGN-CATALOGUE: PASS — ${configs.length} configurations, ${Object.keys(cat.SHEET_FORMATS).length} sheet formats matching poster-render FORMATS, ${want.length} card stocks from STOCK, ${keys.length} catalogue keys present, sample ${cat.SAMPLE_COUNTRY_ID}`) +} diff --git a/plugin-designs/poster-design/src/FullDetailsDialog.vue b/plugin-designs/poster-design/src/FullDetailsDialog.vue new file mode 100644 index 000000000..7b82abdf0 --- /dev/null +++ b/plugin-designs/poster-design/src/FullDetailsDialog.vue @@ -0,0 +1,83 @@ + + + diff --git a/plugin-designs/poster-design/src/templates.js b/plugin-designs/poster-design/src/templates.js new file mode 100644 index 000000000..dfc1fbb0e --- /dev/null +++ b/plugin-designs/poster-design/src/templates.js @@ -0,0 +1,109 @@ +/** + * templates.js: every poster template the library can print, one entry per CONFIGURATION. + * + * A configuration is one template at one setting (a map sheet at 3 × 3, a packet in its lightweight + * variant, a card deck on postcard stock). Its NAME is the stable id a person gives an agent ("use + * map-sheet.country.3x3"), so a name is never renamed once published; a retired configuration keeps + * its name and is marked legacy. + * + * Each entry says where its renderer lives in library/ and, when the gallery can draw it, how: + * render(ctx) returns the template's HTML document drawn from the LIBRARY with the sample data, so a + * library change shows up in the gallery the next time it opens. Entries the gallery cannot draw yet + * say so (render: null) and the card shows the wall's "preview unavailable" placeholder; the details + * dialog states which part is missing, rather than a picture of something else. + * + * Where the library already has the list (card stocks), it is DERIVED here, never retyped. + */ +import { STOCK } from '@map/packet/card-render.js' + +// The sample every map sheet is drawn with: one real country from the data the country wall ships, +// mid-sized (34 groups), on the standard sheet tier, with full outlines, so each sheet shows every part. +export const SAMPLE_COUNTRY_ID = 'country-npl' + +// The library's poster formats (library/packet/poster-render.js FORMATS). The generator is served as a +// pinned asset, not bundled, so its table cannot be imported here; check:poster-design-catalogue reads +// FORMATS from the source and fails when the two disagree. +export const SHEET_FORMATS = { + '3x3': { page: '3 × 3', px: [1500, 1500] }, + '3x4': { page: '3 × 4', px: [1500, 2000], legacy: true }, + letter: { page: 'Letter, portrait', px: [1500, 1941] }, + packet: { page: 'Letter, portrait (packet page 1)', px: [1500, 1941] }, +} + +// Families are the sections of the Styles tab, in reading order. The keys are written out, never +// assembled, so the catalogue checks and the i18n area build can see each one. +export const FAMILY_KEYS = { + mapSheet: 'posterDesign.family.mapSheet', + packet: 'posterDesign.family.packet', + cards: 'posterDesign.family.cards', + wall: 'posterDesign.family.wall', + mapPrint: 'posterDesign.family.mapPrint', +} + +function sheetEntry(format) { + const f = SHEET_FORMATS[format] + return { + name: format === 'packet' ? 'map-sheet.packet-page' : `map-sheet.country.${format}`, + family: 'mapSheet', + labelKey: format === 'packet' ? 'posterDesign.tpl.mapSheetPacketPage' : 'posterDesign.tpl.mapSheetCountry', + size: f.page, + px: f.px, + legacy: !!f.legacy, + renderer: 'library/packet/posterSheets.js generateCountrySheet → library/packet/poster-render.js', + settings: { format }, + sample: SAMPLE_COUNTRY_ID, + render: (ctx) => ctx.countrySheet(format), + } +} + +/** @returns {Array} every configuration, in the gallery's order. */ +export function listConfigurations() { + return [ + ...Object.keys(SHEET_FORMATS).map(sheetEntry), + ...['full', 'lightweight'].map((variant) => ({ + name: `packet.${variant}`, + family: 'packet', + labelKey: variant === 'full' ? 'posterDesign.tpl.packetFull' : 'posterDesign.tpl.packetLightweight', + size: 'Letter, portrait', + renderer: 'library/packet/packet-render.js buildPacket (VARIANTS)', + settings: { variant }, + sample: SAMPLE_COUNTRY_ID, + render: null, + missing: 'posterDesign.missing.packet', + })), + ...Object.entries(STOCK).map(([id, s]) => ({ + name: `cards.${id}`, + family: 'cards', + labelKey: 'posterDesign.tpl.cards', + stock: s, + size: `${s.w} × ${s.h} in`, + renderer: 'library/packet/card-render.js createCardRenderer (STOCK)', + settings: { stock: id }, + sample: SAMPLE_COUNTRY_ID, + render: null, + missing: 'posterDesign.missing.cards', + })), + { + name: 'wall.bless-panels', + family: 'wall', + labelKey: 'posterDesign.tpl.blessPanels', + size: '5 × 8 ft, 3 panels', + renderer: 'plugin-designs/wall-of-people-groups/src/blessPoster.js (not in library/ yet)', + settings: {}, + sample: null, + render: null, + missing: 'posterDesign.missing.bless', + }, + { + name: 'map-print.dialog', + family: 'mapPrint', + labelKey: 'posterDesign.tpl.mapPrint', + size: 'library/constants/posterSizes.js', + renderer: 'library/components/poster/PosterDialog.vue + library/composables/useMapPoster.js', + settings: {}, + sample: null, + render: null, + missing: 'posterDesign.missing.mapPrint', + }, + ] +}