check:experimental-shelf: every experimental plugin, measured working, and two label fonts that 404'd
The Driver asked for "all experimental plugins working"; this makes it a measurement. The gate derives the shelf from the Research Gallery's own rules (manifest minus curated tabs and NOT_EXPERIMENTAL), then checks every bundle on its leaf page and on its Gallery tab, under mapbox and maplibre. It requires content inside the plugin's own element, 0 page errors, 0 failed requests, no loading text, and for each map 'load' fired AND its own GeoJSON data drawn. An empty source does not count. Maps are found by wrapping the engine's Map class in an init script, so no bundle changes for it. It serves this checkout on an OS-assigned port, and self-tests first: a deliberately broken bundle must fail, or it exits 3. Prayer Walk Planner's data is the user's own (zones in localStorage), so the gate seeds one zone and requires it drawn, printing the seed and reason on every run rather than exempting the bundle. It found two real defects on the way. Both are fixed here, and both failed the gate before the fix: - UPG Neighbor: every label layer asked MapLibre's default fonts (or Mapbox's DIN) from OpenFreeMap, which 404s, so no label drew under maplibre. labelFont() now picks Noto Sans under MapLibre; Mapbox is unchanged. - Prayer Walk Planner (always MapLibre): zone titles never drew, for the same reason. The label layer now names Noto Sans Regular. Result: 28/28 PASS across all 7 shelf bundles.
This commit is contained in:
parent
0eae38b20a
commit
b1e0c7fa61
20 changed files with 279 additions and 68 deletions
185
internal/check-experimental-shelf.mjs
Normal file
185
internal/check-experimental-shelf.mjs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-experimental-shelf — "all experimental plugins working", as a measurement.
|
||||
*
|
||||
* THE SHELF is derived, never listed: every bundle in the built manifest that the Research Gallery
|
||||
* would discover into its Experimental folder, i.e. not a curated tab and not in NOT_EXPERIMENTAL.
|
||||
* Both sets are read from plugin-designs/doxa-maps-spa/doxa-maps-spa.vue, so a bundle that joins or
|
||||
* leaves the shelf changes what this gate measures without an edit here.
|
||||
*
|
||||
* PER BUNDLE, PER ENGINE (mapbox, maplibre), on TWO surfaces: its own leaf page (A) and its tab
|
||||
* inside the Research Gallery (D). A surface passes when:
|
||||
* - the element mounted with content (its shadow tree has real nodes),
|
||||
* - 0 page errors and 0 failed requests (4xx/5xx; Mapbox telemetry and favicons excluded),
|
||||
* - no loading text is left on screen (Loading / Adding / Computing),
|
||||
* - and, for every MAP on the page: its 'load' fired, and it DRAWS its own data, meaning at
|
||||
* least one rendered feature on a layer fed by a GeoJSON source. That is the lesson from the
|
||||
* PPLR check: 'load' alone is not loaded, and an empty GeoJSON source still counts as a
|
||||
* source.
|
||||
* Maps are found without touching any bundle: an init script wraps the engine's Map class as the
|
||||
* engine script defines it, so every map the page constructs is recorded.
|
||||
*
|
||||
* IDENTITY: the gate serves THIS checkout's public/ itself, on a port the OS hands out, so it
|
||||
* cannot grade another worktree's tree (the fixed-port trap internal/_serves-this-tree.mjs names).
|
||||
*
|
||||
* SELF-TEST (--self-test, and always first unless --no-self-test): the first shelf bundle's script
|
||||
* is replaced in flight by one that throws. That bundle MUST fail on both surfaces; if it does not,
|
||||
* the gate is blind and exits 3 before grading anything.
|
||||
*
|
||||
* Usage: node internal/check-experimental-shelf.mjs [--engines mapbox,maplibre] [--only a,b]
|
||||
* [--no-self-test] [--self-test]
|
||||
* Exit: 0 all pass · 1 a bundle failed · 3 self-test could not see a broken bundle · 2 setup.
|
||||
* Run browser gates through internal/browser-slot.sh.
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:net'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const argv = process.argv.slice(2)
|
||||
const arg = (n, d) => { const i = argv.indexOf(n); return i >= 0 && argv[i + 1] ? argv[i + 1] : d }
|
||||
const ENGINES = arg('--engines', 'mapbox,maplibre').split(',')
|
||||
const ONLY = arg('--only', '') ? arg('--only', '').split(',') : null
|
||||
const SELF_TEST = !argv.includes('--no-self-test')
|
||||
const WAIT_MS = Number(process.env.SHELF_WAIT_MS || 75000)
|
||||
|
||||
const { chromium } = await import(resolve(ROOT, 'node_modules/playwright/index.mjs'))
|
||||
const manifest = JSON.parse(readFileSync(resolve(ROOT, 'public/doxa-maps/manifest.json'), 'utf8'))
|
||||
const spa = readFileSync(resolve(ROOT, 'plugin-designs/doxa-maps-spa/doxa-maps-spa.vue'), 'utf8')
|
||||
const cfg = JSON.parse(readFileSync(resolve(ROOT, 'vite.doxa-maps-build-config.json'), 'utf8'))
|
||||
const TK = process.env.MAPBOX_TOKEN || cfg.token || ''
|
||||
|
||||
// ── the shelf, derived from the Gallery's own rules ───────────────────────────
|
||||
const notExp = new Set((spa.match(/const NOT_EXPERIMENTAL = new Set\(\[([\s\S]*?)\]\)/) || [, ''])[1].match(/'([a-z0-9-]+)'/g)?.map(s => s.slice(1, -1)) || [])
|
||||
const curated = new Set([...spa.matchAll(/bundle:\s*'([a-z0-9-]+)'/g)].map(m => m[1]))
|
||||
if (!notExp.size || !curated.size) { console.error('check-experimental-shelf: could not read NOT_EXPERIMENTAL / curated tabs from doxa-maps-spa.vue'); process.exit(2) }
|
||||
const shelf = manifest.bundles
|
||||
.filter(b => !notExp.has(b.bundle) && !curated.has(b.bundle) && (b.profiles || []).length)
|
||||
.filter(b => !ONLY || ONLY.includes(b.bundle))
|
||||
.map(b => {
|
||||
// the entry profile the Gallery opens: a map profile, else the one named like the bundle, else the first
|
||||
const p = b.profiles.find(x => x.isMap) || b.profiles.find(x => x.profile === b.bundle) || b.profiles[0]
|
||||
return { bundle: b.bundle, profile: p.profile, isMap: !!p.isMap, tag: p.element || (b.elements || [])[0] || b.bundle }
|
||||
})
|
||||
if (!shelf.length) { console.error('check-experimental-shelf: the shelf is empty'); process.exit(2) }
|
||||
|
||||
// ── SEEDS: bundles whose data is the USER'S, so a fresh browser correctly shows none ─────────
|
||||
// Seeded before load, then required to be DRAWN, which is stronger than exempting them. Printed on
|
||||
// every run with the reason, so a seed can never quietly hide a regression.
|
||||
const SEED = {
|
||||
'prayer-walk-planner': {
|
||||
why: 'walk zones are user-drawn and kept in localStorage; a fresh browser has none, so one known zone is seeded and must be drawn',
|
||||
keyPrefix: 'prayer-walk-planner:v1:',
|
||||
value: JSON.stringify([{ id: 'shelf-seed', title: 'Shelf check zone', coords: [[0, 10], [20, 10], [20, 25], [0, 25]] }]),
|
||||
},
|
||||
}
|
||||
|
||||
// ── our own server, on a port nobody else can be holding ──────────────────────
|
||||
const port = await new Promise(ok => { const s = createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => ok(p)) }) })
|
||||
const srv = spawn(process.execPath, [resolve(ROOT, 'internal/serve-strict.mjs'), '--port', String(port), '--directory', 'public'], { cwd: ROOT, stdio: 'ignore' })
|
||||
const reap = () => { try { srv.kill() } catch (_) {} }
|
||||
process.on('exit', reap); for (const s of ['SIGINT', 'SIGTERM']) process.on(s, () => { reap(); process.exit(130) })
|
||||
const BASE = `http://127.0.0.1:${port}/doxa-maps`
|
||||
for (let i = 0; i < 40; i++) { try { if ((await fetch(BASE + '/manifest.json')).ok) break } catch (_) {} await new Promise(r => setTimeout(r, 250)) }
|
||||
|
||||
// ── every Map the page builds, recorded without touching the bundle ───────────
|
||||
const RECORD_MAPS = () => {
|
||||
window.__shelfMaps = []
|
||||
for (const name of ['mapboxgl', 'maplibregl']) {
|
||||
let v
|
||||
Object.defineProperty(window, name, {
|
||||
configurable: true,
|
||||
get() { return v },
|
||||
set(val) {
|
||||
if (val && val.Map && !val.Map.__shelfWrapped) {
|
||||
const Orig = val.Map
|
||||
class Recorded extends Orig { constructor(...a) { super(...a); window.__shelfMaps.push(this); this.once('load', () => { this.__shelfLoaded = true }) } }
|
||||
Recorded.__shelfWrapped = true
|
||||
try { val.Map = Recorded } catch (_) { try { Object.defineProperty(val, 'Map', { value: Recorded }) } catch (_) {} }
|
||||
}
|
||||
v = val
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
const PROBE = (tag) => `(() => {
|
||||
const acc = []; const w = (r) => { for (const el of r.querySelectorAll('*')) { acc.push(el); if (el.shadowRoot) w(el.shadowRoot) } }; w(document)
|
||||
// CONTENT is the plugin's own subtree, not the page: a small card on a bare leaf and a card inside the
|
||||
// Gallery's chrome must be judged the same way.
|
||||
const host = acc.find(e => e.tagName === ${JSON.stringify('__TAG__')}.toUpperCase())
|
||||
let own = 0, sized = false
|
||||
if (host) { const inner = []; const w2 = (r) => { for (const el of r.querySelectorAll('*')) { inner.push(el); if (el.shadowRoot) w2(el.shadowRoot) } }; w2(host.shadowRoot || host); own = inner.length
|
||||
const hr = host.getBoundingClientRect(); sized = hr.width > 100 && hr.height > 100 }
|
||||
const busy = acc.filter(e => e.children.length === 0 && /Loading|Adding|Computing/i.test(e.textContent || '') && e.getBoundingClientRect().height > 0).length
|
||||
const maps = (window.__shelfMaps || []).filter(m => { try { return m.getContainer() && m.getContainer().isConnected } catch (_) { return false } }).map(m => {
|
||||
let drawn = 0, geo = 0
|
||||
try {
|
||||
const st = m.getStyle(); const srcs = st.sources || {}
|
||||
const geoIds = Object.keys(srcs).filter(k => srcs[k].type === 'geojson'); geo = geoIds.length
|
||||
const layers = (st.layers || []).filter(l => geoIds.includes(l.source)).map(l => l.id)
|
||||
if (layers.length) drawn = m.queryRenderedFeatures({ layers }).length
|
||||
} catch (_) {}
|
||||
return { loaded: !!(m.__shelfLoaded || (m.isStyleLoaded && m.isStyleLoaded())), geo, drawn }
|
||||
})
|
||||
return { own, sized, host: !!host, busy, maps }
|
||||
})()`.replace('__TAG__', tag)
|
||||
|
||||
const b = await chromium.launch({ headless: true, args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--ignore-gpu-blocklist', '--enable-webgl', '--no-sandbox'] })
|
||||
|
||||
async function measure(url, s, poisonBundle) {
|
||||
const p = await b.newPage({ viewport: { width: 1280, height: 800 } })
|
||||
await p.addInitScript(RECORD_MAPS)
|
||||
const seed = SEED[s.bundle]
|
||||
if (seed) await p.addInitScript(({ keyPrefix, value }) => {
|
||||
const get = Storage.prototype.getItem
|
||||
Storage.prototype.getItem = function (k) { const v = get.call(this, k); return (v == null && String(k).startsWith(keyPrefix)) ? value : v }
|
||||
}, seed)
|
||||
if (poisonBundle) await p.route(new RegExp(`/${poisonBundle}/${poisonBundle}\\.js(\\?|$)`), r => r.fulfill({ status: 200, contentType: 'text/javascript', body: 'throw new Error("shelf self-test: deliberately broken bundle")' }))
|
||||
const errs = [], fails = []
|
||||
p.on('pageerror', e => errs.push(e.message.slice(0, 140)))
|
||||
p.on('response', r => { if (r.status() >= 400 && !/events\.mapbox\.com|favicon/.test(r.url())) fails.push(r.status() + ' ' + r.url().replace(/\?.*/, '').slice(-80)) })
|
||||
await p.goto(url)
|
||||
let st = null
|
||||
const t0 = Date.now()
|
||||
while (Date.now() - t0 < WAIT_MS) {
|
||||
await p.waitForTimeout(2500)
|
||||
st = await p.evaluate(PROBE(s.tag))
|
||||
const mapsReady = st.maps.every(m => m.loaded && m.drawn > 0)
|
||||
if (st.own > 8 && st.sized && st.busy === 0 && mapsReady && Date.now() - t0 > 6000) break
|
||||
}
|
||||
await p.close()
|
||||
const why = []
|
||||
if (!st || !st.host) why.push(`<${s.tag}> never appeared`)
|
||||
else if (st.own <= 8 || !st.sized) why.push(`<${s.tag}> mounted empty (${st.own} node(s)${st.sized ? '' : ', not sized'})`)
|
||||
if (st && st.busy) why.push(`${st.busy} loading label(s) left`)
|
||||
if (st) st.maps.forEach((m, i) => { if (!m.loaded) why.push(`map ${i + 1}: 'load' never fired`); else if (!m.drawn) why.push(`map ${i + 1}: no own data drawn (${m.geo} geojson source(s))`) })
|
||||
if (errs.length) why.push(`page error: ${errs[0]}`)
|
||||
if (fails.length) why.push(`failed request: ${fails[0]}`)
|
||||
return { pass: !why.length, why, maps: st ? st.maps.length : 0 }
|
||||
}
|
||||
|
||||
const leaf = (s, eng) => `${BASE}/${s.bundle}/${s.profile}/index.html?provider=${eng}` + (eng === 'mapbox' ? `&tk=${TK}` : '')
|
||||
const gallery = (s, eng) => `${BASE}/doxa-maps-spa/doxa-maps-spa/index.html?tab=${s.bundle}&provider=${eng}` + (eng === 'mapbox' ? `&tk=${TK}` : '')
|
||||
|
||||
let exit = 0
|
||||
try {
|
||||
if (SELF_TEST) {
|
||||
const s = shelf[0]
|
||||
const a = await measure(leaf(s, 'maplibre'), s, s.bundle), d = await measure(gallery(s, 'maplibre'), s, s.bundle)
|
||||
if (a.pass || d.pass) { console.error(`SELF-TEST FAILED: a deliberately broken ${s.bundle} still passed (${a.pass ? 'leaf' : ''} ${d.pass ? 'gallery' : ''}). This gate cannot see a broken bundle.`); exit = 3 }
|
||||
else console.log(`self-test: a deliberately broken ${s.bundle} fails as it must (leaf: ${a.why[0]}; gallery: ${d.why[0]})`)
|
||||
}
|
||||
if (exit !== 3) {
|
||||
for (const [k, v] of Object.entries(SEED)) if (shelf.some(s => s.bundle === k)) console.log(`SEEDED ${k}: ${v.why}`)
|
||||
console.log(`EXPERIMENTAL SHELF — ${shelf.length} bundle(s): ${shelf.map(s => s.bundle).join(', ')} · engines: ${ENGINES.join(', ')}`)
|
||||
for (const s of shelf) for (const eng of ENGINES) for (const [surf, url] of [['A leaf', leaf(s, eng)], ['D gallery', gallery(s, eng)]]) {
|
||||
const r = await measure(url, s)
|
||||
if (!r.pass) exit = 1
|
||||
console.log(`${r.pass ? 'PASS' : 'FAIL'} ${s.bundle.padEnd(24)} ${eng.padEnd(9)} ${surf.padEnd(10)} ${r.maps ? r.maps + ' map(s)' : 'no map'}${r.pass ? '' : ' — ' + r.why.join('; ')}`)
|
||||
}
|
||||
console.log(exit ? 'EXPERIMENTAL-SHELF: FAIL' : `EXPERIMENTAL-SHELF: PASS — every experimental plugin loads on its own page and in the Gallery, under ${ENGINES.join(' and ')}`)
|
||||
}
|
||||
} finally { await b.close(); reap() }
|
||||
process.exit(exit)
|
||||
|
|
@ -201,7 +201,8 @@
|
|||
"check:leaves-mount:poison": "node internal/check-gospel-leaves-mount.mjs --poison",
|
||||
"check:gospel-all": "node internal/check-gospel-all.mjs",
|
||||
"check:no-conflict-markers": "node internal/check-no-conflict-markers.mjs",
|
||||
"check:no-conflict-markers:poison": "node internal/check-no-conflict-markers.mjs --poison"
|
||||
"check:no-conflict-markers:poison": "node internal/check-no-conflict-markers.mjs --poison",
|
||||
"check:experimental-shelf": "node internal/check-experimental-shelf.mjs"
|
||||
},
|
||||
"//scripts": {
|
||||
"_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.",
|
||||
|
|
@ -304,7 +305,8 @@
|
|||
"check:gallery-embed:live-cors": "the Research Gallery on a foreign page under the LIVE static layer's CORS rule (Access-Control-Allow-Origin on files inside the tree's folders, NOT on the root manifest.json, measured on nuxtbase): it must still boot every tab, by reading the CORS-readable manifest copy generate-tree writes beside its bundle.",
|
||||
"check:leaves-mount": "DID THE ELEMENT ACTUALLY MOUNT, on all five gospel leaves. Exists because a ReferenceError thrown during setup dies INSIDE the custom-element boundary: the page loads, every asset returns 200, pageerror fires zero times, and the element sits there with an empty shadow root. A healthy-looking dead page. This asks the cheap unambiguous question the rich gates are too slow to ask on every leaf — is anything there — in five steps that each name their own failure: tag defined, host present, shadowRoot attached, shadow root non-empty, visible text rendered. It does NOT trust the absence of console errors; zero errors is not the same as working. --poison for the negative test.",
|
||||
"check:gospel-all": "EVERYTHING gospel-resources owns, in one command. Its ten gates were all hand-run, which is how a dead bundle shipped: a duplicate module killed all five leaves and the one gate run that day was pointed at the map. check:no-orphan-gates was right that they were reachable — reachable is not run. Deliberately NOT in gates:pre/post: three need a browser, the shared wave holds browser gates under load, and a bundle owner should not be able to slow everyone else's build by adding to a list they do not own. Static gates run first and stop the run on failure, so a scarce browser slot is never spent confirming a table is inconsistent. Exit 75 = NOT MEASURED, never folded into a pass.",
|
||||
"check:no-conflict-markers": "Nothing tracked may carry an unresolved merge. Exists because a merge from this worktree committed a BUILT bundle with 21 conflict markers - both sides concatenated, 1.13MB - and every gospel leaf rendered dead while the SRI hash matched and every request returned 200. The cause was a landing script that resolved one expected file then ran \"git add -A public\", staging every other file including the ones still holding markers. For a built file: take either side, REBUILD, commit the rebuild, then run this before pushing. --poison for the negative test."
|
||||
"check:no-conflict-markers": "Nothing tracked may carry an unresolved merge. Exists because a merge from this worktree committed a BUILT bundle with 21 conflict markers - both sides concatenated, 1.13MB - and every gospel leaf rendered dead while the SRI hash matched and every request returned 200. The cause was a landing script that resolved one expected file then ran \"git add -A public\", staging every other file including the ones still holding markers. For a built file: take either side, REBUILD, commit the rebuild, then run this before pushing. --poison for the negative test.",
|
||||
"check:experimental-shelf": "Every Experimental-folder bundle, leaf + Gallery tab, mapbox + maplibre: mounts with content, 0 errors / failed requests, maps load and DRAW their own data. Self-tests on a deliberately broken bundle first. Run through internal/browser-slot.sh."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/android": "^7.6.9",
|
||||
|
|
|
|||
|
|
@ -230,6 +230,9 @@ function setupMapLayers() {
|
|||
map.value.addLayer({
|
||||
id: 'pwp-polygons-label', type: 'symbol', source: POLY_SRC,
|
||||
layout: {
|
||||
// A font OpenFreeMap's glyph server has. With no text-font, MapLibre asks for its default
|
||||
// (Open Sans / Arial Unicode MS), which OpenFreeMap 404s, and no zone title ever drew.
|
||||
'text-font': ['Noto Sans Regular'],
|
||||
'text-field': ['get', 'title'],
|
||||
'text-size': 12,
|
||||
'text-anchor': 'center'
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
* token-less via the gated host hook). Lives inside the doxa-upg-neighbors merged profile
|
||||
* as the "Flowers" view; reuses the granular per-PG coords from UPGN-MERGE.
|
||||
*/
|
||||
import { labelFont } from '../labelFont.js'
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -222,7 +223,7 @@ function paint() {
|
|||
})
|
||||
if (!m.getLayer(L_CORE_LBL)) m.addLayer({
|
||||
id: L_CORE_LBL, type: 'symbol', source: SRC_CORE, minzoom: 2.2,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.4], 'text-anchor': 'top',
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.4], 'text-anchor': 'top',
|
||||
'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': C_RED_DK, 'text-halo-color': '#fff', 'text-halo-width': 1.4 }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* (this child's own <style> would be dropped by defineCustomElement).
|
||||
*/
|
||||
|
||||
import { labelFont } from '../labelFont.js'
|
||||
import { inject, ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -197,7 +198,7 @@ function ensureLayers(m) {
|
|||
})
|
||||
if (!m.getLayer(L_GREEN_LBL)) m.addLayer({
|
||||
id: L_GREEN_LBL, type: 'symbol', source: SRC_GREEN, minzoom: 3.4,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 10, 'text-offset': [0, 1.1],
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 10, 'text-offset': [0, 1.1],
|
||||
'text-anchor': 'top', 'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': '#0b5e33', 'text-halo-color': '#ffffff', 'text-halo-width': 1.3 }
|
||||
})
|
||||
|
|
@ -212,7 +213,7 @@ function ensureLayers(m) {
|
|||
})
|
||||
if (!m.getLayer(L_RED_LBL)) m.addLayer({
|
||||
id: L_RED_LBL, type: 'symbol', source: SRC_RED, minzoom: 2.4,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.2],
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.2],
|
||||
'text-anchor': 'top', 'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': COLORS.RED_DK, 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 }
|
||||
})
|
||||
|
|
|
|||
17
plugin-designs/upg-neighbor/src/labelFont.js
Normal file
17
plugin-designs/upg-neighbor/src/labelFont.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* labelFont.js — a text-font the map's own glyph server actually has.
|
||||
*
|
||||
* A symbol layer with no text-font asks for the engine default ("Open Sans Regular, Arial Unicode MS
|
||||
* Regular"), and the bold labels here named Mapbox's "DIN Offc Pro Bold". Mapbox serves both.
|
||||
* OpenFreeMap, the basemap under provider=maplibre, serves neither: measured, the glyph request is
|
||||
* a 404 and the labels never draw. So under MapLibre the labels use Noto Sans, which it does serve;
|
||||
* under Mapbox nothing changes.
|
||||
*
|
||||
* Spread into a layer's layout: layout: { 'text-field': …, ...labelFont(m) }
|
||||
*/
|
||||
export function labelFont(map, bold = false) {
|
||||
const ml = typeof window !== 'undefined' && window.maplibregl
|
||||
const isMaplibre = !!(ml && ml.Map && map instanceof ml.Map)
|
||||
if (isMaplibre) return { 'text-font': [bold ? 'Noto Sans Bold' : 'Noto Sans Regular'] }
|
||||
return bold ? { 'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'] } : {}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
* "dataUrl":"<reached-neighbors.json>", "instanceId":"..." }
|
||||
*/
|
||||
|
||||
import { labelFont } from './labelFont.js'
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -318,7 +319,7 @@ function paintAll() {
|
|||
})
|
||||
if (!m.getLayer(L_CL_LABEL)) m.addLayer({
|
||||
id: L_CL_LABEL, type: 'symbol', source: SRC_CL, minzoom: 2.6,
|
||||
layout: { 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.3],
|
||||
layout: { ...labelFont(m), 'text-field': ['get', 'label'], 'text-size': 11, 'text-offset': [0, 1.3],
|
||||
'text-anchor': 'top', 'text-allow-overlap': false, 'text-optional': true },
|
||||
paint: { 'text-color': C_RED_DK, 'text-halo-color': '#ffffff', 'text-halo-width': 1.4 }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
* }
|
||||
*/
|
||||
|
||||
import { labelFont } from './labelFont.js'
|
||||
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -308,7 +309,7 @@ function syncLayers() {
|
|||
id: L_SENDER_LABEL, type: 'symbol', source: SRC_SENDERS,
|
||||
minzoom: 3.4,
|
||||
layout: {
|
||||
'text-field': ['get', 'name'], 'text-size': 10,
|
||||
...labelFont(m), 'text-field': ['get', 'name'], 'text-size': 10,
|
||||
'text-offset': [0, 1.1], 'text-anchor': 'top', 'text-allow-overlap': false
|
||||
},
|
||||
paint: { 'text-color': '#0b5e33', 'text-halo-color': '#ffffff', 'text-halo-width': 1.3 }
|
||||
|
|
@ -329,7 +330,7 @@ function syncLayers() {
|
|||
m.addLayer({
|
||||
id: L_TARGET_LABEL, type: 'symbol', source: SRC_TARGET,
|
||||
layout: {
|
||||
'text-field': ['get', 'name'], 'text-size': 13, 'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'],
|
||||
'text-field': ['get', 'name'], 'text-size': 13, ...labelFont(m, true),
|
||||
'text-offset': [0, -1.4], 'text-anchor': 'bottom', 'text-allow-overlap': true
|
||||
},
|
||||
paint: { 'text-color': '#8a3800', 'text-halo-color': '#ffffff', 'text-halo-width': 1.6 }
|
||||
|
|
|
|||
8
public/doxa-maps/doxa-maps-spa/manifest.json
generated
8
public/doxa-maps/doxa-maps-spa/manifest.json
generated
|
|
@ -863,7 +863,7 @@
|
|||
"bundle": "prayer-walk-planner",
|
||||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"output": "doxa-maps/prayer-walk-planner/prayer-walk-planner.js",
|
||||
"integrity": "sha384-xlbZdmRFPSAnxx7W/cetgQQjY3mAH5byO0uZWvFi4fCYkkzHZnBl78HSRCQcqgv9",
|
||||
"integrity": "sha384-pWPAEBiCv2hPheJQmT2/BNGjlg2HpRsSD8vmqX8s6OoizwlLz2Ygp85el90F5w4z",
|
||||
"elements": [
|
||||
"prayer-walk-planner"
|
||||
],
|
||||
|
|
@ -879,7 +879,7 @@
|
|||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-walk-planner/prayer-walk-planner/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-xlbZdmRFPSAnxx7W/cetgQQjY3mAH5byO0uZWvFi4fCYkkzHZnBl78HSRCQcqgv9\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-pWPAEBiCv2hPheJQmT2/BNGjlg2HpRsSD8vmqX8s6OoizwlLz2Ygp85el90F5w4z\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1059,7 +1059,7 @@
|
|||
"bundle": "upg-neighbor",
|
||||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"output": "doxa-maps/upg-neighbor/upg-neighbor.js",
|
||||
"integrity": "sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN",
|
||||
"integrity": "sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi",
|
||||
"elements": [
|
||||
"upg-neighbor"
|
||||
],
|
||||
|
|
@ -1075,7 +1075,7 @@
|
|||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
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
8
public/doxa-maps/manifest.json
generated
8
public/doxa-maps/manifest.json
generated
|
|
@ -863,7 +863,7 @@
|
|||
"bundle": "prayer-walk-planner",
|
||||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"output": "doxa-maps/prayer-walk-planner/prayer-walk-planner.js",
|
||||
"integrity": "sha384-xlbZdmRFPSAnxx7W/cetgQQjY3mAH5byO0uZWvFi4fCYkkzHZnBl78HSRCQcqgv9",
|
||||
"integrity": "sha384-pWPAEBiCv2hPheJQmT2/BNGjlg2HpRsSD8vmqX8s6OoizwlLz2Ygp85el90F5w4z",
|
||||
"elements": [
|
||||
"prayer-walk-planner"
|
||||
],
|
||||
|
|
@ -879,7 +879,7 @@
|
|||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-walk-planner/prayer-walk-planner/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-xlbZdmRFPSAnxx7W/cetgQQjY3mAH5byO0uZWvFi4fCYkkzHZnBl78HSRCQcqgv9\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-pWPAEBiCv2hPheJQmT2/BNGjlg2HpRsSD8vmqX8s6OoizwlLz2Ygp85el90F5w4z\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -1059,7 +1059,7 @@
|
|||
"bundle": "upg-neighbor",
|
||||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"output": "doxa-maps/upg-neighbor/upg-neighbor.js",
|
||||
"integrity": "sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN",
|
||||
"integrity": "sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi",
|
||||
"elements": [
|
||||
"upg-neighbor"
|
||||
],
|
||||
|
|
@ -1075,7 +1075,7 @@
|
|||
"scriptUrl": "./upg-neighbor/upg-neighbor.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./upg-neighbor/upg-neighbor/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
"script": "<!-- Mapbox engine, fetched early; remove this line if you set \"provider\":\"maplibre\" -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./upg-neighbor/upg-neighbor.js\" integrity=\"sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi\" crossorigin=\"anonymous\"></script>\n<upg-neighbor profile-config='{\"profile\":\"upg-neighbor\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></upg-neighbor>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
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 <prayer-walk-planner> custom element. -->
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-xlbZdmRFPSAnxx7W/cetgQQjY3mAH5byO0uZWvFi4fCYkkzHZnBl78HSRCQcqgv9" crossorigin="anonymous"></script>
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-pWPAEBiCv2hPheJQmT2/BNGjlg2HpRsSD8vmqX8s6OoizwlLz2Ygp85el90F5w4z" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
86
public/doxa-maps/upg-neighbor/upg-neighbor.js
generated
86
public/doxa-maps/upg-neighbor/upg-neighbor.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 <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <upg-neighbor> custom element. -->
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-YViVG3snYU3j6AZ8ZxBIJlBBMUL5Pj2guGdWiiKvexR9VNtQFCHrX+PQ6lLa2DrN" crossorigin="anonymous"></script>
|
||||
<script src="../../upg-neighbor.js" integrity="sha384-L9U5BmHdIQSgYXleGnKoycRFoPQq/zCBFGCK+76Xt++6f/Z0byB9sSlVef7ofjXi" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue