Refactor quality badges to use QualityBadge component with compact mode

- Add compact prop to QualityBadge for inline usage in track lists
- Compact mode shows simple text (24bit/96kHz or CD Quality)
- Use golden gradient for hires badges in compact mode
- Update ArtistDetailView to use QualityBadge compact
- Update FavoritesView to use QualityBadge compact
- Update SearchView to use QualityBadge compact
- Remove duplicate CSS and getQualityLabel functions
- LocalLibrary views keep custom format showing codec (FLAC 24/96)
This commit is contained in:
vicrodh committed 2026-01-28 19:28:43 -06:00
1 parent af74ee8cb8
commit c0e9a94f98
4 files changed
+97 -91

No files matched your search

+63 -19
View File
@@ -4,13 +4,15 @@
bitDepth?: number; bitDepth?: number;
samplingRate?: number; samplingRate?: number;
format?: string; format?: string;
compact?: boolean;
} }
let { let {
quality = '', quality = '',
bitDepth, bitDepth,
samplingRate, samplingRate,
format format,
compact = false
}: Props = $props(); }: Props = $props();
// Determine quality tier // Determine quality tier
@@ -53,7 +55,7 @@
return 'cd'; // Default to CD instead of unknown return 'cd'; // Default to CD instead of unknown
}); });
// Format the display text // Format the display text for full mode
const displayText = $derived.by(() => { const displayText = $derived.by(() => {
if (tier === 'max') { if (tier === 'max') {
const depth = bitDepth || 24; const depth = bitDepth || 24;
@@ -77,6 +79,19 @@
return '16-bit / 44.1 kHz'; return '16-bit / 44.1 kHz';
}); });
// Format the compact display text
const compactText = $derived.by(() => {
if (tier === 'max' || tier === 'hires') {
const depth = bitDepth || 24;
const rate = samplingRate || (tier === 'max' ? 192 : 96);
return `${depth}bit/${rate}kHz`;
}
if (tier === 'mp3') {
return 'MP3';
}
return 'CD Quality';
});
const tierLabel = $derived.by(() => { const tierLabel = $derived.by(() => {
if (tier === 'max') return 'Hi-Res'; if (tier === 'max') return 'Hi-Res';
if (tier === 'hires') return 'Hi-Res'; if (tier === 'hires') return 'Hi-Res';
@@ -96,23 +111,52 @@
const isHiRes = $derived(tier === 'max' || tier === 'hires'); const isHiRes = $derived(tier === 'max' || tier === 'hires');
</script> </script>
<div class="quality-badge" title="{tierLabel}: {displayText}"> {#if compact}
<!-- Icon --> <span class="quality-badge-compact" class:hires={isHiRes} title="{tierLabel}: {displayText}">
<img {compactText}
src={iconPath} </span>
alt={tierLabel} {:else}
class="badge-icon" <div class="quality-badge" title="{tierLabel}: {displayText}">
class:hires={isHiRes} <!-- Icon -->
/> <img
src={iconPath}
alt={tierLabel}
class="badge-icon"
class:hires={isHiRes}
/>
<!-- Text --> <!-- Text -->
<div class="badge-text"> <div class="badge-text">
<span class="tier-label">{tierLabel}</span> <span class="tier-label">{tierLabel}</span>
<span class="quality-info">{displayText}</span> <span class="quality-info">{displayText}</span>
</div>
</div> </div>
</div> {/if}
<style> <style>
/* Compact mode styles */
.quality-badge-compact {
display: inline-block;
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 6px;
background: var(--alpha-10);
color: var(--alpha-85);
border: 1px solid var(--alpha-15);
min-width: 90px;
text-align: center;
box-sizing: border-box;
white-space: nowrap;
}
.quality-badge-compact.hires {
background: linear-gradient(135deg, #fbbf24 0%, #b8860b 100%);
color: #1a1a1a;
border-color: transparent;
}
/* Full mode styles */
.quality-badge { .quality-badge {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -125,21 +169,21 @@
box-sizing: border-box; box-sizing: border-box;
cursor: help; cursor: help;
} }
[data-theme="light"] .quality-badge { [data-theme="light"] .quality-badge {
background: rgba(40, 42, 54, 0.85); background: rgba(40, 42, 54, 0.85);
border: 1px solid rgba(40, 42, 54, 0.95); border: 1px solid rgba(40, 42, 54, 0.95);
} }
[data-theme="light"] .tier-label, [data-theme="light"] .tier-label,
[data-theme="light"] .quality-info { [data-theme="light"] .quality-info {
color: #ffffff; color: #ffffff;
} }
[data-theme="light"] .badge-icon { [data-theme="light"] .badge-icon {
filter: invert(1) brightness(1.5); filter: invert(1) brightness(1.5);
} }
[data-theme="light"] .badge-icon.hires { [data-theme="light"] .badge-icon.hires {
filter: brightness(1.2); filter: brightness(1.2);
} }
@@ -4,6 +4,7 @@
import type { ArtistDetail, QobuzArtist } from '$lib/types'; import type { ArtistDetail, QobuzArtist } from '$lib/types';
import AlbumCard from '../AlbumCard.svelte'; import AlbumCard from '../AlbumCard.svelte';
import TrackMenu from '../TrackMenu.svelte'; import TrackMenu from '../TrackMenu.svelte';
import QualityBadge from '../QualityBadge.svelte';
import { consumeContextTrackFocus, setPlaybackContext, getPlaybackContext } from '$lib/stores/playbackContextStore'; import { consumeContextTrackFocus, setPlaybackContext, getPlaybackContext } from '$lib/stores/playbackContextStore';
import { togglePlay } from '$lib/stores/playerStore'; import { togglePlay } from '$lib/stores/playerStore';
import { getQueue, syncQueueState, playQueueIndex } from '$lib/stores/queueStore'; import { getQueue, syncQueueState, playQueueIndex } from '$lib/stores/queueStore';
@@ -643,13 +644,6 @@
return `${mins}:${secs.toString().padStart(2, '0')}`; return `${mins}:${secs.toString().padStart(2, '0')}`;
} }
function getQualityLabel(track: Track): string {
if (track.hires_streamable && track.maximum_bit_depth && track.maximum_sampling_rate) {
return `${track.maximum_bit_depth}bit/${track.maximum_sampling_rate}kHz`;
}
return 'CD Quality';
}
function handlePausePlayback(event: MouseEvent) { function handlePausePlayback(event: MouseEvent) {
event.stopPropagation(); event.stopPropagation();
void togglePlay(); void togglePlay();
@@ -1426,9 +1420,11 @@
{/if} {/if}
</div> </div>
<div class="track-quality"> <div class="track-quality">
<span class="quality-badge" class:hires={track.hires_streamable}> <QualityBadge
{getQualityLabel(track)} bitDepth={track.maximum_bit_depth}
</span> samplingRate={track.maximum_sampling_rate}
compact
/>
</div> </div>
<div class="track-duration">{formatDuration(track.duration)}</div> <div class="track-duration">{formatDuration(track.duration)}</div>
<div class="track-actions"> <div class="track-actions">
@@ -3435,25 +3431,6 @@
align-items: center; align-items: center;
} }
.track-quality .quality-badge {
font-size: 11px;
font-weight: 600;
padding: 3px 8px;
border-radius: 6px;
background: var(--alpha-10);
color: var(--alpha-85);
border: 1px solid var(--alpha-15);
white-space: nowrap;
min-width: 90px;
text-align: center;
}
.track-quality .quality-badge.hires {
background: linear-gradient(135deg, #fbbf24 0%, #b8860b 100%);
color: #1a1a1a;
border-color: transparent;
}
.track-duration { .track-duration {
font-size: 13px; font-size: 13px;
color: var(--text-muted); color: var(--text-muted);
+11 -32
View File
@@ -4,6 +4,7 @@
import { Heart, Play, Disc3, Mic2, Music, Search, X, LayoutGrid, List, ChevronDown, ListMusic, Edit3, Star, Folder, Library } from 'lucide-svelte'; import { Heart, Play, Disc3, Mic2, Music, Search, X, LayoutGrid, List, ChevronDown, ListMusic, Edit3, Star, Folder, Library } from 'lucide-svelte';
import AlbumCard from '../AlbumCard.svelte'; import AlbumCard from '../AlbumCard.svelte';
import TrackRow from '../TrackRow.svelte'; import TrackRow from '../TrackRow.svelte';
import QualityBadge from '../QualityBadge.svelte';
import VirtualizedTrackList from '../VirtualizedTrackList.svelte'; import VirtualizedTrackList from '../VirtualizedTrackList.svelte';
import PlaylistCollage from '../PlaylistCollage.svelte'; import PlaylistCollage from '../PlaylistCollage.svelte';
import FavoritesEditModal from '../FavoritesEditModal.svelte'; import FavoritesEditModal from '../FavoritesEditModal.svelte';
@@ -607,13 +608,6 @@
return album.release_date_original.slice(0, 4); return album.release_date_original.slice(0, 4);
} }
function getAlbumQualityLabel(album: FavoriteAlbum): string {
if (album.hires && album.maximum_bit_depth && album.maximum_sampling_rate) {
return `${album.maximum_bit_depth}bit/${album.maximum_sampling_rate}kHz`;
}
return album.hires ? 'Hi-Res' : 'CD Quality';
}
function groupAlbums(items: FavoriteAlbum[], mode: AlbumGroupMode) { function groupAlbums(items: FavoriteAlbum[], mode: AlbumGroupMode) {
const prefix = `album-${mode}`; const prefix = `album-${mode}`;
const sorted = [...items].sort((a, b) => { const sorted = [...items].sort((a, b) => {
@@ -1342,9 +1336,11 @@
</div> </div>
</div> </div>
<div class="album-row-quality"> <div class="album-row-quality">
<span class="quality-badge" class:hires={album.hires}> <QualityBadge
{getAlbumQualityLabel(album)} bitDepth={album.maximum_bit_depth}
</span> samplingRate={album.maximum_sampling_rate}
compact
/>
</div> </div>
</div> </div>
{/each} {/each}
@@ -1416,9 +1412,11 @@
</div> </div>
</div> </div>
<div class="album-row-quality"> <div class="album-row-quality">
<span class="quality-badge" class:hires={album.hires}> <QualityBadge
{getAlbumQualityLabel(album)} bitDepth={album.maximum_bit_depth}
</span> samplingRate={album.maximum_sampling_rate}
compact
/>
</div> </div>
</div> </div>
{/each} {/each}
@@ -2160,25 +2158,6 @@
justify-content: flex-end; justify-content: flex-end;
} }
.album-row-quality .quality-badge {
font-size: 11px;
font-weight: 600;
color: var(--alpha-85);
background: var(--alpha-10);
border: 1px solid var(--alpha-15);
border-radius: 6px;
padding: 3px 8px;
min-width: 90px;
text-align: center;
box-sizing: border-box;
}
.album-row-quality .quality-badge.hires {
background: linear-gradient(135deg, #fbbf24 0%, #b8860b 100%);
color: #1a1a1a;
border-color: transparent;
}
.artist-grid { .artist-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+17 -11
View File
@@ -4,6 +4,7 @@
import { Search, Disc3, Music, Mic2, User, X, ChevronLeft, ChevronRight, Crown } from 'lucide-svelte'; import { Search, Disc3, Music, Mic2, User, X, ChevronLeft, ChevronRight, Crown } from 'lucide-svelte';
import AlbumCard from '../AlbumCard.svelte'; import AlbumCard from '../AlbumCard.svelte';
import TrackMenu from '../TrackMenu.svelte'; import TrackMenu from '../TrackMenu.svelte';
import QualityBadge from '../QualityBadge.svelte';
import { getSearchState, setSearchState, subscribeSearchFocus, type SearchResults, type SearchAllResults, type SearchTab, type SearchFilterType } from '$lib/stores/searchState'; import { getSearchState, setSearchState, subscribeSearchFocus, type SearchResults, type SearchAllResults, type SearchTab, type SearchFilterType } from '$lib/stores/searchState';
import { setPlaybackContext } from '$lib/stores/playbackContextStore'; import { setPlaybackContext } from '$lib/stores/playbackContextStore';
import { togglePlay } from '$lib/stores/playerStore'; import { togglePlay } from '$lib/stores/playerStore';
@@ -988,7 +989,13 @@
<div class="track-artist">{track.performer?.name || 'Unknown Artist'}</div> <div class="track-artist">{track.performer?.name || 'Unknown Artist'}</div>
{/if} {/if}
</div> </div>
<div class="track-quality">{getQualityLabel(track)}</div> <div class="track-quality">
<QualityBadge
bitDepth={track.maximum_bit_depth}
samplingRate={track.maximum_sampling_rate}
compact
/>
</div>
<div class="track-duration">{formatDuration(track.duration)}</div> <div class="track-duration">{formatDuration(track.duration)}</div>
<div class="track-actions"> <div class="track-actions">
<TrackMenu <TrackMenu
@@ -1126,7 +1133,13 @@
<div class="track-artist">{track.performer?.name || 'Unknown Artist'}</div> <div class="track-artist">{track.performer?.name || 'Unknown Artist'}</div>
{/if} {/if}
</div> </div>
<div class="track-quality">{getQualityLabel(track)}</div> <div class="track-quality">
<QualityBadge
bitDepth={track.maximum_bit_depth}
samplingRate={track.maximum_sampling_rate}
compact
/>
</div>
<div class="track-duration">{formatDuration(track.duration)}</div> <div class="track-duration">{formatDuration(track.duration)}</div>
<div class="track-actions"> <div class="track-actions">
<TrackMenu <TrackMenu
@@ -1574,15 +1587,8 @@
} }
.track-quality { .track-quality {
font-size: 11px; display: flex;
font-weight: 600; align-items: center;
color: var(--alpha-85);
background: var(--alpha-10);
border: 1px solid var(--alpha-15);
padding: 3px 8px;
border-radius: 6px;
min-width: 90px;
text-align: center;
} }
.track-duration { .track-duration {