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:
1 parent
dd9eb90343
commit
bf4050a592
7 files changed
+247
-20
No files matched your search
@@ -167,7 +167,7 @@ pub async fn get_artist(
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[tauri::command]
|
||||
pub async fn get_artist_detail(
|
||||
@@ -178,7 +178,7 @@ pub async fn get_artist_detail(
|
||||
|
||||
let client = state.client.lock().await;
|
||||
client
|
||||
.get_artist_detail(artist_id, Some(100), Some(0))
|
||||
.get_artist_detail(artist_id, Some(500), Some(0))
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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 {
|
||||
const artwork = getQobuzImage(album.image);
|
||||
const quality = formatQuality(
|
||||
@@ -199,6 +205,7 @@ export function convertQobuzArtist(artist: QobuzArtist): ArtistDetail {
|
||||
const albums: ArtistAlbumSummary[] = [];
|
||||
const epsSingles: ArtistAlbumSummary[] = [];
|
||||
const liveAlbums: ArtistAlbumSummary[] = [];
|
||||
const others: ArtistAlbumSummary[] = [];
|
||||
|
||||
for (const album of albumItems) {
|
||||
const summary = toArtistAlbumSummary(album);
|
||||
@@ -206,6 +213,8 @@ export function convertQobuzArtist(artist: QobuzArtist): ArtistDetail {
|
||||
liveAlbums.push(summary);
|
||||
} else if (isEpOrSingle(album)) {
|
||||
epsSingles.push(summary);
|
||||
} else if (isOtherAlbum(album)) {
|
||||
others.push(summary);
|
||||
} else {
|
||||
albums.push(summary);
|
||||
}
|
||||
@@ -221,6 +230,7 @@ export function convertQobuzArtist(artist: QobuzArtist): ArtistDetail {
|
||||
epsSingles,
|
||||
liveAlbums,
|
||||
compilations: buildCompilationAlbums(artist.tracks_appears_on?.items),
|
||||
others,
|
||||
playlists: toArtistPlaylists(artist.playlists),
|
||||
totalAlbums: artist.albums?.total || artist.albums_count || 0,
|
||||
albumsFetched
|
||||
@@ -238,10 +248,12 @@ export function appendArtistAlbums(
|
||||
const existingAlbumIds = new Set(artist.albums.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 existingOtherIds = new Set(artist.others.map(album => album.id));
|
||||
|
||||
const albums = [...artist.albums];
|
||||
const epsSingles = [...artist.epsSingles];
|
||||
const liveAlbums = [...artist.liveAlbums];
|
||||
const others = [...artist.others];
|
||||
|
||||
for (const album of newAlbums) {
|
||||
const summary = toArtistAlbumSummary(album);
|
||||
@@ -255,6 +267,11 @@ export function appendArtistAlbums(
|
||||
epsSingles.push(summary);
|
||||
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)) {
|
||||
albums.push(summary);
|
||||
existingAlbumIds.add(summary.id);
|
||||
@@ -266,6 +283,7 @@ export function appendArtistAlbums(
|
||||
albums,
|
||||
epsSingles,
|
||||
liveAlbums,
|
||||
others,
|
||||
totalAlbums: totalAlbums ?? artist.totalAlbums,
|
||||
albumsFetched: albumsFetched ?? artist.albumsFetched + newAlbums.length
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
setGreetingEnabled,
|
||||
setCustomGreeting,
|
||||
resetToDefaults,
|
||||
updateLimit,
|
||||
type HomeSettings
|
||||
} from '$lib/stores/homeSettingsStore';
|
||||
import { t } from '$lib/i18n';
|
||||
@@ -57,6 +58,10 @@
|
||||
function handleReset() {
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
function handleLimitChange(key: keyof HomeSettings['limits'], value: number) {
|
||||
updateLimit(key, value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal {isOpen} {onClose} title={$t('home.customizeHome')}>
|
||||
@@ -98,6 +103,92 @@
|
||||
{/if}
|
||||
</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 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-title">{$t('homeSettings.sections')}</div>
|
||||
@@ -388,4 +479,41 @@
|
||||
border-color: var(--text-muted);
|
||||
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>
|
||||
@@ -114,6 +114,7 @@
|
||||
let epsSinglesSection = $state<HTMLDivElement | null>(null);
|
||||
let liveAlbumsSection = $state<HTMLDivElement | null>(null);
|
||||
let compilationsSection = $state<HTMLDivElement | null>(null);
|
||||
let othersSection = $state<HTMLDivElement | null>(null);
|
||||
let playlistsSection = $state<HTMLDivElement | null>(null);
|
||||
let activeJumpSection = $state('about');
|
||||
let jumpObserver: IntersectionObserver | null = null;
|
||||
@@ -149,7 +150,8 @@
|
||||
...artist.albums,
|
||||
...artist.epsSingles,
|
||||
...artist.liveAlbums,
|
||||
...artist.compilations
|
||||
...artist.compilations,
|
||||
...artist.others
|
||||
];
|
||||
loadAllAlbumDownloadStatuses(allAlbums);
|
||||
}
|
||||
@@ -185,7 +187,8 @@
|
||||
...artist.albums,
|
||||
...artist.epsSingles,
|
||||
...artist.liveAlbums,
|
||||
...artist.compilations
|
||||
...artist.compilations,
|
||||
...artist.others
|
||||
];
|
||||
await loadAllAlbumDownloadStatuses(allAlbums);
|
||||
}
|
||||
@@ -339,7 +342,7 @@
|
||||
|
||||
// Truncate bio for collapsed view
|
||||
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);
|
||||
@@ -347,6 +350,7 @@
|
||||
let hasEpsSingles = $derived(artist.epsSingles.length > 0);
|
||||
let hasLiveAlbums = $derived(artist.liveAlbums.length > 0);
|
||||
let hasCompilations = $derived(artist.compilations.length > 0);
|
||||
let hasOthers = $derived(artist.others.length > 0);
|
||||
let hasPlaylists = $derived(artist.playlists.length > 0);
|
||||
let jumpSections = $derived.by(() => [
|
||||
{ id: 'about', label: 'About', el: aboutSection, visible: true },
|
||||
@@ -355,6 +359,7 @@
|
||||
{ id: 'eps', label: 'EPs & Singles', el: epsSinglesSection, visible: hasEpsSingles },
|
||||
{ id: 'live', label: 'Live Albums', el: liveAlbumsSection, visible: hasLiveAlbums },
|
||||
{ id: 'compilations', label: 'Compilations', el: compilationsSection, visible: hasCompilations },
|
||||
{ id: 'others', label: 'Others', el: othersSection, visible: hasOthers },
|
||||
{ id: 'playlists', label: 'Playlists', el: playlistsSection, visible: hasPlaylists },
|
||||
].filter(section => section.visible));
|
||||
|
||||
@@ -747,6 +752,36 @@
|
||||
</div>
|
||||
{/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}
|
||||
<div class="divider"></div>
|
||||
|
||||
|
||||
@@ -107,6 +107,8 @@
|
||||
featuredAlbums: 12
|
||||
};
|
||||
|
||||
let homeLimits = $state(getSettings().limits);
|
||||
|
||||
// Loading states for progressive render
|
||||
let isInitializing = $state(true);
|
||||
let isOverlayVisible = $state(true); // Overlay that fades out when ALL sections ready
|
||||
@@ -215,6 +217,7 @@
|
||||
// Subscribe to home settings changes
|
||||
const unsubscribe = subscribeHomeSettings(() => {
|
||||
homeSettings = getSettings();
|
||||
homeLimits = getSettings().limits;
|
||||
});
|
||||
|
||||
// Load home data
|
||||
@@ -341,7 +344,7 @@
|
||||
return Array.from(counts.entries())
|
||||
.map(([artistId, playCount]) => ({ artistId, playCount }))
|
||||
.sort((a, b) => b.playCount - a.playCount)
|
||||
.slice(0, LIMITS.topArtists);
|
||||
.slice(0, homeLimits.topArtists);
|
||||
}
|
||||
|
||||
async function loadHome() {
|
||||
@@ -373,15 +376,15 @@
|
||||
|
||||
// Start ML data loading FIRST (local SQLite) - this gets the seeds
|
||||
const mlPromise = invoke<HomeSeeds>('reco_get_home_ml', {
|
||||
limitRecentAlbums: LIMITS.recentAlbums,
|
||||
limitContinueTracks: LIMITS.continueTracks,
|
||||
limitTopArtists: LIMITS.topArtists,
|
||||
limitFavorites: Math.max(LIMITS.favoriteAlbums, LIMITS.favoriteTracks)
|
||||
limitRecentAlbums: homeLimits.recentAlbums,
|
||||
limitContinueTracks: homeLimits.continueTracks,
|
||||
limitTopArtists: homeLimits.topArtists,
|
||||
limitFavorites: Math.max(homeLimits.favoriteAlbums, homeLimits.favoriteTracks)
|
||||
});
|
||||
|
||||
// Start Qobuz API calls in parallel (don't await)
|
||||
if (isSectionVisible('newReleases')) {
|
||||
fetchFeaturedAlbums('new-releases', LIMITS.featuredAlbums).then(async albums => {
|
||||
fetchFeaturedAlbums('new-releases', homeLimits.featuredAlbums).then(async albums => {
|
||||
newReleases = albums;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingNewReleases = false;
|
||||
@@ -392,7 +395,7 @@
|
||||
}
|
||||
|
||||
if (isSectionVisible('pressAwards')) {
|
||||
fetchFeaturedAlbums('press-awards', LIMITS.featuredAlbums).then(async albums => {
|
||||
fetchFeaturedAlbums('press-awards', homeLimits.featuredAlbums).then(async albums => {
|
||||
pressAwards = albums;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingPressAwards = false;
|
||||
@@ -403,7 +406,7 @@
|
||||
}
|
||||
|
||||
if (isSectionVisible('mostStreamed')) {
|
||||
fetchFeaturedAlbums('most-streamed', LIMITS.featuredAlbums).then(async albums => {
|
||||
fetchFeaturedAlbums('most-streamed', homeLimits.featuredAlbums).then(async albums => {
|
||||
mostStreamed = albums;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingMostStreamed = false;
|
||||
@@ -414,7 +417,7 @@
|
||||
}
|
||||
|
||||
if (isSectionVisible('qobuzissimes')) {
|
||||
fetchFeaturedAlbums('qobuzissimes', LIMITS.featuredAlbums).then(async albums => {
|
||||
fetchFeaturedAlbums('qobuzissimes', homeLimits.featuredAlbums).then(async albums => {
|
||||
qobuzissimes = albums;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingQobuzissimes = false;
|
||||
@@ -425,7 +428,7 @@
|
||||
}
|
||||
|
||||
if (isSectionVisible('editorPicks')) {
|
||||
fetchFeaturedAlbums('editor-picks', LIMITS.featuredAlbums).then(async albums => {
|
||||
fetchFeaturedAlbums('editor-picks', homeLimits.featuredAlbums).then(async albums => {
|
||||
editorPicks = albums;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingEditorPicks = false;
|
||||
@@ -454,7 +457,7 @@
|
||||
// Recently Played (albums) - start immediately with seeds
|
||||
if (isSectionVisible('recentAlbums')) {
|
||||
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;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingRecentAlbums = false;
|
||||
@@ -466,7 +469,7 @@
|
||||
|
||||
// Top Artists
|
||||
if (isSectionVisible('topArtists')) {
|
||||
fetchArtists(seeds.topArtistIds.slice(0, LIMITS.topArtists)).then(artists => {
|
||||
fetchArtists(seeds.topArtistIds.slice(0, homeLimits.topArtists)).then(artists => {
|
||||
topArtists = artists;
|
||||
loadingTopArtists = false;
|
||||
markSectionFinished();
|
||||
@@ -478,12 +481,12 @@
|
||||
// Favorite Albums
|
||||
if (isSectionVisible('favoriteAlbums')) {
|
||||
// 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([
|
||||
...seeds.favoriteAlbumIds,
|
||||
...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;
|
||||
await loadAllAlbumDownloadStatuses(albums);
|
||||
loadingFavoriteAlbums = false;
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface HomeSection {
|
||||
label: string;
|
||||
visible: boolean;
|
||||
source?: 'qobuz' | 'ml'; // qobuz = editorial, ml = ML-powered recommendations
|
||||
limit?: number; // configurable item count
|
||||
}
|
||||
|
||||
export interface HomeSettings {
|
||||
@@ -30,6 +31,14 @@ export interface HomeSettings {
|
||||
enabled: boolean;
|
||||
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';
|
||||
@@ -52,6 +61,14 @@ const DEFAULT_SETTINGS: HomeSettings = {
|
||||
greeting: {
|
||||
enabled: true,
|
||||
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 {
|
||||
const result: HomeSettings = {
|
||||
sections: [],
|
||||
greeting: saved.greeting ?? DEFAULT_SETTINGS.greeting
|
||||
greeting: saved.greeting ?? DEFAULT_SETTINGS.greeting,
|
||||
limits: { ...DEFAULT_SETTINGS.limits, ...(saved.limits || {}) }
|
||||
};
|
||||
|
||||
// Build section map from saved
|
||||
@@ -304,3 +322,21 @@ export function resetToDefaults(): void {
|
||||
saveSettings();
|
||||
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;
|
||||
}
|
||||
@@ -152,6 +152,13 @@ export interface ArtistDetail {
|
||||
year?: string;
|
||||
quality: string;
|
||||
}[];
|
||||
others: {
|
||||
id: string;
|
||||
title: string;
|
||||
artwork: string;
|
||||
year?: string;
|
||||
quality: string;
|
||||
}[];
|
||||
playlists: {
|
||||
id: number;
|
||||
title: string;
|
||||
|
||||
Reference in new issue
Block a user