Carry the map selection into the resource table
The table listed every link but had no idea what you had just tapped, so answering "what exists for THIS group" meant reading a language name off the map and typing it into a filter box. The map and the table are separate PAGES, so the selection travels as a URL: ?tab=links&iso=<code> from a people group, &country=<ISO3> from a country. FILTERED IN THE PROFILE, NOT IN THE SHARED COMPONENT. DataTable has other consumers and none of them need a filter prop; teaching it one would put a change with repo-wide blast radius behind a feature only this bundle uses. The rows are filtered before they are handed over, which is invisible to every other caller. COUNTRY FILTERING WITHOUT A SECOND FETCH. Rows carry country_codes, the ISO3 set the language is spoken in, so a country filter is a membership test on data already loaded rather than pulling the 679KB pin table just to resolve which languages a country has. +23KB for the column, against 679KB avoided. THE EMPTY CASE SAYS WHY. A selection with no match renders "nothing found for this selection in the pools checked, which is not proof that nothing exists" rather than an empty grid, which would read as a broken filter or, worse, as proof of absence. Switching to a tab that has no language key says so too instead of showing zero rows. Verified end to end on the served tree: clicked a real pin, followed the link THE SHEET RENDERED rather than one built in the test, and landed on 2 rows both carrying cmn with "Showing 2 of 581 resources for Chinese, Mandarin". Country path checked the same way — Nepal 51, Chad 22, unfiltered 581 with no banner, and an unmatched code showing the honest empty state.
This commit is contained in:
parent
410f101846
commit
dfb4f755be
13 changed files with 100 additions and 31 deletions
File diff suppressed because one or more lines are too long
|
|
@ -45,7 +45,24 @@ const TABS = [
|
|||
{ id: 'languages', label: 'Languages by Country', file: 'languages-by-country' },
|
||||
{ id: 'growth', label: 'Access vs Population Growth', file: 'gospel-access-vs-growth' },
|
||||
]
|
||||
const active = ref('links')
|
||||
// DEEP-LINK FILTER. The map and this table are separate PAGES, so "click a pin and the
|
||||
// table follows" is a URL, not shared state. The map links here with ?tab=links&iso=<code>
|
||||
// or &country=<ISO3>, and the rows are filtered HERE rather than by teaching the shared
|
||||
// DataTable a new prop — that component has other consumers and this needs none of them.
|
||||
function qp() {
|
||||
try { return new URLSearchParams(window.location.search) } catch { return new URLSearchParams() }
|
||||
}
|
||||
const params = qp()
|
||||
const fIso = ref((params.get('iso') || '').trim().toLowerCase())
|
||||
const fCountry = ref((params.get('country') || '').trim().toUpperCase())
|
||||
const fLabel = ref((params.get('label') || '').trim())
|
||||
const hasFilter = computed(() => !!(fIso.value || fCountry.value))
|
||||
function clearFilter() {
|
||||
fIso.value = ''; fCountry.value = ''; fLabel.value = ''
|
||||
try { window.history.replaceState({}, '', window.location.pathname) } catch (_) {}
|
||||
}
|
||||
|
||||
const active = ref(TABS.some((t) => t.id === params.get('tab')) ? params.get('tab') : 'links')
|
||||
const current = computed(() => TABS.find((t) => t.id === active.value) || TABS[0])
|
||||
|
||||
// Fetched at runtime — see the note in cities-internet/data-tables.vue. These two
|
||||
|
|
@ -64,7 +81,22 @@ async function ensure(file) {
|
|||
finally { loading.value = false }
|
||||
}
|
||||
watch(current, (t) => t && ensure(t.file), { immediate: true })
|
||||
const table = computed(() => cache.value[current.value?.file] || null)
|
||||
const rawTable = computed(() => cache.value[current.value?.file] || null)
|
||||
|
||||
// Only the links table carries iso639_3/country_codes, so a filter is a NO-OP elsewhere
|
||||
// rather than an empty table — switching tabs under an active filter must not look broken.
|
||||
const table = computed(() => {
|
||||
const t = rawTable.value
|
||||
if (!t || !hasFilter.value || current.value?.id !== 'links') return t
|
||||
const rows = (t.rows || []).filter((r) => {
|
||||
if (fIso.value && String(r.iso639_3 || '').toLowerCase() !== fIso.value) return false
|
||||
if (fCountry.value && !String(r.country_codes || '').toUpperCase().split(' ').includes(fCountry.value)) return false
|
||||
return true
|
||||
})
|
||||
return { ...t, rows }
|
||||
})
|
||||
const filterCount = computed(() => (table.value?.rows || []).length)
|
||||
const totalCount = computed(() => (rawTable.value?.rows || []).length)
|
||||
|
||||
// Computed from the growth table ONCE it has loaded — the hero is hidden until
|
||||
// then rather than rendering zeros, which would read as "no gap" (the opposite of
|
||||
|
|
@ -92,6 +124,9 @@ useShadowStyles(
|
|||
'background:rgba(220,38,38,.07);border:1px solid rgba(220,38,38,.18)}' +
|
||||
'.is-dark .gt-hero{background:rgba(220,38,38,.12);border-color:rgba(248,113,113,.25)}' +
|
||||
'.gt-hero b{font-weight:800;font-variant-numeric:tabular-nums}' +
|
||||
'.gt-filter{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 10px;padding:8px 12px;border:1px solid #d1d4cd;border-left:3px solid #92b195;border-radius:8px;background:#f3f3f1;color:#3b463d;font-size:13px}' +
|
||||
'.gt-clear{border:1px solid #d1d4cd;background:#fff;color:#3b463d;font:600 12px inherit;padding:5px 11px;border-radius:7px;cursor:pointer;min-height:32px}' +
|
||||
'.gt-clear:hover{background:#f3f3f1}' +
|
||||
'.gt-tabs{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:14px}' +
|
||||
'.gt-tab{font:600 12px inherit;padding:7px 14px;border-radius:999px;cursor:pointer;' +
|
||||
'border:1px solid rgba(0,0,0,.14);background:transparent;color:inherit}' +
|
||||
|
|
@ -126,11 +161,21 @@ useShadowStyles(
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="hasFilter" class="gt-filter">
|
||||
<span v-if="current.id === 'links'">
|
||||
Showing <b>{{ fmt(filterCount) }}</b> of {{ fmt(totalCount) }} resources
|
||||
for <b>{{ fLabel || fIso || fCountry }}</b>
|
||||
<template v-if="!filterCount"> — nothing found for this selection in the pools checked, which is not proof that nothing exists.</template>
|
||||
</span>
|
||||
<span v-else>Filtered to <b>{{ fLabel || fIso || fCountry }}</b> — this tab is not filtered; only “Every Resource + Link” carries a language key.</span>
|
||||
<button type="button" class="gt-clear" @click="clearFilter">Show all</button>
|
||||
</div>
|
||||
|
||||
<div v-if="loadErr" class="gt-err">{{ loadErr }}</div>
|
||||
<div v-else-if="!table" class="gt-load">Loading {{ current.label }}…</div>
|
||||
<DataTable
|
||||
v-else
|
||||
:key="current.id"
|
||||
:key="current.id + ':' + fIso + ':' + fCountry"
|
||||
:table="table"
|
||||
:assets-base="assetsBase"
|
||||
:is-dark="isDark"
|
||||
|
|
|
|||
|
|
@ -432,6 +432,13 @@ function setMode(m) {
|
|||
|
||||
const fmt = (n) => (Number(n) || 0).toLocaleString()
|
||||
|
||||
// The table lives on a SIBLING page, so "see all of these" is a URL. Built here rather
|
||||
// than inline in the template so the group link and the country link cannot drift apart.
|
||||
const tableLink = (q) => {
|
||||
const p = new URLSearchParams({ tab: 'links', ...q })
|
||||
return `../data-tables/index.html?${p.toString()}`
|
||||
}
|
||||
|
||||
useShadowStyles(
|
||||
'.gm{position:absolute;inset:0;overflow:hidden}' +
|
||||
'.gm-map{position:absolute;inset:0}' +
|
||||
|
|
@ -492,6 +499,9 @@ useShadowStyles(
|
|||
'.is-dark .gm-mode{border-color:rgba(255,255,255,.22)}' +
|
||||
'.is-dark .gm-mode.on{background:#10b981;border-color:#10b981;color:#04241a}' +
|
||||
// RESOURCE LIST inside the sheet.
|
||||
'.gm-tablink{display:block;margin:10px 0 2px;padding:9px 12px;border:1px solid #d1d4cd;border-left:3px solid #92b195;border-radius:8px;background:#f3f3f1;color:#3b463d;font-weight:600;font-size:12.5px;text-decoration:none;min-height:36px;box-sizing:border-box}' +
|
||||
'.gm-tablink:hover{background:#e9ebe6}' +
|
||||
'.is-dark .gm-tablink{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.18);color:#e5e7eb}' +
|
||||
'.gm-sec{margin-top:12px}' +
|
||||
'.gm-sec-h{font-weight:800;font-size:12px;letter-spacing:.02em;margin-bottom:6px}' +
|
||||
'.gm-load{padding:10px 0;font-size:11.5px;opacity:.65}' +
|
||||
|
|
@ -644,6 +654,11 @@ useShadowStyles(
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<a class="gm-tablink" :href="tableLink({ iso: selectedGroup.iso639_3 || '', label: selectedGroup.language || '' })"
|
||||
v-if="selectedGroup.iso639_3">
|
||||
Every resource in {{ selectedGroup.language }} →
|
||||
</a>
|
||||
|
||||
<div class="gm-sec" v-if="sameLanguage.length">
|
||||
<div class="gm-sec-h">Also speak {{ selectedGroup.language }} ({{ sameLanguage.length }})</div>
|
||||
<button v-for="g in sameLanguage.slice(0, 12)" :key="g.people_group + g.countries"
|
||||
|
|
@ -680,6 +695,11 @@ useShadowStyles(
|
|||
<div class="gm-stat"><span>Growing & unreached</span><b>{{ fmt(selected.growing_unreached) }}</b></div>
|
||||
<div class="gm-stat"><span>Est. deaths/day</span><b>{{ fmt(Math.round(selected.deaths_day)) }}</b></div>
|
||||
|
||||
<a class="gm-tablink" :href="tableLink({ country: selected.iso3 || '', label: selected.country || '' })"
|
||||
v-if="selected.iso3">
|
||||
Every resource used in {{ selected.country }} →
|
||||
</a>
|
||||
|
||||
<div v-if="pgLoading" class="gm-load">Loading resources…</div>
|
||||
<div v-else-if="pgErr" class="gm-err" style="position:static;text-align:left">{{ pgErr }}</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ for (const [code, c] of Object.entries(codes)) {
|
|||
speakers: r.speakers,
|
||||
countries: r.countries.size,
|
||||
example_group: r.sample[0] || '',
|
||||
// ISO3 country codes this language is spoken in, space-joined. Carried so the table
|
||||
// can be filtered to a COUNTRY from a map click without loading the 679KB pin table
|
||||
// just to resolve which languages a country contains.
|
||||
country_codes: [...r.countries].sort().join(' '),
|
||||
resource_url: url,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
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 <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci" 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 <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
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 <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci" 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 <gospel-resources-map> custom element. -->
|
||||
<script src="../gospel-resources.js" integrity="sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt" crossorigin="anonymous"></script>
|
||||
<script src="../gospel-resources.js" integrity="sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci" 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
10
public/doxa-maps/manifest.json
generated
10
public/doxa-maps/manifest.json
generated
|
|
@ -556,7 +556,7 @@
|
|||
"bundle": "gospel-resources",
|
||||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"output": "doxa-maps/gospel-resources/gospel-resources.js",
|
||||
"integrity": "sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt",
|
||||
"integrity": "sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci",
|
||||
"elements": [
|
||||
"gospel-resources-map"
|
||||
],
|
||||
|
|
@ -572,7 +572,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/charts/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -586,7 +586,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/data-tables/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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -600,7 +600,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -614,7 +614,7 @@
|
|||
"scriptUrl": "./gospel-resources/gospel-resources.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./gospel-resources/resources-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=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-fNkv0DCvTk9lRwT+YZUq+4kSz8aV3//MY9kf1iwbAjYQll2DtABUNutRmgzkBOrt\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-ghGJCWgzNE6CZFo6Txri3COKSOwM+LmqEj4p6tBo9B/eUoczfRH9p9Ogo+mT+4ci\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue