Files
AudioApp/public/app.js

345 lines
11 KiB
JavaScript

// API Base URL
const API_BASE = window.location.origin;
// Load dashboard stats on page load
document.addEventListener('DOMContentLoaded', () => {
loadDashboard();
updateSubsonicUrl();
loadSettings();
});
// 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;
}
// Load settings
async function loadSettings() {
try {
const res = await fetch(`${API_BASE}/api/settings`);
const settings = await res.json();
document.getElementById('libraryPath').value = settings.libraryPath;
} catch (error) {
console.error('Error loading settings:', error);
}
}
// Save library path
async function saveLibraryPath() {
const libraryPath = document.getElementById('libraryPath').value;
if (!libraryPath) {
alert('Please enter a library path');
return;
}
try {
const res = await fetch(`${API_BASE}/api/settings`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ libraryPath })
});
const data = await res.json();
if (res.ok) {
alert('Library path updated! Please restart the server for changes to take effect.');
} else {
alert(`Error: ${data.error}`);
}
} catch (error) {
console.error('Error saving settings:', error);
alert('Failed to save settings');
}
}
// Browse for folder (note: web browsers can't actually browse filesystem)
function browseFolder() {
alert('Enter the full path to your audio library folder.\n\nExamples:\n- /Users/username/Music\n- /home/user/music\n- C:\\Users\\username\\Music\n- ./audio_library (relative to server)');
}