This commit is contained in:
Eduard Prigoana 2025-10-22 18:46:21 +03:00
parent c8f0ebbdf9
commit 8665a2d879
4 changed files with 40 additions and 35 deletions

View file

@ -8,7 +8,7 @@ import {
SVG_VOLUME, SVG_MUTE, formatTime, trackDataStore, SVG_VOLUME, SVG_MUTE, formatTime, trackDataStore,
buildTrackFilename, RATE_LIMIT_ERROR_MESSAGE, debounce, buildTrackFilename, RATE_LIMIT_ERROR_MESSAGE, debounce,
sanitizeForFilename, sanitizeForFilename,
getTrackTitle getTrackArtists
} from './utils.js'; } from './utils.js';
const downloadTasks = new Map(); const downloadTasks = new Map();
@ -39,15 +39,13 @@ function addDownloadTask(trackId, track, filename, api) {
const taskEl = document.createElement('div'); const taskEl = document.createElement('div');
taskEl.className = 'download-task'; taskEl.className = 'download-task';
taskEl.dataset.trackId = trackId; taskEl.dataset.trackId = trackId;
const trackTitle = getTrackTitle(track);
taskEl.innerHTML = ` taskEl.innerHTML = `
<div style="display: flex; align-items: start; gap: 0.75rem;"> <div style="display: flex; align-items: start; gap: 0.75rem;">
<img src="${api.getCoverUrl(track.album?.cover, '80')}" <img src="${api.getCoverUrl(track.album?.cover, '80')}"
style="width: 40px; height: 40px; border-radius: 4px; flex-shrink: 0;"> style="width: 40px; height: 40px; border-radius: 4px; flex-shrink: 0;">
<div style="flex: 1; min-width: 0;"> <div style="flex: 1; min-width: 0;">
<div style="font-weight: 500; font-size: 0.9rem; margin-bottom: 0.25rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${trackTitle}</div> <div style="font-weight: 500; font-size: 0.9rem; margin-bottom: 0.25rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${track.title}</div>
<div style="font-size: 0.8rem; color: var(--muted-foreground); margin-bottom: 0.5rem;">${track.artist?.name || 'Unknown'}</div> <div style="font-size: 0.8rem; color: var(--muted-foreground); margin-bottom: 0.5rem;">${track.artist?.name || 'Unknown'}</div>
<div class="download-progress-bar" style="height: 4px; background: var(--secondary); border-radius: 2px; overflow: hidden;"> <div class="download-progress-bar" style="height: 4px; background: var(--secondary); border-radius: 2px; overflow: hidden;">
<div class="download-progress-fill" style="width: 0%; height: 100%; background: var(--highlight); transition: width 0.2s;"></div> <div class="download-progress-fill" style="width: 0%; height: 100%; background: var(--highlight); transition: width 0.2s;"></div>
@ -187,9 +185,8 @@ async function downloadAlbumAsZip(album, tracks, api, quality) {
for (let i = 0; i < tracks.length; i++) { for (let i = 0; i < tracks.length; i++) {
const track = tracks[i]; const track = tracks[i];
const filename = buildTrackFilename(track, quality); const filename = buildTrackFilename(track, quality);
const trackTitle = getTrackTitle(track);
updateBulkDownloadProgress(notification, i, tracks.length, trackTitle); updateBulkDownloadProgress(notification, i, tracks.length, track.title);
const blob = await downloadTrackBlob(track, quality, api); const blob = await downloadTrackBlob(track, quality, api);
zip.file(`${folderName}/${filename}`, blob); zip.file(`${folderName}/${filename}`, blob);
@ -399,6 +396,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const lastfmToggleSetting = document.getElementById('lastfm-toggle-setting'); const lastfmToggleSetting = document.getElementById('lastfm-toggle-setting');
window.loadHomeFeed = loadHomeFeed; window.loadHomeFeed = loadHomeFeed;
function positionContextMenu(menu, x, y, preferLeft = false) { function positionContextMenu(menu, x, y, preferLeft = false) {
menu.style.display = 'block'; menu.style.display = 'block';
menu.style.visibility = 'hidden'; menu.style.visibility = 'hidden';
@ -781,7 +779,9 @@ document.addEventListener('DOMContentLoaded', async () => {
const html = currentQueue.map((track, index) => { const html = currentQueue.map((track, index) => {
const isPlaying = index === player.currentQueueIndex; const isPlaying = index === player.currentQueueIndex;
const trackTitle = getTrackTitle(track); const trackArtists = getTrackArtists(track, {
fallback: "Unknown"
});
return ` return `
<div class="queue-track-item ${isPlaying ? 'playing' : ''}" data-queue-index="${index}" data-track-id="${track.id}" draggable="true"> <div class="queue-track-item ${isPlaying ? 'playing' : ''}" data-queue-index="${index}" data-track-id="${track.id}" draggable="true">
@ -795,8 +795,8 @@ document.addEventListener('DOMContentLoaded', async () => {
<img src="${api.getCoverUrl(track.album?.cover, '80')}" <img src="${api.getCoverUrl(track.album?.cover, '80')}"
class="track-item-cover" loading="lazy"> class="track-item-cover" loading="lazy">
<div class="track-item-details"> <div class="track-item-details">
<div class="title">${trackTitle}</div> <div class="title">${track.title}</div>
<div class="artist">${track.artist?.name || 'Unknown'}</div> <div class="artist">${trackArtists}</div>
</div> </div>
</div> </div>
<div class="track-item-duration">${formatTime(track.duration)}</div> <div class="track-item-duration">${formatTime(track.duration)}</div>
@ -855,11 +855,11 @@ document.addEventListener('DOMContentLoaded', async () => {
function showQueueTrackMenu(e, trackIndex) { function showQueueTrackMenu(e, trackIndex) {
const menu = document.getElementById('queue-track-menu'); const menu = document.getElementById('queue-track-menu');
menu.style.top = `${e.pageY}px`;
menu.style.left = `${e.pageX}px`;
menu.classList.add('show'); menu.classList.add('show');
menu.dataset.trackIndex = trackIndex; menu.dataset.trackIndex = trackIndex;
positionContextMenu(menu, e.pageX, e.pageY, true); positionContextMenu(menu, e.pageX, e.pageY, true);
document.addEventListener('click', hideQueueTrackMenu); document.addEventListener('click', hideQueueTrackMenu);
} }
@ -892,7 +892,9 @@ document.addEventListener('DOMContentLoaded', async () => {
contextTrack = trackDataStore.get(trackItem); contextTrack = trackDataStore.get(trackItem);
if (contextTrack) { if (contextTrack) {
const rect = menuBtn.getBoundingClientRect(); const rect = menuBtn.getBoundingClientRect();
positionContextMenu(contextMenu, rect.left, rect.bottom + 5, true); contextMenu.style.top = `${rect.bottom + 5}px`;
contextMenu.style.left = `${rect.left}px`;
contextMenu.style.display = 'block';
} }
} }
return; return;
@ -922,7 +924,9 @@ document.addEventListener('DOMContentLoaded', async () => {
contextTrack = trackDataStore.get(trackItem); contextTrack = trackDataStore.get(trackItem);
if (contextTrack) { if (contextTrack) {
positionContextMenu(contextMenu, e.pageX, e.pageY, true); contextMenu.style.top = `${e.pageY}px`;
contextMenu.style.left = `${e.pageX}px`;
contextMenu.style.display = 'block';
} }
} }
}); });

View file

@ -1,5 +1,5 @@
//player.js //player.js
import { REPEAT_MODE, formatTime, getTrackTitle } from './utils.js'; import { REPEAT_MODE, formatTime, getTrackArtists } from './utils.js';
export class Player { export class Player {
constructor(audioElement, api, quality = 'LOSSLESS') { constructor(audioElement, api, quality = 'LOSSLESS') {
@ -99,7 +99,6 @@ export class Player {
for (const { track, index } of tracksToPreload) { for (const { track, index } of tracksToPreload) {
if (this.preloadCache.has(track.id)) continue; if (this.preloadCache.has(track.id)) continue;
const trackTitle = getTrackTitle(track);
try { try {
const streamUrl = await this.api.getStreamUrl(track.id, this.quality); const streamUrl = await this.api.getStreamUrl(track.id, this.quality);
@ -114,7 +113,7 @@ export class Player {
} catch (error) { } catch (error) {
if (error.name !== 'AbortError') { if (error.name !== 'AbortError') {
console.debug('Failed to get stream URL for preload:', trackTitle); console.debug('Failed to get stream URL for preload:', track.title);
} }
} }
} }
@ -129,13 +128,13 @@ async playTrackFromQueue() {
const track = currentQueue[this.currentQueueIndex]; const track = currentQueue[this.currentQueueIndex];
this.currentTrack = track; this.currentTrack = track;
const trackTitle = getTrackTitle(track); const trackArtists = getTrackArtists(track);
document.querySelector('.now-playing-bar .cover').src = document.querySelector('.now-playing-bar .cover').src =
this.api.getCoverUrl(track.album?.cover, '1280'); this.api.getCoverUrl(track.album?.cover, '1280');
document.querySelector('.now-playing-bar .title').textContent = trackTitle; document.querySelector('.now-playing-bar .title').textContent = track.title;
document.querySelector('.now-playing-bar .artist').textContent = track.artist?.name || 'Unknown Artist'; document.querySelector('.now-playing-bar .artist').textContent = trackArtists;
document.title = `${trackTitle}${track.artist?.name || 'Unknown'}`; document.title = `${track.title}${track.artist?.name || 'Unknown'}`;
this.updatePlayingTrackIndicator(); this.updatePlayingTrackIndicator();
this.updateMediaSession(track); this.updateMediaSession(track);
@ -184,8 +183,8 @@ async playTrackFromQueue() {
this.setupCrossfadeListener(); this.setupCrossfadeListener();
} catch (error) { } catch (error) {
console.error(`Could not play track: ${trackTitle}`, error); console.error(`Could not play track: ${track.title}`, error);
document.querySelector('.now-playing-bar .title').textContent = `Error: ${trackTitle}`; document.querySelector('.now-playing-bar .title').textContent = `Error: ${track.title}`;
document.querySelector('.now-playing-bar .artist').textContent = error.message || 'Could not load track'; document.querySelector('.now-playing-bar .artist').textContent = error.message || 'Could not load track';
} }
} }
@ -413,7 +412,6 @@ async playTrackFromQueue() {
const artwork = []; const artwork = [];
const sizes = ['1280']; const sizes = ['1280'];
const coverId = track.album?.cover; const coverId = track.album?.cover;
const trackTitle = getTrackTitle(track);
if (coverId) { if (coverId) {
sizes.forEach(size => { sizes.forEach(size => {
@ -426,7 +424,7 @@ async playTrackFromQueue() {
} }
navigator.mediaSession.metadata = new MediaMetadata({ navigator.mediaSession.metadata = new MediaMetadata({
title: trackTitle, title: track.title || 'Unknown Title',
artist: track.artist?.name || 'Unknown Artist', artist: track.artist?.name || 'Unknown Artist',
album: track.album?.title || 'Unknown Album', album: track.album?.title || 'Unknown Album',
artwork: artwork.length > 0 ? artwork : undefined artwork: artwork.length > 0 ? artwork : undefined
@ -471,4 +469,4 @@ applyNormalization() {
console.debug('Failed to update Media Session position:', error); console.debug('Failed to update Media Session position:', error);
} }
} }
} }

View file

@ -1,5 +1,5 @@
//ui.js //ui.js
import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackTitle } from './utils.js'; import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists } from './utils.js';
import { recentActivityManager } from './storage.js'; import { recentActivityManager } from './storage.js';
export class UIRenderer { export class UIRenderer {
@ -25,8 +25,8 @@ export class UIRenderer {
createTrackItemHTML(track, index, showCover = false) { createTrackItemHTML(track, index, showCover = false) {
const playIconSmall = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>'; const playIconSmall = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>';
const trackNumberHTML = `<div class="track-number">${showCover ? playIconSmall : index + 1}</div>`; const trackNumberHTML = `<div class="track-number">${showCover ? playIconSmall : index + 1}</div>`;
const explicitBadge = !hasExplicitContent(track) ? this.createExplicitBadge() : ''; const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : '';
const trackTitle = getTrackTitle(track); const trackArtists = getTrackArtists(track);
return ` return `
<div class="track-item" data-track-id="${track.id}"> <div class="track-item" data-track-id="${track.id}">
@ -35,7 +35,7 @@ export class UIRenderer {
${showCover ? `<img src="${this.api.getCoverUrl(track.album?.cover, '80')}" alt="Track Cover" class="track-item-cover" loading="lazy">` : ''} ${showCover ? `<img src="${this.api.getCoverUrl(track.album?.cover, '80')}" alt="Track Cover" class="track-item-cover" loading="lazy">` : ''}
<div class="track-item-details"> <div class="track-item-details">
<div class="title"> <div class="title">
${trackTitle} ${track.title}
${explicitBadge} ${explicitBadge}
</div> </div>
<div class="artist">${trackArtists}</div> <div class="artist">${trackArtists}</div>
@ -419,4 +419,4 @@ async renderHomePage() {
} }
}); });
} }
} }

View file

@ -70,7 +70,7 @@ export const buildTrackFilename = (track, quality) => {
const artistName = sanitizeForFilename(track.artist?.name); const artistName = sanitizeForFilename(track.artist?.name);
const albumTitle = sanitizeForFilename(track.album?.title); const albumTitle = sanitizeForFilename(track.album?.title);
const trackTitle = sanitizeForFilename(getTrackTitle(track)); const trackTitle = sanitizeForFilename(track.title);
return `${artistName} - ${albumTitle} - ${padded} ${trackTitle}.${extension}`; return `${artistName} - ${albumTitle} - ${padded} ${trackTitle}.${extension}`;
}; };
@ -157,7 +157,10 @@ export const debounce = (func, wait) => {
}; };
}; };
export const getTrackTitle = (track, { fallback = 'Unknown Title' } = {}) => { export const getTrackArtists = (track = {}, { fallback = 'Unknown Artist' } = {}) => {
if (!track?.title) return fallback; if (track?.artists?.length) {
return track?.version ? `${track.title} (${track.version})` : track.title; return track.artists.map(artist => artist?.name).join(', ');
}; }
return fallback;
}