Refine player layout and station data
This commit is contained in:
+269
-29
@@ -1,4 +1,6 @@
|
||||
import { loadRadioStations } from './radio/loadRadioStations.ts';
|
||||
import { radioCountries } from './radio/radioCountries.ts';
|
||||
import { loadManagedStations } from './radio/loadManagedStations.ts';
|
||||
|
||||
// Web version of RadioPlayer — HTML5 Audio + Google Cast Web Sender SDK.
|
||||
|
||||
@@ -26,9 +28,14 @@ let stationLibraryTab = 'all';
|
||||
let stationLibraryQuery = '';
|
||||
let stationLibraryCategory = 'all';
|
||||
let stationLibraryCountry = 'all';
|
||||
let stationLibraryPage = 0;
|
||||
let stationLibraryPageTotal = 1;
|
||||
let stationCatalogState = 'idle';
|
||||
let stationCatalogError = '';
|
||||
let playbackError = '';
|
||||
let stationCountryFilterOpen = false;
|
||||
|
||||
const STATION_LIBRARY_PAGE_SIZE = 12;
|
||||
|
||||
const RADIO_PLACEHOLDER_LOGO = '/images/radio-placeholder.svg';
|
||||
|
||||
@@ -68,9 +75,20 @@ const stationLibrarySummaryEl = document.getElementById('station-library-summary
|
||||
const stationSearchInput = document.getElementById('station-search-input');
|
||||
const stationLibraryCloseBtn = document.getElementById('station-library-close');
|
||||
const stationCategoryListEl = document.getElementById('station-category-list');
|
||||
const stationCountryFilterEl = document.getElementById('station-country-filter');
|
||||
const stationCountryFilterWrapEl = document.querySelector('[data-country-filter]');
|
||||
const stationCountryFilterBtn = document.getElementById('station-country-filter-btn');
|
||||
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 stationLibraryPaginationEl = document.getElementById('station-library-pagination');
|
||||
const stationLibraryPagePrevBtn = document.getElementById('station-library-page-prev');
|
||||
const stationLibraryPageNextBtn = document.getElementById('station-library-page-next');
|
||||
const stationLibraryPageInfo = document.getElementById('station-library-page-info');
|
||||
const stationTabBtns = document.querySelectorAll('[data-station-tab]');
|
||||
|
||||
const radioCountryCodeByName = new Map(radioCountries.map((country) => [country.name, country.code]));
|
||||
const radioCountryNameByCode = new Map(radioCountries.map((country) => [country.code, country.name]));
|
||||
|
||||
// Editor
|
||||
const editBtn = document.getElementById('edit-stations-btn');
|
||||
const stationsListBtn = document.getElementById('stations-list-btn');
|
||||
@@ -390,6 +408,19 @@ function getLastStationId() {
|
||||
try { return localStorage.getItem('lastStationId'); } catch (e) { return null; }
|
||||
}
|
||||
|
||||
function saveLastStationCountry(country) {
|
||||
try { if (country) localStorage.setItem('lastStationCountry', country); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function getLastStationCountry() {
|
||||
try { return localStorage.getItem('lastStationCountry'); } catch (e) { return null; }
|
||||
}
|
||||
|
||||
function restoreLastStationCountry() {
|
||||
const savedCountry = getLastStationCountry();
|
||||
stationLibraryCountry = savedCountry || 'all';
|
||||
}
|
||||
|
||||
// ── castBothMode persistence & UI ────────────────────────────────────────────
|
||||
|
||||
function saveCastBothMode(val) {
|
||||
@@ -553,9 +584,7 @@ function getStationLogoCandidates(station) {
|
||||
function getStationSubtitle(station) {
|
||||
return station?.slogan
|
||||
|| station?.raw?.slogan
|
||||
|| getStationHomepage(station)
|
||||
|| station?.raw?.defaultText
|
||||
|| station?.id
|
||||
|| '';
|
||||
}
|
||||
|
||||
@@ -593,30 +622,164 @@ function getCountryNames() {
|
||||
return Array.from(new Set(stations.map(getStationCountry).filter(Boolean))).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
function getCountryDisplayName(countryValue) {
|
||||
const value = String(countryValue || '').trim();
|
||||
if (!value) return '';
|
||||
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());
|
||||
if (countryName) return countryName;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getCountryFilterDisplayName(countryValue) {
|
||||
const value = String(countryValue || '').trim();
|
||||
if (!value) return '';
|
||||
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());
|
||||
if (countryName) return countryName;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
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) || '';
|
||||
}
|
||||
|
||||
function resetStationLibraryPage() {
|
||||
stationLibraryPage = 0;
|
||||
}
|
||||
|
||||
function goToPreviousStationLibraryPage() {
|
||||
if (stationLibraryPage <= 0) return;
|
||||
stationLibraryPage -= 1;
|
||||
renderStationLibrary();
|
||||
}
|
||||
|
||||
function goToNextStationLibraryPage() {
|
||||
if (stationLibraryPage >= stationLibraryPageTotal - 1) return;
|
||||
stationLibraryPage += 1;
|
||||
renderStationLibrary();
|
||||
}
|
||||
|
||||
function countryCodeToFlagEmoji(countryCode) {
|
||||
const code = String(countryCode || '').trim().toUpperCase();
|
||||
if (!/^[A-Z]{2}$/.test(code)) return '🌐';
|
||||
return String.fromCodePoint(...code.split('').map((char) => 127397 + char.charCodeAt(0)));
|
||||
}
|
||||
|
||||
function countryCodeToFlagUrl(countryCode) {
|
||||
const code = String(countryCode || '').trim().toLowerCase();
|
||||
if (!/^[a-z]{2}$/.test(code)) return '';
|
||||
return `https://flagcdn.com/w20/${code}.png`;
|
||||
}
|
||||
|
||||
function getCountryFlag(countryName) {
|
||||
if (!countryName || countryName === 'all') return '🌐';
|
||||
const countryCode = getCountryCodeFromValue(countryName);
|
||||
return countryCode ? countryCodeToFlagEmoji(countryCode) : '🏳️';
|
||||
}
|
||||
|
||||
function getCountryFlagUrl(countryName) {
|
||||
if (!countryName || countryName === 'all') return 'https://flagcdn.com/w20/eu.png';
|
||||
const countryCode = getCountryCodeFromValue(countryName);
|
||||
return countryCode ? countryCodeToFlagUrl(countryCode) : '';
|
||||
}
|
||||
|
||||
function toggleStationCountryFilter(forceOpen) {
|
||||
stationCountryFilterOpen = typeof forceOpen === 'boolean' ? forceOpen : !stationCountryFilterOpen;
|
||||
renderStationLibrary();
|
||||
}
|
||||
|
||||
function closeStationCountryFilter() {
|
||||
if (!stationCountryFilterOpen) return;
|
||||
stationCountryFilterOpen = false;
|
||||
renderStationLibrary();
|
||||
}
|
||||
|
||||
function renderCountryFilterOptions() {
|
||||
if (!stationCountryFilterEl) return;
|
||||
if (!stationCountryFilterMenu || !stationCountryFilterBtn || !stationCountryFilterText) return;
|
||||
|
||||
const countries = getCountryNames();
|
||||
if (stationLibraryCountry !== 'all' && !countries.includes(stationLibraryCountry)) {
|
||||
if (stationCatalogState === 'ready' && stationLibraryCountry !== 'all' && !countries.includes(stationLibraryCountry)) {
|
||||
stationLibraryCountry = 'all';
|
||||
saveLastStationCountry('all');
|
||||
resetStationLibraryPage();
|
||||
}
|
||||
|
||||
stationCountryFilterEl.innerHTML = '';
|
||||
stationCountryFilterBtn.setAttribute('aria-expanded', stationCountryFilterOpen ? 'true' : 'false');
|
||||
stationCountryFilterText.textContent = getCountryFilterDisplayName(stationLibraryCountry);
|
||||
if (stationCountryFilterFlag) {
|
||||
stationCountryFilterFlag.src = getCountryFlagUrl(stationLibraryCountry);
|
||||
}
|
||||
stationCountryFilterWrapEl?.classList.toggle('open', stationCountryFilterOpen);
|
||||
stationCountryFilterMenu.innerHTML = '';
|
||||
stationCountryFilterMenu.classList.toggle('open', stationCountryFilterOpen);
|
||||
|
||||
const allOption = document.createElement('option');
|
||||
allOption.value = 'all';
|
||||
allOption.textContent = 'All countries';
|
||||
stationCountryFilterEl.appendChild(allOption);
|
||||
const addOption = (label, value) => {
|
||||
const option = document.createElement('button');
|
||||
option.type = 'button';
|
||||
option.className = 'library-select-option' + (stationLibraryCountry === value ? ' active' : '');
|
||||
option.setAttribute('role', 'option');
|
||||
option.setAttribute('aria-selected', stationLibraryCountry === value ? 'true' : 'false');
|
||||
|
||||
countries.forEach((country) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = country;
|
||||
option.textContent = country;
|
||||
stationCountryFilterEl.appendChild(option);
|
||||
});
|
||||
const flag = document.createElement('span');
|
||||
const flagUrl = getCountryFlagUrl(value);
|
||||
flag.className = 'library-select-option-flag';
|
||||
flag.setAttribute('aria-hidden', 'true');
|
||||
if (flagUrl) {
|
||||
const flagImg = document.createElement('img');
|
||||
flagImg.src = flagUrl;
|
||||
flagImg.alt = '';
|
||||
flagImg.setAttribute('aria-hidden', 'true');
|
||||
flagImg.className = 'library-select-option-flag-img';
|
||||
flagImg.loading = 'lazy';
|
||||
flagImg.referrerPolicy = 'no-referrer';
|
||||
flagImg.addEventListener('error', () => {
|
||||
flagImg.remove();
|
||||
flag.textContent = value === 'all' ? '🌐' : '🏳️';
|
||||
}, { once: true });
|
||||
flag.appendChild(flagImg);
|
||||
} else {
|
||||
flag.textContent = value === 'all' ? '🌐' : '🏳️';
|
||||
}
|
||||
|
||||
stationCountryFilterEl.value = stationLibraryCountry;
|
||||
stationCountryFilterEl.disabled = stationCatalogState === 'loading';
|
||||
const text = document.createElement('span');
|
||||
text.className = 'library-select-option-text';
|
||||
text.textContent = label;
|
||||
|
||||
option.appendChild(flag);
|
||||
option.appendChild(text);
|
||||
|
||||
option.addEventListener('click', () => {
|
||||
stationLibraryCountry = value;
|
||||
stationLibraryCategory = 'all';
|
||||
saveLastStationCountry(value);
|
||||
resetStationLibraryPage();
|
||||
stationCountryFilterOpen = false;
|
||||
renderStationLibrary();
|
||||
});
|
||||
stationCountryFilterMenu.appendChild(option);
|
||||
};
|
||||
|
||||
addOption('All countries', 'all');
|
||||
const orderedCountries = countries
|
||||
.slice()
|
||||
.sort((left, right) => {
|
||||
const leftPriority = left.toUpperCase() === 'SI' ? -1 : 0;
|
||||
const rightPriority = right.toUpperCase() === 'SI' ? -1 : 0;
|
||||
if (leftPriority !== rightPriority) return leftPriority - rightPriority;
|
||||
return getCountryFilterDisplayName(left).localeCompare(getCountryFilterDisplayName(right));
|
||||
});
|
||||
|
||||
orderedCountries.forEach((country) => addOption(getCountryFilterDisplayName(country), country));
|
||||
}
|
||||
|
||||
function getFilteredStationEntries() {
|
||||
@@ -668,6 +831,7 @@ function getQuickPickEntries() {
|
||||
function setStationLibraryTab(tab) {
|
||||
stationLibraryTab = tab || 'all';
|
||||
if (stationLibraryTab !== 'categories') stationLibraryCategory = 'all';
|
||||
resetStationLibraryPage();
|
||||
renderStationLibrary();
|
||||
}
|
||||
|
||||
@@ -716,6 +880,7 @@ function renderCategoryChips() {
|
||||
btn.textContent = category === 'all' ? 'All categories' : category;
|
||||
btn.addEventListener('click', () => {
|
||||
stationLibraryCategory = category;
|
||||
resetStationLibraryPage();
|
||||
renderStationLibrary();
|
||||
});
|
||||
stationCategoryListEl.appendChild(btn);
|
||||
@@ -726,6 +891,7 @@ function renderStationLibrary() {
|
||||
try {
|
||||
if (!stationLibraryListEl) return;
|
||||
stationLibraryListEl.innerHTML = '';
|
||||
stationLibraryListEl.scrollTop = 0;
|
||||
stationLibraryEl?.classList.toggle('show-categories', stationLibraryTab === 'categories');
|
||||
|
||||
stationTabBtns.forEach((btn) => {
|
||||
@@ -739,6 +905,10 @@ function renderStationLibrary() {
|
||||
|
||||
if (stationCatalogState === 'loading') {
|
||||
if (stationLibrarySummaryEl) stationLibrarySummaryEl.textContent = 'Loading stations...';
|
||||
if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
|
||||
if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
|
||||
if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
|
||||
stationLibraryPaginationEl?.classList.add('hidden');
|
||||
const empty = document.createElement('li');
|
||||
empty.className = 'library-empty';
|
||||
empty.textContent = 'Loading the local radio catalog...';
|
||||
@@ -748,6 +918,10 @@ function renderStationLibrary() {
|
||||
|
||||
if (stationCatalogState === 'error') {
|
||||
if (stationLibrarySummaryEl) stationLibrarySummaryEl.textContent = 'Unable to load stations';
|
||||
if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
|
||||
if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
|
||||
if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
|
||||
stationLibraryPaginationEl?.classList.add('hidden');
|
||||
const empty = document.createElement('li');
|
||||
empty.className = 'library-empty';
|
||||
empty.textContent = stationCatalogError || 'The local station catalog could not be loaded.';
|
||||
@@ -757,6 +931,10 @@ function renderStationLibrary() {
|
||||
|
||||
if (!stations.length) {
|
||||
if (stationLibrarySummaryEl) stationLibrarySummaryEl.textContent = 'No stations available';
|
||||
if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
|
||||
if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
|
||||
if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
|
||||
stationLibraryPaginationEl?.classList.add('hidden');
|
||||
const empty = document.createElement('li');
|
||||
empty.className = 'library-empty';
|
||||
empty.textContent = 'The local station catalog is empty.';
|
||||
@@ -770,11 +948,23 @@ function renderStationLibrary() {
|
||||
|
||||
const tabLabel = stationLibraryTab === 'favourites' ? 'favourite' : stationLibraryTab === 'recent' ? 'recent' : 'available';
|
||||
if (stationLibrarySummaryEl) {
|
||||
const countryLabel = stationLibraryCountry === 'all' ? '' : ` in ${stationLibraryCountry}`;
|
||||
const countryLabel = stationLibraryCountry === 'all' ? '' : ` in ${getCountryDisplayName(stationLibraryCountry)}`;
|
||||
stationLibrarySummaryEl.textContent = `${entries.length} ${tabLabel} station${entries.length === 1 ? '' : 's'}${countryLabel}`;
|
||||
}
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(entries.length / STATION_LIBRARY_PAGE_SIZE));
|
||||
stationLibraryPageTotal = totalPages;
|
||||
stationLibraryPaginationEl?.classList.toggle('hidden', totalPages <= 1);
|
||||
if (stationLibraryPage >= totalPages) {
|
||||
stationLibraryPage = totalPages - 1;
|
||||
}
|
||||
const pageStart = stationLibraryPage * STATION_LIBRARY_PAGE_SIZE;
|
||||
const pageEntries = entries.slice(pageStart, pageStart + STATION_LIBRARY_PAGE_SIZE);
|
||||
|
||||
if (entries.length === 0) {
|
||||
if (stationLibraryPageInfo) stationLibraryPageInfo.textContent = 'Page 1 of 1';
|
||||
if (stationLibraryPagePrevBtn) stationLibraryPagePrevBtn.disabled = true;
|
||||
if (stationLibraryPageNextBtn) stationLibraryPageNextBtn.disabled = true;
|
||||
const empty = document.createElement('li');
|
||||
empty.className = 'library-empty';
|
||||
empty.textContent = stationLibraryTab === 'favourites'
|
||||
@@ -784,7 +974,21 @@ function renderStationLibrary() {
|
||||
return;
|
||||
}
|
||||
|
||||
entries.forEach(({ station, index, count }) => {
|
||||
if (stationLibraryPageInfo) {
|
||||
stationLibraryPageInfo.textContent = `Page ${stationLibraryPage + 1} of ${totalPages}`;
|
||||
}
|
||||
|
||||
if (stationLibraryPagePrevBtn) {
|
||||
stationLibraryPagePrevBtn.disabled = stationLibraryPage <= 0;
|
||||
stationLibraryPagePrevBtn.setAttribute('aria-disabled', stationLibraryPage <= 0 ? 'true' : 'false');
|
||||
}
|
||||
|
||||
if (stationLibraryPageNextBtn) {
|
||||
stationLibraryPageNextBtn.disabled = stationLibraryPage >= totalPages - 1;
|
||||
stationLibraryPageNextBtn.setAttribute('aria-disabled', stationLibraryPage >= totalPages - 1 ? 'true' : 'false');
|
||||
}
|
||||
|
||||
pageEntries.forEach(({ station, index, count }) => {
|
||||
const title = getStationTitle(station);
|
||||
const li = document.createElement('li');
|
||||
|
||||
@@ -806,7 +1010,7 @@ function renderStationLibrary() {
|
||||
meta.className = 'library-station-meta';
|
||||
const country = document.createElement('span');
|
||||
country.className = 'library-station-country';
|
||||
country.textContent = getStationCountry(station) || getStationCategory(station);
|
||||
country.textContent = getCountryDisplayName(getStationCountry(station)) || getStationCategory(station);
|
||||
const tech = document.createElement('span');
|
||||
tech.className = 'library-station-tech';
|
||||
tech.textContent = getStationTechnicalLabel(station) || getStationCategory(station);
|
||||
@@ -952,11 +1156,17 @@ async function loadStations() {
|
||||
try {
|
||||
stationCatalogState = 'loading';
|
||||
stationCatalogError = '';
|
||||
resetStationLibraryPage();
|
||||
renderStationLibrary();
|
||||
stopCurrentSongPollers();
|
||||
const raw = await loadRadioStations();
|
||||
const managedRaw = await loadManagedStations().catch(() => []);
|
||||
|
||||
stations = raw
|
||||
const normalizedRaw = raw
|
||||
.map((s) => normalizeStationRecord(s))
|
||||
.filter((s) => s.enabled !== false && s.url && s.url.length > 0);
|
||||
|
||||
const normalizedManaged = managedRaw
|
||||
.map((s) => normalizeStationRecord(s))
|
||||
.filter((s) => s.enabled !== false && s.url && s.url.length > 0);
|
||||
|
||||
@@ -964,7 +1174,23 @@ async function loadStations() {
|
||||
.map((s) => normalizeStationRecord(s, true))
|
||||
.filter((s) => s.url && s.url.length > 0);
|
||||
|
||||
stations = stations.concat(userNormalized);
|
||||
const mergedStations = [];
|
||||
const seenStationIds = new Set();
|
||||
const seenStreamUrls = new Set();
|
||||
|
||||
const pushStation = (station) => {
|
||||
if (!station?.id || !station?.url) return;
|
||||
if (seenStationIds.has(station.id) || seenStreamUrls.has(station.url)) return;
|
||||
seenStationIds.add(station.id);
|
||||
seenStreamUrls.add(station.url);
|
||||
mergedStations.push(station);
|
||||
};
|
||||
|
||||
normalizedManaged.forEach(pushStation);
|
||||
normalizedRaw.forEach(pushStation);
|
||||
userNormalized.forEach(pushStation);
|
||||
|
||||
stations = mergedStations;
|
||||
stationCatalogState = stations.length > 0 ? 'ready' : 'empty';
|
||||
console.debug('loadStations: loaded', stations.length, 'stations');
|
||||
|
||||
@@ -1144,11 +1370,11 @@ function updateCoverflowTransforms() {
|
||||
const stageWidth = coverflowStageEl.clientWidth || 320;
|
||||
const isMobile = window.matchMedia('(max-width: 760px)').matches;
|
||||
const isNarrow = window.matchMedia('(max-width: 380px)').matches;
|
||||
const maxVisible = 1;
|
||||
const spacing = isMobile ? Math.min(78, Math.max(62, stageWidth / 3.25)) : Math.min(116, Math.max(94, stageWidth / 3.1));
|
||||
const depth = isMobile ? 26 : 36;
|
||||
const rotation = isMobile ? 0 : 8;
|
||||
const scaleStep = isMobile ? 0.08 : 0.1;
|
||||
const maxVisible = isMobile ? 1 : isNarrow ? 1 : Math.max(1, Math.min(4, Math.floor((stageWidth - 120) / (spacing * 0.95))));
|
||||
|
||||
items.forEach((el, railIndex) => {
|
||||
const idx = Number(el.dataset.idx);
|
||||
@@ -1877,6 +2103,8 @@ function setupEventListeners() {
|
||||
nextBtn?.addEventListener('click', playNext);
|
||||
volumeSlider?.addEventListener('input', handleVolumeInput);
|
||||
muteBtn?.addEventListener('click', toggleMute);
|
||||
stationLibraryPagePrevBtn?.addEventListener('click', goToPreviousStationLibraryPage);
|
||||
stationLibraryPageNextBtn?.addEventListener('click', goToNextStationLibraryPage);
|
||||
|
||||
closeOverlayBtn?.addEventListener('click', closeCastOverlay);
|
||||
castOverlay?.addEventListener('click', (e) => { if (e.target === castOverlay) closeCastOverlay(); });
|
||||
@@ -1887,13 +2115,14 @@ function setupEventListeners() {
|
||||
castBtn?.addEventListener('click', requestCastSession);
|
||||
editorCloseBtn?.addEventListener('click', closeEditorOverlay);
|
||||
stationLibraryCloseBtn?.addEventListener('click', closeStationLibrary);
|
||||
stationCountryFilterBtn?.addEventListener('click', (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
toggleStationCountryFilter();
|
||||
});
|
||||
stationSearchInput?.addEventListener('input', () => {
|
||||
stationLibraryQuery = stationSearchInput.value || '';
|
||||
renderStationLibrary();
|
||||
});
|
||||
stationCountryFilterEl?.addEventListener('change', () => {
|
||||
stationLibraryCountry = stationCountryFilterEl.value || 'all';
|
||||
stationLibraryCategory = 'all';
|
||||
resetStationLibraryPage();
|
||||
renderStationLibrary();
|
||||
});
|
||||
stationTabBtns.forEach((btn) => {
|
||||
@@ -1903,10 +2132,20 @@ function setupEventListeners() {
|
||||
artworkPlaceholder?.addEventListener('click', openStationLibrary);
|
||||
castOutputBtn?.addEventListener('click', toggleCastBothMode);
|
||||
window.addEventListener('resize', updateCoverflowTransforms);
|
||||
document.addEventListener('click', (ev) => {
|
||||
if (!stationCountryFilterOpen) return;
|
||||
if (stationCountryFilterWrapEl?.contains(ev.target)) return;
|
||||
closeStationCountryFilter();
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
||||
if (stationCountryFilterOpen && e.code === 'Escape') {
|
||||
e.preventDefault();
|
||||
closeStationCountryFilter();
|
||||
return;
|
||||
}
|
||||
if (e.code === 'Space') { e.preventDefault(); togglePlay(); }
|
||||
else if (e.code === 'ArrowRight') playNext();
|
||||
else if (e.code === 'ArrowLeft') playPrev();
|
||||
@@ -1959,6 +2198,7 @@ async function init() {
|
||||
|
||||
restoreSavedVolume();
|
||||
restoreCastBothMode();
|
||||
restoreLastStationCountry();
|
||||
await loadStations();
|
||||
setupEventListeners();
|
||||
ensureArtworkPointerFallback();
|
||||
|
||||
Reference in New Issue
Block a user