fix(ui): harden Cyber panel layout and preset restoration

This commit is contained in:
Manjunath committed 2026-10-01 01:50:31 +05:30
1 parent e7707d9a0f
commit e03bf008f3
8 files changed
+695 -16

No files matched your search

+205 -4
View File
@@ -9,6 +9,9 @@ import {
normalizeHudLayout,
} from '../hudLayouts.js';
import { GEV_ACTION_SCHEMAS } from '../voice/actionSchemas.js';
import { createGevActionRunner } from '../voice/gevActions.js';
import { StyleManager } from './applicationShell.js';
import { bindDisplayControls } from './displayControls.js';
import { VisualSettings } from './visualSettings.js';
import { STYLES } from './visualPresets.js';
@@ -137,6 +140,102 @@ test('non-Cyber style presets and explicit HUD choices remain authoritative', (t
}
});
test('leaving an explicitly selected Cyber layout restores the prior visual preset', (t) => {
const { owner, hud } = styleOwner(t, 'tactical');
owner.setStyle('noir');
owner._setHudVariant('cyber', { applyVisualDefaults: true });
assert.equal(owner.activeStyle, 'thermal');
assert.equal(owner._preCyberStyle, 'noir');
// A temporary choice made while Cyber is active does not replace the preset
// that was active before the user entered Cyber.
owner.setStyle('snow');
owner._setHudVariant('cyber', { applyVisualDefaults: true });
assert.equal(owner._preCyberStyle, 'noir');
owner._setHudVariant('operator', { applyVisualDefaults: true });
assert.equal(hud.getVariant(), 'operator');
assert.equal(owner.activeStyle, 'noir');
assert.equal(owner._preCyberStyle, null);
});
test('Display and voice HUD routes preserve explicit Cyber entry and exit authority', async (t) => {
const makeOwner = () => {
const { owner, hud } = styleOwner(t, 'tactical');
owner.setStyle('noir');
owner.setHudLayout = (...args) =>
StyleManager.prototype.setHudLayout.call(owner, ...args);
owner.getControlState = () => ({
hud: { layout: hud.getVariant(), visible: true },
});
return { owner, hud };
};
const display = makeOwner();
const hudLayout = new EventTarget();
hudLayout.value = 'cyber';
const controls = bindDisplayControls({
elements: { hudLayout },
actions: { setHudLayout: (value) => display.owner.setHudLayout(value) },
});
hudLayout.dispatchEvent(new Event('change'));
assert.equal(display.hud.getVariant(), 'cyber');
assert.equal(display.owner.activeStyle, 'thermal');
hudLayout.value = 'operator';
hudLayout.dispatchEvent(new Event('change'));
assert.equal(display.hud.getVariant(), 'operator');
assert.equal(display.owner.activeStyle, 'noir');
controls.destroy();
const voice = makeOwner();
const eventSource = { addEventListener() {}, removeEventListener() {} };
const viewer = {
camera: { moveEnd: { addEventListener() {} } },
clock: { onTick: { addEventListener: () => () => {} } },
scene: { canvas: eventSource },
};
const runVoiceAction = createGevActionRunner({
viewer,
styleManager: voice.owner,
dataManager: {},
});
const entered = await runVoiceAction('set_hud', { layout: 'cyber' });
assert.equal(entered.ok, true);
assert.equal(voice.hud.getVariant(), 'cyber');
assert.equal(voice.owner.activeStyle, 'thermal');
const exited = await runVoiceAction('set_hud', { layout: 'operator' });
assert.equal(exited.ok, true);
assert.equal(voice.hud.getVariant(), 'operator');
assert.equal(voice.owner.activeStyle, 'noir');
});
test('scene and share HUD transitions do not restore a local pre-Cyber preset', async (t) => {
const { owner, hud } = styleOwner(t, 'tactical');
owner.setStyle('noir');
owner._setHudVariant('cyber', { applyVisualDefaults: true });
await owner.applyVisualState({ style: 'retro', hud: { variant: 'cyber' } });
assert.equal(hud.getVariant(), 'cyber');
assert.equal(owner.activeStyle, 'retro');
assert.equal(owner._preCyberStyle, null);
owner._setHudVariant('operator', { applyVisualDefaults: true });
assert.equal(owner.activeStyle, 'retro');
owner.setStyle('surveillance');
owner._setHudVariant('cyber', { applyVisualDefaults: true });
assert.equal(owner._preCyberStyle, 'surveillance');
await owner.restoreShareState({
style: 'thermal',
hudVariant: 'cyber',
});
assert.equal(hud.getVariant(), 'cyber');
assert.equal(owner.activeStyle, 'thermal');
assert.equal(owner._preCyberStyle, null);
owner._setHudVariant('tactical', { applyVisualDefaults: true });
assert.equal(owner.activeStyle, 'thermal');
assert.equal(owner._preCyberStyle, null);
});
test('scene and share restoration override Cyber and preserve restored Cyber parameters', async (t) => {
const { owner, hud } = styleOwner(t);
await owner.applyVisualState({ style: 'retro', hud: { variant: 'minimal' } });
@@ -179,6 +278,104 @@ test('Cyber setup uses a compact toolbar icon and highlights the north-up letter
});
const cyberStyles = read('./styles/cyber.css');
// Source guards supplement rendered popup/hit-target checks. Inspect one rule
// at a time so a safe declaration on a child cannot excuse a clipped ancestor.
function cssRuleBody(styles, selector) {
const match = styles.match(new RegExp(`${selector.source}\\s*\\{([^}]+)\\}`));
assert.ok(match, `Missing CSS rule: ${selector.source}`);
return match[1];
}
test('Cyber Voice clips its decoration without clipping descendant help and errors', () => {
const owner = cssRuleBody(
cyberStyles,
/:root\[data-ui-theme='cyber'\] #command-dock > #gev-voice-control/,
);
assert.match(owner, /isolation:\s*isolate;/);
assert.match(owner, /overflow:\s*visible;/);
assert.match(owner, /clip-path:\s*none;/);
const decoration = cssRuleBody(
cyberStyles,
/:root\[data-ui-theme='cyber'\] #command-dock > #gev-voice-control::before/,
);
assert.match(decoration, /clip-path:\s*polygon\(/);
assert.match(decoration, /pointer-events:\s*none;/);
assert.match(decoration, /inset:\s*0;/);
assert.match(decoration, /z-index:\s*-1;/);
});
test('Cyber dock pins escape the tray mask and its decoration overrides the legacy arrow geometry', () => {
const owner = cssRuleBody(
cyberStyles,
/:root\[data-ui-theme='cyber'\] #command-dock \.dock-popover-content/,
);
assert.match(owner, /clip-path:\s*none;/);
assert.match(owner, /isolation:\s*isolate;/);
// Including the tray owners beats #control-panel's more specific old
// ::after arrow positioning. Otherwise left:auto can collapse the frame.
const decoration = cssRuleBody(
cyberStyles,
/:root\[data-ui-theme='cyber'\]\s+#command-dock\s+:is\(#location-bar, #control-panel\)\s+\.dock-popover-content::after/,
);
assert.match(decoration, /inset:\s*0;/);
assert.match(decoration, /width:\s*auto;/);
assert.match(decoration, /height:\s*auto;/);
assert.match(decoration, /transform:\s*none;/);
assert.match(decoration, /clip-path:\s*var\(--cyber-panel-frame\);/);
assert.match(decoration, /pointer-events:\s*none;/);
});
test('Cyber lower-left telemetry tracks the credit anchor with clearance for its logo row', () => {
const telemetry = cssRuleBody(
cyberStyles,
/#intel-hud\[data-variant='cyber'\] \.hud-bottom-left/,
);
const offset = telemetry.match(/bottom:\s*calc\(2vh \+ ([\d.]+)rem\);/);
assert.ok(offset, 'telemetry must retain the attribution viewport anchor');
const credits = read('./styles/command-dock-trays.css');
const creditOffsets = [
...credits.matchAll(
/#cesium-credits\s*\{[^}]*bottom:\s*calc\(2vh \+ ([\d.]+)rem\);/g,
),
];
assert.ok(creditOffsets.length > 0);
for (const credit of creditOffsets) {
assert.ok(
Number(offset[1]) - Number(credit[1]) >= 2.75,
'reserve the 28px logo row, baseline descent and 12px clearance at the 16px root size',
);
}
assert.doesNotMatch(cyberStyles, /\.hud-bottom-left\s*\{\s*bottom:\s*60px;/);
});
test('shared panel hooks affect only opted-in rail surfaces without overriding disclosure and concealment', () => {
const styles = read('./styles/panel-surfaces.css');
const rail = cssRuleBody(
styles,
/:is\(#left-panel-stack, #right-context-rail\)\s+> \[data-panel-id\]:has\(> \[data-panel-surface\]\)/,
);
assert.match(rail, /pointer-events:\s*auto;/);
assert.doesNotMatch(rail, /(?:display|visibility):/);
assert.doesNotMatch(
styles,
/:is\(#left-panel-stack, #right-context-rail\)\s*> \[data-panel-id\]\s*\{/,
);
assert.match(
styles,
/\[data-panel-surface\]:not\(\[hidden\]\):not\(dialog:not\(\[open\]\)\)\s*\{\s*display: flex;/,
);
assert.match(
styles,
/dialog\[data-panel-surface\]:not\(\[open\]\)\s*\{\s*display: none !important;/,
);
assert.match(
styles,
/body:is\(\.ui-clean-view, \.recording-mode\)[^{]+\{[^}]*visibility: hidden !important;[^}]*pointer-events: none !important;/,
);
});
test('Cyber clipped expand controls retain an inset keyboard-focus outline in map and cockpit', () => {
assert.match(
cyberStyles,
@@ -330,6 +527,7 @@ test('Cyber visual defaults update the style, thermal palette and render state',
test('the Display selector, voice schema and final stylesheet expose Cyber', () => {
const display = read('./templates/display-controls.html');
const stylesheet = read('../../style.css');
const rightRail = read('./rightPanelRail.js');
const hud = read('../hud.js');
const detection = read('../data/detection.js');
const civilRendering = read('../layers/flights/rendering.js');
@@ -415,21 +613,24 @@ test('the Display selector, voice schema and final stylesheet expose Cyber', ()
assert.match(cyberStyles, /--cyber-hud-card-top: max\(100px, 10\.25vh\);/);
assert.match(
cyberStyles,
/--cyber-left-rail-top: max\([\s\S]*?252px,[\s\S]*?calc\(var\(--cyber-hud-card-top\) \+ 146px\)/,
/--cyber-left-rail-top: max\([\s\S]*?220px,[\s\S]*?calc\(var\(--cyber-hud-card-top\) \+ 120px\)/,
);
assert.match(
cyberStyles,
/\.hud-top-left \{[\s\S]*?top: var\(--cyber-hud-card-top\);/,
);
assert.match(cyberStyles, /\.hud-bottom-left \{\s*bottom: 60px;/);
assert.match(
cyberStyles,
/#left-panel-stack \{[\s\S]*?top: var\(--cyber-left-rail-top\) !important;/,
/#left-panel-stack \{[\s\S]*?top: var\(--cyber-left-rail-top\) !important;[\s\S]*?bottom: calc\(2vh \+ 13rem\);/,
);
assert.match(
cyberStyles,
/\.hud-left-edge \{\s*top: calc\(var\(--cyber-left-rail-top\) - 18px\);\s*left: 14px;/,
);
assert.match(
rightRail,
/const leftStackTop = leftStack\?\.getBoundingClientRect\(\)\.top;[\s\S]*?const alignedTop = Number\.isFinite\(leftStackTop\)[\s\S]*?baseTop: alignedTop,/,
);
assert.match(
cyberStyles,
/--right-display-collapsed-width: var\(--cyber-panel-collapsed-width\);/,
@@ -453,7 +654,7 @@ test('the Display selector, voice schema and final stylesheet expose Cyber', ()
assert.match(cyberStyles, /#left-panel-stack:has/);
assert.match(cyberStyles, /left: 54px;/);
assert.match(cyberStyles, /right: 62px;/);
assert.match(cyberStyles, /bottom: calc\(2vh \+ 11rem\);/);
assert.match(cyberStyles, /bottom: calc\(2vh \+ 13rem\);/);
assert.doesNotMatch(cyberStyles, /NAV 01 \/\/ COMMAND BUS \/\/ VIS 03/);
assert.match(cyberStyles, /data-layer-id='satellites'/);
assert.match(cyberStyles, /#search-toggle::before/);
+63 -10
View File
@@ -185,6 +185,11 @@
0 8px 20px rgba(0, 0, 0, 0.28);
}
/* Compact Cyber rails must wrap multi-word actions within their own button. */
:root[data-ui-theme='cyber'] #scene-panel .scene-controls .scene-btn {
white-space: normal;
}
/* Carry the raised voice-module silhouette across the primary panel system.
The six-pixel calibration bay is decorative and stays outside panel content. */
:root[data-ui-theme='cyber']
@@ -924,7 +929,24 @@
--cyber-voice-wave: 105, 203, 209;
--cyber-voice-peak: 226, 240, 240;
--cyber-voice-alert: 196, 84, 84;
border-color: rgba(196, 84, 84, 0.58);
/* Popups extend above this root. Only its decorative layer may be clipped. */
isolation: isolate;
overflow: visible;
clip-path: none;
border-color: transparent;
background: none;
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
:root[data-ui-theme='cyber'] #command-dock > #gev-voice-control::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
pointer-events: none;
border: 1px solid rgba(196, 84, 84, 0.58);
border-radius: 0;
background:
linear-gradient(180deg, rgba(196, 84, 84, 0.14), transparent 34%),
@@ -1192,8 +1214,30 @@
:root[data-ui-theme='cyber'] #command-dock .dock-popover-content {
padding-top: 1.65rem;
border: 1px solid rgba(196, 84, 84, 0.62);
/* The pin straddles the tray edge; keep it outside the decorative mask. */
isolation: isolate;
clip-path: none;
border-color: transparent;
border-radius: 0;
background: none;
box-shadow: none;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
:root[data-ui-theme='cyber']
#command-dock
:is(#location-bar, #control-panel)
.dock-popover-content::after {
content: '';
position: absolute;
inset: 0;
z-index: -1;
width: auto;
height: auto;
transform: none;
pointer-events: none;
border: 1px solid rgba(196, 84, 84, 0.62);
background:
linear-gradient(135deg, rgba(62, 86, 94, 0.22), transparent 46%),
linear-gradient(rgba(188, 211, 218, 0.045) 1px, transparent 1px),
@@ -1456,6 +1500,12 @@
bottom: calc(2vh + 6.5rem);
}
/* Attribution's logo row extends above its text baseline. Reserve the whole
row plus a gap above the dock-relative 2vh + 5rem credit position. */
#intel-hud[data-variant='cyber'] .hud-bottom-left {
bottom: calc(2vh + 7.75rem);
}
#intel-hud[data-variant='cyber'] .hud-corner {
position: fixed;
isolation: isolate;
@@ -1493,12 +1543,21 @@
/* Cyber also owns a distinct desktop composition. Existing components keep
their controls and semantics, but move onto asymmetric mission-deck rails. */
@media (min-width: 961px) and (max-height: 649px) {
/* The compact-height layout drops the desktop rail composition one pixel
below its 650px threshold. Keep the left rail inside the same telemetry
and attribution-safe floor while its panel bodies take over scrolling. */
:root[data-ui-theme='cyber'] #left-panel-stack {
bottom: calc(2vh + 13rem);
}
}
@media (min-width: 961px) and (min-height: 650px) {
:root[data-ui-theme='cyber'] {
/* Compose the upper mission card and both panel rails as one compact lane.
The shared rail anchor keeps expanded left/right panels aligned. */
--cyber-hud-card-top: max(100px, 10.25vh);
--cyber-left-rail-top: max(252px, calc(var(--cyber-hud-card-top) + 146px));
--cyber-left-rail-top: max(220px, calc(var(--cyber-hud-card-top) + 120px));
}
/* Keep the rotated COLL / ONA telemetry in the upper clearance lane. Its
@@ -1510,7 +1569,7 @@
:root[data-ui-theme='cyber'] #left-panel-stack {
top: var(--cyber-left-rail-top) !important;
bottom: calc(2vh + 11rem);
bottom: calc(2vh + 13rem);
left: 54px;
width: 300px;
padding-left: 10px;
@@ -1709,12 +1768,6 @@
right: 28px;
bottom: 28px;
}
/* Match the lower telemetry cards by their top edge while leaving the
shorter coordinate card clear of Cesium attribution. */
#intel-hud[data-variant='cyber'] .hud-bottom-left {
bottom: 60px;
}
}
@media (max-width: 720px) {
+230
View File
@@ -0,0 +1,230 @@
/* Shared structural hooks for panels admitted by the existing rail owners.
Specific legacy component rules retain priority; no panel IDs are registered
here. The rail itself remains pointer-transparent over the globe. */
:is(#left-panel-stack, #right-context-rail)
> [data-panel-id]:has(> [data-panel-surface]) {
position: relative;
inset: auto;
flex: 0 0 auto;
min-height: 0;
pointer-events: auto;
}
#left-panel-stack
> [data-panel-id]:has(> [data-panel-surface]):not(.collapsed) {
flex: 0 1 var(--left-panel-allocated-height, auto);
height: var(--left-panel-allocated-height, auto);
max-height: var(--left-panel-allocated-height, 100%);
}
#left-panel-stack.layout-tail
> [data-panel-id]:has(> [data-panel-surface]):not(.collapsed) {
flex: 0 0 var(--left-panel-allocated-height);
}
#right-context-rail.layout-focus
> [data-panel-id]:has(> [data-panel-surface]):not(.collapsed) {
flex: 0 1 var(--right-panel-allocated-height);
height: var(--right-panel-allocated-height);
max-height: var(--right-panel-allocated-height);
}
/* Opt-in skin: the positioned surface can contain an outward popup. Only the
non-interactive decoration is clipped; only data-panel-body scrolls. */
:root {
--panel-surface-font: var(--font-sans);
--panel-surface-detail: none;
--panel-surface-frame: none;
--panel-surface-shadow: 0 8px 24px rgba(0, 0, 0, 0.38);
--panel-surface-title: var(--text-secondary);
--panel-surface-error: #ff9b8e;
--panel-surface-width: 300px;
--panel-surface-collapsed-width: var(--left-collapsed-width);
}
:root[data-ui-theme='cyber'] {
--panel-surface-font: var(--font-mono);
--panel-surface-detail: var(--cyber-panel-surface-detail);
--panel-surface-frame: var(--cyber-panel-frame);
--panel-surface-shadow: var(--cyber-panel-shadow);
--panel-surface-title: var(--cyber-red-bright);
--panel-surface-error: var(--cyber-red-bright);
--panel-surface-width: var(--cyber-panel-width);
--panel-surface-collapsed-width: var(--cyber-panel-collapsed-width);
}
:root[data-ui-theme='cyber']
#right-context-rail
> [data-panel-id]:has(> [data-panel-surface]) {
--panel-surface-width: var(--cyber-right-panel-width);
}
:is([data-panel-surface], [data-panel-popup]) {
isolation: isolate;
min-width: 0;
min-height: 0;
padding: 12px;
border: 1px solid transparent;
border-radius: var(--panel-radius);
background: none;
color: var(--text-primary);
font: 12px/1.5 var(--panel-surface-font);
overflow: visible;
}
[data-panel-surface]:not(dialog) {
position: relative;
}
:is([data-panel-surface], [data-panel-popup])::before {
content: '';
position: absolute;
inset: -1px;
z-index: -1;
border: 1px solid var(--glass-border);
border-radius: inherit;
background-color: var(--glass-bg);
background-image: var(--panel-surface-detail);
box-shadow: var(--panel-surface-shadow);
clip-path: var(--panel-surface-frame);
pointer-events: none;
}
/* A closed native dialog must retain the browser's display:none. */
[data-panel-surface]:not([hidden]):not(dialog:not([open])) {
display: flex;
flex-direction: column;
gap: 10px;
}
:is(#left-panel-stack, #right-context-rail)
> [data-panel-id]:has(> [data-panel-surface]) {
width: min(100%, var(--panel-expanded-width, var(--panel-surface-width)));
background: none;
box-shadow: none;
}
:is(#left-panel-stack, #right-context-rail)
> [data-panel-id]
> [data-panel-surface] {
height: 100%;
max-height: 100%;
}
:is(#left-panel-stack, #right-context-rail)
> [data-panel-id].collapsed:has(> [data-panel-surface]) {
width: min(
100%,
var(--panel-collapsed-width, var(--panel-surface-collapsed-width))
);
}
#right-context-rail[data-rail-measuring]
> [data-panel-id]
> [data-panel-surface] {
height: auto;
max-height: none;
}
[data-panel-header] {
display: flex;
align-items: center;
flex: 0 0 auto;
gap: 8px;
min-height: 30px;
padding-bottom: 8px;
border-bottom: 1px solid var(--glass-border);
}
.panel-surface-title {
flex: 1;
min-width: 0;
color: var(--panel-surface-title);
font: 600 11px/1.4 var(--font-mono);
letter-spacing: 0.1em;
text-transform: uppercase;
overflow-wrap: anywhere;
}
[data-panel-body] {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
scrollbar-color: var(--glass-border-hover) transparent;
/* Keep an inset focus ring inside the intentional scrolling boundary. */
padding: 3px;
}
.panel-surface-control {
min-width: 0;
min-height: 30px;
max-width: 100%;
padding: 5px 8px;
border: 1px solid var(--glass-border);
border-radius: var(--btn-radius);
background: var(--bg-dark);
color: var(--text-primary);
font: inherit;
accent-color: var(--accent);
}
button.panel-surface-control {
cursor: pointer;
}
.panel-surface-control:hover:not(:disabled):not([aria-disabled='true']) {
border-color: var(--glass-border-hover);
background: var(--accent-dim);
}
.panel-surface-control:focus-visible {
outline: 2px solid var(--text-primary);
outline-offset: -3px;
}
.panel-surface-control:is(:disabled, [aria-disabled='true']) {
opacity: 0.5;
cursor: not-allowed;
}
.panel-surface-error,
.panel-surface-control[aria-invalid='true'] {
color: var(--panel-surface-error);
border-color: var(--panel-surface-error);
}
[data-panel-popup] {
position: absolute;
z-index: 5;
/* The owner supplies placement, width, collision handling and disclosure. */
}
[data-panel-surface].collapsed > :is([data-panel-body], [data-panel-popup]),
[data-panel-id].collapsed
> [data-panel-surface]
> :is([data-panel-body], [data-panel-popup]),
:is(#left-panel-stack, #right-context-rail)
> [data-panel-id]:has(> [data-panel-surface])[hidden],
:is(
[data-panel-surface],
[data-panel-header],
[data-panel-body],
[data-panel-popup]
)[hidden],
dialog[data-panel-surface]:not([open]) {
display: none !important;
}
/* Conforming additions follow the same concealment policy as existing chrome.
Cockpit continues to own rail/dock placement and exclusions in cockpit.css. */
body:is(.ui-clean-view, .recording-mode)
:is([data-panel-surface], [data-panel-popup]),
body:is(.ui-clean-view, .recording-mode)
:is(#left-panel-stack, #right-context-rail)
> [data-panel-id]:has(> [data-panel-surface]) {
opacity: 0 !important;
visibility: hidden !important;
pointer-events: none !important;
}
+21
View File
@@ -196,6 +196,7 @@ export class VisualSettings {
releaseRender: services.releaseContinuousRender,
});
this.activeStyle = 'normal';
this._preCyberStyle = null;
document.documentElement.dataset.gevStyle = this.activeStyle;
this._detectionUserOverridden = false;
this._cockpitVisionMode = 'optical';
@@ -614,6 +615,16 @@ export class VisualSettings {
const previousVariant = this.hud.getVariant();
this.hud.setVariant(variantName);
const nextVariant = this.hud.getVariant();
const enteredCyber = previousVariant !== 'cyber' && nextVariant === 'cyber';
const leftCyber = previousVariant === 'cyber' && nextVariant !== 'cyber';
if (!applyVisualDefaults) {
// Scene/share/programmatic state is authoritative even when it reapplies
// the current Cyber variant. Do not let an older explicit entry restore
// stale local preset memory on the next user-selected exit.
this._preCyberStyle = null;
} else if (enteredCyber) {
this._preCyberStyle = this.activeStyle;
}
if (this._hudLayoutSelect && this._hudLayoutSelect.value !== nextVariant) {
this._hudLayoutSelect.value = nextVariant;
}
@@ -623,6 +634,16 @@ export class VisualSettings {
{ explicit: applyVisualDefaults },
);
if (visualDefaults) this._applyCyberVisualDefaults(visualDefaults);
if (leftCyber) {
const restoreStyle = applyVisualDefaults ? this._preCyberStyle : null;
this._preCyberStyle = null;
if (restoreStyle && restoreStyle !== this.activeStyle) {
this.setStyle(restoreStyle, {
applyPreset: false,
revealParameters: false,
});
}
}
this._syncCyberSonarControl();
this._syncShareState();
this._scheduleAdaptivePanelLayout({ settle: true });