feat(celestial-ring): render the full-globe ring from a store

The celestial ring presents its overlay to a store and the scene chrome
renders it in the globe container, handing back the two canvases the
ring paints. The application shell passes the store in so the keyhole
helpers other packages import stay free of Vue. Without its canvases the
ring still tracks whether the globe is framed and turns itself off.
This commit is contained in:
Sameh Khamis committed 2026-10-31 21:20:27 -07:00
1 parent fd0d51c450
commit 265aa605bd
10 files changed
+496 -90

No files matched your search

+2
View File
@@ -55,6 +55,7 @@ import { PanelNavigation } from './panelNavigation.js';
import { applicationPinia, createUiRuntime } from './vueRuntime.js';
import { useContextStore } from './stores/context.js';
import { useMissionReplayStore } from './stores/missionReplay.js';
import { useCelestialRingStore } from './stores/celestialRing.js';
import { useDisplayStore } from './stores/display.js';
import { useLocaleStore } from './stores/locale.js';
import { useLocationStore } from './stores/location.js';
@@ -414,6 +415,7 @@ export class StyleManager extends ShellFacade {
// Cesium canvas but below the HUD/detection/readout z ladder.
this.celestialRing = new CelestialRing(viewer, {
enabled: false,
store: useCelestialRingStore(applicationPinia()),
onAutoDisable: () =>
this.setCelestialRingEnabled(false, {
syncShare: !!this.shareLinkManager,
+33
View File
@@ -0,0 +1,33 @@
import { defineStore } from 'pinia';
import { markRaw, shallowRef } from 'vue';
/**
* The full-globe celestial ring as its view renders it. The ring owner
* (src/celestialRing.js) presents the overlay's state, the outline and where
* the sun and moon layers and markers sit; the view hands its two canvases
* back to the connected controls, which paint them.
*/
export const useCelestialRingStore = defineStore('gev-celestial-ring', () => {
/**
* `{owner, container, enabled, visible, globeVisible, ephemerisUpdates,
* size, outline, sun, moon, sunMarker, moonMarker}`, or null while no ring
* exists. `container` is the element it sits in; `owner` is passed back
* with each canvas.
*/
const ring = shallowRef(null);
/** @param {?object} next A fresh presentation; every publish re-renders. */
function publish(next) {
ring.value = next && markRaw(next);
}
/** The controls whose `attach(owner, kind, canvas)` each canvas calls. */
const controls = shallowRef(null);
/** @param {?object} next The controls, or null to disconnect. */
function connect(next) {
controls.value = next && markRaw(next);
}
return { ring, publish, controls, connect };
});
+2
View File
@@ -11,6 +11,7 @@ import DataPacks from './panels/DataPacks.vue';
import SceneInteractions from './panels/SceneInteractions.vue';
import SplitFlapText from './panels/SplitFlapText.vue';
import WindInspectionMarker from './panels/WindInspectionMarker.vue';
import CelestialRing from './panels/CelestialRing.vue';
// The ACTIVE STYLE corner shows the style VisualSettings publishes.
const { style } = storeToRefs(useDisplayStore());
@@ -101,6 +102,7 @@ const vToastText = {
<ImagerySplitView />
<DataPacks />
<CelestialRing />
<SceneInteractions />
<WindInspectionMarker />
<MissionReplayOverlay />
+49
View File
@@ -0,0 +1,49 @@
<script setup>
import { storeToRefs } from 'pinia';
import { useCelestialRingStore } from '../../stores/celestialRing.js';
// The full-globe sun/moon ring, inside the viewer's container, as the ring
// owner (src/celestialRing.js) presents it.
const { ring, controls } = storeToRefs(useCelestialRingStore());
// Each canvas goes to the owner it was rendered for, which paints it.
const vRingCanvas = {
mounted(el, { value }) {
controls.value?.attach(value.owner, value.kind, el);
},
beforeUnmount(el, { value }) {
controls.value?.attach(value.owner, value.kind, null);
},
};
const px = (value) => `${value}px`;
const layerStyle = (layer) => ({
...(ring.value.size && {
width: px(ring.value.size.width),
height: px(ring.value.size.height),
}),
...(layer && { transform: layer.transform, opacity: layer.opacity }),
});
// The outline is hidden before it has ever been placed.
const outlineStyle = (outline) =>
outline && {
display: outline.hidden ? 'none' : '',
...(outline.width != null && {
left: px(outline.left),
top: px(outline.top),
width: px(outline.width),
height: px(outline.height),
}),
};
const markerStyle = (marker) =>
marker && {
left: px(marker.left),
top: px(marker.top),
opacity: marker.opacity,
};
</script>
<template>
<Teleport v-if="ring" :to="ring.container">
<div :key="ring.id" id="celestial-ring-overlay" class="celestial-ring-overlay" :class="{ visible: ring.visible, disabled: !ring.enabled }" aria-hidden="true" :data-globe-visible="ring.globeVisible ?? undefined" :data-ephemeris-updates="ring.ephemerisUpdates ?? undefined"><div class="celestial-ring-outline" :style="outlineStyle(ring.outline)"></div><canvas v-ring-canvas="{ owner: ring.owner, kind: 'sun' }" class="celestial-ring-canvas celestial-sun-canvas" :style="layerStyle(ring.sun)"></canvas><canvas v-ring-canvas="{ owner: ring.owner, kind: 'moon' }" class="celestial-ring-canvas celestial-moon-canvas" :style="layerStyle(ring.moon)"></canvas><span class="celestial-marker celestial-sun material-symbols-outlined" :style="markerStyle(ring.sunMarker)">light_mode</span><span class="celestial-marker celestial-moon material-symbols-outlined" :style="markerStyle(ring.moonMarker)">dark_mode</span></div>
</Teleport>
</template>