mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Merge main into the Utilities panel, rebuilt on the Vue panel rail
main moved the panels to Vue views and a one-open-panel rail, and removed
the two templates this branch edited. This merge carries the branch onto
that architecture; it adds no behaviour of its own beyond the port.
- The Utilities panel is a RailPanel in LayerPanelsView with its own rail
item (handyman). LayerPanel renders the Directions rows into its list with
LayerRows, and falls back to Data Layers without it.
- Draw leaves DisplayPanel for DrawControls, DrawMarkList and DrawSwatches,
which render what the draw tool publishes to the display store and call
its actions, as GeoImport does. drawMarks.js words the rows; the DOM-built
drawMarkList.js is gone. A row's fly-to is an immediate navigation like
Import's ('drawing' gets its Cockpit refusal), the name field keeps a
draft through v-field-value, and the colours are one radio group with a
single tab stop.
- UiSegmented draws an option's icon, named by its label, for the shape
buttons.
- The catalogs take main's languages with this branch's strings, Arabic
included, and the tests follow the views to their new homes.
This commit is contained in:
commit
4ebb287d36
699 files changed
+46673
-20476
No files matched your search
+125
-73
@@ -7,6 +7,7 @@ import {
|
||||
layoutRightPanelRail,
|
||||
measurePanelNaturalHeight,
|
||||
} from './panelRails.js';
|
||||
import { applyPanelState, readPanelState } from './rail/panelState.js';
|
||||
|
||||
test('Display uses an inner scroll body only in Cyber', () => {
|
||||
const body = { scrollTop: 95 };
|
||||
@@ -20,61 +21,57 @@ test('Display uses an inner scroll body only in Cyber', () => {
|
||||
assert.equal(displayPanelScroller(null), null);
|
||||
});
|
||||
|
||||
test('Cyber restores one expanded owner, keeps launchers, and restores other theme preferences', () => {
|
||||
for (const mobile of [false]) {
|
||||
const f = fixture('right', {
|
||||
mobile,
|
||||
hud: { visible: true, variant: 'cyber' },
|
||||
});
|
||||
for (const side of ['left', 'right']) {
|
||||
test(`${side} desktop rail keeps one expanded owner in every HUD layout`, () => {
|
||||
for (const variant of ['tactical', 'minimal', 'cyber']) {
|
||||
const f = fixture(side, { hud: { visible: true, variant } });
|
||||
f.expand(f.first, 250);
|
||||
f.expand(f.second, 250);
|
||||
f.options.preferredPanelId = f.second.id;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('accordion-collapsed'), true);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), true);
|
||||
assert.equal(f.first.getAttribute('aria-hidden'), 'true');
|
||||
assert.equal(f.retries(), 0, 'the accordion needs no auto-collapse pass');
|
||||
f.run();
|
||||
assert.equal(
|
||||
f.first.classList.contains('collapsed'),
|
||||
true,
|
||||
'layout does not reopen a peer',
|
||||
);
|
||||
// Phone sheets restore the presentation-only collapse.
|
||||
f.options.windowRef.matchMedia = () => ({ matches: true });
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('collapsed'), false);
|
||||
assert.equal(f.first.classList.contains('accordion-collapsed'), false);
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), false);
|
||||
}
|
||||
});
|
||||
|
||||
test(`${side} rail keeps no accordion on phones, where it is presented as tab sheets`, () => {
|
||||
// A sheet (for example Cameras: CCTV with Street Level) shows several of the
|
||||
// rail's panels at once; the accordion would collapse all but one.
|
||||
const f = fixture(side, { mobile: true });
|
||||
f.expand(f.first, 250);
|
||||
f.expand(f.second, 250);
|
||||
f.options.preferredPanelId = f.second.id;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('cyber-accordion-collapsed'), true);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
|
||||
f.run();
|
||||
assert.equal(
|
||||
f.first.classList.contains('collapsed'),
|
||||
true,
|
||||
'layout does not reopen a peer',
|
||||
);
|
||||
f.options.hud.variant = 'minimal';
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('collapsed'), false);
|
||||
assert.equal(
|
||||
f.first.classList.contains('cyber-accordion-collapsed'),
|
||||
false,
|
||||
);
|
||||
assert.equal(f.first.classList.contains('accordion-collapsed'), false);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
}
|
||||
});
|
||||
|
||||
test('Cyber keeps no accordion on phones, where the rail is presented as tab sheets', () => {
|
||||
// A sheet (for example Cameras: CCTV with Street Level) shows several of the
|
||||
// rail's panels at once; the accordion would collapse all but one.
|
||||
const f = fixture('right', {
|
||||
mobile: true,
|
||||
hud: { visible: true, variant: 'cyber' },
|
||||
});
|
||||
f.expand(f.first, 250);
|
||||
f.expand(f.second, 250);
|
||||
f.options.preferredPanelId = f.second.id;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('collapsed'), false);
|
||||
assert.equal(f.first.classList.contains('cyber-accordion-collapsed'), false);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
});
|
||||
|
||||
test('Cyber restored accordion prefers keyboard focus when no explicit owner exists', () => {
|
||||
const f = fixture('right', { hud: { visible: true, variant: 'cyber' } });
|
||||
f.expand(f.first, 250);
|
||||
f.expand(f.second, 250);
|
||||
f.options.documentRef.activeElement = f.second;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('collapsed'), true);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
});
|
||||
test(`${side} restored accordion prefers keyboard focus when no explicit owner exists`, () => {
|
||||
const f = fixture(side);
|
||||
f.expand(f.first, 250);
|
||||
f.expand(f.second, 250);
|
||||
f.options.documentRef.activeElement = f.second;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('collapsed'), true);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
});
|
||||
}
|
||||
|
||||
function element(
|
||||
id,
|
||||
@@ -173,7 +170,11 @@ function fixture(
|
||||
matchMedia: () => ({ matches: mobile }),
|
||||
getComputedStyle: (node) => node.computed,
|
||||
},
|
||||
onCollapse: (panel) => collapsed.push(panel.id),
|
||||
// The owner presents the patch; the panel's root draws it at once.
|
||||
onCollapse: (panel, patch) => {
|
||||
collapsed.push(panel.id);
|
||||
applyPanelState(panel, { ...readPanelState(panel), ...patch });
|
||||
},
|
||||
onRetry: () => {
|
||||
retries += 1;
|
||||
},
|
||||
@@ -250,16 +251,16 @@ for (const side of ['left', 'right']) {
|
||||
);
|
||||
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
|
||||
});
|
||||
test(`${side} constrained layout preserves the preferred panel and requests another pass`, () => {
|
||||
test(`${side} constrained layout keeps the preferred panel without another pass`, () => {
|
||||
const f = fixture(side);
|
||||
f.expand(f.first, 900);
|
||||
f.expand(f.second, 900);
|
||||
f.options.preferredPanelId = 'second';
|
||||
f.run();
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
assert.equal(f.first.classList.contains('layout-auto-collapsed'), true);
|
||||
assert.equal(f.first.classList.contains('accordion-collapsed'), true);
|
||||
assert.deepEqual(f.collapsed, ['first']);
|
||||
assert.equal(f.retries(), 1);
|
||||
assert.equal(f.retries(), 0);
|
||||
});
|
||||
test(`${side} hidden HUD restores automatic collapse without altering manual collapse`, () => {
|
||||
const f = fixture(side, { hud: { visible: false, variant: 'tactical' } });
|
||||
@@ -277,7 +278,7 @@ test('right layout uses keyboard focus when there is no preferred panel', () =>
|
||||
f.expand(f.second, 900);
|
||||
f.options.documentRef.activeElement = f.second;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('layout-auto-collapsed'), true);
|
||||
assert.equal(f.first.classList.contains('accordion-collapsed'), true);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
});
|
||||
|
||||
@@ -348,6 +349,32 @@ test('right rail aligns to the current left rail and excludes hidden obstacles',
|
||||
assert.equal(f.stack.dataset.safeTop, '200.0');
|
||||
});
|
||||
|
||||
// An opened panel moves the left stack up, but its rail buttons hold still:
|
||||
// the right rail lines up with those buttons, not with the moved stack.
|
||||
test('right rail aligns to the left rail buttons while the left stack is offset', () => {
|
||||
const f = fixture('right');
|
||||
f.options.leftStack.rect.top = 100;
|
||||
f.options.leftStack.style.setProperty('--panel-rail-offset', '186.7px');
|
||||
f.run();
|
||||
assert.equal(f.stack.dataset.safeTop, '286.7');
|
||||
});
|
||||
|
||||
test('left rail buttons hold still while a tall panel moves the stack', () => {
|
||||
const f = fixture('left');
|
||||
f.expand(f.first, 900);
|
||||
f.run();
|
||||
assert.equal(f.stack.dataset.layoutMode, 'focus');
|
||||
assert.ok(
|
||||
parseFloat(f.stack.style.getPropertyValue('--panel-rail-offset')) > 0,
|
||||
);
|
||||
|
||||
// A theme that pins the stack's top (Cyber) leaves nothing to offset.
|
||||
f.stack.computed.getPropertyValue = (name) =>
|
||||
name === '--panel-stack-top-pinned' ? ' 1' : '';
|
||||
f.run();
|
||||
assert.equal(f.stack.style.getPropertyValue('--panel-rail-offset'), '0.0px');
|
||||
});
|
||||
|
||||
// The left rail's `top` animates after the right pass runs, and nothing
|
||||
// re-runs it when the transition lands: the right rail must read the end value.
|
||||
function animatingLeftRail({ keyframes = true } = {}) {
|
||||
@@ -532,26 +559,27 @@ test('right rail enters focus only above available height and leaves below the f
|
||||
assert.equal(f.stack.classList.contains('layout-focus'), false);
|
||||
});
|
||||
|
||||
test('right rail auto-collapse requests only one retry and settles within two passes', () => {
|
||||
test('right rail settles a second tall panel through the accordion in one pass', () => {
|
||||
const f = thrashingRightRail({ weatherOpen: true });
|
||||
const modes = [];
|
||||
for (let pass = 0; pass < 10; pass++) {
|
||||
f.run();
|
||||
modes.push(f.stack.classList.contains('layout-focus'));
|
||||
modes.push(f.stack.dataset.layoutMode);
|
||||
}
|
||||
assert.deepEqual(f.collapsed, ['weather-panel']);
|
||||
assert.equal(f.retries(), 1);
|
||||
assert.deepEqual(modes.slice(1), Array(9).fill(true));
|
||||
assert.equal(f.weather.classList.contains('accordion-collapsed'), true);
|
||||
assert.equal(f.retries(), 0);
|
||||
assert.deepEqual(modes.slice(1), Array(9).fill(modes[0]));
|
||||
});
|
||||
|
||||
test('right rail retry cannot enqueue another retry even if disclosure changes before it runs', () => {
|
||||
test('right rail reopening a peer outside the rail keeps one owner without retrying', () => {
|
||||
const f = thrashingRightRail({ weatherOpen: true });
|
||||
f.run();
|
||||
assert.equal(f.retries(), 1);
|
||||
f.weather.classList.remove('collapsed', 'layout-auto-collapsed');
|
||||
f.weather.classList.remove('collapsed', 'accordion-collapsed');
|
||||
f.run();
|
||||
assert.equal(f.retries(), 1);
|
||||
assert.equal(f.stack.dataset.layoutMode, 'focus');
|
||||
assert.equal(f.retries(), 0);
|
||||
assert.equal(f.weather.classList.contains('collapsed'), true);
|
||||
assert.equal(f.cctv.classList.contains('collapsed'), false);
|
||||
});
|
||||
|
||||
test('right rail restores presentation even when intrinsic measurement throws', () => {
|
||||
@@ -595,14 +623,14 @@ test('right rail measuring pass keeps an opted-in scroller position', () => {
|
||||
});
|
||||
|
||||
for (const variant of ['minimal', 'full']) {
|
||||
test(`right rail does not retry collapse that the ${variant} HUD immediately restores`, () => {
|
||||
test(`right rail settles two tall panels without retrying in the ${variant} HUD`, () => {
|
||||
const f = fixture('right', { hud: { visible: true, variant } });
|
||||
f.expand(f.first, 900);
|
||||
f.expand(f.second, 900);
|
||||
for (let pass = 0; pass < 10; pass++) f.run();
|
||||
assert.equal(f.retries(), 0);
|
||||
assert.equal(f.first.classList.contains('collapsed'), false);
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
assert.equal(f.second.classList.contains('accordion-collapsed'), true);
|
||||
});
|
||||
}
|
||||
// ── Narrow-screen rail overflow pin ──────────────────────────────────────────
|
||||
@@ -654,10 +682,18 @@ function flatRules(block) {
|
||||
|
||||
test('narrow-screen rails pin every hosted panel glow inside its panel box', () => {
|
||||
const css = readStylesheet(new URL('../../style.css', import.meta.url));
|
||||
for (const panel of ['data-panel', 'scene-panel', 'cctv-panel']) {
|
||||
for (const panel of [
|
||||
'data-panel',
|
||||
'utilities-panel',
|
||||
'scene-panel',
|
||||
'cctv-panel',
|
||||
]) {
|
||||
// The glow rule may be shared: `#data-panel .panel-glow, #utilities-panel .panel-glow {`.
|
||||
assert.match(
|
||||
css,
|
||||
new RegExp(`#${panel} \\.panel-glow \\{[^}]*\\binset: -\\d+px;`),
|
||||
new RegExp(
|
||||
`#${panel} \\.panel-glow(?:,[^{]*)? \\{[^}]*\\binset: -\\d+px;`,
|
||||
),
|
||||
`${panel} glow no longer overhangs its panel; revisit this pin`,
|
||||
);
|
||||
}
|
||||
@@ -699,10 +735,11 @@ test('right layout ignores a CSS-hidden expanded panel when deciding exclusivity
|
||||
|
||||
f.run();
|
||||
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), false);
|
||||
// The rail lists the collapsed panels; the CSS-hidden one claims nothing.
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), true);
|
||||
assert.equal(f.stack.dataset.expandedCount, '0');
|
||||
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
|
||||
assert.equal(f.second.getAttribute('aria-hidden'), undefined);
|
||||
assert.equal(weather.classList.contains('collapsed'), false);
|
||||
assert.equal(weather.getAttribute('aria-hidden'), undefined);
|
||||
assert.equal(
|
||||
weather.style.getPropertyValue('--right-panel-allocated-height'),
|
||||
'',
|
||||
@@ -730,9 +767,10 @@ test('right layout still restores panels that exclusive presentation hides', ()
|
||||
|
||||
f.options.hud.visible = false;
|
||||
f.run();
|
||||
assert.equal(f.second.classList.contains('collapsed'), false);
|
||||
// Restored from the space-based collapse, it yields to the accordion owner.
|
||||
assert.equal(f.second.classList.contains('layout-auto-collapsed'), false);
|
||||
assert.deepEqual(f.collapsed, ['second']);
|
||||
assert.equal(f.second.classList.contains('accordion-collapsed'), true);
|
||||
assert.equal(f.first.classList.contains('collapsed'), false);
|
||||
});
|
||||
|
||||
test('right layout ignores a hidden panel: no lane, no gap, no auto-collapse', () => {
|
||||
@@ -766,10 +804,10 @@ test('right layout ignores a hidden panel: no lane, no gap, no auto-collapse', (
|
||||
const retriesBefore = f.retries();
|
||||
f.run();
|
||||
assert.ok(
|
||||
f.collapsed.includes('recent-imagery-panel'),
|
||||
'focus mode collapses the later panel and asks for another pass',
|
||||
imagery.classList.contains('accordion-collapsed'),
|
||||
'the accordion keeps the first panel and collapses the later one',
|
||||
);
|
||||
assert.equal(f.retries(), retriesBefore + 1);
|
||||
assert.equal(f.retries(), retriesBefore);
|
||||
f.run();
|
||||
assert.equal(f.stack.dataset.expandedCount, '1');
|
||||
assert.ok(
|
||||
@@ -778,3 +816,17 @@ test('right layout ignores a hidden panel: no lane, no gap, no auto-collapse', (
|
||||
) <= alone,
|
||||
);
|
||||
});
|
||||
|
||||
test('left rail keeps collapsed panels shown beside Cockpit, still one open', () => {
|
||||
const f = fixture('left');
|
||||
f.options.documentRef.body = {
|
||||
classList: { contains: (name) => name === 'cockpit-mode' },
|
||||
};
|
||||
f.expand(f.first, 250);
|
||||
f.expand(f.second, 250);
|
||||
f.options.preferredPanelId = f.second.id;
|
||||
f.run();
|
||||
assert.equal(f.first.classList.contains('accordion-collapsed'), true);
|
||||
assert.equal(f.stack.classList.contains('layout-exclusive'), false);
|
||||
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
|
||||
});
|
||||
Reference in new issue
Block a user