refactor(app): share application components and request services

This commit is contained in:
Sameh Khamis committed 2026-09-13 22:41:11 -07:00
1 parent a0a0128a59
commit a67bc60a29
84 files changed
+2798 -1731

No files matched your search

+1 -36
View File
@@ -1,36 +1 @@
import { StyleManager } from '../ui.js';
import { flyToAustin } from '../camera.js';
import { initCockpitCloudEffects } from '../cockpitCloudEffects.js';
/** Construct the existing controls and camera presentation. */
export function createStandaloneControls({
scene: { viewer, mapStackController },
loaderStatus,
placeSearch,
defer,
}) {
// Initialize the style manager (post-processing, HUD, locations, share links)
const styleManager = new StyleManager(viewer, {
mapStackController,
placeSearch,
});
defer(() => styleManager.orbitController.stop());
defer(() => styleManager.hud.destroy());
defer(() => styleManager.dispose());
// The previous multi-canvas weather compositor remains disabled. Cockpit
// clouds use a separate, capped low-resolution GPU pass that never attaches
// Cesium fog or post-process stages and is fully stopped in map mode.
const weatherEffects = null;
const cockpitCloudEffects = initCockpitCloudEffects(viewer);
defer(() => cockpitCloudEffects?.destroy());
// If no share link state, do default fly-to Austin
if (!styleManager.hasShareState) {
loaderStatus.textContent = 'Flying to Austin, TX...';
defer(flyToAustin(viewer));
} else {
loaderStatus.textContent = 'Restoring shared view...';
}
return { styleManager, weatherEffects, cockpitCloudEffects };
}
export { createApplicationControls as createStandaloneControls } from '../app/controls.js';
+1 -80
View File
@@ -1,80 +1 @@
import { DataLayerManager } from '../data/manager.js';
import flightsLayer from '../data/flights.js';
import militaryFlightsLayer from '../data/militaryFlights.js';
import alprCamerasLayer from '../data/alprCameras.js';
import earthquakesLayer from '../data/earthquakes.js';
import satellitesLayer from '../data/satellites.js';
import rocketLaunchesLayer from '../data/rocketLaunches.js';
import trafficLayer from '../data/traffic.js';
import cctvLayer from '../data/cctv.js';
import radioLayer from '../data/radio.js';
import bikeshareLayer from '../data/bikeshare.js';
import aisLiveVesselsLayer from '../data/aisLiveVessels.js';
import militaryInstallationsLayer from '../data/militaryInstallations.js';
import militaryAwarenessLayer from '../data/militaryAwareness.js';
import localDataLayers from '../data/localLayers.js';
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
/** Register the standalone layer catalog before allowing state restoration. */
export function createStandaloneData({
scene: { viewer },
controls: { styleManager },
allowQaRegistration,
defer,
}) {
// Initialize data layer manager
const dataManager = new DataLayerManager(viewer, {
allowQaRegistration,
});
defer(async () => {
await dataManager.destroyAll();
if (dataManager.layers.size)
throw new Error(
`Data layers could not be destroyed: ${[...dataManager.layers.keys()].join(', ')}`,
);
});
dataManager.register(flightsLayer);
dataManager.register(militaryFlightsLayer);
dataManager.register(earthquakesLayer);
dataManager.register(alprCamerasLayer);
dataManager.register(satellitesLayer);
dataManager.register(rocketLaunchesLayer);
rocketLaunchesLayer.attachDataManager(dataManager);
dataManager.register(trafficLayer);
dataManager.register(cctvLayer);
dataManager.register(radioLayer);
dataManager.register(bikeshareLayer);
dataManager.register(aisLiveVesselsLayer);
dataManager.register(militaryInstallationsLayer);
dataManager.register(militaryAwarenessLayer);
militaryAwarenessLayer.attachDataManager(dataManager);
for (const layer of localDataLayers) {
dataManager.register(layer);
}
// Restoration starts only after the complete production registry is sealed.
dataManager.finalizeRegistrations(LAYER_STATE_REGISTRY);
if (allowQaRegistration) {
window.__gevQaRegisterLayer = (targetManager, layerModule) => {
if (targetManager !== dataManager)
throw new Error('QA layer manager mismatch');
return dataManager.registerForQa(layerModule);
};
window.__gevQaUnregisterLayer = (targetManager, layerId) => {
if (targetManager !== dataManager)
throw new Error('QA layer manager mismatch');
return dataManager.unregisterForQa(layerId);
};
const register = window.__gevQaRegisterLayer;
const unregister = window.__gevQaUnregisterLayer;
defer(() => {
if (window.__gevQaRegisterLayer === register)
delete window.__gevQaRegisterLayer;
if (window.__gevQaUnregisterLayer === unregister)
delete window.__gevQaUnregisterLayer;
});
}
dataManager.buildTogglePanel(document.getElementById('data-toggles'));
styleManager.attachDataManager(dataManager);
return { dataManager };
}
export { createApplicationData as createStandaloneData } from '../app/data.js';
+1 -25
View File
@@ -1,25 +1 @@
/**
* Extract a human-readable error message from any thrown value.
* Handles Error objects, strings, and plain objects with message/error fields.
* @param {*} error — caught exception value
* @returns {string} best-effort error description
*/
export function describeError(error) {
if (!error) return 'Unknown initialization error';
if (error instanceof Error) {
if (error.message && error.message.trim()) return error.message.trim();
return error.name || 'Initialization error';
}
if (typeof error === 'string' && error.trim()) return error.trim();
if (typeof error === 'object') {
const maybeMessage = String(error.message || error.error || '').trim();
if (maybeMessage) return maybeMessage;
try {
const serialized = JSON.stringify(error);
if (serialized && serialized !== '{}') return serialized;
} catch {
// ignore serialization error
}
}
return String(error);
}
export { describeError } from '../app/errors.js';
+1 -105
View File
@@ -1,105 +1 @@
import * as Cesium from 'cesium';
import { createApplicationViewer } from '../app/viewer.js';
import { registerDataCredits } from '../data/dataCredits.js';
import { configureCreditKeyboardAccess } from '../creditKeyboard.js';
import { MapStackController } from '../mapStackController.js';
import { loadPhotorealisticTileset } from '../mapStartup.js';
import { initLogoGaze } from '../logoGaze.js';
import { uninstallRenderGovernor } from '../renderGovernor.js';
import { describeError } from './errors.js';
/** Construct the standalone globe using the caller's local configuration. */
export async function createStandaloneScene({
googleApiKey,
cesiumToken,
loaderStatus,
signal,
defer,
}) {
defer(initLogoGaze());
const previousKey = window.__GOOGLE_MAPS_API_KEY__;
if (googleApiKey) {
window.__GOOGLE_MAPS_API_KEY__ = googleApiKey;
defer(() => {
if (window.__GOOGLE_MAPS_API_KEY__ !== googleApiKey) return;
if (previousKey === undefined) delete window.__GOOGLE_MAPS_API_KEY__;
else window.__GOOGLE_MAPS_API_KEY__ = previousKey;
});
}
loaderStatus.textContent = 'Configuring viewer...';
// Provider attribution stays visible, including clean-view and recording.
const creditContainer = document.createElement('div');
creditContainer.id = 'cesium-credits';
document.body.appendChild(creditContainer);
defer(() => creditContainer.remove());
const viewer = createApplicationViewer({
container: 'cesiumContainer',
creditContainer,
});
defer(() => {
uninstallRenderGovernor(viewer);
if (!viewer.isDestroyed()) viewer.destroy();
});
registerDataCredits(viewer);
configureCreditKeyboardAccess(document);
loaderStatus.textContent =
googleApiKey || cesiumToken
? 'Loading Google 3D Tiles...'
: 'Loading the keyless globe...';
const photoreal = await loadPhotorealisticTileset(Cesium, {
googleApiKey,
cesiumToken,
});
const tileset = photoreal.tileset;
// A provider can finish after cancellation; retain ownership of its result.
defer(() => {
if (tileset && !tileset.isDestroyed()) {
if (!viewer.scene.primitives.remove(tileset)) tileset.destroy();
}
});
signal.throwIfAborted();
if (tileset) {
viewer.scene.primitives.add(tileset);
// NOTE: Cesium World Terrain intentionally disabled — conflicts with Google 3D Tiles at high zoom.
// Google Photorealistic 3D Tiles provide their own terrain/elevation.
viewer.scene.globe.show = false;
console.info(`[Init] Google 3D Tiles loaded via ${photoreal.route}.`);
} else {
if (photoreal.errors.length) {
const tileError = photoreal.errors.at(-1);
console.warn(
'[Init] Google 3D Tiles unavailable, using the keyless globe:',
tileError,
);
const tileErrorDetail = describeError(tileError);
loaderStatus.textContent = `Google 3D Tiles unavailable (${tileErrorDetail}). Loading the keyless globe...`;
}
viewer.scene.globe.show = true;
}
loaderStatus.textContent = 'Initializing systems...';
const mapStackController = new MapStackController(viewer, {
googleTileset: tileset,
cesiumToken,
initialStack: tileset ? 'photoreal' : 'esri-imagery',
// Task 5 (height-datum fix): rebroadcast stack changes as a window
// CustomEvent so data layers (CCTV per-regime ground resolution) can
// react without coupling MapStackController to layer modules. Fires on
// 'switching'/'ready'/'error'; listeners derive the surface regime from
// live scene state, so intermediate emissions are harmless.
onChange: (state) => {
window.dispatchEvent(
new CustomEvent('gev:map-stack-changed', { detail: state }),
);
},
onError: (message) => console.warn('[MapStack]', message),
});
defer(() => mapStackController.destroy());
await mapStackController.setStack(tileset ? 'photoreal' : 'esri-imagery', {
silent: true,
});
signal.throwIfAborted();
return { viewer, tileset, mapStackController };
}
export { createApplicationScene as createStandaloneScene } from '../app/scene.js';
+5 -48
View File
@@ -1,51 +1,8 @@
import { initFirstRunExperience } from '../firstRunExperience.js';
import { startApplicationChrome } from '../app/startupChrome.js';
import { initKeySetup } from '../keySetup.js';
/** Reveal welcome controls only after restoration and the loading transition. */
export function startStandaloneChrome({
loadingScreen,
styleManager,
dataManager,
signal,
}) {
let disposed = false;
let firstRun;
let revealTimer;
let resolveDelay;
const minimumDelay = new Promise((resolve) => {
resolveDelay = resolve;
export function startStandaloneChrome(options) {
return startApplicationChrome({
initializeSettings: initKeySetup,
...options,
});
const delayTimer = setTimeout(resolveDelay, 1000);
const revealFirstRun = () => {
if (disposed || signal.aborted || firstRun) return;
firstRun = initFirstRunExperience({ styleManager, dataManager });
clearTimeout(revealTimer);
loadingScreen.removeEventListener('transitionend', revealFirstRun);
};
void Promise.all([styleManager.initialRestorePromise, minimumDelay])
.catch(() => {
/* Restoration reports its own outcome through the controls. */
})
.then(() => {
if (disposed || signal.aborted) return;
loadingScreen.classList.add('hidden');
loadingScreen.addEventListener('transitionend', revealFirstRun, {
once: true,
});
revealTimer = setTimeout(revealFirstRun, 900);
});
const keySetup = initKeySetup({ signal });
// Own the pending initializer too; it must not reveal a dialog after abort.
void keySetup.catch(() =>
console.error('Provider settings initialization failed'),
);
return async () => {
disposed = true;
clearTimeout(delayTimer);
clearTimeout(revealTimer);
resolveDelay();
loadingScreen.removeEventListener('transitionend', revealFirstRun);
firstRun?.destroy();
(await keySetup.catch(() => null))?.destroy();
};
}
+3 -2
View File
@@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
import vm from 'node:vm';
const source = readFileSync(
new URL('./startupChrome.js', import.meta.url),
new URL('../app/startupChrome.js', import.meta.url),
'utf8',
)
.replace(/^import .*;\n/gm, '')
@@ -37,7 +37,8 @@ function fixture() {
};
vm.createContext(context);
vm.runInContext(source, context);
const stop = context.startStandaloneChrome({
const stop = context.startApplicationChrome({
initializeSettings: context.initKeySetup,
loadingScreen: {
classList: { add: (value) => events.push(value) },
addEventListener: (type, listener) => listeners.set(type, listener),
+5 -117
View File
@@ -1,120 +1,8 @@
import { SceneDirector } from '../scenes/director.js';
import { initAnnotations } from '../annotations/index.js';
import { initGevVoiceCommands } from '../voice/gevRealtime.js';
import { installScopeMask, destroyScopeMask } from '../scopeMask.js';
import {
installRenderGovernor,
getRenderGovernorDiagnostics,
governorRequestRender,
holdContinuousRender,
releaseContinuousRender,
} from '../renderGovernor.js';
import { createApplicationTools } from '../app/tools.js';
import { startStandaloneChrome } from './startupChrome.js';
/** Attach scene tools, rendering listeners and the standalone debug handle. */
export function createStandaloneTools({
scene,
controls,
data,
loadingScreen,
placeSearch,
voice = {},
signal,
defer,
}) {
const { viewer, tileset, mapStackController } = scene;
const { styleManager, weatherEffects, cockpitCloudEffects } = controls;
const { dataManager } = data;
const sceneDirector = new SceneDirector(viewer, styleManager, dataManager);
defer(() => sceneDirector.destroy());
const annotations = initAnnotations({ viewer, tileset, placeSearch });
defer(() => {
if (window.__gevAnnotations === annotations) delete window.__gevAnnotations;
annotations.destroy();
export function createStandaloneTools(options) {
return createApplicationTools({
startChrome: startStandaloneChrome,
...options,
});
defer(
startStandaloneChrome({ loadingScreen, styleManager, dataManager, signal }),
);
// Idle render governor: flips the scene into requestRenderMode whenever
// nothing animates per frame. Installed AFTER every module above has had
// its chance to register pre-install holds. (perf wave 2)
installRenderGovernor(viewer);
// Install the explicit scope mask used by the DISPLAY controls.
installScopeMask(viewer);
defer(() => destroyScopeMask());
// The follow camera recomputes the tracked target's dead-reckon position
// every frame — tracking anything is a per-frame animation. (perf wave 2)
const removeTrackingListener = viewer.trackedEntityChanged.addEventListener(
() => {
if (viewer.trackedEntity) holdContinuousRender('tracked-entity');
else releaseContinuousRender('tracked-entity');
},
);
// Hidden-state suspension (perf wave 2): when the window/tab is hidden,
// stop the default render loop outright — a hidden canvas repaints for
// nobody, and browser rAF throttling still lets throttled frames burn
// GPU. Holder/data state is untouched, so return is seamless: restore
// the loop, refresh the one DOM surface we gated, render a frame.
const syncVisibilitySuspension = () => {
const hidden = document.hidden;
viewer.useDefaultRenderLoop = !hidden;
cockpitCloudEffects?.setSuspended?.(hidden);
if (!hidden) {
if (dataManager._panelRefreshPendingOnVisible) {
dataManager._panelRefreshPendingOnVisible = false;
dataManager._refreshTogglePanel();
}
governorRequestRender('visibility-restore');
}
};
document.addEventListener('visibilitychange', syncVisibilitySuspension);
defer(() =>
document.removeEventListener('visibilitychange', syncVisibilitySuspension),
);
defer(() => {
removeTrackingListener();
releaseContinuousRender('tracked-entity');
});
// Apply the CURRENT state too — bootstrap can complete while the tab is
// already hidden, and waiting for the next transition would leave the
// loop burning behind a hidden tab. (perf wave 2 fix)
syncVisibilitySuspension();
window.__godsEyeView = {
viewer,
styleManager,
tileset,
dataManager,
sceneDirector,
mapStackController,
annotations,
weatherEffects,
cockpitCloudEffects,
getRenderGovernorDiagnostics,
requestRender: governorRequestRender,
};
const debug = window.__godsEyeView;
defer(() => {
if (window.__godsEyeView === debug) delete window.__godsEyeView;
});
const voiceCommands = initGevVoiceCommands({
...voice,
signal,
placeSearch,
viewer,
styleManager,
dataManager,
sceneDirector,
annotations,
});
defer(() => {
voiceCommands.stop({ removeUi: true });
if (window.__gevVoiceCommands === voiceCommands)
delete window.__gevVoiceCommands;
});
debug.voiceCommands = voiceCommands;
return { sceneDirector, annotations, voiceCommands };
}
+1 -153
View File
@@ -1,153 +1 @@
/** Compose UI controls with the application's existing engines and layer instances. */
import { StyleManager as ApplicationShell } from '../ui/applicationShell.js';
import { LocationSearch } from '../ui/location.js';
import {
CITY_POIS,
GLOBE_VIEW,
flyToGlobeView,
flyToPresetLocation,
flyToPOI,
searchAndFlyTo,
} from '../locations.js';
import { interruptCameraMotion } from '../cameraVerbs.js';
import { IntelHUD } from '../hud.js';
import { ShareLinkManager } from '../sharelink.js';
import { OrbitController } from '../orbit.js';
import {
CelestialRing,
getKeyholeFadeTuning,
isCelestialRingStyleSupported,
setKeyholeFadeTuning,
} from '../celestialRing.js';
import {
destroyTrackedReadout,
initTrackedReadout,
} from '../data/trackedReadout.js';
import {
destroyWorldOverlay,
initWorldOverlay,
} from '../overlays/worldOverlay.js';
import {
destroyDetection,
initDetection,
cycleMode as cycleDetectionMode,
getDetectionDiagnostics as readDetectionDiagnostics,
getDetectionTuning,
getMode as getDetectionMode,
setMode as setDetectionModeByLabel,
suspendDetection,
resumeDetection,
setDetectionStyle,
setDetectionTuning,
} from '../data/detection.js';
import trafficLayer from '../data/traffic.js';
import flightsLayer from '../data/flights.js';
import militaryFlightsLayer from '../data/militaryFlights.js';
import { isTr3b, toggleTr3b } from '../data/tr3bRegistry.js';
import satellitesLayer from '../data/satellites.js';
import cctvLayer from '../data/cctv.js';
import radioLayer from '../data/radio.js';
import bikeshareLayer from '../data/bikeshare.js';
import aisLiveVesselsLayer from '../data/aisLiveVessels.js';
import militaryAwarenessLayer from '../data/militaryAwareness.js';
import militaryInstallationsLayer from '../data/militaryInstallations.js';
import rocketLaunchesLayer from '../data/rocketLaunches.js';
import {
cachedGroundFloor,
cachedMeshFloor,
GROUND_FLOOR_LIFT_M,
meshFloorPreferred,
warmGroundFloor,
} from '../data/groundFloor.js';
import { sampleMeshFloorCells } from '../data/meshFloorSampler.js';
import {
holdContinuousRender,
releaseContinuousRender,
governorRequestRender,
} from '../renderGovernor.js';
import {
setScopeMaskEnabled,
isScopeMaskEnabled,
setScopeMaskFeather,
getScopeMaskFeather,
setScopeTerminusOverride,
getScopeTerminusOverride,
clampScopeTerminusPct,
} from '../scopeMask.js';
import {
fetchRegionalBrief,
regionalDistanceM,
weatherCodeLabel,
} from '../data/regionalBrief.js';
export class StyleManager extends ApplicationShell {
constructor(viewer, options = {}) {
super(viewer, {
...options,
services: {
CITY_POIS,
GLOBE_VIEW,
flyToGlobeView,
flyToPresetLocation,
flyToPOI,
searchAndFlyTo,
interruptCameraMotion,
IntelHUD,
ShareLinkManager,
OrbitController,
CelestialRing,
getKeyholeFadeTuning,
isCelestialRingStyleSupported,
setKeyholeFadeTuning,
destroyTrackedReadout,
initTrackedReadout,
destroyWorldOverlay,
initWorldOverlay,
destroyDetection,
initDetection,
cycleDetectionMode,
readDetectionDiagnostics,
getDetectionTuning,
getDetectionMode,
setDetectionModeByLabel,
suspendDetection,
resumeDetection,
setDetectionStyle,
setDetectionTuning,
trafficLayer,
flightsLayer,
militaryFlightsLayer,
isTr3b,
toggleTr3b,
satellitesLayer,
cctvLayer,
radioLayer,
bikeshareLayer,
aisLiveVesselsLayer,
militaryAwarenessLayer,
militaryInstallationsLayer,
rocketLaunchesLayer,
cachedGroundFloor,
cachedMeshFloor,
GROUND_FLOOR_LIFT_M,
meshFloorPreferred,
warmGroundFloor,
sampleMeshFloorCells,
holdContinuousRender,
releaseContinuousRender,
governorRequestRender,
setScopeMaskEnabled,
isScopeMaskEnabled,
setScopeMaskFeather,
getScopeMaskFeather,
setScopeTerminusOverride,
getScopeTerminusOverride,
clampScopeTerminusPct,
fetchRegionalBrief,
regionalDistanceM,
weatherCodeLabel,
LocationSearch,
},
});
}
}
export { StyleManager } from '../ui/composition.js';