fix(playlists)!: show newly created playlist instantly

This commit is contained in:
2024-12-03 01:48:01 +01:00
parent 73da50b9fd
commit a522aa301d
5 changed files with 101 additions and 77 deletions

View File

@@ -1,11 +1,10 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import * as Dialog from '$lib/components/ui/dialog';
import { enhance } from '$app/forms';
import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button';
import CirclePlus from 'virtual:icons/lucide/circle-plus';
import type { SubmitFunction } from '../../../routes/playlists/$types';
import { getLibraryState } from '$lib/library.svelte';
interface Props {
children?: Snippet;
@@ -14,28 +13,23 @@
let { children, ...props }: Props = $props();
const library = getLibraryState();
let name = $state('');
let submitting = $state(false);
let isOpen = $state(false);
const createPlaylist: SubmitFunction = async ({ cancel }) => {
if (submitting) {
cancel();
return;
async function createPlaylist() {
submitting = true;
const success = await library.createPlaylist(name, fetch);
if (success) {
isOpen = false;
name = '';
}
submitting = true;
isOpen = false;
return async ({ update }) => {
await update({
invalidateAll: true,
reset: true
});
submitting = false;
};
};
submitting = false;
}
</script>
<Dialog.Root bind:open={isOpen}>
@@ -48,22 +42,21 @@
<Dialog.Description>Make it memorable</Dialog.Description>
</Dialog.Header>
<form method="POST" action="/playlists?/create" use:enhance={createPlaylist}>
<Input
name="name"
placeholder="My awesome playlist"
minlength={1}
maxlength={24}
bind:value={name}
/>
<Button
type="submit"
class="mt-2 w-full"
disabled={submitting || name.length < 1 || name.length > 24}
>
<CirclePlus />
Create
</Button>
</form>
<Input
name="name"
placeholder="My awesome playlist"
minlength={1}
maxlength={24}
bind:value={name}
/>
<Button
type="submit"
class="mt-2 w-full"
disabled={submitting || name.length < 1 || name.length > 24}
onclick={createPlaylist}
>
<CirclePlus />
Create
</Button>
</Dialog.Content>
</Dialog.Root>

View File

@@ -26,35 +26,6 @@
const library = getLibraryState();
const player = getPlayerState();
const playPlaylist: SubmitFunction = async () => {
return async ({ update, result }) => {
await update({
invalidateAll: false,
reset: false
});
if (result.type === 'success' && result.data) {
player.applyStatus(result.data);
}
};
};
// TODO: Refactor
const deletePlaylist: SubmitFunction = async ({ action }) => {
const playlistId = parseInt(action.pathname.split('/playlists/')[1].split('/')[0]);
return async ({ update, result }) => {
await update({
invalidateAll: false,
reset: false
});
if (result.type === 'success') {
library.playlists = library.playlists.filter((p) => p.id !== playlistId);
}
};
};
</script>
<!-- svelte-ignore a11y_interactive_supports_focus -->
@@ -79,17 +50,23 @@
</div>
</div>
<div class="flex w-full justify-between gap-1">
<form method="POST" action="/playlists/{id}?/play" use:enhance={playPlaylist}>
<Button type="submit" variant="outline" size="icon">
<Play />
</Button>
</form>
<Button
type="submit"
variant="outline"
size="icon"
onclick={() => player.playPlaylist(id, fetch)}
>
<Play />
</Button>
<form method="POST" action="/playlists/{id}?/delete" use:enhance={deletePlaylist}>
<Button type="submit" variant="outline" size="icon">
<Trash2 />
</Button>
</form>
<Button
type="submit"
variant="outline"
size="icon"
onclick={() => library.deletePlaylist(id, fetch)}
>
<Trash2 />
</Button>
</div>
</div>
</div>

View File

@@ -14,6 +14,35 @@ class LibraryState {
this.playlists = new SvelteMap(playlists.map((p) => [p.id, p]));
}
async createPlaylist(name: string, fetch: typeof globalThis.fetch) {
const formData = new FormData();
formData.set('name', name);
const response = await fetch(`/playlists?/create`, {
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 && 'playlist' in result.data) {
const playlist = result.data.playlist as Playlist;
this.playlists.set(playlist.id, playlist);
return playlist;
}
return;
}
async addTrackToPlaylist(playlistId: number, trackHash: string, fetch: typeof globalThis.fetch) {
const response = await fetch(`/playlists/${playlistId}/add-track/${trackHash}`, {
method: 'POST',
@@ -72,7 +101,12 @@ class LibraryState {
}
}
async swapPlaylistTracks(playlistId: number, aIndex: number, bIndex: number) {
async swapPlaylistTracks(
playlistId: number,
aIndex: number,
bIndex: number,
fetch: typeof globalThis.fetch
) {
const formData = new FormData();
formData.set('a', aIndex.toString());
@@ -103,6 +137,26 @@ class LibraryState {
}
}
}
async deletePlaylist(playlistId: number, fetch: typeof globalThis.fetch) {
const response = await fetch(`/playlists/${playlistId}?/delete`, {
method: 'POST',
headers: {
'x-sveltekit-action': 'true'
},
body: new FormData()
});
if (!response.ok) {
return;
}
const result = deserialize(await response.text());
if (result.type === 'success') {
this.playlists.delete(playlistId);
}
}
}
const LIBRARY_KEY = Symbol('GROOVE_LIBRARY');

View File

@@ -8,7 +8,7 @@
<div
class="grid grid-cols-2 gap-4 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7 2xl:grid-cols-8"
>
{#each library.playlists as playlist}
{#each library.playlists as [_, playlist]}
<PlaylistListing id={playlist.id} name={playlist.name} tracks={playlist.tracks} />
{/each}
</div>

View File

@@ -36,7 +36,7 @@
const aIndex = e.dataTransfer?.getData('text/plain')!;
const bIndex = (e.currentTarget as HTMLElement).getAttribute('data-playlist-index')!;
library.swapPlaylistTracks(playlist.id, Number(aIndex), Number(bIndex));
library.swapPlaylistTracks(playlist.id, Number(aIndex), Number(bIndex), fetch);
}
</script>