Files
Bose-SoundTouch/pkg/service/soundtouchweb/static/js/api.js
T
Tobias GesellchenandClaude Sonnet 4.6 11a6515f4d feat(tunein): add section-grouped results and load-more pagination
TuneIn's profiles API caps initial results at ~10 per container (Stations,
Shows, etc.) and exposes a Pivots.More.Url cursor for the remainder. This
change wires that cursor through the stack so users can load additional
results without leaving the search view.

- tuneInSearchSection now extracts Pivots.More.Url as bmx_next when
  itemToken is present; absent for containers already at their limit
- TuneInSearchNext fetches the cursor URL, which returns a flat Items[]
  (not nested containers), and maps Station/Program/Topic items using
  the existing play/profile builders
- New GET /v1/search/next and /api/tunein/search/next endpoints with
  matching handlers in both service paths
- TuneInBrowser: flat items state replaced with per-section sections
  state; each section shows a header label and a Load more button when
  a cursor is available; browse/navigate mode is unaffected

Relates to #336.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:25:01 +02:00

48 lines
2.3 KiB
JavaScript

const JSON_HEADERS = { 'Content-Type': 'application/json' };
async function req(url, opts = {}) {
const r = await fetch(url, opts);
return r.json();
}
export const api = {
devices: () => req('/api/devices'),
device: (id) => req(`/api/device/${id}`),
discover: () => req('/api/discover', { method: 'POST' }),
key: (id, key) => req(`/api/device-key/${id}/${key}`, { method: 'POST' }),
volume: (id, level) => req(`/api/device-volume/${id}/${level}`, { method: 'POST' }),
bass: (id, level) => req(`/api/control/${id}/bass`, {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify({ level }),
}),
power: (id) => req(`/api/device-power/${id}`, { method: 'POST' }),
recents: (id) => req(`/api/device-recents/${id}`),
zone: (id) => req(`/api/zone/${id}`),
zoneAdd: (masterId, slaveId) => req(`/api/zone/${masterId}/add/${slaveId}`, { method: 'POST' }),
zoneRemove: (masterId, slaveId) => req(`/api/zone/${masterId}/remove/${slaveId}`, { method: 'POST' }),
zoneDissolve: (id) => req(`/api/zone/${id}/dissolve`, { method: 'POST' }),
zoneLeave: (id) => req(`/api/zone/${id}/leave`, { method: 'POST' }),
play: (id, item) => req(`/api/device-play/${id}`, {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify(item),
}),
tuneInBrowse: (path) => req(path ? `/api/tunein/navigate/${path}` : '/api/tunein/navigate'),
tuneInSearch: (q) => req(`/api/tunein/search?q=${encodeURIComponent(q)}`),
tuneInSearchNext: (cursor) => req(`/api/tunein/search/next?cursor=${encodeURIComponent(cursor)}`),
control: (id, action, presetId) => req(`/api/control/${id}/${action}?id=${presetId}`),
selectSource: (id, source, account) => req(`/api/control/${id}/source?name=${encodeURIComponent(source)}&account=${encodeURIComponent(account || '')}`),
tuneInPlay: (deviceId, item) => req(`/api/tunein/play/${deviceId}`, {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify(item),
}),
radioBrowserSearch: (q) => req(`/api/radiobrowser/search?q=${encodeURIComponent(q)}`),
radioBrowserPlay: (deviceId, item) => req(`/api/radiobrowser/play/${deviceId}`, {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify(item),
}),
};