fix images

This commit is contained in:
Eduard Prigoana 2025-11-08 19:38:10 +02:00
parent ab67a5c2dd
commit ea19b3d95e
4 changed files with 142 additions and 49 deletions

View file

@ -174,7 +174,7 @@
<div class="track-list" id="album-detail-tracklist"></div>
</div>
<section id="page-playlist" class="page">
<section id="page-playlist" class="page">
<div class="detail-header">
<img id="playlist-detail-image" class="detail-cover" alt="Playlist Cover">
<div class="detail-info">
@ -189,6 +189,14 @@
</svg>
<span>Play</span>
</button>
<button id="download-playlist-btn" class="btn-secondary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
<span>Download</span>
</button>
</div>
</div>
</div>

View file

@ -8,7 +8,7 @@ import { createRouter, updateTabTitle } from './router.js';
import { initializeSettings } from './settings.js';
import { initializePlayerEvents, initializeTrackInteractions } from './events.js';
import { initializeUIInteractions } from './ui-interactions.js';
import { downloadAlbumAsZip, downloadDiscography, downloadCurrentTrack } from './downloads.js';
import { downloadAlbumAsZip, downloadDiscography, downloadCurrentTrack, downloadPlaylistAsZip } from './downloads.js';
import { debounce, SVG_PLAY } from './utils.js';
function initializeCasting(audioPlayer, castBtn) {
@ -291,7 +291,28 @@ document.addEventListener('DOMContentLoaded', async () => {
alert('Failed to play album: ' + error.message);
}
}
if (e.target.closest('#download-playlist-btn')) {
const btn = e.target.closest('#download-playlist-btn');
if (btn.disabled) return;
const playlistId = window.location.hash.split('/')[1];
if (!playlistId) return;
btn.disabled = true;
const originalHTML = btn.innerHTML;
btn.innerHTML = '<svg class="animate-spin" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle></svg><span>Downloading...</span>';
try {
const { playlist, tracks } = await api.getPlaylist(playlistId);
await downloadPlaylistAsZip(playlist, tracks, api, player.quality, lyricsManager);
} catch (error) {
console.error('Playlist download failed:', error);
alert('Failed to download playlist: ' + error.message);
} finally {
btn.disabled = false;
btn.innerHTML = originalHTML;
}
}
if (e.target.closest('#play-playlist-btn')) {
const btn = e.target.closest('#play-playlist-btn');
if (btn.disabled) return;

View file

@ -185,7 +185,70 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana
const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${folderName}/${filename}`, blob);
// Add LRC to zip if enabled
if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) {
try {
const lyricsData = await lyricsManager.fetchLyrics(track.id);
if (lyricsData) {
const lrcContent = lyricsManager.generateLRCContent(lyricsData, track);
if (lrcContent) {
const lrcFilename = filename.replace(/\.[^.]+$/, '.lrc');
zip.file(`${folderName}/${lrcFilename}`, lrcContent);
}
}
} catch (error) {
console.log('Could not add lyrics for:', trackTitle);
}
}
}
updateBulkDownloadProgress(notification, tracks.length, tracks.length, 'Creating ZIP...');
const zipBlob = await zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: { level: 6 }
});
const url = URL.createObjectURL(zipBlob);
const a = document.createElement('a');
a.href = url;
a.download = `${folderName}.zip`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
completeBulkDownload(notification, true);
} catch (error) {
completeBulkDownload(notification, false, error.message);
throw error;
}
}
export async function downloadPlaylistAsZip(playlist, tracks, api, quality, lyricsManager = null) {
const JSZip = await loadJSZip();
const zip = new JSZip();
const template = localStorage.getItem('zip-folder-template') || '{albumTitle} - {albumArtist} - monochrome.tf';
const folderName = formatTemplate(template, {
albumTitle: playlist.title,
albumArtist: 'Playlist',
year: new Date().getFullYear()
});
const notification = createBulkDownloadNotification('playlist', playlist.title, tracks.length);
try {
for (let i = 0; i < tracks.length; i++) {
const track = tracks[i];
const filename = buildTrackFilename(track, quality);
const trackTitle = getTrackTitle(track);
updateBulkDownloadProgress(notification, i, tracks.length, trackTitle);
const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${folderName}/${filename}`, blob);
if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) {
try {
const lyricsData = await lyricsManager.fetchLyrics(track.id);
@ -255,7 +318,6 @@ export async function downloadDiscography(artist, api, quality, lyricsManager =
const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${rootFolder}/${albumFolder}/${filename}`, blob);
// Add LRC to zip if enabled
if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) {
try {
const lyricsData = await lyricsManager.fetchLyrics(track.id);
@ -306,11 +368,13 @@ function createBulkDownloadNotification(type, name, totalItems) {
const notifEl = document.createElement('div');
notifEl.className = 'download-task bulk-download';
const typeLabel = type === 'album' ? 'Album' : type === 'playlist' ? 'Playlist' : 'Discography';
notifEl.innerHTML = `
<div style="display: flex; align-items: start; gap: 0.75rem;">
<div style="flex: 1; min-width: 0;">
<div style="font-weight: 600; font-size: 0.95rem; margin-bottom: 0.25rem;">
Downloading ${type === 'album' ? 'Album' : 'Discography'}
Downloading ${typeLabel}
</div>
<div style="font-size: 0.85rem; color: var(--muted-foreground); margin-bottom: 0.5rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${name}</div>
<div class="download-progress-bar" style="height: 4px; background: var(--secondary); border-radius: 2px; overflow: hidden;">
@ -385,7 +449,6 @@ export async function downloadCurrentTrack(track, quality, api, lyricsManager =
completeDownloadTask(track.id, true);
// Download LRC if enabled
if (lyricsManager && lyricsSettings.shouldDownloadLyrics()) {
try {
const lyricsData = await lyricsManager.fetchLyrics(track.id);

View file

@ -299,59 +299,60 @@ export class UIRenderer {
}
}
async renderPlaylistPage(playlistId) {
this.showPage('playlist');
async renderPlaylistPage(playlistId) {
this.showPage('playlist');
const imageEl = document.getElementById('playlist-detail-image');
const titleEl = document.getElementById('playlist-detail-title');
const metaEl = document.getElementById('playlist-detail-meta');
const descEl = document.getElementById('playlist-detail-description');
const tracklistContainer = document.getElementById('playlist-detail-tracklist');
imageEl.src = '';
imageEl.style.backgroundColor = 'var(--muted)';
titleEl.innerHTML = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
descEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 100%;"></div>';
tracklistContainer.innerHTML = `
<div class="track-list-header">
<span style="width: 40px; text-align: center;">#</span>
<span>Title</span>
<span class="duration-header">Duration</span>
</div>
${this.createSkeletonTracks(10, true)}
`;
try {
const { playlist, tracks } = await this.api.getPlaylist(playlistId);
const imageEl = document.getElementById('playlist-detail-image');
const titleEl = document.getElementById('playlist-detail-title');
const metaEl = document.getElementById('playlist-detail-meta');
const descEl = document.getElementById('playlist-detail-description');
const tracklistContainer = document.getElementById('playlist-detail-tracklist');
const imageId = playlist.squareImage || playlist.image;
imageEl.src = this.api.getCoverUrl(imageId, '1080');
imageEl.style.backgroundColor = '';
titleEl.textContent = playlist.title;
const totalDuration = calculateTotalDuration(tracks);
metaEl.textContent = `${playlist.numberOfTracks} tracks • ${formatDuration(totalDuration)}`;
descEl.textContent = playlist.description || '';
imageEl.src = '';
imageEl.style.backgroundColor = 'var(--muted)';
titleEl.innerHTML = '<div class="skeleton" style="height: 48px; width: 300px; max-width: 90%;"></div>';
metaEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 200px; max-width: 80%;"></div>';
descEl.innerHTML = '<div class="skeleton" style="height: 16px; width: 100%;"></div>';
tracklistContainer.innerHTML = `
<div class="track-list-header">
<span style="width: 40px; text-align: center;">#</span>
<span>Title</span>
<span class="duration-header">Duration</span>
</div>
${this.createSkeletonTracks(10, true)}
`;
try {
const { playlist, tracks } = await this.api.getPlaylist(playlistId);
imageEl.src = this.api.getCoverUrl(playlist.image || playlist.squareImage, '1280');
imageEl.style.backgroundColor = '';
titleEl.textContent = playlist.title;
const totalDuration = calculateTotalDuration(tracks);
metaEl.textContent = `${playlist.numberOfTracks} tracks • ${formatDuration(totalDuration)}`;
descEl.textContent = playlist.description || '';
tracklistContainer.innerHTML = `
<div class="track-list-header">
<span style="width: 40px; text-align: center;">#</span>
<span>Title</span>
<span class="duration-header">Duration</span>
</div>
`;
this.renderListWithTracks(tracklistContainer, tracks, true);
document.title = `${playlist.title} - Monochrome`;
} catch (error) {
console.error("Failed to load playlist:", error);
tracklistContainer.innerHTML = createPlaceholder(`Could not load playlist details. ${error.message}`);
}
this.renderListWithTracks(tracklistContainer, tracks, true);
document.title = `${playlist.title} - Monochrome`;
} catch (error) {
console.error("Failed to load playlist:", error);
tracklistContainer.innerHTML = createPlaceholder(`Could not load playlist details. ${error.message}`);
}
}
async renderArtistPage(artistId) {
this.showPage('artist');