feat(queue): drag and drop reorder

This commit is contained in:
2024-11-28 02:36:48 +01:00
parent 60613e6502
commit 30e7b758ad
8 changed files with 218 additions and 33 deletions

View File

@@ -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>

View 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>