refactor: separate application UI state owners

This commit is contained in:
Sameh Khamis committed 2026-09-14 15:29:10 -07:00
1 parent ddc1edf3af
commit dc81cf585c
34 files changed
+3378 -2201

No files matched your search

+233
View File
@@ -0,0 +1,233 @@
import * as Cesium from 'cesium';
import {
beginDeferredNavigation,
reassertNavigationHandoff,
runExplicitNavigation,
} from '../navigationPolicy.js';
/** Own camera authority generations, pending search UI and tracking handoff. */
export class NavigationController {
constructor({
viewer,
tracking,
searchInput,
interruptCameraMotion,
isCockpitActive,
clearLocation,
cancelShareSelection,
getDataManager,
stopOrbit,
showToast,
}) {
Object.assign(this, {
viewer,
tracking,
searchInput,
interruptCameraMotion,
isCockpitActive,
clearLocation,
cancelShareSelection,
getDataManager,
stopOrbit,
showToast,
});
this._navigationGeneration = 0;
this._activeLocationSearchGeneration = null;
this._disposed = false;
}
_stampNavigation({
cancelPendingSelection = true,
clearSearchedLocation = true,
} = {}) {
const { flightsLayer, militaryFlightsLayer, satellitesLayer } =
this.tracking;
this._navigationGeneration += 1;
// A newer destination owns the camera, so the last free-text search is no
// longer where we are. DEFERRED navigation opts out here and clears at the
// reassert seam instead: a geocode that never resolves moves no camera, and
// a lookup that fails must not blank a readout that is still true.
if (clearSearchedLocation) this.clearLocation();
if (cancelPendingSelection) {
const passivelyClearedShareSelection = this.cancelShareSelection();
try {
flightsLayer.cancelPendingTrackingRestore?.();
} catch {
/* best effort */
}
try {
militaryFlightsLayer.cancelPendingTrackingRestore?.();
} catch {
/* best effort */
}
try {
satellitesLayer.cancelPendingTrackingRestore?.();
} catch {
/* best effort */
}
// A deliberate destination supersedes share-selected entities that have
// not arrived yet. Active owners publish their clear when released.
if (!passivelyClearedShareSelection && !flightsLayer.getTrackedInfo?.()) {
this.getDataManager()?.setLayerParams(
'flights',
{
selectedFlightsTrackingId: null,
},
{ origin: 'tool' },
);
}
if (
!passivelyClearedShareSelection &&
!militaryFlightsLayer.getTrackedInfo?.()
) {
this.getDataManager()?.setLayerParams(
'military',
{
selectedMilitaryTrackingId: null,
},
{ origin: 'tool' },
);
}
if (
!passivelyClearedShareSelection &&
!satellitesLayer.getTrackedInfo?.()
) {
this.getDataManager()?.setLayerParams(
'satellites',
{
selectedSatTrackingId: null,
},
{ origin: 'tool' },
);
}
}
if (this._activeLocationSearchGeneration !== null) {
this._settleLocationSearchUi(this._activeLocationSearchGeneration);
}
return this._navigationGeneration;
}
_settleLocationSearchUi(generation) {
if (this._activeLocationSearchGeneration !== generation) return;
this._activeLocationSearchGeneration = null;
this.searchInput?.classList.remove('searching', 'expanded');
if (this.searchInput) this.searchInput.value = '';
this.searchInput?.blur();
}
_releaseFollowCamera({
preserveVesselSelection = true,
preserveCameraFlight = false,
trackingOrigin = 'tool',
} = {}) {
const {
flightsLayer,
militaryFlightsLayer,
satellitesLayer,
aisLiveVesselsLayer,
militaryAwarenessLayer,
rocketLaunchesLayer,
} = this.tracking;
let contactSelected = false;
try {
contactSelected = Boolean(
militaryAwarenessLayer.releaseCameraOwnership?.({
preserveVesselSelection,
origin: trackingOrigin,
}),
);
} catch {
try {
flightsLayer.stopTracking?.({ origin: trackingOrigin });
} catch {
/* best-effort release */
}
try {
militaryFlightsLayer.stopTracking?.({ origin: trackingOrigin });
} catch {
/* best-effort release */
}
if (!preserveVesselSelection) {
try {
aisLiveVesselsLayer.clearSelection?.();
} catch {
/* best-effort release */
}
}
}
try {
satellitesLayer.stopTracking?.({ origin: trackingOrigin });
} catch {
/* best-effort release */
}
try {
rocketLaunchesLayer.releaseCameraOwnership?.();
} catch {
/* best-effort release */
}
this.viewer.trackedEntity = undefined;
this.interruptCameraMotion('explicit-navigation');
this.stopOrbit();
if (!preserveCameraFlight) this.viewer.camera.cancelFlight();
try {
this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
} catch {
/* teardown race */
}
return contactSelected;
}
_runExplicitNavigation(noun, navigate, releaseOptions = undefined) {
return runExplicitNavigation({
disposed: this._disposed,
cockpitActive: this.isCockpitActive(),
noun,
showToast: (text) => this.showToast(text),
stamp: () => this._stampNavigation(),
release: () => this._releaseFollowCamera(releaseOptions),
navigate,
});
}
_beginDeferredNavigation(
noun = 'location',
{ cancelPendingSelection = true } = {},
) {
return beginDeferredNavigation({
disposed: this._disposed,
cockpitActive: this.isCockpitActive(),
noun,
showToast: (text) => this.showToast(text),
// The searched-location readout survives the STAMP; only a flight that
// actually starts invalidates it (see the release hook below).
stamp: () =>
this._stampNavigation({
cancelPendingSelection,
clearSearchedLocation: false,
}),
});
}
_reassertNavigationHandoff(generation) {
return reassertNavigationHandoff({
generation,
currentGeneration: this._navigationGeneration,
cockpitActive: this.isCockpitActive(),
disposed: this._disposed,
showToast: (text) => this.showToast(text),
// Reached only once the handoff is granted, immediately before the
// deferred flight starts — so a lookup that failed, was superseded, or
// was refused by the cockpit leaves the old readout standing.
release: () => {
this.clearLocation();
return this._releaseFollowCamera();
},
});
}
stop() {
this._disposed = true;
}
destroy() {
this.stop();
this._stampNavigation();
}
}