Design Prot Merge #1
@@ -4,35 +4,22 @@ import { createApiClient } from '$lib/server/api';
|
||||
|
||||
const PAGE_SIZE = 24;
|
||||
|
||||
type Source = 'all' | 'local' | 'yt';
|
||||
|
||||
function parseSource(value: string | null): Source {
|
||||
return value === 'local' || value === 'yt' ? value : 'all';
|
||||
}
|
||||
|
||||
export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
||||
const query = url.searchParams.get('q')?.trim() ?? '';
|
||||
const src = parseSource(url.searchParams.get('src'));
|
||||
const page = Math.max(1, Number(url.searchParams.get('page')) || 1);
|
||||
|
||||
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||
|
||||
const wantsLocal = src !== 'yt';
|
||||
const wantsYouTube = src !== 'local';
|
||||
|
||||
const tracksPromise = wantsLocal
|
||||
? api.GET('/tracks', {
|
||||
const tracksPromise = api.GET('/tracks', {
|
||||
params: { query: { name: query || undefined, pageNumber: page, pageSize: PAGE_SIZE } }
|
||||
})
|
||||
: Promise.resolve(null);
|
||||
});
|
||||
|
||||
const searchPromise =
|
||||
wantsYouTube && query && locals.accessToken
|
||||
query && locals.accessToken
|
||||
? api.GET('/youtube/search', { params: { query: { query } } })
|
||||
: Promise.resolve(null);
|
||||
|
||||
const playlistsPromise =
|
||||
wantsYouTube && locals.user
|
||||
const playlistsPromise = locals.user
|
||||
? api.GET('/playlists/users/{userId}', {
|
||||
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 50 } }
|
||||
})
|
||||
@@ -44,18 +31,17 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
||||
playlistsPromise
|
||||
]);
|
||||
|
||||
if (wantsLocal && (tracksRes?.error || !tracksRes?.data)) {
|
||||
if (tracksRes.error || !tracksRes.data) {
|
||||
error(502, 'No se pudieron cargar las canciones.');
|
||||
}
|
||||
|
||||
return {
|
||||
src,
|
||||
query,
|
||||
tracks: tracksRes?.data ?? null,
|
||||
tracks: tracksRes.data,
|
||||
ytResults: searchRes?.data ?? null,
|
||||
ytError: Boolean(searchRes?.error),
|
||||
playlists: playlistsRes?.data?.items ?? [],
|
||||
needsAuth: wantsYouTube && !locals.user
|
||||
needsAuth: !locals.user
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
isExplicit: boolean;
|
||||
}
|
||||
|
||||
type Source = 'all' | 'local' | 'yt';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
let searchTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
@@ -37,14 +35,14 @@
|
||||
addedVideoIds = new Set();
|
||||
});
|
||||
|
||||
const showLocal = $derived(data.tracks !== null);
|
||||
const showYouTube = $derived(data.src !== 'local' && !!data.query);
|
||||
const showHeadings = $derived(data.src === 'all' && !!data.query);
|
||||
const showHeadings = $derived(!!data.query);
|
||||
const tracks = $derived(data.tracks);
|
||||
const currentPage = $derived(Number(data.tracks.pageNumber));
|
||||
const total = $derived(Number(data.tracks.totalItemCount));
|
||||
|
||||
function buildHref(src: Source, page = 1, query = data.query) {
|
||||
function buildHref(page = 1, query = data.query) {
|
||||
const params = new URLSearchParams();
|
||||
if (query) params.set('q', query);
|
||||
if (src !== 'all') params.set('src', src);
|
||||
if (page > 1) params.set('page', String(page));
|
||||
const qs = params.toString();
|
||||
return qs ? `/explore?${qs}` : '/explore';
|
||||
@@ -54,7 +52,7 @@
|
||||
const value = (event.currentTarget as HTMLInputElement).value;
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
goto(buildHref(data.src, 1, value.trim()), {
|
||||
goto(buildHref(1, value.trim()), {
|
||||
keepFocus: true,
|
||||
replaceState: true,
|
||||
noScroll: true
|
||||
@@ -69,7 +67,6 @@
|
||||
}
|
||||
|
||||
function togglePlayLocal(index: number) {
|
||||
if (!data.tracks) return;
|
||||
const track = data.tracks.items[index];
|
||||
if (player.current?.id === queueIdForTrack(track)) player.toggle();
|
||||
else player.playQueue(toQueueItems(data.tracks.items), index);
|
||||
@@ -109,18 +106,6 @@
|
||||
ytLoadingMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
const tabs: { src: Source; label: string }[] = [
|
||||
{ src: 'all', label: 'Todo' },
|
||||
{ src: 'local', label: 'Mi música' },
|
||||
{ src: 'yt', label: 'YouTube Music' }
|
||||
];
|
||||
|
||||
const placeholders: Record<Source, string> = {
|
||||
all: 'Buscar en tu música y en YouTube Music...',
|
||||
local: 'Buscar por nombre...',
|
||||
yt: 'Buscar en YouTube Music...'
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -130,23 +115,9 @@
|
||||
|
||||
<section class="px-8 pt-12 pb-8">
|
||||
<h1 class="font-display text-3xl font-bold tracking-tight sm:text-4xl">Explorar</h1>
|
||||
<p class="mt-2 text-neutral-400">Busca a la vez en tu música y en YouTube Music, o filtra por fuente.</p>
|
||||
<p class="mt-2 text-neutral-400">Busca a la vez en tu música y en YouTube Music.</p>
|
||||
|
||||
<div class="mt-8 flex items-center gap-2">
|
||||
{#each tabs as tab (tab.src)}
|
||||
<a
|
||||
href={buildHref(tab.src)}
|
||||
data-sveltekit-noscroll
|
||||
class="rounded-full px-4 py-1.5 text-sm transition {data.src === tab.src
|
||||
? 'bg-emerald-500 font-semibold text-neutral-950'
|
||||
: 'border border-white/15 text-neutral-300 hover:bg-white/5'}"
|
||||
>
|
||||
{tab.label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="relative mt-6">
|
||||
<div class="relative mt-8">
|
||||
<span class="pointer-events-none absolute inset-y-0 left-4 grid place-items-center text-neutral-500">
|
||||
<Search class="h-4 w-4" />
|
||||
</span>
|
||||
@@ -154,7 +125,7 @@
|
||||
type="search"
|
||||
value={data.query}
|
||||
oninput={onSearchInput}
|
||||
placeholder={placeholders[data.src]}
|
||||
placeholder="Buscar en tu música y en YouTube Music..."
|
||||
autocomplete="off"
|
||||
class="w-full rounded-lg border border-white/15 bg-neutral-950 py-3 pl-11 pr-4 text-sm text-neutral-100 placeholder:text-neutral-500 focus:border-[var(--mf-accent)]/50 focus:outline-none"
|
||||
/>
|
||||
@@ -166,11 +137,6 @@
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if showLocal && data.tracks}
|
||||
{@const tracks = data.tracks}
|
||||
{@const currentPage = Number(tracks.pageNumber)}
|
||||
{@const total = Number(tracks.totalItemCount)}
|
||||
|
||||
{#if showHeadings}
|
||||
<h2 class="mt-10 font-display text-lg font-bold tracking-tight">Mi música</h2>
|
||||
{/if}
|
||||
@@ -239,7 +205,7 @@
|
||||
<nav class="mt-8 flex items-center justify-center gap-4">
|
||||
{#if tracks.hasPreviousPage}
|
||||
<a
|
||||
href={buildHref(data.src, currentPage - 1)}
|
||||
href={buildHref(currentPage - 1)}
|
||||
class="rounded-lg border border-white/10 px-5 py-2 text-sm text-white/65 transition hover:bg-white/2.5"
|
||||
>
|
||||
Anterior
|
||||
@@ -248,7 +214,7 @@
|
||||
<span class="text-sm text-neutral-500">Página {currentPage}</span>
|
||||
{#if tracks.hasNextPage}
|
||||
<a
|
||||
href={buildHref(data.src, currentPage + 1)}
|
||||
href={buildHref(currentPage + 1)}
|
||||
class="rounded-lg border border-white/10 px-5 py-2 text-sm text-white/65 transition hover:bg-white/2.5"
|
||||
>
|
||||
Siguiente
|
||||
@@ -257,34 +223,23 @@
|
||||
</nav>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if data.src !== 'local'}
|
||||
{#if data.query}
|
||||
{#if showHeadings}
|
||||
<h2 class="mt-12 font-display text-lg font-bold tracking-tight">YouTube Music</h2>
|
||||
{/if}
|
||||
|
||||
{#if data.needsAuth}
|
||||
{#if data.query || data.src === 'yt'}
|
||||
<div class="mt-6 rounded-2xl border border-white/10 bg-neutral-900/40 p-10 text-center backdrop-blur-md">
|
||||
<Music class="mx-auto h-8 w-8 text-neutral-500" />
|
||||
<p class="mt-3 text-sm text-neutral-300">Inicia sesión para buscar en YouTube Music.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if !data.query}
|
||||
{#if data.src === 'yt'}
|
||||
<div class="mt-16 rounded-2xl border border-white/10 bg-neutral-900/40 p-12 text-center backdrop-blur-md">
|
||||
<Search class="mx-auto h-10 w-10 text-neutral-500" />
|
||||
<p class="mt-4 text-neutral-300">Escribe algo para buscar canciones en YouTube Music.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if data.ytError}
|
||||
<div class="mt-6 rounded-2xl border border-white/10 bg-neutral-900/40 p-10 text-center backdrop-blur-md">
|
||||
<Music class="mx-auto h-8 w-8 text-neutral-500" />
|
||||
<p class="mt-3 text-sm text-neutral-300">YouTube Music no está disponible ahora mismo. Inténtalo de nuevo.</p>
|
||||
</div>
|
||||
{:else if showYouTube}
|
||||
{#if ytItems.length === 0}
|
||||
{:else if ytItems.length === 0}
|
||||
<div class="mt-6 rounded-2xl border border-white/10 bg-neutral-900/40 p-10 text-center backdrop-blur-md">
|
||||
<Music class="mx-auto h-8 w-8 text-neutral-500" />
|
||||
<p class="mt-3 text-sm text-neutral-300">No hay resultados en YouTube Music para «{data.query}».</p>
|
||||
@@ -400,5 +355,4 @@
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user