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