299 lines
9.5 KiB
JavaScript
299 lines
9.5 KiB
JavaScript
// API Base URL
|
|
const API_BASE = window.location.origin;
|
|
|
|
// Load dashboard stats on page load
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadDashboard();
|
|
updateSubsonicUrl();
|
|
});
|
|
|
|
// Switch between tabs
|
|
function switchTab(tabName) {
|
|
// Hide all tabs
|
|
document.querySelectorAll('.tab-content').forEach(tab => {
|
|
tab.classList.remove('active');
|
|
});
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.classList.remove('active');
|
|
});
|
|
|
|
// Show selected tab
|
|
document.getElementById(tabName).classList.add('active');
|
|
event.target.classList.add('active');
|
|
|
|
// Load data for the tab
|
|
if (tabName === 'tracks') loadTracks();
|
|
if (tabName === 'albums') loadAlbums();
|
|
if (tabName === 'playlists') loadPlaylists();
|
|
}
|
|
|
|
// Load dashboard statistics
|
|
async function loadDashboard() {
|
|
try {
|
|
const [tracksRes, albumsRes, playlistsRes] = await Promise.all([
|
|
fetch(`${API_BASE}/api/tracks`),
|
|
fetch(`${API_BASE}/api/albums`),
|
|
fetch(`${API_BASE}/api/playlists`)
|
|
]);
|
|
|
|
const tracks = await tracksRes.json();
|
|
const albums = await albumsRes.json();
|
|
const playlists = await playlistsRes.json();
|
|
|
|
const singles = tracks.tracks.filter(t => t.isSingle).length;
|
|
|
|
document.getElementById('totalTracks').textContent = tracks.tracks.length;
|
|
document.getElementById('totalAlbums').textContent = albums.albums.length;
|
|
document.getElementById('totalSingles').textContent = singles;
|
|
document.getElementById('totalPlaylists').textContent = playlists.playlists.length;
|
|
} catch (error) {
|
|
console.error('Error loading dashboard:', error);
|
|
}
|
|
}
|
|
|
|
// Load all tracks
|
|
async function loadTracks() {
|
|
const trackList = document.getElementById('trackList');
|
|
trackList.innerHTML = '<li class="loading">Loading tracks...</li>';
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/tracks`);
|
|
const data = await res.json();
|
|
|
|
if (data.tracks.length === 0) {
|
|
trackList.innerHTML = '<li class="loading">No tracks found. Scan your library first.</li>';
|
|
return;
|
|
}
|
|
|
|
trackList.innerHTML = data.tracks.map(track => `
|
|
<li class="track-item">
|
|
<div class="track-info">
|
|
<div class="track-title">
|
|
${track.title}
|
|
<span class="badge ${track.isSingle ? 'badge-single' : 'badge-album'}">
|
|
${track.isSingle ? 'Single' : 'Album'}
|
|
</span>
|
|
</div>
|
|
<div class="track-meta">${track.artist} • ${track.album}</div>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-secondary" onclick="toggleSingle('${track.id}', ${!track.isSingle})">
|
|
Mark as ${track.isSingle ? 'Album' : 'Single'}
|
|
</button>
|
|
</div>
|
|
</li>
|
|
`).join('');
|
|
} catch (error) {
|
|
trackList.innerHTML = '<li class="loading">Error loading tracks</li>';
|
|
console.error('Error loading tracks:', error);
|
|
}
|
|
}
|
|
|
|
// Search tracks
|
|
function searchTracks() {
|
|
const searchTerm = document.getElementById('searchTracks').value.toLowerCase();
|
|
const items = document.querySelectorAll('.track-item');
|
|
|
|
items.forEach(item => {
|
|
const text = item.textContent.toLowerCase();
|
|
item.style.display = text.includes(searchTerm) ? 'flex' : 'none';
|
|
});
|
|
}
|
|
|
|
// Toggle single/album status
|
|
async function toggleSingle(trackId, isSingle) {
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/tracks/${trackId}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ isSingle })
|
|
});
|
|
|
|
if (res.ok) {
|
|
loadTracks();
|
|
loadDashboard();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating track:', error);
|
|
alert('Failed to update track');
|
|
}
|
|
}
|
|
|
|
// Load albums
|
|
async function loadAlbums() {
|
|
const albumList = document.getElementById('albumList');
|
|
albumList.innerHTML = '<li class="loading">Loading albums...</li>';
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/albums`);
|
|
const data = await res.json();
|
|
|
|
if (data.albums.length === 0) {
|
|
albumList.innerHTML = '<li class="loading">No albums found</li>';
|
|
return;
|
|
}
|
|
|
|
albumList.innerHTML = data.albums.map(album => `
|
|
<li class="track-item">
|
|
<div class="track-info">
|
|
<div class="track-title">${album.name}</div>
|
|
<div class="track-meta">${album.artist} • ${album.trackCount} tracks</div>
|
|
</div>
|
|
</li>
|
|
`).join('');
|
|
} catch (error) {
|
|
albumList.innerHTML = '<li class="loading">Error loading albums</li>';
|
|
console.error('Error loading albums:', error);
|
|
}
|
|
}
|
|
|
|
// Load playlists
|
|
async function loadPlaylists() {
|
|
const playlistList = document.getElementById('playlistList');
|
|
playlistList.innerHTML = '<li class="loading">Loading playlists...</li>';
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/playlists`);
|
|
const data = await res.json();
|
|
|
|
if (data.playlists.length === 0) {
|
|
playlistList.innerHTML = '<li class="loading">No playlists yet. Create one!</li>';
|
|
return;
|
|
}
|
|
|
|
playlistList.innerHTML = data.playlists.map(playlist => `
|
|
<li class="playlist-item">
|
|
<div class="track-info">
|
|
<div class="track-title">${playlist.name}</div>
|
|
<div class="track-meta">${playlist.trackCount} tracks</div>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-danger" onclick="deletePlaylist('${playlist.id}')">Delete</button>
|
|
</div>
|
|
</li>
|
|
`).join('');
|
|
} catch (error) {
|
|
playlistList.innerHTML = '<li class="loading">Error loading playlists</li>';
|
|
console.error('Error loading playlists:', error);
|
|
}
|
|
}
|
|
|
|
// Show create playlist modal
|
|
function showCreatePlaylist() {
|
|
document.getElementById('createPlaylistModal').classList.add('active');
|
|
}
|
|
|
|
// Close modal
|
|
function closeModal() {
|
|
document.getElementById('createPlaylistModal').classList.remove('active');
|
|
document.getElementById('playlistName').value = '';
|
|
document.getElementById('playlistDescription').value = '';
|
|
}
|
|
|
|
// Create playlist
|
|
async function createPlaylist() {
|
|
const name = document.getElementById('playlistName').value;
|
|
const description = document.getElementById('playlistDescription').value;
|
|
|
|
if (!name) {
|
|
alert('Please enter a playlist name');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/playlists`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name, description })
|
|
});
|
|
|
|
if (res.ok) {
|
|
closeModal();
|
|
loadPlaylists();
|
|
loadDashboard();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error creating playlist:', error);
|
|
alert('Failed to create playlist');
|
|
}
|
|
}
|
|
|
|
// Delete playlist
|
|
async function deletePlaylist(playlistId) {
|
|
if (!confirm('Are you sure you want to delete this playlist?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/playlists/${playlistId}`, {
|
|
method: 'DELETE'
|
|
});
|
|
|
|
if (res.ok) {
|
|
loadPlaylists();
|
|
loadDashboard();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting playlist:', error);
|
|
alert('Failed to delete playlist');
|
|
}
|
|
}
|
|
|
|
// Scan library
|
|
async function scanLibrary() {
|
|
const scanStatus = document.getElementById('scanStatus');
|
|
scanStatus.style.display = 'block';
|
|
|
|
try {
|
|
// Note: This would need a scan endpoint on the server
|
|
alert('Library scanning is currently done via CLI: npm run scan\n\nWe can add a web-based scan button if needed!');
|
|
scanStatus.style.display = 'none';
|
|
} catch (error) {
|
|
console.error('Error scanning library:', error);
|
|
alert('Failed to scan library');
|
|
scanStatus.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Upload audio file
|
|
async function uploadAudio() {
|
|
const fileInput = document.getElementById('uploadFile');
|
|
const file = fileInput.files[0];
|
|
|
|
if (!file) return;
|
|
|
|
const isSingle = confirm('Is this a single track? (Click Cancel if it\'s part of an album)');
|
|
|
|
const formData = new FormData();
|
|
formData.append('audio', file);
|
|
formData.append('isSingle', isSingle);
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/api/upload`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (res.ok) {
|
|
alert('File uploaded successfully!');
|
|
loadDashboard();
|
|
if (document.getElementById('tracks').classList.contains('active')) {
|
|
loadTracks();
|
|
}
|
|
} else {
|
|
alert('Failed to upload file');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error uploading file:', error);
|
|
alert('Failed to upload file');
|
|
}
|
|
|
|
fileInput.value = '';
|
|
}
|
|
|
|
// Update Subsonic URL
|
|
function updateSubsonicUrl() {
|
|
const url = `${window.location.protocol}//${window.location.hostname}:${window.location.port}/rest`;
|
|
document.getElementById('subsonicUrl').value = url;
|
|
}
|