mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Suspend only the layers Context isolation is switching off
The session snapshot recorded a user's switch only after the CCTV panel had already rendered the layer off, so a first switch-off by the user inside Contacts, or after a partly failed restore, still kept the panel open. Context controls now hold the layers _clearLayersOutsideContextMode is switching off while its transitions run; suspendsLayer reads that set, and the snapshot is no longer consulted.
This commit is contained in:
1 parent
f0ee1b95ca
commit
9b46692c64
6 files changed
+50
-63
No files matched your search
@@ -2235,7 +2235,9 @@ 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
|
shared state closed too. Clear selected data layers and Context isolation
|
||||||
(Contacts, Space Missions) switch the layer off without the user asking: the
|
(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
|
panel and its share preference stay as they are, and the layer comes back to an
|
||||||
open panel. A panel opened by hand while the layer is off stays open, and its
|
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.
|
meta line reads the catalog count, not the last active camera.
|
||||||
|
|
||||||
## UI disposal
|
## UI disposal
|
||||||
|
|||||||
@@ -277,7 +277,6 @@ export function recordContextSessionUserChange({
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
snapshot.userSwitched?.add(change.layerId);
|
|
||||||
const isCompanion = CONTEXT_COMPANIONS.has(change.layerId);
|
const isCompanion = CONTEXT_COMPANIONS.has(change.layerId);
|
||||||
if (change.enabled) {
|
if (change.enabled) {
|
||||||
if (CONTEXT_ENTRY_LAYER_IDS.includes(change.layerId)) return false;
|
if (CONTEXT_ENTRY_LAYER_IDS.includes(change.layerId)) return false;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -60,18 +62,14 @@ export class ContextControls {
|
|||||||
if (!this.destroyed) this.actions.showToast(...args);
|
if (!this.destroyed) this.actions.showToast(...args);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Whether Context isolation switched this layer off and leaving the mode
|
* Whether Context isolation is switching this layer off right now: it is
|
||||||
* restores it. Such a layer is suspended, not switched off by the user: a
|
* suspended, and leaving the mode restores it. A switch-off by the user,
|
||||||
* layer the user switched during the session is theirs again.
|
* inside the mode or out of it, is not.
|
||||||
* @param {string} layerId Layer id.
|
* @param {string} layerId Layer id.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
suspendsLayer(layerId) {
|
suspendsLayer(layerId) {
|
||||||
const snapshot = this._contextSessionSnapshot;
|
return this._isolatingLayerIds.has(layerId);
|
||||||
return Boolean(
|
|
||||||
snapshot?.enabledLayerIds?.has(layerId) &&
|
|
||||||
!snapshot.userSwitched?.has(layerId),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
setClearBusy(...args) {
|
setClearBusy(...args) {
|
||||||
if (!this.destroyed) this.actions.setClearBusy(...args);
|
if (!this.destroyed) this.actions.setClearBusy(...args);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import { ContextControls } from './contextControls.js';
|
import { ContextControls } from './contextControls.js';
|
||||||
import { recordContextSessionUserChange } from '../contextModePolicy.js';
|
|
||||||
|
|
||||||
class Button extends EventTarget {
|
class Button extends EventTarget {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -155,7 +154,7 @@ test('a rejected tracked reaction settles without an unhandled rejection', async
|
|||||||
assert.equal(controls._contextLayerReactionPromises.size, 0);
|
assert.equal(controls._contextLayerReactionPromises.size, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a layer Context isolation switches off is suspended until the mode is left', async (t) => {
|
test('only a layer Context isolation is switching off is suspended', async (t) => {
|
||||||
const { controls } = fixture(t);
|
const { controls } = fixture(t);
|
||||||
const enabled = new Set(['cctv']);
|
const enabled = new Set(['cctv']);
|
||||||
// What the CCTV panel is told at each visibility change of its layer.
|
// What the CCTV panel is told at each visibility change of its layer.
|
||||||
@@ -168,6 +167,7 @@ test('a layer Context isolation switches off is suspended until the mode is left
|
|||||||
isEnabled: (layerId) => enabled.has(layerId),
|
isEnabled: (layerId) => enabled.has(layerId),
|
||||||
isEffectivelyEnabled: (layerId) => enabled.has(layerId),
|
isEffectivelyEnabled: (layerId) => enabled.has(layerId),
|
||||||
async setEnabled(layerId, next) {
|
async setEnabled(layerId, next) {
|
||||||
|
// The layer tells its panel it is off while it is being disabled.
|
||||||
if (next) enabled.add(layerId);
|
if (next) enabled.add(layerId);
|
||||||
else enabled.delete(layerId);
|
else enabled.delete(layerId);
|
||||||
seen.push([layerId, next, controls.suspendsLayer(layerId)]);
|
seen.push([layerId, next, controls.suspendsLayer(layerId)]);
|
||||||
@@ -181,47 +181,28 @@ test('a layer Context isolation switches off is suspended until the mode is left
|
|||||||
controls._captureContextSessionSnapshot();
|
controls._captureContextSessionSnapshot();
|
||||||
await controls._clearLayersOutsideContextMode('flights');
|
await controls._clearLayersOutsideContextMode('flights');
|
||||||
assert.deepEqual(seen, [['cctv', false, true]]);
|
assert.deepEqual(seen, [['cctv', false, true]]);
|
||||||
assert.equal(controls.suspendsLayer('cctv'), true);
|
assert.equal(controls.suspendsLayer('cctv'), false, 'only while isolating');
|
||||||
assert.equal(controls.suspendsLayer('street-level'), false);
|
// 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();
|
await controls._restoreContextSession();
|
||||||
assert.equal(enabled.has('cctv'), true);
|
assert.equal(enabled.has('cctv'), true);
|
||||||
assert.equal(controls.suspendsLayer('cctv'), false);
|
// A switch-off after the mode is left is the user's own too.
|
||||||
// A switch-off after the mode is left is the user's own.
|
|
||||||
await controls._dataManager.setEnabled('cctv', false);
|
await controls._dataManager.setEnabled('cctv', false);
|
||||||
assert.deepEqual(seen.at(-1), ['cctv', false, false]);
|
assert.deepEqual(seen.at(-1), ['cctv', false, false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('a layer the user switches inside the mode is no longer suspended', async (t) => {
|
test('a failed isolation leaves no layer suspended', async (t) => {
|
||||||
const { controls } = fixture(t);
|
const { controls } = fixture(t);
|
||||||
const enabled = new Set(['cctv']);
|
|
||||||
controls._dataManager = {
|
controls._dataManager = {
|
||||||
layers: new Map([['cctv', {}]]),
|
layers: new Map([['cctv', {}]]),
|
||||||
getEnabledLayerIds: () => [...enabled],
|
isEffectivelyEnabled: () => true,
|
||||||
getLayerParams: () => null,
|
isEnabled: () => true,
|
||||||
isEffectivelyEnabled: (layerId) => enabled.has(layerId),
|
setEnabled: async () => {
|
||||||
isEnabled: (layerId) => enabled.has(layerId),
|
throw new Error('disable failed');
|
||||||
async setEnabled(layerId, next) {
|
|
||||||
if (next) enabled.add(layerId);
|
|
||||||
else enabled.delete(layerId);
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
controls._captureContextSessionSnapshot();
|
await assert.rejects(controls._clearLayersOutsideContextMode('flights'));
|
||||||
await controls._clearLayersOutsideContextMode('flights');
|
|
||||||
assert.equal(controls.suspendsLayer('cctv'), true);
|
|
||||||
// Inside Contacts the user turns CCTV back on, then off again: the panel
|
|
||||||
// must close like any other switch-off.
|
|
||||||
for (const next of [true, false]) {
|
|
||||||
recordContextSessionUserChange({
|
|
||||||
snapshot: controls._contextSessionSnapshot,
|
|
||||||
change: {
|
|
||||||
type: 'visibility',
|
|
||||||
origin: 'user',
|
|
||||||
layerId: 'cctv',
|
|
||||||
enabled: next,
|
|
||||||
},
|
|
||||||
effectiveContextMode: 'flights',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
assert.equal(controls.suspendsLayer('cctv'), false);
|
assert.equal(controls.suspendsLayer('cctv'), false);
|
||||||
});
|
});
|
||||||
@@ -36,8 +36,6 @@ export function _captureContextSessionSnapshot({ excludeLayerIds = [] } = {}) {
|
|||||||
),
|
),
|
||||||
userAdded: new Set(),
|
userAdded: new Set(),
|
||||||
userRemoved: new Set(),
|
userRemoved: new Set(),
|
||||||
// Every layer the user switched during the session, either way.
|
|
||||||
userSwitched: new Set(),
|
|
||||||
params,
|
params,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -151,7 +149,6 @@ export async function _restoreContextSession({
|
|||||||
enabledLayerIds: new Set(restoreState.enabledLayerIds),
|
enabledLayerIds: new Set(restoreState.enabledLayerIds),
|
||||||
userAdded: new Set(),
|
userAdded: new Set(),
|
||||||
userRemoved: new Set(),
|
userRemoved: new Set(),
|
||||||
userSwitched: new Set(),
|
|
||||||
params: snapshot.params,
|
params: snapshot.params,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,25 +240,35 @@ export async function _clearLayersOutsideContextMode(
|
|||||||
) {
|
) {
|
||||||
const allowed = contextAllowedLayerIds(mode);
|
const allowed = contextAllowedLayerIds(mode);
|
||||||
const pending = [];
|
const pending = [];
|
||||||
for (const [layerId] of this._dataManager.layers || []) {
|
// Panels read this while their layer goes off (suspendsLayer): a layer
|
||||||
// Effective visibility: a disallowed layer still mid-ENABLING must be
|
// isolation switches off is suspended, not switched off by the user.
|
||||||
// isolated too, or it settles ON inside the exclusive mode.
|
const isolating = [];
|
||||||
if (
|
let results;
|
||||||
!allowed.has(layerId) &&
|
try {
|
||||||
this._dataManager.isEffectivelyEnabled(layerId)
|
for (const [layerId] of this._dataManager.layers || []) {
|
||||||
) {
|
// Effective visibility: a disallowed layer still mid-ENABLING must be
|
||||||
pending.push({
|
// isolated too, or it settles ON inside the exclusive mode.
|
||||||
layerId,
|
if (
|
||||||
transition: this._dataManager.setEnabled(layerId, false, {
|
!allowed.has(layerId) &&
|
||||||
notificationToken,
|
this._dataManager.isEffectivelyEnabled(layerId)
|
||||||
...(signal ? { signal } : {}),
|
) {
|
||||||
}),
|
if (!this._isolatingLayerIds.has(layerId)) {
|
||||||
});
|
this._isolatingLayerIds.add(layerId);
|
||||||
|
isolating.push(layerId);
|
||||||
|
}
|
||||||
|
pending.push({
|
||||||
|
layerId,
|
||||||
|
transition: this._dataManager.setEnabled(layerId, false, {
|
||||||
|
notificationToken,
|
||||||
|
...(signal ? { signal } : {}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
results = await Promise.all(pending.map(({ transition }) => transition));
|
||||||
|
} finally {
|
||||||
|
for (const layerId of isolating) this._isolatingLayerIds.delete(layerId);
|
||||||
}
|
}
|
||||||
const results = await Promise.all(
|
|
||||||
pending.map(({ transition }) => transition),
|
|
||||||
);
|
|
||||||
const failed = pending
|
const failed = pending
|
||||||
.filter(
|
.filter(
|
||||||
({ layerId }, index) =>
|
({ layerId }, index) =>
|
||||||
|
|||||||
Reference in new issue
Block a user