diff --git a/web-player/src/lib/components/ui/media/PlayAllButton.svelte b/web-player/src/lib/components/ui/media/PlayAllButton.svelte new file mode 100644 index 0000000..846f243 --- /dev/null +++ b/web-player/src/lib/components/ui/media/PlayAllButton.svelte @@ -0,0 +1,35 @@ + + + diff --git a/web-player/src/lib/components/ui/overlay/AlbumContextMenu.svelte b/web-player/src/lib/components/ui/overlay/AlbumContextMenu.svelte new file mode 100644 index 0000000..12b044c --- /dev/null +++ b/web-player/src/lib/components/ui/overlay/AlbumContextMenu.svelte @@ -0,0 +1,46 @@ + + +{#if menu.state} + {@const { target: albumId, x, y, openLeft } = menu.state} + menu.close()} + items={[ + { icon: ListPlus, label: 'Reproducir a continuación', onclick: () => playNext(albumId) }, + { icon: ListEnd, label: 'Añadir a la cola', onclick: () => addToQueue(albumId) } + ]} + playlistAction={{ + action: '?/addAlbumToPlaylist', + fields: { albumId }, + label: 'Añadir álbum a una playlist' + }} + {playlists} + /> +{/if} diff --git a/web-player/src/lib/components/ui/overlay/TrackContextMenu.svelte b/web-player/src/lib/components/ui/overlay/TrackContextMenu.svelte new file mode 100644 index 0000000..4e4bcb1 --- /dev/null +++ b/web-player/src/lib/components/ui/overlay/TrackContextMenu.svelte @@ -0,0 +1,36 @@ + + +{#if menu.state} + {@const { target: track, x, y, openLeft } = menu.state} + menu.close()} + items={[{ icon: ListPlus, label: 'Reproducir a continuación', onclick: () => playNext(track) }]} + playlistAction={{ + action: '?/addTrack', + fields: { trackId: track.id }, + label: 'Añadir a una playlist' + }} + {playlists} + /> +{/if} diff --git a/web-player/src/lib/components/ui/primitives/InfiniteScroll.svelte b/web-player/src/lib/components/ui/primitives/InfiniteScroll.svelte index e4e0cb8..7074c58 100644 --- a/web-player/src/lib/components/ui/primitives/InfiniteScroll.svelte +++ b/web-player/src/lib/components/ui/primitives/InfiniteScroll.svelte @@ -1,15 +1,23 @@
{#if loading} + {:else if error} +
+ No se pudo cargar más. + +
{/if}
diff --git a/web-player/src/lib/state/menu.svelte.ts b/web-player/src/lib/state/menu.svelte.ts new file mode 100644 index 0000000..1b84a96 --- /dev/null +++ b/web-player/src/lib/state/menu.svelte.ts @@ -0,0 +1,22 @@ +import { contextMenuPosition, type MenuPosition } from '$lib/utils/menuPosition'; + +export type MenuState = { target: T } & MenuPosition; + +export function createMenu() { + let state = $state | null>(null); + + return { + get state() { + return state; + }, + open(event: MouseEvent, target: T) { + event.preventDefault(); + state = { target, ...contextMenuPosition(event) }; + }, + close() { + state = null; + } + }; +} + +export type Menu = ReturnType>; diff --git a/web-player/src/lib/state/menus.svelte.ts b/web-player/src/lib/state/menus.svelte.ts deleted file mode 100644 index 42db1be..0000000 --- a/web-player/src/lib/state/menus.svelte.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { contextMenuPosition, type MenuPosition } from '$lib/utils/menuPosition'; -import { player } from '$lib/player/player.svelte'; -import type { Track } from '$lib/types'; - -export type TrackMenuState = { track: Track } & MenuPosition; - -export function createTrackMenu() { - let state = $state(null); - - return { - get state() { - return state; - }, - open(event: MouseEvent, track: Track) { - event.preventDefault(); - state = { track, ...contextMenuPosition(event) }; - }, - close() { - state = null; - }, - playNext() { - if (!state) return; - player.playNext([state.track]); - state = null; - } - }; -} diff --git a/web-player/src/lib/state/pagedList.svelte.ts b/web-player/src/lib/state/pagedList.svelte.ts new file mode 100644 index 0000000..b6b9580 --- /dev/null +++ b/web-player/src/lib/state/pagedList.svelte.ts @@ -0,0 +1,50 @@ +import type { Paged } from '$lib/types'; + +export function createPagedList( + fetchPage: (pageNumber: number) => Promise>, + keyOf: (item: T) => string | number +) { + let items = $state([]); + let pageNumber = 1; + let hasMore = $state(false); + let loading = $state(false); + let error = $state(false); + + return { + get items() { + return items; + }, + get hasMore() { + return hasMore; + }, + get loading() { + return loading; + }, + get error() { + return error; + }, + reset(initial: Paged | null) { + items = initial?.items ?? []; + pageNumber = initial?.pageNumber ?? 1; + hasMore = initial?.hasNextPage ?? false; + loading = false; + error = false; + }, + async loadMore() { + if (loading) return; + loading = true; + error = false; + try { + const next = await fetchPage(pageNumber + 1); + const seen = new Set(items.map(keyOf)); + items = [...items, ...next.items.filter((item) => !seen.has(keyOf(item)))]; + pageNumber = next.pageNumber; + hasMore = next.hasNextPage; + } catch { + error = true; + } finally { + loading = false; + } + } + }; +} diff --git a/web-player/src/routes/(app)/albums/[id]/+page.svelte b/web-player/src/routes/(app)/albums/[id]/+page.svelte index d0e5bdf..fb24a80 100644 --- a/web-player/src/routes/(app)/albums/[id]/+page.svelte +++ b/web-player/src/routes/(app)/albums/[id]/+page.svelte @@ -1,7 +1,7 @@ @@ -334,7 +291,7 @@ active={player.currentId === track.id} size="lg" trackId={track.id} - onclick={() => togglePlay(i)} + onclick={() => player.playOrToggle(items, i)} oncontextmenu={(e) => trackMenu.open(e, track)} > {#snippet overlay()} @@ -352,8 +309,13 @@ {/each} - {#if sfilter === 'tracks' && hasMore} - + {#if sfilter === 'tracks' && tracksList.hasMore} + tracksList.loadMore()} + hasMore={tracksList.hasMore} + loading={tracksList.loading} + error={tracksList.error} + /> {/if} {:else} openAlbumMenu(e, album.id)} + oncontextmenu={(e) => albumMenu.open(e, album.id)} > {#snippet trailing()}