The universal-props matrix flagged pplr-data-maps as a locale BYPASS: every tab label and every
loading or ready line was an English literal. A new pplr area (library/i18n/locales/<lc>/pplr.json,
all 11 locales) carries the 10 tab labels and 20 status lines; usePplrT() reads them through a
guarded useI18n with the English catalog as fallback. Counts go in as parameters, and their number
formatting is unchanged pending the Driver's numerals decision. The views' own legend and panel text
is the next pass.
check:i18n-parity PASS (all 11 complete); the bundle builds. A library catalog change (the i18n index
globs every area into every i18n bundle), so studio lands it (rule 5B).
needs eyes: PPLR Data Maps with ?lang=ar: the tab bar and the loading and ready lines.
(cherry picked from commit 4a16f0538b)
86 lines
5.4 KiB
Vue
86 lines
5.4 KiB
Vue
<script setup>
|
|
/**
|
|
* pplr-data-maps.vue — PPLR data maps: ten views of the PPLR people-group data in one profile.
|
|
*
|
|
* @description Ten views that let a researcher check the PPLR people-group data by eye
|
|
* @profile pplr-data-maps
|
|
* @bundle pplr-data-maps
|
|
* @element pplr-data-maps
|
|
*
|
|
* MOVED 2026-09-25 from the PPLR-validate-data MFE (far-fast-tools, read-only) into this
|
|
* plugin-designs folder. The ten views are its original app-profiles, now in src/views/, with its
|
|
* own components, instance store and map helpers in src/. This hub replaces the original showcase
|
|
* page's tab bar. The original registered <doxa-map>, which collided with doxa-simple-map; this
|
|
* bundle registers <pplr-data-maps>.
|
|
*
|
|
* Data comes from data/pplr/ (built by scripts/build-data.mjs) through the tree's shared assets
|
|
* base, so the bundle works embedded on any host. d3 loads on first need, for the sunburst.
|
|
*
|
|
* profile-config: { "profile":"pplr-data-maps", "tk":"pk...", "view":"pplr-religion" }
|
|
*/
|
|
import { inject, ref, computed, defineAsyncComponent, onMounted, onBeforeUnmount } from 'vue'
|
|
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
|
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
|
import { setDataBase, ensureD3 } from './src/utils/pplrData.js'
|
|
import { usePplrT } from './src/composables/usePplrT.js'
|
|
|
|
const profileConfig = inject('profileConfig')
|
|
const cfg = computed(() => profileConfig?.value || {})
|
|
setDataBase(resolveAssetsBase(cfg.value))
|
|
|
|
// Order and labels follow the original showcase page's tab bar.
|
|
// Tab labels are the chrome a reader sees first; they come from pplr.json (all 11 locales).
|
|
const t = usePplrT()
|
|
const VIEWS = [
|
|
{ key: 'pplr-religion', label: 'Religion', tKey: 'pplr.tabs.religion', load: () => import('./src/views/pplr-religion.vue') },
|
|
{ key: 'pplr-rop-tree', label: 'ROP Tree', tKey: 'pplr.tabs.ropTree', load: () => import('./src/views/pplr-rop-tree.vue') },
|
|
{ key: 'pplr-language-families', label: 'Language Families', tKey: 'pplr.tabs.languageFamilies', load: () => import('./src/views/pplr-language-families.vue') },
|
|
{ key: 'pplr-semantic-trees', label: 'Semantic Trees', tKey: 'pplr.tabs.semanticTrees', load: () => import('./src/views/pplr-semantic-trees.vue') },
|
|
{ key: 'pplr-translations', label: 'PG Name Locales', tKey: 'pplr.tabs.nameLocales', load: () => import('./src/views/pplr-translations.vue') },
|
|
{ key: 'pplr-d3-tree', label: 'ROP Sunburst', tKey: 'pplr.tabs.ropSunburst', load: () => ensureD3().then(() => import('./src/views/pplr-d3-tree.vue')) },
|
|
{ key: 'pplr-pins', label: 'All PG-in-country pins', tKey: 'pplr.tabs.allPins', load: () => import('./src/views/pplr-pins.vue') },
|
|
{ key: 'pplr-rosetta-cards', label: 'Cross-system ID Rosetta', tKey: 'pplr.tabs.rosetta', load: () => import('./src/views/pplr-rosetta-cards.vue') },
|
|
{ key: 'pplr-coverage', label: 'Translation coverage', tKey: 'pplr.tabs.coverage', load: () => import('./src/views/pplr-coverage.vue') },
|
|
{ key: 'pplr-index', label: 'Index', tKey: 'pplr.tabs.index', load: () => import('./src/views/pplr-index.vue') },
|
|
]
|
|
const COMPONENTS = Object.fromEntries(VIEWS.map((v) => [v.key, defineAsyncComponent(v.load)]))
|
|
const VALID = new Set(VIEWS.map((v) => v.key))
|
|
|
|
// Chosen at setup so a non-default view never mounts the default first (see upg-neighbor.vue).
|
|
const view = ref(VALID.has(cfg.value.view) ? cfg.value.view : 'pplr-religion')
|
|
function setView(k) { if (VALID.has(k)) view.value = k }
|
|
|
|
// The Index view's tiles dispatch pplr-select-profile on window (the original host page listened).
|
|
function onSelect(e) { setView(e?.detail?.profile || e?.detail?.id || e?.detail) }
|
|
onMounted(() => window.addEventListener('pplr-select-profile', onSelect))
|
|
onBeforeUnmount(() => window.removeEventListener('pplr-select-profile', onSelect))
|
|
|
|
useShadowStyles(
|
|
'.pdm-root{position:absolute;inset:0;display:flex;flex-direction:column;overflow:hidden;background:#f3f3f1;' +
|
|
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;isolation:isolate}' +
|
|
'.pdm-bar{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:8px 12px;background:#fff;' +
|
|
'border-bottom:1px solid #d1d4cd;overflow-x:auto;z-index:50}' +
|
|
'.pdm-brand{font:700 12px/1 ui-monospace,monospace;letter-spacing:.06em;text-transform:uppercase;color:#3b463d;white-space:nowrap}' +
|
|
'.pdm-seg{display:inline-flex;gap:4px;flex-wrap:nowrap}' +
|
|
'.pdm-seg button{border:1px solid #d1d4cd;background:#fff;color:#3b463d;cursor:pointer;white-space:nowrap;' +
|
|
'font:600 12px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:7px 11px;border-radius:999px}' +
|
|
'.pdm-seg button.on{background:#92b195;border-color:#92b195;color:#fff}' +
|
|
'.pdm-seg button:hover:not(.on){border-color:#92b195}' +
|
|
'.pdm-stage{flex:1 1 0;position:relative;min-height:0}',
|
|
'pplr-data-maps')
|
|
</script>
|
|
|
|
<template>
|
|
<div class="pdm-root">
|
|
<div class="pdm-bar">
|
|
<span class="pdm-brand">PPLR data maps</span>
|
|
<div class="pdm-seg" role="tablist" aria-label="Choose a view">
|
|
<button v-for="v in VIEWS" :key="v.key" role="tab" :aria-selected="view === v.key"
|
|
:class="{ on: view === v.key }" @click="setView(v.key)">{{ t(v.tKey) }}</button>
|
|
</div>
|
|
</div>
|
|
<div class="pdm-stage">
|
|
<component :is="COMPONENTS[view]" :key="view" />
|
|
</div>
|
|
</div>
|
|
</template>
|