Merge pull request #22 from JulienMaille/fixes

Fixes
This commit is contained in:
Julien 2025-12-26 12:29:59 +01:00 committed by GitHub
commit f3a0e40a1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 184 additions and 139 deletions

View file

@ -491,9 +491,6 @@
</div> </div>
</footer> </footer>
</div> </div>
<script defer data-domain="monochrome.samidy.com" src="https://plausible.canine.tools/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }</script>
<script type="module" src="js/app.js"></script> <script type="module" src="js/app.js"></script>
</body> </body>
</html> </html>

View file

@ -175,12 +175,12 @@ function hideOfflineNotification() {
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const api = new LosslessAPI(apiSettings); const api = new LosslessAPI(apiSettings);
const ui = new UIRenderer(api);
const audioPlayer = document.getElementById('audio-player'); const audioPlayer = document.getElementById('audio-player');
const currentQuality = localStorage.getItem('playback-quality') || 'LOSSLESS'; const currentQuality = localStorage.getItem('playback-quality') || 'LOSSLESS';
const player = new Player(audioPlayer, api, currentQuality); const player = new Player(audioPlayer, api, currentQuality);
const ui = new UIRenderer(api, player);
const scrobbler = new LastFMScrobbler(); const scrobbler = new LastFMScrobbler();
const lyricsManager = new LyricsManager(api); const lyricsManager = new LyricsManager(api);
const lyricsPanel = createLyricsPanel(); const lyricsPanel = createLyricsPanel();

View file

@ -244,10 +244,10 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana
updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); updateBulkDownloadProgress(notification, i, tracks.length, trackTitle);
try {
const blob = await downloadTrackBlob(track, quality, api); const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${folderName}/${filename}`, blob); zip.file(`${folderName}/${filename}`, blob);
try { try {
const meta = buildTrackMetadata(track, api); const meta = buildTrackMetadata(track, api);
const metaFilename = filename.replace(/\.[^.]+$/, '.json'); const metaFilename = filename.replace(/\.[^.]+$/, '.json');
@ -276,6 +276,9 @@ export async function downloadAlbumAsZip(album, tracks, api, quality, lyricsMana
console.log('Could not add lyrics for:', trackTitle); console.log('Could not add lyrics for:', trackTitle);
} }
} }
} catch (err) {
console.error(`Failed to download track ${trackTitle}:`, err);
}
} }
updateBulkDownloadProgress(notification, tracks.length, tracks.length, 'Creating ZIP...'); updateBulkDownloadProgress(notification, tracks.length, tracks.length, 'Creating ZIP...');
@ -323,6 +326,7 @@ export async function downloadPlaylistAsZip(playlist, tracks, api, quality, lyri
updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); updateBulkDownloadProgress(notification, i, tracks.length, trackTitle);
try {
const blob = await downloadTrackBlob(track, quality, api); const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${folderName}/${filename}`, blob); zip.file(`${folderName}/${filename}`, blob);
@ -354,6 +358,9 @@ export async function downloadPlaylistAsZip(playlist, tracks, api, quality, lyri
console.log('Could not add lyrics for:', trackTitle); console.log('Could not add lyrics for:', trackTitle);
} }
} }
} catch (err) {
console.error(`Failed to download track ${trackTitle}:`, err);
}
} }
updateBulkDownloadProgress(notification, tracks.length, tracks.length, 'Creating ZIP...'); updateBulkDownloadProgress(notification, tracks.length, tracks.length, 'Creating ZIP...');
@ -410,6 +417,8 @@ export async function downloadDiscography(artist, api, quality, lyricsManager =
for (const track of tracks) { for (const track of tracks) {
const filename = buildTrackFilename(track, quality); const filename = buildTrackFilename(track, quality);
try {
const blob = await downloadTrackBlob(track, quality, api); const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${rootFolder}/${albumFolder}/${filename}`, blob); zip.file(`${rootFolder}/${albumFolder}/${filename}`, blob);
@ -439,6 +448,9 @@ export async function downloadDiscography(artist, api, quality, lyricsManager =
console.log('Could not add lyrics for:', track.title); console.log('Could not add lyrics for:', track.title);
} }
} }
} catch (err) {
console.error(`Failed to download track ${track.title} in album ${album.title}:`, err);
}
} }
} catch (error) { } catch (error) {
console.error(`Failed to download album ${album.title}:`, error); console.error(`Failed to download album ${album.title}:`, error);

View file

@ -3,8 +3,9 @@ import { SVG_PLAY, SVG_DOWNLOAD, SVG_MENU, formatTime, createPlaceholder, trackD
import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js'; import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js';
export class UIRenderer { export class UIRenderer {
constructor(api) { constructor(api, player) {
this.api = api; this.api = api;
this.player = player;
this.currentTrack = null; this.currentTrack = null;
this.searchAbortController = null; this.searchAbortController = null;
} }
@ -72,6 +73,7 @@ export class UIRenderer {
const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : ''; const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : '';
const trackArtists = getTrackArtists(track); const trackArtists = getTrackArtists(track);
const trackTitle = getTrackTitle(track); const trackTitle = getTrackTitle(track);
const isCurrentTrack = this.player?.currentTrack?.id === track.id;
let yearDisplay = ''; let yearDisplay = '';
const releaseDate = track.album?.releaseDate || track.streamStartDate; const releaseDate = track.album?.releaseDate || track.streamStartDate;
@ -112,7 +114,7 @@ export class UIRenderer {
`; `;
return ` return `
<div class="track-item" data-track-id="${track.id}"> <div class="track-item ${isCurrentTrack ? 'playing' : ''}" data-track-id="${track.id}">
${trackNumberHTML} ${trackNumberHTML}
<div class="track-item-info"> <div class="track-item-info">
<div class="track-item-details"> <div class="track-item-details">
@ -266,24 +268,60 @@ export class UIRenderer {
if (!color) return; if (!color) return;
const root = document.documentElement; const root = document.documentElement;
const theme = root.getAttribute('data-theme');
const isLightMode = theme === 'light';
// Calculate contrast text color let hex = color.replace('#', '');
const hex = color.replace('#', ''); // Handle shorthand hex
const r = parseInt(hex.substr(0, 2), 16); if (hex.length === 3) {
const g = parseInt(hex.substr(2, 2), 16); hex = hex.split('').map(char => char + char).join('');
const b = parseInt(hex.substr(4, 2), 16); }
const brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
let r = parseInt(hex.substr(0, 2), 16);
let g = parseInt(hex.substr(2, 2), 16);
let b = parseInt(hex.substr(4, 2), 16);
// Calculate perceived brightness
let brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
if (isLightMode) {
// In light mode, the background is white.
// We need the color (used for text/highlights) to be dark enough.
// If brightness is too high (> 150), darken it.
while (brightness > 150) {
r = Math.floor(r * 0.9);
g = Math.floor(g * 0.9);
b = Math.floor(b * 0.9);
brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
}
} else {
// In dark mode, the background is dark.
// We need the color to be light enough.
// If brightness is too low (< 80), lighten it.
while (brightness < 80) {
r = Math.min(255, Math.floor(r * 1.15));
g = Math.min(255, Math.floor(g * 1.15));
b = Math.min(255, Math.floor(b * 1.15));
brightness = ((r * 299) + (g * 587) + (b * 114)) / 1000;
// Break if we hit white or can't get brighter to avoid infinite loop
if (r >= 255 && g >= 255 && b >= 255) break;
}
}
const adjustedColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
// Calculate contrast text color for buttons (text on top of the vibrant color)
const foreground = brightness > 128 ? '#000000' : '#ffffff'; const foreground = brightness > 128 ? '#000000' : '#ffffff';
// Set global CSS variables // Set global CSS variables
root.style.setProperty('--primary', color); root.style.setProperty('--primary', adjustedColor);
root.style.setProperty('--primary-foreground', foreground); root.style.setProperty('--primary-foreground', foreground);
root.style.setProperty('--highlight', color); root.style.setProperty('--highlight', adjustedColor);
root.style.setProperty('--highlight-rgb', `${r}, ${g}, ${b}`); root.style.setProperty('--highlight-rgb', `${r}, ${g}, ${b}`);
root.style.setProperty('--active-highlight', color); root.style.setProperty('--active-highlight', adjustedColor);
root.style.setProperty('--ring', color); root.style.setProperty('--ring', adjustedColor);
// Calculate a safe hover color (darken if too light) // Calculate a safe hover color
let hoverColor; let hoverColor;
if (brightness > 200) { if (brightness > 200) {
const dr = Math.floor(r * 0.85); const dr = Math.floor(r * 0.85);
@ -627,7 +665,7 @@ export class UIRenderer {
} }
} }
async renderPlaylistPage(playlistId) { async renderPlaylistPage(playlistId) {
this.showPage('playlist'); this.showPage('playlist');
const imageEl = document.getElementById('playlist-detail-image'); const imageEl = document.getElementById('playlist-detail-image');
@ -662,13 +700,11 @@ async renderPlaylistPage(playlistId) {
imageEl.style.backgroundColor = ''; imageEl.style.backgroundColor = '';
titleEl.textContent = playlist.title; titleEl.textContent = playlist.title;
this.adjustTitleFontSize(titleEl, playlist.title); this.adjustTitleFontSize(titleEl, playlist.title);
const totalDuration = calculateTotalDuration(tracks); const totalDuration = calculateTotalDuration(tracks);
metaEl.textContent = `${playlist.numberOfTracks} tracks • ${formatDuration(totalDuration)}`; metaEl.textContent = `${playlist.numberOfTracks} tracks • ${formatDuration(totalDuration)}`;
descEl.textContent = playlist.description || ''; descEl.textContent = playlist.description || '';
tracklistContainer.innerHTML = ` tracklistContainer.innerHTML = `
@ -680,14 +716,14 @@ async renderPlaylistPage(playlistId) {
`; `;
this.renderListWithTracks(tracklistContainer, tracks, true); this.renderListWithTracks(tracklistContainer, tracks, true);
recentActivityManager.addPlaylist(playlist); recentActivityManager.addPlaylist(playlist);
document.title = `${playlist.title || 'Artist Mix'} - Monochrome`; } catch (error) { document.title = `${playlist.title || 'Artist Mix'} - Monochrome`;
} catch (error) {
console.error("Failed to load playlist:", error); console.error("Failed to load playlist:", error);
tracklistContainer.innerHTML = createPlaceholder(`Could not load playlist details. ${error.message}`); tracklistContainer.innerHTML = createPlaceholder(`Could not load playlist details. ${error.message}`);
} }
} }
async renderArtistPage(artistId) { async renderArtistPage(artistId) {
this.showPage('artist'); this.showPage('artist');