feat(v0.5.3-prep): user-customizable Quick Stations + descriptions
David flagged 2026-06-10: '14 stations wraps 3 lines, original idea was 2 lines tidy, but now thought of user selecting themselves' + 'maybe have a description of each station' + 'be great to keep the code clean.' Architecture: pure data, not code. Adds tuner.quickStations as a new chrome.storage.local key. Default 8 (tidy 2-row layout per the v0.4.0 picks). User can pick any subset of the 46 SomaFM channels via a new Options page card. NEW FILE src/lib/quick-stations.js — DEFAULT_QUICK_IDS + storage helpers (getQuickStations, setQuickStations, resetQuickStations) with the same defensive 'fall back if chrome.storage missing' pattern as history.js + theme.js. CHANGED src/newtab/newtab.js — removed hardcoded QUICK_IDS array, replaced with module-level 'let quickIds = []' populated during init from getQuickStations() + re-loaded when chrome.storage.onChanged fires on tuner.quickStations. renderQuick() now uses module-level quickIds and shows 'No Quick Stations picked — set some in Settings' empty state if the array is empty. src/options/options.html — new 'Quick Stations' card between Appearance and Playback. Contains: helper text + selected-count badge + scrollable <ul> + Reset-to-defaults button. src/options/options.css — ~90 lines for the new picker: scrollable list (max-height 360px), checkbox-name-description row layout, accent-coloured count badge, subtle hover background, brand-styled scrollbar. src/options/options.js — initQuickStationsUI() reads cached channel list from tuner.stationsCache, sorts alphabetically, builds one row per channel with checkbox + name + genre pill + description. Toggle handler writes the current pick set to chrome.storage.local. Reset button confirms then calls resetQuickStations() + re-checks boxes to match defaults. Cross-surface sync via storage.onChanged re-syncs check state if changes happen elsewhere. 5 files (1 new), ~250 lines added. No new permissions, no new dependencies.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// RangerHQ Tuner — Quick Stations preference helpers (v0.5.3).
|
||||
//
|
||||
// The user picks which SomaFM channels appear in the Quick Stations chip
|
||||
// row on the New Tab Page. Stored in chrome.storage.local under
|
||||
// `tuner.quickStations` as an array of SomaFM short ids
|
||||
// (e.g. ['groovesalad', 'defcon', ...]). If the key is missing, the
|
||||
// DEFAULT_QUICK_IDS below are used — a tidy 8 that wraps to 2 rows on
|
||||
// most viewports.
|
||||
//
|
||||
// Cross-surface sync: NewTab's `chrome.storage.onChanged` listener picks
|
||||
// up changes so toggling a checkbox in Options re-renders the chip row
|
||||
// instantly without a reload.
|
||||
|
||||
export const QUICK_STATIONS_KEY = 'tuner.quickStations';
|
||||
|
||||
// 8 curated defaults — the same set Tuner v0.4.0 shipped with. Chosen
|
||||
// for genre breadth (chill / ambient / indie / lounge / space / vocal /
|
||||
// ambient / electronic) and to fit a tidy 2-row chip layout.
|
||||
export const DEFAULT_QUICK_IDS = Object.freeze([
|
||||
'groovesalad',
|
||||
'dronezone',
|
||||
'indiepop',
|
||||
'secretagent',
|
||||
'spacestation',
|
||||
'lush',
|
||||
'deepspaceone',
|
||||
'fluid',
|
||||
]);
|
||||
|
||||
function storage() {
|
||||
if (typeof chrome === 'undefined') return null;
|
||||
if (!chrome.storage || !chrome.storage.local) return null;
|
||||
return chrome.storage.local;
|
||||
}
|
||||
|
||||
/** Read the user's chosen quick-station list, falling back to defaults. */
|
||||
export async function getQuickStations() {
|
||||
const s = storage();
|
||||
if (!s) return [...DEFAULT_QUICK_IDS];
|
||||
const o = await s.get(QUICK_STATIONS_KEY);
|
||||
const v = o[QUICK_STATIONS_KEY];
|
||||
return Array.isArray(v) ? v.filter(x => typeof x === 'string') : [...DEFAULT_QUICK_IDS];
|
||||
}
|
||||
|
||||
/** Persist the user's chosen quick-station list. Empty array = no quick chips. */
|
||||
export async function setQuickStations(ids) {
|
||||
const s = storage();
|
||||
if (!s) return;
|
||||
const arr = Array.isArray(ids) ? ids.filter(x => typeof x === 'string') : [];
|
||||
await s.set({ [QUICK_STATIONS_KEY]: arr });
|
||||
}
|
||||
|
||||
/** Wipe the preference so the default-8 is used again. */
|
||||
export async function resetQuickStations() {
|
||||
const s = storage();
|
||||
if (!s) return;
|
||||
await s.remove(QUICK_STATIONS_KEY);
|
||||
}
|
||||
Reference in New Issue
Block a user