mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
fix(mobile): never trap navigation; top-right button opens all layers
Tab switches no longer run inside a View Transition, which the globe and camera could stall so taps on the tab bar went unanswered, and a capture enter/leave failure can no longer abort a switch. Escape closes the topmost open sheet wherever focus is. The top-right globe button opens one Layers sheet with the visual looks and every layer family. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
This commit is contained in:
3 files changed
+70
-19
No files matched your search
+18
-17
@@ -159,13 +159,16 @@ function applyTab(tab) {
|
|||||||
else el.hidden = id !== tab && !(two && tab === 'capture' && id === prev);
|
else el.hidden = id !== tab && !(two && tab === 'capture' && id === prev);
|
||||||
}
|
}
|
||||||
tabbar.select(tab);
|
tabbar.select(tab);
|
||||||
if (tab === 'capture') {
|
// A camera or sensor failure must never trap someone in Capture.
|
||||||
globe?.pause();
|
try {
|
||||||
capture?.enter();
|
if (tab === 'capture') {
|
||||||
} else {
|
globe?.pause();
|
||||||
if (prev === 'capture') capture?.leave();
|
capture?.enter();
|
||||||
updateGlobeActivity();
|
} else if (prev === 'capture') capture?.leave();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('capture', err);
|
||||||
}
|
}
|
||||||
|
if (tab !== 'capture') updateGlobeActivity();
|
||||||
if (tab === 'you') you?.render?.();
|
if (tab === 'you') you?.render?.();
|
||||||
if (tab === 'rooms') threads?.refresh?.();
|
if (tab === 'rooms') threads?.refresh?.();
|
||||||
if (tab === 'live' && two) chrome?.renderLeadView();
|
if (tab === 'live' && two) chrome?.renderLeadView();
|
||||||
@@ -180,16 +183,14 @@ function selectTab(tab) {
|
|||||||
if (player?.current) player.close();
|
if (player?.current) player.close();
|
||||||
if (world?.current && !isTwoPane()) world.close();
|
if (world?.current && !isTwoPane()) world.close();
|
||||||
if (narrative?.active) narrative.stop();
|
if (narrative?.active) narrative.stop();
|
||||||
const vt = document.startViewTransition && !reducedMotion();
|
// Switch synchronously. A View Transition waits on frames the globe and
|
||||||
if (vt) document.startViewTransition(() => applyTab(tab));
|
// the camera can hold up, which left taps on the tab bar unanswered.
|
||||||
else {
|
applyTab(tab);
|
||||||
applyTab(tab);
|
const v = views[tab];
|
||||||
const v = views[tab];
|
if (v && !v.hidden && !reducedMotion()) {
|
||||||
if (v && !v.hidden) {
|
v.classList.remove('view-enter');
|
||||||
v.classList.remove('view-enter');
|
void v.offsetWidth;
|
||||||
void v.offsetWidth;
|
v.classList.add('view-enter');
|
||||||
v.classList.add('view-enter');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,7 +1299,7 @@ function buildDirector(g) {
|
|||||||
|
|
||||||
// Keyboard: Escape closes the topmost thing.
|
// Keyboard: Escape closes the topmost thing.
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key !== 'Escape') return;
|
if (e.key !== 'Escape' || e.defaultPrevented) return;
|
||||||
if (document.querySelector('.sheet-layer.is-modal:not([hidden])')) return;
|
if (document.querySelector('.sheet-layer.is-modal:not([hidden])')) return;
|
||||||
if (dossier?.isOpen) return dossier.close();
|
if (dossier?.isOpen) return dossier.close();
|
||||||
if (player?.current) return;
|
if (player?.current) return;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export function createGlobeChrome({
|
|||||||
const top = h(`<div class="gc-top">
|
const top = h(`<div class="gc-top">
|
||||||
<div class="gc-readout"><span class="tele gc-alt">—</span><span class="tele gc-ll">—</span></div>
|
<div class="gc-readout"><span class="tele gc-alt">—</span><span class="tele gc-ll">—</span></div>
|
||||||
<div class="gc-buttons">
|
<div class="gc-buttons">
|
||||||
<button class="icon-btn glass-strong liquid" type="button" data-act="lens" aria-label="Visual lens" aria-haspopup="true">${icon('lens')}</button>
|
<button class="icon-btn glass-strong liquid" type="button" data-act="lens" aria-label="Layers" aria-haspopup="dialog">${icon('layers')}</button>
|
||||||
<button class="icon-btn glass-strong liquid gc-compass" type="button" data-act="home" aria-label="Reset view to the whole planet"><span class="compass-n">${icon('north')}</span></button>
|
<button class="icon-btn glass-strong liquid gc-compass" type="button" data-act="home" aria-label="Reset view to the whole planet"><span class="compass-n">${icon('north')}</span></button>
|
||||||
<button class="icon-btn glass-strong liquid" type="button" data-act="credits" aria-label="Map data attribution">${icon('info')}</button>
|
<button class="icon-btn glass-strong liquid" type="button" data-act="credits" aria-label="Map data attribution">${icon('info')}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -409,7 +409,30 @@ export function createGlobeChrome({
|
|||||||
</button>`;
|
</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Every family in one sheet, with the visual looks on top. */
|
||||||
|
function renderAllGrid() {
|
||||||
|
const st = familyState();
|
||||||
|
const active = store.get('lens') || 'cinematic';
|
||||||
|
const on = Object.values(st).reduce((a, x) => a + x.on, 0);
|
||||||
|
const count = Object.values(st).reduce((a, x) => a + x.count, 0);
|
||||||
|
grid.sheet.dataset.family = 'all';
|
||||||
|
grid.head.innerHTML = `<div class="grid-head"><span class="grid-ic">${icon('layers')}</span>
|
||||||
|
<div><h2 class="sheet-title">Layers</h2><div class="sheet-sub num">${on} on · ${fmtNum(count)} contacts</div></div></div>`;
|
||||||
|
grid.body.innerHTML = `<div class="section-h">Look</div>
|
||||||
|
<div class="lens-row" data-hscroll>${LENSES.map((l) => `<button type="button" class="lens-card ${l.id === active ? 'on' : ''}" data-lens3="${l.id}"><span class="lens-sw" style="background:${l.sw}"></span>${l.name}</button>`).join('')}</div>
|
||||||
|
${FAMILIES.map((f) => {
|
||||||
|
const x = st[f.id];
|
||||||
|
const layers = [...x.layers].sort(
|
||||||
|
(a, b) => (b.available !== false) - (a.available !== false),
|
||||||
|
);
|
||||||
|
return `<div class="section-h" data-family="${f.id}"><span class="lead-fam">${icon(FAMILY_ICON[f.id])}${f.name}</span><small class="num">${x.on}/${x.total} · ${fmtNum(x.count)}</small></div>
|
||||||
|
<div class="tiles">${layers.map(tileHtml).join('')}</div>`;
|
||||||
|
}).join('')}
|
||||||
|
<p class="footnote" style="padding:14px 2px 0">Counts are live from each feed; dotted means stale.</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderGrid(fam) {
|
function renderGrid(fam) {
|
||||||
|
if (fam === 'all') return renderAllGrid();
|
||||||
const st = familyState()[fam];
|
const st = familyState()[fam];
|
||||||
const f = FAMILIES.find((x) => x.id === fam);
|
const f = FAMILIES.find((x) => x.id === fam);
|
||||||
grid.sheet.dataset.family = fam;
|
grid.sheet.dataset.family = fam;
|
||||||
@@ -427,6 +450,15 @@ export function createGlobeChrome({
|
|||||||
}
|
}
|
||||||
|
|
||||||
grid.root.addEventListener('click', (e) => {
|
grid.root.addEventListener('click', (e) => {
|
||||||
|
const look = e.target.closest('[data-lens3]');
|
||||||
|
if (look) {
|
||||||
|
haptic('tick');
|
||||||
|
setLens(look.dataset.lens3);
|
||||||
|
$$('[data-lens3]', grid.root).forEach((b) =>
|
||||||
|
b.classList.toggle('on', b === look),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const soon = e.target.closest('[data-soon]');
|
const soon = e.target.closest('[data-soon]');
|
||||||
if (soon) {
|
if (soon) {
|
||||||
toast(
|
toast(
|
||||||
@@ -506,8 +538,8 @@ export function createGlobeChrome({
|
|||||||
top.addEventListener('click', (e) => {
|
top.addEventListener('click', (e) => {
|
||||||
const act = e.target.closest('[data-act]')?.dataset.act;
|
const act = e.target.closest('[data-act]')?.dataset.act;
|
||||||
if (act === 'lens') {
|
if (act === 'lens') {
|
||||||
lensPop.hidden = !lensPop.hidden;
|
|
||||||
haptic('tick');
|
haptic('tick');
|
||||||
|
openGrid('all');
|
||||||
} else if (act === 'home') {
|
} else if (act === 'home') {
|
||||||
haptic('tick');
|
haptic('tick');
|
||||||
hidePick();
|
hidePick();
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ const DEFAULT_DETENTS = {
|
|||||||
* heights?: Record<string,(H:number)=>number>, closeButton?: boolean,
|
* heights?: Record<string,(H:number)=>number>, closeButton?: boolean,
|
||||||
* }} o
|
* }} o
|
||||||
*/
|
*/
|
||||||
|
/** Open dismissible sheets, newest last: Escape closes the top one even
|
||||||
|
* when focus is elsewhere (on the globe, or nowhere after a click). */
|
||||||
|
const escStack = [];
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key !== 'Escape' || e.defaultPrevented || !escStack.length) return;
|
||||||
|
if (e.target.closest?.('input, textarea, [contenteditable]')) return;
|
||||||
|
e.preventDefault();
|
||||||
|
escStack[escStack.length - 1]();
|
||||||
|
});
|
||||||
|
|
||||||
export function createSheet(o) {
|
export function createSheet(o) {
|
||||||
const ev = emitter();
|
const ev = emitter();
|
||||||
const detents = o.detents || ['peek', 'medium', 'large'];
|
const detents = o.detents || ['peek', 'medium', 'large'];
|
||||||
@@ -89,6 +99,11 @@ export function createSheet(o) {
|
|||||||
measure();
|
measure();
|
||||||
root.hidden = false;
|
root.hidden = false;
|
||||||
isOpen = true;
|
isOpen = true;
|
||||||
|
if (o.modal || o.dismissible) {
|
||||||
|
const i = escStack.indexOf(close);
|
||||||
|
if (i >= 0) escStack.splice(i, 1);
|
||||||
|
escStack.push(close);
|
||||||
|
}
|
||||||
if (!current || root.dataset.closing) {
|
if (!current || root.dataset.closing) {
|
||||||
y = maxH() + 40;
|
y = maxH() + 40;
|
||||||
apply(false);
|
apply(false);
|
||||||
@@ -112,6 +127,8 @@ export function createSheet(o) {
|
|||||||
function close() {
|
function close() {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
|
const i = escStack.indexOf(close);
|
||||||
|
if (i >= 0) escStack.splice(i, 1);
|
||||||
root.dataset.closing = '1';
|
root.dataset.closing = '1';
|
||||||
y = maxH() + 40;
|
y = maxH() + 40;
|
||||||
apply(true, false);
|
apply(true, false);
|
||||||
@@ -260,6 +277,7 @@ export function createSheet(o) {
|
|||||||
root.addEventListener('keydown', (e) => {
|
root.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape' && (o.modal || o.dismissible)) {
|
if (e.key === 'Escape' && (o.modal || o.dismissible)) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user