mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
refactor: separate application UI state owners
This commit is contained in:
1 parent
ddc1edf3af
commit
dc81cf585c
34 files changed
+3378
-2201
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user