331 lines
9.5 KiB
JavaScript
331 lines
9.5 KiB
JavaScript
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();
|