FIX: add to queue option from three dot menu
This commit is contained in:
parent
60d53cf87b
commit
c3041a81fd
7 changed files with 16 additions and 116 deletions
13
index.html
13
index.html
|
|
@ -2100,18 +2100,7 @@
|
||||||
<option value="lyrics">Lyrics Panel</option>
|
<option value="lyrics">Lyrics Panel</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
|
||||||
<div class="info">
|
|
||||||
<span class="label">Track List Actions</span>
|
|
||||||
<span class="description"
|
|
||||||
>Choose between a dropdown menu or inline buttons for track actions</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<select id="track-list-actions-mode">
|
|
||||||
<option value="dropdown">Dropdown Menu</option>
|
|
||||||
<option value="inline">Inline Buttons</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<span class="label">Compact Artists</span>
|
<span class="label">Compact Artists</span>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
apiSettings,
|
apiSettings,
|
||||||
themeManager,
|
themeManager,
|
||||||
nowPlayingSettings,
|
nowPlayingSettings,
|
||||||
trackListSettings,
|
|
||||||
downloadQualitySettings,
|
downloadQualitySettings,
|
||||||
} from './storage.js';
|
} from './storage.js';
|
||||||
import { UIRenderer } from './ui.js';
|
import { UIRenderer } from './ui.js';
|
||||||
|
|
@ -318,7 +317,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
|
||||||
const currentTheme = themeManager.getTheme();
|
const currentTheme = themeManager.getTheme();
|
||||||
themeManager.setTheme(currentTheme);
|
themeManager.setTheme(currentTheme);
|
||||||
trackListSettings.getMode();
|
|
||||||
|
|
||||||
initializeSettings(scrobbler, player, api, ui);
|
initializeSettings(scrobbler, player, api, ui);
|
||||||
initializePlayerEvents(player, audioPlayer, scrobbler, ui);
|
initializePlayerEvents(player, audioPlayer, scrobbler, ui);
|
||||||
|
|
|
||||||
|
|
@ -1052,6 +1052,8 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
|
|
||||||
contextTrack = clickedTrack;
|
contextTrack = clickedTrack;
|
||||||
if (contextTrack) {
|
if (contextTrack) {
|
||||||
|
contextMenu._contextTrack = contextTrack;
|
||||||
|
contextMenu._contextType = 'track';
|
||||||
await updateContextMenuLikeState(contextMenu, contextTrack);
|
await updateContextMenuLikeState(contextMenu, contextTrack);
|
||||||
const rect = menuBtn.getBoundingClientRect();
|
const rect = menuBtn.getBoundingClientRect();
|
||||||
positionMenu(contextMenu, rect.left, rect.bottom + 5, rect);
|
positionMenu(contextMenu, rect.left, rect.bottom + 5, rect);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
nowPlayingSettings,
|
nowPlayingSettings,
|
||||||
lyricsSettings,
|
lyricsSettings,
|
||||||
backgroundSettings,
|
backgroundSettings,
|
||||||
trackListSettings,
|
|
||||||
cardSettings,
|
cardSettings,
|
||||||
waveformSettings,
|
waveformSettings,
|
||||||
replayGainSettings,
|
replayGainSettings,
|
||||||
|
|
@ -335,14 +334,7 @@ export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track List Actions Mode
|
|
||||||
const trackListActionsMode = document.getElementById('track-list-actions-mode');
|
|
||||||
if (trackListActionsMode) {
|
|
||||||
trackListActionsMode.value = trackListSettings.getMode();
|
|
||||||
trackListActionsMode.addEventListener('change', (e) => {
|
|
||||||
trackListSettings.setMode(e.target.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compact Artist Toggle
|
// Compact Artist Toggle
|
||||||
const compactArtistToggle = document.getElementById('compact-artist-toggle');
|
const compactArtistToggle = document.getElementById('compact-artist-toggle');
|
||||||
|
|
|
||||||
|
|
@ -461,25 +461,7 @@ export const backgroundSettings = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const trackListSettings = {
|
|
||||||
STORAGE_KEY: 'track-list-actions-mode',
|
|
||||||
|
|
||||||
getMode() {
|
|
||||||
try {
|
|
||||||
let mode = localStorage.getItem(this.STORAGE_KEY) || 'dropdown';
|
|
||||||
if (mode === 'inline') mode = 'dropdown';
|
|
||||||
document.documentElement.setAttribute('data-track-actions-mode', mode);
|
|
||||||
return mode;
|
|
||||||
} catch {
|
|
||||||
return 'dropdown';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setMode(mode) {
|
|
||||||
localStorage.setItem(this.STORAGE_KEY, mode);
|
|
||||||
document.documentElement.setAttribute('data-track-actions-mode', mode);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const cardSettings = {
|
export const cardSettings = {
|
||||||
COMPACT_ARTIST_KEY: 'card-compact-artist',
|
COMPACT_ARTIST_KEY: 'card-compact-artist',
|
||||||
|
|
|
||||||
50
js/ui.js
50
js/ui.js
|
|
@ -224,37 +224,6 @@ export class UIRenderer {
|
||||||
const actionsHTML = isUnavailable
|
const actionsHTML = isUnavailable
|
||||||
? ''
|
? ''
|
||||||
: `
|
: `
|
||||||
<div class="track-actions-inline">
|
|
||||||
<button class="track-action-btn like-btn" data-action="toggle-like" title="Add to Liked">
|
|
||||||
${this.createHeartIcon(false)}
|
|
||||||
</button>
|
|
||||||
<button class="track-action-btn" data-action="play-next" title="Play Next">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M2 6h6" />
|
|
||||||
<path d="M5 3v6" />
|
|
||||||
<path d="M11 6h10" />
|
|
||||||
<path d="M3 12h18" />
|
|
||||||
<path d="M3 18h18" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="track-action-btn" data-action="add-to-queue" title="Add to Queue">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<path d="M3 6h18" />
|
|
||||||
<path d="M3 12h18" />
|
|
||||||
<path d="M3 18h10" />
|
|
||||||
<path d="M16 18h6" />
|
|
||||||
<path d="M19 15v6" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="track-action-btn" data-action="share" title="Share">
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="track-action-btn" data-action="download" title="Download" ${track.isLocal ? 'style="display:none"' : ''}>
|
|
||||||
${SVG_DOWNLOAD}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button class="track-menu-btn" type="button" title="More options" ${track.isLocal ? 'style="display:none"' : ''}>
|
<button class="track-menu-btn" type="button" title="More options" ${track.isLocal ? 'style="display:none"' : ''}>
|
||||||
${SVG_MENU}
|
${SVG_MENU}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -558,21 +527,22 @@ export class UIRenderer {
|
||||||
.map((track, i) => this.createTrackItemHTML(track, i, showCover, hasMultipleDiscs, useTrackNumber))
|
.map((track, i) => this.createTrackItemHTML(track, i, showCover, hasMultipleDiscs, useTrackNumber))
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
|
// Bind data to elements immediately using index, avoiding selector ambiguity
|
||||||
|
Array.from(tempDiv.children).forEach((element, index) => {
|
||||||
|
const track = tracks[index];
|
||||||
|
if (element && track) {
|
||||||
|
trackDataStore.set(element, track);
|
||||||
|
// Async update for like button
|
||||||
|
this.updateLikeState(element, 'track', track.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
while (tempDiv.firstChild) {
|
while (tempDiv.firstChild) {
|
||||||
fragment.appendChild(tempDiv.firstChild);
|
fragment.appendChild(tempDiv.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!append) container.innerHTML = '';
|
if (!append) container.innerHTML = '';
|
||||||
container.appendChild(fragment);
|
container.appendChild(fragment);
|
||||||
|
|
||||||
tracks.forEach((track) => {
|
|
||||||
const element = container.querySelector(`[data-track-id="${track.id}"]`);
|
|
||||||
if (element) {
|
|
||||||
trackDataStore.set(element, track);
|
|
||||||
// Async update for like button
|
|
||||||
this.updateLikeState(element, 'track', track.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setPageBackground(imageUrl) {
|
setPageBackground(imageUrl) {
|
||||||
|
|
|
||||||
37
styles.css
37
styles.css
|
|
@ -1234,19 +1234,13 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
transition: all var(--transition);
|
transition: all var(--transition);
|
||||||
display: none;
|
display: flex;
|
||||||
|
|
||||||
/* Controlled by data-track-actions-mode */
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-track-actions-mode='dropdown'] .track-menu-btn {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item:hover .track-menu-btn {
|
.track-item:hover .track-menu-btn {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
@ -1258,34 +1252,10 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-actions-inline {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
/* Controlled by data-track-actions-mode */
|
|
||||||
gap: 0.25rem;
|
|
||||||
opacity: 0.2;
|
|
||||||
transition: opacity var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-action-btn.active {
|
.track-action-btn.active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-track-actions-mode='inline'] .track-actions-inline {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root[data-track-actions-mode='inline'] .track-list-header,
|
|
||||||
:root[data-track-actions-mode='inline'] .track-item,
|
|
||||||
:root[data-track-actions-mode='inline'] .skeleton-track,
|
|
||||||
:root[data-track-actions-mode='inline'] #playlist-detail-tracklist .track-list-header {
|
|
||||||
grid-template-columns: 40px 1fr 80px 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item:hover .track-actions-inline {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-action-btn {
|
.track-action-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -1313,10 +1283,7 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
grid-template-columns: 40px 1fr 80px 90px;
|
grid-template-columns: 40px 1fr 80px 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-track-actions-mode='inline'] .is-editable .track-list-header,
|
|
||||||
:root[data-track-actions-mode='inline'] .is-editable .track-item {
|
|
||||||
grid-template-columns: 40px 1fr 80px 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue