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.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 = `
`;
- 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 = `
`;
- 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 = '
';
-
- 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.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