import { h } from 'preact'; import { useState, useEffect, useRef } from 'preact/hooks'; import htm from 'htm'; import { api } from '../api.js'; const html = htm.bind(h); function fmt(secs) { if (!secs || secs <= 0) return '0:00'; const m = Math.floor(secs / 60); const s = secs % 60; return `${m}:${s.toString().padStart(2, '0')}`; } // PresetPicker — ★ star button in the top-right corner of the now-playing card. // Translucent when nothing is mapped; golden when the current content already // exists in one of the device's presets. Click to open a slot picker (1–6). function PresetPicker({ deviceId, nowPlaying, presets }) { const [open, setOpen] = useState(false); const [savingSlot, setSavingSlot] = useState(null); const [savedSlot, setSavedSlot] = useState(null); const wrapRef = useRef(null); // Detect whether the current content is already saved as any preset. const currentSource = nowPlaying?.ContentItem?.Source; const currentLocation = nowPlaying?.ContentItem?.Location; const presetList = presets?.Preset ?? []; const isMapped = !!(currentLocation && presetList.some(p => p.ContentItem?.Location === currentLocation && p.ContentItem?.Source === currentSource )); // Close popover on outside click. useEffect(() => { if (!open) return; function onDocClick(e) { if (!wrapRef.current?.contains(e.target)) setOpen(false); } document.addEventListener('click', onDocClick, true); return () => document.removeEventListener('click', onDocClick, true); }, [open]); function save(slotId) { setSavingSlot(slotId); api.storePreset(deviceId, slotId) .then(res => { setSavingSlot(null); setSavedSlot(slotId); setTimeout(() => { setSavedSlot(null); setOpen(false); }, 900); }) .catch(() => { setSavingSlot(null); setOpen(false); }); } return html`
${open && html`
Save as preset
${[1, 2, 3, 4, 5, 6].map(n => html` `)}
`}
`; } export function NowPlaying({ nowPlaying, deviceId, presets }) { const [position, setPosition] = useState(0); useEffect(() => { const pos = nowPlaying?.Time?.Position ?? 0; setPosition(pos); if (nowPlaying?.PlayStatus !== 'PLAY_STATE') return; const id = setInterval(() => setPosition(p => p + 1), 1000); return () => clearInterval(id); }, [nowPlaying?.Time?.Position, nowPlaying?.PlayStatus, nowPlaying?.TrackID, nowPlaying?.ContentItem?.Location]); if (!nowPlaying || nowPlaying.Source === 'STANDBY') { return html`
Standby
`; } const title = nowPlaying.Track || nowPlaying.StationName || nowPlaying.Source; const artURL = nowPlaying.Art?.URL; const isBuffering = nowPlaying.PlayStatus === 'BUFFERING_STATE'; const total = nowPlaying.Time?.Total ?? 0; const pct = total > 0 ? Math.min(100, (position / total) * 100) : 0; return html`
${artURL && html``}
${title}
${nowPlaying.Artist && html`
${nowPlaying.Artist}
`} ${nowPlaying.Album && html`
${nowPlaying.Album}
`}
${nowPlaying.Source} ${isBuffering && html`Buffering…`}
${total > 0 && html`
${fmt(Math.min(position, total))} / ${fmt(total)}
`}
${deviceId && html` <${PresetPicker} deviceId=${deviceId} nowPlaying=${nowPlaying} presets=${presets} /> `}
`; }