'use strict'; /* ── helpers ──────────────────────────────────────────────────────────── */ function fmt_bytes(n) { if (n < 1024) return n + ' B'; if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB'; return (n / (1024 * 1024)).toFixed(1) + ' MB'; } async function api(method, path, body) { const opts = { method }; if (body) { opts.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; opts.body = body; } const res = await fetch(path, opts); return res.json(); } /* ── device info ──────────────────────────────────────────────────────── */ async function refreshInfo() { try { const d = await api('GET', '/api/info'); document.getElementById('info-ip').textContent = d.ip ?? '—'; document.getElementById('info-playing').textContent = d.playing ? 'Playing' : 'Stopped'; document.getElementById('info-cache').textContent = fmt_bytes(d.cacheBytes ?? 0); document.getElementById('info-heap').textContent = fmt_bytes(d.freeHeap ?? 0); document.getElementById('track-uri').textContent = d.currentUri || '—'; document.getElementById('btn-pause').disabled = !d.playing; document.getElementById('btn-stop').disabled = !d.playing; const vol = d.volume ?? 50; document.getElementById('volume-slider').value = vol; document.getElementById('volume-label').textContent = vol; } catch (e) { console.error('Failed to fetch /api/info', e); } } /* ── storage ──────────────────────────────────────────────────────────── */ async function refreshStorage() { try { const entries = await api('GET', '/api/storage'); const list = document.getElementById('cache-list'); const summary = document.getElementById('storage-summary'); list.innerHTML = ''; if (!Array.isArray(entries) || entries.length === 0) { summary.textContent = 'No cached files'; return; } let total = 0; entries.forEach(e => { total += e.size ?? 0; const li = document.createElement('li'); const key = document.createElement('span'); key.className = 'cache-key'; key.textContent = e.key; const sz = document.createElement('span'); sz.className = 'cache-size'; sz.textContent = fmt_bytes(e.size ?? 0); const del = document.createElement('button'); del.className = 'btn btn-danger'; del.style.cssText = 'padding:.2rem .5rem;font-size:.75rem'; del.textContent = '✕'; del.addEventListener('click', () => deleteEntry(e.key)); li.append(key, sz, del); list.appendChild(li); }); summary.textContent = `${entries.length} file${entries.length !== 1 ? 's' : ''} — ${fmt_bytes(total)}`; } catch (e) { console.error('Failed to fetch /api/storage', e); } } async function deleteEntry(key) { await api('DELETE', `/api/storage?key=${encodeURIComponent(key)}`); refreshStorage(); } async function clearCache() { if (!confirm('Delete all cached files?')) return; await api('DELETE', '/api/storage'); refreshStorage(); } /* ── playback ─────────────────────────────────────────────────────────── */ async function sendPlayback(action, value) { const body = value !== undefined ? `action=${action}&value=${encodeURIComponent(value)}` : `action=${action}`; await api('POST', '/api/playback', body); refreshInfo(); } /* ── bluetooth ────────────────────────────────────────────────────────── */ async function refreshBt() { try { const d = await api('GET', '/api/bluetooth'); const status = document.getElementById('bt-status'); const btn = document.getElementById('btn-bt-toggle'); if (d.enabled) { status.textContent = 'Enabled'; status.style.color = '#48bb78'; btn.textContent = 'Disable'; } else { status.textContent = 'Disabled'; status.style.color = '#a0aec0'; btn.textContent = 'Enable'; } } catch (e) { console.error('Failed to fetch /api/bluetooth', e); } } async function toggleBt() { const enabled = document.getElementById('bt-status').textContent === 'Enabled'; await api('POST', '/api/bluetooth', `enabled=${!enabled}`); refreshBt(); } /* ── init ─────────────────────────────────────────────────────────────── */ document.addEventListener('DOMContentLoaded', () => { refreshInfo(); refreshStorage(); refreshBt(); document.getElementById('btn-refresh-info').addEventListener('click', refreshInfo); document.getElementById('btn-clear-cache').addEventListener('click', clearCache); document.getElementById('btn-bt-toggle').addEventListener('click', toggleBt); document.getElementById('btn-pause').addEventListener('click', () => sendPlayback('pause')); document.getElementById('btn-stop').addEventListener('click', () => sendPlayback('stop')); const slider = document.getElementById('volume-slider'); const label = document.getElementById('volume-label'); slider.addEventListener('input', () => { label.textContent = slider.value; }); slider.addEventListener('change', () => sendPlayback('volume', slider.value)); /* Auto-refresh info every 5 s while page is visible */ setInterval(() => { if (!document.hidden) refreshInfo(); }, 5000); });