Merge remote-tracking branch 'origin/development' into regions-plugin/view-address
# Conflicts: # package.json
This commit is contained in:
commit
ceb7168bf3
67 changed files with 3667 additions and 282 deletions
|
|
@ -12,9 +12,42 @@
|
|||
* posterDesign { family, name, size, mobileFriendly, labelKey?, instance? } or an array of them: the poster
|
||||
* design(s) this profile shows, listed by Poster Design's Styles. Carried as written, ONLY when present
|
||||
* (every other manifest keeps its bytes); check:poster-design-catalogue validates it.
|
||||
* posterDesignLabels GENERATED beside it: { <design name>: { <locale>: title } } for every block with a labelKey,
|
||||
* resolved here from library/i18n/locales/<locale>/<area>.json. Poster Design's bundle ships only the
|
||||
* catalogue areas its own code names, so a plugin's own area (upgMap, …) would be missing there and the card
|
||||
* would read English; carrying the titles in the manifest works for any area, with no Poster Design rebuild.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const LOCALES = join(dirname(fileURLToPath(import.meta.url)), '..', 'library', 'i18n', 'locales')
|
||||
// A catalogue key ("area.a.b") in every locale that has it: { en: '…', ar: '…' }; {} when no locale does.
|
||||
function catalogueTitles(key) {
|
||||
const [area, ...path] = String(key || '').split('.')
|
||||
const out = {}
|
||||
if (!area || !path.length || !existsSync(LOCALES)) return out
|
||||
for (const loc of readdirSync(LOCALES)) {
|
||||
const f = join(LOCALES, loc, `${area}.json`)
|
||||
if (!existsSync(f)) continue
|
||||
try {
|
||||
let o = JSON.parse(readFileSync(f, 'utf8'))
|
||||
for (const p of path) o = o && typeof o === 'object' ? o[p] : undefined
|
||||
if (typeof o === 'string' && o.trim()) out[loc] = o
|
||||
} catch { /* a locale file that does not parse names nothing */ }
|
||||
}
|
||||
return out
|
||||
}
|
||||
function posterDesignLabels(pd) {
|
||||
const out = {}
|
||||
for (const b of Array.isArray(pd) ? pd : [pd]) {
|
||||
if (b && typeof b.name === 'string' && typeof b.labelKey === 'string') {
|
||||
const titles = catalogueTitles(b.labelKey)
|
||||
if (Object.keys(titles).length) out[b.name] = titles
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const CORNERS = ['bottom-right', 'bottom-left', 'top-right', 'top-left']
|
||||
|
||||
|
|
@ -82,7 +115,7 @@ export function profileMetaFields(dir, profileName) {
|
|||
const m = readProfileMeta(dir, profileName)
|
||||
// visibility is emitted ONLY when it is "public": absent means team, so every manifest in the
|
||||
// tree today keeps the bytes it has and nothing becomes visible by upgrading the generator.
|
||||
return { stage: m.stage, ...(m.visibility === 'public' ? { visibility: 'public' } : {}), ...(m.label ? { label: m.label } : {}), ...(m.targets ? { targets: m.targets } : {}), ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}), ...(m.mobile ? { mobile: m.mobile } : {}), ...(m.posterDesign ? { posterDesign: m.posterDesign } : {}) }
|
||||
return { stage: m.stage, ...(m.visibility === 'public' ? { visibility: 'public' } : {}), ...(m.label ? { label: m.label } : {}), ...(m.targets ? { targets: m.targets } : {}), ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}), ...(m.mobile ? { mobile: m.mobile } : {}), ...(m.posterDesign ? { posterDesign: m.posterDesign } : {}), ...(m.posterDesign && Object.keys(posterDesignLabels(m.posterDesign)).length ? { posterDesignLabels: posterDesignLabels(m.posterDesign) } : {}) }
|
||||
}
|
||||
|
||||
/** A bundle is production if ANY of its profiles says so. */
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "كورنثوس الأولى",
|
||||
"1PE": "بطرس الأولى",
|
||||
"JOS": "يشوع"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "ملصق سفر من الكتاب المقدس"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "لا",
|
||||
"printHome": "أي طابعة منزلية",
|
||||
"printShop": "مطبعة (ملف PDF، صفحة لكل ورقة)",
|
||||
"layoutNote": "تعرض الأوراق تخطيط ملصق الدولة بمقاس 3 × 3 أقدام في كل مقاس: الترويسة والخريطة والأسماء والجدول وتذييل الآية. سيظهر الملصق المرسوم بكل مقاس في الأنماط عند توفره."
|
||||
"layoutNote": "تعرض الأوراق تخطيط ملصق الدولة بمقاس 3 × 3 أقدام في كل مقاس: الترويسة والخريطة والأسماء والجدول وتذييل الآية. سيظهر الملصق المرسوم بكل مقاس في الأنماط عند توفره.",
|
||||
"sideLanes": "{dims}، أعمدة جانبية",
|
||||
"packetPage1": "{paper} (الصفحة 1 من حزمة الصلاة)",
|
||||
"panels": "{dims}، عدد الألواح: {n}",
|
||||
"anySize": "أي حجم من جدول الأحجام المشترك"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "صفحات الخرائط",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "بطاقات الصلاة",
|
||||
"wall": "ألواح الجدار",
|
||||
"mapPrint": "طباعة الخريطة",
|
||||
"plugins": "من إضافات أخرى"
|
||||
"plugins": "من إضافات أخرى",
|
||||
"experimental": "تجريبية (قيد العمل)",
|
||||
"upgZone": "ملصقات مناطق UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "صفحة خريطة الدولة",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "حزمة الصلاة، مختصرة",
|
||||
"cards": "بطاقات الصلاة",
|
||||
"blessPanels": "ألواح جدار البركة",
|
||||
"mapPrint": "طباعة الخريطة"
|
||||
"mapPrint": "طباعة الخريطة",
|
||||
"upgZonePoster": "ملصق منطقة UPG100",
|
||||
"upgZonePacket": "حزمة صلاة UPG100، الصفحة 1"
|
||||
},
|
||||
"missing": {
|
||||
"bless": "الأوراق وشرائط اللصق ومواضع صفوف البطاقات مأخوذة من هندسة الألواح المشتركة؛ وتُعرض البطاقات كتخطيط، لأن جدار البركة يرسم بطاقاته في إضافته الخاصة.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "ملصقات UPG100",
|
||||
"wallHeading": "ملصقات UPG100",
|
||||
"wallIntroA": "كيف تُطعم جمعًا من خمسة آلاف؟ أجلسهم يسوع جماعاتٍ من خمسين ومن مئة، فصار المستحيل قابلًا للمشاركة (لوقا ٩:١٤). فعلنا المثل مع {n} شعوب غير مبلَّغة: ٢٧ قائمة من نحو خمسين إلى مئة. تُطبع كل قائمة حزمةَ صلاة وملصقًا، ليحمل ٢٧ تلميذًا أو كنيسة أو قائدًا محليًا كلٌّ واحدة — مصلّين لأجلهم ",
|
||||
"peopleStillLoading": "لا تزال الشعوب قيد التحميل، حاول مرة أخرى بعد لحظة"
|
||||
"peopleStillLoading": "لا تزال الشعوب قيد التحميل، حاول مرة أخرى بعد لحظة",
|
||||
"posterDesignTerritory": "ملصق منطقة UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1. Korinther",
|
||||
"1PE": "1. Petrus",
|
||||
"JOS": "Josua"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Bibelbuch-Poster"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "Nein",
|
||||
"printHome": "Jeder Drucker zu Hause",
|
||||
"printShop": "Eine Druckerei (ein PDF, eine Seite pro Bogen)",
|
||||
"layoutNote": "Die Bögen zeigen das Layout des 3 × 3 ft Länderposters in jeder Größe: Kopfzeile, Karte, Namen, Tabelle und die Fußzeile mit dem Bibelvers. Das in jeder Größe gezeichnete Poster erscheint unter Stile, sobald es verfügbar ist."
|
||||
"layoutNote": "Die Bögen zeigen das Layout des 3 × 3 ft Länderposters in jeder Größe: Kopfzeile, Karte, Namen, Tabelle und die Fußzeile mit dem Bibelvers. Das in jeder Größe gezeichnete Poster erscheint unter Stile, sobald es verfügbar ist.",
|
||||
"sideLanes": "{dims}, seitliche Namensspalten",
|
||||
"packetPage1": "{paper} (Seite 1 der Gebetsmappe)",
|
||||
"panels": "{dims}, Tafeln: {n}",
|
||||
"anySize": "Jede Größe aus der gemeinsamen Größentabelle"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Kartenblätter",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Gebetskarten",
|
||||
"wall": "Wandtafeln",
|
||||
"mapPrint": "Kartendruck",
|
||||
"plugins": "Aus anderen Plugins"
|
||||
"plugins": "Aus anderen Plugins",
|
||||
"experimental": "Experimentell (in Arbeit)",
|
||||
"upgZone": "UPG100-Gebietsplakate"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Länder-Kartenblatt",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Gebetsmappe, kompakt",
|
||||
"cards": "Gebetskarten",
|
||||
"blessPanels": "Tafeln der Segnen-Wand",
|
||||
"mapPrint": "Kartendruck"
|
||||
"mapPrint": "Kartendruck",
|
||||
"upgZonePoster": "UPG100-Gebietsplakat",
|
||||
"upgZonePacket": "UPG100-Gebetsmappe, Seite 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "UPG100-Poster",
|
||||
"wallHeading": "UPG100-Poster",
|
||||
"wallIntroA": "Wie würden Sie eine Menge von fünftausend speisen? Jesus ließ sie in Gruppen zu fünfzig und hundert lagern, und das Unmögliche wurde teilbar (Lukas 9,14). Dasselbe haben wir mit den {n} unerreichten Volksgruppen getan: 27 Listen von etwa fünfzig bis hundert. Jede Liste druckt als Gebetspaket und als Poster, damit 27 Jünger, Gemeinden oder einheimische Leiter je eine tragen können — und für sie beten ",
|
||||
"peopleStillLoading": "die Volksgruppen werden noch geladen; versuchen Sie es gleich noch einmal"
|
||||
"peopleStillLoading": "die Volksgruppen werden noch geladen; versuchen Sie es gleich noch einmal",
|
||||
"posterDesignTerritory": "UPG-100-Gebietsplakat"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Corinthians",
|
||||
"1PE": "1 Peter",
|
||||
"JOS": "Joshua"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Bible book poster"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "No",
|
||||
"printHome": "Any home printer",
|
||||
"printShop": "A print shop (a PDF, one page per sheet)",
|
||||
"layoutNote": "The sheets show the layout of the 3 × 3 ft country poster at each size: the header, the map, the names, the table and the scripture footer. The poster drawn at each size appears in Styles as it becomes available."
|
||||
"layoutNote": "The sheets show the layout of the 3 × 3 ft country poster at each size: the header, the map, the names, the table and the scripture footer. The poster drawn at each size appears in Styles as it becomes available.",
|
||||
"sideLanes": "{dims}, side lanes",
|
||||
"packetPage1": "{paper} (packet page 1)",
|
||||
"panels": "{dims}, {n} panels",
|
||||
"anySize": "Any size from the shared size table"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Map sheets",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Prayer cards",
|
||||
"wall": "Wall panels",
|
||||
"mapPrint": "Map print",
|
||||
"plugins": "From other plugins"
|
||||
"plugins": "From other plugins",
|
||||
"experimental": "Experimental (in progress)",
|
||||
"upgZone": "UPG100 zone posters"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Country map sheet",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Prayer packet, lightweight",
|
||||
"cards": "Prayer cards",
|
||||
"blessPanels": "Bless wall panels",
|
||||
"mapPrint": "Map print"
|
||||
"mapPrint": "Map print",
|
||||
"upgZonePoster": "UPG100 zone poster",
|
||||
"upgZonePacket": "UPG100 prayer packet, page 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "UPG100 Posters",
|
||||
"wallHeading": "UPG100 Posters",
|
||||
"wallIntroA": "How would you feed a crowd of five thousand? Jesus sat them down in groups of fifty and a hundred, and the impossible became shareable (Luke 9:14). We did the same with the {n} unreached people groups: 27 lists of about fifty to a hundred. Each list prints as a prayer packet and a poster, so 27 disciples, churches or local leaders can each carry one — praying for them ",
|
||||
"peopleStillLoading": "the people groups are still loading, try again in a moment"
|
||||
"peopleStillLoading": "the people groups are still loading, try again in a moment",
|
||||
"posterDesignTerritory": "UPG-100 territory poster"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Corintios",
|
||||
"1PE": "1 Pedro",
|
||||
"JOS": "Josué"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Cartel de un libro de la Biblia"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "No",
|
||||
"printHome": "Cualquier impresora doméstica",
|
||||
"printShop": "Una imprenta (un PDF, una página por hoja)",
|
||||
"layoutNote": "Las hojas muestran el diseño del póster de país de 3 × 3 ft en cada tamaño: el encabezado, el mapa, los nombres, la tabla y el pie con la Escritura. El póster dibujado en cada tamaño aparecerá en Estilos a medida que esté disponible."
|
||||
"layoutNote": "Las hojas muestran el diseño del póster de país de 3 × 3 ft en cada tamaño: el encabezado, el mapa, los nombres, la tabla y el pie con la Escritura. El póster dibujado en cada tamaño aparecerá en Estilos a medida que esté disponible.",
|
||||
"sideLanes": "{dims}, columnas laterales",
|
||||
"packetPage1": "{paper} (página 1 del paquete de oración)",
|
||||
"panels": "{dims}, paneles: {n}",
|
||||
"anySize": "Cualquier tamaño de la tabla de tamaños compartida"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Hojas de mapa",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Tarjetas de oración",
|
||||
"wall": "Paneles de pared",
|
||||
"mapPrint": "Impresión del mapa",
|
||||
"plugins": "De otros complementos"
|
||||
"plugins": "De otros complementos",
|
||||
"experimental": "Experimentales (en desarrollo)",
|
||||
"upgZone": "Carteles de zonas UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Hoja de mapa del país",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Paquete de oración, ligero",
|
||||
"cards": "Tarjetas de oración",
|
||||
"blessPanels": "Paneles del muro Bendecir",
|
||||
"mapPrint": "Impresión del mapa"
|
||||
"mapPrint": "Impresión del mapa",
|
||||
"upgZonePoster": "Cartel de zona UPG100",
|
||||
"upgZonePacket": "Paquete de oración UPG100, página 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "Carteles UPG100",
|
||||
"wallHeading": "Carteles UPG100",
|
||||
"wallIntroA": "¿Cómo alimentarías a una multitud de cinco mil? Jesús los sentó en grupos de cincuenta y de cien, y lo imposible se volvió compartible (Lucas 9:14). Hicimos lo mismo con los {n} pueblos no alcanzados: 27 listas de unos cincuenta a cien. Cada lista se imprime como paquete de oración y cartel, para que 27 discípulos, iglesias o líderes locales lleven una cada uno — orando por ellos ",
|
||||
"peopleStillLoading": "los pueblos aún se están cargando; inténtalo de nuevo en un momento"
|
||||
"peopleStillLoading": "los pueblos aún se están cargando; inténtalo de nuevo en un momento",
|
||||
"posterDesignTerritory": "Cartel de territorio UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Corinthiens",
|
||||
"1PE": "1 Pierre",
|
||||
"JOS": "Josué"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Affiche d'un livre de la Bible"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "Non",
|
||||
"printHome": "N'importe quelle imprimante",
|
||||
"printShop": "Un imprimeur (un PDF, une page par feuille)",
|
||||
"layoutNote": "Les feuilles montrent la mise en page de l'affiche pays de 3 × 3 ft à chaque format : l'en-tête, la carte, les noms, le tableau et le pied avec l'Écriture. L'affiche dessinée à chaque format apparaîtra dans Styles dès qu'elle sera disponible."
|
||||
"layoutNote": "Les feuilles montrent la mise en page de l'affiche pays de 3 × 3 ft à chaque format : l'en-tête, la carte, les noms, le tableau et le pied avec l'Écriture. L'affiche dessinée à chaque format apparaîtra dans Styles dès qu'elle sera disponible.",
|
||||
"sideLanes": "{dims}, colonnes latérales",
|
||||
"packetPage1": "{paper} (page 1 du dossier de prière)",
|
||||
"panels": "{dims}, panneaux : {n}",
|
||||
"anySize": "Toute taille du tableau de tailles commun"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Feuilles de carte",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Cartes de prière",
|
||||
"wall": "Panneaux muraux",
|
||||
"mapPrint": "Impression de la carte",
|
||||
"plugins": "D’autres plugins"
|
||||
"plugins": "D’autres plugins",
|
||||
"experimental": "Expérimentaux (en cours)",
|
||||
"upgZone": "Affiches de zones UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Feuille de carte du pays",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Dossier de prière, allégé",
|
||||
"cards": "Cartes de prière",
|
||||
"blessPanels": "Panneaux du mur Bénir",
|
||||
"mapPrint": "Impression de la carte"
|
||||
"mapPrint": "Impression de la carte",
|
||||
"upgZonePoster": "Affiche de zone UPG100",
|
||||
"upgZonePacket": "Dossier de prière UPG100, page 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "Affiches UPG100",
|
||||
"wallHeading": "Affiches UPG100",
|
||||
"wallIntroA": "Comment nourririez-vous une foule de cinq mille personnes ? Jésus les a fait asseoir par groupes de cinquante et de cent, et l’impossible est devenu partageable (Luc 9.14). Nous avons fait de même avec les {n} peuples non atteints : 27 listes d’environ cinquante à cent. Chaque liste s’imprime en dossier de prière et en affiche, pour que 27 disciples, Églises ou responsables locaux en portent chacun une — en priant pour eux ",
|
||||
"peopleStillLoading": "les peuples sont encore en cours de chargement ; réessayez dans un instant"
|
||||
"peopleStillLoading": "les peuples sont encore en cours de chargement ; réessayez dans un instant",
|
||||
"posterDesignTerritory": "Affiche de territoire UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 कुरिन्थियों",
|
||||
"1PE": "1 पतरस",
|
||||
"JOS": "यहोशू"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "बाइबल पुस्तक पोस्टर"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "नहीं",
|
||||
"printHome": "कोई भी घरेलू प्रिंटर",
|
||||
"printShop": "प्रिंटिंग प्रेस (PDF, हर शीट के लिए एक पेज)",
|
||||
"layoutNote": "शीटें हर आकार में 3 × 3 फ़ुट देश पोस्टर का लेआउट दिखाती हैं: शीर्षक, मानचित्र, नाम, तालिका और पवित्रशास्त्र वाला फ़ुटर। हर आकार में बना असली पोस्टर उपलब्ध होते ही शैलियाँ में दिखेगा।"
|
||||
"layoutNote": "शीटें हर आकार में 3 × 3 फ़ुट देश पोस्टर का लेआउट दिखाती हैं: शीर्षक, मानचित्र, नाम, तालिका और पवित्रशास्त्र वाला फ़ुटर। हर आकार में बना असली पोस्टर उपलब्ध होते ही शैलियाँ में दिखेगा।",
|
||||
"sideLanes": "{dims}, किनारे के कॉलम",
|
||||
"packetPage1": "{paper} (प्रार्थना पैकेट का पेज 1)",
|
||||
"panels": "{dims}, पैनल: {n}",
|
||||
"anySize": "साझा आकार तालिका का कोई भी आकार"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "मानचित्र पृष्ठ",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "प्रार्थना कार्ड",
|
||||
"wall": "दीवार पैनल",
|
||||
"mapPrint": "मानचित्र प्रिंट",
|
||||
"plugins": "अन्य प्लगइन से"
|
||||
"plugins": "अन्य प्लगइन से",
|
||||
"experimental": "प्रयोगात्मक (काम जारी)",
|
||||
"upgZone": "UPG100 क्षेत्र पोस्टर"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "देश का मानचित्र पृष्ठ",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "प्रार्थना पैकेट, हल्का",
|
||||
"cards": "प्रार्थना कार्ड",
|
||||
"blessPanels": "आशीष दीवार के पैनल",
|
||||
"mapPrint": "मानचित्र प्रिंट"
|
||||
"mapPrint": "मानचित्र प्रिंट",
|
||||
"upgZonePoster": "UPG100 क्षेत्र का पोस्टर",
|
||||
"upgZonePacket": "UPG100 प्रार्थना पैकेट, पेज 1"
|
||||
},
|
||||
"missing": {
|
||||
"bless": "शीटें, चिपकाने की पट्टियाँ और कार्ड-पंक्तियों की जगह साझा पैनल ज्यामिति से आती हैं; कार्ड लेआउट के रूप में दिखाए गए हैं, क्योंकि आशीष दीवार अपने कार्ड अपने प्लगइन में बनाती है।",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "UPG100 पोस्टर",
|
||||
"wallHeading": "UPG100 पोस्टर",
|
||||
"wallIntroA": "पाँच हज़ार की भीड़ को आप कैसे खिलाते? यीशु ने उन्हें पचास-पचास और सौ-सौ के समूहों में बिठाया, और असंभव बाँटने योग्य हो गया (लूका 9:14). हमने {n} अनपहुँचे जनसमूहों के साथ वही किया: लगभग पचास से सौ तक की 27 सूचियाँ. हर सूची प्रार्थना पैकेट और पोस्टर के रूप में छपती है, ताकि 27 शिष्य, कलीसियाएँ या स्थानीय अगुवे एक-एक ले सकें — उनके लिए प्रार्थना करते हुए ",
|
||||
"peopleStillLoading": "जनसमूह अभी लोड हो रहे हैं, थोड़ी देर में फिर कोशिश करें"
|
||||
"peopleStillLoading": "जनसमूह अभी लोड हो रहे हैं, थोड़ी देर में फिर कोशिश करें",
|
||||
"posterDesignTerritory": "UPG-100 क्षेत्र पोस्टर"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Corinzi",
|
||||
"1PE": "1 Pietro",
|
||||
"JOS": "Giosuè"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Poster di un libro della Bibbia"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "No",
|
||||
"printHome": "Qualsiasi stampante di casa",
|
||||
"printShop": "Una tipografia (un PDF, una pagina per foglio)",
|
||||
"layoutNote": "I fogli mostrano l'impaginazione del poster di paese da 3 × 3 ft in ogni formato: intestazione, mappa, nomi, tabella e il piè di pagina con la Scrittura. Il poster disegnato in ogni formato comparirà in Stili appena disponibile."
|
||||
"layoutNote": "I fogli mostrano l'impaginazione del poster di paese da 3 × 3 ft in ogni formato: intestazione, mappa, nomi, tabella e il piè di pagina con la Scrittura. Il poster disegnato in ogni formato comparirà in Stili appena disponibile.",
|
||||
"sideLanes": "{dims}, colonne laterali",
|
||||
"packetPage1": "{paper} (pagina 1 del fascicolo di preghiera)",
|
||||
"panels": "{dims}, pannelli: {n}",
|
||||
"anySize": "Qualsiasi formato dalla tabella dei formati comune"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Fogli mappa",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Schede di preghiera",
|
||||
"wall": "Pannelli da parete",
|
||||
"mapPrint": "Stampa della mappa",
|
||||
"plugins": "Da altri plugin"
|
||||
"plugins": "Da altri plugin",
|
||||
"experimental": "Sperimentali (in corso)",
|
||||
"upgZone": "Poster delle zone UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Foglio mappa del paese",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Fascicolo di preghiera, leggero",
|
||||
"cards": "Schede di preghiera",
|
||||
"blessPanels": "Pannelli del muro Benedici",
|
||||
"mapPrint": "Stampa della mappa"
|
||||
"mapPrint": "Stampa della mappa",
|
||||
"upgZonePoster": "Poster di zona UPG100",
|
||||
"upgZonePacket": "Fascicolo di preghiera UPG100, pagina 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "Poster UPG100",
|
||||
"wallHeading": "Poster UPG100",
|
||||
"wallIntroA": "Come sfameresti una folla di cinquemila? Gesù li fece sedere a gruppi di cinquanta e di cento, e l’impossibile divenne condivisibile (Luca 9:14). Abbiamo fatto lo stesso con i {n} popoli non raggiunti: 27 liste di circa cinquanta-cento. Ogni lista si stampa come pacchetto di preghiera e manifesto, così 27 discepoli, chiese o responsabili locali ne portano una ciascuno — pregando per loro ",
|
||||
"peopleStillLoading": "i popoli si stanno ancora caricando; riprova tra un momento"
|
||||
"peopleStillLoading": "i popoli si stanno ancora caricando; riprova tra un momento",
|
||||
"posterDesignTerritory": "Manifesto di territorio UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Coríntios",
|
||||
"1PE": "1 Pedro",
|
||||
"JOS": "Josué"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Cartaz de um livro da Bíblia"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "Não",
|
||||
"printHome": "Qualquer impressora doméstica",
|
||||
"printShop": "Uma gráfica (um PDF, uma página por folha)",
|
||||
"layoutNote": "As folhas mostram o layout do pôster de país de 3 × 3 ft em cada tamanho: o cabeçalho, o mapa, os nomes, a tabela e o rodapé com a Escritura. O pôster desenhado em cada tamanho aparecerá em Estilos quando estiver disponível."
|
||||
"layoutNote": "As folhas mostram o layout do pôster de país de 3 × 3 ft em cada tamanho: o cabeçalho, o mapa, os nomes, a tabela e o rodapé com a Escritura. O pôster desenhado em cada tamanho aparecerá em Estilos quando estiver disponível.",
|
||||
"sideLanes": "{dims}, colunas laterais",
|
||||
"packetPage1": "{paper} (página 1 do pacote de oração)",
|
||||
"panels": "{dims}, painéis: {n}",
|
||||
"anySize": "Qualquer tamanho da tabela de tamanhos compartilhada"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Folhas de mapa",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Cartões de oração",
|
||||
"wall": "Painéis de parede",
|
||||
"mapPrint": "Impressão do mapa",
|
||||
"plugins": "De outros plugins"
|
||||
"plugins": "De outros plugins",
|
||||
"experimental": "Experimentais (em andamento)",
|
||||
"upgZone": "Cartazes de zonas UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Folha de mapa do país",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Pacote de oração, leve",
|
||||
"cards": "Cartões de oração",
|
||||
"blessPanels": "Painéis do mural Abençoar",
|
||||
"mapPrint": "Impressão do mapa"
|
||||
"mapPrint": "Impressão do mapa",
|
||||
"upgZonePoster": "Cartaz de zona UPG100",
|
||||
"upgZonePacket": "Pacote de oração UPG100, página 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "Cartazes UPG100",
|
||||
"wallHeading": "Cartazes UPG100",
|
||||
"wallIntroA": "Como você alimentaria uma multidão de cinco mil? Jesus os sentou em grupos de cinquenta e de cem, e o impossível tornou-se compartilhável (Lucas 9.14). Fizemos o mesmo com os {n} povos não alcançados: 27 listas de cerca de cinquenta a cem. Cada lista imprime como pacote de oração e cartaz, para que 27 discípulos, igrejas ou líderes locais levem uma cada um — orando por eles ",
|
||||
"peopleStillLoading": "os povos ainda estão carregando; tente de novo em um instante"
|
||||
"peopleStillLoading": "os povos ainda estão carregando; tente de novo em um instante",
|
||||
"posterDesignTerritory": "Cartaz de território UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Corinteni",
|
||||
"1PE": "1 Petru",
|
||||
"JOS": "Iosua"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Poster cu o carte a Bibliei"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "Nu",
|
||||
"printHome": "Orice imprimantă de acasă",
|
||||
"printShop": "O tipografie (un PDF, o pagină pe coală)",
|
||||
"layoutNote": "Colile arată machetarea afișului de țară de 3 × 3 ft la fiecare dimensiune: antetul, harta, numele, tabelul și subsolul cu versetul din Scriptură. Afișul desenat la fiecare dimensiune va apărea în Stiluri când va fi disponibil."
|
||||
"layoutNote": "Colile arată machetarea afișului de țară de 3 × 3 ft la fiecare dimensiune: antetul, harta, numele, tabelul și subsolul cu versetul din Scriptură. Afișul desenat la fiecare dimensiune va apărea în Stiluri când va fi disponibil.",
|
||||
"sideLanes": "{dims}, coloane laterale",
|
||||
"packetPage1": "{paper} (pagina 1 a pachetului de rugăciune)",
|
||||
"panels": "{dims}, panouri: {n}",
|
||||
"anySize": "Orice dimensiune din tabelul comun de dimensiuni"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Foi de hartă",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Cartonașe de rugăciune",
|
||||
"wall": "Panouri de perete",
|
||||
"mapPrint": "Tipărirea hărții",
|
||||
"plugins": "Din alte pluginuri"
|
||||
"plugins": "Din alte pluginuri",
|
||||
"experimental": "Experimentale (în lucru)",
|
||||
"upgZone": "Postere de zone UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Foaie de hartă a țării",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Pachet de rugăciune, ușor",
|
||||
"cards": "Cartonașe de rugăciune",
|
||||
"blessPanels": "Panourile peretelui Binecuvântează",
|
||||
"mapPrint": "Tipărirea hărții"
|
||||
"mapPrint": "Tipărirea hărții",
|
||||
"upgZonePoster": "Poster de zonă UPG100",
|
||||
"upgZonePacket": "Pachet de rugăciune UPG100, pagina 1"
|
||||
},
|
||||
"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.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "Afișe UPG100",
|
||||
"wallHeading": "Afișe UPG100",
|
||||
"wallIntroA": "Cum ai hrăni o mulțime de cinci mii? Isus i-a așezat în grupuri de cincizeci și de o sută, iar imposibilul a devenit împărțibil (Luca 9:14). Am făcut la fel cu cele {n} popoare neatinse: 27 de liste de aproximativ cincizeci până la o sută. Fiecare listă se tipărește ca pachet de rugăciune și afiș, ca 27 de ucenici, biserici sau lideri locali să poarte fiecare câte una — rugându-se pentru ei ",
|
||||
"peopleStillLoading": "popoarele încă se încarcă; încearcă din nou peste o clipă"
|
||||
"peopleStillLoading": "popoarele încă se încarcă; încearcă din nou peste o clipă",
|
||||
"posterDesignTerritory": "Afiș de teritoriu UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "1 Коринфянам",
|
||||
"1PE": "1 Петра",
|
||||
"JOS": "Иисус Навин"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "Плакат книги Библии"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "Нет",
|
||||
"printHome": "Любой домашний принтер",
|
||||
"printShop": "Типография (PDF, одна страница на лист)",
|
||||
"layoutNote": "На листах показан макет плаката страны 3 × 3 фута в каждом размере: заголовок, карта, названия, таблица и нижняя полоса со стихом из Писания. Плакат, нарисованный в каждом размере, появится в разделе «Стили», когда будет готов."
|
||||
"layoutNote": "На листах показан макет плаката страны 3 × 3 фута в каждом размере: заголовок, карта, названия, таблица и нижняя полоса со стихом из Писания. Плакат, нарисованный в каждом размере, появится в разделе «Стили», когда будет готов.",
|
||||
"sideLanes": "{dims}, боковые колонки",
|
||||
"packetPage1": "{paper} (страница 1 молитвенного пакета)",
|
||||
"panels": "{dims}, панелей: {n}",
|
||||
"anySize": "Любой размер из общей таблицы размеров"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "Листы карт",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "Молитвенные карточки",
|
||||
"wall": "Настенные панели",
|
||||
"mapPrint": "Печать карты",
|
||||
"plugins": "Из других плагинов"
|
||||
"plugins": "Из других плагинов",
|
||||
"experimental": "Экспериментальные (в работе)",
|
||||
"upgZone": "Плакаты зон UPG100"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "Лист карты страны",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "Молитвенный пакет, облегчённый",
|
||||
"cards": "Молитвенные карточки",
|
||||
"blessPanels": "Панели стены «Благословить»",
|
||||
"mapPrint": "Печать карты"
|
||||
"mapPrint": "Печать карты",
|
||||
"upgZonePoster": "Плакат зоны UPG100",
|
||||
"upgZonePacket": "Молитвенный пакет UPG100, страница 1"
|
||||
},
|
||||
"missing": {
|
||||
"bless": "Листы, полосы для склейки и места рядов карточек взяты из общей геометрии панелей; карточки показаны как макет, потому что стена «Благословить» рисует свои карточки в собственном плагине.",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "Плакаты UPG100",
|
||||
"wallHeading": "Плакаты UPG100",
|
||||
"wallIntroA": "Как накормить толпу в пять тысяч человек? Иисус рассадил их группами по пятьдесят и по сто, и невозможное стало посильным (Лк. 9:14). Мы поступили так же с {n} неохваченными народами: 27 списков примерно по пятьдесят-сто. Каждый список печатается как молитвенный пакет и плакат, чтобы 27 учеников, церквей или местных служителей взяли по одному — молясь о них ",
|
||||
"peopleStillLoading": "народы ещё загружаются; попробуйте ещё раз через мгновение"
|
||||
"peopleStillLoading": "народы ещё загружаются; попробуйте ещё раз через мгновение",
|
||||
"posterDesignTerritory": "Плакат территории UPG-100"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,8 @@
|
|||
"1CO": "哥林多前书",
|
||||
"1PE": "彼得前书",
|
||||
"JOS": "约书亚记"
|
||||
},
|
||||
"posterDesign": {
|
||||
"label": "圣经书卷海报"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@
|
|||
"no": "否",
|
||||
"printHome": "任何家用打印机",
|
||||
"printShop": "印刷店(PDF,每张纸一页)",
|
||||
"layoutNote": "纸张显示的是 3 × 3 英尺国家海报在各尺寸下的版式:标题、地图、名称、表格和经文页脚。各尺寸实际绘制的海报完成后会出现在“样式”中。"
|
||||
"layoutNote": "纸张显示的是 3 × 3 英尺国家海报在各尺寸下的版式:标题、地图、名称、表格和经文页脚。各尺寸实际绘制的海报完成后会出现在“样式”中。",
|
||||
"sideLanes": "{dims},两侧名单栏",
|
||||
"packetPage1": "{paper}(祷告资料包第 1 页)",
|
||||
"panels": "{dims},展板数:{n}",
|
||||
"anySize": "共享尺寸表中的任意尺寸"
|
||||
},
|
||||
"family": {
|
||||
"mapSheet": "地图页",
|
||||
|
|
@ -36,7 +40,9 @@
|
|||
"cards": "祷告卡",
|
||||
"wall": "墙面展板",
|
||||
"mapPrint": "地图打印",
|
||||
"plugins": "来自其他插件"
|
||||
"plugins": "来自其他插件",
|
||||
"experimental": "实验性(进行中)",
|
||||
"upgZone": "UPG100 区域海报"
|
||||
},
|
||||
"tpl": {
|
||||
"mapSheetCountry": "国家地图页",
|
||||
|
|
@ -45,7 +51,9 @@
|
|||
"packetLightweight": "祷告资料包(精简版)",
|
||||
"cards": "祷告卡",
|
||||
"blessPanels": "祝福墙展板",
|
||||
"mapPrint": "地图打印"
|
||||
"mapPrint": "地图打印",
|
||||
"upgZonePoster": "UPG100 区域海报(单张)",
|
||||
"upgZonePacket": "UPG100 祷告资料包,第 1 页"
|
||||
},
|
||||
"missing": {
|
||||
"bless": "纸张、粘贴条以及卡片行的位置都来自共享的拼板几何;卡片以版式示意,因为祝福墙在其自己的插件中绘制卡片。",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"displayName": "UPG100 海报",
|
||||
"wallHeading": "UPG100 海报",
|
||||
"wallIntroA": "你会怎样喂饱五千人的群众?耶稣让他们一排五十、一排一百地坐下,不可能的事就变得可以分担了(路加福音 9:14)。我们对 {n} 个未得之民做了同样的事:27 份名单,每份约五十到一百。每份名单都能印成祷告手册和海报,让 27 位门徒、教会或当地领袖各领一份 — 为他们祷告 ",
|
||||
"peopleStillLoading": "族群仍在加载中,请稍后再试"
|
||||
"peopleStillLoading": "族群仍在加载中,请稍后再试",
|
||||
"posterDesignTerritory": "UPG-100 区域海报"
|
||||
}
|
||||
|
|
|
|||
1687
library/packet/upg100Zones/clusters.json
Normal file
1687
library/packet/upg100Zones/clusters.json
Normal file
File diff suppressed because it is too large
Load diff
401
library/packet/upg100Zones/legend-tree.js
Normal file
401
library/packet/upg100Zones/legend-tree.js
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
// legend-tree.js — slice 04t §1(a)
|
||||
//
|
||||
// Pure shaper: buildLegendNodes(clusters) -> TreeNode[]
|
||||
//
|
||||
// Turns clusters.json (13 regional `lists` + 2 `deaf_lists` + 12 `asia_lists`)
|
||||
// into the NESTED `nodes` tree consumed by the forge-shared
|
||||
// src/components/SemanticTreeLegend.vue (S04t-D4: nested). It does NOT rebuild a
|
||||
// legend — it only shapes data.
|
||||
//
|
||||
// Tree node shape (per SemanticTreeLegend.vue:13-30):
|
||||
// { id, label, color?, count?, pop?, filter?, info?, children?: TreeNode[] }
|
||||
//
|
||||
// Output shape (26 LEAF rows total):
|
||||
// - 13 Region leaves (top level)
|
||||
// - 1 Deaf leaf (top level; the two deaf_lists collapse into ONE
|
||||
// A017 row, count 123 — R1.1 design correction)
|
||||
// - 1 Asia parent ▸ India (10 state-cluster leaves) / China (2 leaves)
|
||||
//
|
||||
// Parents (Asia / India / China) carry rolled-up count. A stable zoneKey === leaf
|
||||
// id is assigned so the paint layer's ['match', ['get','zoneKey'], …] and the
|
||||
// legend's `select` emit share one key space.
|
||||
|
||||
// Deterministic per-leaf color: even hue spread across the 26 leaves, comma-HSL
|
||||
// (Mapbox paint expressions reject the modern slash hsl() syntax). Mirrors the
|
||||
// slice-04b color rule (profiles/upg-100-list.vue:77).
|
||||
export function leafColor(i, n) {
|
||||
return `hsl(${Math.round((i * 360) / (n || 1))}, 72%, 56%)`;
|
||||
}
|
||||
|
||||
import { evaluate } from './selector-evaluator.js';
|
||||
|
||||
// Count for one leaf: regional/asia carry `count`, deaf carries the A017 total.
|
||||
function leafCount(row) {
|
||||
if (typeof row.count === 'number') return row.count;
|
||||
if (typeof row.expected_count === 'number') return row.expected_count;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ── slice 04u §1(b): population per leaf ─────────────────────────────────────
|
||||
// Sum parseInt(member.population) over a zone's MEMBER records — the same member
|
||||
// set used for `count`. population is a flat string on the live record (full
|
||||
// recache parses it to int; parseInt here is belt-and-suspenders for either form).
|
||||
function popOf(record) {
|
||||
const n = parseInt(record && record.population, 10);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
// Resolve a zone's member records, then sum their population.
|
||||
// - regional + deaf rows use the selector-evaluator (country_code / wagf_* /
|
||||
// union / affinity_bloc) — same path zone-geometry uses for `count`.
|
||||
// - ASIA rows carry a `pg_slugs` selector the evaluator intentionally REJECTS
|
||||
// (it resolves geometry by pre-tagged listId, not by evaluating slugs). For
|
||||
// population we sum records whose slug ∈ selector.values directly — no
|
||||
// evaluator change, no shared-src edit.
|
||||
function leafPopulation(row, records) {
|
||||
if (!Array.isArray(records) || records.length === 0) return undefined;
|
||||
const sel = row && row.selector;
|
||||
if (sel == null) return undefined;
|
||||
|
||||
// Asia state-clusters: explicit pg_slugs member list.
|
||||
if (sel.type === 'pg_slugs') {
|
||||
const want = new Set(Array.isArray(sel.values) ? sel.values : []);
|
||||
let sum = 0;
|
||||
for (const r of records) if (r && want.has(r.slug)) sum += popOf(r);
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Everything else the evaluator handles → Set<slug> → sum member population.
|
||||
const slugs = evaluate(sel, records);
|
||||
let sum = 0;
|
||||
for (const r of records) if (r && slugs.has(r.slug)) sum += popOf(r);
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* buildLegendNodes(clusters, opts?) -> TreeNode[]
|
||||
* @param {object} clusters parsed clusters.json
|
||||
* @param {object} [opts]
|
||||
* @param {number} [opts.deafCount=123] A017 PG count for the unified deaf leaf
|
||||
* @param {Array} [opts.records] normalized live records — when supplied, every
|
||||
* node also gets `pop` (Σ member population; parents roll up).
|
||||
* Omit to keep the prior count-only behaviour (pop undefined).
|
||||
* @returns {Array} nested TreeNode[] (3 top-level groups, 26 leaves total)
|
||||
*/
|
||||
// ★ THE UNIFIED DEAF LEAF'S SELECTOR, DEFINED ONCE. The legend collapses the two deaf_lists into one
|
||||
// leaf (affinity bloc A017) whose selector exists nowhere in clusters.json, so every consumer that
|
||||
// RESOLVES zones had to know it — and each typed its own copy of the literal (the map in
|
||||
// upg-100-list.vue, generate.mjs, packet.mjs). The browser packet path had no copy at all, which is why
|
||||
// the Deaf packet could not be printed from the UI: the zone lookup found nothing and returned silently.
|
||||
export const DEAF_SELECTOR = { type: 'affinity_bloc', rop1: 'A017' };
|
||||
|
||||
// The catalogue key (poster.<key>) that names each REGION_OF grouping in the reader's language. The
|
||||
// English values stay the grouping identity (ids are derived from them), so the heading is translated
|
||||
// at render: here for the legend via opts.regionTitle, and by poster-wall.vue for the wall's sections.
|
||||
// One table for both surfaces, so a region added here cannot be translated on one and English on the other.
|
||||
export const REGION_KEY = {
|
||||
'Africa': 'region_africa',
|
||||
'Middle East': 'region_middle_east',
|
||||
'Central Asia': 'region_central_asia',
|
||||
'Southeast Asia + Oceania': 'region_southeast_asia_oceania',
|
||||
'China + Mongolia': 'region_china_mongolia',
|
||||
'India': 'region_india',
|
||||
'Other': 'region_other',
|
||||
'Americas': 'region_americas',
|
||||
'Europe': 'region_europe',
|
||||
'Deaf': 'region_deaf',
|
||||
};
|
||||
|
||||
export function buildLegendNodes(clusters, opts = {}) {
|
||||
const deafCount = opts.deafCount ?? 123;
|
||||
const records = Array.isArray(opts.records) ? opts.records : null;
|
||||
// ★ OPTIONAL GENERATED TITLE (2026-09-27). `opts.title(zone)` returns a localized title
|
||||
// composed from already-translated parts, or NULL for a zone whose title cannot be generated — see
|
||||
// data/zone-title.js. Passing nothing keeps every hand-written English label exactly as it was, which
|
||||
// is why this is an option rather than a rewrite: the legend, the poster and the packet adopt it
|
||||
// independently, and a caller that has no records yet (records:null) still builds a correct tree.
|
||||
// ⚠ NULL MEANS "KEEP YOURS", NOT "NO TITLE". 18 of 28 zones decline (their labels are sub-national or
|
||||
// taxonomic, and Intl has no subdivisions), so the fallback is the normal path, not the error path.
|
||||
const titleOf = typeof opts.title === 'function'
|
||||
? (zone, fallback) => opts.title(zone) || fallback
|
||||
: (_zone, fallback) => fallback;
|
||||
// `opts.regionTitle(english)` names a region section in the reader's language (see REGION_KEY); the
|
||||
// node id stays derived from the English grouping key, so selection and ids never depend on a locale.
|
||||
const regionTitleOf = typeof opts.regionTitle === 'function'
|
||||
? (english) => opts.regionTitle(english) || english
|
||||
: (english) => english;
|
||||
const lists = clusters.lists || [];
|
||||
const asia = clusters.asia_lists || [];
|
||||
// slice 09b-6 §1(a): wire the 7 uncovered "Frontier Asia" zones (Nepal,
|
||||
// Himalaya, SA-West, Central-Asia, Mongolia, Japan, Scatter). They carry a
|
||||
// `country_codes` selector → resolved by zone-geometry's REGIONAL/ADM0 path
|
||||
// (same as the 13 regional rows), NOT the asia ADM1 path. Default empty so
|
||||
// pre-09b-6 clusters (no uncovered_lists) keep the prior 26-leaf shape.
|
||||
const uncovered = clusters.uncovered_lists || [];
|
||||
|
||||
// Deaf row's selector is implicit (affinity bloc A017), so population can be
|
||||
// summed the same way as the regional rows. Module-level and exported — see DEAF_SELECTOR below.
|
||||
|
||||
// ── ordered leaf list (drives color index space) ───────────────────────────
|
||||
// order: 13 regional, 1 deaf, asia (10 India + 2 China), then the Frontier-Asia
|
||||
// uncovered rows — matches the legend reading order and the paint match arms.
|
||||
// ★ COUNTS ARE DERIVED FROM clusters.json, never asserted here (2026-09-21). This comment
|
||||
// used to claim "33 leaves" and "7 Frontier-Asia uncovered"; the shipped clusters.json
|
||||
// carries 13 + 12 + 2 + 1 deaf = 28, with exactly TWO uncovered rows
|
||||
// (uncovered-scatter, uncovered-nas-a). The stale 26-vs-reality gap also shipped as a
|
||||
// "27/26 zones" badge in upg-100-list.vue. A hardcoded count next to data that grows is a
|
||||
// drift waiting to happen — read the arrays instead.
|
||||
const indiaRows = asia.filter((a) => a.id.startsWith('asia-india'));
|
||||
const chinaRows = asia.filter((a) => a.id.startsWith('asia-china'));
|
||||
const orderedLeafIds = [
|
||||
...lists.map((l) => l.id),
|
||||
'deaf',
|
||||
...indiaRows.map((r) => r.id),
|
||||
...chinaRows.map((r) => r.id),
|
||||
...uncovered.map((u) => u.id),
|
||||
];
|
||||
const N = orderedLeafIds.length; // 33 (26 + 7 uncovered)
|
||||
const colorOf = (id) => leafColor(orderedLeafIds.indexOf(id), N);
|
||||
|
||||
// `pop` is attached only when records were supplied; undefined otherwise so the
|
||||
// prior count-only callers are unaffected. `withPop` adds the key conditionally.
|
||||
const withPop = (node, row, selector) => {
|
||||
if (!records) return node;
|
||||
const sel = selector ?? (row && row.selector);
|
||||
node.pop = leafPopulation({ selector: sel }, records);
|
||||
return node;
|
||||
};
|
||||
// Sum child pops (parent roll-up, S04u-D3). Returns undefined when no records.
|
||||
const sumPop = (children) =>
|
||||
records ? children.reduce((s, c) => s + (c.pop || 0), 0) : undefined;
|
||||
|
||||
// ── 13 Region leaves ───────────────────────────────────────────────────────
|
||||
const regionLeaves = lists.map((l) =>
|
||||
withPop(
|
||||
{
|
||||
id: l.id,
|
||||
zoneKey: l.id,
|
||||
label: titleOf(l, l.label),
|
||||
// Carry the province enumeration the region-name label replaced.
|
||||
...(l.provinceLabel ? { provinceLabel: l.provinceLabel } : {}),
|
||||
color: colorOf(l.id),
|
||||
count: leafCount(l),
|
||||
},
|
||||
l,
|
||||
),
|
||||
);
|
||||
|
||||
// ── 1 Deaf leaf (unified A017; R1.1) ───────────────────────────────────────
|
||||
// D2: deaf pop = Σ population of the 123 A017 members. This WILL double-count
|
||||
// vs the geographic rows (those members also fall inside a regional zone) —
|
||||
// the design-approved deaf double-count (slice 08). Per-zone equality still
|
||||
// holds; only a naïve global sum would over-count, which the proof avoids.
|
||||
const deafLeaf = withPop(
|
||||
{
|
||||
id: 'deaf',
|
||||
zoneKey: 'deaf',
|
||||
// Named like every other leaf: opts.title reads the bloc's name from the strings it was given
|
||||
// (zone-title.js, zone_affinity_A017); the English label is only the no-title fallback.
|
||||
label: titleOf({ id: 'deaf', selector: DEAF_SELECTOR }, 'Deaf (affinity bloc A017)'),
|
||||
color: colorOf('deaf'),
|
||||
count: deafCount,
|
||||
info: 'Unified deaf row — affinity bloc rop1=A017 (collapses the two deaf_lists; zero-delta vs old deaf_predicate). Population double-counts vs geographic rows (design-approved, slice 08).',
|
||||
},
|
||||
null,
|
||||
DEAF_SELECTOR,
|
||||
);
|
||||
|
||||
// ── Asia parent ▸ India / China ────────────────────────────────────────────
|
||||
const mkAsiaLeaf = (r) =>
|
||||
withPop(
|
||||
{
|
||||
id: r.id,
|
||||
zoneKey: r.id,
|
||||
label: titleOf(r, r.label),
|
||||
// The province enumeration the region-name label replaced. Carried through
|
||||
// like adm0/provinces above — a node rebuilt field-by-field silently drops
|
||||
// anything not named, which is exactly how adm0 and population were lost.
|
||||
...(r.provinceLabel ? { provinceLabel: r.provinceLabel } : {}),
|
||||
color: colorOf(r.id),
|
||||
count: leafCount(r),
|
||||
// MIXED GEOMETRY: carry the optional `adm0` ISO3 list through to the node.
|
||||
// zone-geometry.js ALREADY supports it — an Asia zone may union whole ADM0
|
||||
// countries alongside its ADM1 provinces, and its comment names the Nepal
|
||||
// case explicitly — but the node was rebuilt field-by-field here and
|
||||
// silently dropped adm0, so the hook could never fire.
|
||||
...(Array.isArray(r.adm0) && r.adm0.length ? { adm0: r.adm0 } : {}),
|
||||
// Same passthrough reason as adm0: the province enumeration is data the
|
||||
// poster header prints, and rebuilding the node field-by-field drops it.
|
||||
...(Array.isArray(r.provinces) && r.provinces.length ? { provinces: r.provinces } : {}),
|
||||
},
|
||||
r,
|
||||
);
|
||||
const indiaChildren = indiaRows.map(mkAsiaLeaf);
|
||||
const chinaChildren = chinaRows.map(mkAsiaLeaf);
|
||||
|
||||
// (The India / China / "Asia (admin-1)" and "Frontier Asia (uncovered)" PARENT nodes that used to be
|
||||
// built here are gone: since the regional grouping below, the tree is assembled from the LEAVES, so those
|
||||
// parents were constructed and never returned — dead code whose English labels read as reader text.)
|
||||
|
||||
// ── Frontier Asia parent ▸ 7 uncovered zones (09b-6 §1a, W-D1) ─────────────
|
||||
// Each uncovered row carries a `country_codes` selector → ADM0 polygons
|
||||
// (Nepal→NPL, SA-West→AFG+PAK, …). Same leaf shape as regional rows so the
|
||||
// tapestry's single ['match',['get','zoneKey'],…] paints them with the rest.
|
||||
const mkUncoveredLeaf = (u) =>
|
||||
withPop(
|
||||
{
|
||||
id: u.id,
|
||||
zoneKey: u.id,
|
||||
label: titleOf(u, u.label),
|
||||
// Carry the province enumeration the region-name label replaced.
|
||||
...(u.provinceLabel ? { provinceLabel: u.provinceLabel } : {}),
|
||||
color: colorOf(u.id),
|
||||
count: leafCount(u),
|
||||
},
|
||||
u,
|
||||
);
|
||||
const uncoveredChildren = uncovered.map(mkUncoveredLeaf);
|
||||
|
||||
// ── REGIONAL GROUPING — the SAME sections the poster page uses ─────────────
|
||||
// The legend used to emit 14 LOOSE LEAVES at top level (West Africa, Sudan,
|
||||
// Brazil, Laos, …) beside two odd parents, "Asia (admin-1)" and "Frontier Asia
|
||||
// (uncovered)". That grouping read poorly beside the poster page, so the legend
|
||||
// now uses the poster page's own sections.
|
||||
//
|
||||
// So the same 28 zones now group into the poster page's nine regions. The
|
||||
// mapping is duplicated from poster-wall.vue rather than imported because this
|
||||
// file is a PURE SHAPER with no imports by design — but the two lists must stay
|
||||
// in step, so any edit here belongs in poster-wall.vue's REGION_OF too.
|
||||
const REGION_OF = {
|
||||
'west-africa': 'Africa', 'sudan': 'Africa', 'east-africa': 'Africa',
|
||||
'central-africa': 'Africa', 'southern-africa-islands': 'Africa',
|
||||
'maghreb-egypt': 'Middle East', 'middle-east-core': 'Middle East',
|
||||
'uncovered-nas-a': 'Central Asia',
|
||||
'laos': 'Southeast Asia + Oceania', 'oceania-indonesia': 'Southeast Asia + Oceania',
|
||||
'mainland-se-asia-philippines': 'Southeast Asia + Oceania',
|
||||
'brazil': 'Americas', 'andean-caribbean': 'Americas', 'uncovered-scatter': 'Americas',
|
||||
'europe': 'Europe', 'deaf': 'Deaf', 'deaf-old-world': 'Deaf',
|
||||
};
|
||||
const regionFor = (id) =>
|
||||
REGION_OF[id]
|
||||
|| (String(id).startsWith('asia-india-') ? 'India'
|
||||
: String(id).startsWith('asia-china-') ? 'China + Mongolia'
|
||||
: 'Other');
|
||||
|
||||
// Every leaf the tree can paint, flattened out of its source block. India and
|
||||
// China leaves are indiaChildren / chinaChildren; the rest are already flat.
|
||||
const allLeaves = [
|
||||
...regionLeaves,
|
||||
deafLeaf,
|
||||
...indiaChildren,
|
||||
...chinaChildren,
|
||||
...uncoveredChildren,
|
||||
].filter(Boolean);
|
||||
|
||||
const byRegion = new Map();
|
||||
for (const leaf of allLeaves) {
|
||||
const r = regionFor(leaf.id || leaf.zoneKey);
|
||||
if (!byRegion.has(r)) byRegion.set(r, []);
|
||||
byRegion.get(r).push(leaf);
|
||||
}
|
||||
|
||||
// Sections ordered by TOTAL people groups (largest first), leaves within a
|
||||
// section by their own count — the densest prayer need leads at both levels,
|
||||
// matching the poster page exactly.
|
||||
const tops = [...byRegion.entries()]
|
||||
.map(([label, children]) => {
|
||||
children.sort((a, b) => (b.count || 0) - (a.count || 0));
|
||||
const node = {
|
||||
// ★ FOLD DIACRITICS BEFORE STRIPPING, or the mark is DELETED and the id silently becomes a
|
||||
// different id: Gujarāt -> 'region-gujar-t', not 'region-gujarat'. Today's labels are the
|
||||
// fixed ASCII set from regionFor(), so nothing is broken — but the whole point is that
|
||||
// a fork brings its OWN people-group set and names its own regions, and the first accented
|
||||
// label would split one region into two with no error and no empty state. Same class as a measured
|
||||
// join of admin-to-list.json: a naive fold matched 514 of 1009 rows and lost THREE
|
||||
// whole zones, while every surviving zone still looked populated. Fix the class, not the six
|
||||
// spellings we happen to know.
|
||||
id: 'region-' + label.normalize('NFD').replace(/\p{Mn}/gu, '').toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
||||
label: regionTitleOf(label),
|
||||
count: children.reduce((n, c) => n + (c.count || 0), 0),
|
||||
children,
|
||||
};
|
||||
if (records) node.pop = sumPop(children);
|
||||
return node;
|
||||
})
|
||||
.sort((a, b) => (b.count || 0) - (a.count || 0));
|
||||
|
||||
// ── L15 consumer-shape assertion (09b-6 §1b) ───────────────────────────────
|
||||
// Every RENDERED LEAF — regardless of source block (lists / asia_lists /
|
||||
// uncovered_lists) — must carry the same required fields so zone-geometry and
|
||||
// the paint match expression read a uniform shape. Fail loud (throw) rather
|
||||
// than silently paint a malformed zone.
|
||||
assertUniformLeafShape(leafZones(tops), { withPop: !!records });
|
||||
|
||||
return tops;
|
||||
}
|
||||
|
||||
// L15 (09b-6 §1b): assert every leaf zone has the uniform consumer shape.
|
||||
// Required on ALL leaves: id, zoneKey, label, color (string), count (number).
|
||||
// When records were supplied, `pop` must also be a number on every leaf.
|
||||
// Parents (asia/india/china/frontier-asia) are intentionally NOT leaves and are
|
||||
// excluded by leafZones() — they carry roll-ups, not a zoneKey/selector.
|
||||
export function assertUniformLeafShape(leaves, { withPop = false } = {}) {
|
||||
const bad = [];
|
||||
for (const z of leaves || []) {
|
||||
const errs = [];
|
||||
if (typeof z.id !== 'string' || !z.id) errs.push('id');
|
||||
if (typeof z.zoneKey !== 'string' || !z.zoneKey) errs.push('zoneKey');
|
||||
if (typeof z.label !== 'string' || !z.label) errs.push('label');
|
||||
if (typeof z.color !== 'string' || !z.color) errs.push('color');
|
||||
if (typeof z.count !== 'number') errs.push('count');
|
||||
if (withPop && typeof z.pop !== 'number') errs.push('pop');
|
||||
if (errs.length) bad.push({ id: z.id || '(no-id)', errs });
|
||||
}
|
||||
if (bad.length) {
|
||||
// A developer assertion: the text is composed here, inside the throw, so it is plainly not reader copy.
|
||||
throw new Error(
|
||||
`legend-tree L15: ${bad.length} leaf node(s) violate the uniform consumer shape:\n ` +
|
||||
bad.map((b) => `${b.id}: missing/!type [${b.errs.join(', ')}]`).join('\n '),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Flatten a nodes tree to its LEAF nodes (rows that own a zoneKey/paint zone).
|
||||
export function leafZones(nodes) {
|
||||
const out = [];
|
||||
const walk = (arr) => {
|
||||
for (const n of arr) {
|
||||
if (n.children && n.children.length) walk(n.children);
|
||||
else out.push(n);
|
||||
}
|
||||
};
|
||||
walk(nodes || []);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every printable zone — the legend's leaves — each carrying the selector that resolves its groups.
|
||||
*
|
||||
* ★ THE ENUMERATION IS THE LEGEND'S, NOT clusters.json's BUCKETS. Spreading lists/asia_lists/
|
||||
* uncovered_lists gives 27 zones and silently drops `deaf`, which the legend shows as one row; a
|
||||
* packet lookup built that way cannot print Deaf, and a count built that way prints "one of 27" on
|
||||
* page 1 and at the triangle's tip while 28 packets exist. Leaves do not carry selectors, so they are
|
||||
* attached here by id, the unified deaf leaf taking DEAF_SELECTOR.
|
||||
* Returns COPIES; the legend's own node objects are left untouched.
|
||||
*
|
||||
* @param {object} clusters the zone catalogue (clusters.json shape)
|
||||
* @param {object} [opts] passed through to buildLegendNodes
|
||||
* @returns {Array<{id:string, zoneKey:string, label:string, selector:object}>}
|
||||
*/
|
||||
export function zonesWithSelectors(clusters, opts = {}) {
|
||||
const byId = {};
|
||||
for (const k of ['lists', 'asia_lists', 'uncovered_lists']) {
|
||||
for (const z of (clusters && clusters[k]) || []) byId[z.id] = z.selector;
|
||||
}
|
||||
byId.deaf = DEAF_SELECTOR;
|
||||
return leafZones(buildLegendNodes(clusters, opts))
|
||||
.map((z) => ({ ...z, zoneKey: z.zoneKey || z.id, selector: byId[z.id] }));
|
||||
}
|
||||
|
||||
export default buildLegendNodes;
|
||||
165
library/packet/upg100Zones/selector-evaluator.js
Normal file
165
library/packet/upg100Zones/selector-evaluator.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
// selector-evaluator.js — slice 01 (my-upg-100-list)
|
||||
//
|
||||
// Pure function: evaluate(selector, records) -> Set<slug>
|
||||
//
|
||||
// `records` are assumed ALREADY NORMALIZED by normalize.js, i.e. the nested
|
||||
// { value, label } fields (country_code, wagf_region, wagf_block, primary_language)
|
||||
// have been flattened to flat `.value` strings. The evaluator therefore compares
|
||||
// flat record fields against flat selector values — no .value access here.
|
||||
//
|
||||
// Handles the 6 selector types documented in data/clusters.source.md + the qa file:
|
||||
// 1. { type: "country_code", value: "SDN" }
|
||||
// -> records whose country_code === value
|
||||
// 2. { type: "country_codes", values: [...] } (note plural `values`)
|
||||
// -> records whose country_code ∈ values
|
||||
// 3. { type: "wagf_block", value: "west_africa" }
|
||||
// -> records whose wagf_block === value
|
||||
// 4. { type: "wagf_region", value: "europe" }
|
||||
// -> records whose wagf_region === value
|
||||
// 5. { type: "union", selectors: [ <sel>, <sel>, ... ] }
|
||||
// -> union of evaluating each sub-selector
|
||||
// 6. { type: "deaf_predicate", regions: [...] }
|
||||
// -> records where (slug OR name contains "deaf", case-insensitive)
|
||||
// AND wagf_region ∈ regions
|
||||
// 7. { type: "affinity_bloc", rop1: "A017" } (slice 04t §1g)
|
||||
// -> records where r.rop1.value === rop1
|
||||
// NOTE: rop1 is NOT in normalize.js NESTED_FIELDS, so it stays a nested
|
||||
// { value, label } dict on the record. This selector reads `.value`
|
||||
// directly (and tolerates an already-flat rop1 string). The Deaf row's
|
||||
// bloc code is "A017" (123 PGs, == the old deaf_predicate slug set, zero
|
||||
// delta but field-authoritative). 📁 affinity-block.js:36, sources.json:68
|
||||
//
|
||||
// Design contract:
|
||||
// - returns a Set<slug> (always; never null/undefined)
|
||||
// - a bogus VALUE (e.g. country_code "ZZZ") -> empty Set, never a crash
|
||||
// - an UNKNOWN selector type -> throw a clear Error (never silently empty)
|
||||
|
||||
// Collect the slug of a record. Records without a slug are skipped (cannot key a Set entry).
|
||||
function slugOf(record) {
|
||||
if (record == null || typeof record !== 'object') return null;
|
||||
return record.slug ?? null;
|
||||
}
|
||||
|
||||
// True if the haystack string contains "deaf" (case-insensitive).
|
||||
function containsDeaf(s) {
|
||||
return typeof s === 'string' && s.toLowerCase().includes('deaf');
|
||||
}
|
||||
|
||||
// Read the affinity-bloc code off a record's `rop1` field. rop1 is intentionally
|
||||
// NOT flattened by normalize.js, so it normally arrives as { value, label }; this
|
||||
// also tolerates an already-flat string (defensive).
|
||||
function rop1Code(record) {
|
||||
const f = record == null ? null : record.rop1;
|
||||
if (f == null) return null;
|
||||
if (typeof f === 'object') return f.value ?? null;
|
||||
return f; // already-flat string
|
||||
}
|
||||
|
||||
/**
|
||||
* evaluate(selector, records) -> Set<slug>
|
||||
* @param {object} selector one of the 6 documented selector shapes
|
||||
* @param {Array<object>} records ALREADY-normalized UPG records
|
||||
* @returns {Set<string>} set of matching slugs
|
||||
* @throws {Error} on an unknown / malformed selector type
|
||||
*/
|
||||
export function evaluate(selector, records) {
|
||||
const out = new Set();
|
||||
|
||||
if (selector == null || typeof selector !== 'object' || typeof selector.type !== 'string') {
|
||||
throw new Error(
|
||||
`selector-evaluator: malformed selector (expected an object with a string \`type\`), got: ${JSON.stringify(selector)}`,
|
||||
);
|
||||
}
|
||||
const rows = Array.isArray(records) ? records : [];
|
||||
|
||||
switch (selector.type) {
|
||||
case 'country_code': {
|
||||
const want = selector.value;
|
||||
for (const r of rows) {
|
||||
if (r && r.country_code === want) {
|
||||
const slug = slugOf(r);
|
||||
if (slug != null) out.add(slug);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'country_codes': {
|
||||
// plural: match against the `values` array
|
||||
const wanted = new Set(Array.isArray(selector.values) ? selector.values : []);
|
||||
for (const r of rows) {
|
||||
if (r && wanted.has(r.country_code)) {
|
||||
const slug = slugOf(r);
|
||||
if (slug != null) out.add(slug);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'wagf_block': {
|
||||
const want = selector.value;
|
||||
for (const r of rows) {
|
||||
if (r && r.wagf_block === want) {
|
||||
const slug = slugOf(r);
|
||||
if (slug != null) out.add(slug);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'wagf_region': {
|
||||
const want = selector.value;
|
||||
for (const r of rows) {
|
||||
if (r && r.wagf_region === want) {
|
||||
const slug = slugOf(r);
|
||||
if (slug != null) out.add(slug);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'union': {
|
||||
const subs = Array.isArray(selector.selectors) ? selector.selectors : [];
|
||||
for (const sub of subs) {
|
||||
// recurse; sub-selector errors (unknown type) propagate up by design
|
||||
const subSet = evaluate(sub, rows);
|
||||
for (const slug of subSet) out.add(slug);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'deaf_predicate': {
|
||||
const regions = new Set(Array.isArray(selector.regions) ? selector.regions : []);
|
||||
for (const r of rows) {
|
||||
if (!r) continue;
|
||||
const isDeaf = containsDeaf(r.slug) || containsDeaf(r.name);
|
||||
if (isDeaf && regions.has(r.wagf_region)) {
|
||||
const slug = slugOf(r);
|
||||
if (slug != null) out.add(slug);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
case 'affinity_bloc': {
|
||||
// slice 04t §1g — Deaf row: records whose rop1.value === selector.rop1 (e.g. "A017").
|
||||
const want = selector.rop1;
|
||||
for (const r of rows) {
|
||||
if (!r) continue;
|
||||
if (rop1Code(r) === want) {
|
||||
const slug = slugOf(r);
|
||||
if (slug != null) out.add(slug);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(
|
||||
`selector-evaluator: unknown selector type "${selector.type}" ` +
|
||||
`(supported: country_code, country_codes, wagf_block, wagf_region, union, deaf_predicate, affinity_bloc)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default evaluate;
|
||||
138
library/packet/upg100Zones/zone-geometry.js
Normal file
138
library/packet/upg100Zones/zone-geometry.js
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// zone-geometry.js — slice 04t §1(b)
|
||||
//
|
||||
// resolveZoneGeometry(zone, deps) -> FeatureCollection
|
||||
//
|
||||
// Per legend row ("zone"), returns the polygons it needs, each feature stamped
|
||||
// with the row's `zoneKey` + `zoneColor` so the tapestry's single Mapbox
|
||||
// ['match', ['get','zoneKey'], …] fill expression can paint all 26 at once.
|
||||
//
|
||||
// Resolution rules (S04t-D1/D2/D3):
|
||||
// - REGIONAL (13) + DEAF (1): ADM0 country polygons.
|
||||
// member countries = evaluate(zone.selector, records) -> slugs
|
||||
// -> distinct country_code over those member records
|
||||
// -> ADM0 features whose props.iso3 ∈ that set.
|
||||
// (deaf's selector is affinity_bloc/A017 → its 123 members' country_codes.)
|
||||
// - ASIA (12): ADM1 features. Asia rows are state-CLUSTERS, so SELECT-MANY:
|
||||
// every ADM1 feature whose props.listId === zone.id (the on-disk
|
||||
// gb-IND-ADM1 / gb-CHN-ADM1 files are pre-tagged with listId).
|
||||
//
|
||||
// Design contract:
|
||||
// - always returns a FeatureCollection (never null); empty features ⇒ a zone
|
||||
// the proof will FLAG (every zone must resolve ≥1 polygon).
|
||||
// - separate polygons sharing one zone color (S04t-D3); no dissolve here.
|
||||
// - pure: no Mapbox / DOM; testable in plain Node.
|
||||
|
||||
import { evaluate } from './selector-evaluator.js';
|
||||
|
||||
const EMPTY_FC = () => ({ type: 'FeatureCollection', features: [] });
|
||||
|
||||
// Distinct country_code (ISO3) set for a zone's member UPGs.
|
||||
// `records` are normalized (country_code flat); rop1 stays nested (selector reads it).
|
||||
export function memberCountryCodes(zone, records) {
|
||||
const slugs = evaluate(zone.selector, records);
|
||||
const iso = new Set();
|
||||
for (const r of records) {
|
||||
if (r && slugs.has(r.slug) && r.country_code) iso.add(r.country_code);
|
||||
}
|
||||
return iso;
|
||||
}
|
||||
|
||||
// True for the 12 Asia rows (admin-1 resolution).
|
||||
export function isAsiaZone(zone) {
|
||||
return typeof zone.id === 'string' && zone.id.startsWith('asia-');
|
||||
}
|
||||
|
||||
function stamp(feature, zone) {
|
||||
return {
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
...(feature.properties || {}),
|
||||
zoneKey: zone.zoneKey ?? zone.id,
|
||||
zoneColor: zone.color ?? zone.zoneColor ?? '#888',
|
||||
zoneLabel: zone.label,
|
||||
...(zone.id === 'deaf' ? { hatch: true } : {}),
|
||||
},
|
||||
geometry: feature.geometry,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* resolveZoneGeometry(zone, deps) -> FeatureCollection
|
||||
* @param {object} zone a leaf zone { id, zoneKey, label, color, selector? }
|
||||
* @param {object} deps
|
||||
* @param {Array} deps.records normalized UPG records (rop1-bearing for deaf)
|
||||
* @param {object} deps.adm0 FeatureCollection of ADM0 (features props.iso3)
|
||||
* @param {object} deps.adm1Ind FeatureCollection gb-IND-ADM1 (props.listId)
|
||||
* @param {object} deps.adm1Chn FeatureCollection gb-CHN-ADM1 (props.listId)
|
||||
*/
|
||||
export function resolveZoneGeometry(zone, deps) {
|
||||
const { records = [], adm0, adm1Ind, adm1Chn } = deps || {};
|
||||
const fc = EMPTY_FC();
|
||||
|
||||
if (isAsiaZone(zone)) {
|
||||
// ADM1 select-many: every feature pre-tagged with this row's listId.
|
||||
const pools = [adm1Ind, adm1Chn].filter(Boolean);
|
||||
for (const pool of pools) {
|
||||
for (const f of pool.features || []) {
|
||||
if (f.properties && f.properties.listId === zone.id) fc.features.push(stamp(f, zone));
|
||||
}
|
||||
}
|
||||
// slice 09b-8 §1(c): the MIXED-GEOMETRY hook. An Asia zone may ALSO carry an
|
||||
// optional `adm0` array of ISO3 codes (the Nepal-frontier cluster: ['NPL']).
|
||||
// When present, union the matching ADM0 country polygons into the SAME zone so
|
||||
// a single legend row paints both its India states (ADM1) and Nepal (ADM0) as
|
||||
// one zone. Additive + opt-in: every other Asia zone lacks `adm0`, so this loop
|
||||
// is skipped and their resolution is byte-identical to 09b-7.
|
||||
if (Array.isArray(zone.adm0) && zone.adm0.length && adm0) {
|
||||
const want = new Set(zone.adm0);
|
||||
for (const f of adm0.features || []) {
|
||||
if (f.properties && want.has(f.properties.iso3)) fc.features.push(stamp(f, zone));
|
||||
}
|
||||
}
|
||||
return fc;
|
||||
}
|
||||
|
||||
// Regional / deaf: ADM0 by member country_codes.
|
||||
if (!zone.selector) return fc; // a parent (asia/india/china) — no geometry of its own
|
||||
const iso = memberCountryCodes(zone, records);
|
||||
if (adm0 && iso.size) {
|
||||
for (const f of adm0.features || []) {
|
||||
if (f.properties && iso.has(f.properties.iso3)) fc.features.push(stamp(f, zone));
|
||||
}
|
||||
}
|
||||
return fc;
|
||||
}
|
||||
|
||||
/**
|
||||
* resolveAllZones(zones, deps) -> { tapestry: FeatureCollection, perZone: Map }
|
||||
* Builds ONE FeatureCollection (all zones, each feature carrying its zoneKey) for
|
||||
* the single-source tapestry paint, plus a per-zone resolved-count map for the proof.
|
||||
*/
|
||||
export function resolveAllZones(zones, deps) {
|
||||
const tapestry = EMPTY_FC();
|
||||
const perZone = new Map();
|
||||
// deaf-polygon removal (design fix, an earlier fix): the `deaf` affinity overlay is an
|
||||
// ADM0 double-stamp — its A017 selector re-paints land already owned by the 13 geographic
|
||||
// zones (16 of oceania-indonesia's 18 Pacific ISO3 alone). That double-stamp showed deaf
|
||||
// TWICE and underlapped every other region in blue (#18ffff), and the overlapping deaf land
|
||||
// didn't color correctly until click-isolate re-filtered the source to one zone. The earlier
|
||||
// 09b draw-order mitigation (resolve deaf FIRST) only re-stacked the same overlapping geometry;
|
||||
// the directive is to REMOVE the deaf polygon/region layer entirely. So we EXCLUDE
|
||||
// the deaf zone from the tapestry (region-fill) FeatureCollection here. deaf stays fully in
|
||||
// the LIST/legend (built by legend-tree.js buildLegendNodes — untouched) and in the PINS
|
||||
// (built from slugToZone selectors in upg-100-list.vue — untouched). Only the polygon/region
|
||||
// fill loses deaf, which removes the overlap and the until-click coloring bug.
|
||||
for (const zone of zones) {
|
||||
if (zone.id === 'deaf') {
|
||||
// record 0 polygons for the proof's per-zone map; deaf intentionally has no region fill now.
|
||||
perZone.set(zone.zoneKey ?? zone.id, 0);
|
||||
continue;
|
||||
}
|
||||
const fc = resolveZoneGeometry(zone, deps);
|
||||
perZone.set(zone.zoneKey ?? zone.id, fc.features.length);
|
||||
for (const f of fc.features) tapestry.features.push(f);
|
||||
}
|
||||
return { tapestry, perZone };
|
||||
}
|
||||
|
||||
export default resolveZoneGeometry;
|
||||
41
library/packet/upg100Zones/zone-sheet-print.js
Normal file
41
library/packet/upg100Zones/zone-sheet-print.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* zone-sheet-print.js — a UPG-100 zone's map sheet, GENERATED in the browser at the click (the print
|
||||
* standard: generate, store only previews). One helper for every surface that prints a zone (the map's
|
||||
* legend and packets, the poster wall), so they cannot draw the "same" sheet two ways.
|
||||
*
|
||||
* The zone and its pins come from zone-sheet.js — the SAME assignment generate.mjs makes — the geometry
|
||||
* from the full-resolution boundaries generate.mjs draws, and the drawing from the library's generator.
|
||||
*
|
||||
* const zoneSheet = makeZoneSheet({ allRecords, assetsBase: () => base, profileConfig })
|
||||
* usePosterPrints({ …, sheet: zoneSheet }) // (item, format) -> html
|
||||
*
|
||||
* Library imports are RELATIVE, not `@map/…`: the same file runs under the bundler, Node and the
|
||||
* browser-equivalence check (bun), and only a relative path resolves identically in all three.
|
||||
*/
|
||||
import { upg100Zones, assignPins, seriesFacts } from './zone-sheet.js'
|
||||
import { resolveZoneGeometry } from './zone-geometry.js'
|
||||
import { zonesForList } from './zones-for-list.js'
|
||||
import { resolvePeopleGroupList } from '../../lists/peopleGroupList.js'
|
||||
|
||||
// `date` pins the stamp printed on the sheet (default: today) — how a check reproduces a sheet exactly.
|
||||
export function makeZoneSheet({ allRecords, assetsBase, profileConfig, date = null }) {
|
||||
return async function zoneSheet(item, format) {
|
||||
const { generateSheet, loadBoundaries } = await import('../../packet/posterSheets.js')
|
||||
const [{ ISO3_NAME }, records, bounds] = await Promise.all([
|
||||
import('../../data/iso3-name.js'), allRecords(), loadBoundaries(assetsBase()),
|
||||
])
|
||||
const zones = upg100Zones(zonesForList(resolvePeopleGroupList(profileConfig)), records)
|
||||
const zone = zones.find((z) => z.id === (item && item.id))
|
||||
if (!zone) return '' // a selection or an unknown id has no zone sheet; the caller falls through
|
||||
const pins = assignPins(zones, records).get(zone.zoneKey) || []
|
||||
const zoneFC = resolveZoneGeometry(zone, { records, ...bounds })
|
||||
// ★ THE PACKET'S PAGE 1 IS THE FULL UPG-100 POSTER (2026-10-02), not the map-only sheet: the letter-size
|
||||
// poster with its title, the scripture banner, the three-column header and the footer. The packet asks
|
||||
// for 'packet'; this series answers with the full letter sheet, which is the same 1500x1941 page.
|
||||
const page1 = format === 'packet'
|
||||
return generateSheet({
|
||||
zone, pins, zoneFC, format: page1 ? 'letter' : format, ...(page1 ? { sparse: false } : {}), seriesId: 'upg100', ISO3_NAME, adm0: bounds.adm0, adm1Ind: bounds.adm1Ind, adm1Chn: bounds.adm1Chn,
|
||||
series: { ...seriesFacts(zones), index: zones.indexOf(zone) + 1 }, date, assetsBase: assetsBase(),
|
||||
})
|
||||
}
|
||||
}
|
||||
58
library/packet/upg100Zones/zone-sheet.js
Normal file
58
library/packet/upg100Zones/zone-sheet.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* zone-sheet.js — which people groups a map sheet pins, decided ONCE for the Node generator and the
|
||||
* browser alike.
|
||||
*
|
||||
* generate.mjs used to hold this in its main routine: attach each leaf zone's selector, then give every
|
||||
* people group to the FIRST zone that claims it and pin it there. A browser that draws the same sheet
|
||||
* must make exactly the same assignment, or the "same" poster pins a group in a different zone
|
||||
* depending on where it was printed. So both import this module; neither keeps its own copy.
|
||||
*
|
||||
* Pure: no fs, no fetch, no DOM. The caller supplies the records (with latitude/longitude).
|
||||
*/
|
||||
import { buildLegendNodes, leafZones, DEAF_SELECTOR } from './legend-tree.js';
|
||||
import { evaluate } from './selector-evaluator.js';
|
||||
|
||||
/** The UPG-100 leaf zones, each carrying its selector (leaves do not carry it; the catalogue does). */
|
||||
export function upg100Zones(clusters, records) {
|
||||
const zones = leafZones(buildLegendNodes(clusters, { records }));
|
||||
const byId = {};
|
||||
for (const l of clusters.lists || []) byId[l.id] = l.selector;
|
||||
for (const a of clusters.asia_lists || []) byId[a.id] = a.selector;
|
||||
for (const u of clusters.uncovered_lists || []) byId[u.id] = u.selector;
|
||||
byId['deaf'] = DEAF_SELECTOR; // defined once in legend-tree.js
|
||||
for (const z of zones) z.selector = byId[z.id];
|
||||
return zones;
|
||||
}
|
||||
|
||||
/**
|
||||
* zoneKey -> the pins of that zone. A group belongs to the FIRST zone (in `zones` order) whose selector
|
||||
* claims it; groups without coordinates are not pinned. Pins keep the records' own order.
|
||||
* `onSkip(zone, error)` hears about a selector that could not be evaluated (the zone then pins nothing).
|
||||
*/
|
||||
export function assignPins(zones, records, { onSkip = null } = {}) {
|
||||
const slugToZone = {};
|
||||
for (const z of zones) {
|
||||
if (z.selector && z.selector.type === 'pg_slugs') {
|
||||
for (const s of (z.selector.values || [])) if (!(s in slugToZone)) slugToZone[s] = z.zoneKey;
|
||||
} else if (z.selector) {
|
||||
try { for (const s of evaluate(z.selector, records)) if (!(s in slugToZone)) slugToZone[s] = z.zoneKey; }
|
||||
catch (e) { if (onSkip) onSkip(z, e); }
|
||||
}
|
||||
}
|
||||
const pinsByZone = new Map(zones.map((z) => [z.zoneKey, []]));
|
||||
for (const r of records) {
|
||||
const lat = Number(r.latitude), lng = Number(r.longitude);
|
||||
if (!isFinite(lat) || !isFinite(lng)) continue;
|
||||
const zk = slugToZone[r.slug];
|
||||
if (zk && pinsByZone.has(zk)) pinsByZone.get(zk).push({
|
||||
name: r.name || r.slug || '', lat, lng, slug: r.slug, country_code: r.country_code,
|
||||
population: r.population,
|
||||
});
|
||||
}
|
||||
return pinsByZone;
|
||||
}
|
||||
|
||||
/** "Map N of M" and the total the poster states, from the zones being generated. */
|
||||
export function seriesFacts(zones) {
|
||||
return { totalGroups: zones.reduce((a, z) => a + (z.count || 0), 0), totalLists: zones.length, index: 1 };
|
||||
}
|
||||
104
library/packet/upg100Zones/zones-for-list.js
Normal file
104
library/packet/upg100Zones/zones-for-list.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* zones-for-list — which ZONES a project list is clustered into.
|
||||
*
|
||||
* THE GAP THIS CLOSES. Membership is already parameterised by list: library/lists/lists.json registers
|
||||
* five (doxa = Project 2000, unreached = 7000, frontier = 4700, frontier-500k = 700, unengaged), and
|
||||
* resolvePeopleGroupList(profileConfig) already picks one from `config.list` / `?list=` / window.MAP_LIST
|
||||
* with a default. What NO list has is ZONES: a list file says WHICH people groups, and nothing says how
|
||||
* to cluster them into the ~100-group printable zones a poster needs. clusters.json is UPG-100's, and it
|
||||
* was the only answer available.
|
||||
*
|
||||
* ★ WHY THIS IS A RESOLVER AND NOT A DYNAMIC IMPORT. The two consumers do:
|
||||
* import { lists, asia_lists, uncovered_lists } from './data/clusters.json'
|
||||
* and that NAMED import is load-bearing — its own comment says so: only the zone arrays ship, while
|
||||
* clusters.json's eight bookkeeping fields (version, generated, source_total, validation_run, …) are
|
||||
* tree-shaken out. Swapping it for `await import(path)` would ship the whole file in every bundle, and
|
||||
* would make the zone list asynchronous in code that reads it synchronously at setup. So the default
|
||||
* stays a static named import, and alternates are REGISTERED rather than computed from a path.
|
||||
*
|
||||
* ⚠ AND THE DEFAULT MUST BE THE OLD BEHAVIOUR EXACTLY. A list with no zones of its own gets UPG-100's,
|
||||
* which is what every caller gets today — so this file is inert until someone registers a second set.
|
||||
* That is deliberate: the pattern has to be landable before any second list exists, or it cannot be the
|
||||
* thing the second list is built against.
|
||||
*
|
||||
* ★ WHAT A NEW LIST ACTUALLY SHIPS, and this is the pattern for another agent to follow:
|
||||
* 1. data/zones/<list-id>.json the four zone-bearing keys, same shape as clusters.json:
|
||||
* lists / deaf_lists / asia_lists / uncovered_lists
|
||||
* 2. data/zoning-declarations.<id>.json the facts a gate cannot infer — bloc zones, expected orphans,
|
||||
* known geographic overlaps, page budget
|
||||
* 3. one line in ZONES_BY_LIST below
|
||||
* Then `npm run check:zoning-declared -- --list <id>` grades the zoning, and the legend, poster wall and
|
||||
* packet all follow, because all three already enumerate through leafZones(buildLegendNodes(zones)).
|
||||
*
|
||||
* ZONING RULE (2026-09-27), recorded here because it is the judgement the format cannot
|
||||
* capture: cluster by GEOGRAPHY FIRST so the poster maps make sense, balanced with LANGUAGE — languages
|
||||
* usually cluster geographically, so the two mostly agree, and where they disagree geography wins for a
|
||||
* map's sake. UPG-100's own 28 already mix the two: the country_codes zones are geographic, and the 12
|
||||
* India/China pg_slugs zones are hand groupings where language and geography were traded off.
|
||||
*
|
||||
* ⚠ THE DEAF ZONE IS THE KNOWN EXCEPTION AND MUST NOT BE FORCED. `deaf` is affinity bloc A017: 123 groups
|
||||
* spanning every region, global by construction, and the one poster whose zone cannot be a map extent. It
|
||||
* breaks the geography-first rule the rest of the pattern rests on, and it is recorded as not yet
|
||||
* solved. A list whose clusters include an affinity or language bloc inherits the same open question —
|
||||
* declare it in bloc_zones so the gate treats it as a second partition, and do NOT invent a geography for
|
||||
* it to make a validator happy.
|
||||
*
|
||||
* ⚠ THIS MODULE IS BROWSER/VITE-ONLY, BY CONSTRUCTION. The static JSON import below is what keeps the
|
||||
* tree-shaking (see above), and bare `import … from './x.json'` is a bundler feature: plain Node throws
|
||||
* ERR_IMPORT_ASSERTION_TYPE_MISSING without an `with { type: 'json' }` attribute. That is fine and is the
|
||||
* existing convention here — NO Node-side file imports clusters.json as a module; packet.mjs and every
|
||||
* gate read it with readFileSync + JSON.parse (packet.mjs:125). So a Node consumer that wants the same
|
||||
* resolution should read the JSON itself and pass the object on, rather than importing this file.
|
||||
*/
|
||||
import { lists, deaf_lists, asia_lists, uncovered_lists } from './clusters.json'
|
||||
|
||||
/** UPG-100's zones — the default for every list until one registers its own. */
|
||||
const UPG_100_ZONES = { lists, deaf_lists, asia_lists, uncovered_lists }
|
||||
|
||||
/**
|
||||
* list id → its zone set.
|
||||
* ★ A REGISTRY, NOT A DIRECTORY SCAN: each entry is a static import somewhere, so the bundler can see
|
||||
* which zone files a bundle actually reaches and drop the rest. A scan would defeat that and would also
|
||||
* make a missing file a runtime error instead of a build one.
|
||||
*/
|
||||
const ZONES_BY_LIST = {
|
||||
// 'project-3000': PROJECT_3000_ZONES, ← the shape a second list adds
|
||||
}
|
||||
|
||||
/**
|
||||
* The zones for a list id.
|
||||
*
|
||||
* @param {string} [listId] as returned by resolvePeopleGroupList(profileConfig)
|
||||
* @returns {{lists:Array, deaf_lists?:Array, asia_lists:Array, uncovered_lists:Array}}
|
||||
*
|
||||
* ⚠ FALLS BACK RATHER THAN THROWING, and says so once. A list without registered zones is the NORMAL
|
||||
* state right now (five lists, one zone set), so throwing would break every caller for a condition that
|
||||
* is expected. But it is silent-wrong-data territory — a reader would see UPG-100's zones under another
|
||||
* list's name and have no way to tell — so it warns exactly once per id, naming what it did.
|
||||
*/
|
||||
const warned = new Set()
|
||||
export function zonesForList(listId) {
|
||||
const id = String(listId || '')
|
||||
const own = ZONES_BY_LIST[id]
|
||||
if (own) return own
|
||||
if (id && !warned.has(id) && typeof console !== 'undefined') {
|
||||
warned.add(id)
|
||||
console.warn(`[zones] list "${id}" has no zones of its own; showing UPG-100's ${countZones(UPG_100_ZONES)} `
|
||||
+ 'zones. Register a set in data/zones-for-list.js to give it its own poster clusters.')
|
||||
}
|
||||
return UPG_100_ZONES
|
||||
}
|
||||
|
||||
/** Zone count across the zone-bearing keys. NOT a leaf count — buildLegendNodes collapses deaf_lists. */
|
||||
export function countZones(zones) {
|
||||
let n = 0
|
||||
for (const k of ['lists', 'asia_lists', 'uncovered_lists']) n += (zones?.[k] || []).length
|
||||
// ★ deaf_lists is counted as ONE, not as its member count: legend-tree collapses its two entries into a
|
||||
// single unified leaf (legend-tree.js:106, "13 + 12 + 2 + 1 deaf = 28"). Counting its members would
|
||||
// report 29 for a 28-leaf tree — a number that disagrees with every other count in the app.
|
||||
if ((zones?.deaf_lists || []).length) n += 1
|
||||
return n
|
||||
}
|
||||
|
||||
/** Every list id that has its own zones — for a gate or a diagnostic to enumerate. */
|
||||
export function listsWithOwnZones() { return Object.keys(ZONES_BY_LIST) }
|
||||
|
|
@ -262,6 +262,8 @@
|
|||
"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-design-catalogue:self-test": "node plugin-designs/poster-design/scripts/check-catalogue.mjs --self-test",
|
||||
"check:upg100-zone-copy": "node plugin-designs/poster-design/scripts/check-upg100-zone-copy.mjs",
|
||||
"check:upg100-zone-copy:self-test": "node plugin-designs/poster-design/scripts/check-upg100-zone-copy.mjs --self-test",
|
||||
"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",
|
||||
|
|
@ -357,7 +359,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:script-flags 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: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 check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test check:phone-print-keep:self-test check:pinned-fetch-fresh:self-test",
|
||||
"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:pins-in-state check:fork-leaders check:circular-layout 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-internal-role-nouns 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:side-lanes check:side-lanes:self-test check:poster-section check:poster-section: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: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-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep check:pinned-fetch-fresh check:view-address check:view-address:self-test",
|
||||
"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:pins-in-state check:fork-leaders check:circular-layout 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-internal-role-nouns 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:side-lanes check:side-lanes:self-test check:poster-section check:poster-section: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: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-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:upg100-zone-copy check:upg100-zone-copy:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep check:pinned-fetch-fresh check:view-address check:view-address:self-test",
|
||||
"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",
|
||||
|
|
@ -622,6 +624,8 @@
|
|||
"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/<plugin>/<profile>.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: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/<id>.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/<id>.html instead of opening on \"Poster not available yet\". Needs a served tree built with posterSources:true; NOT MEASURED otherwise.",
|
||||
|
|
|
|||
|
|
@ -3,5 +3,12 @@
|
|||
"type": "single",
|
||||
"label": "Bible Posters",
|
||||
"targets": ["web"],
|
||||
"note": "A generator that shows an ENTIRE BOOK of the Bible on one poster, in the languages of the top unreached people groups. Text is fetched at render time from the Free Use Bible API (public-domain / openly-licensed only); no Bibles are stored in the repo. Lives in the Posterverse Experimental shelf."
|
||||
"posterDesign": {
|
||||
"family": "biblePoster",
|
||||
"name": "bible-poster.36x60",
|
||||
"size": "36x60",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "biblePosters.posterDesign.label"
|
||||
},
|
||||
"note": "A generator that shows an ENTIRE BOOK of the Bible on one poster, in the languages of the top unreached people groups. Text is fetched at render time from the Free Use Bible API (public-domain / openly-licensed only); no Bibles are stored in the repo. Lives in the Posterverse Experimental shelf. posterDesign: the whole-book poster is a large-format print (even standard paper is too small for a whole book), so mobileFriendly is false; the card/preview still renders on a phone."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
{
|
||||
"type": "single",
|
||||
"label": "Country Posters — poster wall",
|
||||
"note": "The printable poster wall pane of the Country Posters element (country-posters-mfe composes it); also embeddable alone. profile-config `countries` (ISO-3 list or {code,name,count} records) or `region` (a WAGF region label) shows only those, flat, under a host's own region heading (DX Life's /regions, 2026-09-30); without them the wall is unchanged."
|
||||
"note": "The printable poster wall pane of the Country Posters element (country-posters-mfe composes it); also embeddable alone. profile-config `countries` (ISO-3 list or {code,name,count} records) or `region` (a WAGF region label) shows only those, flat, under a host's own region heading (DX Life's /regions, 2026-09-30); without them the wall is unchanged.",
|
||||
"posterDesign": {
|
||||
"family": "countryPoster",
|
||||
"name": "country-poster.letter",
|
||||
"size": "letter",
|
||||
"mobileFriendly": true,
|
||||
"labelKey": "tabs.countryPosters"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,27 @@
|
|||
{
|
||||
"_comment": "The printable half of UPG-100: a wall of prayer-card posters, one per 100-list entry, with the three print paths. Declared so the Studio rail names it rather than falling back to a humanized id (check:studio-rail).",
|
||||
"label": "UPG100 Posters — Poster Wall"
|
||||
"label": "UPG100 Posters — Poster Wall",
|
||||
"posterDesign": [
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.letter",
|
||||
"size": "letter",
|
||||
"mobileFriendly": true,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
},
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.3x3",
|
||||
"size": "3x3",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
},
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.3x4",
|
||||
"size": "3x4",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,19 +20,14 @@ import { POSTER_LADDER, POSTER_LADDER_ORDER, paperFor } from '@map/constants/pos
|
|||
import { useOptionalT, useOptionalI18n } from '@map/composables/useOptionalT.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { layoutSvgUrl } from './src/layoutSvg.js'
|
||||
import { num, dimsText } from './src/sizeText.js'
|
||||
|
||||
const t = useOptionalT('')
|
||||
const { locale } = useOptionalI18n('')
|
||||
|
||||
// A number as the sizes are spoken: whole, or one decimal place (5.5).
|
||||
const n = (x) => (Number.isInteger(x) ? String(x) : String(Math.round(x * 10) / 10))
|
||||
// Feet once a side reaches a yard and both sides are whole or half feet (3 × 5.5 ft); inches below that (18 × 24 in).
|
||||
function dims(w, h) {
|
||||
const ft = (x) => x / 12
|
||||
const halfFeet = (x) => Number.isInteger(ft(x) * 2)
|
||||
if (Math.min(w, h) >= 36 && halfFeet(w) && halfFeet(h)) return t('posterDesign.sizes.dimsFt', { w: n(ft(w)), h: n(ft(h)) })
|
||||
return t('posterDesign.sizes.dimsIn', { w: n(w), h: n(h) })
|
||||
}
|
||||
// Sizes are named the one way Poster Design names them (src/sizeText.js, shared with Styles).
|
||||
const n = num
|
||||
const dims = (w, h) => dimsText(w, h, t)
|
||||
// Standard printer paper is Letter or A4 by the reader's region, as the print picker decides it.
|
||||
const paperIsA4 = computed(() => {
|
||||
const l = locale && (locale.value ?? locale)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { resolvePosterSize } from '@map/constants/posterSizes.js'
|
|||
import { listConfigurations, FAMILY_KEYS, SAMPLE_COUNTRY_ID } from './src/templates.js'
|
||||
import FullDetailsDialog from './src/FullDetailsDialog.vue'
|
||||
import { loadPluginDesigns } from './src/pluginDesigns.js'
|
||||
import { sizeText, declaredSizeText } from './src/sizeText.js'
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
const instanceId = computed(() => profileConfig.value?.instanceId || 'poster-design-styles')
|
||||
|
|
@ -49,7 +50,7 @@ function stockLabel(s) {
|
|||
const dims = `${s.w} × ${s.h} in`
|
||||
return s.side ? `${noun} ${dims} (${t('wall.stockLandscape')})` : `${noun} ${dims}`
|
||||
}
|
||||
const labelOf = (c) => (c.stock ? `${t(c.labelKey)}: ${stockLabel(c.stock)}` : `${t(c.labelKey)}: ${c.size}`)
|
||||
const labelOf = (c) => (c.stock ? `${t(c.labelKey)}: ${stockLabel(c.stock)}` : `${t(c.labelKey)}: ${sizeText(c.size, t)}`)
|
||||
|
||||
// Designs other plugins declare (their meta's posterDesign, via manifest.json): the built tree's manifest, or the dev
|
||||
// server's. A name already in templates.js stays the library's (the check refuses the clash; the page never doubles it).
|
||||
|
|
@ -60,17 +61,34 @@ onMounted(async () => {
|
|||
const { designs } = await loadPluginDesigns(urls)
|
||||
pluginDesigns.value = designs.filter((d) => !byName.has(d.name))
|
||||
})
|
||||
const pluginLabel = (d) => {
|
||||
// The design's title in the reader's language: the build's per-locale titles (posterDesignLabels; any catalogue
|
||||
// area), then this bundle's own catalogue, then English, then the profile's label.
|
||||
const pluginTitle = (d) => {
|
||||
const lang = String(getRuntimeLocale() || detectLocale() || 'en').split('-')[0]
|
||||
if (d.titles && d.titles[lang]) return d.titles[lang]
|
||||
const v = d.labelKey ? t(d.labelKey) : ''
|
||||
return `${v && v !== d.labelKey ? v : d.label}: ${d.size}`
|
||||
if (v && v !== d.labelKey) return v
|
||||
return (d.titles && d.titles.en) || d.label
|
||||
}
|
||||
const pluginLabel = (d) => {
|
||||
return `${pluginTitle(d)}: ${declaredSizeText(d.size, t)}`
|
||||
}
|
||||
|
||||
// PRODUCTION designs first, by family. EXPERIMENTAL ones (marked in templates.js: still being designed, or no
|
||||
// preview) and every other plugin's design (made by its own plugin, so drawn nowhere here) go last, in ONE
|
||||
// Experimental group that starts collapsed.
|
||||
const isExperimental = (id) => byPluginName.value.has(id) || byName.get(id)?.experimental === true
|
||||
const experimentalHeading = computed(() => t('posterDesign.family.experimental'))
|
||||
// No `count`: these are templates, not lists of people, so the sticker reads "Show full details" alone.
|
||||
const items = computed(() => [
|
||||
...configs.map((c) => ({ id: c.name, label: labelOf(c) })),
|
||||
...pluginDesigns.value.map((d) => ({ id: d.name, label: pluginLabel(d) })),
|
||||
])
|
||||
const groupBy = (item) => (byPluginName.value.has(item.id) ? t('posterDesign.family.plugins') : t(FAMILY_KEYS[byName.get(item.id)?.family] || FAMILY_KEYS.mapSheet))
|
||||
const items = computed(() => {
|
||||
const all = [
|
||||
...configs.map((c) => ({ id: c.name, label: labelOf(c) })),
|
||||
...pluginDesigns.value.map((d) => ({ id: d.name, label: pluginLabel(d) })),
|
||||
]
|
||||
return [...all.filter((x) => !isExperimental(x.id)), ...all.filter((x) => isExperimental(x.id))]
|
||||
})
|
||||
const groupBy = (item) => (isExperimental(item.id) ? experimentalHeading.value : t(FAMILY_KEYS[byName.get(item.id)?.family] || FAMILY_KEYS.mapSheet))
|
||||
const collapsedBy = (sec) => (sec && sec.name) === experimentalHeading.value // PosterWall passes the section
|
||||
|
||||
// ── drawing a configuration from the library ──────────────────────────────────────────────────
|
||||
const countryById = (id) => (countryData.countries || []).find((c) => c.id === id)
|
||||
|
|
@ -133,7 +151,17 @@ function panels(sizeKey) {
|
|||
const size = resolvePosterSize(sizeKey)
|
||||
return size ? { image: panelsSvgUrl(size.sheets) } : null
|
||||
}
|
||||
const ctx = { countrySheet, cardDeck, packetDocument, panels }
|
||||
// A UPG100 zone's sheet, from the library copy of UPG100's zone code: ALL the records (a zone's pins depend on which
|
||||
// earlier zone claimed a group first, as on UPG100's own wall), the list from this page's config. Loaded on first use.
|
||||
let zoneSheetFn = null
|
||||
async function zoneSheet(format, zoneId) {
|
||||
if (!zoneSheetFn) {
|
||||
const { makeZoneSheet } = await import('@map/packet/upg100Zones/zone-sheet-print.js')
|
||||
zoneSheetFn = makeZoneSheet({ allRecords: () => recordsFor.all(), assetsBase: () => assetsBase.value, profileConfig })
|
||||
}
|
||||
return zoneSheetFn({ id: zoneId }, format)
|
||||
}
|
||||
const ctx = { countrySheet, cardDeck, packetDocument, panels, zoneSheet }
|
||||
|
||||
// The printed document's size, measured when it is drawn: what "file size" means in the details.
|
||||
// render() answers a poster's HTML (a map sheet) or a document { html, page, width, height } (a deck, a packet).
|
||||
|
|
@ -175,15 +203,15 @@ async function sheet(item) {
|
|||
|
||||
// ── "Show full details" ───────────────────────────────────────────────────────────────────────
|
||||
const open = ref(null)
|
||||
const openLabel = computed(() => (!open.value ? '' : open.value.design ? pluginLabel(open.value.design) : labelOf(open.value)))
|
||||
const openLabel = computed(() => (!open.value ? '' : open.value.design ? pluginLabel(open.value.design) : labelOf(byName.get(open.value.name))))
|
||||
// A plugin's design opens in the same dialog, as a configuration: its renderer is the plugin's page, and the dialog
|
||||
// shows that page live.
|
||||
function showDetails(item) {
|
||||
const d = byPluginName.value.get(item.id)
|
||||
open.value = d
|
||||
? { name: d.name, size: d.size, settings: { family: d.family, mobileFriendly: d.mobileFriendly, ...(d.instance ? { preset: d.instance } : {}) },
|
||||
? { name: d.name, size: declaredSizeText(d.size, t), settings: { family: d.family, mobileFriendly: d.mobileFriendly, ...(d.instance ? { preset: d.instance } : {}) },
|
||||
renderer: `plugin-designs/${d.bundle}/${d.profile}.vue`, pageUrl: d.pageUrl, design: d }
|
||||
: byName.get(item.id) || null
|
||||
: byName.has(item.id) ? { ...byName.get(item.id), size: sizeText(byName.get(item.id).size, t) } : null
|
||||
}
|
||||
|
||||
const heading = computed(() => t('posterDesign.styles.heading'))
|
||||
|
|
@ -231,6 +259,7 @@ useShadowStyles(`
|
|||
unit=""
|
||||
item-count-key="posterDesign.templateCount"
|
||||
:collapsible="true"
|
||||
:collapsed-by="collapsedBy"
|
||||
:header-action="{ labelKey: 'posterDesign.showFullDetails', ariaKey: 'posterDesign.showFullDetailsAria' }"
|
||||
:preview-src="previewSrc"
|
||||
:unsupported="withheld"
|
||||
|
|
|
|||
|
|
@ -102,9 +102,12 @@ for (const c of configs) if (!cat.FAMILY_KEYS[c.family]) fail(`'${c.name}' has f
|
|||
// 5. the samples
|
||||
const data = JSON.parse(readFileSync(join(ROOT, 'library/data/country-posters.json'), 'utf8'))
|
||||
const samples = [...new Set([cat.SAMPLE_COUNTRY_ID, ...configs.map((c) => c.sample).filter(Boolean)])]
|
||||
// A UPG100 zone sample (a template with `plugin: 'my-upg-100-list'`) is one of the zones in the library copy's catalogue.
|
||||
const ZONE_IDS = (() => { try { const z = JSON.parse(readFileSync(join(ROOT, 'library/packet/upg100Zones/clusters.json'), 'utf8')); return new Set(['lists', 'asia_lists', 'uncovered_lists'].flatMap((k) => (z[k] || []).map((x) => x.id))) } catch { return new Set() } })()
|
||||
function sampleArm(ids, countries, withheld) {
|
||||
const fs = []
|
||||
for (const id of ids) {
|
||||
if (ZONE_IDS.has(id)) continue // a real UPG100 zone (drawn from the library copy)
|
||||
const s = countries.find((c) => c.id === id)
|
||||
if (!s) fs.push(`5: sample '${id}' is not in library/data/country-posters.json`)
|
||||
else if (withheld(s)) continue // its card shows the withheld sentence; it is never drawn here
|
||||
|
|
@ -134,7 +137,7 @@ function gatherBlocks() {
|
|||
}
|
||||
return out
|
||||
}
|
||||
function designArms({ names, blocks, ledger }) {
|
||||
function designArms({ names, blocks, ledger, drawnFor = new Map() }) {
|
||||
const fs = []
|
||||
for (const { where, block, presets } of blocks) {
|
||||
const probs = PD.blockProblems(block)
|
||||
|
|
@ -143,9 +146,15 @@ function designArms({ names, blocks, ledger }) {
|
|||
if (block.labelKey && !enKey(block.labelKey)) fs.push(`6: ${where} labelKey '${block.labelKey}' is not in the English catalogue`)
|
||||
if (block.instance && !presets.includes(block.instance)) fs.push(`6: ${where} instance '${block.instance}' is not one of its presets`)
|
||||
}
|
||||
const all = [...names, ...blocks.filter((b) => !PD.blockProblems(b.block).length).map((b) => b.block.name)]
|
||||
const seen = new Set()
|
||||
for (const n of all) { if (seen.has(n)) fs.push(`7: two designs are named '${n}'`); seen.add(n) }
|
||||
// A template that DRAWS a plugin's design (`plugin: '<dir>'`) carries that plugin's own name, so the same name in that
|
||||
// plugin's block is the same design, counted once. Anywhere else a repeated name is a clash.
|
||||
const seen = new Set(names)
|
||||
for (const n of names.filter((x, i) => names.indexOf(x) !== i)) fs.push(`7: two templates are named '${n}'`)
|
||||
for (const b of blocks.filter((x) => !PD.blockProblems(x.block).length)) {
|
||||
const n = b.block.name, plugin = String(b.where).split('/')[0]
|
||||
if (seen.has(n) && drawnFor.get(n) !== plugin) fs.push(`7: two designs are named '${n}'`)
|
||||
seen.add(n)
|
||||
}
|
||||
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)) }
|
||||
}
|
||||
|
|
@ -153,7 +162,7 @@ const LEDGER_FILE = join(ROOT, 'plugin-designs/poster-design/src/catalogue-names
|
|||
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')
|
||||
const blocks = gatherBlocks()
|
||||
const real = { names: configs.map((c) => c.name), blocks, ledger }
|
||||
const real = { names: configs.map((c) => c.name), blocks, ledger, drawnFor: new Map(configs.filter((c) => c.plugin).map((c) => [c.name, c.plugin])) }
|
||||
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const example = { family: 'regionPoster', name: 'region-poster.66x66', size: '66x66', mobileFriendly: true }
|
||||
|
|
@ -162,6 +171,7 @@ if (process.argv.includes('--self-test')) {
|
|||
['an invalid block (mobileFriendly missing)', { ...real, blocks: [...blocks, B({ family: 'regionPoster', name: 'region-poster.x', size: '66x66' })] }, /^6: .*mobileFriendly/],
|
||||
['a name clash with templates.js', { ...real, blocks: [...blocks, B({ ...example, name: real.names[0] })] }, /^7: /],
|
||||
['a renamed ledger name', { ...real, names: real.names.map((n, i) => (i === 0 ? `${n}-renamed` : n)) }, /^8: /],
|
||||
['another plugin reusing a name a template draws for UPG100', { ...real, blocks: [...blocks, B({ ...example, name: 'upg100-territory-poster.3x3' })] }, /^7: /],
|
||||
['a block naming a preset that does not exist', { ...real, blocks: [...blocks, B({ ...example, instance: 'nowhere' }, ['africa'])] }, /^6: .*instance/],
|
||||
]
|
||||
let ok = true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check:upg100-zone-copy — library/packet/upg100Zones/ is UPG100's zone-poster code, copied so Poster Design (and any
|
||||
* page) can draw a UPG100 zone poster from the library. Two modes, chosen by what is on the tree:
|
||||
*
|
||||
* COPY while UPG100's originals (plugin-designs/my-upg-100-list/data/<file>) exist: every library file equals
|
||||
* its original with ONLY the import specifiers normalised (the copy's library paths are shorter), and
|
||||
* clusters.json byte for byte. A changed literal, a renamed variable, any edit in a function body FAILS:
|
||||
* change both, or change the plugin and copy it again.
|
||||
* ONE-PATH once UPG100 has switched to the library copy and deleted its originals: no plugin file may define the
|
||||
* zone functions again (one definition, one owner), as check:country-sheet-one-path does for countries.
|
||||
* Half the originals present = FAIL (a switch left halfway).
|
||||
*
|
||||
* --self-test: a one-character edit inside a function body, a changed clusters.json and a missing copy each FAIL; a
|
||||
* change to an import specifier alone PASSES (the normalisation is no wider than the specifiers).
|
||||
*/
|
||||
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
||||
const LIB = join(ROOT, 'library/packet/upg100Zones')
|
||||
const PLUGIN = join(ROOT, 'plugin-designs/my-upg-100-list')
|
||||
const FILES = ['zone-sheet-print.js', 'zone-sheet.js', 'legend-tree.js', 'selector-evaluator.js', 'zone-geometry.js', 'zones-for-list.js', 'clusters.json']
|
||||
const DEFINES = /export\s+(?:async\s+)?function\s+(makeZoneSheet|upg100Zones|assignPins|seriesFacts|resolveZoneGeometry|zonesForList|buildLegendNodes)\b/
|
||||
|
||||
// Only the quoted specifier of a static import/export-from, a dynamic import() or a bare import is replaced.
|
||||
export const normalise = (s) => s
|
||||
.replace(/(\bfrom\s*)(['"])[^'"\n]+\2/g, '$1$2§$2')
|
||||
.replace(/(\bimport\(\s*)(['"])[^'"\n]+\2/g, '$1$2§$2')
|
||||
.replace(/(^\s*import\s*)(['"])[^'"\n]+\2/gm, '$1$2§$2')
|
||||
|
||||
export function arms(read) {
|
||||
const fails = []
|
||||
const originals = FILES.filter((f) => read('plugin', f) != null)
|
||||
if (originals.length && originals.length < FILES.length) fails.push(`half switched: UPG100 still has ${originals.join(', ')} but not ${FILES.filter((f) => !originals.includes(f)).join(', ')}`)
|
||||
for (const f of FILES) if (read('lib', f) == null) fails.push(`the library copy has no ${f}`)
|
||||
if (originals.length === FILES.length) {
|
||||
for (const f of FILES) {
|
||||
const a = read('plugin', f), b = read('lib', f)
|
||||
if (b == null) continue
|
||||
const same = f.endsWith('.json') ? a === b : normalise(a) === normalise(b)
|
||||
if (!same) fails.push(`${f}: the library copy differs from UPG100's original beyond its import paths (change both, or copy again)`)
|
||||
}
|
||||
return { mode: 'COPY', fails }
|
||||
}
|
||||
if (!originals.length) {
|
||||
for (const f of read('pluginFiles')) { const m = (read('pluginAbs', f) || '').match(DEFINES); if (m) fails.push(`one path: ${f} defines ${m[1]} again; the library copy is the only one`) }
|
||||
return { mode: 'ONE-PATH', fails }
|
||||
}
|
||||
return { mode: 'HALF', fails }
|
||||
}
|
||||
|
||||
const disk = (overrides = {}) => (where, f) => {
|
||||
const key = `${where}:${f}`
|
||||
if (key in overrides) return overrides[key]
|
||||
if (where === 'pluginFiles') {
|
||||
const out = []; const walk = (d) => { for (const e of readdirSync(d, { withFileTypes: true })) { const p = join(d, e.name); if (e.isDirectory()) { if (!/node_modules|assets/.test(e.name)) walk(p) } else if (/\.(m?js|vue)$/.test(e.name)) out.push(p) } }
|
||||
walk(PLUGIN); return out
|
||||
}
|
||||
const p = where === 'pluginAbs' ? f : join(where === 'lib' ? LIB : join(PLUGIN, 'data'), f)
|
||||
return existsSync(p) ? readFileSync(p, 'utf8') : null
|
||||
}
|
||||
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const real = disk()
|
||||
const body = real('lib', 'zone-sheet.js')
|
||||
const at = body.indexOf('return ', body.indexOf('export function upg100Zones'))
|
||||
const spec = real('lib', 'zone-sheet-print.js')
|
||||
const cases = [
|
||||
['a one-character edit inside a function body', { 'lib:zone-sheet.js': body.slice(0, at) + 'return ' + body.slice(at + 7) }, true],
|
||||
['a changed clusters.json', { 'lib:clusters.json': real('lib', 'clusters.json').replace('"id"', '"id "') }, true],
|
||||
['a missing copy', { 'lib:selector-evaluator.js': null }, true],
|
||||
// ONE-PATH (after UPG100 switches): no originals, and a plugin file that defines the zone code again must fail
|
||||
['one path: a plugin file defining makeZoneSheet again', { ...Object.fromEntries(FILES.map((f) => [`plugin:${f}`, null])), 'pluginFiles:undefined': ['/fake/again.js'], 'pluginAbs:/fake/again.js': 'export function makeZoneSheet() {}' }, true],
|
||||
['one path: no originals and no second definition', { ...Object.fromEntries(FILES.map((f) => [`plugin:${f}`, null])), 'pluginFiles:undefined': [] }, false],
|
||||
['half switched: some originals deleted', { 'plugin:zone-geometry.js': null }, true],
|
||||
['an import specifier changed alone', { 'lib:zone-sheet-print.js': spec.replace("from './zone-sheet.js'", "from './zone-sheet.js?v=2'") }, false],
|
||||
]
|
||||
let ok = true
|
||||
for (const [name, ov, shouldFail] of cases) {
|
||||
const { fails } = arms(disk(ov))
|
||||
const good = shouldFail ? fails.length > 0 : fails.length === 0
|
||||
console.log(` ${good ? '✓' : '✗'} ${name}: ${fails.length ? 'FAILS' : 'passes'} (${shouldFail ? 'must fail' : 'must pass'})${fails[0] ? ' -> ' + fails[0] : ''}`)
|
||||
if (!good) ok = false
|
||||
}
|
||||
console.log(ok ? 'UPG100-ZONE-COPY SELF-TEST: PASS' : 'UPG100-ZONE-COPY SELF-TEST: FAIL'); process.exit(ok ? 0 : 1)
|
||||
}
|
||||
const { mode, fails } = arms(disk())
|
||||
if (fails.length) { console.log(`UPG100-ZONE-COPY: FAIL (${mode}, ${fails.length})`); fails.forEach((f) => console.log(` - ${f}`)); process.exit(1) }
|
||||
console.log(`UPG100-ZONE-COPY: PASS (${mode}) — the ${FILES.length} files of library/packet/upg100Zones/ ${mode === 'COPY' ? "equal UPG100's originals beyond their import paths" : 'are the only definition of the zone code'}.`)
|
||||
|
|
@ -63,7 +63,7 @@ async function copyName() {
|
|||
<dd><code dir="ltr" class="pd-dialog__name">{{ config.name }}</code>
|
||||
<button type="button" class="pd-dialog__copy" @click="copyName">{{ t('posterDesign.details.copyName') }}</button></dd>
|
||||
<dt>{{ t('posterDesign.details.size') }}</dt>
|
||||
<dd dir="ltr">{{ config.size }}<span v-if="config.px"> · {{ config.px[0] }} × {{ config.px[1] }} px</span></dd>
|
||||
<dd>{{ config.size }}<span v-if="config.px"> · {{ ltr(`${config.px[0]} × ${config.px[1]} px`) }}</span></dd>
|
||||
<template v-if="!config.pageUrl">
|
||||
<dt>{{ t('posterDesign.details.fileSize') }}</dt>
|
||||
<dd>{{ fileSize }}</dd>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,16 @@
|
|||
"cards.postcardLandscape",
|
||||
"cards.a6",
|
||||
"wall.bless-panels",
|
||||
"map-print.dialog"
|
||||
"map-print.dialog",
|
||||
"country-poster.letter",
|
||||
"upg100-territory-poster.letter",
|
||||
"upg100-territory-poster.3x3",
|
||||
"upg100-territory-poster.3x4",
|
||||
"wall.bless-36x48",
|
||||
"wall.bless-24x36",
|
||||
"wall.bless-60x96",
|
||||
"bible-poster.36x60",
|
||||
"upg100-territory-packet.page-1",
|
||||
"bless-india.range-badge-60x96"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ export function designsFromManifest(manifest, manifestUrl) {
|
|||
const rel = inst ? inst.iframeUrl : p.iframeUrl
|
||||
let pageUrl = rel
|
||||
try { pageUrl = new URL(rel, manifestUrl).href } catch { /* keep it as written */ }
|
||||
// titles: the labelKey resolved per locale by the build (posterDesignLabels), so any area reads translated
|
||||
out.push({ name: d.name, family: d.family, size: d.size, mobileFriendly: d.mobileFriendly, labelKey: d.labelKey || '',
|
||||
titles: (p.posterDesignLabels && p.posterDesignLabels[d.name]) || {},
|
||||
label: (inst && inst.title) || p.label || p.profile, bundle: b.bundle, profile: p.profile, instance: d.instance || '', pageUrl })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
plugin-designs/poster-design/src/sizeText.js
Normal file
39
plugin-designs/poster-design/src/sizeText.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* sizeText.js — a poster's size in the reader's language, the ONE way Poster Design names sizes (Styles and Sizes).
|
||||
* Numbers are spoken whole or to one decimal (5.5); a side of a yard or more in whole or half feet reads in feet
|
||||
* (3 × 5.5 ft), anything smaller in inches (18 × 24 in). Every word comes from the posterDesign.sizes catalogue.
|
||||
*
|
||||
* spec = { dims: [w, h] } inches · { dims, lanes: true } · { dims, panels: n } · { paper: 'letter', packetPage? } ·
|
||||
* { any: true } (a template that prints at whatever size its reader picks)
|
||||
*/
|
||||
import { resolvePosterSize } from '@map/constants/posterSizes.js'
|
||||
|
||||
export const num = (x) => (Number.isInteger(x) ? String(x) : String(Math.round(x * 10) / 10))
|
||||
|
||||
export function dimsText(w, h, t) {
|
||||
const ft = (x) => x / 12
|
||||
const halfFeet = (x) => Number.isInteger(ft(x) * 2)
|
||||
if (Math.min(w, h) >= 36 && halfFeet(w) && halfFeet(h)) return t('posterDesign.sizes.dimsFt', { w: num(ft(w)), h: num(ft(h)) })
|
||||
return t('posterDesign.sizes.dimsIn', { w: num(w), h: num(h) })
|
||||
}
|
||||
|
||||
export function sizeText(spec, t) {
|
||||
if (!spec) return ''
|
||||
if (spec.any) return t('posterDesign.sizes.anySize')
|
||||
if (spec.paper) {
|
||||
const paper = t('posterDesign.sizes.paperLetter')
|
||||
return spec.packetPage ? t('posterDesign.sizes.packetPage1', { paper }) : paper
|
||||
}
|
||||
const dims = dimsText(spec.dims[0], spec.dims[1], t)
|
||||
if (spec.lanes) return t('posterDesign.sizes.sideLanes', { dims })
|
||||
if (spec.panels) return t('posterDesign.sizes.panels', { dims, n: num(spec.panels) })
|
||||
return dims
|
||||
}
|
||||
|
||||
/** A plugin's declared size: a ladder key ("66x66") through the shared ladder, letter / a4 by name; anything else as written. */
|
||||
export function declaredSizeText(size, t) {
|
||||
if (size === 'letter') return t('posterDesign.sizes.paperLetter')
|
||||
if (size === 'a4') return t('posterDesign.sizes.paperA4')
|
||||
const s = resolvePosterSize(size)
|
||||
return s && s.inches && !s.paper && s.id !== 'paper' ? dimsText(s.inches.w, s.inches.h, t) : String(size || '')
|
||||
}
|
||||
|
|
@ -32,19 +32,22 @@ export const SAMPLE_COUNTRY_ID = 'country-npl'
|
|||
// country prints at 66x66 or 36x60 today: those two are drawn with the largest drawable countries (China, Sudan) to
|
||||
// show the template, and their details say so (missing: noCountryAtSize). Names never change (src/catalogue-names.json).
|
||||
export const SHEET_FORMATS = {
|
||||
'3x3': { page: '3 × 3', px: [1500, 1500] },
|
||||
'60x96': { page: '5 × 8 ft, side lanes', px: [2500, 4000], sample: 'country-ind' },
|
||||
'66x66': { page: '5.5 × 5.5 ft, side lanes', px: [2750, 2750], sample: 'country-chn', missing: 'posterDesign.missing.noCountryAtSize' },
|
||||
'36x60': { page: '3 × 5 ft, side lanes', px: [1500, 2500], sample: 'country-sdn', missing: 'posterDesign.missing.noCountryAtSize' },
|
||||
'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] },
|
||||
// `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] },
|
||||
// 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 },
|
||||
'36x60': { size: { dims: [36, 60], lanes: true }, px: [1500, 2500], sample: 'country-sdn', missing: 'posterDesign.missing.noCountryAtSize', experimental: true },
|
||||
'3x4': { size: { dims: [36, 48] }, px: [1500, 2000], legacy: true },
|
||||
letter: { size: { paper: 'letter' }, px: [1500, 1941] },
|
||||
packet: { size: { paper: 'letter', packetPage: true }, 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',
|
||||
upgZone: 'posterDesign.family.upgZone',
|
||||
packet: 'posterDesign.family.packet',
|
||||
cards: 'posterDesign.family.cards',
|
||||
wall: 'posterDesign.family.wall',
|
||||
|
|
@ -57,9 +60,10 @@ function sheetEntry(format) {
|
|||
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,
|
||||
size: f.size,
|
||||
px: f.px,
|
||||
legacy: !!f.legacy,
|
||||
...(f.experimental ? { experimental: true } : {}),
|
||||
...(f.missing ? { missing: f.missing } : {}),
|
||||
renderer: 'library/packet/posterSheets.js generateCountrySheet → library/packet/poster-render.js',
|
||||
settings: { format },
|
||||
|
|
@ -68,15 +72,38 @@ function sheetEntry(format) {
|
|||
}
|
||||
}
|
||||
|
||||
// UPG100's zone posters, drawn from the LIBRARY COPY of UPG100's zone code (library/packet/upg100Zones/, kept equal to
|
||||
// the plugin's own by check:upg100-zone-copy). They carry UPG100's own design names (`plugin`), so a drawn card here
|
||||
// replaces that plugin's no-preview card, and the names never change. The sample zone is one of UPG100's real zones.
|
||||
export const SAMPLE_ZONE_ID = 'middle-east-core'
|
||||
const ZONE_RENDERER = 'library/packet/upg100Zones/zone-sheet-print.js makeZoneSheet → library/packet/posterSheets.js generateSheet'
|
||||
function zoneEntry(format) {
|
||||
const f = SHEET_FORMATS[format]
|
||||
const page1 = format === 'packet'
|
||||
return {
|
||||
name: page1 ? 'upg100-territory-packet.page-1' : `upg100-territory-poster.${format}`,
|
||||
family: 'upgZone',
|
||||
labelKey: page1 ? 'posterDesign.tpl.upgZonePacket' : 'posterDesign.tpl.upgZonePoster',
|
||||
plugin: 'my-upg-100-list',
|
||||
size: f.size,
|
||||
px: f.px,
|
||||
renderer: ZONE_RENDERER,
|
||||
settings: { format },
|
||||
sample: SAMPLE_ZONE_ID,
|
||||
render: (ctx) => ctx.zoneSheet(format, SAMPLE_ZONE_ID),
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {Array<object>} every configuration, in the gallery's order. */
|
||||
export function listConfigurations() {
|
||||
return [
|
||||
...Object.keys(SHEET_FORMATS).map(sheetEntry),
|
||||
...['letter', '3x3', '3x4', 'packet'].map(zoneEntry),
|
||||
...['full', 'lightweight'].map((variant) => ({
|
||||
name: `packet.${variant}`,
|
||||
family: 'packet',
|
||||
labelKey: variant === 'full' ? 'posterDesign.tpl.packetFull' : 'posterDesign.tpl.packetLightweight',
|
||||
size: 'Letter, portrait',
|
||||
size: { paper: 'letter' },
|
||||
renderer: 'library/packet/packet-render.js buildPacket (VARIANTS)',
|
||||
settings: { variant },
|
||||
sample: SAMPLE_COUNTRY_ID,
|
||||
|
|
@ -88,7 +115,7 @@ export function listConfigurations() {
|
|||
family: 'cards',
|
||||
labelKey: 'posterDesign.tpl.cards',
|
||||
stock: s,
|
||||
size: `${s.w} × ${s.h} in`,
|
||||
size: { dims: [s.w, s.h] },
|
||||
renderer: 'library/packet/card-render.js createCardRenderer (STOCK)',
|
||||
settings: { stock: id },
|
||||
sample: SAMPLE_COUNTRY_ID,
|
||||
|
|
@ -99,7 +126,7 @@ export function listConfigurations() {
|
|||
name: 'wall.bless-panels',
|
||||
family: 'wall',
|
||||
labelKey: 'posterDesign.tpl.blessPanels',
|
||||
size: '5 × 8 ft, 3 panels',
|
||||
size: { dims: [60, 96], panels: 3 },
|
||||
renderer: 'library/components/poster/panelSheets.js planPanels (the sheets, joins and content boxes); the cards are drawn by plugin-designs/wall-of-people-groups/src/blessPoster.js',
|
||||
settings: { size: '60x96', joinFill: 'glue' },
|
||||
sample: null,
|
||||
|
|
@ -111,11 +138,12 @@ export function listConfigurations() {
|
|||
name: 'map-print.dialog',
|
||||
family: 'mapPrint',
|
||||
labelKey: 'posterDesign.tpl.mapPrint',
|
||||
size: 'library/constants/posterSizes.js',
|
||||
size: { any: true }, // the reader picks one from the shared ladder (library/constants/posterSizes.js)
|
||||
renderer: 'library/components/poster/PosterDialog.vue + library/composables/useMapPoster.js',
|
||||
settings: {},
|
||||
sample: null,
|
||||
render: null,
|
||||
experimental: true, // no preview: it prints the live map the reader is looking at
|
||||
missing: 'posterDesign.missing.mapPrint',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,5 +5,10 @@
|
|||
"note": "Monolithic composite (capability 3): ONE root .vue nests the zoomable d3 wall, its cards, and the prayer and comments dialogs. index.js registers exactly ONE tag (wall-of-people-groups); none of the inner components is independently embeddable.",
|
||||
"config": {
|
||||
"pinHint": false
|
||||
}
|
||||
},
|
||||
"posterDesign": [
|
||||
{"family": "wall", "name": "wall.bless-36x48", "size": "36x48", "mobileFriendly": false, "labelKey": "wall.blessBanner"},
|
||||
{"family": "wall", "name": "wall.bless-24x36", "size": "24x36", "mobileFriendly": false, "labelKey": "wall.blessBanner"},
|
||||
{"family": "wall", "name": "wall.bless-60x96", "size": "60x96", "mobileFriendly": false, "labelKey": "wall.blessBanner"}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <bible-posters> custom element. -->
|
||||
<script src="../bible-posters.js" integrity="sha384-ynxYcvYpparOlz9opwuHgBDP0XFUVXdThQWubDo1majENhbrlnf+AaGacWKIwIWx" crossorigin="anonymous"></script>
|
||||
<script src="../bible-posters.js" integrity="sha384-twS8rEG4vtm8MPjq+ytjSf6g4stmnzr/6KYbVRDhQ3TH7MiTgsNG7X19Y3Ncb0JJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"bundle": "bible-posters",
|
||||
"scriptUrl": "./bible-posters/bible-posters.js",
|
||||
"output": "dx-maps/bible-posters/bible-posters.js",
|
||||
"integrity": "sha384-ynxYcvYpparOlz9opwuHgBDP0XFUVXdThQWubDo1majENhbrlnf+AaGacWKIwIWx",
|
||||
"integrity": "sha384-twS8rEG4vtm8MPjq+ytjSf6g4stmnzr/6KYbVRDhQ3TH7MiTgsNG7X19Y3Ncb0JJ",
|
||||
"elements": [
|
||||
"bible-posters"
|
||||
],
|
||||
|
|
@ -54,6 +54,28 @@
|
|||
"targets": [
|
||||
"web"
|
||||
],
|
||||
"posterDesign": {
|
||||
"family": "biblePoster",
|
||||
"name": "bible-poster.36x60",
|
||||
"size": "36x60",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "biblePosters.posterDesign.label"
|
||||
},
|
||||
"posterDesignLabels": {
|
||||
"bible-poster.36x60": {
|
||||
"ar": "ملصق سفر من الكتاب المقدس",
|
||||
"de": "Bibelbuch-Poster",
|
||||
"en": "Bible book poster",
|
||||
"es": "Cartel de un libro de la Biblia",
|
||||
"fr": "Affiche d'un livre de la Bible",
|
||||
"hi": "बाइबल पुस्तक पोस्टर",
|
||||
"it": "Poster di un libro della Bibbia",
|
||||
"pt": "Cartaz de um livro da Bíblia",
|
||||
"ro": "Poster cu o carte a Bibliei",
|
||||
"ru": "Плакат книги Библии",
|
||||
"zh": "圣经书卷海报"
|
||||
}
|
||||
},
|
||||
"file": "bible-posters.vue",
|
||||
"description": "A whole book of the Bible on one poster, in justified columns under a bold title, from a public-domain text.",
|
||||
"element": "bible-posters",
|
||||
|
|
@ -63,7 +85,7 @@
|
|||
"scriptUrl": "./bible-posters/bible-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./bible-posters/bible-posters/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./bible-posters/bible-posters.js\" integrity=\"sha384-ynxYcvYpparOlz9opwuHgBDP0XFUVXdThQWubDo1majENhbrlnf+AaGacWKIwIWx\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<bible-posters profile-config='{\"profile\":\"bible-posters\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bible-posters>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./bible-posters/bible-posters.js\" integrity=\"sha384-twS8rEG4vtm8MPjq+ytjSf6g4stmnzr/6KYbVRDhQ3TH7MiTgsNG7X19Y3Ncb0JJ\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<bible-posters profile-config='{\"profile\":\"bible-posters\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bible-posters>"
|
||||
},
|
||||
"bundle": "bible-posters"
|
||||
}
|
||||
|
|
@ -161,6 +183,21 @@
|
|||
"labelKey": "blessIndiaPosters.rangeBadge60x96"
|
||||
}
|
||||
],
|
||||
"posterDesignLabels": {
|
||||
"bless-india.range-badge-60x96": {
|
||||
"ar": "شارات النطاق · 60×96 (5×8 قدم)",
|
||||
"de": "Nummern-Marken · 60×96 (5×8 Fuß)",
|
||||
"en": "Range badges · 60×96 (5×8 ft)",
|
||||
"es": "Insignias numeradas · 60×96 (5×8 pies)",
|
||||
"fr": "Badges de numéros · 60×96 (5×8 pi)",
|
||||
"hi": "क्रमांकित बैज · 60×96 (5×8 फुट)",
|
||||
"it": "Badge numerati · 60×96 (5×8 ft)",
|
||||
"pt": "Emblemas numerados · 60×96 (5×8 pés)",
|
||||
"ro": "Insigne numerotate · 60×96 (5×8 ft)",
|
||||
"ru": "Номерные значки · 60×96 (5×8 футов)",
|
||||
"zh": "编号标记 · 60×96(5×8 英尺)"
|
||||
}
|
||||
},
|
||||
"file": "bless-india-posters.vue",
|
||||
"description": "A preview surface for the whole-India poster problem — fitting every one of India's people groups onto a single printable sheet. Each design attempt is generated live from the current data (nothing stored) and shown as its printable sheets with the glue strips, headed \"Bless India in Jesus' name\" with the Numbers 6:24-26 blessing. Experimental; a phone sees the preview but Print is blocked until a design is proven safe on a phone.",
|
||||
"element": "bless-india-posters",
|
||||
|
|
@ -300,6 +337,28 @@
|
|||
"profile": "country-poster-wall",
|
||||
"stage": "experimental",
|
||||
"label": "Country Posters — poster wall",
|
||||
"posterDesign": {
|
||||
"family": "countryPoster",
|
||||
"name": "country-poster.letter",
|
||||
"size": "letter",
|
||||
"mobileFriendly": true,
|
||||
"labelKey": "tabs.countryPosters"
|
||||
},
|
||||
"posterDesignLabels": {
|
||||
"country-poster.letter": {
|
||||
"ar": "ملصقات الدول",
|
||||
"de": "Länderposter",
|
||||
"en": "Country Posters",
|
||||
"es": "Carteles por país",
|
||||
"fr": "Affiches par pays",
|
||||
"hi": "देश पोस्टर",
|
||||
"it": "Poster per paese",
|
||||
"pt": "Cartazes por país",
|
||||
"ro": "Afișe pe țări",
|
||||
"ru": "Плакаты по странам",
|
||||
"zh": "国家海报"
|
||||
}
|
||||
},
|
||||
"file": "country-poster-wall.vue",
|
||||
"description": "One printable card per country, grouped into COLLAPSIBLE regional sections so the",
|
||||
"element": "country-posters-map",
|
||||
|
|
@ -966,7 +1025,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA",
|
||||
"integrity": "sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -980,6 +1039,70 @@
|
|||
"profile": "poster-wall",
|
||||
"stage": "experimental",
|
||||
"label": "UPG100 Posters — Poster Wall",
|
||||
"posterDesign": [
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.letter",
|
||||
"size": "letter",
|
||||
"mobileFriendly": true,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
},
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.3x3",
|
||||
"size": "3x3",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
},
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.3x4",
|
||||
"size": "3x4",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
}
|
||||
],
|
||||
"posterDesignLabels": {
|
||||
"upg100-territory-poster.letter": {
|
||||
"ar": "ملصق منطقة UPG-100",
|
||||
"de": "UPG-100-Gebietsplakat",
|
||||
"en": "UPG-100 territory poster",
|
||||
"es": "Cartel de territorio UPG-100",
|
||||
"fr": "Affiche de territoire UPG-100",
|
||||
"hi": "UPG-100 क्षेत्र पोस्टर",
|
||||
"it": "Manifesto di territorio UPG-100",
|
||||
"pt": "Cartaz de território UPG-100",
|
||||
"ro": "Afiș de teritoriu UPG-100",
|
||||
"ru": "Плакат территории UPG-100",
|
||||
"zh": "UPG-100 区域海报"
|
||||
},
|
||||
"upg100-territory-poster.3x3": {
|
||||
"ar": "ملصق منطقة UPG-100",
|
||||
"de": "UPG-100-Gebietsplakat",
|
||||
"en": "UPG-100 territory poster",
|
||||
"es": "Cartel de territorio UPG-100",
|
||||
"fr": "Affiche de territoire UPG-100",
|
||||
"hi": "UPG-100 क्षेत्र पोस्टर",
|
||||
"it": "Manifesto di territorio UPG-100",
|
||||
"pt": "Cartaz de território UPG-100",
|
||||
"ro": "Afiș de teritoriu UPG-100",
|
||||
"ru": "Плакат территории UPG-100",
|
||||
"zh": "UPG-100 区域海报"
|
||||
},
|
||||
"upg100-territory-poster.3x4": {
|
||||
"ar": "ملصق منطقة UPG-100",
|
||||
"de": "UPG-100-Gebietsplakat",
|
||||
"en": "UPG-100 territory poster",
|
||||
"es": "Cartel de territorio UPG-100",
|
||||
"fr": "Affiche de territoire UPG-100",
|
||||
"hi": "UPG-100 क्षेत्र पोस्टर",
|
||||
"it": "Manifesto di territorio UPG-100",
|
||||
"pt": "Cartaz de território UPG-100",
|
||||
"ro": "Afiș de teritoriu UPG-100",
|
||||
"ru": "Плакат территории UPG-100",
|
||||
"zh": "UPG-100 区域海报"
|
||||
}
|
||||
},
|
||||
"file": "poster-wall.vue",
|
||||
"description": "A wall of printable poster cards, one per 100-list entry, each with a \"Show on map\"",
|
||||
"element": "my-upg-100-list-map",
|
||||
|
|
@ -989,7 +1112,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1005,8 +1128,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1022,8 +1145,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1154,7 +1277,7 @@
|
|||
"bundle": "poster-design",
|
||||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"output": "dx-maps/poster-design/poster-design.js",
|
||||
"integrity": "sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl",
|
||||
"integrity": "sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8",
|
||||
"elements": [
|
||||
"poster-design"
|
||||
],
|
||||
|
|
@ -1177,7 +1300,7 @@
|
|||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-design/poster-design-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1193,7 +1316,7 @@
|
|||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-design/poster-sizes/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1209,7 +1332,7 @@
|
|||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-design/poster-styles/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1798,6 +1921,70 @@
|
|||
"stage": "experimental",
|
||||
"label": "Wall of People Groups",
|
||||
"mobile": "desktop-only",
|
||||
"posterDesign": [
|
||||
{
|
||||
"family": "wall",
|
||||
"name": "wall.bless-36x48",
|
||||
"size": "36x48",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "wall.blessBanner"
|
||||
},
|
||||
{
|
||||
"family": "wall",
|
||||
"name": "wall.bless-24x36",
|
||||
"size": "24x36",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "wall.blessBanner"
|
||||
},
|
||||
{
|
||||
"family": "wall",
|
||||
"name": "wall.bless-60x96",
|
||||
"size": "60x96",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "wall.blessBanner"
|
||||
}
|
||||
],
|
||||
"posterDesignLabels": {
|
||||
"wall.bless-36x48": {
|
||||
"ar": "باركوا الأمم باسم يسوع",
|
||||
"de": "Segnet die Nationen im Namen Jesu",
|
||||
"en": "Bless the nations in Jesus’ name",
|
||||
"es": "Bendigan a las naciones en el nombre de Jesús",
|
||||
"fr": "Bénissez les nations au nom de Jésus",
|
||||
"hi": "यीशु के नाम में राष्ट्रों को आशीष दो",
|
||||
"it": "Benedite le nazioni nel nome di Gesù",
|
||||
"pt": "Abençoem as nações em nome de Jesus",
|
||||
"ro": "Binecuvântați națiunile în Numele lui Isus",
|
||||
"ru": "Благословляйте народы во имя Иисуса",
|
||||
"zh": "奉耶稣的名祝福万国"
|
||||
},
|
||||
"wall.bless-24x36": {
|
||||
"ar": "باركوا الأمم باسم يسوع",
|
||||
"de": "Segnet die Nationen im Namen Jesu",
|
||||
"en": "Bless the nations in Jesus’ name",
|
||||
"es": "Bendigan a las naciones en el nombre de Jesús",
|
||||
"fr": "Bénissez les nations au nom de Jésus",
|
||||
"hi": "यीशु के नाम में राष्ट्रों को आशीष दो",
|
||||
"it": "Benedite le nazioni nel nome di Gesù",
|
||||
"pt": "Abençoem as nações em nome de Jesus",
|
||||
"ro": "Binecuvântați națiunile în Numele lui Isus",
|
||||
"ru": "Благословляйте народы во имя Иисуса",
|
||||
"zh": "奉耶稣的名祝福万国"
|
||||
},
|
||||
"wall.bless-60x96": {
|
||||
"ar": "باركوا الأمم باسم يسوع",
|
||||
"de": "Segnet die Nationen im Namen Jesu",
|
||||
"en": "Bless the nations in Jesus’ name",
|
||||
"es": "Bendigan a las naciones en el nombre de Jesús",
|
||||
"fr": "Bénissez les nations au nom de Jésus",
|
||||
"hi": "यीशु के नाम में राष्ट्रों को आशीष दो",
|
||||
"it": "Benedite le nazioni nel nome di Gesù",
|
||||
"pt": "Abençoem as nações em nome de Jesus",
|
||||
"ro": "Binecuvântați națiunile în Numele lui Isus",
|
||||
"ru": "Благословляйте народы во имя Иисуса",
|
||||
"zh": "奉耶稣的名祝福万国"
|
||||
}
|
||||
},
|
||||
"file": "wall-of-people-groups.vue",
|
||||
"description": "Every Doxa people group as a card on one zoomable prayer wall",
|
||||
"element": "wall-of-people-groups",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
"bundle": "bible-posters",
|
||||
"scriptUrl": "./bible-posters/bible-posters.js",
|
||||
"output": "dx-maps/bible-posters/bible-posters.js",
|
||||
"integrity": "sha384-ynxYcvYpparOlz9opwuHgBDP0XFUVXdThQWubDo1majENhbrlnf+AaGacWKIwIWx",
|
||||
"integrity": "sha384-twS8rEG4vtm8MPjq+ytjSf6g4stmnzr/6KYbVRDhQ3TH7MiTgsNG7X19Y3Ncb0JJ",
|
||||
"elements": [
|
||||
"bible-posters"
|
||||
],
|
||||
|
|
@ -54,6 +54,28 @@
|
|||
"targets": [
|
||||
"web"
|
||||
],
|
||||
"posterDesign": {
|
||||
"family": "biblePoster",
|
||||
"name": "bible-poster.36x60",
|
||||
"size": "36x60",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "biblePosters.posterDesign.label"
|
||||
},
|
||||
"posterDesignLabels": {
|
||||
"bible-poster.36x60": {
|
||||
"ar": "ملصق سفر من الكتاب المقدس",
|
||||
"de": "Bibelbuch-Poster",
|
||||
"en": "Bible book poster",
|
||||
"es": "Cartel de un libro de la Biblia",
|
||||
"fr": "Affiche d'un livre de la Bible",
|
||||
"hi": "बाइबल पुस्तक पोस्टर",
|
||||
"it": "Poster di un libro della Bibbia",
|
||||
"pt": "Cartaz de um livro da Bíblia",
|
||||
"ro": "Poster cu o carte a Bibliei",
|
||||
"ru": "Плакат книги Библии",
|
||||
"zh": "圣经书卷海报"
|
||||
}
|
||||
},
|
||||
"file": "bible-posters.vue",
|
||||
"description": "A whole book of the Bible on one poster, in justified columns under a bold title, from a public-domain text.",
|
||||
"element": "bible-posters",
|
||||
|
|
@ -63,7 +85,7 @@
|
|||
"scriptUrl": "./bible-posters/bible-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./bible-posters/bible-posters/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./bible-posters/bible-posters.js\" integrity=\"sha384-ynxYcvYpparOlz9opwuHgBDP0XFUVXdThQWubDo1majENhbrlnf+AaGacWKIwIWx\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<bible-posters profile-config='{\"profile\":\"bible-posters\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bible-posters>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./bible-posters/bible-posters.js\" integrity=\"sha384-twS8rEG4vtm8MPjq+ytjSf6g4stmnzr/6KYbVRDhQ3TH7MiTgsNG7X19Y3Ncb0JJ\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<bible-posters profile-config='{\"profile\":\"bible-posters\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></bible-posters>"
|
||||
},
|
||||
"bundle": "bible-posters"
|
||||
}
|
||||
|
|
@ -161,6 +183,21 @@
|
|||
"labelKey": "blessIndiaPosters.rangeBadge60x96"
|
||||
}
|
||||
],
|
||||
"posterDesignLabels": {
|
||||
"bless-india.range-badge-60x96": {
|
||||
"ar": "شارات النطاق · 60×96 (5×8 قدم)",
|
||||
"de": "Nummern-Marken · 60×96 (5×8 Fuß)",
|
||||
"en": "Range badges · 60×96 (5×8 ft)",
|
||||
"es": "Insignias numeradas · 60×96 (5×8 pies)",
|
||||
"fr": "Badges de numéros · 60×96 (5×8 pi)",
|
||||
"hi": "क्रमांकित बैज · 60×96 (5×8 फुट)",
|
||||
"it": "Badge numerati · 60×96 (5×8 ft)",
|
||||
"pt": "Emblemas numerados · 60×96 (5×8 pés)",
|
||||
"ro": "Insigne numerotate · 60×96 (5×8 ft)",
|
||||
"ru": "Номерные значки · 60×96 (5×8 футов)",
|
||||
"zh": "编号标记 · 60×96(5×8 英尺)"
|
||||
}
|
||||
},
|
||||
"file": "bless-india-posters.vue",
|
||||
"description": "A preview surface for the whole-India poster problem — fitting every one of India's people groups onto a single printable sheet. Each design attempt is generated live from the current data (nothing stored) and shown as its printable sheets with the glue strips, headed \"Bless India in Jesus' name\" with the Numbers 6:24-26 blessing. Experimental; a phone sees the preview but Print is blocked until a design is proven safe on a phone.",
|
||||
"element": "bless-india-posters",
|
||||
|
|
@ -300,6 +337,28 @@
|
|||
"profile": "country-poster-wall",
|
||||
"stage": "experimental",
|
||||
"label": "Country Posters — poster wall",
|
||||
"posterDesign": {
|
||||
"family": "countryPoster",
|
||||
"name": "country-poster.letter",
|
||||
"size": "letter",
|
||||
"mobileFriendly": true,
|
||||
"labelKey": "tabs.countryPosters"
|
||||
},
|
||||
"posterDesignLabels": {
|
||||
"country-poster.letter": {
|
||||
"ar": "ملصقات الدول",
|
||||
"de": "Länderposter",
|
||||
"en": "Country Posters",
|
||||
"es": "Carteles por país",
|
||||
"fr": "Affiches par pays",
|
||||
"hi": "देश पोस्टर",
|
||||
"it": "Poster per paese",
|
||||
"pt": "Cartazes por país",
|
||||
"ro": "Afișe pe țări",
|
||||
"ru": "Плакаты по странам",
|
||||
"zh": "国家海报"
|
||||
}
|
||||
},
|
||||
"file": "country-poster-wall.vue",
|
||||
"description": "One printable card per country, grouped into COLLAPSIBLE regional sections so the",
|
||||
"element": "country-posters-map",
|
||||
|
|
@ -966,7 +1025,7 @@
|
|||
"bundle": "my-upg-100-list",
|
||||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"output": "dx-maps/my-upg-100-list/my-upg-100-list.js",
|
||||
"integrity": "sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA",
|
||||
"integrity": "sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD",
|
||||
"elements": [
|
||||
"my-upg-100-list-map"
|
||||
],
|
||||
|
|
@ -980,6 +1039,70 @@
|
|||
"profile": "poster-wall",
|
||||
"stage": "experimental",
|
||||
"label": "UPG100 Posters — Poster Wall",
|
||||
"posterDesign": [
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.letter",
|
||||
"size": "letter",
|
||||
"mobileFriendly": true,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
},
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.3x3",
|
||||
"size": "3x3",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
},
|
||||
{
|
||||
"family": "upgList",
|
||||
"name": "upg100-territory-poster.3x4",
|
||||
"size": "3x4",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "upgMap.posterDesignTerritory"
|
||||
}
|
||||
],
|
||||
"posterDesignLabels": {
|
||||
"upg100-territory-poster.letter": {
|
||||
"ar": "ملصق منطقة UPG-100",
|
||||
"de": "UPG-100-Gebietsplakat",
|
||||
"en": "UPG-100 territory poster",
|
||||
"es": "Cartel de territorio UPG-100",
|
||||
"fr": "Affiche de territoire UPG-100",
|
||||
"hi": "UPG-100 क्षेत्र पोस्टर",
|
||||
"it": "Manifesto di territorio UPG-100",
|
||||
"pt": "Cartaz de território UPG-100",
|
||||
"ro": "Afiș de teritoriu UPG-100",
|
||||
"ru": "Плакат территории UPG-100",
|
||||
"zh": "UPG-100 区域海报"
|
||||
},
|
||||
"upg100-territory-poster.3x3": {
|
||||
"ar": "ملصق منطقة UPG-100",
|
||||
"de": "UPG-100-Gebietsplakat",
|
||||
"en": "UPG-100 territory poster",
|
||||
"es": "Cartel de territorio UPG-100",
|
||||
"fr": "Affiche de territoire UPG-100",
|
||||
"hi": "UPG-100 क्षेत्र पोस्टर",
|
||||
"it": "Manifesto di territorio UPG-100",
|
||||
"pt": "Cartaz de território UPG-100",
|
||||
"ro": "Afiș de teritoriu UPG-100",
|
||||
"ru": "Плакат территории UPG-100",
|
||||
"zh": "UPG-100 区域海报"
|
||||
},
|
||||
"upg100-territory-poster.3x4": {
|
||||
"ar": "ملصق منطقة UPG-100",
|
||||
"de": "UPG-100-Gebietsplakat",
|
||||
"en": "UPG-100 territory poster",
|
||||
"es": "Cartel de territorio UPG-100",
|
||||
"fr": "Affiche de territoire UPG-100",
|
||||
"hi": "UPG-100 क्षेत्र पोस्टर",
|
||||
"it": "Manifesto di territorio UPG-100",
|
||||
"pt": "Cartaz de território UPG-100",
|
||||
"ro": "Afiș de teritoriu UPG-100",
|
||||
"ru": "Плакат территории UPG-100",
|
||||
"zh": "UPG-100 区域海报"
|
||||
}
|
||||
},
|
||||
"file": "poster-wall.vue",
|
||||
"description": "A wall of printable poster cards, one per 100-list entry, each with a \"Show on map\"",
|
||||
"element": "my-upg-100-list-map",
|
||||
|
|
@ -989,7 +1112,7 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1005,8 +1128,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1022,8 +1145,8 @@
|
|||
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
"script": "<!-- MapLibre engine (the default), fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js\" integrity=\"sha384-5+cfbwT0iiub6VsQAdn6yz16nr6sDiQoHx6tm4O8OVYXHYOxcffFmCJBL0dgdvGp\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css\" integrity=\"sha384-uTttxo/aOKbdE5RlD/SPzSDoDmNvGlUYPjONi2MN/b7c9HPSvW07OIuyP7uL6jxK\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- MapLibre draws this map and needs no token. For Mapbox, paste this map's embed.scriptMapbox from manifest.json instead: it sets \"provider\":\"mapbox\" and preloads the Mapbox engine; fill \"tk\" with your token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>",
|
||||
"scriptMapbox": "<!-- Mapbox engine, fetched early -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<link rel=\"preload\" as=\"style\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.css\" integrity=\"sha384-rp1X/jJ9MpWPef9OimfXhOaOc4Gzs87yQvF+ajwY6X2H4PTeHNycNlLWiWvj7ehV\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD\" crossorigin=\"anonymous\"></script>\n<!-- Mapbox draws this map: fill \"tk\" with your Mapbox token. -->\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"provider\":\"mapbox\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1154,7 +1277,7 @@
|
|||
"bundle": "poster-design",
|
||||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"output": "dx-maps/poster-design/poster-design.js",
|
||||
"integrity": "sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl",
|
||||
"integrity": "sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8",
|
||||
"elements": [
|
||||
"poster-design"
|
||||
],
|
||||
|
|
@ -1177,7 +1300,7 @@
|
|||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-design/poster-design-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-design-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1193,7 +1316,7 @@
|
|||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-design/poster-sizes/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-sizes\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -1209,7 +1332,7 @@
|
|||
"scriptUrl": "./poster-design/poster-design.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./poster-design/poster-styles/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./poster-design/poster-design.js\" integrity=\"sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<poster-design profile-config='{\"profile\":\"poster-styles\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></poster-design>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1798,6 +1921,70 @@
|
|||
"stage": "experimental",
|
||||
"label": "Wall of People Groups",
|
||||
"mobile": "desktop-only",
|
||||
"posterDesign": [
|
||||
{
|
||||
"family": "wall",
|
||||
"name": "wall.bless-36x48",
|
||||
"size": "36x48",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "wall.blessBanner"
|
||||
},
|
||||
{
|
||||
"family": "wall",
|
||||
"name": "wall.bless-24x36",
|
||||
"size": "24x36",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "wall.blessBanner"
|
||||
},
|
||||
{
|
||||
"family": "wall",
|
||||
"name": "wall.bless-60x96",
|
||||
"size": "60x96",
|
||||
"mobileFriendly": false,
|
||||
"labelKey": "wall.blessBanner"
|
||||
}
|
||||
],
|
||||
"posterDesignLabels": {
|
||||
"wall.bless-36x48": {
|
||||
"ar": "باركوا الأمم باسم يسوع",
|
||||
"de": "Segnet die Nationen im Namen Jesu",
|
||||
"en": "Bless the nations in Jesus’ name",
|
||||
"es": "Bendigan a las naciones en el nombre de Jesús",
|
||||
"fr": "Bénissez les nations au nom de Jésus",
|
||||
"hi": "यीशु के नाम में राष्ट्रों को आशीष दो",
|
||||
"it": "Benedite le nazioni nel nome di Gesù",
|
||||
"pt": "Abençoem as nações em nome de Jesus",
|
||||
"ro": "Binecuvântați națiunile în Numele lui Isus",
|
||||
"ru": "Благословляйте народы во имя Иисуса",
|
||||
"zh": "奉耶稣的名祝福万国"
|
||||
},
|
||||
"wall.bless-24x36": {
|
||||
"ar": "باركوا الأمم باسم يسوع",
|
||||
"de": "Segnet die Nationen im Namen Jesu",
|
||||
"en": "Bless the nations in Jesus’ name",
|
||||
"es": "Bendigan a las naciones en el nombre de Jesús",
|
||||
"fr": "Bénissez les nations au nom de Jésus",
|
||||
"hi": "यीशु के नाम में राष्ट्रों को आशीष दो",
|
||||
"it": "Benedite le nazioni nel nome di Gesù",
|
||||
"pt": "Abençoem as nações em nome de Jesus",
|
||||
"ro": "Binecuvântați națiunile în Numele lui Isus",
|
||||
"ru": "Благословляйте народы во имя Иисуса",
|
||||
"zh": "奉耶稣的名祝福万国"
|
||||
},
|
||||
"wall.bless-60x96": {
|
||||
"ar": "باركوا الأمم باسم يسوع",
|
||||
"de": "Segnet die Nationen im Namen Jesu",
|
||||
"en": "Bless the nations in Jesus’ name",
|
||||
"es": "Bendigan a las naciones en el nombre de Jesús",
|
||||
"fr": "Bénissez les nations au nom de Jésus",
|
||||
"hi": "यीशु के नाम में राष्ट्रों को आशीष दो",
|
||||
"it": "Benedite le nazioni nel nome di Gesù",
|
||||
"pt": "Abençoem as nações em nome de Jesus",
|
||||
"ro": "Binecuvântați națiunile în Numele lui Isus",
|
||||
"ru": "Благословляйте народы во имя Иисуса",
|
||||
"zh": "奉耶稣的名祝福万国"
|
||||
}
|
||||
},
|
||||
"file": "wall-of-people-groups.vue",
|
||||
"description": "Every Doxa people group as a card on one zoomable prayer wall",
|
||||
"element": "wall-of-people-groups",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-Ro1xEK8nsVAqeFfELEuWINGGqtC49CNtwOhMPisvz/FtgDBuDirOP/Vtz+DcBihA" crossorigin="anonymous"></script>
|
||||
<script src="../my-upg-100-list.js" integrity="sha384-RoUFVUuNTZd9S6IGDGEbW7D975a4q6EFiZH4xdye1ggd3bbArVu2MT3tBYjXVWiD" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-design> custom element. -->
|
||||
<script src="../poster-design.js" integrity="sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl" crossorigin="anonymous"></script>
|
||||
<script src="../poster-design.js" integrity="sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-design> custom element. -->
|
||||
<script src="../poster-design.js" integrity="sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl" crossorigin="anonymous"></script>
|
||||
<script src="../poster-design.js" integrity="sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <poster-design> custom element. -->
|
||||
<script src="../poster-design.js" integrity="sha384-TDt3YQwUlV9VrdE7hzxKPm3MKnMHbhFzurEJ33Nr1+nLzfh5XbblIgNz51Gk0sSl" crossorigin="anonymous"></script>
|
||||
<script src="../poster-design.js" integrity="sha384-hiRS9PFXm3IHABow+MMmBh4VYVldRxUQSnx7b8wh3H1UW4N+Dq22fTV/oWGGvSP8" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue