mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Choosing a language in the picker now applies it at once. The choice is stored and ?lang is kept in step through replaceState, and the picker keeps focus. Code that writes translated text itself re-presents it on a switch: onLocaleChange() lets an owner listen, removed at teardown, and status writers hand over a function of the locale (voice cards, scene director, SDR, draw hints, imagery split, map sources, Bhote Koshi media) so the words are rebuilt rather than re-sent. The HUD, cockpit, radio, CCTV, launch, awareness and credit texts follow the same hook, and applyDocumentTranslations can be applied again without undoing text code wrote since. The unsaved-work registry and persistLocaleAndReload existed only to guard the reload, so they go, with their catalog entries.
172 lines
5.6 KiB
JavaScript
172 lines
5.6 KiB
JavaScript
import { bindRadioControls } from './radioBindings.js';
|
|
import {
|
|
createRadioTunerState,
|
|
renderRadioState,
|
|
showRadioTuner,
|
|
} from './radioPresentation.js';
|
|
import { useRadioStore } from './stores/radio.js';
|
|
import { applicationPinia } from './vueRuntime.js';
|
|
import { onLocaleChange } from '../i18n/index.js';
|
|
|
|
/**
|
|
* Own Radio interaction, the tuner and the subscription; receive playback and
|
|
* application actions. The panel view renders the rest of the Radio panel
|
|
* from what this publishes (`gev-radio` store).
|
|
*/
|
|
export class RadioControls {
|
|
constructor({
|
|
elements,
|
|
radio,
|
|
actions,
|
|
canvas,
|
|
store = useRadioStore(applicationPinia()),
|
|
}) {
|
|
Object.assign(this, elements);
|
|
this.store = store;
|
|
this.radio = radio;
|
|
this.actions = actions;
|
|
this.canvas = canvas;
|
|
this.destroyed = false;
|
|
this.listeners = new AbortController();
|
|
this._radioUnsubscribe = null;
|
|
this._radioState = null;
|
|
this._radioView = null;
|
|
this._radioCategorySignature = '';
|
|
this._radioFilterOptions = null;
|
|
this._radioVolumeShown = null;
|
|
this._cockpitDisclosure = { display: false, radio: false };
|
|
this._radioTunerView = createRadioTunerState();
|
|
this._radioTunerStations = [];
|
|
this._radioTunerPool = [];
|
|
this._radioTunerDragging = false;
|
|
this._radioTunerDragStartSlot = 0;
|
|
this._radioTunerDragSnapshot = null;
|
|
this._radioTunerLastSlot = 0;
|
|
this._radioTunerDragDirection = 0;
|
|
this._radioTunerCoordinate = 0;
|
|
this._radioTunerPointerId = null;
|
|
this._radioTunerKeyboardKey = null;
|
|
this._radioTunerBandSignature = '';
|
|
this._radioTunerSelectedId = null;
|
|
this._radioTunerBandPinnedForNavigation = false;
|
|
this._refreshRadioTunerBand = null;
|
|
this._refreshRadioTunerText = null;
|
|
bindRadioControls.call(this);
|
|
// The panel carries translated text; present it again in a new locale,
|
|
// from a fresh layer state when there is one (its category labels are
|
|
// worded by the layer).
|
|
this._offLocale = onLocaleChange(() => {
|
|
const state = this.radio.getUIState?.() ?? this._radioState;
|
|
if (state) this._renderRadioState(state);
|
|
this._refreshRadioTunerText?.();
|
|
});
|
|
}
|
|
|
|
listen(target, type, handler, options = {}) {
|
|
target?.addEventListener(type, handler, {
|
|
...options,
|
|
signal: this.listeners.signal,
|
|
});
|
|
}
|
|
|
|
connect() {
|
|
this._radioUnsubscribe?.();
|
|
this._radioUnsubscribe = null;
|
|
if (this.destroyed) return;
|
|
this._radioUnsubscribe = this.radio.subscribe?.((state) =>
|
|
this._renderRadioState(state),
|
|
);
|
|
}
|
|
|
|
_renderRadioState(state) {
|
|
renderRadioState.call(this, state);
|
|
}
|
|
|
|
/**
|
|
* Reveal the newly enabled directory and transport inside Context without
|
|
* moving focus, the page, or the globe. Only the expanded Enable path calls
|
|
* this helper.
|
|
* @param {HTMLElement} trigger Initiating Radio Enable button.
|
|
* @returns {Promise<boolean>} Whether the internal scroller moved.
|
|
*/
|
|
async _revealRadioControlsAfterExplicitEnable(trigger) {
|
|
const contextPanel = document.getElementById('global-context-panel');
|
|
const scroller =
|
|
(document.documentElement?.dataset.uiTheme === 'cyber' &&
|
|
contextPanel?.querySelector('.cyber-panel-body')) ||
|
|
contextPanel?.querySelector('.global-context-panel-inner');
|
|
const directory = this._radioPanel?.querySelector('.radio-directory-row');
|
|
const transport = this._radioPanel?.querySelector('.radio-transport');
|
|
if (
|
|
!contextPanel ||
|
|
contextPanel.classList.contains('collapsed') ||
|
|
!scroller ||
|
|
!directory ||
|
|
!transport ||
|
|
!this._radioState?.enabled
|
|
)
|
|
return false;
|
|
|
|
await new Promise((resolve) =>
|
|
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
|
);
|
|
if (this.destroyed || !this._radioState?.enabled || !trigger?.isConnected)
|
|
return false;
|
|
|
|
const viewport = scroller.getBoundingClientRect();
|
|
const directoryRect = directory.getBoundingClientRect();
|
|
const transportRect = transport.getBoundingClientRect();
|
|
const margin = 10;
|
|
const minimum =
|
|
scroller.scrollTop + transportRect.bottom - (viewport.bottom - margin);
|
|
const maximum =
|
|
scroller.scrollTop + directoryRect.top - (viewport.top + margin);
|
|
const desired =
|
|
minimum <= maximum
|
|
? Math.min(Math.max(scroller.scrollTop, minimum), maximum)
|
|
: minimum;
|
|
const next = Math.min(
|
|
Math.max(0, scroller.scrollHeight - scroller.clientHeight),
|
|
Math.max(0, desired),
|
|
);
|
|
if (Math.abs(next - scroller.scrollTop) < 1) return false;
|
|
const reducedMotion = window.matchMedia?.(
|
|
'(prefers-reduced-motion: reduce)',
|
|
)?.matches;
|
|
scroller.scrollTo({
|
|
top: next,
|
|
behavior: reducedMotion ? 'auto' : 'smooth',
|
|
});
|
|
return true;
|
|
}
|
|
|
|
destroy() {
|
|
if (this.destroyed) return;
|
|
this.destroyed = true;
|
|
this._offLocale();
|
|
this.listeners.abort();
|
|
if (this._storeToken) this.store.detach(this._storeToken);
|
|
this._radioUnsubscribe?.();
|
|
this._radioUnsubscribe = null;
|
|
const pointer = this._radioTunerPointerId;
|
|
this._radioTunerPointerId = null;
|
|
this._radioTunerDragging = false;
|
|
this._radioTunerKeyboardKey = null;
|
|
this._radioTunerDragSnapshot = null;
|
|
this._radioTunerPool = [];
|
|
this._radioTunerStations = [];
|
|
this._radioTunerBandPinnedForNavigation = false;
|
|
try {
|
|
if (pointer !== null)
|
|
this._radioTunerSlider?.releasePointerCapture(pointer);
|
|
} catch {
|
|
/* capture already released */
|
|
}
|
|
showRadioTuner.call(this, { dragging: false, static: false });
|
|
document
|
|
.getElementById('title-bar')
|
|
?.classList.remove('radio-broadcasting');
|
|
this.radio.endTuning();
|
|
}
|
|
}
|