Design Prot Merge #1
Vendored
+140
@@ -454,6 +454,40 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/youtube/albums": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Search Albums On YouTube Music. */
|
||||||
|
get: operations["SearchYouTubeAlbums"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/youtube/albums/{albumId}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/** Get A YouTube Music Album With Its Tracklist. */
|
||||||
|
get: operations["GetYouTubeAlbum"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/youtube/tracks/{videoId}/stream": {
|
"/youtube/tracks/{videoId}/stream": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -696,6 +730,37 @@ export interface components {
|
|||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
YouTubeAlbumDetail: {
|
||||||
|
album: components["schemas"]["YouTubeAlbumResult"];
|
||||||
|
description: string;
|
||||||
|
/** Format: int32 */
|
||||||
|
totalDurationSeconds: number | string;
|
||||||
|
tracks: components["schemas"]["YouTubeAlbumTrack"][];
|
||||||
|
};
|
||||||
|
YouTubeAlbumResult: {
|
||||||
|
albumId: string;
|
||||||
|
title: string;
|
||||||
|
artist: string;
|
||||||
|
thumbnailUrl: string;
|
||||||
|
/** Format: int32 */
|
||||||
|
releaseYear: null | number | string;
|
||||||
|
isSingle: boolean;
|
||||||
|
isEp: boolean;
|
||||||
|
artists: components["schemas"]["YouTubeArtistRef"][];
|
||||||
|
};
|
||||||
|
YouTubeAlbumSearchResult: {
|
||||||
|
items: components["schemas"]["YouTubeAlbumResult"][];
|
||||||
|
continuationToken: string;
|
||||||
|
};
|
||||||
|
YouTubeAlbumTrack: {
|
||||||
|
videoId: string;
|
||||||
|
title: string;
|
||||||
|
/** Format: int32 */
|
||||||
|
durationSeconds: number | string;
|
||||||
|
/** Format: int32 */
|
||||||
|
trackNumber: number | string;
|
||||||
|
isExplicit: boolean;
|
||||||
|
};
|
||||||
YouTubeArtistRef: {
|
YouTubeArtistRef: {
|
||||||
id: null | string;
|
id: null | string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -1962,6 +2027,81 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
SearchYouTubeAlbums: {
|
||||||
|
parameters: {
|
||||||
|
query: {
|
||||||
|
query: string;
|
||||||
|
continuation?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["YouTubeAlbumSearchResult"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Bad Request */
|
||||||
|
400: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/problem+json": components["schemas"]["HttpValidationProblemDetails"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
GetYouTubeAlbum: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
albumId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["YouTubeAlbumDetail"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
/** @description Not Found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
GetYouTubeTrackStream: {
|
GetYouTubeTrackStream: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|||||||
@@ -1,35 +1,68 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PlaylistArt from './PlaylistArt.svelte';
|
import PlaylistArt from './PlaylistArt.svelte';
|
||||||
|
import Cover from './Cover.svelte';
|
||||||
import { hueFor } from '$lib/theme/color';
|
import { hueFor } from '$lib/theme/color';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
href?: string;
|
||||||
|
subtitle?: string;
|
||||||
releaseYear?: number;
|
releaseYear?: number;
|
||||||
trackCount: number;
|
trackCount?: number;
|
||||||
trackIds: (string | number)[];
|
trackIds?: (string | number)[];
|
||||||
|
coverSrc?: string | null;
|
||||||
index?: number;
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { id, title, releaseYear, trackCount, trackIds, index = 0 }: Props = $props();
|
let {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
href,
|
||||||
|
subtitle,
|
||||||
|
releaseYear,
|
||||||
|
trackCount,
|
||||||
|
trackIds = [],
|
||||||
|
coverSrc,
|
||||||
|
index = 0
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
const meta = $derived(
|
const meta = $derived(
|
||||||
[releaseYear, `${trackCount} ${trackCount === 1 ? 'canción' : 'canciones'}`]
|
[
|
||||||
.filter((part) => part !== undefined && part !== null)
|
subtitle,
|
||||||
|
releaseYear ? String(releaseYear) : undefined,
|
||||||
|
trackCount === undefined
|
||||||
|
? undefined
|
||||||
|
: `${trackCount} ${trackCount === 1 ? 'canción' : 'canciones'}`
|
||||||
|
]
|
||||||
|
.filter((part) => !!part)
|
||||||
.join(' · ')
|
.join(' · ')
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/albums/{id}"
|
href={href ?? `/albums/${id}`}
|
||||||
class="group animate-enter min-w-0 text-left"
|
class="group animate-enter min-w-0 text-left"
|
||||||
style="animation-delay:{index * 45}ms"
|
style="animation-delay:{index * 45}ms"
|
||||||
>
|
>
|
||||||
<PlaylistArt
|
{#if coverSrc}
|
||||||
{trackIds}
|
<Cover
|
||||||
hue={hueFor(id)}
|
trackId={id}
|
||||||
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
src={coverSrc}
|
||||||
/>
|
hue={hueFor(id)}
|
||||||
|
size="large"
|
||||||
|
alt={title}
|
||||||
|
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<PlaylistArt
|
||||||
|
{trackIds}
|
||||||
|
hue={hueFor(id)}
|
||||||
|
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<div class="mt-2.5 truncate text-fg">{title}</div>
|
<div class="mt-2.5 truncate text-fg">{title}</div>
|
||||||
<div class="mt-0.5 truncate text-sm text-fg-3">{meta}</div>
|
{#if meta}
|
||||||
|
<div class="mt-0.5 truncate text-sm text-fg-3">{meta}</div>
|
||||||
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
|
import { createApiClient, requireUser, unwrapOrError } from '$lib/server/api';
|
||||||
|
import { addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ params, locals, url, fetch }) => {
|
||||||
|
const user = requireUser(locals, url);
|
||||||
|
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||||
|
|
||||||
|
const [albumRes, playlistsRes] = await Promise.all([
|
||||||
|
api.GET('/youtube/albums/{albumId}', { params: { path: { albumId: params.albumId } } }),
|
||||||
|
api.GET('/playlists/users/{userId}', {
|
||||||
|
params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 50 } }
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
const album = unwrapOrError(albumRes, 'No se pudo cargar el álbum de YouTube Music.', 404);
|
||||||
|
|
||||||
|
return { album, playlists: playlistsRes.data?.items ?? [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
addYouTubeToPlaylist: addYouTubeToPlaylistAction
|
||||||
|
};
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||||
|
import Play from '@lucide/svelte/icons/play';
|
||||||
|
import Pause from '@lucide/svelte/icons/pause';
|
||||||
|
import { player, type QueueItem } from '$lib/player/player.svelte';
|
||||||
|
import { hueFor } from '$lib/theme/color';
|
||||||
|
import { fmtTime } from '$lib/format';
|
||||||
|
import Page from '$lib/components/ui/Page.svelte';
|
||||||
|
import Button from '$lib/components/ui/Button.svelte';
|
||||||
|
import Cover from '$lib/components/ui/Cover.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 type { TrackTarget } from '$lib/tracks';
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
const album = $derived(data.album.album);
|
||||||
|
const tracks = $derived(data.album.tracks);
|
||||||
|
|
||||||
|
let contextMenu = $state<ContextMenuState | 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} · Musify</title>
|
||||||
|
<meta name="description" content="Álbum {album.title} de {album.artist} en YouTube Music." />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<Page>
|
||||||
|
<a
|
||||||
|
href="/explore"
|
||||||
|
class="group inline-flex items-center text-fg-3 transition-colors hover:text-accent-soft"
|
||||||
|
>
|
||||||
|
<span class="grid h-8 w-8 place-items-center transition-colors group-hover:text-accent-soft">
|
||||||
|
<ArrowLeft class="h-4 w-4" />
|
||||||
|
</span>
|
||||||
|
Volver a explorar
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col gap-5 sm:mt-6 sm:flex-row sm:items-end sm:gap-6">
|
||||||
|
<Cover
|
||||||
|
trackId={album.albumId}
|
||||||
|
src={album.thumbnailUrl}
|
||||||
|
hue={hueFor(album.albumId)}
|
||||||
|
size="large"
|
||||||
|
alt={album.title}
|
||||||
|
class="h-36 w-36 shrink-0 rounded-art-lg shadow-art-lg sm:h-44 sm:w-44"
|
||||||
|
/>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">
|
||||||
|
{album.isSingle ? 'Single' : album.isEp ? 'EP' : 'Álbum'} · YouTube Music
|
||||||
|
</p>
|
||||||
|
<h1
|
||||||
|
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||||
|
>
|
||||||
|
{album.title}
|
||||||
|
</h1>
|
||||||
|
<p class="mt-3 text-base text-fg-2">{album.artist}</p>
|
||||||
|
<p class="mt-1 text-sm text-muted">
|
||||||
|
{[
|
||||||
|
album.releaseYear ? String(album.releaseYear) : undefined,
|
||||||
|
`${tracks.length} ${tracks.length === 1 ? 'canción' : 'canciones'}`,
|
||||||
|
fmtTime(Number(data.album.totalDurationSeconds))
|
||||||
|
]
|
||||||
|
.filter((part) => !!part)
|
||||||
|
.join(' · ')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 flex flex-wrap items-center gap-2.5 sm:mt-7">
|
||||||
|
<Button size="sm" onclick={playAll} disabled={queue.length === 0}>
|
||||||
|
{#if isCurrentQueue && player.playing}
|
||||||
|
<Pause class="h-4 w-4" fill="currentColor" strokeWidth={1.5} />
|
||||||
|
Pausar
|
||||||
|
{:else}
|
||||||
|
<Play class="h-4 w-4" fill="currentColor" strokeWidth={1.5} />
|
||||||
|
Reproducir
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if data.album.description}
|
||||||
|
<p class="mt-6 max-w-3xl text-sm text-fg-3">{data.album.description}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<TrackTable columns="32px 1fr 64px" columnsMobile="28px 1fr 52px" class="mt-6 sm:mt-8">
|
||||||
|
{#snippet headers()}
|
||||||
|
<span class="text-center">#</span>
|
||||||
|
<span>Título</span>
|
||||||
|
<span class="hidden text-center sm:block">Duración</span>
|
||||||
|
{/snippet}
|
||||||
|
{#each tracks as track, i (track.videoId)}
|
||||||
|
<TrackRow oncontextmenu={(e) => (contextMenu = contextMenuStateFor(e, targetFor(i)))}>
|
||||||
|
<TrackIndexCell
|
||||||
|
index={i}
|
||||||
|
active={player.current.id === track.videoId}
|
||||||
|
playing={player.playing}
|
||||||
|
onToggle={() => playFrom(i)}
|
||||||
|
/>
|
||||||
|
<TrackTitleCell
|
||||||
|
trackId={track.videoId}
|
||||||
|
title={track.title}
|
||||||
|
artist={album.artist}
|
||||||
|
coverSrc={album.thumbnailUrl}
|
||||||
|
hue={hueFor(track.videoId)}
|
||||||
|
explicit={track.isExplicit}
|
||||||
|
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}
|
||||||
@@ -35,11 +35,17 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
|||||||
})
|
})
|
||||||
: Promise.resolve(null);
|
: Promise.resolve(null);
|
||||||
|
|
||||||
const [tracksRes, searchRes, playlistsRes, albumsRes] = await Promise.all([
|
const youtubeAlbumsPromise =
|
||||||
|
query.length >= SEARCH_MIN_LENGTH
|
||||||
|
? api.GET('/youtube/albums', { params: { query: { query } } })
|
||||||
|
: Promise.resolve(null);
|
||||||
|
|
||||||
|
const [tracksRes, searchRes, playlistsRes, albumsRes, youtubeAlbumsRes] = await Promise.all([
|
||||||
tracksPromise,
|
tracksPromise,
|
||||||
searchPromise,
|
searchPromise,
|
||||||
playlistsPromise,
|
playlistsPromise,
|
||||||
albumsPromise
|
albumsPromise,
|
||||||
|
youtubeAlbumsPromise
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const tracks = unwrapOrError(tracksRes, 'No se pudieron cargar las canciones.');
|
const tracks = unwrapOrError(tracksRes, 'No se pudieron cargar las canciones.');
|
||||||
@@ -48,6 +54,7 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
|||||||
query,
|
query,
|
||||||
tracks,
|
tracks,
|
||||||
albums: albumsRes?.data?.items ?? [],
|
albums: albumsRes?.data?.items ?? [],
|
||||||
|
youtubeAlbums: youtubeAlbumsRes?.data?.items ?? [],
|
||||||
ytResults: searchRes?.data ?? null,
|
ytResults: searchRes?.data ?? null,
|
||||||
ytError: Boolean(searchRes?.error),
|
ytError: Boolean(searchRes?.error),
|
||||||
youtubeFiller: query
|
youtubeFiller: query
|
||||||
|
|||||||
@@ -101,12 +101,14 @@
|
|||||||
const hasMore = $derived(showTracks && (localHasNext || ytContinuation !== ''));
|
const hasMore = $derived(showTracks && (localHasNext || ytContinuation !== ''));
|
||||||
|
|
||||||
const albums = $derived(data.albums);
|
const albums = $derived(data.albums);
|
||||||
|
const youtubeAlbums = $derived(data.youtubeAlbums);
|
||||||
|
const hasAlbums = $derived(albums.length > 0 || youtubeAlbums.length > 0);
|
||||||
|
|
||||||
const nothingFound = $derived(
|
const nothingFound = $derived(
|
||||||
!!data.query &&
|
!!data.query &&
|
||||||
!data.ytError &&
|
!data.ytError &&
|
||||||
(showTracks ? items.length === 0 : true) &&
|
(showTracks ? items.length === 0 : true) &&
|
||||||
(showAlbums ? albums.length === 0 : true) &&
|
(showAlbums ? !hasAlbums : true) &&
|
||||||
filters.length > 0
|
filters.length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -230,7 +232,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showAlbums && albums.length > 0}
|
{#if showAlbums && hasAlbums}
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<SectionHeading title="Álbumes" />
|
<SectionHeading title="Álbumes" />
|
||||||
<MediaGrid class="mt-4">
|
<MediaGrid class="mt-4">
|
||||||
@@ -240,16 +242,26 @@
|
|||||||
title={album.title}
|
title={album.title}
|
||||||
releaseYear={album.releaseYear === null ? undefined : Number(album.releaseYear)}
|
releaseYear={album.releaseYear === null ? undefined : Number(album.releaseYear)}
|
||||||
trackCount={Number(album.trackCount)}
|
trackCount={Number(album.trackCount)}
|
||||||
trackIds={[]}
|
|
||||||
index={i}
|
index={i}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
{#each youtubeAlbums as album, i (album.albumId)}
|
||||||
|
<AlbumCard
|
||||||
|
id={album.albumId}
|
||||||
|
href="/albums/youtube/{album.albumId}"
|
||||||
|
title={album.title}
|
||||||
|
subtitle={album.artist}
|
||||||
|
releaseYear={album.releaseYear === null ? undefined : Number(album.releaseYear)}
|
||||||
|
coverSrc={album.thumbnailUrl}
|
||||||
|
index={albums.length + i}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
</MediaGrid>
|
</MediaGrid>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showTracks && items.length > 0}
|
{#if showTracks && items.length > 0}
|
||||||
{#if showAlbums && albums.length > 0}
|
{#if showAlbums && hasAlbums}
|
||||||
<SectionHeading title="Canciones" class="mt-10" />
|
<SectionHeading title="Canciones" class="mt-10" />
|
||||||
{/if}
|
{/if}
|
||||||
<MediaGrid as="ul" class="mt-6">
|
<MediaGrid as="ul" class="mt-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user