feat: add play next functionality, inline track actions, and notifications
This commit is contained in:
parent
3db7a04971
commit
15315ab0c8
9 changed files with 253 additions and 36 deletions
11
index.html
11
index.html
|
|
@ -22,6 +22,7 @@
|
||||||
<audio id="audio-player"></audio>
|
<audio id="audio-player"></audio>
|
||||||
<div id="context-menu">
|
<div id="context-menu">
|
||||||
<ul>
|
<ul>
|
||||||
|
<li data-action="play-next">Play Next</li>
|
||||||
<li data-action="add-to-queue">Add to Queue</li>
|
<li data-action="add-to-queue">Add to Queue</li>
|
||||||
<li data-action="download">Download</li>
|
<li data-action="download">Download</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -324,6 +325,16 @@
|
||||||
<option value="karaoke">Karaoke Mode</option>
|
<option value="karaoke">Karaoke Mode</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">Download Lyrics</span>
|
<span class="label">Download Lyrics</span>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
//js/app.js
|
//js/app.js
|
||||||
import { LosslessAPI } from './api.js';
|
import { LosslessAPI } from './api.js';
|
||||||
import { apiSettings, themeManager, nowPlayingSettings } from './storage.js';
|
import { apiSettings, themeManager, nowPlayingSettings, trackListSettings } from './storage.js';
|
||||||
import { UIRenderer } from './ui.js';
|
import { UIRenderer } from './ui.js';
|
||||||
import { Player } from './player.js';
|
import { Player } from './player.js';
|
||||||
import { LastFMScrobbler } from './lastfm.js';
|
import { LastFMScrobbler } from './lastfm.js';
|
||||||
|
|
@ -202,6 +202,7 @@ 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);
|
initializePlayerEvents(player, audioPlayer, scrobbler);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,27 @@ function createDownloadNotification() {
|
||||||
return downloadNotificationContainer;
|
return downloadNotificationContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showNotification(message) {
|
||||||
|
const container = createDownloadNotification();
|
||||||
|
|
||||||
|
const notifEl = document.createElement('div');
|
||||||
|
notifEl.className = 'download-task';
|
||||||
|
|
||||||
|
notifEl.innerHTML = `
|
||||||
|
<div style="display: flex; align-items: start;">
|
||||||
|
${message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(notifEl);
|
||||||
|
|
||||||
|
// Auto remove
|
||||||
|
setTimeout(() => {
|
||||||
|
notifEl.style.animation = 'slideOut 0.3s ease';
|
||||||
|
setTimeout(() => notifEl.remove(), 300);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
export function addDownloadTask(trackId, track, filename, api) {
|
export function addDownloadTask(trackId, track, filename, api) {
|
||||||
const container = createDownloadNotification();
|
const container = createDownloadNotification();
|
||||||
|
|
||||||
|
|
|
||||||
62
js/events.js
62
js/events.js
|
|
@ -1,7 +1,7 @@
|
||||||
//js/events.js
|
//js/events.js
|
||||||
import { SVG_PLAY, SVG_PAUSE, SVG_VOLUME, SVG_MUTE, REPEAT_MODE, trackDataStore, RATE_LIMIT_ERROR_MESSAGE, buildTrackFilename } from './utils.js';
|
import { SVG_PLAY, SVG_PAUSE, SVG_VOLUME, SVG_MUTE, REPEAT_MODE, trackDataStore, RATE_LIMIT_ERROR_MESSAGE, buildTrackFilename } from './utils.js';
|
||||||
import { lastFMStorage } from './storage.js';
|
import { lastFMStorage } from './storage.js';
|
||||||
import { addDownloadTask, updateDownloadProgress, completeDownloadTask, downloadTrackWithMetadata } from './downloads.js';
|
import { addDownloadTask, updateDownloadProgress, completeDownloadTask, showNotification, downloadTrackWithMetadata } from './downloads.js';
|
||||||
import { lyricsSettings } from './storage.js';
|
import { lyricsSettings } from './storage.js';
|
||||||
import { updateTabTitle } from './router.js';
|
import { updateTabTitle } from './router.js';
|
||||||
|
|
||||||
|
|
@ -283,6 +283,29 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
let contextTrack = null;
|
let contextTrack = null;
|
||||||
|
|
||||||
mainContent.addEventListener('click', e => {
|
mainContent.addEventListener('click', e => {
|
||||||
|
const actionBtn = e.target.closest('.track-action-btn');
|
||||||
|
if (actionBtn) {
|
||||||
|
e.stopPropagation();
|
||||||
|
const trackItem = actionBtn.closest('.track-item');
|
||||||
|
if (trackItem) {
|
||||||
|
const track = trackDataStore.get(trackItem);
|
||||||
|
const action = actionBtn.dataset.action;
|
||||||
|
|
||||||
|
if (action === 'add-to-queue' && track) {
|
||||||
|
player.addToQueue(track);
|
||||||
|
renderQueue(player);
|
||||||
|
showNotification(`Added to queue: ${track.title}`);
|
||||||
|
} else if (action === 'play-next' && track) {
|
||||||
|
player.addNextToQueue(track);
|
||||||
|
renderQueue(player);
|
||||||
|
showNotification(`Playing next: ${track.title}`);
|
||||||
|
} else if (action === 'download' && track) {
|
||||||
|
handleDownload(track, player, api);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const menuBtn = e.target.closest('.track-menu-btn');
|
const menuBtn = e.target.closest('.track-menu-btn');
|
||||||
if (menuBtn) {
|
if (menuBtn) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -334,9 +357,14 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const action = e.target.dataset.action;
|
const action = e.target.dataset.action;
|
||||||
|
|
||||||
if (action === 'add-to-queue' && contextTrack) {
|
if (action === 'play-next' && contextTrack) {
|
||||||
|
player.addNextToQueue(contextTrack);
|
||||||
|
renderQueue(player);
|
||||||
|
showNotification(`Playing next: ${contextTrack.title}`);
|
||||||
|
} else if (action === 'add-to-queue' && contextTrack) {
|
||||||
player.addToQueue(contextTrack);
|
player.addToQueue(contextTrack);
|
||||||
renderQueue(player);
|
renderQueue(player);
|
||||||
|
showNotification(`Added to queue: ${contextTrack.title}`);
|
||||||
} else if (action === 'download' && contextTrack) {
|
} else if (action === 'download' && contextTrack) {
|
||||||
await downloadTrackWithMetadata(contextTrack, player.quality, api, lyricsManager);
|
await downloadTrackWithMetadata(contextTrack, player.quality, api, lyricsManager);
|
||||||
}
|
}
|
||||||
|
|
@ -412,3 +440,33 @@ function positionMenu(menu, x, y, anchorRect = null) {
|
||||||
menu.style.left = `${left}px`;
|
menu.style.left = `${left}px`;
|
||||||
menu.style.visibility = 'visible';
|
menu.style.visibility = 'visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleDownload(track, player, api) {
|
||||||
|
const quality = player.quality;
|
||||||
|
const filename = buildTrackFilename(track, quality);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { taskEl, abortController } = addDownloadTask(
|
||||||
|
track.id,
|
||||||
|
track,
|
||||||
|
filename,
|
||||||
|
api
|
||||||
|
);
|
||||||
|
|
||||||
|
await api.downloadTrack(track.id, quality, filename, {
|
||||||
|
signal: abortController.signal,
|
||||||
|
onProgress: (progress) => {
|
||||||
|
updateDownloadProgress(track.id, progress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
completeDownloadTask(track.id, true);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
const errorMsg = error.message === RATE_LIMIT_ERROR_MESSAGE
|
||||||
|
? error.message
|
||||||
|
: 'Download failed. Please try again.';
|
||||||
|
completeDownloadTask(track.id, false, errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
20
js/player.js
20
js/player.js
|
|
@ -322,6 +322,26 @@ export class Player {
|
||||||
this.saveQueueState();
|
this.saveQueueState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addNextToQueue(track) {
|
||||||
|
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
|
||||||
|
const insertIndex = this.currentQueueIndex + 1;
|
||||||
|
|
||||||
|
// Insert after current track
|
||||||
|
currentQueue.splice(insertIndex, 0, track);
|
||||||
|
|
||||||
|
// If we are shuffling, we might want to also add it to the original queue for consistency,
|
||||||
|
// though syncing that is tricky. The standard logic often just appends to the active queue view.
|
||||||
|
if (this.shuffleActive) {
|
||||||
|
this.originalQueueBeforeShuffle.push(track); // Just append to end of main list? Or logic needed.
|
||||||
|
// Simplest is to just modify the active playing queue.
|
||||||
|
} else {
|
||||||
|
// In linear mode, `currentQueue` IS `this.queue`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveQueueState();
|
||||||
|
this.preloadNextTracks(); // Update preload since next track changed
|
||||||
|
}
|
||||||
|
|
||||||
removeFromQueue(index) {
|
removeFromQueue(index) {
|
||||||
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
|
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//js/settings
|
//js/settings
|
||||||
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings, backgroundSettings } from './storage.js';
|
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings, backgroundSettings, trackListSettings } from './storage.js';
|
||||||
|
|
||||||
export function initializeSettings(scrobbler, player, api, ui) {
|
export function initializeSettings(scrobbler, player, api, ui) {
|
||||||
const lastfmConnectBtn = document.getElementById('lastfm-connect-btn');
|
const lastfmConnectBtn = document.getElementById('lastfm-connect-btn');
|
||||||
|
|
@ -176,6 +176,15 @@ 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Download Lyrics Toggle
|
// Download Lyrics Toggle
|
||||||
const downloadLyricsToggle = document.getElementById('download-lyrics-toggle');
|
const downloadLyricsToggle = document.getElementById('download-lyrics-toggle');
|
||||||
if (downloadLyricsToggle) {
|
if (downloadLyricsToggle) {
|
||||||
|
|
|
||||||
|
|
@ -349,6 +349,25 @@ export const backgroundSettings = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const trackListSettings = {
|
||||||
|
STORAGE_KEY: 'track-list-actions-mode',
|
||||||
|
|
||||||
|
getMode() {
|
||||||
|
try {
|
||||||
|
const mode = localStorage.getItem(this.STORAGE_KEY) || 'dropdown';
|
||||||
|
document.documentElement.setAttribute('data-track-actions-mode', mode);
|
||||||
|
return mode;
|
||||||
|
} catch (e) {
|
||||||
|
return 'dropdown';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setMode(mode) {
|
||||||
|
localStorage.setItem(this.STORAGE_KEY, mode);
|
||||||
|
document.documentElement.setAttribute('data-track-actions-mode', mode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const queueManager = {
|
export const queueManager = {
|
||||||
STORAGE_KEY: 'monochrome-queue',
|
STORAGE_KEY: 'monochrome-queue',
|
||||||
|
|
||||||
|
|
|
||||||
49
js/ui.js
49
js/ui.js
|
|
@ -1,6 +1,6 @@
|
||||||
//js/ui.js
|
//js/ui.js
|
||||||
import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js';
|
import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js';
|
||||||
import { recentActivityManager, backgroundSettings } from './storage.js';
|
import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js';
|
||||||
|
|
||||||
export class UIRenderer {
|
export class UIRenderer {
|
||||||
constructor(api) {
|
constructor(api) {
|
||||||
|
|
@ -85,6 +85,43 @@ export class UIRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const actionsHTML = `
|
||||||
|
<div class="track-actions-inline">
|
||||||
|
<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="download" title="Download">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||||
|
<polyline points="7 10 12 15 17 10"></polyline>
|
||||||
|
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="track-menu-btn" type="button" title="More options">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="1"></circle>
|
||||||
|
<circle cx="12" cy="5" r="1"></circle>
|
||||||
|
<circle cx="12" cy="19" r="1"></circle>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="track-item" data-track-id="${track.id}">
|
<div class="track-item" data-track-id="${track.id}">
|
||||||
${trackNumberHTML}
|
${trackNumberHTML}
|
||||||
|
|
@ -98,13 +135,9 @@ export class UIRenderer {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-item-duration">${formatTime(track.duration)}</div>
|
<div class="track-item-duration">${formatTime(track.duration)}</div>
|
||||||
<button class="track-menu-btn" type="button" title="More options">
|
<div class="track-item-actions">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
${actionsHTML}
|
||||||
<circle cx="12" cy="12" r="1"></circle>
|
</div>
|
||||||
<circle cx="12" cy="5" r="1"></circle>
|
|
||||||
<circle cx="12" cy="19" r="1"></circle>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
93
styles.css
93
styles.css
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--spacing-xs: 0.5rem;
|
--spacing-xs: 0.4rem;
|
||||||
--spacing-sm: 0.75rem;
|
--spacing-sm: 0.5rem;
|
||||||
--spacing-md: 1rem;
|
--spacing-md: 1rem;
|
||||||
--spacing-lg: 1.5rem;
|
--spacing-lg: 1.5rem;
|
||||||
--spacing-xl: 2rem;
|
--spacing-xl: 2rem;
|
||||||
|
|
@ -682,9 +682,17 @@ body.has-page-background .track-item:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item-duration {
|
.track-item-duration {
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--muted-foreground);
|
color: var(--muted-foreground);
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-item-actions {
|
||||||
|
justify-self: flex-end;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 40px;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-menu-btn {
|
.track-menu-btn {
|
||||||
|
|
@ -695,13 +703,17 @@ body.has-page-background .track-item:hover {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
transition: all var(--transition);
|
transition: all var(--transition);
|
||||||
display: flex;
|
display: none; /* 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;
|
||||||
}
|
}
|
||||||
|
|
@ -717,6 +729,46 @@ body.has-page-background .track-item:hover {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track-actions-inline {
|
||||||
|
display: none; /* Controlled by data-track-actions-mode */
|
||||||
|
gap: 0.25rem;
|
||||||
|
opacity: 0.2; /* Barely visible instead of invisible */
|
||||||
|
transition: opacity var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-track-actions-mode="inline"] .track-actions-inline {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-item:hover .track-actions-inline {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
.track-actions-inline {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-action-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--muted-foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
transition: all var(--transition);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0; /* Prevent buttons from squishing */
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-action-btn:hover {
|
||||||
|
background-color: var(--secondary);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
|
|
@ -2280,16 +2332,11 @@ input:checked + .slider::before {
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item {
|
.track-item {
|
||||||
grid-template-columns: 28px 1fr 45px 32px;
|
grid-template-columns: 28px 1fr auto auto;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-number {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
width: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item-info {
|
.track-item-info {
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
@ -2306,16 +2353,7 @@ input:checked + .slider::before {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item-details .title {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item-details .artist {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-item-duration {
|
.track-item-duration {
|
||||||
font-size: 0.75rem;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
@ -2330,6 +2368,10 @@ input:checked + .slider::before {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track-action-btn {
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.queue-track-item {
|
.queue-track-item {
|
||||||
grid-template-columns: 24px 1fr 40px 28px;
|
grid-template-columns: 24px 1fr 40px 28px;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
|
|
@ -2433,11 +2475,10 @@ input:checked + .slider::before {
|
||||||
.track-item {
|
.track-item {
|
||||||
grid-template-columns: 24px 1fr 40px 28px;
|
grid-template-columns: 24px 1fr 40px 28px;
|
||||||
gap: 0.375rem;
|
gap: 0.375rem;
|
||||||
padding: 0.5rem;
|
padding: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-number {
|
.track-number {
|
||||||
font-size: 0.75rem;
|
|
||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2447,15 +2488,15 @@ input:checked + .slider::before {
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item-details .title {
|
.track-item-details .title {
|
||||||
font-size: 0.8rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item-details .artist {
|
.track-item-details .artist {
|
||||||
font-size: 0.7rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-item-duration {
|
.track-item-duration {
|
||||||
font-size: 0.7rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-menu-btn {
|
.track-menu-btn {
|
||||||
|
|
@ -2481,6 +2522,10 @@ input:checked + .slider::before {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-track-actions-mode="inline"] .track-actions-inline .track-action-btn:not([data-action="play-next"]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 360px) {
|
@media (max-width: 360px) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue