diff --git a/frontend/js/app.js b/frontend/js/app.js deleted file mode 100644 index b9ce889..0000000 --- a/frontend/js/app.js +++ /dev/null @@ -1,9 +0,0 @@ -import { loadAccounts } from './components/load.js'; -import { initSearch } from './components/search.js'; -import { initDownload } from './components/download.js'; - -document.addEventListener('DOMContentLoaded', () => { - loadAccounts(); - initSearch(); - initDownload(); -}); \ No newline at end of file diff --git a/frontend/js/components/download.js b/frontend/js/components/download.js deleted file mode 100644 index dca2c16..0000000 --- a/frontend/js/components/download.js +++ /dev/null @@ -1,247 +0,0 @@ -export function initSearch() { - document.getElementById('searchForm').addEventListener('submit', handleSearch); -} - -async function handleSearch(e) { - e.preventDefault(); - const account = document.getElementById('accountSelect').value; - const query = document.getElementById('searchQuery').value; - - if (!account || !query) return; - - try { - const response = await fetch(`/api/search?account=${encodeURIComponent(account)}&query=${encodeURIComponent(query)}`); - const data = await response.json(); - displayResults(data); - } catch (error) { - console.error('Search failed:', error); - document.getElementById('resultsContainer').innerHTML = '
Search failed. Please try again.
'; - } -} - -function displayResults(data) { - const container = document.getElementById('resultsContainer'); - container.innerHTML = ''; - - ['albums', 'tracks'].forEach(type => { - if (data[type]?.length > 0) { - const section = document.createElement('details'); - section.className = 'category'; - section.innerHTML = ` - ${type.charAt(0).toUpperCase() + type.slice(1)} (${data[type].length}) -
- ${data[type].map(item => ` -
- ${createItemCard(item)} -
- `).join('')} -
- `; - container.appendChild(section); - } - }); -} - -function createItemCard(item) { - return ` -
- ${item.image_url ? `${item.name}` : ''} -
-
-

${item.name}${item.explicit ? 'E' : ''}

- ${item.artists ? `

Artists: ${item.artists.join(', ')}

` : ''} - ${item.owner ? `

Owner: ${item.owner}

` : ''} - ${item.total_tracks ? `

Total Tracks: ${item.total_tracks}

` : ''} -
- ${['album', 'track'].includes(item.type) ? ` - - ` : ''} -
-
- `; -} - -export function initDownload() { - document.getElementById('resultsContainer').addEventListener('click', async (e) => { - if (e.target.closest('.download-btn')) { - const card = e.target.closest('.item-card'); - const btn = card.querySelector('.download-btn'); - const account = document.getElementById('accountSelect').value; - const { spotifyId, type } = card.dataset; - const originalBtnHTML = btn.innerHTML; - - try { - // Show loading state - btn.innerHTML = `Loading`; - btn.classList.add('loading'); - btn.style.pointerEvents = 'none'; - - const statusLine = document.createElement('div'); - statusLine.className = 'status-line'; - card.appendChild(statusLine); - - const response = await fetch( - `/api/download?account=${encodeURIComponent(account)}&type=${type}&id=${spotifyId}` - ); - - const reader = response.body - .pipeThrough(new TextDecoderStream()) - .getReader(); - - let buffer = ''; - let lastPercentage = -1; - let totalTracks = 0; - let completedTracks = 0; - let currentTrackNumber = 0; - let currentTrackName = ''; - let isArtistDownload = type === 'artist'; - let firstAlbumStart = isArtistDownload; - - while (true) { - const { done, value } = await reader.read(); - if (done) { - // Update to checkmark and stop animation - btn.innerHTML = `Completed`; - btn.classList.remove('loading'); - btn.classList.add('completed'); - btn.style.pointerEvents = 'none'; - - if (isArtistDownload) { - statusLine.textContent = 'Artist download complete!'; - statusLine.classList.add('completed'); - } - break; - } - - buffer += value; - const lines = buffer.split('\n'); - buffer = lines.pop() || ''; - - for (const line of lines) { - if (!line.trim()) continue; - try { - const data = JSON.parse(line); - - switch(data.type) { - case 'track_start': - statusLine.textContent = 'Starting track download...'; - break; - - case 'album_start': - totalTracks = data.total_tracks; - if (isArtistDownload && firstAlbumStart) { - statusLine.textContent = `Downloading ${data.artist}`; - firstAlbumStart = false; - } else { - statusLine.textContent = `Downloading album: ${data.album}`; - } - break; - - case 'playlist_start': - statusLine.textContent = `Starting playlist: ${data.name}`; - break; - - case 'metadata': - if (type === 'track') { - statusLine.textContent = `Downloading: ${data.name} by ${data.artists[0]}`; - } - break; - - case 'download_start': - statusLine.textContent += ` (${formatBytes(data.total_size)})`; - break; - - case 'download_progress': - if (Math.floor(data.percentage) > lastPercentage) { - lastPercentage = Math.floor(data.percentage); - const progress = `${lastPercentage}% of ${formatBytes(data.total)}`; - if (type === 'track') { - statusLine.textContent = `Downloading: ${progress}`; - } else if (type === 'album') { - statusLine.textContent = `Downloading track ${currentTrackNumber}/${totalTracks}: ${currentTrackName} (${progress})`; - } else { - statusLine.textContent = `Downloading track ${completedTracks + 1}: ${progress}`; - } - } - break; - - case 'album_track_start': - currentTrackNumber = data.number; - currentTrackName = data.track; - statusLine.textContent = `Downloading track ${data.number}/${totalTracks}: ${data.track}`; - lastPercentage = -1; // Reset progress for new track - break; - - case 'playlist_track_start': - statusLine.textContent = `Downloading track ${data.position}: ${data.track}`; - lastPercentage = -1; // Reset progress for new track - break; - - case 'conversion_start': - statusLine.textContent = `Converting to ${data.format}...`; - break; - - case 'track_complete': - completedTracks++; - statusLine.textContent = `Completed: ${data.output_path.split('/').pop()}`; - statusLine.classList.add('completed'); - break; - - case 'track_skip': - completedTracks++; - statusLine.textContent = `Skipped: ${data.reason === 'already_exists' ? 'Already exists' : data.reason}`; - statusLine.classList.add('warning'); - break; - - case 'album_complete': - statusLine.textContent = `Album complete! ${data.successful}/${data.total} tracks`; - statusLine.classList.add('completed'); - break; - - case 'playlist_complete': - statusLine.textContent = `Playlist complete! ${data.downloaded} tracks`; - statusLine.classList.add('completed'); - break; - } - - } catch (e) { - console.error('Error parsing JSON:', e); - } - } - } - - } catch (error) { - console.error('Download failed:', error); - // Restore original button state - btn.innerHTML = originalBtnHTML; - btn.classList.remove('loading', 'completed'); - btn.style.pointerEvents = 'auto'; - - const statusLine = card.querySelector('.status-line'); - statusLine.textContent = `Download failed: ${error.message}`; - statusLine.classList.add('error'); - } - } - }); -} - -// Helper function to format bytes -function formatBytes(bytes) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; -} diff --git a/frontend/js/components/load.js b/frontend/js/components/load.js deleted file mode 100644 index c3166cf..0000000 --- a/frontend/js/components/load.js +++ /dev/null @@ -1,14 +0,0 @@ -// Account loading functionality -export async function loadAccounts() { - try { - const response = await fetch('/api/check'); - const data = await response.json(); - const select = document.getElementById('accountSelect'); - - select.innerHTML = data.accounts.length - ? data.accounts.map(acc => ``).join('') - : ''; - } catch (error) { - console.error('Error loading accounts:', error); - } -} \ No newline at end of file diff --git a/frontend/js/components/search.js b/frontend/js/components/search.js deleted file mode 100644 index fdfb62e..0000000 --- a/frontend/js/components/search.js +++ /dev/null @@ -1,75 +0,0 @@ -export function initSearch() { - document.getElementById('searchForm').addEventListener('submit', handleSearch); -} - -async function handleSearch(e) { - e.preventDefault(); - const account = document.getElementById('accountSelect').value; - const query = document.getElementById('searchQuery').value; - - if (!account || !query) return; - - const container = document.getElementById('resultsContainer'); - container.innerHTML = 'Loading...'; - - try { - const response = await fetch(`/api/search?account=${encodeURIComponent(account)}&query=${encodeURIComponent(query)}`); - const data = await response.json(); - displayResults(data); - } catch (error) { - console.error('Search failed:', error); - container.innerHTML = '
Search failed. Please try again.
'; - } -} - -function displayResults(data) { - const container = document.getElementById('resultsContainer'); - container.innerHTML = ''; - - ['albums', 'tracks'].forEach(type => { - if (data[type]?.length > 0) { - const section = document.createElement('details'); - section.className = 'category'; - section.innerHTML = ` - ${type.charAt(0).toUpperCase() + type.slice(1)} (${data[type].length}) -
- ${data[type].map(item => ` -
- ${createItemCard(item)} -
- `).join('')} -
- `; - container.appendChild(section); - } - }); -} - -function createItemCard(item) { - return ` -
-
- ${item.image_url ? `${item.name}` : ''} -
-
-

${item.name}${item.explicit ? 'E' : ''}

- ${item.artists ? `

Artists: ${item.artists.join(', ')}

` : ''} - ${item.owner ? `

Owner: ${item.owner}

` : ''} - ${item.total_tracks ? `

Total Tracks: ${item.total_tracks}

` : ''} -
- ${['album', 'track'].includes(item.type) ? ` - - ` : ''} -
-
-
- `; -} \ No newline at end of file