From b5ace5ac28582249be2b25b69d66599ff9dca6fa Mon Sep 17 00:00:00 2001 From: 409 Date: Mon, 2 Dec 2024 23:20:27 +0100 Subject: [PATCH] feat(playlists): single playlist view --- bun.lockb | Bin 97776 -> 97776 bytes src/lib/components/groove/AppSidebar.svelte | 7 +- .../components/groove/PlaylistCover.svelte | 48 +++++++++++ .../components/groove/PlaylistListing.svelte | 56 ++++--------- src/lib/components/groove/TrackListing.svelte | 3 +- src/lib/components/ui/table/index.ts | 28 +++++++ src/lib/components/ui/table/table-body.svelte | 16 ++++ .../components/ui/table/table-caption.svelte | 16 ++++ src/lib/components/ui/table/table-cell.svelte | 23 ++++++ .../components/ui/table/table-footer.svelte | 16 ++++ src/lib/components/ui/table/table-head.svelte | 23 ++++++ .../components/ui/table/table-header.svelte | 16 ++++ src/lib/components/ui/table/table-row.svelte | 23 ++++++ src/lib/components/ui/table/table.svelte | 18 ++++ src/lib/player.svelte.ts | 45 ++++++++++ src/routes/playlists/[id]/+page.svelte | 78 ++++++++++++++++++ 16 files changed, 372 insertions(+), 44 deletions(-) create mode 100644 src/lib/components/groove/PlaylistCover.svelte create mode 100644 src/lib/components/ui/table/index.ts create mode 100644 src/lib/components/ui/table/table-body.svelte create mode 100644 src/lib/components/ui/table/table-caption.svelte create mode 100644 src/lib/components/ui/table/table-cell.svelte create mode 100644 src/lib/components/ui/table/table-footer.svelte create mode 100644 src/lib/components/ui/table/table-head.svelte create mode 100644 src/lib/components/ui/table/table-header.svelte create mode 100644 src/lib/components/ui/table/table-row.svelte create mode 100644 src/lib/components/ui/table/table.svelte create mode 100644 src/routes/playlists/[id]/+page.svelte diff --git a/bun.lockb b/bun.lockb index e6d5434d22daf3a49b4bb2d39345869e6f705a06..ca985f3b6281ac03d9aefe5d3399b83fdab0e5bb 100755 GIT binary patch delta 27 icmezHoAtwQ)`l&Ni7cFqaRz!OW=47j+p}00+bRK@!wFyj delta 25 hcmezHoAtwQ)`l&Ni7ZS^4Ac3Q8O66(u`rfZ0sw~f2!#Lu diff --git a/src/lib/components/groove/AppSidebar.svelte b/src/lib/components/groove/AppSidebar.svelte index 8e52536..618bec3 100644 --- a/src/lib/components/groove/AppSidebar.svelte +++ b/src/lib/components/groove/AppSidebar.svelte @@ -86,8 +86,13 @@ {#each library.playlists as playlist} + {@const playlistLocation = `/playlists/${playlist.id}`} - + {playlist.name} diff --git a/src/lib/components/groove/PlaylistCover.svelte b/src/lib/components/groove/PlaylistCover.svelte new file mode 100644 index 0000000..6f6a4d2 --- /dev/null +++ b/src/lib/components/groove/PlaylistCover.svelte @@ -0,0 +1,48 @@ + + +
1}> + {#each coverImages as cover} + + {:else} +
+ {/each} +
diff --git a/src/lib/components/groove/PlaylistListing.svelte b/src/lib/components/groove/PlaylistListing.svelte index 940084c..8656b72 100644 --- a/src/lib/components/groove/PlaylistListing.svelte +++ b/src/lib/components/groove/PlaylistListing.svelte @@ -1,14 +1,17 @@ -
-
1}> - {#each coverImages as cover} - - {:else} -
- {/each} -
+ + +
goto(`/playlists/${id}`)} +> + +

{name}

- {dayjs(totalDuration, 'milliseconds').format('mm:ss')} + {dayjs.duration(totalDuration, 'milliseconds').format('mm:ss')}

{tracks.length} track{tracks.length !== 1 ? 's' : ''} diff --git a/src/lib/components/groove/TrackListing.svelte b/src/lib/components/groove/TrackListing.svelte index 8caceb8..a8f55d3 100644 --- a/src/lib/components/groove/TrackListing.svelte +++ b/src/lib/components/groove/TrackListing.svelte @@ -5,11 +5,10 @@ interface Props { track: Track; - currentlyPlaying?: boolean; oncontextmenu?: (event: MouseEvent, hash: string) => any; } - let { track, currentlyPlaying = false, oncontextmenu }: Props = $props(); + let { track, oncontextmenu }: Props = $props();

diff --git a/src/lib/components/ui/table/index.ts b/src/lib/components/ui/table/index.ts new file mode 100644 index 0000000..14695c8 --- /dev/null +++ b/src/lib/components/ui/table/index.ts @@ -0,0 +1,28 @@ +import Root from "./table.svelte"; +import Body from "./table-body.svelte"; +import Caption from "./table-caption.svelte"; +import Cell from "./table-cell.svelte"; +import Footer from "./table-footer.svelte"; +import Head from "./table-head.svelte"; +import Header from "./table-header.svelte"; +import Row from "./table-row.svelte"; + +export { + Root, + Body, + Caption, + Cell, + Footer, + Head, + Header, + Row, + // + Root as Table, + Body as TableBody, + Caption as TableCaption, + Cell as TableCell, + Footer as TableFooter, + Head as TableHead, + Header as TableHeader, + Row as TableRow, +}; diff --git a/src/lib/components/ui/table/table-body.svelte b/src/lib/components/ui/table/table-body.svelte new file mode 100644 index 0000000..6c20c01 --- /dev/null +++ b/src/lib/components/ui/table/table-body.svelte @@ -0,0 +1,16 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-caption.svelte b/src/lib/components/ui/table/table-caption.svelte new file mode 100644 index 0000000..2b0cba5 --- /dev/null +++ b/src/lib/components/ui/table/table-caption.svelte @@ -0,0 +1,16 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-cell.svelte b/src/lib/components/ui/table/table-cell.svelte new file mode 100644 index 0000000..d859856 --- /dev/null +++ b/src/lib/components/ui/table/table-cell.svelte @@ -0,0 +1,23 @@ + + +[role=checkbox]]:translate-y-[2px]", + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-footer.svelte b/src/lib/components/ui/table/table-footer.svelte new file mode 100644 index 0000000..0267c47 --- /dev/null +++ b/src/lib/components/ui/table/table-footer.svelte @@ -0,0 +1,16 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-head.svelte b/src/lib/components/ui/table/table-head.svelte new file mode 100644 index 0000000..45b2896 --- /dev/null +++ b/src/lib/components/ui/table/table-head.svelte @@ -0,0 +1,23 @@ + + +[role=checkbox]]:translate-y-[2px]", + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-header.svelte b/src/lib/components/ui/table/table-header.svelte new file mode 100644 index 0000000..684a57b --- /dev/null +++ b/src/lib/components/ui/table/table-header.svelte @@ -0,0 +1,16 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table-row.svelte b/src/lib/components/ui/table/table-row.svelte new file mode 100644 index 0000000..9e693bc --- /dev/null +++ b/src/lib/components/ui/table/table-row.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/table/table.svelte b/src/lib/components/ui/table/table.svelte new file mode 100644 index 0000000..e3a95b6 --- /dev/null +++ b/src/lib/components/ui/table/table.svelte @@ -0,0 +1,18 @@ + + +
+ + {@render children?.()} +
+
diff --git a/src/lib/player.svelte.ts b/src/lib/player.svelte.ts index 9f9013c..96570ba 100644 --- a/src/lib/player.svelte.ts +++ b/src/lib/player.svelte.ts @@ -3,6 +3,7 @@ import type { PlayerStatus } from './proto/player'; import { getContext, setContext } from 'svelte'; import { PlayerClient } from './proto/player.client'; import { protoTransport } from '../hooks.client'; +import { deserialize } from '$app/forms'; class PlayerState { volume = $state(0.0); @@ -27,6 +28,50 @@ class PlayerState { stream.responses.onMessage((status) => this.applyStatus(status)); } + async playTrack(hash: string, fetch: typeof globalThis.fetch) { + const response = await fetch(`/tracks/${hash}?/play`, { + method: 'POST', + headers: { + 'x-sveltekit-action': 'true' + }, + body: new FormData() + }); + + if (!response.ok) { + return; + } + + const result = deserialize(await response.text()); + + if (result.type === 'success' && result.data) { + const track = result.data.track as Track | null; + const position = result.data.position as bigint; + + this.currentlyPlaying = track; + this.progress = position; + } + } + + async playPlaylist(id: number, fetch: typeof globalThis.fetch) { + const response = await fetch(`/playlists/${id}?/play`, { + method: 'POST', + headers: { + 'x-sveltekit-action': 'true' + }, + body: new FormData() + }); + + if (!response.ok) { + return; + } + + const result = deserialize(await response.text()); + + if (result.type === 'success' && result.data) { + this.applyStatus(result.data as unknown as PlayerStatus); + } + } + applyStatus(status: PlayerStatus) { if (!this.adjustingVolume) { this.volume = status.volume; diff --git a/src/routes/playlists/[id]/+page.svelte b/src/routes/playlists/[id]/+page.svelte new file mode 100644 index 0000000..13663af --- /dev/null +++ b/src/routes/playlists/[id]/+page.svelte @@ -0,0 +1,78 @@ + + +{#if playlist} +
+
+
+ +
+
+

{playlist.name}

+

+ {playlist.tracks.length} track{playlist.tracks.length !== 1 ? 's' : ''} +

+

+ {dayjs.duration(totalDuration, 'milliseconds').format('HH:mm:ss')} +

+
+
+
+
+ +
+ + + + Position + Track + + Artist + Duration + + + + + {#each playlist.tracks as track, i} + player.playTrack(track.hash, fetch)}> + {i + 1} + + + + {track.name} + {track.artistName} + {dayjs + .duration(Number(track.duration), 'milliseconds') + .format('mm:ss')} + + {/each} + + +
+
+{:else} + 404 +{/if}