feat(ux): increase artist album limits and add Others section

- Increase artist detail album fetch from 100 to 500
- Increase bio truncation from 300 to 500 chars
- Add Others category for greatest hits/anthologies
- Add configurable limits for Home sections
- Update HomeSettingsModal with limits grid UI

cp
This commit is contained in:
vicrodh committed 2026-01-15 08:00:16 -06:00
1 parent dd9eb90343
commit bf4050a592
7 files changed
+247 -20

No files matched your search

+2 -2
View File
@@ -167,7 +167,7 @@ pub async fn get_artist(
} }
/// Get artist detail with albums, playlists, and appears-on tracks /// Get artist detail with albums, playlists, and appears-on tracks
/// Fetches 100 albums initially to ensure each album type section /// Fetches 500 albums initially to ensure each album type section
/// (Discography, EPs, Live, etc.) has enough albums loaded /// (Discography, EPs, Live, etc.) has enough albums loaded
#[tauri::command] #[tauri::command]
pub async fn get_artist_detail( pub async fn get_artist_detail(
@@ -178,7 +178,7 @@ pub async fn get_artist_detail(
let client = state.client.lock().await; let client = state.client.lock().await;
client client
.get_artist_detail(artist_id, Some(100), Some(0)) .get_artist_detail(artist_id, Some(500), Some(0))
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
+18
View File
@@ -89,6 +89,12 @@ function isLiveAlbum(album: QobuzAlbum): boolean {
return /\blive\b|\blive at\b|\blive in\b|\ben concert\b|\bin concert\b|\bconcert\b|\bunplugged\b|\bsessions?\b/.test(title); return /\blive\b|\blive at\b|\blive in\b|\ben concert\b|\bin concert\b|\bconcert\b|\bunplugged\b|\bsessions?\b/.test(title);
} }
function isOtherAlbum(album: QobuzAlbum): boolean {
const title = album.title?.toLowerCase() ?? '';
return /\b(greatest hits|best of|anthology|collection|box set|essentials|retrospective|very best)\b/.test(title);
}
function toArtistAlbumSummary(album: QobuzAlbum): ArtistAlbumSummary { function toArtistAlbumSummary(album: QobuzAlbum): ArtistAlbumSummary {
const artwork = getQobuzImage(album.image); const artwork = getQobuzImage(album.image);
const quality = formatQuality( const quality = formatQuality(
@@ -199,6 +205,7 @@ export function convertQobuzArtist(artist: QobuzArtist): ArtistDetail {
const albums: ArtistAlbumSummary[] = []; const albums: ArtistAlbumSummary[] = [];
const epsSingles: ArtistAlbumSummary[] = []; const epsSingles: ArtistAlbumSummary[] = [];
const liveAlbums: ArtistAlbumSummary[] = []; const liveAlbums: ArtistAlbumSummary[] = [];
const others: ArtistAlbumSummary[] = [];
for (const album of albumItems) { for (const album of albumItems) {
const summary = toArtistAlbumSummary(album); const summary = toArtistAlbumSummary(album);
@@ -206,6 +213,8 @@ export function convertQobuzArtist(artist: QobuzArtist): ArtistDetail {
liveAlbums.push(summary); liveAlbums.push(summary);
} else if (isEpOrSingle(album)) { } else if (isEpOrSingle(album)) {
epsSingles.push(summary); epsSingles.push(summary);
} else if (isOtherAlbum(album)) {
others.push(summary);
} else { } else {
albums.push(summary); albums.push(summary);
} }
@@ -221,6 +230,7 @@ export function convertQobuzArtist(artist: QobuzArtist): ArtistDetail {
epsSingles, epsSingles,
liveAlbums, liveAlbums,
compilations: buildCompilationAlbums(artist.tracks_appears_on?.items), compilations: buildCompilationAlbums(artist.tracks_appears_on?.items),
others,
playlists: toArtistPlaylists(artist.playlists), playlists: toArtistPlaylists(artist.playlists),
totalAlbums: artist.albums?.total || artist.albums_count || 0, totalAlbums: artist.albums?.total || artist.albums_count || 0,
albumsFetched albumsFetched
@@ -238,10 +248,12 @@ export function appendArtistAlbums(
const existingAlbumIds = new Set(artist.albums.map(album => album.id)); const existingAlbumIds = new Set(artist.albums.map(album => album.id));
const existingEpIds = new Set(artist.epsSingles.map(album => album.id)); const existingEpIds = new Set(artist.epsSingles.map(album => album.id));
const existingLiveIds = new Set(artist.liveAlbums.map(album => album.id)); const existingLiveIds = new Set(artist.liveAlbums.map(album => album.id));
const existingOtherIds = new Set(artist.others.map(album => album.id));
const albums = [...artist.albums]; const albums = [...artist.albums];
const epsSingles = [...artist.epsSingles]; const epsSingles = [...artist.epsSingles];
const liveAlbums = [...artist.liveAlbums]; const liveAlbums = [...artist.liveAlbums];
const others = [...artist.others];
for (const album of newAlbums) { for (const album of newAlbums) {
const summary = toArtistAlbumSummary(album); const summary = toArtistAlbumSummary(album);
@@ -255,6 +267,11 @@ export function appendArtistAlbums(
epsSingles.push(summary); epsSingles.push(summary);
existingEpIds.add(summary.id); existingEpIds.add(summary.id);
} }
} else if (isOtherAlbum(album)) {
if (!existingOtherIds.has(summary.id)) {
others.push(summary);
existingOtherIds.add(summary.id);
}
} else if (!existingAlbumIds.has(summary.id)) { } else if (!existingAlbumIds.has(summary.id)) {
albums.push(summary); albums.push(summary);
existingAlbumIds.add(summary.id); existingAlbumIds.add(summary.id);
@@ -266,6 +283,7 @@ export function appendArtistAlbums(
albums, albums,
epsSingles, epsSingles,
liveAlbums, liveAlbums,
others,
totalAlbums: totalAlbums ?? artist.totalAlbums, totalAlbums: totalAlbums ?? artist.totalAlbums,
albumsFetched: albumsFetched ?? artist.albumsFetched + newAlbums.length albumsFetched: albumsFetched ?? artist.albumsFetched + newAlbums.length
}; };
+128
View File
@@ -11,6 +11,7 @@
setGreetingEnabled, setGreetingEnabled,
setCustomGreeting, setCustomGreeting,
resetToDefaults, resetToDefaults,
updateLimit,
type HomeSettings type HomeSettings
} from '$lib/stores/homeSettingsStore'; } from '$lib/stores/homeSettingsStore';
import { t } from '$lib/i18n'; import { t } from '$lib/i18n';
@@ -57,6 +58,10 @@
function handleReset() { function handleReset() {
resetToDefaults(); resetToDefaults();
} }
function handleLimitChange(key: keyof HomeSettings['limits'], value: number) {
updateLimit(key, value);
}
</script> </script>
<Modal {isOpen} {onClose} title={$t('home.customizeHome')}> <Modal {isOpen} {onClose} title={$t('home.customizeHome')}>
@@ -98,6 +103,92 @@
{/if} {/if}
</div> </div>
<!-- Item Limits -->
<div class="settings-section">
<div class="section-title">Item Limits</div>
<p class="section-desc">Configure how many items to show in each section</p>
<div class="limits-grid">
<div class="limit-item">
<label class="limit-label" for="limit-recent">Recently Played</label>
<input
id="limit-recent"
type="number"
class="limit-input"
min="1"
max="100"
value={settings.limits.recentAlbums}
onchange={(e) => handleLimitChange('recentAlbums', Number(e.currentTarget.value))}
/>
</div>
<div class="limit-item">
<label class="limit-label" for="limit-continue">Continue Listening</label>
<input
id="limit-continue"
type="number"
class="limit-input"
min="1"
max="100"
value={settings.limits.continueTracks}
onchange={(e) => handleLimitChange('continueTracks', Number(e.currentTarget.value))}
/>
</div>
<div class="limit-item">
<label class="limit-label" for="limit-artists">Top Artists</label>
<input
id="limit-artists"
type="number"
class="limit-input"
min="1"
max="100"
value={settings.limits.topArtists}
onchange={(e) => handleLimitChange('topArtists', Number(e.currentTarget.value))}
/>
</div>
<div class="limit-item">
<label class="limit-label" for="limit-fav-albums">Favorite Albums</label>
<input
id="limit-fav-albums"
type="number"
class="limit-input"
min="1"
max="100"
value={settings.limits.favoriteAlbums}
onchange={(e) => handleLimitChange('favoriteAlbums', Number(e.currentTarget.value))}
/>
</div>
<div class="limit-item">
<label class="limit-label" for="limit-fav-tracks">Favorite Tracks</label>
<input
id="limit-fav-tracks"
type="number"
class="limit-input"
min="1"
max="100"
value={settings.limits.favoriteTracks}
onchange={(e) => handleLimitChange('favoriteTracks', Number(e.currentTarget.value))}
/>
</div>
<div class="limit-item">
<label class="limit-label" for="limit-featured">Featured Albums</label>
<input
id="limit-featured"
type="number"
class="limit-input"
min="1"
max="100"
value={settings.limits.featuredAlbums}
onchange={(e) => handleLimitChange('featuredAlbums', Number(e.currentTarget.value))}
/>
</div>
</div>
</div>
<!-- Sections Order --> <!-- Sections Order -->
<div class="settings-section"> <div class="settings-section">
<div class="section-title">{$t('homeSettings.sections')}</div> <div class="section-title">{$t('homeSettings.sections')}</div>
@@ -388,4 +479,41 @@
border-color: var(--text-muted); border-color: var(--text-muted);
color: var(--text-primary); color: var(--text-primary);
} }
.limits-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.limit-item {
display: flex;
flex-direction: column;
gap: 6px;
}
.limit-label {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
}
.limit-input {
padding: 8px 12px;
background: var(--bg-secondary);
border: 1px solid var(--bg-tertiary);
border-radius: 6px;
color: var(--text-primary);
font-size: 13px;
transition: all 150ms ease;
}
.limit-input:focus {
outline: none;
border-color: var(--accent);
}
.limit-input:hover {
border-color: var(--text-muted);
}
</style> </style>
@@ -114,6 +114,7 @@
let epsSinglesSection = $state<HTMLDivElement | null>(null); let epsSinglesSection = $state<HTMLDivElement | null>(null);
let liveAlbumsSection = $state<HTMLDivElement | null>(null); let liveAlbumsSection = $state<HTMLDivElement | null>(null);
let compilationsSection = $state<HTMLDivElement | null>(null); let compilationsSection = $state<HTMLDivElement | null>(null);
let othersSection = $state<HTMLDivElement | null>(null);
let playlistsSection = $state<HTMLDivElement | null>(null); let playlistsSection = $state<HTMLDivElement | null>(null);
let activeJumpSection = $state('about'); let activeJumpSection = $state('about');
let jumpObserver: IntersectionObserver | null = null; let jumpObserver: IntersectionObserver | null = null;
@@ -149,7 +150,8 @@
...artist.albums, ...artist.albums,
...artist.epsSingles, ...artist.epsSingles,
...artist.liveAlbums, ...artist.liveAlbums,
...artist.compilations ...artist.compilations,
...artist.others
]; ];
loadAllAlbumDownloadStatuses(allAlbums); loadAllAlbumDownloadStatuses(allAlbums);
} }
@@ -185,7 +187,8 @@
...artist.albums, ...artist.albums,
...artist.epsSingles, ...artist.epsSingles,
...artist.liveAlbums, ...artist.liveAlbums,
...artist.compilations ...artist.compilations,
...artist.others
]; ];
await loadAllAlbumDownloadStatuses(allAlbums); await loadAllAlbumDownloadStatuses(allAlbums);
} }
@@ -339,7 +342,7 @@
// Truncate bio for collapsed view // Truncate bio for collapsed view
let truncatedBio = $derived( let truncatedBio = $derived(
bioText && bioText.length > 300 ? bioText.slice(0, 300) + '...' : bioText bioText && bioText.length > 500 ? bioText.slice(0, 500) + '...' : bioText
); );
let hasMoreAlbums = $derived(!!onLoadMore && artist.albumsFetched < artist.totalAlbums); let hasMoreAlbums = $derived(!!onLoadMore && artist.albumsFetched < artist.totalAlbums);
@@ -347,6 +350,7 @@
let hasEpsSingles = $derived(artist.epsSingles.length > 0); let hasEpsSingles = $derived(artist.epsSingles.length > 0);
let hasLiveAlbums = $derived(artist.liveAlbums.length > 0); let hasLiveAlbums = $derived(artist.liveAlbums.length > 0);
let hasCompilations = $derived(artist.compilations.length > 0); let hasCompilations = $derived(artist.compilations.length > 0);
let hasOthers = $derived(artist.others.length > 0);
let hasPlaylists = $derived(artist.playlists.length > 0); let hasPlaylists = $derived(artist.playlists.length > 0);
let jumpSections = $derived.by(() => [ let jumpSections = $derived.by(() => [
{ id: 'about', label: 'About', el: aboutSection, visible: true }, { id: 'about', label: 'About', el: aboutSection, visible: true },
@@ -355,6 +359,7 @@
{ id: 'eps', label: 'EPs & Singles', el: epsSinglesSection, visible: hasEpsSingles }, { id: 'eps', label: 'EPs & Singles', el: epsSinglesSection, visible: hasEpsSingles },
{ id: 'live', label: 'Live Albums', el: liveAlbumsSection, visible: hasLiveAlbums }, { id: 'live', label: 'Live Albums', el: liveAlbumsSection, visible: hasLiveAlbums },
{ id: 'compilations', label: 'Compilations', el: compilationsSection, visible: hasCompilations }, { id: 'compilations', label: 'Compilations', el: compilationsSection, visible: hasCompilations },
{ id: 'others', label: 'Others', el: othersSection, visible: hasOthers },
{ id: 'playlists', label: 'Playlists', el: playlistsSection, visible: hasPlaylists }, { id: 'playlists', label: 'Playlists', el: playlistsSection, visible: hasPlaylists },
].filter(section => section.visible)); ].filter(section => section.visible));
@@ -747,6 +752,36 @@
</div> </div>
{/if} {/if}
{#if artist.others.length > 0}
<div class="divider"></div>
<div class="discography section-anchor" bind:this={othersSection}>
<h2 class="section-title">Others</h2>
<div class="albums-grid">
{#each artist.others as album}
<AlbumCard
albumId={album.id}
artwork={album.artwork}
title={album.title}
artist={album.year || ''}
quality={album.quality}
onPlay={onAlbumPlay ? () => onAlbumPlay(album.id) : undefined}
onPlayNext={onAlbumPlayNext ? () => onAlbumPlayNext(album.id) : undefined}
onPlayLater={onAlbumPlayLater ? () => onAlbumPlayLater(album.id) : undefined}
onShareQobuz={onAlbumShareQobuz ? () => onAlbumShareQobuz(album.id) : undefined}
onShareSonglink={onAlbumShareSonglink ? () => onAlbumShareSonglink(album.id) : undefined}
onDownload={onAlbumDownload ? () => onAlbumDownload(album.id) : undefined}
isAlbumFullyDownloaded={isAlbumDownloaded(album.id)}
onOpenContainingFolder={onOpenAlbumFolder ? () => onOpenAlbumFolder(album.id) : undefined}
onReDownloadAlbum={onReDownloadAlbum ? () => onReDownloadAlbum(album.id) : undefined}
{downloadStateVersion}
onclick={() => { onAlbumClick?.(album.id); loadAlbumDownloadStatus(album.id); }}
/>
{/each}
</div>
</div>
{/if}
{#if artist.playlists.length > 0} {#if artist.playlists.length > 0}
<div class="divider"></div> <div class="divider"></div>
+17 -14
View File
@@ -107,6 +107,8 @@
featuredAlbums: 12 featuredAlbums: 12
}; };
let homeLimits = $state(getSettings().limits);
// Loading states for progressive render // Loading states for progressive render
let isInitializing = $state(true); let isInitializing = $state(true);
let isOverlayVisible = $state(true); // Overlay that fades out when ALL sections ready let isOverlayVisible = $state(true); // Overlay that fades out when ALL sections ready
@@ -215,6 +217,7 @@
// Subscribe to home settings changes // Subscribe to home settings changes
const unsubscribe = subscribeHomeSettings(() => { const unsubscribe = subscribeHomeSettings(() => {
homeSettings = getSettings(); homeSettings = getSettings();
homeLimits = getSettings().limits;
}); });
// Load home data // Load home data
@@ -341,7 +344,7 @@
return Array.from(counts.entries()) return Array.from(counts.entries())
.map(([artistId, playCount]) => ({ artistId, playCount })) .map(([artistId, playCount]) => ({ artistId, playCount }))
.sort((a, b) => b.playCount - a.playCount) .sort((a, b) => b.playCount - a.playCount)
.slice(0, LIMITS.topArtists); .slice(0, homeLimits.topArtists);
} }
async function loadHome() { async function loadHome() {
@@ -373,15 +376,15 @@
// Start ML data loading FIRST (local SQLite) - this gets the seeds // Start ML data loading FIRST (local SQLite) - this gets the seeds
const mlPromise = invoke<HomeSeeds>('reco_get_home_ml', { const mlPromise = invoke<HomeSeeds>('reco_get_home_ml', {
limitRecentAlbums: LIMITS.recentAlbums, limitRecentAlbums: homeLimits.recentAlbums,
limitContinueTracks: LIMITS.continueTracks, limitContinueTracks: homeLimits.continueTracks,
limitTopArtists: LIMITS.topArtists, limitTopArtists: homeLimits.topArtists,
limitFavorites: Math.max(LIMITS.favoriteAlbums, LIMITS.favoriteTracks) limitFavorites: Math.max(homeLimits.favoriteAlbums, homeLimits.favoriteTracks)
}); });
// Start Qobuz API calls in parallel (don't await) // Start Qobuz API calls in parallel (don't await)
if (isSectionVisible('newReleases')) { if (isSectionVisible('newReleases')) {
fetchFeaturedAlbums('new-releases', LIMITS.featuredAlbums).then(async albums => { fetchFeaturedAlbums('new-releases', homeLimits.featuredAlbums).then(async albums => {
newReleases = albums; newReleases = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingNewReleases = false; loadingNewReleases = false;
@@ -392,7 +395,7 @@
} }
if (isSectionVisible('pressAwards')) { if (isSectionVisible('pressAwards')) {
fetchFeaturedAlbums('press-awards', LIMITS.featuredAlbums).then(async albums => { fetchFeaturedAlbums('press-awards', homeLimits.featuredAlbums).then(async albums => {
pressAwards = albums; pressAwards = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingPressAwards = false; loadingPressAwards = false;
@@ -403,7 +406,7 @@
} }
if (isSectionVisible('mostStreamed')) { if (isSectionVisible('mostStreamed')) {
fetchFeaturedAlbums('most-streamed', LIMITS.featuredAlbums).then(async albums => { fetchFeaturedAlbums('most-streamed', homeLimits.featuredAlbums).then(async albums => {
mostStreamed = albums; mostStreamed = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingMostStreamed = false; loadingMostStreamed = false;
@@ -414,7 +417,7 @@
} }
if (isSectionVisible('qobuzissimes')) { if (isSectionVisible('qobuzissimes')) {
fetchFeaturedAlbums('qobuzissimes', LIMITS.featuredAlbums).then(async albums => { fetchFeaturedAlbums('qobuzissimes', homeLimits.featuredAlbums).then(async albums => {
qobuzissimes = albums; qobuzissimes = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingQobuzissimes = false; loadingQobuzissimes = false;
@@ -425,7 +428,7 @@
} }
if (isSectionVisible('editorPicks')) { if (isSectionVisible('editorPicks')) {
fetchFeaturedAlbums('editor-picks', LIMITS.featuredAlbums).then(async albums => { fetchFeaturedAlbums('editor-picks', homeLimits.featuredAlbums).then(async albums => {
editorPicks = albums; editorPicks = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingEditorPicks = false; loadingEditorPicks = false;
@@ -454,7 +457,7 @@
// Recently Played (albums) - start immediately with seeds // Recently Played (albums) - start immediately with seeds
if (isSectionVisible('recentAlbums')) { if (isSectionVisible('recentAlbums')) {
const recentAlbumIds = normalizeAlbumIds(seeds.recentlyPlayedAlbumIds); const recentAlbumIds = normalizeAlbumIds(seeds.recentlyPlayedAlbumIds);
fetchAlbums(recentAlbumIds.slice(0, LIMITS.recentAlbums)).then(async albums => { fetchAlbums(recentAlbumIds.slice(0, homeLimits.recentAlbums)).then(async albums => {
recentAlbums = albums; recentAlbums = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingRecentAlbums = false; loadingRecentAlbums = false;
@@ -466,7 +469,7 @@
// Top Artists // Top Artists
if (isSectionVisible('topArtists')) { if (isSectionVisible('topArtists')) {
fetchArtists(seeds.topArtistIds.slice(0, LIMITS.topArtists)).then(artists => { fetchArtists(seeds.topArtistIds.slice(0, homeLimits.topArtists)).then(artists => {
topArtists = artists; topArtists = artists;
loadingTopArtists = false; loadingTopArtists = false;
markSectionFinished(); markSectionFinished();
@@ -478,12 +481,12 @@
// Favorite Albums // Favorite Albums
if (isSectionVisible('favoriteAlbums')) { if (isSectionVisible('favoriteAlbums')) {
// Get favorite track details to extract album IDs // Get favorite track details to extract album IDs
fetchTracks(seeds.favoriteTrackIds.slice(0, LIMITS.favoriteTracks)).then(async favoriteTrackDetails => { fetchTracks(seeds.favoriteTrackIds.slice(0, homeLimits.favoriteTracks)).then(async favoriteTrackDetails => {
const favoriteAlbumIds = normalizeAlbumIds([ const favoriteAlbumIds = normalizeAlbumIds([
...seeds.favoriteAlbumIds, ...seeds.favoriteAlbumIds,
...favoriteTrackDetails.map(track => track.albumId) ...favoriteTrackDetails.map(track => track.albumId)
]); ]);
const albums = await fetchAlbums(favoriteAlbumIds.slice(0, LIMITS.favoriteAlbums)); const albums = await fetchAlbums(favoriteAlbumIds.slice(0, homeLimits.favoriteAlbums));
favoriteAlbums = albums; favoriteAlbums = albums;
await loadAllAlbumDownloadStatuses(albums); await loadAllAlbumDownloadStatuses(albums);
loadingFavoriteAlbums = false; loadingFavoriteAlbums = false;
+37 -1
View File
@@ -22,6 +22,7 @@ export interface HomeSection {
label: string; label: string;
visible: boolean; visible: boolean;
source?: 'qobuz' | 'ml'; // qobuz = editorial, ml = ML-powered recommendations source?: 'qobuz' | 'ml'; // qobuz = editorial, ml = ML-powered recommendations
limit?: number; // configurable item count
} }
export interface HomeSettings { export interface HomeSettings {
@@ -30,6 +31,14 @@ export interface HomeSettings {
enabled: boolean; enabled: boolean;
customText: string | null; // null = use default time-based greeting customText: string | null; // null = use default time-based greeting
}; };
limits: {
recentAlbums: number;
continueTracks: number;
topArtists: number;
favoriteAlbums: number;
favoriteTracks: number;
featuredAlbums: number;
};
} }
const STORAGE_KEY = 'qbz-home-settings'; const STORAGE_KEY = 'qbz-home-settings';
@@ -52,6 +61,14 @@ const DEFAULT_SETTINGS: HomeSettings = {
greeting: { greeting: {
enabled: true, enabled: true,
customText: null customText: null
},
limits: {
recentAlbums: 20,
continueTracks: 10,
topArtists: 20,
favoriteAlbums: 12,
favoriteTracks: 10,
featuredAlbums: 12
} }
}; };
@@ -88,7 +105,8 @@ function loadSettings(): HomeSettings {
function mergeWithDefaults(saved: Partial<HomeSettings>): HomeSettings { function mergeWithDefaults(saved: Partial<HomeSettings>): HomeSettings {
const result: HomeSettings = { const result: HomeSettings = {
sections: [], sections: [],
greeting: saved.greeting ?? DEFAULT_SETTINGS.greeting greeting: saved.greeting ?? DEFAULT_SETTINGS.greeting,
limits: { ...DEFAULT_SETTINGS.limits, ...(saved.limits || {}) }
}; };
// Build section map from saved // Build section map from saved
@@ -304,3 +322,21 @@ export function resetToDefaults(): void {
saveSettings(); saveSettings();
notifyListeners(); notifyListeners();
} }
/**
* Update a specific limit
*/
export function updateLimit(key: keyof HomeSettings['limits'], value: number): void {
if (value >= 1 && value <= 100) {
settings.limits[key] = value;
saveSettings();
notifyListeners();
}
}
/**
* Get current limits
*/
export function getLimits(): HomeSettings['limits'] {
return settings.limits;
}
+7
View File
@@ -152,6 +152,13 @@ export interface ArtistDetail {
year?: string; year?: string;
quality: string; quality: string;
}[]; }[];
others: {
id: string;
title: string;
artwork: string;
year?: string;
quality: string;
}[];
playlists: { playlists: {
id: number; id: number;
title: string; title: string;