MediaCard pasa a construir su propio arte (src/trackIds vía Artwork size="fill", o un snippet art a medida) y su botón de reproducir (onPlay, con scrim y PlayButton glass en hover) en vez de depender de que cada wrapper le pase un snippet. Sustituye a AlbumCard, PlaylistCard y MixTile, que se borran junto con MediaGrid.svelte (sus grids pasan a los @utility grid-cards/grid-cards-lg/grid-tiles/ grid-wide de P1). format.ts gana albumMeta() y playlistMeta() (antes cálculos ad-hoc dentro de cada tarjeta). El tile de género en Explorar pasa de un style= con el gradiente inline a la receta @utility genre-tile + --tile-hue. De paso, corrige la pluralización "seguidor/es" del perfil de usuario que quedó suelta. Delta documentado: el hover-zoom del arte y el scrim+PlayButton (antes exclusivos de MixTile) pasan a ser comportamiento de MediaCard condicionado a la prop onPlay — se aplicarían a cualquier tarjeta futura que la use, por consistencia. Verificado con npm run check/build; sin QA visual autenticada (Docker no disponible en esta sesión).
102 lines
2.8 KiB
Svelte
102 lines
2.8 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from 'svelte';
|
|
import { enhance } from '$app/forms';
|
|
import ListMusic from '@lucide/svelte/icons/list-music';
|
|
import Page from '$lib/components/ui/Page.svelte';
|
|
import MediaCard from '$lib/components/ui/MediaCard.svelte';
|
|
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
|
import Button from '$lib/components/ui/Button.svelte';
|
|
import Avatar from '$lib/components/ui/Avatar.svelte';
|
|
import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte';
|
|
import SectionHeading from '$lib/components/ui/SectionHeading.svelte';
|
|
import { plural, playlistMeta } from '$lib/format';
|
|
|
|
let { data, form } = $props();
|
|
|
|
const profile = $derived(data.profile);
|
|
const playlists = $derived(data.playlists);
|
|
const canFollow = $derived(!data.isOwnProfile && !data.isAnonymous);
|
|
|
|
let following = $state(untrack(() => data.isFollowing));
|
|
let submitting = $state(false);
|
|
|
|
$effect(() => {
|
|
if (form && 'following' in form && typeof form.following === 'boolean')
|
|
following = form.following;
|
|
});
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>{profile.name}</title>
|
|
<meta name="description" content="Perfil de {profile.name} en Musify." />
|
|
</svelte:head>
|
|
|
|
{#snippet followAction()}
|
|
<form
|
|
method="POST"
|
|
action={following ? '?/unfollow' : '?/follow'}
|
|
use:enhance={() => {
|
|
submitting = true;
|
|
return async ({ update }) => {
|
|
await update();
|
|
submitting = false;
|
|
};
|
|
}}
|
|
>
|
|
<Button
|
|
type="submit"
|
|
size="sm"
|
|
variant={following ? 'secondary' : 'primary'}
|
|
disabled={submitting}
|
|
>
|
|
{following ? 'Dejar de seguir' : 'Seguir'}
|
|
</Button>
|
|
</form>
|
|
{/snippet}
|
|
|
|
<Page>
|
|
<CollectionHeader
|
|
eyebrow="Perfil"
|
|
title={profile.name}
|
|
meta="{plural(
|
|
Number(profile.followersCount),
|
|
'seguidor',
|
|
'seguidores'
|
|
)} · {profile.followingCount} siguiendo"
|
|
align="center"
|
|
actions={canFollow ? followAction : undefined}
|
|
>
|
|
{#snippet cover()}
|
|
<Avatar name={profile.name} src={profile.profilePictureUrl} size="hero" />
|
|
{/snippet}
|
|
</CollectionHeader>
|
|
|
|
<div class="mt-9">
|
|
<SectionHeading title="Playlists públicas" subtitle="Creadas y compartidas por este usuario" />
|
|
|
|
{#if playlists.length > 0}
|
|
<div class="mt-6 grid-cards-lg">
|
|
{#each playlists as playlist, i (playlist.id)}
|
|
<MediaCard
|
|
href="/playlists/{playlist.id}"
|
|
title={playlist.name}
|
|
subtitle={playlistMeta(Number(playlist.trackCount))}
|
|
trackIds={playlist.coverTrackIds}
|
|
src="/api/playlists/{playlist.id}/cover?size=large&v={encodeURIComponent(
|
|
playlist.updatedAt
|
|
)}"
|
|
index={i}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<EmptyState
|
|
icon={ListMusic}
|
|
title="Sin playlists públicas"
|
|
description="Este usuario todavía no ha compartido ninguna playlist pública."
|
|
class="mt-6"
|
|
/>
|
|
{/if}
|
|
</div>
|
|
</Page>
|