Remove YouTube integration from web-player; unify Home track rows
Drops every YouTube-backed route, proxy and type (external albums/tracks, thumbnail proxy, youtube.ts) and simplifies player/track/mix types to the local-only catalog now that the backend contract dropped Source/ExternalId. Genre tiles on Explore and "Populares esta semana" on Home stay as deliberate client-only placeholders per product decision, just without the YouTube backing. Also finishes the design-system unification pass: Home's three hand-rolled track rows (Continuar escuchando, Playlist destacada, Populares esta semana) now reuse the shared TrackTitleCell component instead of duplicating cover+title+artist markup, and the orphaned Rail.svelte (no importers) is removed.
This commit is contained in:
@@ -38,9 +38,7 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch, parent
|
||||
|
||||
const tracks = tracksRes.data?.items ?? [];
|
||||
const inAlbum = new Set(tracks.map((track) => track.id));
|
||||
const library = (libraryRes?.data?.items ?? []).filter(
|
||||
(track) => track.source === 'Local' && !inAlbum.has(track.id)
|
||||
);
|
||||
const library = (libraryRes?.data?.items ?? []).filter((track) => !inAlbum.has(track.id));
|
||||
|
||||
const isOwner = user !== null && String(album.ownerUserId) === user.sub;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { player, queueIdForTrack, toQueueItems } from '$lib/player/player.svelte';
|
||||
import { player, toQueueItems } from '$lib/player/player.svelte';
|
||||
import Page from '$lib/components/ui/Page.svelte';
|
||||
import Modal from '$lib/components/ui/Modal.svelte';
|
||||
import Alert from '$lib/components/ui/Alert.svelte';
|
||||
@@ -19,9 +19,7 @@
|
||||
let editing = $state(false);
|
||||
let confirmingDelete = $state(false);
|
||||
|
||||
const isCurrentQueue = $derived(
|
||||
tracks.some((track) => queueIdForTrack(track) === player.current.id)
|
||||
);
|
||||
const isCurrentQueue = $derived(tracks.some((track) => track.id === player.current.id));
|
||||
|
||||
function playAll() {
|
||||
if (tracks.length === 0) return;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import {
|
||||
player,
|
||||
queueIdForTrack,
|
||||
toQueueItems,
|
||||
type ApiTrackLike
|
||||
} from '$lib/player/player.svelte';
|
||||
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';
|
||||
@@ -31,7 +26,7 @@
|
||||
|
||||
<TrackTable index action={isOwner} class="mt-6 sm:mt-8">
|
||||
{#each tracks as track, i (track.id)}
|
||||
{@const active = player.current.id === queueIdForTrack(track)}
|
||||
{@const active = player.current.id === track.id}
|
||||
<TrackRow {active}>
|
||||
<TrackIndexCell index={i} {active} playing={player.playing} onToggle={() => playFrom(i)} />
|
||||
<TrackTitleCell
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
import type { PageServerLoad, Actions } from './$types';
|
||||
import { createApiClient, optionalUser, unwrapOrError } from '$lib/server/api';
|
||||
import {
|
||||
addYouTubeToPlaylistAction,
|
||||
addYoutubeAlbumToPlaylistAction
|
||||
} from '$lib/server/playlistActions';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, locals, url, fetch, parent }) => {
|
||||
const { allowAnonymousListening } = await parent();
|
||||
const user = optionalUser(locals, url, allowAnonymousListening);
|
||||
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||
|
||||
const [albumRes, playlistsRes] = await Promise.all([
|
||||
api.GET('/albums/external/{source}/{externalId}', {
|
||||
params: { path: { source: params.source, externalId: params.externalId } }
|
||||
}),
|
||||
user
|
||||
? api.GET('/playlists/users/{userId}', {
|
||||
params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 50 } }
|
||||
})
|
||||
: Promise.resolve(null)
|
||||
]);
|
||||
|
||||
const album = unwrapOrError(albumRes, 'No se pudo cargar el álbum.', 404);
|
||||
|
||||
return { album, playlists: playlistsRes?.data?.items ?? [], section: null };
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
addYouTubeToPlaylist: addYouTubeToPlaylistAction,
|
||||
addYoutubeAlbumToPlaylist: addYoutubeAlbumToPlaylistAction
|
||||
};
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Play from '@lucide/svelte/icons/play';
|
||||
import Pause from '@lucide/svelte/icons/pause';
|
||||
import { player, type QueueItem } from '$lib/player/player.svelte';
|
||||
import { fmtTime } 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';
|
||||
import Cover from '$lib/components/ui/Cover.svelte';
|
||||
import Alert from '$lib/components/ui/Alert.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 TrackTitleCell from '$lib/components/ui/TrackTitleCell.svelte';
|
||||
import TrackContextMenu, {
|
||||
contextMenuStateFor,
|
||||
type ContextMenuState
|
||||
} from '$lib/components/TrackContextMenu.svelte';
|
||||
import AlbumContextMenu, {
|
||||
albumContextMenuStateFor,
|
||||
type AlbumMenuState
|
||||
} from '$lib/components/AlbumContextMenu.svelte';
|
||||
import type { TrackTarget } from '$lib/tracks';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
const album = $derived(data.album.album);
|
||||
const tracks = $derived(data.album.tracks);
|
||||
|
||||
let contextMenu = $state<ContextMenuState | null>(null);
|
||||
let albumMenu = $state<AlbumMenuState | null>(null);
|
||||
|
||||
const queue = $derived<QueueItem[]>(
|
||||
tracks.map((track) => ({
|
||||
id: track.videoId,
|
||||
title: track.title,
|
||||
artist: album.artist,
|
||||
source: 'youtube' as const,
|
||||
coverUrl: album.thumbnailUrl,
|
||||
explicit: track.isExplicit
|
||||
}))
|
||||
);
|
||||
|
||||
const isCurrentQueue = $derived(tracks.some((track) => track.videoId === player.current.id));
|
||||
|
||||
function targetFor(index: number): TrackTarget {
|
||||
const track = tracks[index];
|
||||
return {
|
||||
kind: 'youtube',
|
||||
song: {
|
||||
videoId: track.videoId,
|
||||
title: track.title,
|
||||
artist: album.artist,
|
||||
album: album.title,
|
||||
durationSeconds: Number(track.durationSeconds),
|
||||
thumbnailUrl: album.thumbnailUrl,
|
||||
isExplicit: track.isExplicit
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function playFrom(index: number) {
|
||||
player.playOrToggle(queue, index);
|
||||
}
|
||||
|
||||
function playAll() {
|
||||
if (queue.length === 0) return;
|
||||
if (isCurrentQueue) player.toggle();
|
||||
else player.playQueue(queue, 0);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{album.title}</title>
|
||||
<meta name="description" content="Álbum {album.title} de {album.artist}," />
|
||||
</svelte:head>
|
||||
|
||||
<Page>
|
||||
<BackLink href="/explore" label="Volver a explorar" />
|
||||
|
||||
<CollectionHeader
|
||||
eyebrow={album.isSingle ? 'Single' : album.isEp ? 'EP' : 'Álbum'}
|
||||
title={album.title}
|
||||
description={album.artist}
|
||||
meta={[
|
||||
album.releaseYear ? String(album.releaseYear) : undefined,
|
||||
`${tracks.length} ${tracks.length === 1 ? 'canción' : 'canciones'}`,
|
||||
fmtTime(Number(data.album.totalDurationSeconds))
|
||||
]
|
||||
.filter((part) => !!part)
|
||||
.join(' · ')}
|
||||
>
|
||||
{#snippet cover()}
|
||||
<Cover
|
||||
trackId={album.albumId}
|
||||
src={album.thumbnailUrl}
|
||||
size="large"
|
||||
alt={album.title}
|
||||
class="h-36 w-36 shrink-0 rounded-[18px] shadow-cover-lg sm:h-41 sm:w-41"
|
||||
oncontextmenu={(e) => (albumMenu = albumContextMenuStateFor(e, 'youtube', album.albumId))}
|
||||
/>
|
||||
{/snippet}
|
||||
{#snippet actions()}
|
||||
<Button size="sm" onclick={playAll} disabled={queue.length === 0}>
|
||||
{#if isCurrentQueue && player.playing}
|
||||
<Pause class="h-4 w-4" strokeWidth={1.5} />
|
||||
Pausar
|
||||
{:else}
|
||||
<Play class="h-4 w-4" strokeWidth={1.5} />
|
||||
Reproducir
|
||||
{/if}
|
||||
</Button>
|
||||
{/snippet}
|
||||
</CollectionHeader>
|
||||
|
||||
{#if form?.message}
|
||||
<Alert tone="danger" class="mt-4">{form.message}</Alert>
|
||||
{/if}
|
||||
|
||||
{#if data.album.description}
|
||||
<p class="mt-6 text-sm text-fg-3">{data.album.description}</p>
|
||||
{/if}
|
||||
|
||||
<TrackTable index class="mt-6 sm:mt-8">
|
||||
{#each tracks as track, i (track.videoId)}
|
||||
{@const active = player.current.id === track.videoId}
|
||||
<TrackRow
|
||||
{active}
|
||||
oncontextmenu={(e) => (contextMenu = contextMenuStateFor(e, targetFor(i)))}
|
||||
>
|
||||
<TrackIndexCell index={i} {active} playing={player.playing} onToggle={() => playFrom(i)} />
|
||||
<TrackTitleCell
|
||||
trackId={track.videoId}
|
||||
title={track.title}
|
||||
artist={album.artist}
|
||||
coverSrc={album.thumbnailUrl}
|
||||
explicit={track.isExplicit}
|
||||
{active}
|
||||
onClick={() => playFrom(i)}
|
||||
/>
|
||||
<TrackMeta>{fmtTime(Number(track.durationSeconds))}</TrackMeta>
|
||||
</TrackRow>
|
||||
{/each}
|
||||
</TrackTable>
|
||||
</Page>
|
||||
|
||||
{#if contextMenu}
|
||||
<TrackContextMenu
|
||||
menu={contextMenu}
|
||||
playlists={data.playlists}
|
||||
onClose={() => (contextMenu = null)}
|
||||
onAddToQueue={() => {
|
||||
const menu = contextMenu;
|
||||
if (menu?.kind === 'youtube') {
|
||||
const index = tracks.findIndex((track) => track.videoId === menu.song.videoId);
|
||||
if (index >= 0) player.addToQueue(queue[index]);
|
||||
}
|
||||
contextMenu = null;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if albumMenu}
|
||||
<AlbumContextMenu
|
||||
menu={albumMenu}
|
||||
playlists={data.playlists}
|
||||
onClose={() => (albumMenu = null)}
|
||||
onPlayNext={() => {
|
||||
player.playNext(queue);
|
||||
albumMenu = null;
|
||||
}}
|
||||
onAddToQueue={() => {
|
||||
player.appendToQueue(queue);
|
||||
albumMenu = null;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user