feat(queue): drag and drop reorder
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
import { getCoverUrl } from '$lib/covers';
|
||||
import VolumeSlider from '$lib/components/groove/VolumeSlider.svelte';
|
||||
import type { SubmitFunction } from '../../../routes/player/$types';
|
||||
import Queue from './Queue.svelte';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
@@ -133,35 +134,7 @@
|
||||
|
||||
<Separator class="my-1" />
|
||||
|
||||
<form
|
||||
class="flex h-full flex-col gap-1 overflow-y-auto pr-3"
|
||||
method="POST"
|
||||
use:enhance={submitPlayerAction}
|
||||
>
|
||||
{#each player.queue as track, i}
|
||||
<button
|
||||
type="submit"
|
||||
class="flex flex-row items-center gap-2 rounded-lg px-3 py-2 transition-all hover:bg-secondary"
|
||||
formaction="/player?/skip-to-queue-index&index={i}"
|
||||
>
|
||||
<div class="min-w-8 overflow-hidden rounded-md">
|
||||
<img src={getCoverUrl(track.hash)} class="aspect-square size-8" alt="Cover" />
|
||||
</div>
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<p
|
||||
class="w-full self-start overflow-hidden text-ellipsis text-nowrap text-left text-sm text-foreground/80"
|
||||
>
|
||||
{track.name}
|
||||
</p>
|
||||
<p
|
||||
class="w-full self-start overflow-hidden text-left text-xs text-muted-foreground"
|
||||
>
|
||||
{track.artistName}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</form>
|
||||
<Queue />
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
|
||||
|
||||
108
src/lib/components/groove/Queue.svelte
Normal file
108
src/lib/components/groove/Queue.svelte
Normal file
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
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';
|
||||
|
||||
const player = getPlayerState();
|
||||
|
||||
const submitPlayerAction: SubmitFunction = async () => {
|
||||
return async ({ update, result }) => {
|
||||
await update({
|
||||
invalidateAll: false
|
||||
});
|
||||
|
||||
if (result.type !== 'success' || !result.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('isPaused' in result.data) {
|
||||
if (!('volume' in result.data)) {
|
||||
player.isPaused = result.data.isPaused;
|
||||
} else {
|
||||
player.applyStatus(result.data);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function onDragStart(e: DragEvent) {
|
||||
const element = e.target as HTMLElement;
|
||||
|
||||
e.dataTransfer?.setData('text/plain', element.getAttribute('data-queue-index')!);
|
||||
}
|
||||
|
||||
async function onDrop(e: DragEvent) {
|
||||
const aIndex = e.dataTransfer?.getData('text/plain')!;
|
||||
const bIndex = (e.target as HTMLElement).getAttribute('data-queue-index')!;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.set('a', aIndex);
|
||||
formData.set('b', bIndex);
|
||||
|
||||
const response = await fetch(`/player?/swap-queue-indices`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-sveltekit-action': 'true'
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = deserialize(await response.text());
|
||||
|
||||
if (result.type === 'success' && result.data && 'tracks' in result.data) {
|
||||
player.queue = result.data.tracks as Track[];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="flex h-full flex-col gap-1 overflow-y-auto pr-3"
|
||||
method="POST"
|
||||
use:enhance={submitPlayerAction}
|
||||
>
|
||||
{#each player.queue as track, i (track.hash)}
|
||||
<button
|
||||
animate:flip={{ duration: 100 }}
|
||||
type="submit"
|
||||
class="flex flex-row items-center gap-2 rounded-lg px-3 py-2 transition-all hover:bg-secondary"
|
||||
formaction="/player?/skip-to-queue-index&index={i}"
|
||||
draggable={true}
|
||||
ondragstart={onDragStart}
|
||||
ondrop={onDrop}
|
||||
ondragover={(e) => e.preventDefault()}
|
||||
data-queue-index={i}
|
||||
>
|
||||
<div class="min-w-8 overflow-hidden rounded-md">
|
||||
<img src={getCoverUrl(track.hash)} class="aspect-square size-8" alt="Cover" />
|
||||
</div>
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<p
|
||||
class="w-full self-start overflow-hidden text-ellipsis text-nowrap text-left text-sm text-foreground/80"
|
||||
>
|
||||
{track.name}
|
||||
</p>
|
||||
<p class="w-full self-start overflow-hidden text-left text-xs text-muted-foreground">
|
||||
{track.artistName}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</form>
|
||||
|
||||
<style>
|
||||
button {
|
||||
-webkit-user-drag: element !important;
|
||||
}
|
||||
|
||||
button > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user