diff --git a/web-player/src/lib/components/music/ManageTrackList.svelte b/web-player/src/lib/components/music/ManageTrackList.svelte new file mode 100644 index 0000000..b5b72c4 --- /dev/null +++ b/web-player/src/lib/components/music/ManageTrackList.svelte @@ -0,0 +1,143 @@ + + + + +
+
+ allSelected, toggleAll} /> + Título + {#each COLUMNS as column (column.id)} + + {/each} + + +
+
+ {#each tracks as track, i (track.id)} + {@const active = player.currentId === track.id} +
+ selected.has(track.id), (checked) => toggle(track.id, checked)} + /> +
onPlay(i)} + class="flex min-w-0 flex-1 text-left" + > + + {#snippet overlay()} + {#if active} + + {/if} + {/snippet} + +
+ {#each COLUMNS as column (column.id)} + + {/each} + + {fmtTime(track.duration)} + + onDelete(track)}> + + +
+ {/each} +
+
diff --git a/web-player/src/lib/components/music/library/LibraryAlbumEditor.svelte b/web-player/src/lib/components/music/library/LibraryAlbumEditor.svelte new file mode 100644 index 0000000..b3aaaa9 --- /dev/null +++ b/web-player/src/lib/components/music/library/LibraryAlbumEditor.svelte @@ -0,0 +1,193 @@ + + + + +
+
{ + submitting = true; + return async ({ update, result }) => { + await update({ reset: false }); + submitting = false; + if (result.type === 'success') onClose(); + }; + }} + class="mt-6 grid gap-8 lg:grid-cols-[auto_1fr]" + > + + + + +
+ +

Las canciones del álbum usan esta portada.

+ + + + + + + + + + + + + + + + +
+ +
+
+
+

Canciones

+

+ {inAlbum.length > 0 ? plural(inAlbum.length, 'canción', 'canciones') : 'Sin canciones'} +

+
+ +
+ + {#if available.length > 0} +

Añadir desde tu biblioteca

+ + {/if} + +
+ {#if inAlbum.length > 0} +

En este álbum

+ + {:else} + + {/if} +
+ + {#if formMessage} + {formMessage} + {/if} + +
+ + {#if !isNew} + + {/if} +
+
+
+
+ +{#if album} + (confirmingDelete = false)} + title="¿Eliminar «{album.title}»?" + description="Las canciones no se borran: solo se elimina el álbum." + confirmLabel="Eliminar" + action="?/deleteAlbum" + fields={{ albumId: album.id }} + /> +{/if} diff --git a/web-player/src/lib/components/music/library/LibraryAlbumsSection.svelte b/web-player/src/lib/components/music/library/LibraryAlbumsSection.svelte new file mode 100644 index 0000000..bcfedec --- /dev/null +++ b/web-player/src/lib/components/music/library/LibraryAlbumsSection.svelte @@ -0,0 +1,55 @@ + + +{#if editing} +
+ {#key editing.album?.id} + (editing = null)} + /> + {/key} +
+{:else} +
+ (editing = { album: null })} + /> + {#each albums as album, i (album.id)} + { + event.preventDefault(); + editing = { album }; + }} + /> + {/each} +
+{/if} diff --git a/web-player/src/lib/components/music/library/LibraryTracksSection.svelte b/web-player/src/lib/components/music/library/LibraryTracksSection.svelte new file mode 100644 index 0000000..336b83d --- /dev/null +++ b/web-player/src/lib/components/music/library/LibraryTracksSection.svelte @@ -0,0 +1,124 @@ + + +{#if errorMessage} + {errorMessage} +{/if} + +{#if tracks.length > 0} + {#if selectedIds.length > 0} +
+ {plural(selectedIds.length, 'seleccionada', 'seleccionadas')} + + +
+ {:else} +
+ + +
+ {/if} + + {#if visible.length > 0} + (pendingDelete = [track])} + /> + {:else} + + {/if} +{:else} + + {#snippet actions()} + + {/snippet} + +{/if} + + 0} + onClose={() => (pendingDelete = [])} + title="¿Borrar {pendingTitle}?" + description="Esta acción no se puede deshacer: se eliminarán de tu biblioteca, de tus álbumes y de las playlists donde estén." + confirmLabel="Borrar" + action="?/deleteTracks" + fields={{ trackIds: pendingDelete.map((track) => track.id).join(',') }} +/> diff --git a/web-player/src/lib/components/music/library/LibraryUploadsSection.svelte b/web-player/src/lib/components/music/library/LibraryUploadsSection.svelte new file mode 100644 index 0000000..78f4fc4 --- /dev/null +++ b/web-player/src/lib/components/music/library/LibraryUploadsSection.svelte @@ -0,0 +1,161 @@ + + +
+
+
{ + event.preventDefault(); + dragging = true; + }} + ondragleave={() => (dragging = false)} + ondrop={onDrop} + class="flex min-h-64 flex-col items-center justify-center rounded-panel border border-dashed p-6 text-center transition sm:p-10 {dragging + ? 'border-accent/60 bg-accent-tint' + : 'border-line-strong bg-surface'}" + > + + + +

+ Arrastra tus archivos aquí +

+

+ WAV, FLAC, AIFF o MP3 · hasta 500 MB por archivo · puedes soltar carpetas enteras +

+ + +
+ + single?.title ?? '', (value) => single && setTitle(single.id, value)} + titleDisabled={single === null} + /> +
+ +
+
+
+

Cola de subida

+

+ {queue.length > 0 ? queueMeta : 'Todavía no hay archivos'} +

+
+
+ {#if queue.length > 0} + + {/if} + +
+
+ {#if queue.length > 0} + + {:else} +

+ Los archivos que añadas aparecerán aquí. +

+ {/if} +
+
diff --git a/web-player/src/lib/components/music/library/TrackPickList.svelte b/web-player/src/lib/components/music/library/TrackPickList.svelte new file mode 100644 index 0000000..71ec02d --- /dev/null +++ b/web-player/src/lib/components/music/library/TrackPickList.svelte @@ -0,0 +1,38 @@ + + +
+ {#each tracks as track (track.id)} +
+ onPick(track)} + class="px-3 py-2" + > + {#snippet trailing()} + + {/snippet} + +
+ {/each} +
diff --git a/web-player/src/lib/components/music/library/UploadQueueList.svelte b/web-player/src/lib/components/music/library/UploadQueueList.svelte new file mode 100644 index 0000000..27f1665 --- /dev/null +++ b/web-player/src/lib/components/music/library/UploadQueueList.svelte @@ -0,0 +1,98 @@ + + + + +
+
+ Título + + + + + Estado + +
+
+ {#each items as item (item.id)} +
+
+ + {item.format} + +
+ onTitle(item.id, event.currentTarget.value)} + aria-label="Título de {item.file.name}" + class="w-full truncate rounded-tag bg-transparent text-sm text-fg transition-colors hover:bg-surface focus:bg-surface-2 focus:outline-none" + /> +

{item.file.name}

+
+
+ + + + + + {#if item.error} + + {:else} + + {/if} + + onRemove(item.id)}> + + +
+ {/each} +
+
diff --git a/web-player/src/lib/components/music/library/UploadSettings.svelte b/web-player/src/lib/components/music/library/UploadSettings.svelte new file mode 100644 index 0000000..180422f --- /dev/null +++ b/web-player/src/lib/components/music/library/UploadSettings.svelte @@ -0,0 +1,167 @@ + + + diff --git a/web-player/src/lib/components/shell/TopBar.svelte b/web-player/src/lib/components/shell/TopBar.svelte index 072d8a1..0491e4f 100644 --- a/web-player/src/lib/components/shell/TopBar.svelte +++ b/web-player/src/lib/components/shell/TopBar.svelte @@ -4,10 +4,9 @@ import type { SessionUser } from '$lib/types'; import { SEARCH_DEBOUNCE_MS, SEARCH_MIN_LENGTH } from '$lib/config'; import { searchHref } from '$lib/utils/hrefs'; - import Search from '@lucide/svelte/icons/search'; - import X from '@lucide/svelte/icons/x'; import ChevronLeft from '@lucide/svelte/icons/chevron-left'; import AccountMenu from '$lib/components/shell/AccountMenu.svelte'; + import SearchInput from '$lib/components/ui/primitives/SearchInput.svelte'; import IconButton from '$lib/components/ui/primitives/IconButton.svelte'; import Button from '$lib/components/ui/primitives/Button.svelte'; @@ -21,7 +20,6 @@ let query = $derived( page.url.pathname === '/explore' ? (page.url.searchParams.get('q') ?? '') : '' ); - let searchInput: HTMLInputElement | undefined = $state(); let searchTimeout: ReturnType | undefined; function runSearch(term: string) { @@ -33,16 +31,14 @@ }); } - function onInput() { + function oninput() { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => runSearch(query.trim()), SEARCH_DEBOUNCE_MS); } - function clearQuery() { - query = ''; + function onclear() { clearTimeout(searchTimeout); runSearch(''); - searchInput?.focus(); } function onSubmit(event: SubmitEvent) { @@ -58,29 +54,12 @@