web-player: use playlist totals from the API, cache genres and parallelize explore requests

This commit is contained in:
2026-09-24 05:21:29 +02:00
parent b011ccdd5b
commit e89463fb83
10 changed files with 61 additions and 84 deletions
+4
View File
@@ -942,6 +942,10 @@ export interface components {
largeImageKeyName: null | string;
ownerUserId: string;
visibility: components["schemas"]["PlaylistVisibility"];
/** Format: int32 */
trackCount: number | string;
/** Format: double */
durationSeconds: number | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
-2
View File
@@ -9,9 +9,7 @@ export const PLAYLIST_TRACKS_PAGE_SIZE = 200;
export const LIBRARY_PICKER_PAGE_SIZE = 50;
export const LIBRARY_PAGE_SIZE = 50;
export const PLAYLISTS_PAGE_SIZE = 50;
export const PLAYLIST_SUMMARY_TRACKS_PAGE_SIZE = 500;
export const PROFILE_PLAYLISTS_PAGE_SIZE = 50;
export const COUNT_ONLY_PAGE_SIZE = 1;
export const EXPLORE_ALBUMS_PAGE_SIZE = 10;
export const EXPLORE_ALBUMS_LIMIT = 18;
export const HOME_MIXES_LIMIT = 6;
+18
View File
@@ -0,0 +1,18 @@
import type { ApiClient } from '$lib/server/api';
import type { components } from '$lib/api/schema';
type GenreInfo = components['schemas']['GenreResponse'];
const CACHE_TTL_MS = 5 * 60_000;
let cached: { genres: GenreInfo[]; expiresAt: number } | null = null;
export async function getGenres(api: ApiClient): Promise<GenreInfo[]> {
if (cached && cached.expiresAt > Date.now()) return cached.genres;
const { data } = await api.GET('/genres');
if (!data) return cached?.genres ?? [];
cached = { genres: data, expiresAt: Date.now() + CACHE_TTL_MS };
return data;
}
+6 -14
View File
@@ -1,15 +1,6 @@
import { describe, expect, it } from 'vitest';
import type { components } from '$lib/api/schema';
import {
toAlbum,
toDatedTrack,
toLikedTrack,
toMix,
toPage,
toPlaylist,
toPlaylistSummary,
toTrack
} from './mappers';
import { toAlbum, toDatedTrack, toLikedTrack, toMix, toPage, toPlaylist, toTrack } from './mappers';
const trackDto: components['schemas']['TrackApplicationResponse'] = {
id: 't1',
@@ -107,24 +98,25 @@ describe('toPlaylist', () => {
largeImageKeyName: null,
ownerUserId: '42',
visibility: 'Public',
trackCount: '7',
durationSeconds: '90.5',
createdAt: '',
updatedAt: 'u',
coverTrackIds: []
};
it('drops storage keys', () => {
it('drops storage keys and normalizes totals', () => {
expect(toPlaylist(dto)).toEqual({
id: 'p1',
name: 'List',
description: 'd',
visibility: 'Public',
ownerUserId: '42',
trackCount: 7,
durationSeconds: 90.5,
coverTrackIds: [],
updatedAt: 'u'
});
});
it('adds the track count in summaries', () => {
expect(toPlaylistSummary(dto, 7).trackCount).toBe(7);
});
});
describe('toPage', () => {
+3 -5
View File
@@ -1,5 +1,5 @@
import type { components } from '$lib/api/schema';
import type { Album, LikedTrack, Mix, Paged, Playlist, PlaylistSummary, Track } from '$lib/types';
import type { Album, LikedTrack, Mix, Paged, Playlist, Track } from '$lib/types';
type TrackDto = components['schemas']['TrackApplicationResponse'];
type AlbumDto = components['schemas']['AlbumApplicationResponse'];
@@ -75,15 +75,13 @@ export function toPlaylist(dto: PlaylistDto): Playlist {
description: dto.description,
visibility: dto.visibility,
ownerUserId: dto.ownerUserId,
trackCount: Number(dto.trackCount) || 0,
durationSeconds: Number(dto.durationSeconds) || 0,
coverTrackIds: dto.coverTrackIds,
updatedAt: dto.updatedAt
};
}
export function toPlaylistSummary(dto: PlaylistDto, trackCount: number): PlaylistSummary {
return { ...toPlaylist(dto), trackCount };
}
export function toPage<D, T>(dto: PageDto<D>, map: (item: D) => T): Paged<T> {
return {
items: dto.items.map(map),
+2 -4
View File
@@ -38,14 +38,12 @@ export interface Playlist {
description: string | null;
visibility: 'Public' | 'Private';
ownerUserId: string;
trackCount: number;
durationSeconds: number;
coverTrackIds: string[];
updatedAt: string;
}
export interface PlaylistSummary extends Playlist {
trackCount: number;
}
export interface Mix {
id: string;
title: string;