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.textContent = `Go to ${label}`;
|
||||||
albumItem.style.display = track.album ? 'block' : 'none';
|
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"
|
// Handle multiple artists for "Go to artist"
|
||||||
const artistItem = contextMenu.querySelector('li[data-action="go-to-artist"]');
|
const artistItem = contextMenu.querySelector('li[data-action="go-to-artist"]');
|
||||||
if (artistItem) {
|
if (artistItem) {
|
||||||
// Remove any previously added multiple artist items
|
|
||||||
contextMenu.querySelectorAll('.dynamic-artist-item').forEach((i) => i.remove());
|
|
||||||
|
|
||||||
const artists = Array.isArray(contextTrack.artists)
|
const artists = Array.isArray(contextTrack.artists)
|
||||||
? contextTrack.artists
|
? contextTrack.artists
|
||||||
: contextTrack.artist
|
: contextTrack.artist
|
||||||
|
|
@ -1519,35 +1516,14 @@ async function updateContextMenuLikeState(contextMenu, contextTrack) {
|
||||||
const canShowArtist = type === 'track' || type === 'album';
|
const canShowArtist = type === 'track' || type === 'album';
|
||||||
|
|
||||||
if (artists.length > 1 && canShowArtist) {
|
if (artists.length > 1 && canShowArtist) {
|
||||||
artistItem.style.display = 'none';
|
artistItem.style.display = 'block';
|
||||||
// Sort artists by name to be consistent
|
artistItem.textContent = 'Go to artists';
|
||||||
[...artists]
|
artistItem.dataset.hasMultipleArtists = 'true';
|
||||||
.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);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Restore default behavior for single artist
|
|
||||||
const hasArtist = artists.length > 0;
|
const hasArtist = artists.length > 0;
|
||||||
artistItem.style.display = hasArtist && canShowArtist ? 'block' : 'none';
|
artistItem.style.display = hasArtist && canShowArtist ? 'block' : 'none';
|
||||||
if (hasArtist) {
|
artistItem.dataset.hasMultipleArtists = 'false';
|
||||||
artistItem.textContent = `Go to ${artists[0].name || 'artist'}`;
|
artistItem.textContent = artists.length > 1 ? 'Go to artists' : 'Go to artist';
|
||||||
} else {
|
|
||||||
artistItem.textContent = 'Go to artist';
|
|
||||||
}
|
|
||||||
delete artistItem.dataset.artistId;
|
delete artistItem.dataset.artistId;
|
||||||
delete artistItem.dataset.trackerSheetId;
|
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' };
|
item = { id, uuid: id, title: card.querySelector('.card-title')?.textContent || 'Item' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contextMenu._originalHTML) {
|
||||||
|
contextMenu.innerHTML = contextMenu._originalHTML;
|
||||||
|
contextMenu._originalHTML = null;
|
||||||
|
}
|
||||||
|
|
||||||
contextTrack = item;
|
contextTrack = item;
|
||||||
contextMenu._contextTrack = item;
|
contextMenu._contextTrack = item;
|
||||||
contextMenu._contextType = type;
|
contextMenu._contextType = type;
|
||||||
|
|
@ -1639,12 +1620,21 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
clickedTrack &&
|
clickedTrack &&
|
||||||
contextTrack.id === clickedTrack.id
|
contextTrack.id === clickedTrack.id
|
||||||
) {
|
) {
|
||||||
|
if (contextMenu._originalHTML) {
|
||||||
|
contextMenu.innerHTML = contextMenu._originalHTML;
|
||||||
|
}
|
||||||
contextMenu.style.display = 'none';
|
contextMenu.style.display = 'none';
|
||||||
|
contextMenu._contextType = null;
|
||||||
|
contextMenu._originalHTML = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
contextTrack = clickedTrack;
|
contextTrack = clickedTrack;
|
||||||
if (contextTrack) {
|
if (contextTrack) {
|
||||||
|
if (contextMenu._originalHTML) {
|
||||||
|
contextMenu.innerHTML = contextMenu._originalHTML;
|
||||||
|
contextMenu._originalHTML = null;
|
||||||
|
}
|
||||||
contextMenu._contextTrack = contextTrack;
|
contextMenu._contextTrack = contextTrack;
|
||||||
contextMenu._contextType = 'track';
|
contextMenu._contextType = 'track';
|
||||||
await updateContextMenuLikeState(contextMenu, contextTrack);
|
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'))) {
|
if (trackItem && (trackItem.classList.contains('unavailable') || trackItem.classList.contains('blocked'))) {
|
||||||
return;
|
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 parentList = trackItem.closest('.track-list');
|
||||||
const allTrackElements = Array.from(parentList.querySelectorAll('.track-item'));
|
const allTrackElements = Array.from(parentList.querySelectorAll('.track-item'));
|
||||||
const trackList = allTrackElements.map((el) => trackDataStore.get(el)).filter(Boolean);
|
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');
|
const card = e.target.closest('.card');
|
||||||
if (card) {
|
if (card) {
|
||||||
// Don't navigate if card is blocked (unless clicking menu button)
|
// 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) {
|
||||||
if (contextTrack.isLocal) return;
|
if (contextTrack.isLocal) return;
|
||||||
|
|
||||||
|
if (contextMenu._originalHTML) {
|
||||||
|
contextMenu.innerHTML = contextMenu._originalHTML;
|
||||||
|
contextMenu._originalHTML = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Hide actions for unavailable tracks
|
// Hide actions for unavailable tracks
|
||||||
const unavailableActions = ['play-next', 'add-to-queue', 'download', 'track-mix'];
|
const unavailableActions = ['play-next', 'add-to-queue', 'download', 'track-mix'];
|
||||||
contextMenu.querySelectorAll('[data-action]').forEach((btn) => {
|
contextMenu.querySelectorAll('[data-action]').forEach((btn) => {
|
||||||
|
|
@ -1745,6 +1754,12 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
uuid: id,
|
uuid: id,
|
||||||
title: card.querySelector('.card-title')?.textContent,
|
title: card.querySelector('.card-title')?.textContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (contextMenu._originalHTML) {
|
||||||
|
contextMenu.innerHTML = contextMenu._originalHTML;
|
||||||
|
contextMenu._originalHTML = null;
|
||||||
|
}
|
||||||
|
|
||||||
contextTrack = item;
|
contextTrack = item;
|
||||||
contextMenu._contextTrack = item;
|
contextMenu._contextTrack = item;
|
||||||
contextMenu._contextType = type.replace('userplaylist', 'user-playlist');
|
contextMenu._contextType = type.replace('userplaylist', 'user-playlist');
|
||||||
|
|
@ -1756,7 +1771,14 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', () => {
|
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) => {
|
contextMenu.addEventListener('click', async (e) => {
|
||||||
|
|
@ -1767,11 +1789,48 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
const action = target.dataset.action;
|
const action = target.dataset.action;
|
||||||
const track = contextMenu._contextTrack || contextTrack;
|
const track = contextMenu._contextTrack || contextTrack;
|
||||||
const type = contextMenu._contextType || 'track';
|
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) {
|
if (action && track) {
|
||||||
// Track context menu action
|
// Track context menu action
|
||||||
trackContextMenuAction(action, type, track);
|
trackContextMenuAction(action, type, track);
|
||||||
await handleTrackAction(action, track, player, api, lyricsManager, type, ui, scrobbler, target.dataset);
|
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.style.display = 'none';
|
||||||
contextMenu._contextType = null;
|
contextMenu._contextType = null;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
3
js/ui.js
3
js/ui.js
|
|
@ -12,6 +12,7 @@ import {
|
||||||
trackDataStore,
|
trackDataStore,
|
||||||
hasExplicitContent,
|
hasExplicitContent,
|
||||||
getTrackArtists,
|
getTrackArtists,
|
||||||
|
getTrackArtistsHTML,
|
||||||
getTrackTitle,
|
getTrackTitle,
|
||||||
getTrackYearDisplay,
|
getTrackYearDisplay,
|
||||||
createQualityBadgeHTML,
|
createQualityBadgeHTML,
|
||||||
|
|
@ -371,7 +372,7 @@ export class UIRenderer {
|
||||||
${explicitBadge}
|
${explicitBadge}
|
||||||
${qualityBadge}
|
${qualityBadge}
|
||||||
</div>
|
</div>
|
||||||
<div class="artist">${escapeHtml(trackArtists)}${yearDisplay}</div>
|
<div class="artist">${getTrackArtistsHTML(track)}${yearDisplay}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-item-duration">${isUnavailable || isBlocked ? '--:--' : track.duration ? formatTime(track.duration) : '--:--'}</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;
|
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 */
|
/* Updated Volume Controls Layout */
|
||||||
|
|
||||||
.player-actions-row {
|
.player-actions-row {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue