web-player: normalize tracks, albums and playlists into single domain types at the server boundary

This commit is contained in:
2026-09-24 05:09:37 +02:00
parent 37fc229bf6
commit 5b2a0e4d46
41 changed files with 526 additions and 452 deletions
@@ -12,7 +12,7 @@
import ListMusic from '@lucide/svelte/icons/list-music';
function seek(value: number) {
if (player.current.duration > 0) player.seekFraction(value / player.current.duration);
if (player.duration > 0) player.seekFraction(value / player.duration);
}
</script>
@@ -39,13 +39,13 @@
</span>
<Slider
value={player.progress}
max={player.current.duration}
max={player.duration}
label="Barra de progreso"
oninput={seek}
variant="seek"
/>
<span class="min-w-8 text-xs text-fg-2 tabular-nums">
{fmtTime(player.current.duration)}
{fmtTime(player.duration)}
</span>
</div>
</div>
@@ -53,7 +53,7 @@
</IconButton>
</div>
{#if player.current.id}
{#if player.current}
<p class="mb-3 text-eyebrow text-fg-2">Reproduciendo</p>
<div class="mb-6 surface-active rounded-control p-2.5">
<MediaIdentity
@@ -13,27 +13,20 @@
let { compact = false }: Props = $props();
const isLiked = $derived(player.current.id !== '' && liked.isLiked(player.current.id));
const current = $derived(player.current);
const isLiked = $derived(current !== null && liked.isLiked(current.id));
function toggleLike() {
if (!player.current.id) return;
liked.toggle({
id: player.current.id,
title: player.current.title,
artist: player.current.artist,
explicit: player.current.explicit,
ownerUserId: player.current.ownerUserId,
duration: player.current.duration
});
if (current) liked.toggle(current);
}
</script>
<div class="flex min-w-0 items-center gap-2.5 md:gap-3">
{#if player.current.id}
{#if current}
<Artwork
trackIds={[player.current.id]}
trackIds={[current.id]}
size="sm"
alt={player.current.title}
alt={current.title}
class="shrink-0 md:size-cover-md"
/>
{:else}
@@ -41,14 +34,14 @@
{/if}
<div class="min-w-0">
<div class="flex min-w-0 items-center gap-1.5">
{#if player.current.explicit}
{#if current?.explicit}
<ExplicitBadge />
{/if}
<span class="truncate text-sm text-fg">{player.current.title}</span>
<span class="truncate text-sm text-fg">{current?.title ?? ''}</span>
</div>
<ArtistLink
name={player.current.artist || '—'}
ownerUserId={player.current.ownerUserId}
name={current?.artist || '—'}
ownerUserId={current?.ownerUserId}
class="mt-0.5 text-xs text-fg-2"
/>
</div>
@@ -1,18 +1,9 @@
<script lang="ts" module>
import type { Track } from '$lib/types';
export type TrackListColumn = 'added' | 'uploaded' | 'plays';
export interface TrackListTrack {
id: string | number;
title: string;
artist?: string | null;
ownerUserId?: string | number | null;
isExplicit?: boolean;
explicit?: boolean;
duration?: number | string;
listensCount?: number | string;
addedAt?: string | number;
uploadedAt?: string | number;
}
export type TrackListTrack = Track & { date?: string | number };
const COLUMN_PRESET: Record<TrackListColumn, { label: string; width: string }> = {
added: { label: 'Añadida', width: '124px' },
@@ -108,7 +99,7 @@
</div>
<div class="mt-1 flex flex-col gap-0.5" role="list">
{#each tracks as track, i (track.id)}
{@const active = player.current.id === track.id}
{@const active = player.currentId === track.id}
<div
role="listitem"
@@ -145,7 +136,7 @@
title={track.title}
artist={track.artist}
ownerUserId={track.ownerUserId}
explicit={track.isExplicit ?? track.explicit ?? false}
explicit={track.explicit}
{active}
>
{#snippet overlay()}
@@ -158,16 +149,14 @@
{#each columns as column (column)}
<span class="hidden truncate text-center text-xs text-fg-2 tabular-nums sm:block">
{#if column === 'plays'}
{Number(track.listensCount ?? 0)}
{:else if column === 'added' && track.addedAt}
{fmtDate(track.addedAt)}
{:else if column === 'uploaded' && track.uploadedAt}
{fmtDate(track.uploadedAt)}
{track.listensCount}
{:else if track.date}
{fmtDate(track.date)}
{/if}
</span>
{/each}
<span class="truncate text-center text-xs text-fg-2 tabular-nums">
{fmtTime(Number(track.duration))}
{fmtTime(track.duration)}
</span>
{#if rowAction}
{#if rowAction.action}