Hide map panels when entering the cockpit

This commit is contained in:
German Khokhlov committed 2026-11-02 22:21:45 +04:00
1 parent 2087b55b47
commit 73ea608881
4 files changed
+51 -7

No files matched your search

+30 -6
View File
@@ -4,6 +4,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import puppeteer from 'puppeteer';
import { blockPaidQaMaps } from './qa-map-policy.mjs';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const shotsDir = path.join(repoRoot, 'qa-shots', 'cockpit-utility');
@@ -29,6 +30,7 @@ const browser = await puppeteer.launch({
],
});
const page = await browser.newPage();
await blockPaidQaMaps(page);
const failures = [];
const consoleErrors = [];
const localHttpErrors = [];
@@ -827,6 +829,7 @@ try {
const readinessPage = await browser.newPage();
let deferredInstallationReadiness;
try {
await blockPaidQaMaps(readinessPage);
await readinessPage.setViewport({ width: 1440, height: 900 });
readinessPage.on('pageerror', (error) => consoleErrors.push(error.message));
await readinessPage.setRequestInterception(true);
@@ -1150,6 +1153,14 @@ try {
},
{ timeout: 10_000 },
);
const startsClear = await page.evaluate(() => {
const hidden = ['left-panel-stack', 'intel-hud', 'corner-actions',
'cockpit-context', 'cockpit-signal-stream', 'cockpit-utility-controls'];
return hidden.every((id) => document.getElementById(id)?.getClientRects().length === 0)
&& document.getElementById('cockpit-speed-value').getClientRects().length > 0;
});
check('Cockpit starts with flight instruments and hides the map panels', startsClear);
await page.click('#cockpit-panels-toggle');
const firstCockpitContact = await page.evaluate(() => {
const awareness = window.__godsEyeView.dataManager.layers
.get('military-awareness')?.module;
@@ -1511,6 +1522,9 @@ try {
const before = read();
document.getElementById('cockpit-entry')?.click();
await new Promise((resolve) => setTimeout(resolve, 120));
const startsClear = document.getElementById('cockpit-panels-toggle').getAttribute('aria-pressed') === 'false';
document.getElementById('cockpit-panels-toggle').click();
await new Promise((resolve) => requestAnimationFrame(resolve));
const during = read();
manager.setPanelCollapsed('data-panel', false, { persist: false, syncShare: false });
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
@@ -1531,6 +1545,7 @@ try {
before,
during,
after: read(),
startsClear,
exited,
clearancePx,
contactAutoCollapsed,
@@ -1540,7 +1555,7 @@ try {
});
check(
'Cockpit temporarily collapses standard panels and restores the exact map layout on exit',
cockpitPanelRoundTrip.exited
cockpitPanelRoundTrip.exited && cockpitPanelRoundTrip.startsClear
&& Object.values(cockpitPanelRoundTrip.during).every(Boolean)
&& JSON.stringify(cockpitPanelRoundTrip.after)
=== JSON.stringify(cockpitPanelRoundTrip.before)
@@ -1605,6 +1620,7 @@ try {
const states = [read()];
for (let index = 0; index < 6; index += 1) {
current.click();
await Promise.resolve();
states.push(read());
}
const originalStyle = manager.activeStyle;
@@ -1616,8 +1632,10 @@ try {
const enteredOnNoir = await reenter();
const entryNoir = read();
cockpit.setVisionMode('optical');
await Promise.resolve();
const normalInsideNoir = read();
cockpit.setVisionMode('crt');
await Promise.resolve();
const cockpitOnlyCrt = read();
const exitedNoir = cockpit.exit({ restoreTracking: true }) === true;
await settle();
@@ -1692,6 +1710,9 @@ try {
await page.screenshot({ path: path.join(shotsDir, 'vision-noir.png') });
await page.evaluate(() => window.__godsEyeView.styleManager.cockpitView.setVisionMode('optical'));
await page.$eval('#cockpit-panels-toggle', button => {
if (button.getAttribute('aria-pressed') === 'false') button.click();
});
const desktopState = async (variant, openKind) => page.evaluate(async ({ variantName, kind }) => {
const manager = window.__godsEyeView.styleManager;
const hud = document.getElementById('cockpit-hud');
@@ -1877,7 +1898,7 @@ try {
// the wall Cockpit actually solves against — not by injecting an anchor.
// The card is bottom-anchored, so an explicit height moves its top edge.
const minTop = Math.max(96, window.innerHeight * 0.12);
const moveSignalTopTo = (wantTop) => {
const moveSignalTopTo = async (wantTop) => {
for (let pass = 0; pass < 8; pass += 1) {
const rect = signal.getBoundingClientRect();
const delta = wantTop - rect.top;
@@ -1890,9 +1911,11 @@ try {
// the now-shorter strip, so idempotence is asserted where the strip's
// composition is unchanged.)
manager.cockpitView.syncSignalLayout();
await Promise.resolve();
const firstPassTop = hud.style.getPropertyValue('--cockpit-utility-top');
const firstPassPrimaryOnly = utility.classList.contains('layout-primary-only');
manager.cockpitView.syncSignalLayout();
await Promise.resolve();
return {
firstPassTop,
firstPassPrimaryOnly,
@@ -1903,6 +1926,7 @@ try {
signal.style.maxHeight = 'none';
signal.style.height = '150px';
manager.cockpitView.syncSignalLayout();
await Promise.resolve();
const expandedHeight = Math.max(displayControl.scrollHeight, displayControl.getBoundingClientRect().height);
const collapsedHeight = Math.max(50, radioControl.scrollHeight);
const stripHeight = expandedHeight + collapsedHeight + 7;
@@ -1927,11 +1951,11 @@ try {
// because 150 px of card leaves a corridor sized for the OLD strip. Seed from
// the measured requirement instead, which holds for any future panel height,
// and assert the precondition rather than assuming it.
const roomySeed = moveSignalTopTo(exactSignalTop + 80);
const roomySeed = await moveSignalTopTo(exactSignalTop + 80);
const roomySeedExpanded = !utility.classList.contains('layout-primary-only')
&& roomySeed.firstPassPrimaryOnly === false;
const exact = {
...moveSignalTopTo(exactSignalTop),
...await moveSignalTopTo(exactSignalTop),
utilityTop: hud.style.getPropertyValue('--cockpit-utility-top'),
primaryOnly: utility.classList.contains('layout-primary-only'),
siblingVisible: getComputedStyle(radioControl).display !== 'none',
@@ -1941,7 +1965,7 @@ try {
const constrained = {
// One pixel on the far side of the same fit (`exactSignalTop` already sits
// one pixel inside it), so this is still the one-pixel constraint it claims.
...moveSignalTopTo(exactSignalTop - 2 * BOUNDARY_EPSILON_PX),
...await moveSignalTopTo(exactSignalTop - 2 * BOUNDARY_EPSILON_PX),
utilityTop: hud.style.getPropertyValue('--cockpit-utility-top'),
primaryOnly: utility.classList.contains('layout-primary-only'),
siblingDisplay: getComputedStyle(radioControl).display,
@@ -1952,7 +1976,7 @@ try {
<= signal.getBoundingClientRect().top - 7,
};
const restored = {
...moveSignalTopTo(exactSignalTop + 80),
...await moveSignalTopTo(exactSignalTop + 80),
utilityTop: hud.style.getPropertyValue('--cockpit-utility-top'),
primaryOnly: utility.classList.contains('layout-primary-only'),
siblingVisible: getComputedStyle(radioControl).display !== 'none',
+3
View File
@@ -210,6 +210,9 @@ export class CockpitViewController {
this._listen(this.entry, 'click', () => this.enter());
this._listen(this.tr3bToggle, 'click', () => this.toggleTrackedTr3b());
this._listen(this.mapViewButton, 'click', () => this.exit());
this._listen(document.getElementById('view-switcher'), 'click', (event) => {
if (event.target.closest?.('#cockpit-panels-toggle')) this.scheduleContextLayout();
});
this._listen(this.visionPrevious, 'click', () => this.cycleVisionMode(-1));
this._listen(this.visionCurrent, 'click', () => this.cycleVisionMode(1));
this._listen(this.visionNext, 'click', () => this.cycleVisionMode(1));
+7
View File
@@ -1984,6 +1984,13 @@ body.cockpit-mode
) {
display: none !important;
}
/* The visor starts with flight instruments. Panels restores the optional
displays for this flight without changing the map's panel preferences. */
body.cockpit-mode:has(#cockpit-panels-toggle[aria-pressed='false'])
:is(#left-panel-stack, #intel-hud, #corner-actions, #global-loading-status,
#cockpit-utility-controls, #cockpit-context, #cockpit-signal-stream) {
display: none !important;
}
body.cockpit-mode #left-panel-stack {
z-index: 147;
display: flex;
+11 -1
View File
@@ -1,6 +1,6 @@
<script setup>
import { storeToRefs } from 'pinia';
import { computed } from 'vue';
import { computed, ref, watch } from 'vue';
import { t } from '../../i18n/index.js';
import {
createInstrumentState,
@@ -9,6 +9,7 @@ import {
} from '../cockpitInstrumentState.js';
import { vClassToggle } from '../rail/classToggle.js';
import { useCockpitStore } from '../stores/cockpit.js';
import { useRadioStore } from '../stores/radio.js';
import { useGlobeActionsStore } from '../stores/globeActions.js';
import CockpitBriefing from './panels/CockpitBriefing.vue';
import CockpitContext from './panels/CockpitContext.vue';
@@ -23,6 +24,12 @@ import CockpitUtilityControls from './panels/CockpitUtilityControls.vue';
const { presence, instruments, route, vision, utilityLayout } =
storeToRefs(useCockpitStore());
const active = computed(() => Boolean(presence.value?.active));
const panelsOpen = ref(false);
const radio = useRadioStore();
watch(active, () => { panelsOpen.value = false; });
watch(() => radio.utility?.display || radio.utility?.radio, (open) => {
if (active.value && open) panelsOpen.value = true;
});
const globe = useGlobeActionsStore();
const { resetBusy } = storeToRefs(globe);
const initialInstruments = createInstrumentState();
@@ -200,6 +207,9 @@ const tickStyle = (tick) =>
</section>
<nav id="view-switcher" :aria-label="t('View switcher')">
<button v-if="active" id="cockpit-panels-toggle" type="button" :aria-pressed="panelsOpen" @click="panelsOpen = !panelsOpen">
<span>{{ t('Panels') }}</span>
</button>
<button id="cockpit-reset-globe" type="button" :aria-label="resetBusy ? t('Resetting cockpit to full globe view') : t('Reset cockpit to full globe view')" :title="t('Exit cockpit and return to full globe view')" :hidden="!active" @click="globe.act('reset')">
<span class="material-symbols-outlined" aria-hidden="true">public</span>
<span>{{ t('RESET') }}</span>