mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
feat(bhote-koshi): render the reconstruction panel from a store
The Bhote Koshi layer keeps its panel controls as plain records and presents them to a store; the context rail renders the panel as its last child and marks itself while it is on. Controls are bound to the enable that published them, and an unchanged frame is not sent again.
This commit is contained in:
1 parent
9ea6d7e8aa
commit
fd0d51c450
10 files changed
+580
-234
No files matched your search
@@ -0,0 +1,35 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { markRaw, shallowRef } from 'vue';
|
||||
|
||||
/**
|
||||
* The Bhote Koshi event's reconstruction panel as its view renders it. The
|
||||
* event layer (src/data/bhoteKoshiEvent.js) presents each control's state
|
||||
* and the field reports; the view sends clicks and slider moves back to the
|
||||
* connected controls.
|
||||
*/
|
||||
export const useBhoteKoshiPanelStore = defineStore(
|
||||
'gev-bhote-koshi-panel',
|
||||
() => {
|
||||
/**
|
||||
* `{owner, sceneBeat, refs, beforeDate, afterDate, reports}`, or null while
|
||||
* the event layer is off. Each entry of `refs` holds one control's
|
||||
* `textContent`, `hidden`, `disabled`, `value` and `attributes`.
|
||||
*/
|
||||
const panel = shallowRef(null);
|
||||
|
||||
/** @param {?object} next A fresh presentation; every publish re-renders. */
|
||||
function publish(next) {
|
||||
panel.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
/** The controls the panel's buttons, sliders and reports call. */
|
||||
const controls = shallowRef(null);
|
||||
|
||||
/** @param {?object} next The controls, or null to disconnect. */
|
||||
function connect(next) {
|
||||
controls.value = next && markRaw(next);
|
||||
}
|
||||
|
||||
return { panel, publish, controls, connect };
|
||||
},
|
||||
);
|
||||
@@ -3,8 +3,10 @@ import { storeToRefs } from 'pinia';
|
||||
import { t } from '../../i18n/index.js';
|
||||
import { vClassToggle } from '../rail/classToggle.js';
|
||||
import { useCockpitStore } from '../stores/cockpit.js';
|
||||
import { useBhoteKoshiPanelStore } from '../stores/bhoteKoshiPanel.js';
|
||||
import { useContextStore } from '../stores/context.js';
|
||||
import AwarenessPanel from './panels/AwarenessPanel.vue';
|
||||
import BhoteKoshiPanel from './panels/BhoteKoshiPanel.vue';
|
||||
import MissionDetail from './panels/MissionDetail.vue';
|
||||
import MissionRoster from './panels/MissionRoster.vue';
|
||||
import RadioPanel from './panels/RadioPanel.vue';
|
||||
@@ -13,11 +15,13 @@ import RadioPanel from './panels/RadioPanel.vue';
|
||||
// publishes them; until it does, the panel shows its initial markup.
|
||||
const { view: ctx } = storeToRefs(useContextStore());
|
||||
const { presence: cockpit } = storeToRefs(useCockpitStore());
|
||||
// While the Bhote Koshi event is on, its panel is the rail's only content.
|
||||
const { panel: bhotePanel } = storeToRefs(useBhoteKoshiPanelStore());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Global Context is deliberately independent from the left layer accordion. -->
|
||||
<aside id="right-context-rail" class="ui-scroll">
|
||||
<aside id="right-context-rail" class="ui-scroll" v-class-toggle="{ 'bhote-event-active': !!bhotePanel }">
|
||||
<div id="weather-panel" class="panel-collapsible" data-panel-id="weather-panel" hidden>
|
||||
<div class="panel-glow"></div>
|
||||
<div class="weather-panel-inner" data-rail-scroller>
|
||||
@@ -86,6 +90,7 @@ const { presence: cockpit } = storeToRefs(useCockpitStore());
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BhoteKoshiPanel />
|
||||
</aside>
|
||||
|
||||
<div id="scene-runtime"></div>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { t } from '../../../i18n/index.js';
|
||||
import { useBhoteKoshiPanelStore } from '../../stores/bhoteKoshiPanel.js';
|
||||
|
||||
// The Bhote Koshi reconstruction controls, at the end of the context rail,
|
||||
// as the event layer (src/data/bhoteKoshiEvent.js) presents them. Clicks and
|
||||
// slider moves go back to the layer.
|
||||
const { panel, controls } = storeToRefs(useBhoteKoshiPanelStore());
|
||||
const call = (name, ...args) => controls.value?.[name]?.(...args);
|
||||
const sliderValue = (event, scale) => Number(event.target.value) / scale;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside v-if="panel" id="bhote-koshi-event-panel" :class="{ 'bhote-event-scene-beat': panel.sceneBeat }" :aria-label="t('Bhote Koshi flood reconstruction controls')">
|
||||
<div class="bhote-event-header">
|
||||
<div>
|
||||
<div class="bhote-event-kicker">{{ t('EVENT RECONSTRUCTION · 26 AUG 2026') }}</div>
|
||||
<div class="bhote-event-title">{{ t('BHOTE KOSHI OUTBURST FLOOD') }}</div>
|
||||
</div>
|
||||
<button class="bhote-event-icon-btn" type="button" data-action="close" :title="t('Close event layer')" :aria-label="t('Close event layer')" @click="call('close')">×</button>
|
||||
</div>
|
||||
<div class="bhote-event-status-row">
|
||||
<span class="bhote-event-status observed" :hidden="panel.refs.observedStatus.hidden">{{ t('OBSERVED IMAGERY') }}</span>
|
||||
<span class="bhote-event-status reconstructed">{{ t('SCHEMATIC CORRIDOR') }}</span>
|
||||
</div>
|
||||
<section class="bhote-event-section" data-role="imagery-comparison" :hidden="panel.refs.imagerySection.hidden">
|
||||
<div class="bhote-event-section-head">
|
||||
<span>{{ t('2021 HISTORICAL REFERENCE / 2026 POST-EVENT') }}</span>
|
||||
<span data-role="split-readout">{{ panel.refs.splitReadout.textContent }}</span>
|
||||
</div>
|
||||
<input class="bhote-event-range" data-role="split" type="range" min="0" max="100" :value="panel.refs.splitInput.value" :aria-label="t('Historical reference and post-event image split')" @input="call('setSplit', sliderValue($event, 100))" />
|
||||
<div class="bhote-event-dates">
|
||||
<span data-role="before-date">{{ panel.beforeDate.date ? `${panel.beforeDate.name} · ` : panel.beforeDate.name }}<span v-if="panel.beforeDate.date" class="bhote-event-date">{{ panel.beforeDate.date }}</span></span>
|
||||
<span data-role="after-date">{{ panel.afterDate.date ? `${panel.afterDate.name} · ` : panel.afterDate.name }}<span v-if="panel.afterDate.date" class="bhote-event-date">{{ panel.afterDate.date }}</span></span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="bhote-event-section">
|
||||
<div class="bhote-event-section-head">
|
||||
<span data-role="timeline-label">{{ panel.refs.timelineLabel.textContent }}</span>
|
||||
<span data-role="time">{{ panel.refs.time.textContent }}</span>
|
||||
</div>
|
||||
<input class="bhote-event-range flood" data-role="progress" type="range" min="0" max="1000" :value="panel.refs.progressInput.value" :hidden="panel.refs.progressInput.hidden" :disabled="panel.refs.progressInput.disabled" :aria-label="panel.refs.progressInput.attributes['aria-label']" @input="call('previewProgress', sliderValue($event, 1000))" @change="call('commitProgress', sliderValue($event, 1000), { final: true })" />
|
||||
<div class="bhote-event-story-nav" :aria-label="t('Story beat navigation')">
|
||||
<button type="button" data-action="previous-beat" :title="t('Previous story beat')" :aria-label="panel.refs.previousButton.attributes['aria-label']" :disabled="panel.refs.previousButton.disabled" @click="call('previousBeat')">‹</button>
|
||||
<div class="bhote-event-story-current">
|
||||
<span data-role="beat-index">{{ panel.refs.beatIndex.textContent }}</span>
|
||||
<strong data-role="beat-title" aria-live="polite">{{ panel.refs.beatTitle.textContent }}</strong>
|
||||
<small data-role="beat-meta">{{ panel.refs.beatMeta.textContent }}</small>
|
||||
</div>
|
||||
<button type="button" data-action="next-beat" :title="t('Next story beat')" :aria-label="panel.refs.nextButton.attributes['aria-label']" :disabled="panel.refs.nextButton.disabled" @click="call('nextBeat')">›</button>
|
||||
</div>
|
||||
<div class="bhote-event-actions">
|
||||
<button type="button" data-action="play" :aria-label="panel.refs.playButton.attributes['aria-label']" :disabled="panel.refs.playButton.disabled" @click="call('play')">{{ panel.refs.playButton.textContent }}</button>
|
||||
<button type="button" data-action="play-scene" :hidden="panel.refs.playSceneButton.hidden" :title="t('Play the next shot and continue through this scene')" :aria-label="panel.refs.playSceneButton.attributes['aria-label']" :disabled="panel.refs.playSceneButton.disabled" @click="call('playScene')">{{ panel.refs.playSceneButton.textContent }}</button>
|
||||
<button type="button" data-action="cinematic" :aria-pressed="panel.refs.cinematicButton.attributes['aria-pressed']" :hidden="panel.refs.cinematicButton.hidden" :disabled="panel.refs.cinematicButton.disabled" @click="call('toggleCinematic')">{{ panel.refs.cinematicButton.textContent }}</button>
|
||||
<button type="button" data-action="story-replay" :aria-label="panel.refs.storyReplayButton.attributes['aria-label']" :hidden="panel.refs.storyReplayButton.hidden" :disabled="panel.refs.storyReplayButton.disabled" @click="call('replayCinematic')">{{ panel.refs.storyReplayButton.textContent }}</button>
|
||||
<button type="button" data-action="open-source" :hidden="panel.refs.openSourceButton.hidden" :disabled="panel.refs.openSourceButton.disabled" @click="call('openActiveSource')">{{ t('↗ OPEN ORIGINAL') }}</button>
|
||||
<button type="button" data-action="corridor" :hidden="panel.refs.corridorButton.hidden" :disabled="panel.refs.corridorButton.disabled" @click="call('focusCorridor')">{{ t('⌖ LOWER GORGE') }}</button>
|
||||
</div>
|
||||
</section>
|
||||
<details class="bhote-event-evidence">
|
||||
<summary>{{ t('FIELD REPORTS · {count}', { count: panel.reports.length }) }}</summary>
|
||||
<div class="bhote-event-report-list"><button v-for="(report, index) in panel.reports" :key="index" type="button" class="bhote-event-report" @click="call('openReport', index)"><span>{{ report.label }}</span><small>{{ report.status }}</small></button></div>
|
||||
</details>
|
||||
<div class="bhote-event-credit">
|
||||
<span>{{ t('GEOLOCATIONS · {credit}', { credit: 'GEO GEORGE SHADRACH' }) }}</span>
|
||||
<button type="button" data-action="geolocation-map" @click="call('openGeolocationMap')">{{ t('↗ OPEN PUBLIC MAP') }}</button>
|
||||
</div>
|
||||
<p class="bhote-event-caveat"><span data-role="caveat">{{ panel.refs.caveat.textContent }}</span><span data-role="imagery-cloud-note" :hidden="panel.refs.imageryCloudNote.hidden">{{ t(' Clouds are preserved from the source imagery.') }}</span></p>
|
||||
</aside>
|
||||
</template>
|
||||
Reference in new issue
Block a user