fix(playlists)!: show newly created playlist instantly
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user