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 {
uint32 id = 1;
optional uint32 starting_rank = 2;
}

View File

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

View File

@@ -54,7 +54,7 @@
size="icon"
onclick={(e) => {
e.stopPropagation();
player.playPlaylist(id, fetch);
player.playPlaylist({ id }, fetch);
}}
>
<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`, {
method: 'POST',
headers: {
'x-sveltekit-action': 'true'
},
body: new FormData()
body: formData
});
if (!response.ok) {

View File

@@ -153,6 +153,10 @@ export interface PlayPlaylistRequest {
* @generated from protobuf field: uint32 id = 1;
*/
id: number;
/**
* @generated from protobuf field: optional uint32 starting_rank = 2;
*/
startingRank?: number;
}
// @generated message type with reflection information, may provide speed optimized methods
class TrackRequest$Type extends MessageType<TrackRequest> {
@@ -768,7 +772,8 @@ export const TracksRequest = new TracksRequest$Type();
class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> {
constructor() {
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 {
@@ -786,6 +791,9 @@ class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> {
case /* uint32 id */ 1:
message.id = reader.uint32();
break;
case /* optional uint32 starting_rank */ 2:
message.startingRank = reader.uint32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
@@ -801,6 +809,9 @@ class PlayPlaylistRequest$Type extends MessageType<PlayPlaylistRequest> {
/* uint32 id = 1; */
if (message.id !== 0)
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;
if (u !== false)
(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';
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 response = await client.playPlaylist({
id: parseInt(params.id)
id: parseInt(params.id),
startingRank: start ? Number(start) : undefined
});
return serializable<PlayerStatus>(response.response);

View File

@@ -8,6 +8,7 @@
import { getCoverUrl } from '$lib/covers';
import Play from 'virtual:icons/lucide/play';
import Trash2 from 'virtual:icons/lucide/trash-2';
import AudioLines from 'virtual:icons/lucide/audio-lines';
import { getPlayerState } from '$lib/player.svelte';
import { Button } from '$lib/components/ui/button';
import * as ContextMenu from '$lib/components/ui/context-menu';
@@ -58,7 +59,7 @@
</div>
<div class="mx-4">
<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
>
</div>
@@ -80,7 +81,7 @@
<Table.Row
{...props}
class="select-none"
ondblclick={() => player.playTrack(track.hash, fetch)}
ondblclick={() => player.playPlaylist({ id: playlist.id, startRank: i }, fetch)}
data-playlist-index={i}
ondragstart={onDragStart}
ondrop={onDrop}
@@ -88,14 +89,23 @@
draggable
>
<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
class="overflow-hidden rounded-md"
class:brightness-[40%]={player.currentlyPlaying?.hash === track.hash}
src={getCoverUrl(track.hash)}
alt=""
/>
</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="text-right"
>{dayjs