From d7ab85f5c4873cd1919f1fa2499e7fad5a5b96da Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 3 Nov 2026 02:38:25 +0400 Subject: [PATCH] Keep Vue layer rows mounted through globe switches and rollback --- scripts/qa-layer-handoff.mjs | 4 ++++ src/app/data.js | 10 +++++++--- src/app/layerPresentation.js | 10 +++++++++- src/ui/layerPanel.js | 12 +++++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/scripts/qa-layer-handoff.mjs b/scripts/qa-layer-handoff.mjs index 77df33698..a38328b03 100644 --- a/scripts/qa-layer-handoff.mjs +++ b/scripts/qa-layer-handoff.mjs @@ -96,6 +96,7 @@ try { window.__handoff = { dataManager, entry, + row: document.querySelector('[data-layer-id="flights"] .data-toggle-btn'), timer: entry.intervalId, model: entry.module, pollCount: window.__flightPolls, @@ -145,6 +146,8 @@ try { before.dataManager === dataManager && before.entry === entry && before.model === entry.module, + sameRow: Boolean(before.row) && + before.row === document.querySelector('[data-layer-id="flights"] .data-toggle-btn'), sameTimer: before.timer === entry.intervalId && entry.intervalId != null, sourcePolls: window.__flightPolls - before.pollCount, @@ -162,6 +165,7 @@ try { assert.ok( result.sameOwner && result.sameTimer && + result.sameRow && result.active && result.hud && result.drawn, diff --git a/src/app/data.js b/src/app/data.js index bc6b0a394..c293cd487 100644 --- a/src/app/data.js +++ b/src/app/data.js @@ -34,10 +34,14 @@ export function createApplicationData({ `Data layers could not be destroyed: ${[...dataManager.layers.keys()].join(', ')}`, ); }); - const presentation = new LayerPresentation(dataManager, { + const presentation = layerSession?.presentation ?? new LayerPresentation(dataManager, { weatherClock: catalog?.weatherClock, }); - defer(() => presentation.destroy()); + if (layerSession) layerSession.presentation = presentation; + defer(() => { + if (layerSession && !layerSession.lifetime.signal.aborted) presentation.suspend(); + else presentation.destroy(); + }); onData?.(dataManager); if (!catalog?.layers || !catalog?.metadata) throw new TypeError('An application layer catalog is required'); @@ -66,7 +70,7 @@ export function createApplicationData({ delete window.__gevQaUnregisterLayer; }); } - presentation.mount(document.getElementById('data-toggles')); + presentation.mount(document.getElementById('data-toggles'), catalog.weatherClock); styleManager.attachDataManager(dataManager); // The sonar scene draws on the viewer; the Navara engine has none. if (viewer) defer(createCyberSonarScene(viewer, dataManager)); diff --git a/src/app/layerPresentation.js b/src/app/layerPresentation.js index eaa0e0b57..80cc3aeef 100644 --- a/src/app/layerPresentation.js +++ b/src/app/layerPresentation.js @@ -17,6 +17,7 @@ export class LayerPresentation { this._panel = null; this.pendingVisible = false; this._unsubscribe = manager.subscribeActivity((change) => { + if (this.suspended && change.type !== 'destroy-all') return; if (change.type === 'status') this.refresh(); else if (change.type === 'destroy-all') this.destroy(); else { @@ -67,9 +68,16 @@ export class LayerPresentation { }); return this._panel; } - mount(container) { + mount(container, weatherClock = this.weatherClock) { + this.weatherClock = weatherClock; + this.panel.weatherClock = weatherClock; + this.suspended = false; this.panel.mount(container); } + suspend() { + this.suspended = true; + this._panel?.suspend(); + } /** Hand the Recent Imagery readout factory to the panel (see LayerPanel). */ attachRecentImagery(factory) { // Detaching never builds a panel that teardown already released. diff --git a/src/ui/layerPanel.js b/src/ui/layerPanel.js index ac2469e3a..e00910396 100644 --- a/src/ui/layerPanel.js +++ b/src/ui/layerPanel.js @@ -273,6 +273,7 @@ export class LayerPanel { } mount(container) { if (this._destroyed) return; + this._suspended = false; this._releaseBindings(); if (this._toggleContainer && this._toggleContainer !== container) renderVue(null, this._toggleContainer); @@ -315,6 +316,15 @@ export class LayerPanel { this._cancelRowControlsRefresh = null; for (const remove of this._removers.splice(0)) remove(); } + // During a globe switch the keyed Vue rows stay mounted. Their event + // generation expires here, before any legacy scene-owned modules are freed. + suspend() { + this._suspended = true; + this._releaseBindings(); + this._weatherPanel?.destroy(); + this._weatherPanel = null; + this.attachRecentImagery(null); + } destroy() { if (this._destroyed) return; this._destroyed = true; @@ -383,7 +393,7 @@ export class LayerPanel { /** Present every row from the layers' current state and render them. */ _paint() { - if (this._destroyed || !this._toggleContainer) return; + if (this._destroyed || this._suspended || !this._toggleContainer) return; const layers = new Map(this.getAll().map((layer) => [layer.id, layer])); const items = this._rows.map((row) => { if (row.heading) return { key: row.key, heading: t(row.heading) };