progress
This commit is contained in:
parent
5142b5153e
commit
bb6be656e0
3 changed files with 269 additions and 10 deletions
|
|
@ -504,16 +504,93 @@ def start_sync():
|
|||
|
||||
@app.route('/api/search', methods=['POST'])
|
||||
def search_music():
|
||||
# Placeholder: simulates a music search
|
||||
"""Real search using soulseek_client"""
|
||||
data = request.get_json()
|
||||
query = data.get('query', '')
|
||||
print(f"Simulating search for: {query}")
|
||||
# In a real implementation, you would call soulseek_client.search()
|
||||
mock_results = [
|
||||
{"title": "Bohemian Rhapsody", "artist": "Queen", "album": "A Night at the Opera", "type": "track", "quality": "FLAC", "username": "user1", "filename": "Queen - Bohemian Rhapsody.flac", "file_size": 35000000},
|
||||
{"title": "A Night at the Opera", "artist": "Queen", "type": "album", "track_count": 12, "size_mb": 350, "username": "user2"}
|
||||
]
|
||||
return jsonify({"results": mock_results})
|
||||
query = data.get('query')
|
||||
if not query:
|
||||
return jsonify({"error": "No search query provided."}), 400
|
||||
|
||||
print(f"Web UI Search for: '{query}'")
|
||||
|
||||
try:
|
||||
tracks, albums = asyncio.run(soulseek_client.search(query))
|
||||
|
||||
# Convert to dictionaries for JSON response
|
||||
processed_albums = []
|
||||
for album in albums:
|
||||
album_dict = album.__dict__.copy()
|
||||
album_dict["tracks"] = [track.__dict__ for track in album.tracks]
|
||||
album_dict["result_type"] = "album"
|
||||
processed_albums.append(album_dict)
|
||||
|
||||
processed_tracks = []
|
||||
for track in tracks:
|
||||
track_dict = track.__dict__.copy()
|
||||
track_dict["result_type"] = "track"
|
||||
processed_tracks.append(track_dict)
|
||||
|
||||
# Sort by quality score
|
||||
all_results = sorted(processed_albums + processed_tracks, key=lambda x: x.get('quality_score', 0), reverse=True)
|
||||
|
||||
return jsonify({"results": all_results})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Search error: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/download', methods=['POST'])
|
||||
def start_download():
|
||||
"""Simple download route"""
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"error": "No download data provided."}), 400
|
||||
|
||||
try:
|
||||
result_type = data.get('result_type', 'track')
|
||||
|
||||
if result_type == 'album':
|
||||
tracks = data.get('tracks', [])
|
||||
if not tracks:
|
||||
return jsonify({"error": "No tracks found in album."}), 400
|
||||
|
||||
started_downloads = 0
|
||||
for track_data in tracks:
|
||||
try:
|
||||
download_id = asyncio.run(soulseek_client.download(
|
||||
track_data.get('username'),
|
||||
track_data.get('filename'),
|
||||
track_data.get('size', 0)
|
||||
))
|
||||
if download_id:
|
||||
started_downloads += 1
|
||||
except Exception as e:
|
||||
print(f"Failed to start track download: {e}")
|
||||
continue
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"Started {started_downloads} downloads from album"
|
||||
})
|
||||
|
||||
else:
|
||||
# Single track download
|
||||
username = data.get('username')
|
||||
filename = data.get('filename')
|
||||
file_size = data.get('size', 0)
|
||||
|
||||
if not username or not filename:
|
||||
return jsonify({"error": "Missing username or filename."}), 400
|
||||
|
||||
download_id = asyncio.run(soulseek_client.download(username, filename, file_size))
|
||||
|
||||
if download_id:
|
||||
return jsonify({"success": True, "message": "Download started"})
|
||||
else:
|
||||
return jsonify({"error": "Failed to start download"}), 500
|
||||
|
||||
except Exception as e:
|
||||
print(f"Download error: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/artists')
|
||||
def get_artists():
|
||||
|
|
|
|||
|
|
@ -924,6 +924,154 @@ async function loadSyncData() {
|
|||
async function loadDownloadsData() {
|
||||
// Downloads page loads search results dynamically
|
||||
console.log('Downloads page loaded');
|
||||
|
||||
// Connect downloads search button
|
||||
const searchButton = document.getElementById('downloads-search-btn');
|
||||
const searchInput = document.getElementById('downloads-search-input');
|
||||
|
||||
if (searchButton && searchInput) {
|
||||
searchButton.addEventListener('click', performDownloadsSearch);
|
||||
searchInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') performDownloadsSearch();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function performDownloadsSearch() {
|
||||
const query = document.getElementById('downloads-search-input').value.trim();
|
||||
if (!query) {
|
||||
showToast('Please enter a search term', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
showLoadingOverlay('Searching...');
|
||||
|
||||
const response = await fetch('/api/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
showToast(`Search error: ${data.error}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const results = data.results || [];
|
||||
displayDownloadsResults(results);
|
||||
|
||||
if (results.length === 0) {
|
||||
showToast('No results found', 'error');
|
||||
} else {
|
||||
showToast(`Found ${results.length} results`, 'success');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
showToast('Search failed', 'error');
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
function displayDownloadsResults(results) {
|
||||
const resultsArea = document.getElementById('search-results-area');
|
||||
if (!resultsArea) return;
|
||||
|
||||
if (!results.length) {
|
||||
resultsArea.innerHTML = '<div class="search-results-placeholder"><p>No search results found.</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
results.forEach((result, index) => {
|
||||
const isAlbum = result.result_type === 'album';
|
||||
|
||||
if (isAlbum) {
|
||||
const trackCount = result.tracks ? result.tracks.length : 0;
|
||||
html += `
|
||||
<div class="search-result-item album-result">
|
||||
<div class="result-info">
|
||||
<strong>🎵 ${escapeHtml(result.album_title || result.title || 'Unknown Album')}</strong><br>
|
||||
<span>by ${escapeHtml(result.artist || 'Unknown Artist')}</span><br>
|
||||
<small>${trackCount} tracks • ${escapeHtml(result.quality || 'Mixed')}</small>
|
||||
</div>
|
||||
<button onclick="downloadAlbum(${index})" class="download-btn">⬇ Download Album</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
const sizeText = result.size ? `${(result.size / 1024 / 1024).toFixed(1)} MB` : 'Unknown size';
|
||||
html += `
|
||||
<div class="search-result-item track-result">
|
||||
<div class="result-info">
|
||||
<strong>${escapeHtml(result.title || 'Unknown Title')}</strong><br>
|
||||
<span>by ${escapeHtml(result.artist || 'Unknown Artist')}</span><br>
|
||||
<small>${sizeText} • ${escapeHtml(result.quality || 'Unknown')} ${result.bitrate ? `${result.bitrate}kbps` : ''}</small>
|
||||
</div>
|
||||
<button onclick="downloadTrack(${index})" class="download-btn">⬇ Download</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
resultsArea.innerHTML = html;
|
||||
// Store results globally for download functions
|
||||
window.currentSearchResults = results;
|
||||
}
|
||||
|
||||
async function downloadTrack(index) {
|
||||
const results = window.currentSearchResults;
|
||||
if (!results || !results[index]) return;
|
||||
|
||||
const track = results[index];
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(track)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast(`Download started: ${track.title}`, 'success');
|
||||
} else {
|
||||
showToast(`Download failed: ${data.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Download error:', error);
|
||||
showToast('Failed to start download', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadAlbum(index) {
|
||||
const results = window.currentSearchResults;
|
||||
if (!results || !results[index]) return;
|
||||
|
||||
const album = results[index];
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(album)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast(data.message, 'success');
|
||||
} else {
|
||||
showToast(`Album download failed: ${data.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Album download error:', error);
|
||||
showToast('Failed to start album download', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadArtistsData() {
|
||||
|
|
@ -1275,4 +1423,6 @@ window.authenticateTidal = authenticateTidal;
|
|||
window.browsePath = browsePath;
|
||||
window.selectResult = selectResult;
|
||||
window.startStream = startStream;
|
||||
window.startDownload = startDownload;
|
||||
window.startDownload = startDownload;
|
||||
window.downloadTrack = downloadTrack;
|
||||
window.downloadAlbum = downloadAlbum;
|
||||
|
|
@ -2006,4 +2006,36 @@ body {
|
|||
|
||||
.version-modal-overlay:not(.hidden) .version-modal {
|
||||
animation: modalFadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* Simple Downloads Results Styling */
|
||||
.search-result-item {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-result-item .result-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-result-item .download-btn {
|
||||
background: rgba(29, 185, 84, 0.1);
|
||||
color: #1ed760;
|
||||
border: 1px solid rgba(29, 185, 84, 0.3);
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-result-item .download-btn:hover {
|
||||
background: rgba(29, 185, 84, 0.2);
|
||||
border-color: rgba(29, 185, 84, 0.5);
|
||||
}
|
||||
Loading…
Reference in a new issue