NEW: compact mode for cards, default true for artists

This commit is contained in:
Julien Maille 2026-01-04 22:53:40 +01:00
parent 976f24ef1a
commit 27b6b98643
5 changed files with 158 additions and 19 deletions

View file

@ -526,6 +526,26 @@
<option value="inline">Inline Buttons</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Compact Artists</span>
<span class="description">Show artist cards in a compact, horizontal layout</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="compact-artist-toggle">
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Compact Albums</span>
<span class="description">Show album cards in a compact, horizontal layout</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="compact-album-toggle">
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">

View file

@ -1,5 +1,5 @@
//js/settings
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings, backgroundSettings, trackListSettings } from './storage.js';
import { themeManager, lastFMStorage, nowPlayingSettings, lyricsSettings, backgroundSettings, trackListSettings, cardSettings } from './storage.js';
import { db } from './db.js';
import { authManager } from './firebase/auth.js';
import { syncManager } from './firebase/sync.js';
@ -266,6 +266,24 @@ export function initializeSettings(scrobbler, player, api, ui) {
});
}
// Compact Artist Toggle
const compactArtistToggle = document.getElementById('compact-artist-toggle');
if (compactArtistToggle) {
compactArtistToggle.checked = cardSettings.isCompactArtist();
compactArtistToggle.addEventListener('change', (e) => {
cardSettings.setCompactArtist(e.target.checked);
});
}
// Compact Album Toggle
const compactAlbumToggle = document.getElementById('compact-album-toggle');
if (compactAlbumToggle) {
compactAlbumToggle.checked = cardSettings.isCompactAlbum();
compactAlbumToggle.addEventListener('change', (e) => {
cardSettings.setCompactAlbum(e.target.checked);
});
}
// Download Lyrics Toggle
const downloadLyricsToggle = document.getElementById('download-lyrics-toggle');
if (downloadLyricsToggle) {

View file

@ -446,6 +446,36 @@ export const trackListSettings = {
}
};
export const cardSettings = {
COMPACT_ARTIST_KEY: 'card-compact-artist',
COMPACT_ALBUM_KEY: 'card-compact-album',
isCompactArtist() {
try {
const val = localStorage.getItem(this.COMPACT_ARTIST_KEY);
return val === null ? true : val === 'true';
} catch (e) {
return true;
}
},
setCompactArtist(enabled) {
localStorage.setItem(this.COMPACT_ARTIST_KEY, enabled ? 'true' : 'false');
},
isCompactAlbum() {
try {
return localStorage.getItem(this.COMPACT_ALBUM_KEY) === 'true';
} catch (e) {
return false;
}
},
setCompactAlbum(enabled) {
localStorage.setItem(this.COMPACT_ALBUM_KEY, enabled ? 'true' : 'false');
}
};
export const queueManager = {
STORAGE_KEY: 'monochrome-queue',

View file

@ -1,7 +1,7 @@
//js/ui.js
import { SVG_PLAY, SVG_DOWNLOAD, SVG_MENU, SVG_HEART, formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js';
import { openLyricsPanel } from './lyrics.js';
import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js';
import { recentActivityManager, backgroundSettings, trackListSettings, cardSettings } from './storage.js';
import { db } from './db.js';
import { getVibrantColorFromImage } from './vibrant-color.js';
@ -190,8 +190,9 @@ export class UIRenderer {
createPlaylistCardHTML(playlist) {
const imageId = playlist.squareImage || playlist.image || playlist.uuid; // Fallback or use a specific cover getter if needed
const isCompact = cardSettings.isCompactAlbum();
return `
<div class="card" data-playlist-id="${playlist.uuid}" data-href="#playlist/${playlist.uuid}" style="cursor: pointer;">
<div class="card ${isCompact ? 'compact' : ''}" data-playlist-id="${playlist.uuid}" data-href="#playlist/${playlist.uuid}" style="cursor: pointer;">
<div class="card-image-wrapper">
<img src="${this.api.getCoverUrl(imageId)}" alt="${playlist.title}" class="card-image" loading="lazy">
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="playlist" title="Add to Liked">
@ -201,8 +202,10 @@ export class UIRenderer {
${SVG_PLAY}
</button>
</div>
<h4 class="card-title">${playlist.title}</h4>
<p class="card-subtitle">${playlist.numberOfTracks || 0} tracks</p>
<div class="card-info">
<h4 class="card-title">${playlist.title}</h4>
<p class="card-subtitle">${playlist.numberOfTracks || 0} tracks</p>
</div>
</div>
`;
}
@ -210,9 +213,10 @@ export class UIRenderer {
createMixCardHTML(mix) {
const imageSrc = mix.cover || 'assets/appicon.png';
const description = mix.subTitle || mix.description || '';
const isCompact = cardSettings.isCompactAlbum();
return `
<div class="card" data-mix-id="${mix.id}" data-href="#mix/${mix.id}" style="cursor: pointer;">
<div class="card ${isCompact ? 'compact' : ''}" data-mix-id="${mix.id}" data-href="#mix/${mix.id}" style="cursor: pointer;">
<div class="card-image-wrapper">
<img src="${imageSrc}" alt="${mix.title}" class="card-image" loading="lazy">
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="mix" title="Add to Liked">
@ -222,8 +226,10 @@ export class UIRenderer {
${SVG_PLAY}
</button>
</div>
<h4 class="card-title">${mix.title}</h4>
<p class="card-subtitle">${description}</p>
<div class="card-info">
<h4 class="card-title">${mix.title}</h4>
<p class="card-subtitle">${description}</p>
</div>
</div>
`;
}
@ -266,8 +272,10 @@ export class UIRenderer {
}
}
const isCompact = cardSettings.isCompactAlbum();
return `
<div class="card user-playlist" data-playlist-id="${playlist.id}" data-href="#userplaylist/${playlist.id}" style="cursor: pointer;">
<div class="card user-playlist ${isCompact ? 'compact' : ''}" data-playlist-id="${playlist.id}" data-href="#userplaylist/${playlist.id}" style="cursor: pointer;">
<div class="card-image-wrapper">
${imageHTML}
<button class="edit-playlist-btn" data-action="edit-playlist" title="Edit Playlist">
@ -289,8 +297,10 @@ export class UIRenderer {
${SVG_PLAY}
</button>
</div>
<h4 class="card-title">${playlist.name}</h4>
<p class="card-subtitle">${playlist.tracks ? playlist.tracks.length : (playlist.numberOfTracks || 0)} tracks</p>
<div class="card-info">
<h4 class="card-title">${playlist.name}</h4>
<p class="card-subtitle">${playlist.tracks ? playlist.tracks.length : (playlist.numberOfTracks || 0)} tracks</p>
</div>
</div>
`;
}
@ -312,8 +322,10 @@ export class UIRenderer {
typeLabel = ' • Single';
}
const isCompact = cardSettings.isCompactAlbum();
return `
<div class="card" data-album-id="${album.id}" data-href="#album/${album.id}" style="cursor: pointer;">
<div class="card ${isCompact ? 'compact' : ''}" data-album-id="${album.id}" data-href="#album/${album.id}" style="cursor: pointer;">
<div class="card-image-wrapper">
<img src="${this.api.getCoverUrl(album.cover)}" alt="${album.title}" class="card-image" loading="lazy">
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="album" title="Add to Liked">
@ -323,16 +335,19 @@ export class UIRenderer {
${SVG_PLAY}
</button>
</div>
<h4 class="card-title">${album.title} ${explicitBadge}</h4>
<p class="card-subtitle">${album.artist?.name ?? ''}</p>
<p class="card-subtitle">${yearDisplay}${typeLabel}</p>
<div class="card-info">
<h4 class="card-title">${album.title} ${explicitBadge}</h4>
<p class="card-subtitle">${album.artist?.name ?? ''}</p>
<p class="card-subtitle">${yearDisplay}${typeLabel}</p>
</div>
</div>
`;
}
createArtistCardHTML(artist) {
const isCompact = cardSettings.isCompactArtist();
return `
<div class="card artist" data-artist-id="${artist.id}" data-href="#artist/${artist.id}" style="cursor: pointer;">
<div class="card artist ${isCompact ? 'compact' : ''}" data-artist-id="${artist.id}" data-href="#artist/${artist.id}" style="cursor: pointer;">
<div class="card-image-wrapper">
<img src="${this.api.getArtistPictureUrl(artist.picture)}" alt="${artist.name}" class="card-image" loading="lazy">
<button class="like-btn card-like-btn" data-action="toggle-like" data-type="artist" title="Add to Liked">

View file

@ -635,7 +635,7 @@ body.has-page-background .track-item:hover {
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: var(--spacing-lg);
}
@ -2512,7 +2512,7 @@ input:checked + .slider::before {
}
.card-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: var(--spacing-md);
}
@ -2784,7 +2784,7 @@ input:checked + .slider::before {
@media (max-width: 480px) {
.card-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: var(--spacing-sm);
}
@ -3130,6 +3130,62 @@ input:checked + .slider::before {
color: var(--muted-foreground);
}
/* Compact Cards (Artist and Album) */
.card.compact {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
padding: 0.5rem;
min-height: 60px;
}
.card.compact .card-image-wrapper {
width: 48px;
height: 48px;
margin-bottom: 0;
flex-shrink: 0;
}
.card.artist.compact .card-image-wrapper {
width: 40px;
height: 40px;
}
.card.compact .card-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.card.compact .card-title {
margin: 0;
font-size: 0.9rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card.compact .card-subtitle {
margin: 0;
font-size: 0.8rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--muted-foreground);
}
.card.compact .card-like-btn {
display: none !important;
}
.card.compact:hover .card-like-btn {
opacity: 1;
}
/* Mobile adjustments */
@media (max-width: 768px) {
.side-panel {