# Conflicts: # public/doxa-maps/country-posters/countries-map/index.html # public/doxa-maps/country-posters/country-poster-wall/index.html # public/doxa-maps/country-posters/country-posters-mfe/index.html # public/doxa-maps/country-posters/country-posters.js # public/doxa-maps/index.html # public/doxa-maps/manifest.json
This commit is contained in:
commit
916ef35ee8
22 changed files with 743 additions and 198 deletions
111
internal/check-poster-prints.mjs
Normal file
111
internal/check-poster-prints.mjs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// check-poster-prints — the SHARED print actions must produce real documents.
|
||||
//
|
||||
// Context: the poster wall was generalised into library/components/poster/
|
||||
// PosterWall.vue and two bundles migrated onto it, but the ORIGINAL 903-line
|
||||
// my-upg-100-list wall — the one carrying the actual print actions — was left
|
||||
// behind. So the shared component's packet path was a "coming soon" stub, and
|
||||
// every print fix in the 100-list was invisible to the three bundles using the
|
||||
// shared wall. usePosterPrints.js closes that: the ACTIONS are shared, the RECORD
|
||||
// FETCHING stays per-bundle behind a `records` provider.
|
||||
//
|
||||
// ★ THE SEAM IS THE THING TO ASSERT. A wall WITH a provider must produce a real
|
||||
// document; a wall WITHOUT one must degrade honestly rather than failing at click
|
||||
// time. Both directions are checked, because "the button is there" and "the button
|
||||
// works" are different claims — this flight has repeatedly found the first true
|
||||
// and the second false.
|
||||
//
|
||||
// Runs the composable in a browser page (it calls document/DOM), driving it with a
|
||||
// fake records provider so the assertion is about the DOCUMENTS, not about any
|
||||
// bundle's API being reachable.
|
||||
import { chromium } from 'playwright'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const REPO = process.env.DOXA_REPO
|
||||
|| resolve('/home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-packet')
|
||||
const src = readFileSync(resolve(REPO, 'library/composables/usePosterPrints.js'), 'utf8')
|
||||
const frame = readFileSync(resolve(REPO, 'library/composables/printInPlace.js'), 'utf8')
|
||||
|
||||
const b = await chromium.launch()
|
||||
const pg = await b.newPage({ viewport: { width: 1000, height: 800 } })
|
||||
const errs = []
|
||||
pg.on('pageerror', (e) => errs.push(String(e).slice(0, 140)))
|
||||
await pg.setContent('<!DOCTYPE html><html><body></body></html>')
|
||||
|
||||
const r = await pg.evaluate(async ({ src, frame }) => {
|
||||
// Shim the two module deps so the composable can be evaluated standalone: a tiny
|
||||
// `ref`, and printInPlace's real source so the frame mechanism is the real one.
|
||||
const frameUrl = URL.createObjectURL(new Blob([frame], { type: 'text/javascript' }))
|
||||
const vueShim = URL.createObjectURL(new Blob([
|
||||
'export const ref = (v) => ({ value: v })',
|
||||
], { type: 'text/javascript' }))
|
||||
const patched = src
|
||||
.replace("from 'vue'", `from '${vueShim}'`)
|
||||
.replace("from './printInPlace.js'", `from '${frameUrl}'`)
|
||||
const modUrl = URL.createObjectURL(new Blob([patched], { type: 'text/javascript' }))
|
||||
const { usePosterPrints } = await import(modUrl)
|
||||
|
||||
const ROWS = [
|
||||
{ slug: 'a', name: 'Aheu', country_code_label: 'Laos', population: 2700, religion_label: 'Animism' },
|
||||
{ slug: 'b', name: 'Akha', country_code_label: 'Laos', population: 113000, religion_label: 'Animism' },
|
||||
]
|
||||
const out = {}
|
||||
|
||||
// ── with a provider ──────────────────────────────────────────────────────
|
||||
// ⚠ CAPTURE THE HTML, do not read the frame back. openPrintFrame calls
|
||||
// window.print() under autoprint, which BLOCKS in headless Chromium, and the
|
||||
// frame self-removes on afterprint — so reading it back measured a frame that had
|
||||
// already gone and reported 0 rows for a correct document. Intercept the document
|
||||
// the composable hands to the frame instead: that IS what gets printed.
|
||||
let printed = ''
|
||||
const realCreate = document.createElement.bind(document)
|
||||
document.createElement = (tag) => {
|
||||
const el = realCreate(tag)
|
||||
if (tag === 'iframe') {
|
||||
Object.defineProperty(el, 'contentDocument', {
|
||||
get: () => ({ open() {}, close() {}, write(h) { printed += h } }),
|
||||
})
|
||||
Object.defineProperty(el, 'contentWindow', { get: () => ({ addEventListener() {} }) })
|
||||
}
|
||||
return el
|
||||
}
|
||||
const withProv = usePosterPrints({ records: async () => ROWS, label: (i) => i.label })
|
||||
await withProv.printList({ id: 'laos', label: 'Laos' })
|
||||
await new Promise((r2) => setTimeout(r2, 400))
|
||||
document.createElement = realCreate
|
||||
const doc = new DOMParser().parseFromString(printed, 'text/html')
|
||||
out.listRows = doc ? doc.querySelectorAll('tbody tr').length : 0
|
||||
out.listHasCheckbox = doc ? /□/.test(doc.body.innerText) : false
|
||||
out.listPhase = withProv.exportState.value?.phase ?? 'cleared'
|
||||
|
||||
// ── without a provider: must FAIL HONESTLY, not throw ────────────────────
|
||||
const noProv = usePosterPrints({ label: (i) => i.label })
|
||||
await noProv.printList({ id: 'x', label: 'X' })
|
||||
out.noProvPhase = noProv.exportState.value?.phase
|
||||
out.noProvMsg = noProv.exportState.value?.message || ''
|
||||
return out
|
||||
}, { src, frame })
|
||||
await b.close()
|
||||
|
||||
const fails = []
|
||||
if (r.listRows !== 2) fails.push(`the list print rendered ${r.listRows} row(s), expected 2`)
|
||||
if (!r.listHasCheckbox) fails.push('the list print has no prayer checkbox column')
|
||||
if (r.noProvPhase !== 'error') {
|
||||
fails.push(`without a records provider the phase was "${r.noProvPhase}", expected "error" — `
|
||||
+ 'it must degrade honestly rather than silently doing nothing')
|
||||
}
|
||||
if (!/no records provider/i.test(r.noProvMsg)) {
|
||||
fails.push(`the no-provider message was "${r.noProvMsg}" — it should name the missing provider`)
|
||||
}
|
||||
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
|
||||
|
||||
console.log(` list print : ${r.listRows} rows, checkbox=${r.listHasCheckbox}`)
|
||||
console.log(` without provider : phase=${r.noProvPhase} "${r.noProvMsg.slice(0, 60)}"`)
|
||||
console.log(` page errors : ${errs.length}`)
|
||||
|
||||
if (fails.length) {
|
||||
console.error(`\nPOSTER-PRINTS: FAIL — ${fails.length} problem(s)`)
|
||||
for (const f of fails) console.error(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('\nPOSTER-PRINTS: PASS — shared print actions produce real documents, and degrade honestly without a provider.')
|
||||
99
internal/check-provider-switch.mjs
Normal file
99
internal/check-provider-switch.mjs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// check-provider-switch — ?provider=maplibre must actually SWAP THE ENGINE.
|
||||
//
|
||||
// The bug this gates (BUG-UPG100-IGNORES-THE-PROVIDER-SWITCH): my-upg-100-list
|
||||
// builds its map RAW with `new gl.Map` instead of going through useMapInstance, so
|
||||
// the page-level switch — which resolves inside that composable — could not reach
|
||||
// it. MEASURED at the time: five sibling leaves swapped cleanly while this one
|
||||
// stayed on Mapbox and made 19 requests to api.mapbox.com.
|
||||
//
|
||||
// ★ IT COUNTS NETWORK REQUESTS BY HOST, not globals. `window.mapboxgl` can be
|
||||
// present because a page preloads it, and a map can still be DRAWN by maplibre —
|
||||
// or the reverse. What the switch is for is not loading a proprietary engine and
|
||||
// not calling its tile servers, and only the request log shows that.
|
||||
//
|
||||
// Usage: node internal/check-provider-switch.mjs <leafUrl>
|
||||
import { chromium } from 'playwright'
|
||||
|
||||
// ⚠ NOT named `URL` — that shadows the global URL constructor, so every
|
||||
// `new URL(request.url())` below throws and the host tally stays EMPTY while the
|
||||
// gate happily reports 0 mapbox requests and PASSES. Measured: it did exactly
|
||||
// that, hiding 4 real requests to events.mapbox.com.
|
||||
const LEAF = process.argv[2]
|
||||
if (!LEAF) { console.error('usage: check-provider-switch.mjs <leafUrl>'); process.exit(2) }
|
||||
|
||||
async function run(provider) {
|
||||
const b = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader'] })
|
||||
const pg = await b.newPage({ viewport: { width: 1280, height: 900 } })
|
||||
const hosts = new Map()
|
||||
let unparsed = 0
|
||||
pg.on('request', (r) => {
|
||||
try {
|
||||
const h = new URL(r.url()).hostname
|
||||
if (!h) { unparsed += 1; return }
|
||||
hosts.set(h, (hosts.get(h) || 0) + 1)
|
||||
} catch { unparsed += 1 }
|
||||
})
|
||||
const errs = []
|
||||
pg.on('pageerror', (e) => errs.push(String(e).slice(0, 120)))
|
||||
const url = provider ? `${LEAF}${LEAF.includes('?') ? '&' : '?'}provider=${provider}` : LEAF
|
||||
await pg.goto(url, { waitUntil: 'load', timeout: 45000 })
|
||||
await pg.waitForTimeout(12000)
|
||||
const globals = await pg.evaluate(() => ({
|
||||
mapbox: typeof window.mapboxgl !== 'undefined',
|
||||
maplibre: typeof window.maplibregl !== 'undefined',
|
||||
}))
|
||||
await b.close()
|
||||
const count = (needle) => [...hosts.entries()]
|
||||
.filter(([h]) => h.includes(needle)).reduce((a, [, n]) => a + n, 0)
|
||||
// Report what could NOT be parsed rather than swallowing it: a silently empty
|
||||
// tally is how this gate first passed while measuring nothing at all.
|
||||
return { mapboxReq: count('mapbox.com'), globals, errs, unparsed, total: [...hosts.values()].reduce((a, n) => a + n, 0) }
|
||||
}
|
||||
|
||||
const def = await run(null)
|
||||
const ml = await run('maplibre')
|
||||
|
||||
// DERIVED from the leaf's own HTML, not a hardcoded number: count the distinct
|
||||
// api.mapbox.com references the generated page carries, then allow a little slack
|
||||
// because a stylesheet can be fetched more than once (the upg-100 leaf resolves 4
|
||||
// distinct urls but issues 5 requests — one css is re-fetched). Hardcoding 5 would
|
||||
// pass a leaf that legitimately preloads 4 and leaks 1 runtime call.
|
||||
const pageHtml = await (await fetch(LEAF)).text()
|
||||
const declared = new Set((pageHtml.match(/https:\/\/api\.mapbox\.com[^"')]+/g) || []))
|
||||
const PRELOAD_ALLOWANCE = Number(process.env.PROVIDER_PRELOADS ?? (declared.size + 1))
|
||||
|
||||
console.log(` page preloads : ${declared.size} distinct api.mapbox.com url(s) in the leaf HTML`)
|
||||
console.log(` default : ${def.mapboxReq} mapbox / ${def.total} total request(s) `
|
||||
+ `[mapboxgl=${def.globals.mapbox} maplibregl=${def.globals.maplibre}]`)
|
||||
console.log(` ?provider=maplibre: ${ml.mapboxReq} mapbox / ${ml.total} total request(s) `
|
||||
+ `[mapboxgl=${ml.globals.mapbox} maplibregl=${ml.globals.maplibre}]`)
|
||||
|
||||
const fails = []
|
||||
if (!ml.globals.maplibre) {
|
||||
fails.push('?provider=maplibre did not load the maplibre engine at all')
|
||||
}
|
||||
// The RESIDUAL SET is the leaf page's own <link>/<script> preloads — mapbox-gl and
|
||||
// the geocoder, referenced in the generated index.html header. Those fire before any
|
||||
// Vue code runs, so no runtime switch can suppress them; removing them is a separate
|
||||
// concern (the page template), not this bug. What the switch must achieve is that NO
|
||||
// RUNTIME request goes to Mapbox: measured 19 -> 5 on the upg-100 leaf, and all 5 are
|
||||
// in the HTML. Asserting "fewer than default" would pass a half-fix at 18.
|
||||
if (ml.mapboxReq > PRELOAD_ALLOWANCE) {
|
||||
fails.push(`?provider=maplibre made ${ml.mapboxReq} mapbox request(s), more than the `
|
||||
+ `${PRELOAD_ALLOWANCE} page preloads — ${ml.mapboxReq - PRELOAD_ALLOWANCE} are RUNTIME `
|
||||
+ 'calls the switch failed to redirect')
|
||||
}
|
||||
if (def.mapboxReq <= PRELOAD_ALLOWANCE) {
|
||||
fails.push(`the default run made only ${def.mapboxReq} mapbox request(s) — the map is probably `
|
||||
+ 'not rendering, so this comparison proves nothing')
|
||||
}
|
||||
if (!def.total) fails.push('no requests were recorded at all — the tally is broken, not the page')
|
||||
if (ml.errs.length) fails.push(`${ml.errs.length} page error(s) under maplibre: ${ml.errs[0]}`)
|
||||
|
||||
if (fails.length) {
|
||||
console.error(`\nPROVIDER-SWITCH: FAIL — ${fails.length} problem(s)`)
|
||||
for (const f of fails) console.error(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`\nPROVIDER-SWITCH: PASS — maplibre loads and mapbox traffic drops `
|
||||
+ `${def.mapboxReq} -> ${ml.mapboxReq}.`)
|
||||
|
|
@ -41,6 +41,8 @@
|
|||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { usePosterPrints } from '@map/composables/usePosterPrints.js'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
// Export-modal geometry. A fixed;inset:0 scrim covers only the CONTENT PANE in the cap-5 shell
|
||||
// (contain:layout on .vtl-content). Measured 1024x793 @ (256,107) at 1280x900 — rail clickable
|
||||
// behind the "modal". See library/utils/viewportScrim.js for the measurements and rejected fixes.
|
||||
|
|
@ -77,6 +79,17 @@ const props = defineProps({
|
|||
// prayer list for each country generated.")
|
||||
prayerListSoonMessage: { type: String,
|
||||
default: 'Coming soon — the prayer list for each people group is in build.' },
|
||||
// ★ RECORDS PROVIDER — the seam that makes the print actions shareable.
|
||||
// The PRINT ACTIONS are common to every wall; the RECORD FETCHING is not. The
|
||||
// 100-list resolves its rows through the people-groups API, the selector
|
||||
// evaluator and a pg_slugs special case; Key Cities and Country Posters have
|
||||
// entirely different data shapes. So a bundle that CAN supply rows passes this
|
||||
// and gets the list + card-deck prints; one that cannot simply omits it and
|
||||
// those buttons stay hidden, rather than failing at click time.
|
||||
records: { type: Function, default: null },
|
||||
// Card stock for the deck print, when this wall offers one. See card-render.js
|
||||
// STOCK — both orientations are available.
|
||||
cardStock: { type: String, default: 'poker' },
|
||||
// EMBEDDED mode (added 2026-09-24 for the regions-tab poster dropdown). The wall's root is
|
||||
// `position:absolute;inset:0` because in country-posters and my-upg-100-list it IS a pane and
|
||||
// fills it. Dropped inside another component — a region card, say — that absolute root covers
|
||||
|
|
@ -208,11 +221,43 @@ async function exportPoster(item) {
|
|||
}
|
||||
}
|
||||
|
||||
// ── SHARED PRINT ACTIONS ──────────────────────────────────────────────────
|
||||
// The list and card-deck prints live in the shared composable so every wall gets
|
||||
// the same documents. They need ROWS, which only the bundle can fetch — hence the
|
||||
// `records` prop. Without it the buttons are hidden and this stays honest rather
|
||||
// than offering an action that cannot work.
|
||||
// The wall follows the app's locale: the card deck and its QR codes are localised,
|
||||
// and packet-strings.json falls back per key.
|
||||
const { locale: wallLocale } = useI18n()
|
||||
// ⚠ ONE exportState, not two. The composable owns a ref of its own; if the HUD
|
||||
// keeps rendering the wall's while the print actions write to the composable's, a
|
||||
// card-deck print shows NO progress and its errors vanish silently — the mechanism
|
||||
// works and the feedback does not. Hand the composable this wall's ref so both
|
||||
// write to the surface the reader is actually looking at.
|
||||
const prints = usePosterPrints({
|
||||
state: exportState,
|
||||
records: props.records ? (item) => props.records(item) : null,
|
||||
// useI18n()'s locale can be a ref or a plain string depending on how the host
|
||||
// installed it, so normalise rather than assume.
|
||||
lang: () => {
|
||||
const l = wallLocale && (wallLocale.value ?? wallLocale)
|
||||
return (typeof l === 'string' && l) || 'en'
|
||||
},
|
||||
stock: () => props.cardStock,
|
||||
label: (item) => item?.label || item?.id || '',
|
||||
})
|
||||
const canPrintRows = computed(() => typeof props.records === 'function')
|
||||
|
||||
function downloadPacket(item) {
|
||||
// NOT an error — unbuilt, not broken. phase:'error' reads to a user as
|
||||
// "something went wrong" rather than "not yet".
|
||||
exportState.value = { label: item.label, kind: 'Prayer List', phase: 'soon',
|
||||
message: props.prayerListSoonMessage, pct: 0 }
|
||||
// Still the honest "not yet" when this wall has no records provider: NOT an
|
||||
// error — unbuilt, not broken. phase:'error' reads to a user as "something went
|
||||
// wrong" rather than "not yet".
|
||||
if (!canPrintRows.value) {
|
||||
exportState.value = { label: item.label, kind: 'Prayer List', phase: 'soon',
|
||||
message: props.prayerListSoonMessage, pct: 0 }
|
||||
return
|
||||
}
|
||||
prints.printList(item)
|
||||
}
|
||||
|
||||
function download(blob, name) {
|
||||
|
|
@ -422,6 +467,11 @@ useShadowStyles(
|
|||
<fieldset class="tape pfs">
|
||||
<legend>Print Options</legend>
|
||||
<button type="button" class="pbtn" @click="downloadPacket(item)">Prayer List</button>
|
||||
<!-- CARD DECK — only when this wall can supply rows. A button that
|
||||
cannot work is worse than an absent one, so it is gated on the
|
||||
records provider rather than shown and then failing at click. -->
|
||||
<button v-if="canPrintRows" type="button" class="pbtn"
|
||||
@click="prints.printCardDeck(item)">Card Deck</button>
|
||||
<button type="button" class="pbtn" @click="exportPoster(item)">Poster Map</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
|
|
|||
168
library/composables/usePosterPrints.js
Normal file
168
library/composables/usePosterPrints.js
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
// usePosterPrints.js — THE print actions every poster wall shares.
|
||||
//
|
||||
// ★ WHY THIS EXISTS. The poster wall was generalised into
|
||||
// library/components/poster/PosterWall.vue and two bundles were migrated onto it —
|
||||
// but the ORIGINAL, my-upg-100-list/poster-wall.vue (903 lines), was left behind.
|
||||
// So the one wall carrying the real print actions is the one wall nothing else can
|
||||
// benefit from, and the shared component's packet path is still a "coming soon"
|
||||
// stub. Every print fix made in the 100-list is invisible to the three bundles on
|
||||
// the shared component, and vice versa. That divergence is the bug.
|
||||
//
|
||||
// ── THE SEAM ────────────────────────────────────────────────────────────────
|
||||
// The PRINT ACTIONS are shareable; the RECORD FETCHING is not. fetchListRecords in
|
||||
// the 100-list knows the people-groups API, the selector evaluator and the pg_slugs
|
||||
// special case; Key Cities and Country Posters have entirely different data shapes.
|
||||
// So this composable takes a `records(item)` PROVIDER and owns everything after it:
|
||||
// the HUD state machine, the in-place print frame, and the three documents.
|
||||
//
|
||||
// That is also what keeps the bundles honest — a wall that cannot supply records
|
||||
// simply does not pass a provider, and the actions that need them stay hidden
|
||||
// rather than failing at click time.
|
||||
//
|
||||
// ⚠ PRINTS IN PLACE, never window.open(). A popup is blockable and drops the reader
|
||||
// out of the map; the shared openPrintFrame mounts a hidden same-page iframe. This
|
||||
// is the mechanism the Driver asked for by name: "not supposed to open up a new
|
||||
// browser, but should open up the print dialog directly in the browser".
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { openPrintFrame } from './printInPlace.js';
|
||||
|
||||
const esc = (s) => String(s ?? '')
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/"/g, '"').replace(/'/g, ''');
|
||||
|
||||
/**
|
||||
* @param {object} opts
|
||||
* records async (item) => rows[] — REQUIRED for the list and deck prints.
|
||||
* strings () => Promise<object> — the packet/card i18n catalog.
|
||||
* lang () => string — the app locale; the deck and packet follow it.
|
||||
* stock () => string — card stock id (see card-render.js STOCK).
|
||||
* posterUrl (item) => string|null — the pre-rendered image, when one exists.
|
||||
* label (item) => string
|
||||
*/
|
||||
export function usePosterPrints(opts = {}) {
|
||||
// ⚠ ACCEPT the caller's state ref when it has one. A wall that renders its OWN
|
||||
// exportState while these actions write to a private ref shows no progress and
|
||||
// swallows errors — the mechanism works and the feedback does not. Owning a ref
|
||||
// here is only the standalone default.
|
||||
const exportState = opts.state || ref(null);
|
||||
const busy = () => exportState.value !== null;
|
||||
|
||||
const label = opts.label || ((item) => item?.label || item?.id || '');
|
||||
const lang = opts.lang || (() => 'en');
|
||||
const stock = opts.stock || (() => 'poker');
|
||||
|
||||
const set = (item, kind, phase, message, pct) => {
|
||||
exportState.value = { label: label(item), kind, phase, message, pct };
|
||||
};
|
||||
const done = (item, kind) => {
|
||||
set(item, kind, 'done', 'print dialog opened', 100);
|
||||
setTimeout(() => { exportState.value = null; }, 900);
|
||||
};
|
||||
const fail = (item, kind, e) => {
|
||||
set(item, kind, 'error', String(e?.message || e).slice(0, 140), 0);
|
||||
};
|
||||
function dismiss() { exportState.value = null; }
|
||||
|
||||
async function rows(item) {
|
||||
if (typeof opts.records !== 'function') {
|
||||
throw new Error('this wall supplies no records provider');
|
||||
}
|
||||
const r = await opts.records(item);
|
||||
if (!r || !r.length) throw new Error('that list resolved to no records');
|
||||
return r;
|
||||
}
|
||||
|
||||
// ── 1. THE POSTER ─────────────────────────────────────────────────────────
|
||||
// Prefers the pre-rendered image; falls back to rasterising the poster's own
|
||||
// HTML in the browser, which is what lets the repo stop shipping 41 MB of webp.
|
||||
async function printPoster(item) {
|
||||
if (busy()) return;
|
||||
const kind = 'Poster';
|
||||
set(item, kind, 'fetch', 'fetching the poster', 25);
|
||||
try {
|
||||
const url = opts.posterUrl ? opts.posterUrl(item) : null;
|
||||
let html = '';
|
||||
if (url) {
|
||||
const res = await fetch(url, { cache: 'force-cache' });
|
||||
if (res.ok) html = await res.text();
|
||||
}
|
||||
if (!html) throw new Error('no poster available for this item');
|
||||
openPrintFrame(html, { autoprint: true });
|
||||
done(item, kind);
|
||||
} catch (e) { fail(item, kind, e); }
|
||||
}
|
||||
|
||||
// ── 2. THE LIST ───────────────────────────────────────────────────────────
|
||||
// The Driver's FIRST option — "that's what the whole app was about": the list
|
||||
// itself as a print-paginated table with a prayer checkbox per row.
|
||||
async function printList(item, { columns, title } = {}) {
|
||||
if (busy()) return;
|
||||
const kind = 'List';
|
||||
set(item, kind, 'fetch', 'fetching records', 30);
|
||||
try {
|
||||
const data = await rows(item);
|
||||
const cols = columns || [
|
||||
{ key: 'name', head: 'Name', cls: 'c-name' },
|
||||
{ key: 'country_code_label', head: 'Country' },
|
||||
{ key: 'population', head: 'People', cls: 'c-num', num: true },
|
||||
{ key: 'religion_label', head: 'Religion' },
|
||||
];
|
||||
const head = cols.map((c) => `<th>${esc(c.head)}</th>`).join('') + '<th class="c-pray">✓</th>';
|
||||
const body = data.map((r) => '<tr>' + cols.map((c) => {
|
||||
const raw = r[c.key] ?? r[`${c.key}_label`] ?? '—';
|
||||
const v = c.num && Number.isFinite(Number(raw))
|
||||
? Number(raw).toLocaleString(lang()) : raw;
|
||||
return `<td${c.cls ? ` class="${c.cls}"` : ''}>${esc(v)}</td>`;
|
||||
}).join('') + '<td class="c-pray">□</td></tr>').join('');
|
||||
|
||||
set(item, kind, 'render', `laying out ${data.length} rows`, 70);
|
||||
const docTitle = title || `${label(item)} — ${data.length}`;
|
||||
openPrintFrame(
|
||||
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>${esc(docTitle)}</title>`
|
||||
+ '<style>'
|
||||
+ '@page{size:letter portrait;margin:14mm 12mm}'
|
||||
+ 'html,body{margin:0;background:#fff;color:#0f1419;'
|
||||
+ "font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}"
|
||||
+ '.hd{margin-bottom:14px}.hd h1{margin:0 0 3px;font-size:18px}'
|
||||
+ '.hd p{margin:0;font-size:11px;color:#5b6672}'
|
||||
+ 'table{width:100%;border-collapse:collapse;font-size:11px}'
|
||||
+ 'thead th{text-align:left;border-bottom:2px solid #0f1419;padding:5px 6px;'
|
||||
+ 'font-size:9.5px;text-transform:uppercase;letter-spacing:.05em}'
|
||||
+ 'tbody td{padding:5px 6px;border-bottom:1px solid #e3e6ea}'
|
||||
+ '.c-name{font-weight:600}.c-num{font-variant-numeric:tabular-nums}'
|
||||
+ '.c-pray{width:26px;text-align:center;font-size:13px}'
|
||||
+ 'tbody tr{break-inside:avoid}'
|
||||
+ '</style></head><body>'
|
||||
+ `<div class="hd"><h1>${esc(docTitle)}</h1>`
|
||||
+ `<p>${data.length} entries · pray for them by name</p></div>`
|
||||
+ `<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`
|
||||
+ '</body></html>',
|
||||
{ autoprint: true },
|
||||
);
|
||||
done(item, kind);
|
||||
} catch (e) { fail(item, kind, e); }
|
||||
}
|
||||
|
||||
// ── 3. THE CARD DECK ──────────────────────────────────────────────────────
|
||||
// One card per PAGE at the chosen stock, so the print dialog shows CARDS rather
|
||||
// than letter paper. The geometry lives in the shared card renderer.
|
||||
async function printCardDeck(item) {
|
||||
if (busy()) return;
|
||||
const kind = 'Card Deck';
|
||||
set(item, kind, 'fetch', 'fetching records', 30);
|
||||
try {
|
||||
const data = await rows(item);
|
||||
const [{ createCardRenderer }, strings] = await Promise.all([
|
||||
import('../packet/card-render.js'),
|
||||
opts.strings ? opts.strings() : import('../scripts/posters/packet-strings.json').then((m) => m.default || m),
|
||||
]);
|
||||
set(item, kind, 'render', `laying out ${data.length} cards`, 70);
|
||||
const R = createCardRenderer({ strings, lang: lang(), stock: stock() });
|
||||
openPrintFrame(R.buildDeck(item, data), { autoprint: true });
|
||||
done(item, kind);
|
||||
} catch (e) { fail(item, kind, e); }
|
||||
}
|
||||
|
||||
return { exportState, dismiss, printPoster, printList, printCardDeck };
|
||||
}
|
||||
|
|
@ -57,6 +57,8 @@
|
|||
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
|
||||
"check:card-deck": "node internal/check-card-deck.mjs",
|
||||
"check:poster-raster": "node internal/check-poster-raster.mjs",
|
||||
"check:poster-prints": "node internal/check-poster-prints.mjs",
|
||||
"check:provider-switch": "node internal/check-provider-switch.mjs",
|
||||
"check:mfe-two-tab": "node internal/check-mfe-two-tab.mjs",
|
||||
"check:share-policy": "node internal/check-share-policy.mjs",
|
||||
"check:zone-census": "node internal/check-zone-census.mjs",
|
||||
|
|
|
|||
|
|
@ -260,9 +260,36 @@ const NOT_EXPERIMENTAL = new Set([
|
|||
'template-bundle',
|
||||
'people-groups-grid',
|
||||
'research-dashboard',
|
||||
'big-picture'
|
||||
'big-picture',
|
||||
// roundup ruling (EXPERIMENTAL-MAPS-ROUNDUP.md, 2026-09-24): scaffolding and authoring
|
||||
// tooling, not maps -- the studio still surfaces them for developers.
|
||||
'demo-map',
|
||||
'golden-path-check',
|
||||
// doxa-research-map is the PART already composed into the Simple Research tab
|
||||
// (research-map-plus-grid) -- same redundancy rule as research-dashboard/big-picture.
|
||||
'doxa-research-map',
|
||||
// stack-demo is broken and Driver-DEFERRED; a broken tab in the giveaway shelf reads as
|
||||
// product damage. It returns when someone fixes it (a tracked issue).
|
||||
'stack-demo'
|
||||
])
|
||||
|
||||
// ── EXPERIMENTAL ORDER = KINGDOM IMPACT, not the alphabet ────────────────────────────────
|
||||
// The roundup charter orders the folder by "potential to get people TO the unreached groups
|
||||
// and help them meet and understand Jesus", finalized 2026-09-24 AFTER reading every map's
|
||||
// rendered output (EXPERIMENTAL-MAPS-ROUNDUP.md has the per-map reasoning). Rank is
|
||||
// EDITORIAL, the same class of decision as the curated TAB_DEF above -- which is why it
|
||||
// lives here and not in per-bundle meta: the manifest deliberately strips meta config, and
|
||||
// an order is a statement about the whole shelf, not about one bundle. Unranked bundles
|
||||
// still appear (discovery is never gated by this) -- they sort after the ranked, A-Z.
|
||||
const EXPERIMENTAL_RANK = {
|
||||
'prayer-walk-planner': 1, // feet on streets: zones -> printed one-page walking maps
|
||||
'upg-neighbor': 2, // the rescued legacy set; entry = send-map ("who goes, to whom")
|
||||
'key-cities': 3, // where the unreached physically live
|
||||
'all-people-groups': 4, // the census view that feeds everything else
|
||||
'country-posters': 5, // mobilization print artifacts
|
||||
'doxa-vision-card': 6, // vision framing
|
||||
}
|
||||
|
||||
// The tab manifest. Each entry CURATES one tab: which bundle+profile it shows, in what order, under
|
||||
// what human label. Every tab nests its plugin's REAL custom element — there is no iframe mode.
|
||||
//
|
||||
|
|
@ -570,8 +597,12 @@ function experimentalTabsFromManifest(manifest) {
|
|||
discovered: true // marks an auto-discovered tab (used by the rail + tests)
|
||||
})
|
||||
}
|
||||
// Stable, predictable order so the folder does not reshuffle between builds.
|
||||
out.sort((a, z) => a.label.localeCompare(z.label))
|
||||
// Ranked first (Kingdom impact, see EXPERIMENTAL_RANK), then A-Z -- stable between builds.
|
||||
out.sort((a, z) => {
|
||||
const ra = EXPERIMENTAL_RANK[a.id] || 999
|
||||
const rz = EXPERIMENTAL_RANK[z.id] || 999
|
||||
return ra !== rz ? ra - rz : a.label.localeCompare(z.label)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
* element (same double-emit pattern as people-groups-grid.vue's `groups-filtered`), so a plain
|
||||
* host page hears it standalone and a Vue-nesting shell hears it as a prop-less DOM event.
|
||||
*/
|
||||
import { inject, ref, computed, onMounted, getCurrentInstance } from 'vue'
|
||||
import { inject, ref, computed, onMounted, watch, getCurrentInstance } from 'vue'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { splitLastWord } from './src/useLastWordHighlight.js'
|
||||
import { useRegionsI18n } from './src/useRegionsI18n.js'
|
||||
|
|
@ -102,7 +102,7 @@ const isRTL = computed(() => RTL_LOCALES.has(i18nLocale.value))
|
|||
// We rendered only the region cards, so the standalone leaf page had no title and no strapline —
|
||||
// invisible inside the composite, where the tab strip names the view, and plainly missing on
|
||||
// /doxa-maps/regions-dashboard/regions-list/index.html, which is a page in its own right.
|
||||
const { t } = useRegionsI18n()
|
||||
const { t, tf } = useRegionsI18n()
|
||||
const headTitle = computed(() => splitLastWord(t('Regions')))
|
||||
|
||||
// ★ REGISTER BEBAS NEUE AT DOCUMENT LEVEL, not in the shadow root: @font-face is document-scoped
|
||||
|
|
@ -135,7 +135,18 @@ function formatNumber(n) {
|
|||
}
|
||||
|
||||
function summaryLine(region) {
|
||||
return `${formatNumber(region.count)} people groups in ${formatNumber(region.countryCount)} countries`
|
||||
// THEIRS, pages/regions/index.vue:29-31:
|
||||
// t('%count% people groups in %countries% countries')
|
||||
// .replace('%count%', formatNumber(region.count))
|
||||
// .replace('%countries%', formatNumber(region.countryCount))
|
||||
// This was a hardcoded English template, so /ru/regions rendered "315 people groups in 41
|
||||
// countries" while theirs reads "315 групп людей в 41 странах". The ru string was ALREADY in
|
||||
// library/i18n/locales/ru/regions.json — copied, never wired. Caught by reading the Russian
|
||||
// screenshot, not by any assertion.
|
||||
return tf('%count% people groups in %countries% countries', {
|
||||
count: formatNumber(region.count),
|
||||
countries: formatNumber(region.countryCount),
|
||||
})
|
||||
}
|
||||
|
||||
// ── emit (Vue + native composed CustomEvent, same pattern as people-groups-grid) ────────────
|
||||
|
|
@ -171,7 +182,10 @@ function selectCountry(country, region) {
|
|||
// "See Africa dashboard" / "See Asia dashboard" — same wording pattern as DX-life's
|
||||
// dashboardLink() in app/pages/regions/index.vue (t('See %region% dashboard')).
|
||||
function dashboardLink(region) {
|
||||
return `See ${region.label || 'Other'} dashboard`
|
||||
// THEIRS: t('See %region% dashboard') — ru renders "Открыть панель: %region%", which is not
|
||||
// even the same word order, so a hardcoded English template could never be patched into it.
|
||||
// The translation was already catalogued; only the wiring was missing.
|
||||
return tf('See %region% dashboard', { region: region.label || t('Other') })
|
||||
}
|
||||
|
||||
// ── data fetch (same live endpoint + fields as people-groups-grid.vue) ──────────────────────
|
||||
|
|
@ -259,6 +273,14 @@ function onPosterShowOnMap(ev) {
|
|||
}
|
||||
}
|
||||
|
||||
// A LOCALE CHANGE MUST REFETCH, not just re-render. The region and country LABELS come from
|
||||
// the API's ?lang=, not from the i18n catalogue — MEASURED on their live site, which serves
|
||||
// "Азия"/"Африка"/"Азербайджан (3)" on /ru/regions against "Africa"/"Angola" on /regions. With
|
||||
// no watcher this tab kept whatever labels the first fetch returned, so switching to Russian
|
||||
// translated the chrome and left every region and country name in English. region-dashboards.vue
|
||||
// already had this watcher (line 541); this tab was simply missed.
|
||||
watch(languageCode, () => { loadRegions() })
|
||||
|
||||
onMounted(() => { if (typeof window !== 'undefined') window.addEventListener(POSTER_EVENT, onPosterShowOnMap) })
|
||||
onBeforeUnmount(() => { if (typeof window !== 'undefined') window.removeEventListener(POSTER_EVENT, onPosterShowOnMap) })
|
||||
|
||||
|
|
@ -322,9 +344,17 @@ useShadowStyles(
|
|||
// 7vw, --font-size-5xl) and the subtitle is --font-size-md in --color-brand-lighter.
|
||||
// cqw not vw, for the same composite-pane reason as every other clamp here.
|
||||
'.rl-head{ display:flex; flex-direction:column; gap:10px; text-align:center; }' +
|
||||
// NO text-transform. MEASURED on their live h1, both locales: text-transform is `none`
|
||||
// (/regions and /ru/regions alike). This rule used to force uppercase, which is INVISIBLE in
|
||||
// English because Bebas Neue is an all-caps face — and wrong the moment the text is not Latin:
|
||||
// Bebas carries no Cyrillic, so /ru falls back to a normal system face and their heading reads
|
||||
// "Регионы" while ours read "РЕГИОНЫ". The same conclusion is already written out for
|
||||
// .region-card__title below ("DX-life applies NO transform on top of the font"); this rule was
|
||||
// simply missed, and no check caught it because the vs-live property diff covers the DASHBOARD
|
||||
// tab's .rd-title, never this one. _probe-list-vs-live.mjs now covers it.
|
||||
'.rl-title{ margin:0; font-size:clamp(47.6837px, 7cqw, 74.5058px); line-height:1;' +
|
||||
' font-family:"Bebas Neue","Arial Narrow",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;' +
|
||||
' font-weight:400; text-transform:uppercase; }' +
|
||||
' font-weight:400; }' +
|
||||
'.rl-title__last{ color:#92b195; }' +
|
||||
'.rl-summary{ margin:0; color:var(--dx-muted); font-size:19.5312px; }' +
|
||||
'.rl-status{ padding:28px; text-align:center; color:var(--dx-muted); font-size:19.5312px; }' +
|
||||
|
|
@ -419,7 +449,10 @@ useShadowStyles(
|
|||
<h1 class="rl-title">{{ headTitle.head ? headTitle.head + ' ' : '' }}<span class="rl-title__last">{{ headTitle.last }}</span></h1>
|
||||
<p class="rl-summary">{{ t('Find unreached people groups by region and country.') }}</p>
|
||||
</div>
|
||||
<div v-if="loading" class="rl-status">Loading regions…</div>
|
||||
<!-- THEIR string, not a new one: 'Loading results...' is in their common.json and so is
|
||||
already translated in all eight locales. Their /regions is server-rendered and has no
|
||||
loading state of its own, but reusing their wording beats inventing English here. -->
|
||||
<div v-if="loading" class="rl-status">{{ t('Loading results...') }}</div>
|
||||
<div v-else-if="loadFailed" class="rl-status rl-failed" role="status">
|
||||
Region data could not be loaded. This is a connection problem, not a filter.
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
/**
|
||||
* upg-neighbor.vue — LEGACY-WRAPPER bundle (experimental roundup).
|
||||
*
|
||||
* @description Rescues the three pre-bundler experimental maps that live inside the legacy
|
||||
* upg-neighbor IIFE — send-map, flower-mesh and lang-cluster — without rebuilding them and
|
||||
* @description Rescues the five pre-bundler experimental maps that live inside the legacy
|
||||
* upg-neighbor IIFE — send-map, the upg-neighbors/bloom cockpit, lang-cluster and flower-mesh — without rebuilding them and
|
||||
* without letting their old engine touch the modern bundles: each profile mounts its element
|
||||
* inside a sandboxed IFRAME whose host page (assets/legacy/upg-neighbor/index.html) loads the
|
||||
* prebuilt script, shims its relative /api fetches to the live API, and passes the token.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <country-posters-map> custom element. -->
|
||||
<script src="../country-posters.js" integrity="sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI" crossorigin="anonymous"></script>
|
||||
<script src="../country-posters.js" integrity="sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
175
public/doxa-maps/country-posters/country-posters.js
generated
175
public/doxa-maps/country-posters/country-posters.js
generated
File diff suppressed because one or more lines are too long
16
public/doxa-maps/doxa-maps-spa/doxa-maps-spa.js
generated
16
public/doxa-maps/doxa-maps-spa/doxa-maps-spa.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
|
||||
<script src="../../doxa-maps-spa.js" integrity="sha384-Ce+DKsLgwfej+rgoWeaWFuMMbR3Y4H+8io/lVL+lQfX0O3iGKwDLf4remDkHGCrP" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-maps-spa.js" integrity="sha384-1f+/rBSGo/1jyUmRmGg34CIU/PcGkB7M3jah1DO0ocb5O0zj81wpUemXcz4WAFz8" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
|
||||
<script src="../doxa-maps-spa.js" integrity="sha384-Ce+DKsLgwfej+rgoWeaWFuMMbR3Y4H+8io/lVL+lQfX0O3iGKwDLf4remDkHGCrP" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-maps-spa.js" integrity="sha384-1f+/rBSGo/1jyUmRmGg34CIU/PcGkB7M3jah1DO0ocb5O0zj81wpUemXcz4WAFz8" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
71
public/doxa-maps/key-cities/key-cities.js
generated
71
public/doxa-maps/key-cities/key-cities.js
generated
File diff suppressed because one or more lines are too long
22
public/doxa-maps/manifest.json
generated
22
public/doxa-maps/manifest.json
generated
|
|
@ -154,7 +154,7 @@
|
|||
"bundle": "country-posters",
|
||||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"output": "doxa-maps/country-posters/country-posters.js",
|
||||
"integrity": "sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI",
|
||||
"integrity": "sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF",
|
||||
"elements": [
|
||||
"country-posters-map"
|
||||
],
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/countries-map/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=\"./country-posters/country-posters.js\" integrity=\"sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"countries-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -188,7 +188,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-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=\"./country-posters/country-posters.js\" integrity=\"sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -202,7 +202,7 @@
|
|||
"scriptUrl": "./country-posters/country-posters.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./country-posters/country-posters-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=\"./country-posters/country-posters.js\" integrity=\"sha384-pWCBnzi9A8axrT5aW4DjigzZ8utiQeU+OVsOIKVKA9vC61ArtxaRxjqQ8l5xNxzI\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./country-posters/country-posters.js\" integrity=\"sha384-KCb+2VIX0x0RZlPudE1oeY64Wp7UPNmJo8Zx7EkADp5+BYHfB2KsRlzS6ssYZMIF\" crossorigin=\"anonymous\"></script>\n<country-posters-map profile-config='{\"profile\":\"country-posters-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></country-posters-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -298,7 +298,7 @@
|
|||
"bundle": "doxa-maps-spa",
|
||||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"output": "doxa-maps/doxa-maps-spa/doxa-maps-spa.js",
|
||||
"integrity": "sha384-Ce+DKsLgwfej+rgoWeaWFuMMbR3Y4H+8io/lVL+lQfX0O3iGKwDLf4remDkHGCrP",
|
||||
"integrity": "sha384-1f+/rBSGo/1jyUmRmGg34CIU/PcGkB7M3jah1DO0ocb5O0zj81wpUemXcz4WAFz8",
|
||||
"elements": [
|
||||
"doxa-maps-spa"
|
||||
],
|
||||
|
|
@ -322,7 +322,7 @@
|
|||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/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=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-Ce+DKsLgwfej+rgoWeaWFuMMbR3Y4H+8io/lVL+lQfX0O3iGKwDLf4remDkHGCrP\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-1f+/rBSGo/1jyUmRmGg34CIU/PcGkB7M3jah1DO0ocb5O0zj81wpUemXcz4WAFz8\" crossorigin=\"anonymous\"></script>\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
@ -856,7 +856,7 @@
|
|||
"bundle": "regions-dashboard",
|
||||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"output": "doxa-maps/regions-dashboard/regions-dashboard.js",
|
||||
"integrity": "sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS",
|
||||
"integrity": "sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix",
|
||||
"elements": [
|
||||
"regions-dashboard"
|
||||
],
|
||||
|
|
@ -876,7 +876,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -890,7 +890,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-list/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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -904,7 +904,7 @@
|
|||
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./regions-dashboard/regions-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=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix\" crossorigin=\"anonymous\"></script>\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1076,7 +1076,7 @@
|
|||
{
|
||||
"profile": "upg-neighbor",
|
||||
"file": "upg-neighbor.vue",
|
||||
"description": "Rescues the three pre-bundler experimental maps that live inside the legacy",
|
||||
"description": "Rescues the five pre-bundler experimental maps that live inside the legacy",
|
||||
"element": "upg-neighbor",
|
||||
"type": "parameterized",
|
||||
"isMap": false,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
115
public/doxa-maps/regions-dashboard/regions-dashboard.js
generated
115
public/doxa-maps/regions-dashboard/regions-dashboard.js
generated
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
|
||||
<script src="../regions-dashboard.js" integrity="sha384-BHIpCnisN9qu6gdJpslS3Q59xknjMZfO4AL3tNe6jbYfdg3CShiTaVWQYv5KyidS" crossorigin="anonymous"></script>
|
||||
<script src="../regions-dashboard.js" integrity="sha384-tj7QLhUCvAluRlp9Bp2ddjbXLcknRx2v1nMotvvY8XKImLplx6WkSdHh5MQC1Rix" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue