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'])
|
@app.route('/api/search', methods=['POST'])
|
||||||
def search_music():
|
def search_music():
|
||||||
# Placeholder: simulates a music search
|
"""Real search using soulseek_client"""
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
query = data.get('query', '')
|
query = data.get('query')
|
||||||
print(f"Simulating search for: {query}")
|
if not query:
|
||||||
# In a real implementation, you would call soulseek_client.search()
|
return jsonify({"error": "No search query provided."}), 400
|
||||||
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},
|
print(f"Web UI Search for: '{query}'")
|
||||||
{"title": "A Night at the Opera", "artist": "Queen", "type": "album", "track_count": 12, "size_mb": 350, "username": "user2"}
|
|
||||||
]
|
try:
|
||||||
return jsonify({"results": mock_results})
|
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')
|
@app.route('/api/artists')
|
||||||
def get_artists():
|
def get_artists():
|
||||||
|
|
|
||||||
|
|
@ -924,6 +924,154 @@ async function loadSyncData() {
|
||||||
async function loadDownloadsData() {
|
async function loadDownloadsData() {
|
||||||
// Downloads page loads search results dynamically
|
// Downloads page loads search results dynamically
|
||||||
console.log('Downloads page loaded');
|
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() {
|
async function loadArtistsData() {
|
||||||
|
|
@ -1276,3 +1424,5 @@ window.browsePath = browsePath;
|
||||||
window.selectResult = selectResult;
|
window.selectResult = selectResult;
|
||||||
window.startStream = startStream;
|
window.startStream = startStream;
|
||||||
window.startDownload = startDownload;
|
window.startDownload = startDownload;
|
||||||
|
window.downloadTrack = downloadTrack;
|
||||||
|
window.downloadAlbum = downloadAlbum;
|
||||||
|
|
@ -2007,3 +2007,35 @@ body {
|
||||||
.version-modal-overlay:not(.hidden) .version-modal {
|
.version-modal-overlay:not(.hidden) .version-modal {
|
||||||
animation: modalFadeIn 0.3s ease-out;
|
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