Merge pull request #217 from blacksigkill/feature/multi-artists

Feature : go to artist for multiartists tracks
This commit is contained in:
Eduard Prigoana 2026-02-20 12:49:46 +02:00 committed by GitHub
commit 40c4d8ab3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 128 additions and 11 deletions

View file

@ -2302,10 +2302,6 @@ document.addEventListener('DOMContentLoaded', async () => {
albumItem.textContent = `Go to ${label}`;
albumItem.style.display = track.album ? 'block' : 'none';
}
if (artistItem) {
const hasArtist = track.artist || (track.artists && track.artists.length > 0);
artistItem.style.display = hasArtist ? 'block' : 'none';
}
}
}
}

View file

@ -765,7 +765,8 @@ export async function handleTrackAction(
lyricsManager,
type = 'track',
ui = null,
scrobbler = null
scrobbler = null,
extraData = null
) {
if (!item) return;
@ -1171,8 +1172,12 @@ export async function handleTrackAction(
modal.classList.add('active');
} else if (action === 'go-to-artist') {
const artistId = item.artist?.id || item.artists?.[0]?.id;
if (artistId) {
const artistId = extraData?.artistId || item.artist?.id || item.artists?.[0]?.id;
const trackerSheetId = extraData?.trackerSheetId || (item.isTracker ? item.trackerInfo?.sheetId : null);
if (trackerSheetId) {
navigate(`/unreleased/${trackerSheetId}`);
} else if (artistId) {
navigate(`/artist/${artistId}`);
}
} else if (action === 'go-to-album') {
@ -1499,6 +1504,30 @@ async function updateContextMenuLikeState(contextMenu, contextTrack) {
item.textContent = label;
}
});
// Handle multiple artists for "Go to artist"
const artistItem = contextMenu.querySelector('li[data-action="go-to-artist"]');
if (artistItem) {
const artists = Array.isArray(contextTrack.artists)
? contextTrack.artists
: contextTrack.artist
? [contextTrack.artist]
: [];
const canShowArtist = type === 'track' || type === 'album';
if (artists.length > 1 && canShowArtist) {
artistItem.style.display = 'block';
artistItem.textContent = 'Go to artists';
artistItem.dataset.hasMultipleArtists = 'true';
} else {
const hasArtist = artists.length > 0;
artistItem.style.display = hasArtist && canShowArtist ? 'block' : 'none';
artistItem.dataset.hasMultipleArtists = 'false';
artistItem.textContent = artists.length > 1 ? 'Go to artists' : 'Go to artist';
delete artistItem.dataset.artistId;
delete artistItem.dataset.trackerSheetId;
}
}
}
export function initializeTrackInteractions(player, api, mainContent, contextMenu, lyricsManager, ui, scrobbler) {
@ -1561,6 +1590,11 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
item = { id, uuid: id, title: card.querySelector('.card-title')?.textContent || 'Item' };
}
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
contextMenu._originalHTML = null;
}
contextTrack = item;
contextMenu._contextTrack = item;
contextMenu._contextType = type;
@ -1586,12 +1620,21 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
clickedTrack &&
contextTrack.id === clickedTrack.id
) {
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
}
contextMenu.style.display = 'none';
contextMenu._contextType = null;
contextMenu._originalHTML = null;
return;
}
contextTrack = clickedTrack;
if (contextTrack) {
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
contextMenu._originalHTML = null;
}
contextMenu._contextTrack = contextTrack;
contextMenu._contextType = 'track';
await updateContextMenuLikeState(contextMenu, contextTrack);
@ -1606,7 +1649,7 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
if (trackItem && (trackItem.classList.contains('unavailable') || trackItem.classList.contains('blocked'))) {
return;
}
if (trackItem && !trackItem.dataset.queueIndex && !e.target.closest('.remove-from-playlist-btn')) {
if (trackItem && !trackItem.dataset.queueIndex && !e.target.closest('.remove-from-playlist-btn') && !e.target.closest('.artist-link')) {
const parentList = trackItem.closest('.track-list');
const allTrackElements = Array.from(parentList.querySelectorAll('.track-item'));
const trackList = allTrackElements.map((el) => trackDataStore.get(el)).filter(Boolean);
@ -1621,6 +1664,20 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
}
}
// Handle artist link clicks in track lists
const artistLink = e.target.closest('.artist-link');
if (artistLink) {
e.stopPropagation();
const artistId = artistLink.dataset.artistId;
const trackerSheetId = artistLink.dataset.trackerSheetId;
if (trackerSheetId) {
navigate(`/unreleased/${trackerSheetId}`);
} else if (artistId) {
navigate(`/artist/${artistId}`);
}
return;
}
const card = e.target.closest('.card');
if (card) {
// Don't navigate if card is blocked (unless clicking menu button)
@ -1661,6 +1718,11 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
if (contextTrack) {
if (contextTrack.isLocal) return;
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
contextMenu._originalHTML = null;
}
// Hide actions for unavailable tracks
const unavailableActions = ['play-next', 'add-to-queue', 'download', 'track-mix'];
contextMenu.querySelectorAll('[data-action]').forEach((btn) => {
@ -1692,6 +1754,12 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
uuid: id,
title: card.querySelector('.card-title')?.textContent,
};
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
contextMenu._originalHTML = null;
}
contextTrack = item;
contextMenu._contextTrack = item;
contextMenu._contextType = type.replace('userplaylist', 'user-playlist');
@ -1703,7 +1771,14 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
});
document.addEventListener('click', () => {
contextMenu.style.display = 'none';
if (contextMenu.style.display === 'block') {
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
}
contextMenu.style.display = 'none';
contextMenu._contextType = null;
contextMenu._originalHTML = null;
}
});
contextMenu.addEventListener('click', async (e) => {
@ -1714,10 +1789,45 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
const action = target.dataset.action;
const track = contextMenu._contextTrack || contextTrack;
const type = contextMenu._contextType || 'track';
if (action === 'go-to-artists' || (action === 'go-to-artist' && target.dataset.hasMultipleArtists === 'true')) {
const artists = Array.isArray(track.artists) ? track.artists : track.artist ? [track.artist] : [];
if (artists.length > 1) {
// Save original HTML if not already saved
if (!contextMenu._originalHTML) {
contextMenu._originalHTML = contextMenu.innerHTML;
}
// Render sub-menu
let subMenuHTML = '<li data-action="back-to-main-menu" style="font-weight: bold; border-bottom: 1px solid var(--border); margin-bottom: 0.5rem; padding: 0.75rem 1rem; cursor: pointer;">← Back</li>';
artists.forEach((artist) => {
subMenuHTML += `<li data-action="go-to-artist" data-artist-id="${artist.id}" style="padding: 0.75rem 1rem; cursor: pointer;">${escapeHtml(artist.name || 'Unknown Artist')}</li>`;
});
contextMenu.innerHTML = `<ul>${subMenuHTML}</ul>`;
return;
}
}
if (action === 'back-to-main-menu') {
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
contextMenu._originalHTML = null;
// Re-update like state since we replaced the HTML
await updateContextMenuLikeState(contextMenu, track);
}
return;
}
if (action && track) {
// Track context menu action
trackContextMenuAction(action, type, track);
await handleTrackAction(action, track, player, api, lyricsManager, type, ui, scrobbler);
await handleTrackAction(action, track, player, api, lyricsManager, type, ui, scrobbler, target.dataset);
}
// Reset menu state before closing
if (contextMenu._originalHTML) {
contextMenu.innerHTML = contextMenu._originalHTML;
contextMenu._originalHTML = null;
}
contextMenu.style.display = 'none';
contextMenu._contextType = null;

View file

@ -12,6 +12,7 @@ import {
trackDataStore,
hasExplicitContent,
getTrackArtists,
getTrackArtistsHTML,
getTrackTitle,
getTrackYearDisplay,
createQualityBadgeHTML,
@ -371,7 +372,7 @@ export class UIRenderer {
${explicitBadge}
${qualityBadge}
</div>
<div class="artist">${escapeHtml(trackArtists)}${yearDisplay}</div>
<div class="artist">${getTrackArtistsHTML(track)}${yearDisplay}</div>
</div>
</div>
<div class="track-item-duration">${isUnavailable || isBlocked ? '--:--' : track.duration ? formatTime(track.duration) : '--:--'}</div>

View file

@ -4866,6 +4866,16 @@ input:checked + .slider::before {
text-decoration: underline;
}
.track-item .artist .artist-link {
cursor: pointer;
transition: color var(--transition);
}
.track-item .artist .artist-link:hover {
color: var(--highlight);
text-decoration: underline;
}
/* Updated Volume Controls Layout */
.player-actions-row {