A lot of changes

This commit is contained in:
2026-07-17 18:56:23 +02:00
parent aac9224c2d
commit 4fed9bd059
35 changed files with 2127 additions and 185 deletions
+1 -1
View File
@@ -30,7 +30,7 @@
</a>
<form method="POST" action="/logout">
<button
class="rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white backdrop-blur-md transition hover:bg-white/5"
class="rounded-full border border-white/10 px-4 py-2 text-sm text-white/65 transition hover:bg-white/2.5"
>
Salir
</button>
+3 -3
View File
@@ -30,11 +30,11 @@
const mainLinks = [
{ href: '/', label: 'Inicio', icon: Home },
{ href: '/explore', label: 'Buscar', icon: Search },
{ href: '/playlists', label: 'Mis listas', icon: ListMusic }
{ href: '/playlists', label: 'Listas', icon: ListMusic }
];
const secondaryLinks = [
{ href: '/library', label: 'Tus canciones subidas', icon: Library },
{ href: '/library', label: 'Canciones subidas', icon: Library },
{ href: '/upload', label: 'Subir música', icon: Upload }
];
@@ -65,7 +65,7 @@
<a
href={link.href}
data-active={active}
class="relative flex items-center gap-3 rounded-[10px] px-3 py-2.5 text-[14px] font-semibold transition-colors duration-100 {active
class="relative flex items-center gap-3 rounded-[10px] px-3 py-2.5 font-semibold transition-colors duration-100 {active
? 'text-[var(--mf-text)]'
: 'text-[var(--mf-text-2)] hover:bg-white/5 hover:text-[var(--mf-text)]'}"
>
@@ -396,17 +396,16 @@
<button
type="button"
onclick={() => addToQueue(contextMenu)}
class="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-left text-sm font-semibold text-[var(--mf-text-2)] transition hover:bg-white/5"
class="flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-sm text-[var(--mf-text-2)] transition hover:bg-white/5"
>
<ListPlus class="h-[18px] w-[18px] shrink-0" strokeWidth={2} />
<ListPlus class="h-6 w-6 shrink-0" strokeWidth={2} />
<span class="text-base">Añadir a la cola</span>
</button>
<div class="group/addmenu relative">
<div
class="flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-semibold text-[var(--mf-text-2)] transition group-hover/addmenu:bg-white/5"
class="flex items-center gap-3 rounded-lg px-2 py-2.5 text-sm text-[var(--mf-text-2)] transition group-hover/addmenu:bg-white/5"
>
<ListMusic class="h-[18px] w-[18px] shrink-0" strokeWidth={2} />
<span class="flex-1">Añadir a una playlist</span>
<span class="flex-1 text-base cursor-default">Añadir a una playlist</span>
<ChevronRight class="h-4 w-4 shrink-0 text-[var(--mf-text-3)]" />
</div>
<div
@@ -445,9 +444,9 @@
trackIds={[]}
hue={hueFor(playlist.id)}
size="small"
class="h-11 w-11 flex-shrink-0 rounded-lg"
class="h-8 w-8 flex-shrink-0 rounded-lg"
/>
<span class="truncate">{playlist.name}</span>
<span class="truncate text-base text-[var(--mf-text-2)]">{playlist.name}</span>
</button>
</form>
{/each}
@@ -1,5 +1,5 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { error, fail } from '@sveltejs/kit';
import { createApiClient } from '$lib/server/api';
const PAGE_SIZE = 50;
@@ -16,3 +16,18 @@ export const load: PageServerLoad = async ({ locals, fetch }) => {
return { tracks: data };
};
export const actions: Actions = {
deleteTrack: async ({ request, locals, fetch }) => {
if (!locals.accessToken) return fail(401, { message: 'Inicia sesión.' });
const trackId = String((await request.formData()).get('trackId') ?? '');
if (!trackId) return fail(400, { message: 'Falta la canción.' });
const api = createApiClient({ fetch, accessToken: locals.accessToken });
const { error: err } = await api.DELETE('/tracks/{trackId}', {
params: { path: { trackId } }
});
if (err) return fail(502, { message: 'No se pudo borrar la canción.' });
return { deleted: true };
}
};
+48 -31
View File
@@ -1,8 +1,9 @@
<script lang="ts">
import { enhance } from '$app/forms';
import Upload from '@lucide/svelte/icons/upload';
import Headphones from '@lucide/svelte/icons/headphones';
import Clock from '@lucide/svelte/icons/clock';
import { player } from '$lib/player/player.svelte';
import Play from '@lucide/svelte/icons/play';
import X from '@lucide/svelte/icons/x';
import { player, queueIdForTrack, toQueueItems } from '$lib/player/player.svelte';
import { HUES, fmtTime } from '$lib/theme/color';
import Cover from '$lib/components/ui/Cover.svelte';
import NowPlaying from '$lib/components/ui/NowPlaying.svelte';
@@ -22,8 +23,8 @@
function togglePlay(index: number) {
const track = items[index];
if (player.current.id === track.id) player.toggle();
else player.playQueue(items, index);
if (player.current.id === queueIdForTrack(track)) player.toggle();
else player.playQueue(toQueueItems(items), index);
}
</script>
@@ -53,50 +54,66 @@
{#if items.length > 0}
<div class="mt-8">
<div
class="grid grid-cols-[1fr_120px_160px] items-center gap-6 border-b border-[var(--mf-border)] px-3 pb-3 text-xs font-semibold uppercase tracking-wide text-[var(--mf-text-3)]"
class="grid grid-cols-[32px_1fr_100px_100px_64px_36px] items-center gap-8 border-b border-white/10 px-3 pb-2 text-xs uppercase tracking-wide text-neutral-500"
>
<span class="text-center">#</span>
<span>Título</span>
<span class="flex items-center justify-end gap-1.5"><Clock class="h-3.5 w-3.5" /> Duración</span>
<span class="flex items-center gap-1.5"><Headphones class="h-3.5 w-3.5" /> Escuchas</span>
<span class="text-center">Subida</span>
<span class="text-center">Escuchas</span>
<span class="text-center">Duración</span>
<span></span>
</div>
<div class="mt-2 flex flex-col">
<div class="mt-1 flex flex-col">
{#each items as track, i (track.id)}
<div
class="group grid animate-enter grid-cols-[1fr_120px_160px] items-center gap-6 rounded-[10px] px-3 py-4 transition duration-100 hover:bg-[var(--mf-surface-hover)]"
style="animation-delay:{i * 35}ms"
class="group grid grid-cols-[32px_1fr_100px_100px_64px_36px] items-center gap-8 rounded-[10px] px-3 py-[9px] transition hover:bg-neutral-900/60"
>
<button
type="button"
onclick={() => togglePlay(i)}
class="flex min-w-0 items-center gap-3.5 text-left active:scale-[0.99]"
aria-label={player.current.id === track.id && player.playing ? 'Pausar' : 'Reproducir'}
class="relative grid h-8 w-8 place-items-center overflow-hidden rounded text-sm text-neutral-500"
>
{#if player.current.id === track.id}
<NowPlaying paused={!player.playing} />
{:else}
<span class="group-hover:hidden">{i + 1}</span>
<Play class="hidden h-3.5 w-3.5 text-white group-hover:block" fill="currentColor" />
{/if}
</button>
<button
type="button"
onclick={() => togglePlay(i)}
class="flex min-w-0 items-center gap-3.5 text-left"
>
<Cover
trackId={track.id}
hue={hueFor(track.id)}
size="small"
alt={track.title}
class="h-[46px] w-[46px] flex-shrink-0 rounded-md"
>
{#if player.current.id === track.id}
<NowPlaying paused={!player.playing} />
{/if}
</Cover>
<div class="min-w-0 flex-1">
<div class="truncate text-[14.5px] font-semibold text-[var(--mf-text)]">
{track.title}
</div>
<div class="truncate text-[12.5px] text-[var(--mf-text-3)]">
{dateFormatter.format(new Date(track.createdAt))}
</div>
</div>
class="h-[42px] w-[42px] flex-shrink-0 rounded-md"
/>
<div class="min-w-0 truncate text-[14.5px] font-semibold text-neutral-100">{track.title}</div>
</button>
<span class="text-right text-[12.5px] tabular-nums text-[var(--mf-text-3)]">
<div class="truncate text-center text-[12.5px] text-neutral-500">
{dateFormatter.format(new Date(track.createdAt))}
</div>
<span class="text-center text-[12.5px] tabular-nums text-neutral-500">
{Number(track.listensCount)}
</span>
<span class="text-center text-[12.5px] tabular-nums text-neutral-500">
{fmtTime(Number(track.duration))}
</span>
<div class="flex items-center gap-1.5 text-[12.5px] tabular-nums text-[var(--mf-text-3)]">
<Headphones class="h-3.5 w-3.5" />
{Number(track.listensCount)}
</div>
<form method="POST" action="?/deleteTrack" use:enhance={() => async ({ update }) => update()}>
<input type="hidden" name="trackId" value={track.id} />
<button
type="submit"
aria-label="Borrar canción"
class="grid h-7 w-7 place-items-center rounded-lg text-neutral-500 opacity-0 transition hover:bg-white/5 hover:text-white group-hover:opacity-100"
>
<X class="h-3.5 w-3.5" />
</button>
</form>
</div>
{/each}
</div>
@@ -130,14 +130,14 @@
if (e.key === 'Escape') creating = false;
}}
>
<div class="animate-pop w-full max-w-2xl rounded-2xl bg-[var(--mf-bg)] p-6 shadow-2xl">
<div class="animate-pop w-full max-w-2xl rounded-lg bg-[var(--mf-bg)] p-6 shadow-2xl">
<div class="flex items-center justify-between">
<h2 class="text-2xl font-bold tracking-tight">Nueva lista</h2>
<button
type="button"
onclick={() => (creating = false)}
aria-label="Cerrar"
class="grid h-8 w-8 place-items-center rounded-full text-neutral-400 transition hover:bg-white/5 hover:text-white"
class="grid h-8 w-8 place-items-center rounded-lg text-neutral-400 transition hover:bg-white/5 hover:text-white"
>
<X class="h-4 w-4" />
</button>
@@ -222,7 +222,7 @@
<button
type="button"
onclick={() => (creating = false)}
class="rounded-lg border border-white/15 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-white/5"
class="rounded-lg border border-white/10 px-5 py-2.5 text-sm text-white/65 transition hover:bg-white/2.5"
>
Cancelar
</button>
@@ -33,7 +33,7 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
params: { path: { playlistId: params.id }, query: { pageNumber: 1, pageSize: 200 } }
}),
api.GET('/tracks/users/{userId}', {
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 200 } }
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 50 } }
})
]);
@@ -160,7 +160,7 @@
<button
type="button"
onclick={reset}
class="rounded-lg border border-white/20 px-8 py-3.5 text-base font-semibold text-white backdrop-blur-md transition hover:bg-white/5"
class="rounded-lg border border-white/10 px-8 py-3.5 text-base text-white/65 transition hover:bg-white/2.5"
>
Subir otra
</button>
@@ -311,7 +311,7 @@
<div class="flex items-center justify-end gap-4">
<a
href="/explore"
class="rounded-lg border border-white/20 px-8 py-3.5 text-base font-semibold text-white backdrop-blur-md transition hover:bg-white/5"
class="rounded-lg border border-white/10 px-8 py-3.5 text-base text-white/65 transition hover:bg-white/2.5"
>
Cancelar
</a>