feat(frontend): show recently played from listening history on home
This commit is contained in:
Vendored
+92
@@ -161,6 +161,23 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/tracks/{id}/cover": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get A Track Cover Image. */
|
||||||
|
get: operations["GetTrackCover"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/tracks/{id}/stream": {
|
"/tracks/{id}/stream": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -264,6 +281,23 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/users/{id}/listening-history": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get A User'S Listening History. */
|
||||||
|
get: operations["GetListeningHistory"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
@@ -908,6 +942,35 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
GetTrackCover: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
size?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
/** @description Not Found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
GetTrackStream: {
|
GetTrackStream: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -1158,4 +1221,33 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
GetListeningHistory: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: number | string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["TrackApplicationResponse"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Not Found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,15 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
|
|||||||
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 8 } }
|
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 8 } }
|
||||||
})
|
})
|
||||||
: Promise.resolve({ data: undefined });
|
: Promise.resolve({ data: undefined });
|
||||||
|
const recentlyPlayedPromise = locals.user
|
||||||
|
? api.GET('/users/{id}/listening-history', { params: { path: { id: locals.user.sub } } })
|
||||||
|
: Promise.resolve({ data: undefined });
|
||||||
|
|
||||||
const [latest, playlists] = await Promise.all([latestPromise, playlistsPromise]);
|
const [latest, playlists, recentlyPlayed] = await Promise.all([
|
||||||
|
latestPromise,
|
||||||
|
playlistsPromise,
|
||||||
|
recentlyPlayedPromise
|
||||||
|
]);
|
||||||
const playlistItems = playlists.data?.items ?? [];
|
const playlistItems = playlists.data?.items ?? [];
|
||||||
|
|
||||||
const covers = await Promise.all(
|
const covers = await Promise.all(
|
||||||
@@ -28,6 +35,7 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
|
|||||||
return {
|
return {
|
||||||
latest: latest.data?.items ?? [],
|
latest: latest.data?.items ?? [],
|
||||||
playlists: playlistItems,
|
playlists: playlistItems,
|
||||||
trackIds: Object.fromEntries(covers) as Record<string, string[]>
|
trackIds: Object.fromEntries(covers) as Record<string, string[]>,
|
||||||
|
recentlyPlayed: recentlyPlayed.data ?? []
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,22 @@
|
|||||||
const latest = $derived(data.latest);
|
const latest = $derived(data.latest);
|
||||||
const playlists = $derived(data.playlists);
|
const playlists = $derived(data.playlists);
|
||||||
const trackIds = $derived(data.trackIds);
|
const trackIds = $derived(data.trackIds);
|
||||||
|
const recentlyPlayed = $derived.by(() => {
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const merged: { id: string; title: string }[] = [];
|
||||||
|
for (const t of player.recentlyPlayed) {
|
||||||
|
const id = String(t.id);
|
||||||
|
if (seen.has(id)) continue;
|
||||||
|
seen.add(id);
|
||||||
|
merged.push({ id, title: t.title });
|
||||||
|
}
|
||||||
|
for (const t of data.recentlyPlayed) {
|
||||||
|
if (seen.has(t.id)) continue;
|
||||||
|
seen.add(t.id);
|
||||||
|
merged.push({ id: t.id, title: t.title });
|
||||||
|
}
|
||||||
|
return merged.slice(0, 15);
|
||||||
|
});
|
||||||
|
|
||||||
const greeting = (() => {
|
const greeting = (() => {
|
||||||
const h = new Date().getHours();
|
const h = new Date().getHours();
|
||||||
@@ -41,6 +57,12 @@
|
|||||||
if (player.current.id === track.id) player.toggle();
|
if (player.current.id === track.id) player.toggle();
|
||||||
else player.playQueue(latest, index);
|
else player.playQueue(latest, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function playRecent(index: number) {
|
||||||
|
const track = recentlyPlayed[index];
|
||||||
|
if (player.current.id === track.id) player.toggle();
|
||||||
|
else player.playQueue(recentlyPlayed, index);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -61,19 +83,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if player.recentlyPlayed.length > 0}
|
{#if recentlyPlayed.length > 0}
|
||||||
<section class="px-8 pb-7 pt-4">
|
<section class="px-8 pb-3 pt-6">
|
||||||
<h2 class="mb-4 font-display text-xl font-bold">Escuchado recientemente</h2>
|
<h2 class="mb-4 font-display text-xl font-bold">Escuchado recientemente</h2>
|
||||||
<div class="grid max-w-[1400px] grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-3.5">
|
<div class="grid max-w-[1400px] grid-cols-[repeat(auto-fill,minmax(260px,1fr))] gap-3.5">
|
||||||
{#each player.recentlyPlayed as track (track.id)}
|
{#each recentlyPlayed as track, i (track.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => player.playTrack(track.id)}
|
onclick={() => playRecent(i)}
|
||||||
class="flex items-center gap-3.5 rounded-xl bg-[var(--mf-surface)] p-2 text-left transition hover:bg-[var(--mf-surface-hover)]"
|
class="flex items-center gap-3.5 rounded-xl bg-[var(--mf-surface)] p-2 text-left transition hover:bg-[var(--mf-surface-hover)]"
|
||||||
>
|
>
|
||||||
<Cover
|
<Cover
|
||||||
trackId={track.id}
|
trackId={track.id}
|
||||||
hue={track.hue}
|
hue={hueFor(track.id)}
|
||||||
size="small"
|
size="small"
|
||||||
alt={track.title}
|
alt={track.title}
|
||||||
class="h-14 w-14 flex-shrink-0 rounded-lg"
|
class="h-14 w-14 flex-shrink-0 rounded-lg"
|
||||||
@@ -84,7 +106,6 @@
|
|||||||
</Cover>
|
</Cover>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="truncate text-sm font-semibold text-[var(--mf-text)]">{track.title}</div>
|
<div class="truncate text-sm font-semibold text-[var(--mf-text)]">{track.title}</div>
|
||||||
<div class="truncate text-[12.5px] text-[var(--mf-text-3)]">{track.artist || '—'}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -92,7 +113,7 @@
|
|||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section class="px-8 pb-10 pt-6">
|
<section class="px-8 pb-3 pt-3">
|
||||||
<h2 class="mb-4 font-display text-xl font-bold">Novedades</h2>
|
<h2 class="mb-4 font-display text-xl font-bold">Novedades</h2>
|
||||||
{#if latest.length > 0}
|
{#if latest.length > 0}
|
||||||
<div class="grid max-w-[1400px] grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-4">
|
<div class="grid max-w-[1400px] grid-cols-[repeat(auto-fill,minmax(160px,1fr))] gap-4">
|
||||||
@@ -142,8 +163,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="px-8 pb-16 pt-2">
|
<section class="px-8 pb-16 pt-3">
|
||||||
<h2 class="mb-4 font-display text-xl font-bold">Tus listas</h2>
|
<h2 class="mb-4 font-display text-xl font-bold">Mis listas</h2>
|
||||||
{#if playlists.length > 0}
|
{#if playlists.length > 0}
|
||||||
<div class="grid max-w-[1400px] grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-4">
|
<div class="grid max-w-[1400px] grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-4">
|
||||||
{#each playlists as playlist, i (playlist.id)}
|
{#each playlists as playlist, i (playlist.id)}
|
||||||
|
|||||||
Reference in New Issue
Block a user