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

View File

@@ -26,35 +26,6 @@
const library = getLibraryState(); const library = getLibraryState();
const player = getPlayerState(); 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> </script>
<!-- svelte-ignore a11y_interactive_supports_focus --> <!-- svelte-ignore a11y_interactive_supports_focus -->
@@ -79,17 +50,23 @@
</div> </div>
</div> </div>
<div class="flex w-full justify-between gap-1"> <div class="flex w-full justify-between gap-1">
<form method="POST" action="/playlists/{id}?/play" use:enhance={playPlaylist}> <Button
<Button type="submit" variant="outline" size="icon"> type="submit"
variant="outline"
size="icon"
onclick={() => player.playPlaylist(id, fetch)}
>
<Play /> <Play />
</Button> </Button>
</form>
<form method="POST" action="/playlists/{id}?/delete" use:enhance={deletePlaylist}> <Button
<Button type="submit" variant="outline" size="icon"> type="submit"
variant="outline"
size="icon"
onclick={() => library.deletePlaylist(id, fetch)}
>
<Trash2 /> <Trash2 />
</Button> </Button>
</form>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -14,6 +14,35 @@ class LibraryState {
this.playlists = new SvelteMap(playlists.map((p) => [p.id, p])); 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) { async addTrackToPlaylist(playlistId: number, trackHash: string, fetch: typeof globalThis.fetch) {
const response = await fetch(`/playlists/${playlistId}/add-track/${trackHash}`, { const response = await fetch(`/playlists/${playlistId}/add-track/${trackHash}`, {
method: 'POST', 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(); const formData = new FormData();
formData.set('a', aIndex.toString()); 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'); const LIBRARY_KEY = Symbol('GROOVE_LIBRARY');

View File

@@ -8,7 +8,7 @@
<div <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" 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} /> <PlaylistListing id={playlist.id} name={playlist.name} tracks={playlist.tracks} />
{/each} {/each}
</div> </div>

View File

@@ -36,7 +36,7 @@
const aIndex = e.dataTransfer?.getData('text/plain')!; const aIndex = e.dataTransfer?.getData('text/plain')!;
const bIndex = (e.currentTarget as HTMLElement).getAttribute('data-playlist-index')!; 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> </script>