add sub context menu for artists + add links to artists
This commit is contained in:
parent
eddb202b1b
commit
8be0fa944b
4 changed files with 102 additions and 41 deletions
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
121
js/events.js
121
js/events.js
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
3
js/ui.js
3
js/ui.js
|
|
@ -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>
|
||||
|
|
|
|||
10
styles.css
10
styles.css
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue