Collapse the CCTV panel when the layer is switched off

The panel opened itself when a camera became active, but nothing closed it
when the layer went off: it stayed expanded, empty, and its meta line still
described the last camera. The collapse runs only on the on-to-off change, so a
panel opened by hand while the layer is off stays open, and a context-mode
layer clear keeps the panel as Radio does.
This commit is contained in:
Said Turksever committed 2026-11-13 17:26:34 +00:00
1 parent 7df581765d
commit 0325805eb0
4 files changed
+97 -1

No files matched your search

+2
View File
@@ -1197,6 +1197,8 @@ export class StyleManager extends ShellFacade {
toggleEnabled: (...args) => this._toggleCctvEnabled(...args),
runExplicitFocus: (...args) => this._runExplicitCctvFocus(...args),
setPanelCollapsed: (...args) => this.setPanelCollapsed(...args),
preservePanelStateDuringClear: () =>
this._preservePanelStateDuringLayerClear,
showToast: (message) => this._showToast(message),
syncViewport: () => this._syncCctvPanelViewport(),
},
+1
View File
@@ -54,6 +54,7 @@ export class CctvControls {
this._cctvSummaryTypingTimer = null;
this._lastCctvSummaryText = '';
this._lastSeenCctvActiveId = null;
this._wasCctvEnabled = false;
this._cctvChipHideTimer = null;
this._cctvChipWasBusy = false;
// The camera grid chip as last published; its label stays while hidden.
+78
View File
@@ -513,3 +513,81 @@ test('the panel meta line reads a bearing that rounds to north as HDG 0°', () =
assert.match(meta(359.4), /· HDG 359° ·/);
assert.match(meta(359.6), /· HDG 0° ·/);
});
/** The panel collapses the test asked for, as `[panelId, collapsed]` pairs. */
function collapseCalls(controls) {
const calls = [];
controls.actions.setPanelCollapsed = (panelId, collapsed) => {
calls.push([panelId, collapsed]);
// The panel chrome owns the class the controls read back.
controls._cctvPanel.classList.toggle('collapsed', collapsed);
};
return calls;
}
const activeState = {
enabled: true,
count: 1,
cameraIndex: [],
activeCameraId: camera.id,
activeCamera: camera,
};
test('switching the layer off collapses the panel it opened', (t) => {
const { controls } = panel(t);
const calls = collapseCalls(controls);
controls._renderCctvState(activeState);
assert.deepEqual(calls, [['cctv-panel', false]]);
// The layer keeps its active camera while off.
controls._renderCctvState({ ...activeState, enabled: false });
assert.deepEqual(calls.at(-1), ['cctv-panel', true]);
// Later notifications while off leave a panel the user reopened alone.
controls._renderCctvState({ ...activeState, enabled: false });
assert.equal(calls.length, 2);
});
test('a layer that was never on leaves an open panel alone', (t) => {
const { controls } = panel(t);
const calls = collapseCalls(controls);
controls._renderCctvState({ enabled: false, count: 3, cameraIndex: [] });
controls._renderCctvState({ enabled: false, count: 3, cameraIndex: [] });
assert.deepEqual(calls, []);
});
test('the data manager switching the layer off collapses the panel too', (t) => {
const { controls } = panel(t);
const calls = collapseCalls(controls);
let on = true;
controls.actions.isEnabled = () => on;
controls._renderCctvState(activeState);
on = false;
controls._renderCctvState(activeState);
assert.deepEqual(calls.at(-1), ['cctv-panel', true]);
});
test('a context-mode layer clear keeps the panel open', (t) => {
const { controls } = panel(t);
const calls = collapseCalls(controls);
controls.actions.preservePanelStateDuringClear = () => true;
controls._renderCctvState(activeState);
controls._renderCctvState({ ...activeState, enabled: false });
assert.deepEqual(calls, [['cctv-panel', false]]);
});
test('an already collapsed panel is not collapsed again', (t) => {
const { controls } = panel(t);
controls._renderCctvState(activeState);
const calls = collapseCalls(controls);
controls._cctvPanel.classList.add('collapsed');
controls._renderCctvState({ ...activeState, enabled: false });
assert.deepEqual(calls, []);
});
test('the meta line of a disabled layer does not describe its last camera', () => {
const off = { ...activeState, enabled: false, count: 3 };
assert.equal(
presentCctvPanel(off, false).meta,
'3 cameras loaded · enable CCTV to activate',
);
assert.match(presentCctvPanel(activeState, true).meta, /^Austin · HDG /);
});
+16 -1
View File
@@ -90,7 +90,9 @@ export function presentCctvPanel(state, enabled) {
}
function metaText(state, enabled, activeCamera, catalogCount) {
if (activeCamera) {
// The layer keeps its active camera while off: a disabled panel must not
// describe a feed it no longer shows.
if (enabled && activeCamera) {
const provider = cctvSourceLabelText(
activeCamera.sourceLabel || activeCamera.provider || 'Configured Source',
);
@@ -188,6 +190,19 @@ export function _renderCctvState(state) {
});
}
this._lastSeenCctvActiveId = effectiveActiveId;
// Switching the layer off closes the panel it opened: nothing is left to
// show. Only the on → off change collapses, so a panel the user opens
// while the layer is off stays open, and a context-mode layer clear keeps
// the panel for the layers it restores.
if (
this._wasCctvEnabled &&
!enabled &&
!this.actions.preservePanelStateDuringClear?.() &&
!this._cctvPanel?.classList?.contains('collapsed')
) {
this.actions.setPanelCollapsed('cctv-panel', true);
}
this._wasCctvEnabled = enabled;
this._updateCctvSyncChip(state?.loading, enabled);