mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-07 20:04:00 +00:00
fix(launches): hand the camera over before stopping, and only blame a fresh index error
- releaseCameraOwnership marks the camera released before stopping, so a flight navigation asked to preserve survives; exit also releases it, so an inactive layer never cancels another owner's flight. - The failure toast names the mission index only when its error is from the current Context attempt (stats.errorAt), not an earlier one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
13219828e3
commit
efa4374644
7 files changed
+41
-10
No files matched your search
Vendored
+2
-1
@@ -14,10 +14,10 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
releaseCameraOwnership() {
|
||||
// Navigation takes the camera; a failed entry must not restore the old view.
|
||||
layerState._entryCameraView = null;
|
||||
layerState._cameraReleased = true;
|
||||
parts.panel.clearMissionRosterHover();
|
||||
parts.replay.stopMissionReplay();
|
||||
parts.selection.stopMissionZoomAnchor();
|
||||
layerState._cameraReleased = true;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -60,6 +60,7 @@ export function createControls({ state: layerState, services, parts, source }) {
|
||||
orbitMatches: layerState._orbitMatches,
|
||||
lastUpdate: layerState._lastUpdate,
|
||||
error: layerState._lastError,
|
||||
errorAt: layerState._lastErrorAt,
|
||||
stale: layerState._stale,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import http from 'node:http';
|
||||
import * as Cesium from 'cesium';
|
||||
import { DataLayerManager } from '../../data/manager.js';
|
||||
import { ContextControls } from '../../ui/contextControls.js';
|
||||
import { spaceMissionsIndexFailure } from '../../ui/contextActions.js';
|
||||
import { createLaunchSource } from './source.js';
|
||||
import { createState } from './state.js';
|
||||
import { createModel } from './model.js';
|
||||
@@ -454,7 +455,6 @@ test('a refresh failure after a loaded index keeps the mode and its records', as
|
||||
await f.settle();
|
||||
assert.deepEqual(f.view(), framed);
|
||||
});
|
||||
// Append to src/layers/launches/failedEntry.test.mjs (uses its fixture()).
|
||||
test('exiting during a successful entry flight cancels the mission-owned flight', async (t) => {
|
||||
const f = await fixture(t);
|
||||
f.upstream.status = 200;
|
||||
@@ -473,10 +473,7 @@ test('exiting during a successful entry flight cancels the mission-owned flight'
|
||||
'leaving Space Missions stops the flight it started (main cancels it)',
|
||||
);
|
||||
});
|
||||
// Append to src/layers/launches/failedEntry.test.mjs (uses its fixture()).
|
||||
// Encodes the "surface the reason where the user acted" fix: the roster that
|
||||
// carries 30-DAY MISSION INDEX UNAVAILABLE is hidden by the rollback, so the
|
||||
// lasting feedback (the toast) has to name the cause.
|
||||
// The rollback hides the roster, so the toast has to name the cause.
|
||||
test('a failed first load tells the user the mission index is unavailable', async (t) => {
|
||||
const f = await fixture(t);
|
||||
await f.manager.setEnabled('earthquakes', true, { origin: 'user' });
|
||||
@@ -491,3 +488,29 @@ test('a failed first load tells the user the mission index is unavailable', asyn
|
||||
`visible feedback names the failure, got ${JSON.stringify(f.toasts)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('navigation taking the camera keeps a mission flight it asked to preserve', async (t) => {
|
||||
const f = await fixture(t);
|
||||
f.upstream.status = 200;
|
||||
await f.manager.setEnabled('rocket-launches', true, { origin: 'user' });
|
||||
await f.settle();
|
||||
const flight = f.camera.flight;
|
||||
assert.ok(flight, 'the entry flight is in progress');
|
||||
f.missions.releaseCameraOwnership();
|
||||
assert.equal(f.camera.flight, flight, 'navigation decides about the flight');
|
||||
// After exit an inactive layer never cancels another owner's flight.
|
||||
await f.manager.setEnabled('rocket-launches', false, { origin: 'user' });
|
||||
await f.settle();
|
||||
f.camera.flyTo({ destination: new Cesium.Cartesian3(1, 1, 1) });
|
||||
const other = f.camera.flight;
|
||||
f.missions.releaseCameraOwnership();
|
||||
assert.equal(f.camera.flight, other);
|
||||
});
|
||||
|
||||
test("an earlier attempt's index error is not blamed for a later failure", async (t) => {
|
||||
const f = await fixture(t);
|
||||
await f.manager.setEnabled('rocket-launches', true, { origin: 'user' });
|
||||
await f.settle();
|
||||
assert.match(spaceMissionsIndexFailure(f.manager), /mission index/);
|
||||
assert.equal(spaceMissionsIndexFailure(f.manager, Date.now() + 1), null);
|
||||
});
|
||||
@@ -228,6 +228,7 @@ export function createIngestion({
|
||||
} catch (error) {
|
||||
if (layerState._enabled && token === layerState._lifecycleToken) {
|
||||
layerState._lastError = error.message;
|
||||
layerState._lastErrorAt = Date.now();
|
||||
console.warn('[Data:RocketLaunches] Fetch error:', error);
|
||||
// No index yet: fail the enable so Context rolls back.
|
||||
// After a loaded index, a failed refresh keeps its records.
|
||||
|
||||
@@ -285,6 +285,7 @@ export function createLifecycle({
|
||||
parts.ingestion.clearPostTleRetry();
|
||||
parts.panel.clearMissionRosterHover();
|
||||
parts.replay.stopMissionReplay();
|
||||
layerState._cameraReleased = true;
|
||||
parts.selection.stopMissionZoomAnchor();
|
||||
if (layerState._dataSource) layerState._dataSource.show = false;
|
||||
parts.orbitRendering.syncMissionOrbitPrimitiveVisibility();
|
||||
@@ -310,6 +311,7 @@ export function createLifecycle({
|
||||
await parts.ingestion.restoreSatelliteDependency();
|
||||
parts.panel.clearMissionRosterHover();
|
||||
parts.replay.stopMissionReplay();
|
||||
layerState._cameraReleased = true;
|
||||
parts.selection.stopMissionZoomAnchor();
|
||||
parts.ingestion.clearPostTleRetry();
|
||||
if (layerState._clickHandler) layerState._clickHandler.destroy();
|
||||
|
||||
@@ -19,6 +19,8 @@ export function createState({ services }) {
|
||||
|
||||
state._lastError = null;
|
||||
|
||||
state._lastErrorAt = null;
|
||||
|
||||
state._entryCameraView = null;
|
||||
|
||||
state._entryLandedView = null;
|
||||
|
||||
@@ -4,12 +4,13 @@ import {
|
||||
contextModeWord,
|
||||
} from '../contextModePolicy.js';
|
||||
|
||||
/** Why a Space Missions entry failed, when its mission index never loaded. */
|
||||
export function spaceMissionsIndexFailure(dataManager) {
|
||||
/** Why a Space Missions entry failed: its mission index never loaded, and
|
||||
* the error is no older than `since` (an earlier attempt's is not the cause). */
|
||||
export function spaceMissionsIndexFailure(dataManager, since = 0) {
|
||||
const stats = dataManager?.layers
|
||||
?.get('rocket-launches')
|
||||
?.module?.getStats?.();
|
||||
return stats?.error && stats.lastUpdate == null
|
||||
return stats?.error && stats.lastUpdate == null && stats.errorAt >= since
|
||||
? t(
|
||||
'Space Missions unavailable: the mission index could not load. Try again later.',
|
||||
)
|
||||
|
||||
@@ -60,11 +60,12 @@ export function _initGlobalContextPanel() {
|
||||
const nextMode =
|
||||
this._contextMode === 'space-missions' ? null : 'space-missions';
|
||||
this._claimContextVisualAuthority();
|
||||
const startedAt = Date.now();
|
||||
void this._runUserFacingContextAction(
|
||||
(notificationToken) =>
|
||||
this._selectContextMode(nextMode, { notificationToken }),
|
||||
() =>
|
||||
(nextMode && spaceMissionsIndexFailure(this._dataManager)) ||
|
||||
(nextMode && spaceMissionsIndexFailure(this._dataManager, startedAt)) ||
|
||||
t(
|
||||
'Space Missions could not complete the requested transition; try again',
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user