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
@@ -174,5 +174,13 @@
menu={albumMenu}
playlists={data.playlists}
onClose={() => (albumMenu = null)}
onPlayNext={() => {
player.playNext(queue);
albumMenu = null;
}}
onAddToQueue={() => {
player.appendToQueue(queue);
albumMenu = null;
}}
/>
{/if}
@@ -4,6 +4,7 @@
import { untrack } from 'svelte';
import { goto } from '$app/navigation';
import { player } from '$lib/player/player.svelte';
import { fetchAlbumQueueItems } from '$lib/albums';
import { fmtTime } from '$lib/format';
import Page from '$lib/components/ui/Page.svelte';
import PageHeader from '$lib/components/ui/PageHeader.svelte';
@@ -163,14 +164,24 @@
contextMenu = contextMenuStateFor(event, target);
}
function isForeignAlbum(album: LocalAlbum) {
return String(album.ownerUserId) !== data.user?.sub;
}
function openAlbumMenu(event: MouseEvent, kind: 'local' | 'youtube', albumId: string) {
albumMenu = albumContextMenuStateFor(event, kind, albumId);
}
async function albumPlayNext() {
if (!albumMenu) return;
const { kind, albumId } = albumMenu;
albumMenu = null;
player.playNext(await fetchAlbumQueueItems(kind, albumId));
}
async function albumAddToQueue() {
if (!albumMenu) return;
const { kind, albumId } = albumMenu;
albumMenu = null;
player.appendToQueue(await fetchAlbumQueueItems(kind, albumId));
}
function addToQueue() {
if (!contextMenu) return;
player.addToQueue(queueItemForTarget(contextMenu));
@@ -255,9 +266,7 @@
href="/albums/{entry.album.id}"
class="group/card animate-enter block min-w-0 rounded-art focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none"
style="animation-delay:{Math.min(i, 10) * 45}ms"
oncontextmenu={isForeignAlbum(entry.album)
? (e) => openAlbumMenu(e, 'local', entry.album.id)
: undefined}
oncontextmenu={(e) => openAlbumMenu(e, 'local', entry.album.id)}
>
<div class="relative">
<PlaylistArt
@@ -395,5 +404,7 @@
menu={albumMenu}
playlists={data.playlists}
onClose={() => (albumMenu = null)}
onPlayNext={albumPlayNext}
onAddToQueue={albumAddToQueue}
/>
{/if}
+7 -2
View File
@@ -1,7 +1,11 @@
import type { PageServerLoad, Actions } from './$types';
import { createApiClient, requireUser } from '$lib/server/api';
import { fetchYoutubeFiller } from '$lib/server/youtube';
import { addTrackAction, addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
import {
addAlbumToPlaylistAction,
addTrackAction,
addYouTubeToPlaylistAction
} from '$lib/server/playlistActions';
import {
HOME_ALBUMS_LIMIT,
HOME_LATEST_PAGE_SIZE,
@@ -62,5 +66,6 @@ export const load: PageServerLoad = async ({ locals, url, fetch }) => {
export const actions: Actions = {
addTrack: addTrackAction,
addYouTubeToPlaylist: addYouTubeToPlaylistAction
addYouTubeToPlaylist: addYouTubeToPlaylistAction,
addAlbumToPlaylist: addAlbumToPlaylistAction
};
+47 -4
View File
@@ -24,6 +24,11 @@
contextMenuStateFor,
type ContextMenuState
} from '$lib/components/TrackContextMenu.svelte';
import AlbumContextMenu, {
albumContextMenuStateFor,
type AlbumMenuState
} from '$lib/components/AlbumContextMenu.svelte';
import { fetchAlbumQueueItems } from '$lib/albums';
import type { YouTubeSong } from '$lib/types';
import { appendUnique } from '$lib/collections';
import { HOME_LATEST_PAGE_SIZE } from '$lib/config';
@@ -33,6 +38,7 @@
let { data } = $props();
let contextMenu = $state<ContextMenuState | null>(null);
let albumMenu = $state<AlbumMenuState | null>(null);
let youtubeFillerItems = $state<YouTubeSong[]>([]);
$effect(() => {
@@ -109,6 +115,24 @@
contextMenu = null;
}
function openAlbumMenu(event: MouseEvent, albumId: string) {
albumMenu = albumContextMenuStateFor(event, 'local', albumId);
}
async function albumPlayNext() {
if (!albumMenu) return;
const { kind, albumId } = albumMenu;
albumMenu = null;
player.playNext(await fetchAlbumQueueItems(kind, albumId));
}
async function albumAddToQueue() {
if (!albumMenu) return;
const { kind, albumId } = albumMenu;
albumMenu = null;
player.appendToQueue(await fetchAlbumQueueItems(kind, albumId));
}
function albumCaption(album: (typeof albums)[number]) {
return [
album.releaseYear ? String(album.releaseYear) : undefined,
@@ -195,6 +219,7 @@
: `/albums/${album.id}`}
class="group/card animate-enter block w-42 shrink-0 rounded-art focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg focus-visible:outline-none"
style="animation-delay:{Math.min(i, 10) * 45}ms"
oncontextmenu={(e) => openAlbumMenu(e, album.id)}
>
<div class="relative">
{#if album.thumbnailUrl}
@@ -266,9 +291,13 @@
>
{playlist.name}
</div>
{#if playlist.description}
<div class="mt-0.5 truncate text-sm text-muted">{playlist.description}</div>
{/if}
<div class="mt-0.5 truncate text-sm text-muted">
{#if playlist.description}
{playlist.description}
{:else}
{/if}
</div>
</a>
{/each}
<a
@@ -330,7 +359,11 @@
</section>
{/if}
{#if recentlyPlayed.length === 0 && albums.length === 0 && mixes.length === 0 && playlists.length === 0 && newTargets.length === 0}
<EmptyState icon={Music} title="Todavía no hay música" description="Sube tu música o explora nuevas canciones, álbumes y listas de reproducción." />
<EmptyState
icon={Music}
title="Todavía no hay música"
description="Sube tu música o explora nuevas canciones, álbumes y listas de reproducción."
/>
{/if}
</div>
@@ -387,3 +420,13 @@
onAddToQueue={addToQueue}
/>
{/if}
{#if albumMenu}
<AlbumContextMenu
menu={albumMenu}
{playlists}
onClose={() => (albumMenu = null)}
onPlayNext={albumPlayNext}
onAddToQueue={albumAddToQueue}
/>
{/if}
@@ -0,0 +1,16 @@
import type { RequestHandler } from './$types';
import { json } from '@sveltejs/kit';
import { createApiClient, unwrapOrError } from '$lib/server/api';
import { ALBUM_TRACKS_PAGE_SIZE } from '$lib/config';
export const GET: RequestHandler = async ({ params, locals, fetch }) => {
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
const result = await api.GET('/albums/{albumId}/tracks', {
params: {
path: { albumId: params.id },
query: { pageNumber: 1, pageSize: ALBUM_TRACKS_PAGE_SIZE }
}
});
return json(unwrapOrError(result, 'No se pudo cargar el álbum.'));
};
@@ -0,0 +1,12 @@
import type { RequestHandler } from './$types';
import { json } from '@sveltejs/kit';
import { createApiClient, unwrapOrError } from '$lib/server/api';
export const GET: RequestHandler = async ({ params, locals, fetch }) => {
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
const result = await api.GET('/youtube/albums/{albumId}', {
params: { path: { albumId: params.albumId } }
});
return json(unwrapOrError(result, 'No se pudo cargar el álbum de YouTube.'));
};