mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(world-overlay): render the visible map target list from a store
The world overlay presents its focusable mirror of painted cards through subscribeWorldOverlayActions() and answers activateWorldOverlayAction(); the application shell publishes the list to a store and the scene chrome renders the buttons and the status, announcing a repeated focus again. The module stays free of Vue for the portable transit entry, and its canvases remain painting surfaces.
This commit is contained in:
1 parent
662f51fdf3
commit
b22e138c55
11 files changed
+297
-82
No files matched your search
@@ -56,6 +56,7 @@ 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 { useWorldOverlayActionsStore } from './stores/worldOverlayActions.js';
|
||||
import { useDisplayStore } from './stores/display.js';
|
||||
import { useLocaleStore } from './stores/locale.js';
|
||||
import { useLocationStore } from './stores/location.js';
|
||||
@@ -106,6 +107,8 @@ export class StyleManager extends ShellFacade {
|
||||
CelestialRing,
|
||||
initTrackedReadout,
|
||||
initWorldOverlay,
|
||||
subscribeWorldOverlayActions,
|
||||
activateWorldOverlayAction,
|
||||
initDetection,
|
||||
setDetectionStyle,
|
||||
trafficLayer,
|
||||
@@ -479,6 +482,13 @@ export class StyleManager extends ShellFacade {
|
||||
// detection and tracked-readout initialize. It stays transparent until a
|
||||
// production source explicitly registers entries.
|
||||
initWorldOverlay(viewer);
|
||||
// Its focusable mirror of the painted cards, which the scene chrome
|
||||
// renders.
|
||||
const overlayActionsStore = useWorldOverlayActionsStore(applicationPinia());
|
||||
overlayActionsStore.connect({ activate: activateWorldOverlayAction });
|
||||
this._overlayActionsUnsubscribe = subscribeWorldOverlayActions((view) =>
|
||||
overlayActionsStore.publish(view),
|
||||
);
|
||||
|
||||
// Initialize detection overlay BEFORE style stages so the composite
|
||||
// stage is first in the post-process pipeline
|
||||
@@ -1834,7 +1844,12 @@ export class StyleManager extends ShellFacade {
|
||||
}
|
||||
destroyTrackedReadout();
|
||||
destroyDetection();
|
||||
this._overlayActionsUnsubscribe?.();
|
||||
this._overlayActionsUnsubscribe = null;
|
||||
destroyWorldOverlay();
|
||||
const overlayActionsStore = useWorldOverlayActionsStore(applicationPinia());
|
||||
overlayActionsStore.connect(null);
|
||||
overlayActionsStore.publish(null);
|
||||
this.celestialRing?.destroy();
|
||||
this._visualSettings.destroy();
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ import {
|
||||
initTrackedReadout,
|
||||
} from '../data/trackedReadout.js';
|
||||
import {
|
||||
activateWorldOverlayAction,
|
||||
destroyWorldOverlay,
|
||||
initWorldOverlay,
|
||||
subscribeWorldOverlayActions,
|
||||
} from '../overlays/worldOverlay.js';
|
||||
import {
|
||||
destroyDetection,
|
||||
@@ -88,6 +90,8 @@ export class StyleManager extends ApplicationShell {
|
||||
initTrackedReadout,
|
||||
destroyWorldOverlay,
|
||||
initWorldOverlay,
|
||||
subscribeWorldOverlayActions,
|
||||
activateWorldOverlayAction,
|
||||
destroyDetection,
|
||||
initDetection,
|
||||
cycleDetectionMode,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { markRaw, shallowRef } from 'vue';
|
||||
|
||||
/**
|
||||
* The visible map target list as its view renders it. The world overlay
|
||||
* (src/overlays/worldOverlay.js) presents the actionable cards it painted and
|
||||
* the last focus announcement; the application shell publishes them here and
|
||||
* connects the overlay's activation.
|
||||
*/
|
||||
export const useWorldOverlayActionsStore = defineStore(
|
||||
'gev-world-overlay-actions',
|
||||
() => {
|
||||
/**
|
||||
* `{owner, items: [{key, label, selected}], status}`, or null without an
|
||||
* overlay host. `owner` is passed back with each activation.
|
||||
*/
|
||||
const actions = shallowRef(null);
|
||||
|
||||
/** @param {?object} next A fresh presentation; every publish re-renders. */
|
||||
function publish(next) {
|
||||
actions.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
/** The controls whose `activate(owner, key)` a target button calls. */
|
||||
const controls = shallowRef(null);
|
||||
|
||||
/** @param {?object} next The controls, or null to disconnect. */
|
||||
function connect(next) {
|
||||
controls.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
return { actions, publish, controls, connect };
|
||||
},
|
||||
);
|
||||
@@ -4,6 +4,7 @@ import { t } from '../../i18n/index.js';
|
||||
import { useDisplayStore } from '../stores/display.js';
|
||||
import { useLocationStore } from '../stores/location.js';
|
||||
import { useStatusStore } from '../stores/status.js';
|
||||
import { useWorldOverlayActionsStore } from '../stores/worldOverlayActions.js';
|
||||
import { vClassToggle } from '../rail/classToggle.js';
|
||||
import ImagerySplitView from './panels/ImagerySplitView.vue';
|
||||
import MissionReplayOverlay from './panels/MissionReplayOverlay.vue';
|
||||
@@ -21,6 +22,24 @@ const { style } = storeToRefs(useDisplayStore());
|
||||
const { loading, traffic, cctv, toast, fps } = storeToRefs(useStatusStore());
|
||||
// The ORBIT indicator LocationControls offers with the location tray.
|
||||
const { view: location } = storeToRefs(useLocationStore());
|
||||
// The focusable mirror of the overlay cards the world overlay painted.
|
||||
const { actions: targets, controls: overlayControls } = storeToRefs(
|
||||
useWorldOverlayActionsStore(),
|
||||
);
|
||||
// Each button activates the target of the list it was rendered for.
|
||||
const onOverlayAction = (owner, key) => () =>
|
||||
overlayControls.value?.activate(owner, key);
|
||||
// Every accepted focus rewrites the status, so the live region announces a
|
||||
// repeated focus again.
|
||||
const vOverlayStatus = {
|
||||
mounted(el, { value }) {
|
||||
el.textContent = value?.status ?? '';
|
||||
},
|
||||
updated(el, { value, oldValue }) {
|
||||
if (value?.announced !== oldValue?.announced || !value)
|
||||
el.textContent = value?.status ?? '';
|
||||
},
|
||||
};
|
||||
// Every show rewrites the toast's text, so the live region announces a
|
||||
// repeated message again; hiding leaves it as it fades.
|
||||
const vToastText = {
|
||||
@@ -39,8 +58,8 @@ const vToastText = {
|
||||
<canvas id="world-overlay-canvas"></canvas>
|
||||
</div>
|
||||
<div id="world-overlay-actions" class="world-overlay-accessibility" role="region" aria-label="Visible map targets" data-i18n-aria-label>
|
||||
<div id="world-overlay-action-list"></div>
|
||||
<div id="world-overlay-status" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
<div id="world-overlay-action-list"><button v-for="item in targets?.items ?? []" :key="item.key" type="button" :aria-label="item.label" :aria-pressed="String(item.selected)" :data-overlay-action-key="item.key" :onClick="onOverlayAction(targets.owner, item.key)">{{ item.label }}</button></div>
|
||||
<div id="world-overlay-status" v-overlay-status="targets" role="status" aria-live="polite" aria-atomic="true"></div>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
|
||||
Reference in new issue
Block a user