diff --git a/bun.lockb b/bun.lockb index 3d79beb..e6d5434 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 5f3d177..392dea0 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@sveltejs/kit": "^2.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0", "autoprefixer": "^10.4.20", - "bits-ui": "^1.0.0-next.64", + "bits-ui": "^1.0.0-next.65", "clsx": "^2.1.1", "globals": "^15.0.0", "lucide-svelte": "^0.462.0", diff --git a/protos/library.proto b/protos/library.proto index ee0ed1d..4be63f6 100644 --- a/protos/library.proto +++ b/protos/library.proto @@ -6,6 +6,11 @@ package library; service Library { rpc ListTracks(google.protobuf.Empty) returns (TrackList); + rpc ListPlaylists(google.protobuf.Empty) returns (ListPlaylistsResponse); + rpc CreatePlaylist(CreatePlaylistRequest) returns (CreatePlaylistResponse); + rpc DeletePlaylist(DeletePlaylistRequest) returns (google.protobuf.Empty); + rpc AddTrackToPlaylist(AddTrackToPlaylistRequest) returns (google.protobuf.Empty); + rpc RemoveTrackFromPlaylist(RemoveTrackFromPlaylistRequest) returns (google.protobuf.Empty); } message TrackList { @@ -19,3 +24,35 @@ message Track { uint64 artist_id = 4; uint64 duration = 5; } + +message Playlist { + uint32 id = 1; + string name = 2; + repeated Track tracks = 3; +} + +message ListPlaylistsResponse { + repeated Playlist playlists = 1; +} + +message CreatePlaylistRequest { + string name = 1; +} + +message CreatePlaylistResponse { + Playlist playlist = 1; +} + +message DeletePlaylistRequest { + uint32 id = 1; +} + +message AddTrackToPlaylistRequest { + uint32 playlist_id = 1; + string track_hash = 2; +} + +message RemoveTrackFromPlaylistRequest { + uint32 playlist_id = 1; + uint32 track_rank = 2; +} diff --git a/protos/player.proto b/protos/player.proto index c4c5813..15a5fae 100644 --- a/protos/player.proto +++ b/protos/player.proto @@ -15,6 +15,8 @@ service Player { rpc SetVolume(SetVolumeRequest) returns (SetVolumeResponse); rpc PlayTrackNext(TrackRequest) returns (Queue); rpc AddTrackToQueue(TrackRequest) returns (Queue); + rpc AddTracksToQueue(TracksRequest) returns (Queue); + rpc PlayPlaylist(PlayPlaylistRequest) returns (PlayerStatus); rpc SwapQueueIndices(SwapQueueIndicesRequest) returns (Queue); rpc SkipTrack(google.protobuf.Empty) returns (PlayerStatus); rpc SkipToQueueIndex(SkipToQueueIndexRequest) returns (PlayerStatus); @@ -69,3 +71,11 @@ message SwapQueueIndicesRequest { uint32 a = 1; uint32 b = 2; } + +message TracksRequest { + repeated string tracks = 1; +} + +message PlayPlaylistRequest { + uint32 id = 1; +} diff --git a/src/lib/components/groove/AppSidebar.svelte b/src/lib/components/groove/AppSidebar.svelte index 094e79b..8e52536 100644 --- a/src/lib/components/groove/AppSidebar.svelte +++ b/src/lib/components/groove/AppSidebar.svelte @@ -1,7 +1,17 @@ @@ -64,6 +74,25 @@ {/snippet} + + + + {#snippet child({ props })} + + + Create Playlist + + {/snippet} + + + {#each library.playlists as playlist} + + + {playlist.name} + + + {/each} + diff --git a/src/lib/components/groove/CreatePlaylistDialog.svelte b/src/lib/components/groove/CreatePlaylistDialog.svelte new file mode 100644 index 0000000..8c9297a --- /dev/null +++ b/src/lib/components/groove/CreatePlaylistDialog.svelte @@ -0,0 +1,69 @@ + + + + + {@render children?.()} + + + + Name your Playlist + Make it memorable + + + + + 24} + > + + Create + + + + diff --git a/src/lib/components/groove/Footer.svelte b/src/lib/components/groove/Footer.svelte index 319dedd..d873a4d 100644 --- a/src/lib/components/groove/Footer.svelte +++ b/src/lib/components/groove/Footer.svelte @@ -52,10 +52,6 @@ }; }; - async function skipToQueueIndex(index: number) { - console.log(`SKIP TO QUEUE INDEX ${index}`); - } - const submitPlayerAction: SubmitFunction = async () => { return async ({ update, result }) => { await update({ diff --git a/src/lib/components/groove/PlaylistListing.svelte b/src/lib/components/groove/PlaylistListing.svelte new file mode 100644 index 0000000..9d4da20 --- /dev/null +++ b/src/lib/components/groove/PlaylistListing.svelte @@ -0,0 +1,115 @@ + + + + 1}> + {#each coverImages as cover} + + {:else} + + {/each} + + + + {name} + + + {dayjs(totalDuration, 'milliseconds').format('mm:ss')} + + + {tracks.length} track{tracks.length !== 1 ? 's' : ''} + + + + + + + + + + + + + + + + + + diff --git a/src/lib/components/groove/Queue.svelte b/src/lib/components/groove/Queue.svelte index 9781dc2..1f7bf4c 100644 --- a/src/lib/components/groove/Queue.svelte +++ b/src/lib/components/groove/Queue.svelte @@ -2,9 +2,9 @@ import { deserialize, enhance } from '$app/forms'; import { getCoverUrl } from '$lib/covers'; import { getPlayerState } from '$lib/player.svelte'; - import { flip } from 'svelte/animate'; import type { SubmitFunction } from '../../../routes/player/$types'; import type { Track } from '$lib/proto/library'; + import { ScrollArea } from '$lib/components/ui/scroll-area'; const player = getPlayerState(); @@ -63,37 +63,35 @@ } - - {#each player.queue as track, i} - e.preventDefault()} - data-queue-index={i} - > - - - - - - {track.name} - - - {track.artistName} - - - - {/each} + + + {#each player.queue as track, i} + e.preventDefault()} + data-queue-index={i} + > + + + + + + {track.name} + + + {track.artistName} + + + + {/each} +
+ {dayjs(totalDuration, 'milliseconds').format('mm:ss')} +
+ {tracks.length} track{tracks.length !== 1 ? 's' : ''} +
- {track.name} -
- {track.artistName} -
+ {track.name} +
+ {track.artistName} +