mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-09 04:44:01 +00:00
Merge pull request #1634 from bilawalsidhu/fix/layer-state-registered-only
Layer state keeps only layers the application registered
This commit is contained in:
4 files changed
+101
-9
No files matched your search
@@ -0,0 +1,3 @@
|
||||
- An application built with fewer layers no longer restores, saves or shares a
|
||||
layer it does not have when a link or saved state from a full build names it
|
||||
(samehkhamis, #1634).
|
||||
@@ -5627,6 +5627,13 @@ and unreachable upstream (502/504) separately from road geometry.
|
||||
explicit-empty form); unknown option tokens are ignored. Restoration
|
||||
settles independently per layer so one failed or unavailable source cannot
|
||||
block its siblings.
|
||||
- An application that registers fewer layers (an embedder with a smaller
|
||||
catalog) keeps only its own: `LayerStateCoordinator` drops enabled-layer ids
|
||||
the application did not register before it normalizes a share, saved state or
|
||||
explicit change, so such a layer is never restored, written to local storage
|
||||
or carried into a link this application shares. Options that depend on a
|
||||
dropped layer (a tracked selection) go with it. An application with every
|
||||
layer registered is unchanged.
|
||||
- Layer tokens are permanent public compatibility identifiers. The reservation
|
||||
JSON ledger at `src/data/layerStateTokenReservations.json` pins all existing
|
||||
one-character assignments (all letters plus `0`, `1` and
|
||||
|
||||
@@ -3121,3 +3121,70 @@ test('Active Fires: the codec accepts whole megawatts in bounds and the three co
|
||||
confidence: 'nominal',
|
||||
});
|
||||
});
|
||||
|
||||
// An application built without a layer (a smaller catalog) receives a link or
|
||||
// saved state from a build that has it: the layer is neither restored nor
|
||||
// stored nor carried into the links this application shares.
|
||||
function managerWithout(withheld) {
|
||||
const manager = new DataLayerManager({});
|
||||
for (const id of REGISTERED_LAYER_IDS)
|
||||
if (!withheld.includes(id)) manager.register(fakeLayer(id));
|
||||
manager.finalizeRegistrations(
|
||||
LAYER_STATE_REGISTRY.filter(({ id }) => !withheld.includes(id)),
|
||||
);
|
||||
return manager;
|
||||
}
|
||||
|
||||
test('layers the application did not register are never restored, stored or shared', async () => {
|
||||
const manager = managerWithout(['earthquakes']);
|
||||
const storage = memoryStorage();
|
||||
const share = shareSink();
|
||||
const coordinator = new LayerStateCoordinator(manager, share, { storage });
|
||||
const incoming = createDefaultLayerState();
|
||||
incoming.enabledLayerIds = ['earthquakes', 'traffic'];
|
||||
await coordinator.start({ shareLayerState: incoming });
|
||||
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, ['traffic']);
|
||||
assert.deepEqual(share.provider().enabledLayerIds, ['traffic']);
|
||||
assert.equal(manager.layers.has('earthquakes'), false);
|
||||
|
||||
await manager.setEnabled('cctv', true, { origin: 'user' });
|
||||
const [[, written]] = storage.writes;
|
||||
assert.deepEqual(parseStoredLayerState(written).enabledLayerIds, [
|
||||
'cctv',
|
||||
'traffic',
|
||||
]);
|
||||
assert.deepEqual(share.provider().enabledLayerIds, ['cctv', 'traffic']);
|
||||
|
||||
await coordinator.applyShareLayerState({ shareLayerState: incoming });
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, ['traffic']);
|
||||
coordinator.destroy();
|
||||
|
||||
const saved = createDefaultLayerState();
|
||||
saved.enabledLayerIds = ['earthquakes', 'traffic'];
|
||||
const local = new LayerStateCoordinator(
|
||||
managerWithout(['earthquakes']),
|
||||
shareSink(),
|
||||
{ storage: memoryStorage(serializeStoredLayerState(saved)) },
|
||||
);
|
||||
await local.start();
|
||||
assert.equal(local.source, 'local');
|
||||
assert.deepEqual(local.getDurableState().enabledLayerIds, ['traffic']);
|
||||
local.destroy();
|
||||
});
|
||||
|
||||
test('an application with every layer registered keeps every requested layer', async () => {
|
||||
const coordinator = new LayerStateCoordinator(
|
||||
productionManager(),
|
||||
shareSink(),
|
||||
{ storage: memoryStorage() },
|
||||
);
|
||||
const incoming = createDefaultLayerState();
|
||||
incoming.enabledLayerIds = ['earthquakes', 'traffic'];
|
||||
await coordinator.start({ shareLayerState: incoming });
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, [
|
||||
'earthquakes',
|
||||
'traffic',
|
||||
]);
|
||||
coordinator.destroy();
|
||||
});
|
||||
@@ -106,9 +106,7 @@ export class LayerStateCoordinator {
|
||||
} = {}) {
|
||||
if (this._destroyed)
|
||||
throw new Error('Layer-state coordinator is destroyed');
|
||||
let selected = shareLayerState
|
||||
? normalizeLayerState(shareLayerState)
|
||||
: null;
|
||||
let selected = shareLayerState ? this._normalize(shareLayerState) : null;
|
||||
if (selected) {
|
||||
this._source = 'share';
|
||||
this._shareCreatedAtMs = Number.isFinite(shareCreatedAtMs)
|
||||
@@ -124,7 +122,7 @@ export class LayerStateCoordinator {
|
||||
/* best effort */
|
||||
}
|
||||
if (stored) {
|
||||
selected = stored;
|
||||
selected = this._normalize(stored);
|
||||
this._source = 'local';
|
||||
}
|
||||
} else {
|
||||
@@ -163,9 +161,7 @@ export class LayerStateCoordinator {
|
||||
for (const controller of this._restoreControllers.values())
|
||||
controller.abort('superseded-by-share-navigation');
|
||||
this._revokePendingTrackingWatch('superseded-by-share-navigation');
|
||||
const selected = shareLayerState
|
||||
? normalizeLayerState(shareLayerState)
|
||||
: null;
|
||||
const selected = shareLayerState ? this._normalize(shareLayerState) : null;
|
||||
this._source = selected ? 'share' : 'legacy-share';
|
||||
this._shareCreatedAtMs =
|
||||
selected && Number.isFinite(shareCreatedAtMs) ? shareCreatedAtMs : null;
|
||||
@@ -182,6 +178,25 @@ export class LayerStateCoordinator {
|
||||
return this._source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canonical layer state holding only the layers this application
|
||||
* registered. An application built without a layer never restores, stores
|
||||
* or shares it, even when a link or saved state from a build that has it
|
||||
* names it. Filtering before normalizing lets normalization drop options
|
||||
* that depend on a layer that is not here (a tracked selection).
|
||||
*/
|
||||
_normalize(candidate) {
|
||||
const input = candidate && typeof candidate === 'object' ? candidate : {};
|
||||
return normalizeLayerState({
|
||||
...input,
|
||||
enabledLayerIds: Array.isArray(input.enabledLayerIds)
|
||||
? input.enabledLayerIds.filter((id) =>
|
||||
this.dataManager.layers.has(String(id)),
|
||||
)
|
||||
: [],
|
||||
});
|
||||
}
|
||||
|
||||
getDurableState() {
|
||||
return cloneLayerState(this._durableState);
|
||||
}
|
||||
@@ -345,7 +360,7 @@ export class LayerStateCoordinator {
|
||||
}
|
||||
|
||||
_commitExplicit(candidate) {
|
||||
this._durableState = normalizeLayerState(candidate);
|
||||
this._durableState = this._normalize(candidate);
|
||||
const serialized = serializeStoredLayerState(this._durableState);
|
||||
try {
|
||||
if (this.storage?.getItem?.(LAYER_STATE_STORAGE_KEY) !== serialized) {
|
||||
@@ -478,7 +493,7 @@ export class LayerStateCoordinator {
|
||||
...this._durableState.options[selected.optionOwner],
|
||||
[selected.optionKey]: null,
|
||||
};
|
||||
this._durableState = normalizeLayerState({
|
||||
this._durableState = this._normalize({
|
||||
...this._durableState,
|
||||
options: {
|
||||
...this._durableState.options,
|
||||
|
||||
Reference in new issue
Block a user