let apiBaseUrl = 'http://localhost:3000/api/v1'; const processingList = []; async function checkConnectivity() { try { const response = await fetch(`${apiBaseUrl}/ping`); if (!response.ok) { document.getElementById('connectivity-status').classList.remove('hidden'); return; } document.getElementById('connectivity-status').classList.add('hidden'); } catch { document.getElementById('connectivity-status').classList.remove('hidden'); } } function setApiUrl() { const url = document.getElementById('api-url'); if (url === null || url.value === '') { console.log('Using default API URL'); apiBaseUrl = `http://${window.location.hostname}:3000/api/v1`; } else { apiBaseUrl = url.value; } } async function sendAdd(url) { return fetch(`${apiBaseUrl}/queue`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({song: url}) }); } async function addToQueue() { const song = document.getElementById('song-url'); if (!song || !(song instanceof HTMLInputElement)) { alert('Failed to find song URL input'); return; } const url = song.value; if (!url) { alert('Please enter a song URL'); return; } const response = await sendAdd(url); if (response.ok) { loadQueue(); song.value = ''; } else { const errorMessage = await response.text(); alert(`Failed to add song to queue: ${errorMessage}`); } } async function deleteFromQueue(item) { const song = encodeURIComponent(item.song.url); const response = await fetch(`${apiBaseUrl}/queue/${song}`, { method: 'DELETE' }); if (response.ok) { loadQueue(); } else { const errorMessage = await response.text(); alert(`Failed to delete song from queue: ${errorMessage}`); } } async function retry(item) { const song = encodeURIComponent(item.song.url); const response = await fetch(`${apiBaseUrl}/queue/retry/${song}`, { method: 'POST' }); if (response.ok) { loadQueue(); loadHistory(); } else { const errorMessage = await response.text(); alert(`Failed to retry song: ${errorMessage}`); } } async function clearQueue() { // Show a confirmation dialog if (!confirm('Are you sure you want to clear the queue?')) { return; } const response = await fetch(`${apiBaseUrl}/queue`, { method: 'DELETE' }); if (response.ok) { loadQueue(); } else { const errorMessage = await response.text(); alert(`Failed to clear queue: ${errorMessage}`); } } async function loadQueue() { const response = await fetch(`${apiBaseUrl}/queue`); const queue = await response.json(); const queueList = document.getElementById('queue'); if (!queueList) { alert('Failed to find queue list'); return; } queueList.innerHTML = ''; queue.forEach(item => { const element = constructQueueItem(item); const deleteButton = document.createElement('button'); deleteButton.classList.add('delete'); deleteButton.textContent = 'x'; deleteButton.addEventListener('click', () => deleteFromQueue(item)); element.prepend(deleteButton); queueList.appendChild(element); }); } async function loadProcessing() { const response = await fetch(`${apiBaseUrl}/queue/processing`); const status = await response.json(); const processingDiv = document.getElementById('currently-processing'); if (!processingDiv) { alert('Failed to find processing status element'); return; } if (status.length === 0) { processingDiv.textContent = 'Nothing processing'; } status.forEach(item => { // Add new items if they are not already in the list if (!processingList.some(element => element.id === item.item.id)) { const listItem = constructQueueItem(item.item); const details = document.createElement('details'); details.id = item.item.id; const summary = document.createElement('summary'); summary.innerHTML = listItem.innerHTML details.appendChild(summary); const trackCount = document.createElement('p'); trackCount.innerText = item.item.song.trackCount + ' tracks'; details.appendChild(trackCount); const progress = document.createElement('p'); progress.classList.add('progress'); progress.textContent = item.status; details.appendChild(progress); processingDiv.appendChild(details); processingList.push(details); } else { // Update the progress of existing items const element = processingList.find(element => element.id === item.item.id); const progress = element.querySelector('.progress'); progress.innerText = item.status; } }); // Remove items that are no longer processing const toRemove = processingList.filter(element => !status.some(item => item.item.id === element.id)); toRemove.forEach(element => { console.log('Removing', element); processingList.splice(processingList.indexOf(element), 1); element.remove(); // Add the item to the history const historyList = document.getElementById('history'); if (!historyList) { alert('Failed to find history list'); return; } historyList.prepend(element.querySelector('summary')); }); } async function loadHistory() { const response = await fetch(`${apiBaseUrl}/queue/history`); const history = await response.json(); const historyList = document.getElementById('history'); if (!historyList) { alert('Failed to find history list'); return; } historyList.innerHTML = ''; history.reverse().forEach(item => { const element = constructQueueItem(item); if (!item.result.success) { const retryButton = document.createElement('button'); retryButton.classList.add('retry'); retryButton.textContent = 'Retry'; retryButton.addEventListener('click', () => retry(item)); element.appendChild(retryButton); } historyList.appendChild(element); }) } function getSourceIcon(item) { const sourceToIcon = new Map([ ['spotify', 's'], ['qobuz', 'q'], ['unknown', '?'] ]); return sourceToIcon.get(item.song.source) ?? '?'; } function getState(item) { const state = document.createElement('span'); state.classList.add('state'); if (!item.result) { state.textContent = '⌛'; state.title = 'Processing'; } else { state.textContent = item.result.success ? '✓' : '✗'; state.title = item.result.success ? 'Processed' : 'Failed: ' + item.result.error; } return state; } function getCoverImage(item) { const img = document.createElement('img'); img.src = item.song.cover || "./assets/cover-default.png"; img.alt = `${item.song.title} - ${item.song.artist}`; img.classList.add('cover-art'); return img; } function constructQueueItem(item) { const element = document.createElement('li'); const title = document.createElement('span'); title.classList.add('title'); title.textContent = `${item.song.title} - ${item.song.artist}`; element.appendChild(title); const coverImage = getCoverImage(item); element.prepend(coverImage); const source = document.createElement('span'); source.classList.add('source'); source.textContent = getSourceIcon(item); const link = document.createElement('a'); link.href = item.song.url; link.appendChild(source); element.appendChild(link); element.appendChild(getState(item)); return element; } function setup() { const addSongButton = document.getElementById('add-song'); if (!addSongButton) { alert('Failed to find add song button'); return; } addSongButton.addEventListener('click', addToQueue); const addSongInput = document.getElementById('song-url'); if (!addSongInput) { alert('Failed to find song URL input'); return; } addSongInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { addToQueue(); } }); addSongInput.addEventListener('drop', (event) => { event.preventDefault(); addSongInput.value = event.dataTransfer.getData('text'); addToQueue(); }); const refreshQueueButton = document.getElementById('refresh-queue'); if (!refreshQueueButton) { alert('Failed to find refresh queue button'); return; } refreshQueueButton.addEventListener('click', loadQueue); const clearQueueButton = document.getElementById('clear-queue'); if (!clearQueueButton) { alert('Failed to find clear queue button'); return; } clearQueueButton.addEventListener('click', clearQueue); const apiUrlButton = document.getElementById('set-api'); if (!apiUrlButton) { alert('Failed to find set API URL button'); return; } apiUrlButton.addEventListener('click', setApiUrl); document.addEventListener('DOMContentLoaded', () => { setApiUrl(); loadQueue(); loadHistory(); checkConnectivity(); setInterval(checkConnectivity, 5000); setInterval(loadProcessing, 5000); }); } setup();