feat: add country selection, cron automation, sparkle effects and layout fixes
This commit is contained in:
+387
-5
@@ -1,10 +1,11 @@
|
||||
import { loadRadioStations } from './radio/loadRadioStations.ts';
|
||||
import { radioCountries } from './radio/radioCountries.ts';
|
||||
import { defaultSelectedRadioCountryCodes, managedCountryCode, radioCountries } from './radio/radioCountries.ts';
|
||||
import { getLastManagedCatalogSource, loadManagedStations } from './radio/loadManagedStations.ts';
|
||||
import {
|
||||
clearPlayerPersistence,
|
||||
deletePersistedUserStationById,
|
||||
getPersistenceBackend,
|
||||
getPersistedSelectedRadioCountryCodes,
|
||||
getPlayerPersistenceSnapshot,
|
||||
getPersistedCastBothMode,
|
||||
getPersistedFavoriteStationIds,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
persistLastImportedAt,
|
||||
persistLastStationCountry,
|
||||
persistLastStationId,
|
||||
persistSelectedRadioCountryCodes,
|
||||
persistRecentStationHistory,
|
||||
persistStationHealth,
|
||||
persistStationUsageCounts,
|
||||
@@ -58,6 +60,17 @@ let stationLibraryQuery = '';
|
||||
let stationLibraryCategory = 'all';
|
||||
let stationLibraryCountry = 'all';
|
||||
let stationLibrarySort = 'recommended';
|
||||
let selectedRadioCountryCodes = new Set(defaultSelectedRadioCountryCodes);
|
||||
let draftSelectedRadioCountryCodes = new Set(defaultSelectedRadioCountryCodes);
|
||||
let availableRadioCountries = radioCountries.map((country) => ({
|
||||
...country,
|
||||
stationCount: null,
|
||||
pinned: country.code === managedCountryCode,
|
||||
}));
|
||||
let availableRadioCountriesLoaded = false;
|
||||
let availableRadioCountriesRequest = null;
|
||||
let countrySelectionQuery = '';
|
||||
let countrySelectionSaving = false;
|
||||
let stationLibraryPage = 0;
|
||||
let stationLibraryPageTotal = 1;
|
||||
let stationCatalogState = 'idle';
|
||||
@@ -123,6 +136,15 @@ const stationCountryFilterBtn = document.getElementById('station-country-filter-
|
||||
const stationCountryFilterMenu = document.getElementById('station-country-filter-menu');
|
||||
const stationCountryFilterText = document.getElementById('station-country-filter-text');
|
||||
const stationCountryFilterFlag = document.getElementById('station-country-filter-flag');
|
||||
const countrySelectionOverlay = document.getElementById('country-selection-overlay');
|
||||
const countrySelectionSearchInput = document.getElementById('country-selection-search-input');
|
||||
const countrySelectionSummaryEl = document.getElementById('country-selection-summary');
|
||||
const countrySelectionEmptyEl = document.getElementById('country-selection-empty');
|
||||
const countrySelectionListEl = document.getElementById('country-selection-list');
|
||||
const countrySelectionDefaultsBtn = document.getElementById('country-selection-defaults-btn');
|
||||
const countrySelectionAllBtn = document.getElementById('country-selection-all-btn');
|
||||
const countrySelectionSaveBtn = document.getElementById('country-selection-save-btn');
|
||||
const countrySelectionCancelBtn = document.getElementById('country-selection-cancel-btn');
|
||||
const stationSortSelect = document.getElementById('station-sort-select');
|
||||
const stationSortBtn = document.getElementById('station-sort-btn');
|
||||
const stationSortText = document.getElementById('station-sort-text');
|
||||
@@ -138,6 +160,7 @@ const installPromptBannerEl = document.getElementById('install-prompt-banner');
|
||||
const installPromptActionBtn = document.getElementById('install-prompt-action');
|
||||
const installPromptDismissBtn = document.getElementById('install-prompt-dismiss');
|
||||
|
||||
const radioCountryByCode = new Map(radioCountries.map((country) => [country.code, country]));
|
||||
const radioCountryCodeByName = new Map(radioCountries.map((country) => [country.name, country.code]));
|
||||
const radioCountryNameByCode = new Map(radioCountries.map((country) => [country.code, country.name]));
|
||||
|
||||
@@ -175,6 +198,8 @@ const castOutputText = document.getElementById('cast-output-text');
|
||||
const IMPORT_EXPORT_SCHEMA = 'radioplayer-local-data';
|
||||
const IMPORT_EXPORT_VERSION = 1;
|
||||
const PLAYBACK_START_TIMEOUT_MS = 12000;
|
||||
const RADIO_BROWSER_COUNTRIES_API_ENDPOINT = 'https://de1.api.radio-browser.info/json/countries';
|
||||
const SERVICE_WORKER_SYNC_COUNTRIES_MESSAGE = 'set-sync-countries';
|
||||
|
||||
// ── Utilities ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1091,6 +1116,324 @@ function restoreLastStationCountry() {
|
||||
stationLibraryCountry = savedCountry || 'all';
|
||||
}
|
||||
|
||||
function normalizeSelectedRadioCountryCodes(codes) {
|
||||
const normalized = Array.isArray(codes) ? codes : Array.from(codes || []);
|
||||
const uniqueCodes = Array.from(new Set(
|
||||
normalized
|
||||
.map((entry) => (typeof entry === 'string' ? entry.trim().toUpperCase() : ''))
|
||||
.filter((entry) => /^[A-Z]{2}$/.test(entry)),
|
||||
));
|
||||
|
||||
if (!uniqueCodes.includes(managedCountryCode)) {
|
||||
uniqueCodes.unshift(managedCountryCode);
|
||||
}
|
||||
|
||||
return uniqueCodes.length > 0 ? uniqueCodes : [...defaultSelectedRadioCountryCodes];
|
||||
}
|
||||
|
||||
function getSelectedRadioCountryCodes() {
|
||||
return normalizeSelectedRadioCountryCodes(selectedRadioCountryCodes);
|
||||
}
|
||||
|
||||
function restoreSelectedRadioCountryCodes() {
|
||||
selectedRadioCountryCodes = new Set(normalizeSelectedRadioCountryCodes(getPersistedSelectedRadioCountryCodes()));
|
||||
draftSelectedRadioCountryCodes = new Set(getSelectedRadioCountryCodes());
|
||||
}
|
||||
|
||||
function getAvailableRadioCountryNameByCode(countryCode) {
|
||||
const code = String(countryCode || '').trim().toUpperCase();
|
||||
if (!code) return '';
|
||||
return availableRadioCountries.find((country) => country.code === code)?.name
|
||||
|| radioCountryNameByCode.get(code)
|
||||
|| radioCountryByCode.get(code)?.name
|
||||
|| code;
|
||||
}
|
||||
|
||||
function mergeAvailableRadioCountries(countries) {
|
||||
const mergedCountries = new Map();
|
||||
|
||||
radioCountries.forEach((country) => {
|
||||
mergedCountries.set(country.code, {
|
||||
name: country.name,
|
||||
code: country.code,
|
||||
stationCount: null,
|
||||
pinned: country.code === managedCountryCode,
|
||||
});
|
||||
});
|
||||
|
||||
countries.forEach((country) => {
|
||||
const code = String(country?.code || '').trim().toUpperCase();
|
||||
if (!/^[A-Z]{2}$/.test(code)) return;
|
||||
|
||||
const existing = mergedCountries.get(code);
|
||||
mergedCountries.set(code, {
|
||||
name: String(country?.name || existing?.name || code).trim() || code,
|
||||
code,
|
||||
stationCount: Number.isFinite(country?.stationCount) && Number(country.stationCount) >= 0
|
||||
? Number(country.stationCount)
|
||||
: existing?.stationCount ?? null,
|
||||
pinned: code === managedCountryCode,
|
||||
});
|
||||
});
|
||||
|
||||
availableRadioCountries = Array.from(mergedCountries.values()).sort((left, right) => {
|
||||
if (left.code === managedCountryCode) return -1;
|
||||
if (right.code === managedCountryCode) return 1;
|
||||
return left.name.localeCompare(right.name, undefined, { sensitivity: 'base' });
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureAvailableRadioCountriesLoaded() {
|
||||
if (availableRadioCountriesLoaded) {
|
||||
return availableRadioCountries;
|
||||
}
|
||||
|
||||
if (availableRadioCountriesRequest) {
|
||||
return availableRadioCountriesRequest;
|
||||
}
|
||||
|
||||
availableRadioCountriesRequest = (async () => {
|
||||
try {
|
||||
const response = await fetch(RADIO_BROWSER_COUNTRIES_API_ENDPOINT, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
},
|
||||
mode: 'cors',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed loading countries: ${response.status}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!Array.isArray(payload)) {
|
||||
throw new Error('Invalid country list response');
|
||||
}
|
||||
|
||||
const remoteCountries = payload
|
||||
.map((entry) => ({
|
||||
code: String(entry?.iso_3166_1 || '').trim().toUpperCase(),
|
||||
name: String(entry?.name || '').trim(),
|
||||
stationCount: Number(entry?.stationcount),
|
||||
}))
|
||||
.filter((entry) => /^[A-Z]{2}$/.test(entry.code) && entry.name.length > 0);
|
||||
|
||||
mergeAvailableRadioCountries(remoteCountries);
|
||||
} catch (error) {
|
||||
console.debug('Falling back to bundled country list', error);
|
||||
mergeAvailableRadioCountries([]);
|
||||
} finally {
|
||||
availableRadioCountriesLoaded = true;
|
||||
availableRadioCountriesRequest = null;
|
||||
}
|
||||
|
||||
return availableRadioCountries;
|
||||
})();
|
||||
|
||||
return availableRadioCountriesRequest;
|
||||
}
|
||||
|
||||
function renderCountrySelectionOverlay() {
|
||||
if (!countrySelectionListEl || !countrySelectionSummaryEl || !countrySelectionEmptyEl) return;
|
||||
|
||||
if (countrySelectionSaveBtn) {
|
||||
countrySelectionSaveBtn.disabled = countrySelectionSaving;
|
||||
countrySelectionSaveBtn.textContent = countrySelectionSaving ? 'Saving...' : 'Save';
|
||||
}
|
||||
if (countrySelectionDefaultsBtn) countrySelectionDefaultsBtn.disabled = countrySelectionSaving;
|
||||
if (countrySelectionAllBtn) countrySelectionAllBtn.disabled = countrySelectionSaving;
|
||||
if (countrySelectionCancelBtn) countrySelectionCancelBtn.disabled = countrySelectionSaving;
|
||||
|
||||
const normalizedQuery = countrySelectionQuery.trim().toLowerCase();
|
||||
const entries = availableRadioCountries.filter((country) => {
|
||||
if (!normalizedQuery) return true;
|
||||
return country.name.toLowerCase().includes(normalizedQuery) || country.code.toLowerCase().includes(normalizedQuery);
|
||||
});
|
||||
|
||||
const selectedCount = normalizeSelectedRadioCountryCodes(draftSelectedRadioCountryCodes).length;
|
||||
const loadingLabel = availableRadioCountriesLoaded ? '' : ' Loading available countries...';
|
||||
countrySelectionSummaryEl.textContent = countrySelectionSaving
|
||||
? 'Saving country selection...'
|
||||
: `${selectedCount} countries enabled.${loadingLabel}`;
|
||||
|
||||
countrySelectionEmptyEl.classList.toggle('hidden', entries.length > 0);
|
||||
countrySelectionListEl.innerHTML = '';
|
||||
|
||||
if (entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
entries.forEach((country) => {
|
||||
const item = document.createElement('li');
|
||||
item.className = 'device country-selection-item';
|
||||
|
||||
const row = document.createElement('label');
|
||||
row.className = 'country-selection-option';
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.checked = country.code === managedCountryCode || draftSelectedRadioCountryCodes.has(country.code);
|
||||
checkbox.disabled = country.code === managedCountryCode || countrySelectionSaving;
|
||||
|
||||
checkbox.addEventListener('change', () => {
|
||||
if (checkbox.checked) {
|
||||
draftSelectedRadioCountryCodes.add(country.code);
|
||||
} else {
|
||||
draftSelectedRadioCountryCodes.delete(country.code);
|
||||
}
|
||||
renderCountrySelectionOverlay();
|
||||
});
|
||||
|
||||
const flag = document.createElement('span');
|
||||
flag.className = 'country-selection-flag';
|
||||
flag.setAttribute('aria-hidden', 'true');
|
||||
|
||||
const flagUrl = countryCodeToFlagUrl(country.code);
|
||||
if (flagUrl) {
|
||||
const flagImg = document.createElement('img');
|
||||
flagImg.src = flagUrl;
|
||||
flagImg.alt = '';
|
||||
flagImg.className = 'country-selection-flag-img';
|
||||
flagImg.loading = 'lazy';
|
||||
flagImg.referrerPolicy = 'no-referrer';
|
||||
flagImg.addEventListener('error', () => {
|
||||
flagImg.remove();
|
||||
flag.textContent = country.code === managedCountryCode ? '🇸🇮' : countryCodeToFlagEmoji(country.code);
|
||||
}, { once: true });
|
||||
flag.appendChild(flagImg);
|
||||
} else {
|
||||
flag.textContent = country.code === managedCountryCode ? '🇸🇮' : countryCodeToFlagEmoji(country.code);
|
||||
}
|
||||
|
||||
const copy = document.createElement('div');
|
||||
copy.className = 'country-selection-copy';
|
||||
|
||||
const title = document.createElement('div');
|
||||
title.className = 'device-main';
|
||||
title.textContent = country.code === managedCountryCode ? `${country.name} (managed)` : country.name;
|
||||
|
||||
const sub = document.createElement('div');
|
||||
sub.className = 'device-sub';
|
||||
if (country.code === managedCountryCode) {
|
||||
sub.textContent = 'Always enabled managed catalog';
|
||||
} else if (Number.isFinite(country.stationCount) && country.stationCount > 0) {
|
||||
sub.textContent = `${country.code} • ${country.stationCount.toLocaleString()} stations available`;
|
||||
} else {
|
||||
sub.textContent = country.code;
|
||||
}
|
||||
|
||||
copy.appendChild(title);
|
||||
copy.appendChild(sub);
|
||||
row.appendChild(checkbox);
|
||||
row.appendChild(flag);
|
||||
row.appendChild(copy);
|
||||
item.appendChild(row);
|
||||
countrySelectionListEl.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async function openCountrySelectionOverlay() {
|
||||
if (!countrySelectionOverlay) return;
|
||||
|
||||
draftSelectedRadioCountryCodes = new Set(getSelectedRadioCountryCodes());
|
||||
countrySelectionQuery = '';
|
||||
countrySelectionSaving = false;
|
||||
if (countrySelectionSearchInput) {
|
||||
countrySelectionSearchInput.value = '';
|
||||
}
|
||||
|
||||
renderCountrySelectionOverlay();
|
||||
countrySelectionOverlay.classList.remove('hidden');
|
||||
countrySelectionOverlay.setAttribute('aria-hidden', 'false');
|
||||
closeStationCountryFilter();
|
||||
|
||||
void ensureAvailableRadioCountriesLoaded().then(() => {
|
||||
renderCountrySelectionOverlay();
|
||||
});
|
||||
|
||||
window.setTimeout(() => countrySelectionSearchInput?.focus(), 30);
|
||||
}
|
||||
|
||||
function closeCountrySelectionOverlay() {
|
||||
if (!countrySelectionOverlay) return;
|
||||
countrySelectionOverlay.classList.add('hidden');
|
||||
countrySelectionOverlay.setAttribute('aria-hidden', 'true');
|
||||
countrySelectionSaving = false;
|
||||
}
|
||||
|
||||
function getNormalizedStationCountryCode(station) {
|
||||
const directCode = typeof station?.countryCode === 'string' ? station.countryCode.trim().toUpperCase() : '';
|
||||
if (/^[A-Z]{2}$/.test(directCode)) return directCode;
|
||||
|
||||
const countryValue = typeof station?.country === 'string' ? station.country.trim() : '';
|
||||
if (/^[A-Z]{2}$/.test(countryValue)) return countryValue.toUpperCase();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function isEnabledRadioCountryStation(station) {
|
||||
const countryCode = getNormalizedStationCountryCode(station);
|
||||
if (!countryCode) return true;
|
||||
return getSelectedRadioCountryCodes().includes(countryCode);
|
||||
}
|
||||
|
||||
async function sendSelectedCountriesToServiceWorker({ syncNow = false } = {}) {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
return { ok: false, skipped: true };
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.ready.catch(() => null);
|
||||
const target = navigator.serviceWorker.controller || registration?.active || registration?.waiting;
|
||||
if (!target) {
|
||||
return { ok: false, skipped: true };
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const channel = new MessageChannel();
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
resolve({ ok: false, timedOut: true });
|
||||
}, syncNow ? 25000 : 8000);
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
clearTimeout(timeoutId);
|
||||
resolve(event.data || { ok: true });
|
||||
};
|
||||
|
||||
target.postMessage({
|
||||
type: SERVICE_WORKER_SYNC_COUNTRIES_MESSAGE,
|
||||
countryCodes: getSelectedRadioCountryCodes(),
|
||||
syncNow,
|
||||
}, [channel.port2]);
|
||||
});
|
||||
}
|
||||
|
||||
async function saveCountrySelection() {
|
||||
if (countrySelectionSaving) return;
|
||||
|
||||
countrySelectionSaving = true;
|
||||
renderCountrySelectionOverlay();
|
||||
|
||||
try {
|
||||
selectedRadioCountryCodes = new Set(normalizeSelectedRadioCountryCodes(draftSelectedRadioCountryCodes));
|
||||
persistSelectedRadioCountryCodes(selectedRadioCountryCodes);
|
||||
await sendSelectedCountriesToServiceWorker({ syncNow: true });
|
||||
await loadStations();
|
||||
if (statusTextEl) {
|
||||
statusTextEl.textContent = 'Country selection updated';
|
||||
}
|
||||
closeCountrySelectionOverlay();
|
||||
} catch (error) {
|
||||
console.error('Saving country selection failed', error);
|
||||
if (statusTextEl) {
|
||||
statusTextEl.textContent = 'Unable to refresh selected countries';
|
||||
}
|
||||
} finally {
|
||||
countrySelectionSaving = false;
|
||||
renderCountrySelectionOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// ── castBothMode persistence & UI ────────────────────────────────────────────
|
||||
|
||||
function saveCastBothMode(val) {
|
||||
@@ -1563,7 +1906,7 @@ function getStationTechnicalLabel(station) {
|
||||
}
|
||||
|
||||
function getStationDetails(station) {
|
||||
return [getStationCountry(station), getStationTechnicalLabel(station)].filter(Boolean).join(' • ');
|
||||
return [getCountryDisplayName(getStationCountry(station)), getStationTechnicalLabel(station)].filter(Boolean).join(' • ');
|
||||
}
|
||||
|
||||
function getStationSearchText(station) {
|
||||
@@ -1637,7 +1980,7 @@ function getCountryDisplayName(countryValue) {
|
||||
if (value === 'all') return 'All countries';
|
||||
if (value === 'SI') return 'Slovenia (managed)';
|
||||
if (/^[A-Z]{2}$/i.test(value)) {
|
||||
const countryName = radioCountryNameByCode.get(value.toUpperCase());
|
||||
const countryName = getAvailableRadioCountryNameByCode(value.toUpperCase());
|
||||
if (countryName) return countryName;
|
||||
}
|
||||
return value;
|
||||
@@ -1649,7 +1992,7 @@ function getCountryFilterDisplayName(countryValue) {
|
||||
if (value === 'all') return 'All countries';
|
||||
if (value.toUpperCase() === 'SI') return 'SLOVENIA (managed)';
|
||||
if (/^[A-Z]{2}$/i.test(value)) {
|
||||
const countryName = radioCountryNameByCode.get(value.toUpperCase());
|
||||
const countryName = getAvailableRadioCountryNameByCode(value.toUpperCase());
|
||||
if (countryName) return countryName;
|
||||
}
|
||||
return value;
|
||||
@@ -1659,7 +2002,9 @@ function getCountryCodeFromValue(countryValue) {
|
||||
const value = String(countryValue || '').trim();
|
||||
if (!value || value === 'all') return '';
|
||||
if (/^[A-Z]{2}$/i.test(value)) return value.toUpperCase();
|
||||
return radioCountryCodeByName.get(value) || '';
|
||||
return radioCountryCodeByName.get(value)
|
||||
|| availableRadioCountries.find((country) => country.name === value)?.code
|
||||
|| '';
|
||||
}
|
||||
|
||||
function resetStationLibraryPage() {
|
||||
@@ -1839,6 +2184,15 @@ function renderCountryFilterOptions() {
|
||||
});
|
||||
|
||||
orderedCountries.forEach((country) => addOption(getCountryFilterDisplayName(country), country));
|
||||
|
||||
const manageButton = document.createElement('button');
|
||||
manageButton.type = 'button';
|
||||
manageButton.className = 'library-select-manage-btn';
|
||||
manageButton.textContent = 'Choose countries...';
|
||||
manageButton.addEventListener('click', () => {
|
||||
void openCountrySelectionOverlay();
|
||||
});
|
||||
stationCountryFilterMenu.appendChild(manageButton);
|
||||
}
|
||||
|
||||
function getFilteredStationEntries() {
|
||||
@@ -2262,6 +2616,7 @@ async function loadStations() {
|
||||
const managedRaw = await loadManagedStations().catch(() => []);
|
||||
|
||||
const normalizedRaw = raw
|
||||
.filter((station) => isEnabledRadioCountryStation(station))
|
||||
.map((s) => normalizeStationRecord(s))
|
||||
.filter((s) => s.enabled !== false && s.url && s.url.length > 0);
|
||||
|
||||
@@ -3293,6 +3648,26 @@ function setupEventListeners() {
|
||||
installAppBtn?.addEventListener('click', promptInstallApp);
|
||||
castBtn?.addEventListener('click', requestCastSession);
|
||||
editorCloseBtn?.addEventListener('click', closeEditorOverlay);
|
||||
countrySelectionCancelBtn?.addEventListener('click', closeCountrySelectionOverlay);
|
||||
countrySelectionOverlay?.addEventListener('click', (e) => {
|
||||
if (e.target === countrySelectionOverlay) closeCountrySelectionOverlay();
|
||||
});
|
||||
countrySelectionSearchInput?.addEventListener('input', () => {
|
||||
countrySelectionQuery = countrySelectionSearchInput.value || '';
|
||||
renderCountrySelectionOverlay();
|
||||
});
|
||||
countrySelectionDefaultsBtn?.addEventListener('click', () => {
|
||||
draftSelectedRadioCountryCodes = new Set(defaultSelectedRadioCountryCodes);
|
||||
renderCountrySelectionOverlay();
|
||||
});
|
||||
countrySelectionAllBtn?.addEventListener('click', async () => {
|
||||
await ensureAvailableRadioCountriesLoaded();
|
||||
draftSelectedRadioCountryCodes = new Set(availableRadioCountries.map((country) => country.code));
|
||||
renderCountrySelectionOverlay();
|
||||
});
|
||||
countrySelectionSaveBtn?.addEventListener('click', () => {
|
||||
void saveCountrySelection();
|
||||
});
|
||||
exportUserDataBtn?.addEventListener('click', handleExportUserData);
|
||||
importUserDataBtn?.addEventListener('click', () => importUserDataInput?.click());
|
||||
importUserDataInput?.addEventListener('change', () => {
|
||||
@@ -3358,6 +3733,11 @@ function setupEventListeners() {
|
||||
closeSortFilter();
|
||||
return;
|
||||
}
|
||||
if (countrySelectionOverlay && !countrySelectionOverlay.classList.contains('hidden') && e.code === 'Escape') {
|
||||
e.preventDefault();
|
||||
closeCountrySelectionOverlay();
|
||||
return;
|
||||
}
|
||||
if (e.code === 'Space') { e.preventDefault(); togglePlay(); }
|
||||
else if (e.code === 'ArrowRight') playNext();
|
||||
else if (e.code === 'ArrowLeft') playPrev();
|
||||
@@ -3412,8 +3792,10 @@ async function init() {
|
||||
restoreSavedVolume();
|
||||
restoreCastBothMode();
|
||||
restoreLastStationCountry();
|
||||
restoreSelectedRadioCountryCodes();
|
||||
await loadStations();
|
||||
setupEventListeners();
|
||||
void sendSelectedCountriesToServiceWorker();
|
||||
lockPortraitOrientation();
|
||||
ensureArtworkPointerFallback();
|
||||
scheduleArtworkShine();
|
||||
|
||||
Reference in New Issue
Block a user