add sub context menu for artists + add links to artists

This commit is contained in:
BlackSigkill 2026-02-20 10:49:56 +01:00
parent eddb202b1b
commit 8be0fa944b
4 changed files with 102 additions and 41 deletions

View file

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

View file

@ -1508,9 +1508,6 @@ async function updateContextMenuLikeState(contextMenu, contextTrack) {
// Handle multiple artists for "Go to artist"
const artistItem = contextMenu.querySelector('li[data-action="go-to-artist"]');
if (artistItem) {
// Remove any previously added multiple artist items
contextMenu.querySelectorAll('.dynamic-artist-item').forEach((i) => i.remove());
const artists = Array.isArray(contextTrack.artists)
? contextTrack.artists
: contextTrack.artist
@ -1519,35 +1516,14 @@ async function updateContextMenuLikeState(contextMenu, contextTrack) {
const canShowArtist = type === 'track' || type === 'album';
if (artists.length > 1 && canShowArtist) {
artistItem.style.display = 'none';
// Sort artists by name to be consistent
[...artists]
.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
.forEach((artist) => {
const li = document.createElement('li');
li.classList.add('dynamic-artist-item');
li.dataset.action = 'go-to-artist';
li.dataset.artistId = artist.id || '';
// Handle tracker/unreleased tracks
const isTracker =
contextTrack.isTracker || (contextTrack.id && String(contextTrack.id).startsWith('tracker-'));
if (isTracker && contextTrack.trackerInfo?.sheetId) {
li.dataset.trackerSheetId = contextTrack.trackerInfo.sheetId;
}
li.textContent = `Go to ${artist.name || 'Unknown Artist'}`;
artistItem.parentNode.insertBefore(li, artistItem.nextSibling);
});
artistItem.style.display = 'block';
artistItem.textContent = 'Go to artists';
artistItem.dataset.hasMultipleArtists = 'true';
} else {
// Restore default behavior for single artist
const hasArtist = artists.length > 0;
artistItem.style.display = hasArtist && canShowArtist ? 'block' : 'none';
if (hasArtist) {
artistItem.textContent = `Go to ${artists[0].name || 'artist'}`;
} else {
artistItem.textContent = 'Go to artist';
}
artistItem.dataset.hasMultipleArtists = 'false';
artistItem.textContent = artists.length > 1 ? 'Go to artists' : 'Go to artist';
delete artistItem.dataset.artistId;
delete artistItem.dataset.trackerSheetId;
}
@ -1614,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;
@ -1639,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);
@ -1659,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);
@ -1674,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)
@ -1714,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) => {
@ -1745,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');
@ -1756,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) => {
@ -1767,11 +1789,48 @@ 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]
.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
.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, 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 {