This commit is contained in:
2026-08-18 14:38:00 +02:00
parent f54ebec0eb
commit d867a60126
14 changed files with 325 additions and 64 deletions
+30
View File
@@ -0,0 +1,30 @@
import { toQueueItems, type ApiTrackLike, type QueueItem } from '$lib/player/player.svelte';
interface YouTubeAlbumTracksResponse {
album: { artist: string; thumbnailUrl: string };
tracks: { videoId: string; title: string; isExplicit: boolean }[];
}
export async function fetchAlbumQueueItems(
kind: 'local' | 'youtube',
albumId: string
): Promise<QueueItem[]> {
if (kind === 'local') {
const res = await fetch(`/api/albums/${albumId}/tracks`);
if (!res.ok) return [];
const data = (await res.json()) as { items: ApiTrackLike[] };
return toQueueItems(data.items ?? []);
}
const res = await fetch(`/api/albums/youtube/${albumId}/tracks`);
if (!res.ok) return [];
const detail = (await res.json()) as YouTubeAlbumTracksResponse;
return detail.tracks.map((track) => ({
id: track.videoId,
title: track.title,
artist: detail.album.artist,
source: 'youtube' as const,
coverUrl: detail.album.thumbnailUrl,
explicit: track.isExplicit
}));
}
@@ -25,15 +25,19 @@
<script lang="ts">
import { enhance } from '$app/forms';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import ListPlus from '@lucide/svelte/icons/list-plus';
import ListEnd from '@lucide/svelte/icons/list-end';
import PlaylistArt from '$lib/components/ui/PlaylistArt.svelte';
interface Props {
menu: AlbumMenuState;
playlists: { id: string; name: string }[];
onClose: () => void;
onPlayNext: () => void;
onAddToQueue: () => void;
}
let { menu, playlists, onClose }: Props = $props();
let { menu, playlists, onClose, onPlayNext, onAddToQueue }: Props = $props();
function onWindowKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') onClose();
@@ -55,6 +59,22 @@
class="fixed z-40 w-60 rounded-panel border border-line bg-elevated p-1.5 shadow-menu"
style="left:{menu.x}px; top:{menu.y}px;"
>
<button
type="button"
onclick={onPlayNext}
class="flex w-full items-center gap-3 rounded-control px-2 py-2.5 text-left text-sm text-fg-2 transition hover:bg-hover"
>
<ListPlus class="h-5 w-5 shrink-0" strokeWidth={2} />
<span class="text-base">Reproducir a continuación</span>
</button>
<button
type="button"
onclick={onAddToQueue}
class="flex w-full items-center gap-3 rounded-control px-2 py-2.5 text-left text-sm text-fg-2 transition hover:bg-hover"
>
<ListEnd class="h-5 w-5 shrink-0" strokeWidth={2} />
<span class="text-base">Añadir a la cola</span>
</button>
{#if playlists.length > 0}
<div class="group/addmenu relative">
<div
@@ -45,7 +45,7 @@
: ''}"
></div>
<div
class="pointer-events-none absolute inset-0 bg-scrim/25 opacity-0 transition-opacity duration-300 group-hover/tile:opacity-100"
class="pointer-events-none absolute inset-0 bg-scrim/25 opacity-0 transition-opacity duration-300"
></div>
<div
class="relative flex h-full flex-col justify-end p-3 sm:p-4 {banner
+16 -2
View File
@@ -342,15 +342,29 @@ class PlayerState {
}
addToQueue(item: QueueItem) {
this.playNext([item]);
}
playNext(items: QueueItem[]) {
if (items.length === 0) return;
if (this.currentId === null) {
this.playQueue([item], 0);
this.playQueue(items, 0);
return;
}
const tracks = [...this.tracks];
tracks.splice(this.#index() + 1, 0, toTrack(item));
tracks.splice(this.#index() + 1, 0, ...items.map(toTrack));
this.tracks = tracks;
}
appendToQueue(items: QueueItem[]) {
if (items.length === 0) return;
if (this.currentId === null) {
this.playQueue(items, 0);
return;
}
this.tracks = [...this.tracks, ...items.map(toTrack)];
}
playTrack(id: string | number) {
if (this.currentId === id) {
this.toggle();