Playback
diff --git a/src/options/options.js b/src/options/options.js
index e2cda8b..a28d5cf 100644
--- a/src/options/options.js
+++ b/src/options/options.js
@@ -13,6 +13,10 @@ import {
THEME_KEY, THEME_DEFAULT, VALID_THEMES,
getTheme, setTheme, applyTheme,
} from '../lib/theme.js';
+import {
+ QUICK_STATIONS_KEY, DEFAULT_QUICK_IDS,
+ getQuickStations, setQuickStations, resetQuickStations,
+} from '../lib/quick-stations.js';
const STORAGE_KEYS = {
stations: 'tuner.stationsCache',
@@ -34,6 +38,10 @@ const els = {
volumeDisplay: document.getElementById('opt-volume-display'),
lastStation: document.getElementById('opt-last-station'),
toast: document.getElementById('opt-toast'),
+ // Quick Stations picker (v0.5.3)
+ qsCount: document.getElementById('opt-qs-count'),
+ qsList: document.getElementById('opt-qs-list'),
+ qsReset: document.getElementById('opt-qs-reset'),
};
init().catch(err => {
@@ -48,6 +56,9 @@ async function init() {
// cross-surface changes (popup/newtab pick a theme → Options reflects).
await initThemeUI();
+ // Quick Stations — render checkbox list of all SomaFM channels.
+ await initQuickStationsUI();
+
await refreshStats();
await refreshPlayback();
@@ -101,6 +112,15 @@ async function init() {
const radio = document.querySelector(`input[name="opt-theme"][value="${v}"]`);
if (radio) radio.checked = true;
}
+ if (changes[QUICK_STATIONS_KEY]) {
+ // Re-check the boxes to match the new picks (in case another surface
+ // wiped storage or reset to defaults).
+ const newPicks = new Set(await getQuickStations());
+ for (const cb of document.querySelectorAll('#opt-qs-list input[type=checkbox]')) {
+ cb.checked = newPicks.has(cb.value);
+ }
+ updateQuickStationsCount(newPicks.size);
+ }
if (Object.keys(changes).some(k => k.startsWith('tuner.'))) {
refreshStats();
refreshPlayback();
@@ -125,6 +145,99 @@ async function initThemeUI() {
}
}
+/** Render the Quick Stations picker — checkbox per SomaFM channel from
+ * the cached catalogue, with description below each name. Toggling a
+ * checkbox writes the current picks to chrome.storage.local under
+ * tuner.quickStations. NewTab's storage.onChanged listener re-renders
+ * the chip row instantly without a reload. */
+async function initQuickStationsUI() {
+ const cache = (await chrome.storage.local.get('tuner.stationsCache'))['tuner.stationsCache'] || [];
+ // Stations come in as { id: "somafm:groovesalad", name, description, genre, ... }
+ // Sort alphabetically by name for predictable picker order.
+ const channels = [...cache].sort((a, b) => (a.name || '').localeCompare(b.name || ''));
+ const picks = new Set(await getQuickStations());
+
+ els.qsList.innerHTML = '';
+ if (!channels.length) {
+ const li = document.createElement('li');
+ li.className = 'opt-qs-loading';
+ li.textContent = 'Channel list not cached yet — open the New Tab Page once to populate it.';
+ els.qsList.appendChild(li);
+ return;
+ }
+
+ for (const ch of channels) {
+ const shortId = (ch.id || '').split(':')[1];
+ if (!shortId) continue;
+
+ const li = document.createElement('li');
+ li.className = 'opt-qs-row';
+
+ const label = document.createElement('label');
+ label.className = 'opt-qs-label';
+
+ const cb = document.createElement('input');
+ cb.type = 'checkbox';
+ cb.value = shortId;
+ cb.checked = picks.has(shortId);
+ cb.addEventListener('change', onQuickStationToggle);
+
+ const main = document.createElement('div');
+ main.className = 'opt-qs-main';
+
+ const name = document.createElement('div');
+ name.className = 'opt-qs-name';
+ name.textContent = ch.name || shortId;
+
+ if (ch.genre) {
+ const g = document.createElement('div');
+ g.className = 'opt-qs-genre';
+ g.textContent = ch.genre;
+ main.appendChild(name);
+ main.appendChild(g);
+ } else {
+ main.appendChild(name);
+ }
+
+ if (ch.description) {
+ const desc = document.createElement('div');
+ desc.className = 'opt-qs-desc';
+ desc.textContent = ch.description;
+ main.appendChild(desc);
+ }
+
+ label.appendChild(cb);
+ label.appendChild(main);
+ li.appendChild(label);
+ els.qsList.appendChild(li);
+ }
+
+ updateQuickStationsCount(picks.size);
+
+ els.qsReset.addEventListener('click', async () => {
+ if (!confirm('Reset Quick Stations to the default 8?')) return;
+ await resetQuickStations();
+ // Re-check the boxes to match defaults
+ const defaultSet = new Set(DEFAULT_QUICK_IDS);
+ for (const cb of document.querySelectorAll('#opt-qs-list input[type=checkbox]')) {
+ cb.checked = defaultSet.has(cb.value);
+ }
+ updateQuickStationsCount(defaultSet.size);
+ toast('Quick Stations reset to defaults');
+ });
+}
+
+async function onQuickStationToggle() {
+ const all = [...document.querySelectorAll('#opt-qs-list input[type=checkbox]')];
+ const picked = all.filter(c => c.checked).map(c => c.value);
+ await setQuickStations(picked);
+ updateQuickStationsCount(picked.length);
+}
+
+function updateQuickStationsCount(n) {
+ els.qsCount.textContent = `${n} selected`;
+}
+
async function refreshStats() {
const [history, favs, all] = await Promise.all([
getHistory(),