feat(playlists): double clicking track in playlist queues next tracks

This commit is contained in:
2024-12-03 02:21:29 +01:00
parent 26d815f2e3
commit 2411c5e01c
7 changed files with 48 additions and 10 deletions

View File

@@ -78,4 +78,5 @@ message TracksRequest {
message PlayPlaylistRequest { message PlayPlaylistRequest {
uint32 id = 1; uint32 id = 1;
optional uint32 starting_rank = 2;
} }

View File

@@ -10,8 +10,10 @@
import { cn } from '$lib/utils'; import { cn } from '$lib/utils';
import CreatePlaylistDialog from './CreatePlaylistDialog.svelte'; import CreatePlaylistDialog from './CreatePlaylistDialog.svelte';
import { getLibraryState } from '$lib/library.svelte'; import { getLibraryState } from '$lib/library.svelte';
import { getPlayerState } from '$lib/player.svelte';
const library = getLibraryState(); const library = getLibraryState();
const player = getPlayerState();
</script> </script>
<Sidebar.Root collapsible="icon"> <Sidebar.Root collapsible="icon">
@@ -92,6 +94,7 @@
class="cursor-pointer" class="cursor-pointer"
isActive={$page.url.pathname === playlistLocation} isActive={$page.url.pathname === playlistLocation}
href={playlistLocation} href={playlistLocation}
ondblclick={() => player.playPlaylist({ id: playlist.id }, fetch)}
> >
{playlist.name} {playlist.name}
</Sidebar.MenuSubButton> </Sidebar.MenuSubButton>

View File

@@ -54,7 +54,7 @@
size="icon" size="icon"
onclick={(e) => { onclick={(e) => {
e.stopPropagation(); e.stopPropagation();
player.playPlaylist(id, fetch); player.playPlaylist({ id }, fetch);
}} }}
> >
<Play /> <Play />

View File

@@ -52,13 +52,21 @@ class PlayerState {
} }
} }
async playPlaylist(id: number, fetch: typeof globalThis.fetch) { async playPlaylist(opts: { id: number; startRank?: number }, fetch: typeof globalThis.fetch) {
const { id, startRank } = opts;
const formData = new FormData();
if (startRank) {
formData.set('start', startRank.toString());
}
const response = await fetch(`/playlists/${id}?/play`, { const response = await fetch(`/playlists/${id}?/play`, {
method: 'POST', method: 'POST',
headers: { headers: {
'x-sveltekit-action': 'true' 'x-sveltekit-action': 'true'
}, },
body: new FormData() body: formData
}); });
if (!response.ok) { if (!response.ok) {

View File

@@ -153,6 +153,10 @@ export interface PlayPlaylistRequest {
* @generated from protobuf field: uint32 id = 1; * @generated from protobuf field: uint32 id = 1;
*/ */
id: number; id: number;
/**
* @generated from protobuf field: optional uint32 starting_rank = 2;
*/
startingRank?: number;
} }
// @generated message type with reflection information, may provide speed optimized methods // @generated message type with reflection information, may provide speed optimized methods
class TrackRequest$Type extends MessageType<TrackRequest> { class TrackRequest$Type extends MessageType<TrackRequest> {
@@ -768,7 +772,8 @@ export const TracksRequest = new TracksRequest$Type();
class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> { class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> {
constructor() { constructor() {
super("player.PlayPlaylistRequest", [ super("player.PlayPlaylistRequest", [
{ no: 1, name: "id", kind: "scalar", T: 13 /*ScalarType.UINT32*/ } { no: 1, name: "id", kind: "scalar", T: 13 /*ScalarType.UINT32*/ },
{ no: 2, name: "starting_rank", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ }
]); ]);
} }
create(value?: PartialMessage<PlayPlaylistRequest>): PlayPlaylistRequest { create(value?: PartialMessage<PlayPlaylistRequest>): PlayPlaylistRequest {
@@ -786,6 +791,9 @@ class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> {
case /* uint32 id */ 1: case /* uint32 id */ 1:
message.id = reader.uint32(); message.id = reader.uint32();
break; break;
case /* optional uint32 starting_rank */ 2:
message.startingRank = reader.uint32();
break;
default: default:
let u = options.readUnknownField; let u = options.readUnknownField;
if (u === "throw") if (u === "throw")
@@ -801,6 +809,9 @@ class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> {
/* uint32 id = 1; */ /* uint32 id = 1; */
if (message.id !== 0) if (message.id !== 0)
writer.tag(1, WireType.Varint).uint32(message.id); writer.tag(1, WireType.Varint).uint32(message.id);
/* optional uint32 starting_rank = 2; */
if (message.startingRank !== undefined)
writer.tag(2, WireType.Varint).uint32(message.startingRank);
let u = options.writeUnknownFields; let u = options.writeUnknownFields;
if (u !== false) if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);

View File

@@ -8,11 +8,16 @@ import { fail } from '@sveltejs/kit';
import type { TrackList } from '$lib/proto/library'; import type { TrackList } from '$lib/proto/library';
export const actions = { export const actions = {
play: async ({ params }) => { play: async ({ params, request }) => {
const formData = await request.formData();
const start = formData.get('start')?.toString();
const client = new PlayerClient(protoTransport); const client = new PlayerClient(protoTransport);
const response = await client.playPlaylist({ const response = await client.playPlaylist({
id: parseInt(params.id) id: parseInt(params.id),
startingRank: start ? Number(start) : undefined
}); });
return serializable<PlayerStatus>(response.response); return serializable<PlayerStatus>(response.response);

View File

@@ -8,6 +8,7 @@
import { getCoverUrl } from '$lib/covers'; import { getCoverUrl } from '$lib/covers';
import Play from 'virtual:icons/lucide/play'; import Play from 'virtual:icons/lucide/play';
import Trash2 from 'virtual:icons/lucide/trash-2'; import Trash2 from 'virtual:icons/lucide/trash-2';
import AudioLines from 'virtual:icons/lucide/audio-lines';
import { getPlayerState } from '$lib/player.svelte'; import { getPlayerState } from '$lib/player.svelte';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import * as ContextMenu from '$lib/components/ui/context-menu'; import * as ContextMenu from '$lib/components/ui/context-menu';
@@ -58,7 +59,7 @@
</div> </div>
<div class="mx-4"> <div class="mx-4">
<div class="mb-2"> <div class="mb-2">
<Button class="size-12" onclick={() => player.playPlaylist(playlist.id, fetch)} <Button class="size-12" onclick={() => player.playPlaylist({ id: playlist.id }, fetch)}
><Play /></Button ><Play /></Button
> >
</div> </div>
@@ -80,7 +81,7 @@
<Table.Row <Table.Row
{...props} {...props}
class="select-none" class="select-none"
ondblclick={() => player.playTrack(track.hash, fetch)} ondblclick={() => player.playPlaylist({ id: playlist.id, startRank: i }, fetch)}
data-playlist-index={i} data-playlist-index={i}
ondragstart={onDragStart} ondragstart={onDragStart}
ondrop={onDrop} ondrop={onDrop}
@@ -88,14 +89,23 @@
draggable draggable
> >
<Table.Cell class="text-center">{i + 1}</Table.Cell> <Table.Cell class="text-center">{i + 1}</Table.Cell>
<Table.Cell class="w-0"> <Table.Cell class="relative w-0">
<div
class="absolute bottom-0 left-0 right-0 top-0 z-10 hidden items-center justify-center"
class:!flex={player.currentlyPlaying?.hash === track.hash}
>
<AudioLines class="size-6 animate-pulse text-primary" />
</div>
<img <img
class="overflow-hidden rounded-md" class="overflow-hidden rounded-md"
class:brightness-[40%]={player.currentlyPlaying?.hash === track.hash}
src={getCoverUrl(track.hash)} src={getCoverUrl(track.hash)}
alt="" alt=""
/> />
</Table.Cell> </Table.Cell>
<Table.Cell class="text-nowrap">{track.name}</Table.Cell> <Table.Cell class="text-nowrap">
{track.name}
</Table.Cell>
<Table.Cell class="w-fit text-nowrap">{track.artistName}</Table.Cell> <Table.Cell class="w-fit text-nowrap">{track.artistName}</Table.Cell>
<Table.Cell class="text-right" <Table.Cell class="text-right"
>{dayjs >{dayjs