web-player: track tables (TrackList)
Add TrackList.svelte, replacing TrackTable+TrackRow+TrackIndexCell+
TrackMeta and the 6 track tables (library, liked, mix, album,
playlist, album's library picker): declarative columns
(added/uploaded/plays) with fixed widths as module constants, an
index with an EQ indicator for the active track (or an overlay on the
art when index={false}, for the library picker), and a rowAction that
builds the form+IconButton (removeTrack/addTrack/deleteTrack) or a
direct onclick (Like).
AlbumTrackTable, PlaylistTrackTable and LibraryPicker are inlined
into their pages and removed, along with the 4 components TrackList
replaces.
Decision (recommended by the plan itself, not blocking): remove the
"Album" column, which in all 3 tables that had it always showed "—"
(a placeholder with no real data).
Verified with npm run check/build; no authenticated visual QA (Docker
unavailable this session).
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { player, toQueueItems, isQueueCurrent, playAllOrToggle } from '$lib/player/player.svelte';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
import Page from '$lib/components/ui/Page.svelte';
|
||||
import Modal from '$lib/components/ui/Modal.svelte';
|
||||
import Alert from '$lib/components/ui/Alert.svelte';
|
||||
import AlbumForm from '$lib/components/ui/AlbumForm.svelte';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import SectionHeading from '$lib/components/ui/SectionHeading.svelte';
|
||||
import TrackList from '$lib/components/ui/TrackList.svelte';
|
||||
import AlbumHeader from './components/AlbumHeader.svelte';
|
||||
import AlbumTrackTable from './components/AlbumTrackTable.svelte';
|
||||
import LibraryPicker from './components/LibraryPicker.svelte';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -24,6 +26,14 @@
|
||||
function playAll() {
|
||||
playAllOrToggle(toQueueItems(tracks));
|
||||
}
|
||||
|
||||
function playTrackFrom(index: number) {
|
||||
player.playOrToggle(toQueueItems(tracks), index);
|
||||
}
|
||||
|
||||
function playLibraryFrom(index: number) {
|
||||
player.playOrToggle(toQueueItems(library), index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -50,11 +60,26 @@
|
||||
{/if}
|
||||
|
||||
{#if tracks.length > 0}
|
||||
<AlbumTrackTable {tracks} {isOwner} />
|
||||
<TrackList
|
||||
{tracks}
|
||||
onPlay={playTrackFrom}
|
||||
rowAction={isOwner
|
||||
? { action: '?/removeTrack', icon: X, label: 'Quitar del álbum' }
|
||||
: undefined}
|
||||
class="mt-6 sm:mt-8"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if isOwner && library.length > 0}
|
||||
<LibraryPicker {library} />
|
||||
<div class="mt-10 sm:mt-12">
|
||||
<SectionHeading title="Tus canciones subidas" />
|
||||
<TrackList
|
||||
tracks={library}
|
||||
index={false}
|
||||
onPlay={playLibraryFrom}
|
||||
rowAction={{ action: '?/addTrack', icon: Plus, label: 'Añadir al álbum' }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</Page>
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import { player, toQueueItems, type ApiTrackLike } from '$lib/player/player.svelte';
|
||||
import { fmtTime } from '$lib/format';
|
||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||
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 TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte';
|
||||
import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte';
|
||||
import { pressable } from '$lib/actions/pressable';
|
||||
|
||||
type AlbumTrack = ApiTrackLike & { duration: number | string; listensCount: number | string };
|
||||
|
||||
interface Props {
|
||||
tracks: AlbumTrack[];
|
||||
isOwner: boolean;
|
||||
}
|
||||
|
||||
let { tracks, isOwner }: Props = $props();
|
||||
|
||||
function playFrom(index: number) {
|
||||
player.playOrToggle(toQueueItems(tracks), index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<TrackTable
|
||||
index
|
||||
action={isOwner}
|
||||
meta={[{ label: 'Escuchas', width: '100px' }]}
|
||||
class="mt-6 sm:mt-8"
|
||||
>
|
||||
{#each tracks as track, i (track.id)}
|
||||
{@const active = player.current.id === track.id}
|
||||
<TrackRow {active}>
|
||||
<TrackIndexCell index={i} {active} playing={player.playing} onToggle={() => playFrom(i)} />
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
use:pressable={() => playFrom(i)}
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class="flex min-w-0 flex-1 text-left"
|
||||
>
|
||||
<MediaIdentity
|
||||
trackId={track.id}
|
||||
title={track.title}
|
||||
artist={track.artist}
|
||||
ownerUserId={track.ownerUserId}
|
||||
explicit={track.isExplicit}
|
||||
{active}
|
||||
/>
|
||||
</div>
|
||||
<TrackMeta class="hidden sm:block">{Number(track.listensCount)}</TrackMeta>
|
||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||
{#if isOwner}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/removeTrack"
|
||||
use:enhance={() =>
|
||||
async ({ update }) =>
|
||||
update()}
|
||||
>
|
||||
<input type="hidden" name="trackId" value={track.id} />
|
||||
<IconButton type="submit" label="Quitar del álbum" size="xs" revealOnHover>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</IconButton>
|
||||
</form>
|
||||
{/if}
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
@@ -1,74 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
import { player, toQueueItems, type ApiTrackLike } from '$lib/player/player.svelte';
|
||||
import { fmtTime } from '$lib/format';
|
||||
import SectionHeading from '$lib/components/ui/SectionHeading.svelte';
|
||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||
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 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 };
|
||||
|
||||
interface Props {
|
||||
library: LibraryTrack[];
|
||||
}
|
||||
|
||||
let { library }: Props = $props();
|
||||
|
||||
function playFrom(index: number) {
|
||||
player.playOrToggle(toQueueItems(library), index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mt-10 sm:mt-12">
|
||||
<SectionHeading title="Tus canciones subidas" />
|
||||
<TrackTable action meta={[{ label: 'Escuchas', width: '100px' }]}>
|
||||
{#each library as track, i (track.id)}
|
||||
{@const active = player.current.id === track.id}
|
||||
<TrackRow {active}>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
use:pressable={() => playFrom(i)}
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class="flex min-w-0 flex-1 text-left"
|
||||
>
|
||||
<MediaIdentity
|
||||
trackId={track.id}
|
||||
title={track.title}
|
||||
artist={track.artist}
|
||||
ownerUserId={track.ownerUserId}
|
||||
explicit={track.isExplicit}
|
||||
{active}
|
||||
>
|
||||
{#snippet overlay()}
|
||||
{#if player.current.id === track.id}
|
||||
<EqBars overlay paused={!player.playing} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</MediaIdentity>
|
||||
</div>
|
||||
<TrackMeta class="hidden sm:block">{Number(track.listensCount)}</TrackMeta>
|
||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/addTrack"
|
||||
use:enhance={() =>
|
||||
async ({ update }) =>
|
||||
update()}
|
||||
>
|
||||
<input type="hidden" name="trackId" value={track.id} />
|
||||
<IconButton type="submit" label="Añadir al álbum" size="xs" revealOnHover>
|
||||
<Plus class="h-3.5 w-3.5" />
|
||||
</IconButton>
|
||||
</form>
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
</div>
|
||||
@@ -1,26 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import Upload from '@lucide/svelte/icons/upload';
|
||||
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, plural } from '$lib/format';
|
||||
import { 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';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||
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 TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte';
|
||||
import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte';
|
||||
import { pressable } from '$lib/actions/pressable';
|
||||
import TrackList from '$lib/components/ui/TrackList.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
const items = $derived(data.tracks.items);
|
||||
const total = $derived(Number(data.tracks.totalItemCount));
|
||||
const listTracks = $derived(items.map((track) => ({ ...track, uploadedAt: track.createdAt })));
|
||||
|
||||
function togglePlay(index: number) {
|
||||
player.playOrToggle(toQueueItems(items), index);
|
||||
@@ -48,54 +42,13 @@
|
||||
</PageHeader>
|
||||
|
||||
{#if items.length > 0}
|
||||
<TrackTable
|
||||
index
|
||||
action
|
||||
meta={[
|
||||
{ label: 'Álbum', width: '76px' },
|
||||
{ label: 'Subida', width: '124px' },
|
||||
{ label: 'Escuchas', width: '106px' }
|
||||
]}
|
||||
<TrackList
|
||||
tracks={listTracks}
|
||||
columns={['uploaded', 'plays']}
|
||||
onPlay={togglePlay}
|
||||
rowAction={{ action: '?/deleteTrack', icon: X, label: 'Borrar canción' }}
|
||||
class="mt-7 sm:mt-8"
|
||||
>
|
||||
{#each items as track, i (track.id)}
|
||||
{@const active = player.current.id === track.id}
|
||||
<TrackRow {active}>
|
||||
<TrackIndexCell
|
||||
index={i}
|
||||
{active}
|
||||
playing={player.playing}
|
||||
onToggle={() => togglePlay(i)}
|
||||
/>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
use:pressable={() => togglePlay(i)}
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class="flex min-w-0 flex-1 text-left"
|
||||
>
|
||||
<MediaIdentity trackId={track.id} title={track.title} {active} />
|
||||
</div>
|
||||
<TrackMeta class="hidden sm:block">—</TrackMeta>
|
||||
<TrackMeta class="hidden sm:block">{fmtDate(track.createdAt)}</TrackMeta>
|
||||
<TrackMeta class="hidden sm:block">{Number(track.listensCount)}</TrackMeta>
|
||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/deleteTrack"
|
||||
use:enhance={() =>
|
||||
async ({ update }) =>
|
||||
update()}
|
||||
>
|
||||
<input type="hidden" name="trackId" value={track.id} />
|
||||
<IconButton type="submit" label="Borrar canción" size="xs" revealOnHover>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</IconButton>
|
||||
</form>
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
/>
|
||||
{:else}
|
||||
<EmptyState
|
||||
icon={Music}
|
||||
|
||||
@@ -3,20 +3,15 @@
|
||||
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, plural } from '$lib/format';
|
||||
import { 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';
|
||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
||||
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 TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte';
|
||||
import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte';
|
||||
import { pressable } from '$lib/actions/pressable';
|
||||
import TrackList from '$lib/components/ui/TrackList.svelte';
|
||||
|
||||
const tracks = $derived(liked.list);
|
||||
const listTracks = $derived(tracks.map((track) => ({ ...track, addedAt: track.likedAt })));
|
||||
const isCurrentQueue = $derived(isQueueCurrent(tracks));
|
||||
|
||||
function playFrom(index: number) {
|
||||
@@ -55,57 +50,17 @@
|
||||
</CollectionHeader>
|
||||
|
||||
{#if tracks.length > 0}
|
||||
<TrackTable
|
||||
index
|
||||
action
|
||||
meta={[
|
||||
{ label: 'Álbum', width: '76px' },
|
||||
{ label: 'Añadida', width: '124px' },
|
||||
{ label: 'Escuchas', width: '106px' }
|
||||
]}
|
||||
<TrackList
|
||||
tracks={listTracks}
|
||||
columns={['added', 'plays']}
|
||||
onPlay={playFrom}
|
||||
rowAction={{
|
||||
onclick: (track) => liked.toggle({ ...track, artist: track.artist ?? undefined }),
|
||||
icon: X,
|
||||
label: 'Quitar de Me gusta'
|
||||
}}
|
||||
class="mt-6 sm:mt-8"
|
||||
>
|
||||
{#each tracks as track, i (track.id)}
|
||||
{@const active = player.current.id === track.id}
|
||||
<TrackRow {active}>
|
||||
<TrackIndexCell
|
||||
index={i}
|
||||
{active}
|
||||
playing={player.playing}
|
||||
onToggle={() => playFrom(i)}
|
||||
/>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
use:pressable={() => playFrom(i)}
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class="flex min-w-0 flex-1 text-left"
|
||||
>
|
||||
<MediaIdentity
|
||||
trackId={track.id}
|
||||
title={track.title}
|
||||
artist={track.artist}
|
||||
ownerUserId={track.ownerUserId}
|
||||
explicit={track.explicit}
|
||||
{active}
|
||||
/>
|
||||
</div>
|
||||
<TrackMeta class="hidden sm:block">—</TrackMeta>
|
||||
<TrackMeta class="hidden sm:block">{fmtDate(track.likedAt)}</TrackMeta>
|
||||
<TrackMeta class="hidden sm:block">{Number(track.listensCount ?? 0)}</TrackMeta>
|
||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||
<IconButton
|
||||
label="Quitar de Me gusta"
|
||||
size="xs"
|
||||
revealOnHover
|
||||
onclick={() => liked.toggle(track)}
|
||||
>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</IconButton>
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
/>
|
||||
{:else}
|
||||
<EmptyState
|
||||
icon={Heart}
|
||||
|
||||
@@ -10,24 +10,12 @@
|
||||
import Alert from '$lib/components/ui/Alert.svelte';
|
||||
import Artwork from '$lib/components/ui/Artwork.svelte';
|
||||
import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte';
|
||||
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 TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte';
|
||||
import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte';
|
||||
import { pressable } from '$lib/actions/pressable';
|
||||
import TrackList from '$lib/components/ui/TrackList.svelte';
|
||||
import ContextMenu from '$lib/components/ui/ContextMenu.svelte';
|
||||
import ListPlus from '@lucide/svelte/icons/list-plus';
|
||||
import { createTrackMenu } from '$lib/menus.svelte';
|
||||
import { mixItemKey, mixItemTarget } from '$lib/mixes';
|
||||
import {
|
||||
isTargetCurrent,
|
||||
queueItemForTarget,
|
||||
targetArtist,
|
||||
targetId,
|
||||
targetListensCount,
|
||||
targetTitle
|
||||
} from '$lib/tracks';
|
||||
import { mixItemTarget } from '$lib/mixes';
|
||||
import { queueItemForTarget } from '$lib/tracks';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -35,6 +23,15 @@
|
||||
const items = $derived(mix.items);
|
||||
const targets = $derived(items.map(mixItemTarget));
|
||||
const queue = $derived(targets.map(queueItemForTarget));
|
||||
const listTracks = $derived(
|
||||
items.map((item) => ({
|
||||
id: item.trackId,
|
||||
title: item.title,
|
||||
artist: item.artist,
|
||||
duration: item.durationSeconds,
|
||||
listensCount: item.listensCount
|
||||
}))
|
||||
);
|
||||
|
||||
const totalSeconds = $derived(
|
||||
items.reduce((total, item) => total + Number(item.durationSeconds), 0)
|
||||
@@ -96,31 +93,13 @@
|
||||
<Alert tone="danger" class="mt-4">{form.message}</Alert>
|
||||
{/if}
|
||||
|
||||
<TrackTable index meta={[{ label: 'Escuchas', width: '100px' }]} class="mt-6 sm:mt-8">
|
||||
{#each items as item, i (mixItemKey(item))}
|
||||
{@const active = isTargetCurrent(targets[i])}
|
||||
<TrackRow {active} oncontextmenu={(e) => trackMenu.open(e, targets[i])}>
|
||||
<TrackIndexCell index={i} {active} playing={player.playing} onToggle={() => playFrom(i)} />
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
use:pressable={() => playFrom(i)}
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class="flex min-w-0 flex-1 text-left"
|
||||
>
|
||||
<MediaIdentity
|
||||
trackId={targetId(targets[i])}
|
||||
title={targetTitle(targets[i])}
|
||||
artist={targetArtist(targets[i])}
|
||||
{active}
|
||||
/>
|
||||
</div>
|
||||
<TrackMeta class="hidden sm:block">{Number(targetListensCount(targets[i]) ?? 0)}</TrackMeta>
|
||||
<TrackMeta>{fmtTime(Number(item.durationSeconds))}</TrackMeta>
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
<TrackList
|
||||
tracks={listTracks}
|
||||
columns={['plays']}
|
||||
onPlay={playFrom}
|
||||
oncontextmenu={(e, _track, i) => trackMenu.open(e, targets[i])}
|
||||
class="mt-6 sm:mt-8"
|
||||
/>
|
||||
</Page>
|
||||
|
||||
{#if trackMenu.state}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<script lang="ts">
|
||||
import ListMusic from '@lucide/svelte/icons/list-music';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import { player, toQueueItems, isQueueCurrent, playAllOrToggle } from '$lib/player/player.svelte';
|
||||
import Page from '$lib/components/ui/Page.svelte';
|
||||
import Modal from '$lib/components/ui/Modal.svelte';
|
||||
import PlaylistForm from '$lib/components/ui/PlaylistForm.svelte';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
||||
import TrackList from '$lib/components/ui/TrackList.svelte';
|
||||
import PlaylistHeader from './components/PlaylistHeader.svelte';
|
||||
import PlaylistTrackTable from './components/PlaylistTrackTable.svelte';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
const playlist = $derived(data.playlist);
|
||||
const tracks = $derived(data.tracks);
|
||||
const listTracks = $derived(tracks.map((track) => ({ ...track, addedAt: track.createdAt })));
|
||||
|
||||
let editing = $state(false);
|
||||
let confirmingDelete = $state(false);
|
||||
@@ -22,6 +24,10 @@
|
||||
function playAll() {
|
||||
playAllOrToggle(toQueueItems(tracks));
|
||||
}
|
||||
|
||||
function playFrom(index: number) {
|
||||
player.playOrToggle(toQueueItems(tracks), index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -44,7 +50,13 @@
|
||||
/>
|
||||
|
||||
{#if tracks.length > 0}
|
||||
<PlaylistTrackTable {tracks} />
|
||||
<TrackList
|
||||
tracks={listTracks}
|
||||
columns={['added', 'plays']}
|
||||
onPlay={playFrom}
|
||||
rowAction={{ action: '?/removeTrack', icon: X, label: 'Quitar de la playlist' }}
|
||||
class="mt-6 sm:mt-8"
|
||||
/>
|
||||
{:else}
|
||||
<EmptyState
|
||||
icon={ListMusic}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import { player, toQueueItems, type ApiTrackLike } from '$lib/player/player.svelte';
|
||||
import { fmtTime, fmtDate } from '$lib/format';
|
||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||
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 TrackIndexCell from '$lib/components/ui/TrackIndexCell.svelte';
|
||||
import MediaIdentity from '$lib/components/ui/MediaIdentity.svelte';
|
||||
import { pressable } from '$lib/actions/pressable';
|
||||
|
||||
type PlaylistTrack = ApiTrackLike & {
|
||||
createdAt: string;
|
||||
duration: number | string;
|
||||
listensCount: number | string;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
tracks: PlaylistTrack[];
|
||||
}
|
||||
|
||||
let { tracks }: Props = $props();
|
||||
|
||||
function playFrom(index: number) {
|
||||
player.playOrToggle(toQueueItems(tracks), index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<TrackTable
|
||||
index
|
||||
action
|
||||
meta={[
|
||||
{ label: 'Álbum', width: '76px' },
|
||||
{ label: 'Añadida', width: '124px' },
|
||||
{ label: 'Escuchas', width: '106px' }
|
||||
]}
|
||||
class="mt-6 sm:mt-8"
|
||||
>
|
||||
{#each tracks as track, i (track.id)}
|
||||
{@const active = player.current.id === track.id}
|
||||
<TrackRow {active}>
|
||||
<TrackIndexCell index={i} playing={player.playing} onToggle={() => playFrom(i)} {active} />
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
use:pressable={() => playFrom(i)}
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class="flex min-w-0 flex-1 text-left"
|
||||
>
|
||||
<MediaIdentity
|
||||
trackId={track.id}
|
||||
title={track.title}
|
||||
artist={track.artist}
|
||||
ownerUserId={track.ownerUserId}
|
||||
explicit={track.isExplicit}
|
||||
{active}
|
||||
/>
|
||||
</div>
|
||||
<TrackMeta class="hidden sm:block">—</TrackMeta>
|
||||
<TrackMeta class="hidden sm:block">{fmtDate(track.createdAt)}</TrackMeta>
|
||||
<TrackMeta class="hidden sm:block">{Number(track.listensCount)}</TrackMeta>
|
||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/removeTrack"
|
||||
use:enhance={() =>
|
||||
async ({ update }) =>
|
||||
update()}
|
||||
>
|
||||
<input type="hidden" name="trackId" value={track.id} />
|
||||
<IconButton type="submit" label="Quitar de la playlist" size="xs" revealOnHover>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</IconButton>
|
||||
</form>
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
Reference in New Issue
Block a user