fix images
This commit is contained in:
parent
ab67a5c2dd
commit
ea19b3d95e
4 changed files with 142 additions and 49 deletions
10
index.html
10
index.html
|
|
@ -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>
|
||||
|
|
|
|||
25
js/app.js
25
js/app.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
85
js/ui.js
85
js/ui.js
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue