From fb4a8ccc0e46588b3025747a45274ec1d40004d2 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Mon, 21 Sep 2026 00:14:20 +0200 Subject: [PATCH] web-player: rows (ListRow, MediaIdentity, EqBars) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add MediaIdentity.svelte (renames and generalizes TrackTitleCell, purely presentational: art + title + subtitle, no click handling) and ListRow.svelte on top of it (href|onclick, active, size xs/sm/lg, variant row|card, leading/trailing/overlay). Also used by player/TrackInfo... (TrackInfo itself is left for a later pass, which already has an explicit task to touch that file). Migrated to ListRow: SearchResultRow (Explore, 4 groups — component removed), Home (Continue listening, Popular, spotlight, Your playlists), Queue (Up next), Sidebar (playlists). The 6 track tables (AlbumTrackTable, PlaylistTrackTable, library, liked, mixes, LibraryPicker) migrate to MediaIdentity with their own local use:pressable, since TrackList doesn't exist yet. EqBars gains an `overlay` prop (scrim/45 background, on-art color) replacing NowPlaying.svelte (removed). Along the way, fixes 6 plural() calls that a previous pass had left unmigrated (liked, library, mixes, playlists, PlaylistHeader, Home). Deltas documented in the plan's log: Sidebar's active row now uses the accent tint (previously neutral gray); Home's "Your playlists" card loses its redundant author line and its radius becomes rounded-art; Queue rows lose the art's opacity-80. Verified with npm run check/build; no authenticated visual QA (Docker unavailable this session). --- web-player/docs/frontend-refactor-plan.md | 2 +- web-player/src/lib/components/Sidebar.svelte | 31 ++-- .../src/lib/components/player/Queue.svelte | 61 +++---- .../src/lib/components/ui/ContextMenu.svelte | 12 +- .../src/lib/components/ui/EqBars.svelte | 42 +++-- .../src/lib/components/ui/ListRow.svelte | 90 ++++++++++ ...kTitleCell.svelte => MediaIdentity.svelte} | 38 ++-- .../src/lib/components/ui/NowPlaying.svelte | 20 --- .../lib/components/ui/SearchResultRow.svelte | 65 ------- .../[id]/components/AlbumTrackTable.svelte | 29 +-- .../[id]/components/LibraryPicker.svelte | 41 +++-- .../src/routes/(app)/explore/+page.svelte | 75 ++++---- .../src/routes/(app)/library/+page.svelte | 23 ++- .../src/routes/(app)/liked/+page.svelte | 33 ++-- .../src/routes/(app)/mixes/[id]/+page.svelte | 29 +-- .../src/routes/(app)/playlists/+page.svelte | 4 +- .../[id]/components/PlaylistHeader.svelte | 3 +- .../[id]/components/PlaylistTrackTable.svelte | 29 +-- web-player/src/routes/+page.svelte | 170 +++++++----------- 19 files changed, 407 insertions(+), 390 deletions(-) create mode 100644 web-player/src/lib/components/ui/ListRow.svelte rename web-player/src/lib/components/ui/{TrackTitleCell.svelte => MediaIdentity.svelte} (65%) delete mode 100644 web-player/src/lib/components/ui/NowPlaying.svelte delete mode 100644 web-player/src/lib/components/ui/SearchResultRow.svelte diff --git a/web-player/docs/frontend-refactor-plan.md b/web-player/docs/frontend-refactor-plan.md index 0b52aab..02f9b8f 100644 --- a/web-player/docs/frontend-refactor-plan.md +++ b/web-player/docs/frontend-refactor-plan.md @@ -779,7 +779,7 @@ Tareas: | P4 | ✅ | (pendiente) | Decisión tomada sin bloquear: se elimina el botón "adelante" decorativo (TopBar) y el icono `AlignJustify` decorativo (PlayerExtras) por ser código muerto visual. `TransportControls` (shuffle/prev/next/repeat) se deja sin migrar a IconButton a propósito: son iconos sin chrome (sin fondo/hit-box), migrarlos habría añadido una píldora de hover no presente hoy — riesgo visual no verificable sin Docker | | P5 | 🟡 | (pendiente) | Hecho: pressable/clickOutside, toggle.svelte.ts, plural() (+minúsculas, Δ visible), helpers de player.svelte.ts (toQueueItem/isQueueCurrent/playAllOrToggle/playShuffled, addToQueue→playNextItem), navigation.svelte.ts (searchHref/appNavLinks — appNavLinks creado pero su migración a Sidebar/TabsBar se deja para P12 tal como indica esa parte), menus.svelte.ts (createTrackMenu, migrado en Inicio/Explorar/Mezcla). **Pendiente a propósito**: la tarea 5 (eliminar el envoltorio `TrackTarget` de tracks.ts y sus 8 accesores) se difiere — es el cambio de mayor riesgo del plan (toca tipos en Inicio/Explorar/Mezcla/menús de forma transversal) y no se pudo verificar visualmente sin Docker; abordarlo junto con P13 cuando se reescriban esas páginas | | P6 | ✅ | (pendiente) | z-index de AccountMenu unificado a z-(--z-menu) (antes z-20, ahora al mismo nivel que ContextMenu — Δ menor, mejora consistencia); sin QA visual autenticada (Docker no disponible) | -| P7 | ⬜ | | | +| P7 | ✅ | (pendiente) | MediaIdentity queda "puro" (sin onClick) según spec; las 6 tablas de pista (P8) envuelven su propio `use:pressable` hasta que P8 las sustituya por TrackList. Deltas documentados: fila activa de Sidebar ahora usa bg-accent-tint/text-accent-soft (antes gris neutro, ahora consistente con el resto de filas activas); tarjeta "Tus playlists" de Inicio pierde la línea "por {tu nombre}" (redundante, siempre el propio usuario) y su radio pasa de rounded-panel a rounded-art (consistencia con la tarjeta "Continuar"); filas de Queue "A continuación" pierden el opacity-80 del arte (ListRow no expone esa personalización). Sin QA visual autenticada (Docker no disponible) | | P8 | ⬜ | | | | P9 | ⬜ | | | | P10 | ⬜ | | | diff --git a/web-player/src/lib/components/Sidebar.svelte b/web-player/src/lib/components/Sidebar.svelte index 223f487..b2733ff 100644 --- a/web-player/src/lib/components/Sidebar.svelte +++ b/web-player/src/lib/components/Sidebar.svelte @@ -4,7 +4,7 @@ import { liked } from '$lib/player/liked.svelte'; import { createPlaylistModal } from '$lib/playlists.svelte'; import type { SessionUser } from '$lib/types'; - import Artwork from './ui/Artwork.svelte'; + import ListRow from './ui/ListRow.svelte'; import IconButton from './ui/IconButton.svelte'; import Home from '@lucide/svelte/icons/house'; import Compass from '@lucide/svelte/icons/compass'; @@ -92,26 +92,17 @@
{#each playlists as playlist (playlist.id)} {@const active = page.url.pathname === `/playlists/${playlist.id}`} - - -
-
- {playlist.name} -
-
-
+ {active} + size="xs" + title={playlist.name} + coverSrc="/api/playlists/{playlist.id}/cover?size=small&v={encodeURIComponent( + playlist.updatedAt + )}" + trackIds={playlist.coverTrackIds} + class="px-3 py-1.75" + /> {:else} {/each} diff --git a/web-player/src/lib/components/ui/EqBars.svelte b/web-player/src/lib/components/ui/EqBars.svelte index b9610dd..136dd1e 100644 --- a/web-player/src/lib/components/ui/EqBars.svelte +++ b/web-player/src/lib/components/ui/EqBars.svelte @@ -3,21 +3,41 @@ size?: number; barWidth?: number; paused?: boolean; + overlay?: boolean; class?: string; } - let { size = 13, barWidth = 2, paused = false, class: klass = '' }: Props = $props(); + let { + size = 13, + barWidth = 2, + paused = false, + overlay = false, + class: klass = '' + }: Props = $props(); const delays = [0, 0.3, 0.6]; -
- {#each delays as delay (delay)} - - {/each} -
+{#if overlay} +
+ {#each delays as delay (delay)} + + {/each} +
+{:else} +
+ {#each delays as delay (delay)} + + {/each} +
+{/if} diff --git a/web-player/src/lib/components/ui/ListRow.svelte b/web-player/src/lib/components/ui/ListRow.svelte new file mode 100644 index 0000000..83e7b77 --- /dev/null +++ b/web-player/src/lib/components/ui/ListRow.svelte @@ -0,0 +1,90 @@ + + +{#snippet content()} + {@render leading?.()} + + {@render trailing?.()} +{/snippet} + +{#if href} + + {@render content()} + +{:else} +
{})} + {oncontextmenu} + class={classes} + {style} + > + {@render content()} +
+{/if} diff --git a/web-player/src/lib/components/ui/TrackTitleCell.svelte b/web-player/src/lib/components/ui/MediaIdentity.svelte similarity index 65% rename from web-player/src/lib/components/ui/TrackTitleCell.svelte rename to web-player/src/lib/components/ui/MediaIdentity.svelte index 221375f..9d1b76a 100644 --- a/web-player/src/lib/components/ui/TrackTitleCell.svelte +++ b/web-player/src/lib/components/ui/MediaIdentity.svelte @@ -3,25 +3,27 @@ import Artwork from './Artwork.svelte'; import ExplicitBadge from './ExplicitBadge.svelte'; import ArtistLink from './ArtistLink.svelte'; - import { pressable } from '$lib/actions/pressable'; interface Props { - trackId: string | number; + trackId?: string | number; + trackIds?: (string | number)[]; title: string; artist?: string | null; ownerUserId?: string | number | null; coverSrc?: string | null; - size?: 'sm' | 'lg'; + size?: 'xs' | 'sm' | 'lg'; explicit?: boolean; active?: boolean; titleClass?: string; - onClick: () => void; + class?: string; + art?: Snippet; overlay?: Snippet; badge?: Snippet; } let { trackId, + trackIds, title, artist, ownerUserId, @@ -30,23 +32,27 @@ explicit = false, active = false, titleClass = '', - onClick, + class: klass = '', + art, overlay, badge }: Props = $props(); - -
event.stopPropagation()} - class="flex min-w-0 items-center gap-3.5 text-left" -> - - {@render overlay?.()} - +
+ {#if art} + {@render art()} + {:else if trackId !== undefined || coverSrc || trackIds?.length} + + {@render overlay?.()} + + {/if}
{#if explicit} diff --git a/web-player/src/lib/components/ui/NowPlaying.svelte b/web-player/src/lib/components/ui/NowPlaying.svelte deleted file mode 100644 index ff25910..0000000 --- a/web-player/src/lib/components/ui/NowPlaying.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
- {#each delays as delay (delay)} - - {/each} -
diff --git a/web-player/src/lib/components/ui/SearchResultRow.svelte b/web-player/src/lib/components/ui/SearchResultRow.svelte deleted file mode 100644 index a0f2b79..0000000 --- a/web-player/src/lib/components/ui/SearchResultRow.svelte +++ /dev/null @@ -1,65 +0,0 @@ - - -{#snippet content()} - {@render art(artClass)} -
-
- {#if explicit} - - {/if} - {title} -
- {#if subtitle} -
{subtitle}
- {/if} -
- {#if meta} - {meta} - {/if} -{/snippet} - -{#if href} - - {@render content()} - -{:else} - -{/if} diff --git a/web-player/src/routes/(app)/albums/[id]/components/AlbumTrackTable.svelte b/web-player/src/routes/(app)/albums/[id]/components/AlbumTrackTable.svelte index e9e5e4b..437d332 100644 --- a/web-player/src/routes/(app)/albums/[id]/components/AlbumTrackTable.svelte +++ b/web-player/src/routes/(app)/albums/[id]/components/AlbumTrackTable.svelte @@ -8,7 +8,8 @@ import TrackRow from '$lib/components/ui/TrackRow.svelte'; import TrackMeta from '$lib/components/ui/TrackMeta.svelte'; import TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; + import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte'; + import { pressable } from '$lib/actions/pressable'; type AlbumTrack = ApiTrackLike & { duration: number | string; listensCount: number | string }; @@ -34,15 +35,23 @@ {@const active = player.current.id === track.id} playFrom(i)} /> - playFrom(i)} - /> + +
playFrom(i)} + onclick={(event) => event.stopPropagation()} + class="flex min-w-0 flex-1 text-left" + > + +
{fmtTime(Number(track.duration))} {#if isOwner} diff --git a/web-player/src/routes/(app)/albums/[id]/components/LibraryPicker.svelte b/web-player/src/routes/(app)/albums/[id]/components/LibraryPicker.svelte index 5281c8e..b35a965 100644 --- a/web-player/src/routes/(app)/albums/[id]/components/LibraryPicker.svelte +++ b/web-player/src/routes/(app)/albums/[id]/components/LibraryPicker.svelte @@ -8,8 +8,9 @@ import TrackTable from '$lib/components/ui/TrackTable.svelte'; import TrackRow from '$lib/components/ui/TrackRow.svelte'; import TrackMeta from '$lib/components/ui/TrackMeta.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; - import NowPlaying from '$lib/components/ui/NowPlaying.svelte'; + import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte'; + import { pressable } from '$lib/actions/pressable'; + import EqBars from '$lib/components/ui/EqBars.svelte'; type LibraryTrack = ApiTrackLike & { duration: number | string; listensCount: number | string }; @@ -30,21 +31,29 @@ {#each library as track, i (track.id)} {@const active = player.current.id === track.id} - playFrom(i)} + +
playFrom(i)} + onclick={(event) => event.stopPropagation()} + class="flex min-w-0 flex-1 text-left" > - {#snippet overlay()} - {#if player.current.id === track.id} - - {/if} - {/snippet} - + + {#snippet overlay()} + {#if player.current.id === track.id} + + {/if} + {/snippet} + +
{fmtTime(Number(track.duration))}
{#each capped(songRows) as { target: item, seconds }, i (targetId(item))}
  • - togglePlay(i)} oncontextmenu={(e) => openContextMenu(e, item)} > - {#snippet art(artClass)} - - {#if isTargetCurrent(item)} - - {/if} - + {#snippet overlay()} + {#if isTargetCurrent(item)} + + {/if} {/snippet} - + {#snippet trailing()} + + {/snippet} +
  • {/each} @@ -460,16 +462,19 @@
      {#each capped(albums) as album (album.id)}
    • - openAlbumMenu(e, album.id)} > - {#snippet art(artClass)} - + {#snippet trailing()} + {/snippet} - +
    • {/each}
    @@ -489,11 +494,11 @@
      {#each capped(users) as u (u.id)}
    • - - {#snippet art(artClass)} - + + {#snippet art()} + {/snippet} - +
    • {/each}
    @@ -513,22 +518,16 @@
      {#each capped(playlistMatches) as playlist (playlist.id)}
    • - - {#snippet art(artClass)} - - {/snippet} - + size="lg" + coverSrc="/api/playlists/{playlist.id}/cover?size=medium&v={encodeURIComponent( + playlist.updatedAt + )}" + trackIds={playlist.coverTrackIds} + />
    • {/each}
    diff --git a/web-player/src/routes/(app)/library/+page.svelte b/web-player/src/routes/(app)/library/+page.svelte index dd150d0..df7866d 100644 --- a/web-player/src/routes/(app)/library/+page.svelte +++ b/web-player/src/routes/(app)/library/+page.svelte @@ -4,7 +4,7 @@ import X from '@lucide/svelte/icons/x'; import Music from '@lucide/svelte/icons/music'; import { player, toQueueItems } from '$lib/player/player.svelte'; - import { fmtTime, fmtDate } from '$lib/format'; + import { fmtTime, fmtDate, plural } from '$lib/format'; import Page from '$lib/components/ui/Page.svelte'; import PageHeader from '$lib/components/ui/PageHeader.svelte'; import EmptyState from '$lib/components/ui/EmptyState.svelte'; @@ -14,7 +14,8 @@ import TrackRow from '$lib/components/ui/TrackRow.svelte'; import TrackMeta from '$lib/components/ui/TrackMeta.svelte'; import TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; + import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte'; + import { pressable } from '$lib/actions/pressable'; let { data } = $props(); @@ -34,7 +35,7 @@ {#snippet actions()} {#if items.length > 0} @@ -66,12 +67,16 @@ playing={player.playing} onToggle={() => togglePlay(i)} /> - togglePlay(i)} - /> + +
    togglePlay(i)} + onclick={(event) => event.stopPropagation()} + class="flex min-w-0 flex-1 text-left" + > + +
    diff --git a/web-player/src/routes/(app)/liked/+page.svelte b/web-player/src/routes/(app)/liked/+page.svelte index 7f7dd1e..9b2c953 100644 --- a/web-player/src/routes/(app)/liked/+page.svelte +++ b/web-player/src/routes/(app)/liked/+page.svelte @@ -3,7 +3,7 @@ import X from '@lucide/svelte/icons/x'; import { player, isQueueCurrent, playAllOrToggle } from '$lib/player/player.svelte'; import { liked } from '$lib/player/liked.svelte'; - import { fmtDate, fmtTime } from '$lib/format'; + import { fmtDate, fmtTime, plural } from '$lib/format'; import Page from '$lib/components/ui/Page.svelte'; import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte'; import Button from '$lib/components/ui/Button.svelte'; @@ -13,7 +13,8 @@ import TrackRow from '$lib/components/ui/TrackRow.svelte'; import TrackMeta from '$lib/components/ui/TrackMeta.svelte'; import TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; + import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte'; + import { pressable } from '$lib/actions/pressable'; const tracks = $derived(liked.list); const isCurrentQueue = $derived(isQueueCurrent(tracks)); @@ -36,7 +37,7 @@ {#snippet cover()} @@ -73,15 +74,23 @@ playing={player.playing} onToggle={() => playFrom(i)} /> - playFrom(i)} - /> + +
    playFrom(i)} + onclick={(event) => event.stopPropagation()} + class="flex min-w-0 flex-1 text-left" + > + +
    diff --git a/web-player/src/routes/(app)/mixes/[id]/+page.svelte b/web-player/src/routes/(app)/mixes/[id]/+page.svelte index 7461c1f..035fa36 100644 --- a/web-player/src/routes/(app)/mixes/[id]/+page.svelte +++ b/web-player/src/routes/(app)/mixes/[id]/+page.svelte @@ -3,7 +3,7 @@ import Pause from '@lucide/svelte/icons/pause'; import Shuffle from '@lucide/svelte/icons/shuffle'; import { player, isQueueCurrent, playAllOrToggle, playShuffled } from '$lib/player/player.svelte'; - import { fmtTime } from '$lib/format'; + import { fmtTime, plural } from '$lib/format'; import Page from '$lib/components/ui/Page.svelte'; import BackLink from '$lib/components/ui/BackLink.svelte'; import Button from '$lib/components/ui/Button.svelte'; @@ -14,7 +14,8 @@ import TrackRow from '$lib/components/ui/TrackRow.svelte'; import TrackMeta from '$lib/components/ui/TrackMeta.svelte'; import TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; + import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte'; + import { pressable } from '$lib/actions/pressable'; import ContextMenu from '$lib/components/ui/ContextMenu.svelte'; import ListPlus from '@lucide/svelte/icons/list-plus'; import { createTrackMenu } from '$lib/menus.svelte'; @@ -59,7 +60,7 @@ eyebrow="Mezcla" title={mix.title} description={mix.subtitle ?? undefined} - meta="{items.length} {items.length === 1 ? 'canción' : 'canciones'} · {fmtTime(totalSeconds)}" + meta="{plural(items.length, 'canción', 'canciones')} · {fmtTime(totalSeconds)}" > {#snippet cover()} trackMenu.open(e, targets[i])}> playFrom(i)} /> - playFrom(i)} - /> + +
    playFrom(i)} + onclick={(event) => event.stopPropagation()} + class="flex min-w-0 flex-1 text-left" + > + +
    {fmtTime(Number(item.durationSeconds))} diff --git a/web-player/src/routes/(app)/playlists/+page.svelte b/web-player/src/routes/(app)/playlists/+page.svelte index 89567a4..275a01a 100644 --- a/web-player/src/routes/(app)/playlists/+page.svelte +++ b/web-player/src/routes/(app)/playlists/+page.svelte @@ -7,14 +7,14 @@ import EmptyState from '$lib/components/ui/EmptyState.svelte'; import Button from '$lib/components/ui/Button.svelte'; import { createPlaylistModal } from '$lib/playlists.svelte'; - import { fmtDurationLong } from '$lib/format'; + import { fmtDurationLong, plural } from '$lib/format'; let { data } = $props(); const items = $derived(data.playlists.items); const totals = $derived(data.totals); const summary = $derived( - `${totals.playlistCount} ${totals.playlistCount === 1 ? 'Playlist' : 'Playlists'} · ${totals.trackCount} ${totals.trackCount === 1 ? 'Canción' : 'Canciones'} · ${fmtDurationLong(totals.durationSeconds)}` + `${plural(totals.playlistCount, 'playlist', 'playlists')} · ${plural(totals.trackCount, 'canción', 'canciones')} · ${fmtDurationLong(totals.durationSeconds)}` ); diff --git a/web-player/src/routes/(app)/playlists/[id]/components/PlaylistHeader.svelte b/web-player/src/routes/(app)/playlists/[id]/components/PlaylistHeader.svelte index 1fb01d7..4858521 100644 --- a/web-player/src/routes/(app)/playlists/[id]/components/PlaylistHeader.svelte +++ b/web-player/src/routes/(app)/playlists/[id]/components/PlaylistHeader.svelte @@ -2,6 +2,7 @@ import Artwork from '$lib/components/ui/Artwork.svelte'; import Button from '$lib/components/ui/Button.svelte'; import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte'; + import { plural } from '$lib/format'; interface Props { id: string; @@ -30,7 +31,7 @@ }: Props = $props(); const meta = $derived( - `${trackIds.length} ${trackIds.length === 1 ? 'Canción' : 'Canciones'} · ${ + `${plural(trackIds.length, 'canción', 'canciones')} · ${ visibility === 'Public' ? 'Pública' : 'Privada' }` ); diff --git a/web-player/src/routes/(app)/playlists/[id]/components/PlaylistTrackTable.svelte b/web-player/src/routes/(app)/playlists/[id]/components/PlaylistTrackTable.svelte index f5bf4a3..a3aed20 100644 --- a/web-player/src/routes/(app)/playlists/[id]/components/PlaylistTrackTable.svelte +++ b/web-player/src/routes/(app)/playlists/[id]/components/PlaylistTrackTable.svelte @@ -8,7 +8,8 @@ import TrackRow from '$lib/components/ui/TrackRow.svelte'; import TrackMeta from '$lib/components/ui/TrackMeta.svelte'; import TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; + import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte'; + import { pressable } from '$lib/actions/pressable'; type PlaylistTrack = ApiTrackLike & { createdAt: string; @@ -41,15 +42,23 @@ {@const active = player.current.id === track.id} playFrom(i)} {active} /> - playFrom(i)} - /> + +
    playFrom(i)} + onclick={(event) => event.stopPropagation()} + class="flex min-w-0 flex-1 text-left" + > + +
    diff --git a/web-player/src/routes/+page.svelte b/web-player/src/routes/+page.svelte index 1958c08..8e16d07 100644 --- a/web-player/src/routes/+page.svelte +++ b/web-player/src/routes/+page.svelte @@ -6,7 +6,7 @@ import { player, isQueueCurrent, playAllOrToggle, playShuffled } from '$lib/player/player.svelte'; import { pressable } from '$lib/actions/pressable'; import { mergeRecentlyPlayed } from '$lib/recentlyPlayed'; - import { fmtTime, fmtDurationLong, fmtPlays } from '$lib/format'; + import { fmtTime, fmtDurationLong, fmtPlays, plural } from '$lib/format'; import { isTargetCurrent, queueItemForTarget, @@ -19,11 +19,10 @@ targetTitle, type TrackTarget } from '$lib/tracks'; - import ArtistLink from '$lib/components/ui/ArtistLink.svelte'; import Artwork from '$lib/components/ui/Artwork.svelte'; import MixTile from '$lib/components/ui/MixTile.svelte'; import EqBars from '$lib/components/ui/EqBars.svelte'; - import TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte'; + import ListRow from '$lib/components/ui/ListRow.svelte'; import SectionHeading from '$lib/components/ui/SectionHeading.svelte'; import EmptyState from '$lib/components/ui/EmptyState.svelte'; import Button from '$lib/components/ui/Button.svelte'; @@ -72,7 +71,7 @@ spotlightTrackCount === 0 ? spotlightVisibilityLabel : [ - `${spotlightTrackCount} ${spotlightTrackCount === 1 ? 'Canción' : 'Canciones'}`, + plural(spotlightTrackCount, 'canción', 'canciones'), fmtDurationLong(spotlightDurationSeconds), spotlightVisibilityLabel ].join(' · ') @@ -176,26 +175,19 @@
    {#each continueItems as item, i (item.id)} {@const isCurrent = player.current.id === item.id} -
    playContinue(i)} - class="animate-enter rounded-art p-2.25 pr-3.5 transition-colors {isCurrent - ? 'bg-accent-tint' - : 'hover:bg-hover'}" - style="--i:{i}" + playContinue(i)} oncontextmenu={(e) => openContextMenu(e, targetForQueueItem(item))} - > - playContinue(i)} - /> -
    + active={isCurrent} + size="lg" + variant="card" + title={item.title} + subtitle={item.artist} + subtitleHref={item.ownerUserId} + trackId={item.id} + class="animate-enter" + style="--i:{i}" + /> {/each}
    {:else} @@ -254,33 +246,22 @@
    {#each spotlightRows as { target, seconds }, i (targetId(target))} -
    playSpotlightTrack(i)} - class="flex items-center gap-4 rounded-control p-2 text-left transition-colors {isTargetCurrent( - target - ) - ? 'bg-accent-tint' - : 'hover:bg-hover'}" + playSpotlightTrack(i)} + active={isTargetCurrent(target)} + size="sm" + title={targetTitle(target)} + subtitle={targetArtist(target)} + subtitleHref={targetOwnerUserId(target)} > - {i + 1} -
    -
    {i + 1} - {targetTitle(target)} -
    - -
    - {fmtTime(seconds)} -
    + {/snippet} + {#snippet trailing()} + {fmtTime(seconds)} + {/snippet} + {:else}

    Esta playlist todavía no tiene canciones.

    {/each} @@ -314,37 +295,32 @@
    {#each popularTargets as target, i (targetId(target))} {@const isCurrent = isTargetCurrent(target)} -
    playPopular(i)} - class="flex items-center gap-3.5 rounded-control p-2 transition-colors {isCurrent - ? 'bg-accent-tint' - : 'hover:bg-hover'}" + playPopular(i)} oncontextmenu={(e) => openContextMenu(e, target)} + active={isCurrent} + size="sm" + title={targetTitle(target)} + subtitle={targetArtist(target)} + subtitleHref={targetOwnerUserId(target)} + explicit={targetExplicit(target)} + trackId={targetId(target)} > - - {#if isCurrent} - - {:else} - {i + 1} - {/if} - -
    - playPopular(i)} - /> -
    - -
    + {#snippet leading()} + + {#if isCurrent} + + {:else} + {i + 1} + {/if} + + {/snippet} + {#snippet trailing()} + + {/snippet} + {/each}
    {:else} @@ -377,36 +353,18 @@ {#if playlists.length > 0}
    {#each playlists as playlist, i (playlist.id)} -
    goto(`/playlists/${playlist.id}`)} - class="group/card animate-enter flex items-center gap-3.5 rounded-panel p-3 transition-colors hover:bg-hover" + goto(`/playlists/${playlist.id}`)} + size="lg" + variant="card" + title={playlist.name} + subtitle={playlist.description} + coverSrc="/api/playlists/{playlist.id}/cover?size=medium&v={encodeURIComponent( + playlist.updatedAt + )}" + trackIds={playlist.coverTrackIds} style="--i:{i}" - > - -
    -
    - {playlist.name} -
    - {#if playlist.description} -
    {playlist.description}
    - {/if} - -
    -
    + /> {/each}
    {:else}