mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Merge pull request #1723 from bilawalsidhu/fix/cctv-panel-collapse-on-disable
Collapse the CCTV panel when the layer is switched off
This commit is contained in:
11 files changed
+289
-4
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
- Collapse the CCTV panel when the layer is switched off. It stayed open with
|
||||||
|
an empty frame and a meta line that still described the last camera; the
|
||||||
|
meta line of a disabled layer now reads the catalog count. Contacts, Space
|
||||||
|
Missions and Clear selected data layers leave the panel as it is
|
||||||
|
(asturksever, #1723).
|
||||||
@@ -2229,6 +2229,17 @@ shown without a fade once it is decoded (or after one second if its decode never
|
|||||||
settles), so a new camera's preview is blank only while its frame loads. Disposal
|
settles), so a new camera's preview is blank only while its frame loads. Disposal
|
||||||
releases listeners, subscriptions, image handlers, summary timers and chip-hide timers.
|
releases listeners, subscriptions, image handlers, summary timers and chip-hide timers.
|
||||||
|
|
||||||
|
The panel opens when a camera becomes active and collapses when the layer is
|
||||||
|
switched off, from its own switch or anywhere else. The panel chrome applies
|
||||||
|
the collapse, so a panel the desktop accordion only folded has its saved and
|
||||||
|
shared state closed too. Clear selected data layers and Context isolation
|
||||||
|
(Contacts, Space Missions) switch the layer off without the user asking: the
|
||||||
|
panel and its share preference stay as they are, and the layer comes back to an
|
||||||
|
open panel. Only the switch-off isolation makes counts (`suspendsLayer`, true
|
||||||
|
while `_clearLayersOutsideContextMode` switches the layer off): the user
|
||||||
|
switching CCTV off inside Contacts closes the panel as anywhere else. A panel opened by hand while the layer is off stays open, and its
|
||||||
|
meta line reads the catalog count, not the last active camera.
|
||||||
|
|
||||||
## UI disposal
|
## UI disposal
|
||||||
|
|
||||||
UI disposal releases the CCTV subscription, command-dock observer, legacy drag
|
UI disposal releases the CCTV subscription, command-dock observer, legacy drag
|
||||||
|
|||||||
@@ -339,6 +339,45 @@ try {
|
|||||||
'#cctv-frame',
|
'#cctv-frame',
|
||||||
(image) => !image.getAttribute('src') && image.dataset.cameraId === '',
|
(image) => !image.getAttribute('src') && image.dataset.cameraId === '',
|
||||||
);
|
);
|
||||||
|
// The address bar follows the share state after a short delay: wait for the
|
||||||
|
// CCTV panel token (1 collapsed, 0 open) the step expects.
|
||||||
|
const panelState = async (expectedToken) => {
|
||||||
|
const settled = await page
|
||||||
|
.waitForFunction(
|
||||||
|
(token) =>
|
||||||
|
new RegExp(`(?:^|_)v\\.c\\.${token}(?:_|$)`).test(
|
||||||
|
new URLSearchParams(location.hash.slice(1)).get('ui') || '',
|
||||||
|
),
|
||||||
|
{ timeout: 5000 },
|
||||||
|
expectedToken,
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => true,
|
||||||
|
() => false,
|
||||||
|
);
|
||||||
|
return page.evaluate(
|
||||||
|
(shared) => ({
|
||||||
|
collapsed: document
|
||||||
|
.getElementById('cctv-panel')
|
||||||
|
.classList.contains('collapsed'),
|
||||||
|
shared,
|
||||||
|
ui: new URLSearchParams(location.hash.slice(1)).get('ui'),
|
||||||
|
}),
|
||||||
|
settled ? expectedToken : null,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const closedOnDisable = await panelState('1');
|
||||||
|
check(
|
||||||
|
'disable collapses the panel and its share preference',
|
||||||
|
closedOnDisable.collapsed && closedOnDisable.shared === '1',
|
||||||
|
JSON.stringify(closedOnDisable),
|
||||||
|
);
|
||||||
|
// The switch sits inside the collapsed panel: reopen it from the right rail.
|
||||||
|
await page.click('[data-rail-item="cctv-panel"]');
|
||||||
|
await page.waitForFunction(
|
||||||
|
() =>
|
||||||
|
!document.getElementById('cctv-panel').classList.contains('collapsed'),
|
||||||
|
);
|
||||||
await page.click('#cctv-enable-btn');
|
await page.click('#cctv-enable-btn');
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
() =>
|
() =>
|
||||||
@@ -351,6 +390,42 @@ try {
|
|||||||
'disable clears the preview and re-enable reacquires the camera frame',
|
'disable clears the preview and re-enable reacquires the camera frame',
|
||||||
disabledCleanly,
|
disabledCleanly,
|
||||||
);
|
);
|
||||||
|
// Contacts switches CCTV off to isolate itself and restores it on the way
|
||||||
|
// out: the open panel and its share preference survive the round trip.
|
||||||
|
const cameraBeforeContacts = await page.evaluate(
|
||||||
|
() =>
|
||||||
|
window.__godsEyeView.styleManager._cctvControls.getState()
|
||||||
|
?.activeCameraId,
|
||||||
|
);
|
||||||
|
const entered = await page.evaluate(() =>
|
||||||
|
window.__godsEyeView.styleManager.setContextMode('flights'),
|
||||||
|
);
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => !window.__godsEyeView.dataManager.isEnabled('cctv'),
|
||||||
|
);
|
||||||
|
const duringContacts = await panelState('0');
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.__godsEyeView.styleManager.setContextMode(null),
|
||||||
|
);
|
||||||
|
await page.waitForFunction(() =>
|
||||||
|
window.__godsEyeView.dataManager.isEnabled('cctv'),
|
||||||
|
);
|
||||||
|
const afterContacts = await panelState('0');
|
||||||
|
check(
|
||||||
|
'Contacts isolation keeps the open CCTV panel and restores its camera',
|
||||||
|
entered?.ok === true &&
|
||||||
|
!duringContacts.collapsed &&
|
||||||
|
duringContacts.shared === '0' &&
|
||||||
|
!afterContacts.collapsed &&
|
||||||
|
afterContacts.shared === '0' &&
|
||||||
|
(await page.evaluate(
|
||||||
|
(cameraId) =>
|
||||||
|
window.__godsEyeView.styleManager._cctvControls.getState()
|
||||||
|
?.activeCameraId === cameraId,
|
||||||
|
cameraBeforeContacts,
|
||||||
|
)),
|
||||||
|
JSON.stringify({ entered, duringContacts, afterContacts }),
|
||||||
|
);
|
||||||
await page.$eval('#cctv-frame', (image) =>
|
await page.$eval('#cctv-frame', (image) =>
|
||||||
image.scrollIntoView({ block: 'nearest' }),
|
image.scrollIntoView({ block: 'nearest' }),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1197,6 +1197,11 @@ export class StyleManager extends ShellFacade {
|
|||||||
toggleEnabled: (...args) => this._toggleCctvEnabled(...args),
|
toggleEnabled: (...args) => this._toggleCctvEnabled(...args),
|
||||||
runExplicitFocus: (...args) => this._runExplicitCctvFocus(...args),
|
runExplicitFocus: (...args) => this._runExplicitCctvFocus(...args),
|
||||||
setPanelCollapsed: (...args) => this.setPanelCollapsed(...args),
|
setPanelCollapsed: (...args) => this.setPanelCollapsed(...args),
|
||||||
|
// Clear selected data layers and Context isolation switch the layer
|
||||||
|
// off without the user asking: the panel keeps its state for both.
|
||||||
|
preservePanelState: () =>
|
||||||
|
this._preservePanelStateDuringLayerClear ||
|
||||||
|
this._contextSuspendsLayer('cctv'),
|
||||||
showToast: (message) => this._showToast(message),
|
showToast: (message) => this._showToast(message),
|
||||||
syncViewport: () => this._syncCctvPanelViewport(),
|
syncViewport: () => this._syncCctvPanelViewport(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export class CctvControls {
|
|||||||
this._cctvSummaryTypingTimer = null;
|
this._cctvSummaryTypingTimer = null;
|
||||||
this._lastCctvSummaryText = '';
|
this._lastCctvSummaryText = '';
|
||||||
this._lastSeenCctvActiveId = null;
|
this._lastSeenCctvActiveId = null;
|
||||||
|
this._wasCctvEnabled = false;
|
||||||
this._cctvChipHideTimer = null;
|
this._cctvChipHideTimer = null;
|
||||||
this._cctvChipWasBusy = false;
|
this._cctvChipWasBusy = false;
|
||||||
// The camera grid chip as last published; its label stays while hidden.
|
// The camera grid chip as last published; its label stays while hidden.
|
||||||
|
|||||||
@@ -513,3 +513,96 @@ 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.4), /· HDG 359° ·/);
|
||||||
assert.match(meta(359.6), /· HDG 0° ·/);
|
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]);
|
||||||
|
// A panel reopened by hand stays open through later notifications while
|
||||||
|
// the layer is off: only the on → off change collapses.
|
||||||
|
controls._cctvPanel.classList.remove('collapsed');
|
||||||
|
controls._renderCctvState({ ...activeState, enabled: false });
|
||||||
|
assert.equal(calls.length, 2);
|
||||||
|
assert.equal(controls._cctvPanel.classList.contains('collapsed'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 layer the application suspends keeps its panel through the round trip', (t) => {
|
||||||
|
const { controls } = panel(t);
|
||||||
|
const calls = collapseCalls(controls);
|
||||||
|
// Clear selected data layers and Context isolation both answer true.
|
||||||
|
let suspended = true;
|
||||||
|
controls.actions.preservePanelState = () => suspended;
|
||||||
|
controls._renderCctvState(activeState);
|
||||||
|
controls._renderCctvState({ ...activeState, enabled: false });
|
||||||
|
assert.equal(controls._cctvPanel.classList.contains('collapsed'), false);
|
||||||
|
suspended = false;
|
||||||
|
controls._renderCctvState(activeState);
|
||||||
|
assert.equal(controls._cctvPanel.classList.contains('collapsed'), false);
|
||||||
|
assert.ok(calls.every(([, collapsed]) => collapsed === false));
|
||||||
|
// The next switch-off is the user's again.
|
||||||
|
controls._renderCctvState({ ...activeState, enabled: false });
|
||||||
|
assert.deepEqual(calls.at(-1), ['cctv-panel', true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a panel the accordion folded still has its saved state closed', (t) => {
|
||||||
|
const { controls } = panel(t);
|
||||||
|
controls._renderCctvState(activeState);
|
||||||
|
const calls = [];
|
||||||
|
controls.actions.setPanelCollapsed = (...args) => calls.push(args);
|
||||||
|
// Folded for layout only: the class is there, the saved state is open. The
|
||||||
|
// panel chrome tells the two apart, so the controls always ask it.
|
||||||
|
controls._cctvPanel.classList.add('collapsed');
|
||||||
|
controls._renderCctvState({ ...activeState, enabled: false });
|
||||||
|
assert.deepEqual(calls, [['cctv-panel', true]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
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 /);
|
||||||
|
});
|
||||||
@@ -90,7 +90,9 @@ export function presentCctvPanel(state, enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function metaText(state, enabled, activeCamera, catalogCount) {
|
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(
|
const provider = cctvSourceLabelText(
|
||||||
activeCamera.sourceLabel || activeCamera.provider || 'Configured Source',
|
activeCamera.sourceLabel || activeCamera.provider || 'Configured Source',
|
||||||
);
|
);
|
||||||
@@ -188,6 +190,20 @@ export function _renderCctvState(state) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._lastSeenCctvActiveId = effectiveActiveId;
|
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. Clear selected data layers and
|
||||||
|
// Context isolation keep the panel: the layer comes back with it. The
|
||||||
|
// panel chrome decides whether anything changes: a panel the desktop
|
||||||
|
// accordion only folded still has an open saved state to close.
|
||||||
|
if (
|
||||||
|
this._wasCctvEnabled &&
|
||||||
|
!enabled &&
|
||||||
|
!this.actions.preservePanelState?.()
|
||||||
|
) {
|
||||||
|
this.actions.setPanelCollapsed('cctv-panel', true);
|
||||||
|
}
|
||||||
|
this._wasCctvEnabled = enabled;
|
||||||
|
|
||||||
this._updateCctvSyncChip(state?.loading, enabled);
|
this._updateCctvSyncChip(state?.loading, enabled);
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export class ContextControls {
|
|||||||
this._contextRestoreState = null;
|
this._contextRestoreState = null;
|
||||||
this._contextLayerReactionPromises = new Set();
|
this._contextLayerReactionPromises = new Set();
|
||||||
this._preservePanelStateDuringLayerClear = false;
|
this._preservePanelStateDuringLayerClear = false;
|
||||||
|
/** Layers Context isolation is switching off right now. */
|
||||||
|
this._isolatingLayerIds = new Set();
|
||||||
this._userFacingContextNotificationTokens = new Set();
|
this._userFacingContextNotificationTokens = new Set();
|
||||||
this._clearSelectedLayersPromise = null;
|
this._clearSelectedLayersPromise = null;
|
||||||
this._clearSelectedLayersManagerPromise = null;
|
this._clearSelectedLayersManagerPromise = null;
|
||||||
@@ -59,6 +61,16 @@ export class ContextControls {
|
|||||||
showToast(...args) {
|
showToast(...args) {
|
||||||
if (!this.destroyed) this.actions.showToast(...args);
|
if (!this.destroyed) this.actions.showToast(...args);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Whether Context isolation is switching this layer off right now: it is
|
||||||
|
* suspended, and leaving the mode restores it. A switch-off by the user,
|
||||||
|
* inside the mode or out of it, is not.
|
||||||
|
* @param {string} layerId Layer id.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
suspendsLayer(layerId) {
|
||||||
|
return this._isolatingLayerIds.has(layerId);
|
||||||
|
}
|
||||||
setClearBusy(...args) {
|
setClearBusy(...args) {
|
||||||
if (!this.destroyed) this.actions.setClearBusy(...args);
|
if (!this.destroyed) this.actions.setClearBusy(...args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,3 +153,56 @@ test('a rejected tracked reaction settles without an unhandled rejection', async
|
|||||||
await controls._waitForContextLayerSettlement();
|
await controls._waitForContextLayerSettlement();
|
||||||
assert.equal(controls._contextLayerReactionPromises.size, 0);
|
assert.equal(controls._contextLayerReactionPromises.size, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('only a layer Context isolation is switching off is suspended', async (t) => {
|
||||||
|
const { controls } = fixture(t);
|
||||||
|
const enabled = new Set(['cctv']);
|
||||||
|
// What the CCTV panel is told at each visibility change of its layer.
|
||||||
|
const seen = [];
|
||||||
|
controls._dataManager = {
|
||||||
|
layers: new Map([['cctv', {}]]),
|
||||||
|
getEnabledLayerIds: () => [...enabled],
|
||||||
|
getLayerParams: () => null,
|
||||||
|
setLayerParams() {},
|
||||||
|
isEnabled: (layerId) => enabled.has(layerId),
|
||||||
|
isEffectivelyEnabled: (layerId) => enabled.has(layerId),
|
||||||
|
async setEnabled(layerId, next) {
|
||||||
|
// The layer tells its panel it is off while it is being disabled.
|
||||||
|
if (next) enabled.add(layerId);
|
||||||
|
else enabled.delete(layerId);
|
||||||
|
seen.push([layerId, next, controls.suspendsLayer(layerId)]);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
async restoreEnabledLayerIds(layerIds) {
|
||||||
|
for (const layerId of layerIds) await this.setEnabled(layerId, true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.equal(controls.suspendsLayer('cctv'), false);
|
||||||
|
controls._captureContextSessionSnapshot();
|
||||||
|
await controls._clearLayersOutsideContextMode('flights');
|
||||||
|
assert.deepEqual(seen, [['cctv', false, true]]);
|
||||||
|
assert.equal(controls.suspendsLayer('cctv'), false, 'only while isolating');
|
||||||
|
// Inside the mode the user turns CCTV back on, then off: their own.
|
||||||
|
await controls._dataManager.setEnabled('cctv', true);
|
||||||
|
await controls._dataManager.setEnabled('cctv', false);
|
||||||
|
assert.deepEqual(seen.at(-1), ['cctv', false, false]);
|
||||||
|
await controls._restoreContextSession();
|
||||||
|
assert.equal(enabled.has('cctv'), true);
|
||||||
|
// A switch-off after the mode is left is the user's own too.
|
||||||
|
await controls._dataManager.setEnabled('cctv', false);
|
||||||
|
assert.deepEqual(seen.at(-1), ['cctv', false, false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a failed isolation leaves no layer suspended', async (t) => {
|
||||||
|
const { controls } = fixture(t);
|
||||||
|
controls._dataManager = {
|
||||||
|
layers: new Map([['cctv', {}]]),
|
||||||
|
isEffectivelyEnabled: () => true,
|
||||||
|
isEnabled: () => true,
|
||||||
|
setEnabled: async () => {
|
||||||
|
throw new Error('disable failed');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await assert.rejects(controls._clearLayersOutsideContextMode('flights'));
|
||||||
|
assert.equal(controls.suspendsLayer('cctv'), false);
|
||||||
|
});
|
||||||
@@ -240,6 +240,11 @@ export async function _clearLayersOutsideContextMode(
|
|||||||
) {
|
) {
|
||||||
const allowed = contextAllowedLayerIds(mode);
|
const allowed = contextAllowedLayerIds(mode);
|
||||||
const pending = [];
|
const pending = [];
|
||||||
|
// Panels read this while their layer goes off (suspendsLayer): a layer
|
||||||
|
// isolation switches off is suspended, not switched off by the user.
|
||||||
|
const isolating = [];
|
||||||
|
let results;
|
||||||
|
try {
|
||||||
for (const [layerId] of this._dataManager.layers || []) {
|
for (const [layerId] of this._dataManager.layers || []) {
|
||||||
// Effective visibility: a disallowed layer still mid-ENABLING must be
|
// Effective visibility: a disallowed layer still mid-ENABLING must be
|
||||||
// isolated too, or it settles ON inside the exclusive mode.
|
// isolated too, or it settles ON inside the exclusive mode.
|
||||||
@@ -247,6 +252,10 @@ export async function _clearLayersOutsideContextMode(
|
|||||||
!allowed.has(layerId) &&
|
!allowed.has(layerId) &&
|
||||||
this._dataManager.isEffectivelyEnabled(layerId)
|
this._dataManager.isEffectivelyEnabled(layerId)
|
||||||
) {
|
) {
|
||||||
|
if (!this._isolatingLayerIds.has(layerId)) {
|
||||||
|
this._isolatingLayerIds.add(layerId);
|
||||||
|
isolating.push(layerId);
|
||||||
|
}
|
||||||
pending.push({
|
pending.push({
|
||||||
layerId,
|
layerId,
|
||||||
transition: this._dataManager.setEnabled(layerId, false, {
|
transition: this._dataManager.setEnabled(layerId, false, {
|
||||||
@@ -256,9 +265,10 @@ export async function _clearLayersOutsideContextMode(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const results = await Promise.all(
|
results = await Promise.all(pending.map(({ transition }) => transition));
|
||||||
pending.map(({ transition }) => transition),
|
} finally {
|
||||||
);
|
for (const layerId of isolating) this._isolatingLayerIds.delete(layerId);
|
||||||
|
}
|
||||||
const failed = pending
|
const failed = pending
|
||||||
.filter(
|
.filter(
|
||||||
({ layerId }, index) =>
|
({ layerId }, index) =>
|
||||||
|
|||||||
@@ -891,6 +891,10 @@ export class ShellFacade {
|
|||||||
return this._contextControls?._preservePanelStateDuringLayerClear ?? false;
|
return this._contextControls?._preservePanelStateDuringLayerClear ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_contextSuspendsLayer(layerId) {
|
||||||
|
return this._contextControls?.suspendsLayer(layerId) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
_runUserFacingContextAction(...args) {
|
_runUserFacingContextAction(...args) {
|
||||||
return this._contextControls?._runUserFacingContextAction(...args);
|
return this._contextControls?._runUserFacingContextAction(...args);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user