feat(playlists): double clicking track in playlist queues next tracks
This commit is contained in:
@@ -78,4 +78,5 @@ message TracksRequest {
|
|||||||
|
|
||||||
message PlayPlaylistRequest {
|
message PlayPlaylistRequest {
|
||||||
uint32 id = 1;
|
uint32 id = 1;
|
||||||
|
optional uint32 starting_rank = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user