From fa2dc9fc87944f749ff0ad7352d2bd563beacef2 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sun, 1 Nov 2026 11:06:54 -0800 Subject: [PATCH] feat(radio): hand the tuner slider's events to RadioControls through the store RadioTuner binds the slider's pointer, key and value events in its template and calls RadioControls through the radio store; the drag, its slot and the pointer capture stay with RadioControls, unchanged. --- CHANGELOG.md | 13 +-- docs/VUE-MIGRATION.md | 3 +- src/ui/radioBindings.js | 153 ++++++++++------------------- src/ui/views/panels/RadioTuner.vue | 16 ++- 4 files changed, 72 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 384a88168..be12030d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,12 +58,13 @@ list renders. Street Level's fit and panorama filters and Recent Imagery's mode are `UiSegmented` too: the Street Level store's owner answers `setPano` and `setRenderMode`, and `RecentImageryReadout` takes - `onSetMode`. The Radio panel's and Cockpit's internet radio controls call - `RadioControls`, and the local RTL-SDR card's controls `LocalSdrControls`, - through the radio store's new `connect`/`act`; the card labels its gain - options itself. `act` returns what the answering action returned. `syncScopeControls` (`./ui/display`) and - `shouldHideCollapsedRightPanels` (`./ui/layout`) are deprecated: the Display - view and the rail now own what they did. `./ui/input` imports Vue + `onSetMode`. The Radio panel's and Cockpit's internet radio controls and + the tuner slider's events call `RadioControls`, and the local RTL-SDR + card's controls `LocalSdrControls`, through the radio store's new + `connect`/`act`; the card labels its gain options itself. `act` returns + what the answering action returned. `syncScopeControls` (`./ui/display`) + and `shouldHideCollapsedRightPanels` (`./ui/layout`) are deprecated: the + Display view and the rail now own what they did. `./ui/input` imports Vue (samehkhamis). - `./ui/cctv` and `./ui/radio` import Vue and Pinia: their controllers diff --git a/docs/VUE-MIGRATION.md b/docs/VUE-MIGRATION.md index 91f91b904..daa17ff84 100644 --- a/docs/VUE-MIGRATION.md +++ b/docs/VUE-MIGRATION.md @@ -153,7 +153,8 @@ a feature supplying only its accent. the same way; fields a person edits take their value through `v-field-value`, which never writes a focused field; `RadioTuner` renders the directory tuner, where RadioControls keeps the - drag and its slot and publishes the band, readouts, slider and needle, + drag and its slot, takes the slider's pointer, key and value events + through the store and publishes the band, readouts, slider and needle, and the component lays out the tick tape against the dial's measured width and lays out the snapped needle before it drops the drag class; `ContextView` renders the mode switch and the nearby sites diff --git a/src/ui/radioBindings.js b/src/ui/radioBindings.js index 3fd03519a..58664ed90 100644 --- a/src/ui/radioBindings.js +++ b/src/ui/radioBindings.js @@ -382,7 +382,47 @@ export function bindRadioControls() { }, tunerListenerOptions, ); - // The Radio panel's and Cockpit's controls call these through the store. + const updateTunerFromPointer = (event) => { + const rect = this._radioTunerSlider?.getBoundingClientRect(); + if (!rect || !this._radioTunerStations.length) return false; + const position = radioTunerPointerPosition( + event.clientX, + rect.left, + rect.width, + this._radioTunerStations.length, + ); + if (position.stationIndex > this._radioTunerLastSlot) + this._radioTunerDragDirection = 1; + else if (position.stationIndex < this._radioTunerLastSlot) + this._radioTunerDragDirection = -1; + this._radioTunerLastSlot = position.stationIndex; + this._radioTunerCoordinate = position.coordinate; + showRadioTuner.call(this, { slot: position.stationIndex }); + tunerPreview({ coordinate: position.coordinate }); + return true; + }; + const releaseNavigationBand = () => { + this._radioTunerBandPinnedForNavigation = false; + }; + this.listen( + this.canvas, + 'pointerdown', + releaseNavigationBand, + tunerListenerOptions, + ); + this.listen( + this.canvas, + 'wheel', + releaseNavigationBand, + tunerListenerOptions, + ); + // The directory order is catalog/filter authority, not camera authority. + // Globe motion therefore never rebuilds or re-ranks the frequency band. + this._radioSelectedHandler = () => + this.actions.setPanelCollapsed('radio-panel', false); + this.listen(document, 'gev:radio-selected', this._radioSelectedHandler); + // The Radio panel's and Cockpit's controls, and the tuner slider's events, + // call these through the store. // Cockpit owns the Cesium camera even though it intentionally clears // viewer.trackedEntity, so its station steps pass `rotate: false`: they // must never start the map-view rotation/fallback flights that would @@ -421,30 +461,7 @@ export function bindRadioControls() { showRadioVolume.call(this, value); this.actions.setParams({ volume: value / 100 }, { origin: 'user' }); }, - }); - const updateTunerFromPointer = (event) => { - const rect = this._radioTunerSlider?.getBoundingClientRect(); - if (!rect || !this._radioTunerStations.length) return false; - const position = radioTunerPointerPosition( - event.clientX, - rect.left, - rect.width, - this._radioTunerStations.length, - ); - if (position.stationIndex > this._radioTunerLastSlot) - this._radioTunerDragDirection = 1; - else if (position.stationIndex < this._radioTunerLastSlot) - this._radioTunerDragDirection = -1; - this._radioTunerLastSlot = position.stationIndex; - this._radioTunerCoordinate = position.coordinate; - showRadioTuner.call(this, { slot: position.stationIndex }); - tunerPreview({ coordinate: position.coordinate }); - return true; - }; - this.listen( - this._radioTunerSlider, - 'pointerdown', - (event) => { + tunerPointerDown: (event) => { if (!beginTuner()) return; this._radioTunerPointerId = event.pointerId; this._radioTunerSlider.focus({ preventScroll: true }); @@ -456,12 +473,7 @@ export function bindRadioControls() { updateTunerFromPointer(event); event.preventDefault(); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'pointermove', - (event) => { + tunerPointerMove: (event) => { if ( !this._radioTunerDragging || this._radioTunerPointerId !== event.pointerId @@ -470,12 +482,7 @@ export function bindRadioControls() { updateTunerFromPointer(event); event.preventDefault(); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'input', - () => { + tunerInput: () => { if (this._radioTunerPointerId !== null || this._radioTunerKeyboardKey) return; // A drag the input starts begins from the selected station's slot. @@ -496,21 +503,11 @@ export function bindRadioControls() { this._radioTunerCoordinate = inputSlot.stationIndex; tunerPreview({ coordinate: this._radioTunerCoordinate }); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'change', - () => { + tunerChange: () => { if (this._radioTunerPointerId === null && !this._radioTunerKeyboardKey) finishTuner(true); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'pointerup', - (event) => { + tunerPointerUp: (event) => { if (this._radioTunerPointerId !== event.pointerId) return; updateTunerFromPointer(event); finishTuner(true); @@ -521,12 +518,7 @@ export function bindRadioControls() { } event.preventDefault(); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'pointercancel', - (event) => { + tunerPointerCancel: (event) => { if ( this._radioTunerPointerId !== null && this._radioTunerPointerId !== event.pointerId @@ -539,24 +531,14 @@ export function bindRadioControls() { } finishTuner(false); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'lostpointercapture', - (event) => { + tunerLostPointerCapture: (event) => { if ( this._radioTunerDragging && this._radioTunerPointerId === event.pointerId ) finishTuner(false); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'keydown', - (event) => { + tunerKeyDown: (event) => { if (event.key === 'Escape' && this._radioTunerDragging) { event.preventDefault(); finishTuner(false); @@ -602,12 +584,7 @@ export function bindRadioControls() { showRadioTuner.call(this, { slot: slot.slot }); tunerPreview({ coordinate: this._radioTunerCoordinate }); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'keyup', - (event) => { + tunerKeyUp: (event) => { if ( !this._radioTunerKeyboardKey || event.key !== this._radioTunerKeyboardKey @@ -616,32 +593,6 @@ export function bindRadioControls() { event.preventDefault(); finishTuner(true); }, - tunerListenerOptions, - ); - this.listen( - this._radioTunerSlider, - 'blur', - () => finishTuner(true), - tunerListenerOptions, - ); - const releaseNavigationBand = () => { - this._radioTunerBandPinnedForNavigation = false; - }; - this.listen( - this.canvas, - 'pointerdown', - releaseNavigationBand, - tunerListenerOptions, - ); - this.listen( - this.canvas, - 'wheel', - releaseNavigationBand, - tunerListenerOptions, - ); - // The directory order is catalog/filter authority, not camera authority. - // Globe motion therefore never rebuilds or re-ranks the frequency band. - this._radioSelectedHandler = () => - this.actions.setPanelCollapsed('radio-panel', false); - this.listen(document, 'gev:radio-selected', this._radioSelectedHandler); + tunerBlur: () => finishTuner(true), + }); } diff --git a/src/ui/views/panels/RadioTuner.vue b/src/ui/views/panels/RadioTuner.vue index 0a200246e..e7bac5a2e 100644 --- a/src/ui/views/panels/RadioTuner.vue +++ b/src/ui/views/panels/RadioTuner.vue @@ -15,11 +15,15 @@ import { useRadioStore } from '../../stores/radio.js'; // The Radio panel's directory tuner. RadioControls owns the drag and // publishes the band, the readouts, the slider and where the needle sits; -// until it does, the tuner shows its initial markup. The tick tape is laid -// out here against the dial's width. A hidden dial measures 0 wide, so the -// last real width is kept and no ticks are drawn before there is one: ticks -// laid out for no width would slide into place when the tuner appears. -const { tuner } = storeToRefs(useRadioStore()); +// until it does, the tuner shows its initial markup. The slider hands its +// pointer, key and value events to RadioControls through the store. The +// tick tape is laid out here against the dial's width. A hidden dial +// measures 0 wide, so the last real width is kept and no ticks are drawn +// before there is one: ticks laid out for no width would slide into place +// when the tuner appears. +const store = useRadioStore(); +const { act } = store; +const { tuner } = storeToRefs(store); const dial = useTemplateRef('dial'); const dialWidth = shallowRef(0); let resizeObserver = null; @@ -68,6 +72,8 @@ const vTunerDragging = {