mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(share): restore a share link opened in an already-open tab (#1416)
* fix(share): restore a share link opened by hash navigation Opening a different share link in an already-open tab (pasting it into the address bar, following a link, or Back/Forward) changed the URL but left the camera, visual style, map, panels and layers on the old view until a reload. The share manager now listens for hashchange and popstate. A new hash that parses as a share and differs from the last hash the app read or wrote runs the start-up restore pipeline: lane authority is reserved, URL writes are held (including a pending debounced write of the old view), the camera restore takes a new navigation generation, and the layer coordinator replaces any in-flight layer restore with the new share's layers through the passive share origin. A newer navigation supersedes an older restore end to end; only the newest one releases URL writes. The app's own replaceState writes never restore. Listeners are removed on destroy. Returning to the Normal style and resetting an already-active preset now also apply when a share is restored after start-up, so the result matches a fresh load of the same link. * docs: changelog for #1416
This commit is contained in:
1 parent
301bc2e0b3
commit
b7ece1d4d1
8 files changed
+688
-58
No files matched your search
@@ -148,6 +148,36 @@ export class LayerStateCoordinator {
|
||||
return this.restorePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the running layer state with a share opened later in this tab
|
||||
* (hash navigation). Every in-flight restore and pending shared Follow is
|
||||
* superseded; the new state restores through the same passive share origin
|
||||
* as start-up, so recipient-local preferences are never written. A share
|
||||
* without a layer payload restores the defaults, as a reload of it would.
|
||||
*/
|
||||
applyShareLayerState({
|
||||
shareLayerState = null,
|
||||
shareCreatedAtMs = null,
|
||||
} = {}) {
|
||||
if (this._destroyed) return Promise.resolve([]);
|
||||
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;
|
||||
this._source = selected ? 'share' : 'legacy-share';
|
||||
this._shareCreatedAtMs =
|
||||
selected && Number.isFinite(shareCreatedAtMs) ? shareCreatedAtMs : null;
|
||||
this._durableState = selected || createDefaultLayerState();
|
||||
this.shareLinkManager?.onLayerStateChange?.();
|
||||
this._notifyDurableState();
|
||||
this.restorePromise = this._restoreSelectedState(
|
||||
LAYER_RESTORE_ORIGINS.share,
|
||||
);
|
||||
return this.restorePromise;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this._source;
|
||||
}
|
||||
@@ -312,15 +342,19 @@ export class LayerStateCoordinator {
|
||||
}
|
||||
|
||||
async _restoreSelectedState(origin) {
|
||||
// One controller set per run: a superseded run must not clear the newer
|
||||
// run's controllers when it settles.
|
||||
const controllers = new Map();
|
||||
for (const entry of LAYER_STATE_REGISTRY) {
|
||||
this._restoreControllers.set(entry.id, new AbortController());
|
||||
controllers.set(entry.id, new AbortController());
|
||||
}
|
||||
this._restoreControllers = controllers;
|
||||
try {
|
||||
await this._waitForRestoreGate();
|
||||
const enabled = new Set(this._durableState.enabledLayerIds);
|
||||
const settled = await Promise.allSettled(
|
||||
LAYER_STATE_REGISTRY.map(async (entry) => {
|
||||
const controller = this._restoreControllers.get(entry.id);
|
||||
const controller = controllers.get(entry.id);
|
||||
const targetEnabled = enabled.has(entry.id);
|
||||
const options = layerOptionsForRestore(this._durableState, entry.id);
|
||||
if (origin === LAYER_RESTORE_ORIGINS.share && options) {
|
||||
@@ -370,7 +404,7 @@ export class LayerStateCoordinator {
|
||||
}));
|
||||
}),
|
||||
);
|
||||
this.lastRestoreResults = settled.map((result, index) => {
|
||||
const results = settled.map((result, index) => {
|
||||
if (result.status === 'fulfilled') return result.value;
|
||||
const entry = LAYER_STATE_REGISTRY[index];
|
||||
return {
|
||||
@@ -387,9 +421,13 @@ export class LayerStateCoordinator {
|
||||
succeeded: false,
|
||||
};
|
||||
});
|
||||
return this.lastRestoreResults.map((result) => ({ ...result }));
|
||||
// A run superseded by a newer share keeps the newer run's results.
|
||||
if (this._restoreControllers === controllers)
|
||||
this.lastRestoreResults = results;
|
||||
return results.map((result) => ({ ...result }));
|
||||
} finally {
|
||||
this._restoreControllers.clear();
|
||||
if (this._restoreControllers === controllers)
|
||||
this._restoreControllers.clear();
|
||||
this._notifyDurableState();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user