UPG Neighbor: a neighbor's dot and its card point at each other
A neighbor dot showed a pointer and did nothing on click. Clicking a dot now picks and scrolls to its card; clicking a card (or Enter on it) flies to the dot and rings it, so the map and the brief agree on which neighbor you are looking at.
This commit is contained in:
parent
33e8ed6c4b
commit
3a1aa55a89
1 changed files with 30 additions and 1 deletions
|
|
@ -50,6 +50,23 @@ const byLevel = computed(() => [1, 2, 3].map((e) => ({ e, list: neighbors.value.
|
|||
const believersNear = computed(() => neighbors.value.reduce((a, n) => a + n.g.believers, 0))
|
||||
|
||||
function select(g) { if (g && !g.reached) { nbState.pgid = g.pgid; query.value = '' } }
|
||||
|
||||
// The neighbor in focus: a dot click picks its card, a card click flies to its dot. Both ways, so the
|
||||
// map and the brief never disagree about which neighbor you are looking at.
|
||||
const activeId = ref(null)
|
||||
watch(() => nbState.pgid, () => { activeId.value = null })
|
||||
function focusNeighbor(n, fly = true) {
|
||||
activeId.value = n.g.pgid
|
||||
applyActive()
|
||||
const m = map.value
|
||||
if (fly && m) { try { m.stop(); m.easeTo({ center: [n.g.lng, n.g.lat], zoom: Math.max(m.getZoom(), 5), duration: 700 }) } catch (_) {} }
|
||||
const card = mapContainer.value?.getRootNode?.()?.querySelector?.(`[data-nb="${n.g.pgid}"]`)
|
||||
card?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })
|
||||
}
|
||||
function applyActive() {
|
||||
const m = map.value
|
||||
if (m?.getLayer?.('nb-a')) m.setFilter('nb-a', ['==', ['get', 'id'], activeId.value || ''])
|
||||
}
|
||||
function clearSelection() { nbState.pgid = null }
|
||||
|
||||
// ─── Search / default list ───────────────────────────────────────────────────
|
||||
|
|
@ -115,9 +132,12 @@ function ensureLayers(m) {
|
|||
if (!m.getLayer('nb-n')) m.addLayer({ id: 'nb-n', type: 'circle', source: SRC_N, paint: {
|
||||
'circle-color': ['get', 'color'], 'circle-radius': ['match', ['get', 'e'], 1, 7, 2, 6, 5],
|
||||
'circle-stroke-width': 1.5, 'circle-stroke-color': '#fff' } })
|
||||
if (!m.getLayer('nb-a')) m.addLayer({ id: 'nb-a', type: 'circle', source: SRC_N, filter: ['==', ['get', 'id'], ''], paint: {
|
||||
'circle-color': 'rgba(0,0,0,0)', 'circle-radius': 11, 'circle-stroke-width': 3, 'circle-stroke-color': SELECTED } })
|
||||
if (!m.getLayer('nb-s')) m.addLayer({ id: 'nb-s', type: 'circle', source: SRC_S, paint: {
|
||||
'circle-color': UNREACHED, 'circle-radius': 10, 'circle-stroke-width': 3, 'circle-stroke-color': SELECTED } })
|
||||
applyDim(m)
|
||||
applyActive()
|
||||
}
|
||||
function applyDim(m) {
|
||||
if (m.getLayer('nb-u')) m.setPaintProperty('nb-u', 'circle-opacity', selected.value ? 0.18 : 0.55)
|
||||
|
|
@ -162,6 +182,12 @@ onMounted(async () => {
|
|||
const g = data.value?.unreached.find((u) => u.pgid === id)
|
||||
if (g) select(g)
|
||||
})
|
||||
// A neighbor dot had a pointer cursor and no click handler; it now picks its card.
|
||||
m.on('click', 'nb-n', (ev) => {
|
||||
const id = ev.features?.[0]?.properties?.id
|
||||
const n = neighbors.value.find((x) => x.g.pgid === id)
|
||||
if (n) focusNeighbor(n, false)
|
||||
})
|
||||
for (const id of ['nb-u', 'nb-n']) {
|
||||
m.on('mouseenter', id, () => { m.getCanvas().style.cursor = 'pointer' })
|
||||
m.on('mouseleave', id, () => { m.getCanvas().style.cursor = '' })
|
||||
|
|
@ -207,6 +233,9 @@ useShadowStyles(`
|
|||
.mb-level p { margin:0 0 6px; color:#6b7280; font-size:12px; line-height:1.35; }
|
||||
.mb-dot { width:11px; height:11px; border-radius:50%; flex:0 0 auto; }
|
||||
.mb-card { border:1px solid #e5e7eb; border-radius:8px; padding:8px 10px; margin-bottom:6px; font-size:12px; line-height:1.35; }
|
||||
.mb-card { cursor:pointer; }
|
||||
.mb-card:hover, .mb-card:focus-visible { border-color:#9fb3d9; }
|
||||
.mb-card.on { border-color:${SELECTED}; box-shadow:0 0 0 2px ${SELECTED}55; }
|
||||
.mb-card b { font-size:13px; }
|
||||
.mb-card .muted { color:#6b7280; }
|
||||
.mb-same { font:700 10px/1 inherit; color:#7a3e00; background:#fdeedd; border-radius:999px; padding:3px 6px; margin-left:4px; }
|
||||
|
|
@ -285,7 +314,7 @@ useShadowStyles(`
|
|||
<section v-for="lv in byLevel" :key="lv.e" class="mb-level">
|
||||
<h3><span class="mb-dot" :style="{ background: E_COLORS[lv.e] }"></span>{{ $t(E_TITLE[lv.e]) }}</h3>
|
||||
<p>{{ $t(E_LONG[lv.e]) }}</p>
|
||||
<div v-for="n in lv.list" :key="n.g.pgid" class="mb-card">
|
||||
<div v-for="n in lv.list" :key="n.g.pgid" class="mb-card" :class="{ on: activeId === n.g.pgid }" :data-nb="n.g.pgid" role="button" tabindex="0" @click="focusNeighbor(n)" @keydown.enter="focusNeighbor(n)">
|
||||
<b>{{ n.g.name }}</b><span v-if="n.same" class="mb-same">{{ $t('neighbor.mob.samePeople') }}</span><br>
|
||||
{{ n.g.country }} · {{ n.g.langName }}<br>
|
||||
<span class="muted">{{ $t('neighbor.mob.cardStats', { b: fmtNum(n.g.believers), km: n.km.toLocaleString() }) }}</span>
|
||||
|
|
|
|||
Loading…
Reference in a new issue