Merge branch 'main' into prereq-10-osm-overlays

This commit is contained in:
Said Turksever authored and GitHub committed 2026-11-03 14:34:32 +00:00
commit 8e40d2689b
424 files changed
+25311 -956

No files matched your search

+10
View File
@@ -603,6 +603,16 @@ test('the phone shell keeps the required attribution visible', () => {
idleCredit,
/display:\s*none|visibility:\s*hidden|opacity:\s*0/,
);
// A long credit line (map plus outline credits) wraps inside the screen:
// it wraps, and the box gets a real width, set before the 0.8 scale, so it
// is divided by it.
assert.match(idleCredit, /white-space: normal;/);
assert.match(
idleCredit,
/[^-]width: calc\(\(100vw - 20px\) \/ 0\.8\) !important;/,
);
assert.match(idleCredit, /max-width: none !important;/);
assert.match(idleCredit, /scale: 0\.8;/);
// Open sheet: above everything the sheet covers, drawn above the sheets.
const sheetCredit = rule(
'body.mobile-shell[data-mobile-sheet]:not(.recording-mode):not(.cockpit-mode):not(.ui-clean-view) #cesium-credits',
+3
View File
@@ -241,9 +241,12 @@ export function bindRadioControls() {
const pool = this._radioTunerPool.length
? this._radioTunerPool
: this._radioTunerStations;
// Silent unless a stream is active; an active stream follows the selection.
const { audioState } = this.radio.getUIState();
const cycled = this.radio.cycleStation(direction, {
rotate,
stationIds: pool.map((station) => station.id),
autoplay: ['loading', 'buffering', 'playing'].includes(audioState),
origin: 'user',
});
if (!cycled) {
+35 -1
View File
@@ -47,7 +47,9 @@ function fixture() {
publishTuner() {},
};
const calls = [];
const cycleOptions = [];
const subscribers = [];
const uiState = { audioState: 'stopped' };
const radio = {
subscribe(callback) {
subscribers.push(callback);
@@ -57,10 +59,12 @@ function fixture() {
endTuning() {
calls.push('endTuning');
},
cycleStation(direction) {
cycleStation(direction, options) {
calls.push(['cycle', direction]);
cycleOptions.push(options);
return true;
},
getUIState: () => uiState,
};
const actions = {
isRegistered: () => true,
@@ -90,6 +94,8 @@ function fixture() {
store,
actions,
calls,
cycleOptions,
uiState,
subscribers,
document,
cleanup() {
@@ -177,3 +183,31 @@ test('destruction releases an active tuner pointer after revoking listeners', ()
f.cleanup();
}
});
test('Next and Previous play only while a stream is active', () => {
const f = fixture();
try {
f.controls.connect();
for (const audioState of ['stopped', 'paused', 'error']) {
f.uiState.audioState = audioState;
f.store.act('cycleRadio', 1);
f.store.act('cycleRadio', -1);
}
assert.deepEqual(
f.cycleOptions.map((options) => options.autoplay),
[false, false, false, false, false, false],
);
f.cycleOptions.length = 0;
for (const audioState of ['loading', 'buffering', 'playing']) {
f.uiState.audioState = audioState;
f.store.act('cycleRadio', 1);
f.store.act('cycleRadio', -1);
}
assert.deepEqual(
f.cycleOptions.map((options) => options.autoplay),
[true, true, true, true, true, true],
);
} finally {
f.cleanup();
}
});
+3 -1
View File
@@ -25,7 +25,9 @@ export function renderRadioState(state) {
const interactive = enabled && !transitioning && !uncertain;
const selected = state.selected || null;
const hasStations = state.filteredCount > 0;
const activePlayback = ['playing', 'buffering'].includes(state.audioState);
const activePlayback = ['loading', 'buffering', 'playing'].includes(
state.audioState,
);
document
.getElementById('title-bar')
?.classList.toggle('radio-broadcasting', state.audioState === 'playing');
+4
View File
@@ -389,6 +389,10 @@ body:not(.ui-clean-view):not(.recording-mode) #cesium-credits {
}
@media (max-width: 900px) {
#command-dock #location-bar .location-toolbar-label {
overflow: hidden;
}
#command-dock {
--dock-popover-mobile-width: calc(100vw - 1.5rem);
}
+9 -1
View File
@@ -382,7 +382,15 @@ body.mobile-shell:not(.recording-mode):not(.cockpit-mode):not(.ui-clean-view)
right: auto;
bottom: calc(var(--mobile-tab-space) + 106px) !important;
left: 10px;
max-width: calc(100vw - 20px);
/* With the base rule's nowrap the credits run off the right edge, and a
max-width alone wrapped them into a narrow column: the line needs wrapping
and a real width. The width applies before the 0.8 scale, keeping the
scaled line 10px from the right edge, so every credit and "Data
attribution" stay on screen. Like the offset above, it must beat the
dock's width: auto !important. */
width: calc((100vw - 20px) / 0.8) !important;
max-width: none !important;
white-space: normal;
transform-origin: left bottom;
scale: 0.8;
}
+1 -1
View File
@@ -67,7 +67,7 @@ const radioWords = computed(() => ({
<a id="radio-station-homepage" :href="radio ? radio.station.homepage || undefined : '#'" target="_blank" rel="noopener noreferrer" :hidden="radio ? !radio.station.homepage : true">{{ t('STATION SITE') }}</a>
<a href="https://www.radio-browser.info/" target="_blank" rel="noopener noreferrer">{{ t('DIRECTORY: RADIO BROWSER') }}</a>
</div>
<p class="radio-privacy">{{ t('Audio connects directly to the broadcaster after you press play. Your IP is visible to that broadcaster.') }}</p>
<p class="radio-privacy">{{ t('Audio connects directly to the broadcaster when you press play, choose a station on the globe or ask voice to play. Your IP is visible to that broadcaster.') }}</p>
<SdrCard />
</RailPanel>
</template>