feat!: playlists

This commit is contained in:
2024-12-01 03:49:08 +01:00
parent 1a4bde9618
commit 2772abcd2f
40 changed files with 1530 additions and 69 deletions

View File

@@ -0,0 +1,115 @@
<script lang="ts">
import type { Track } from '$lib/proto/library';
import dayjs from 'dayjs';
import { Button } from '$lib/components/ui/button';
import Trash2 from 'virtual:icons/lucide/trash-2';
import { enhance } from '$app/forms';
import Play from 'virtual:icons/lucide/play';
import type { SubmitFunction } from '../../../routes/playlists/[id]/$types';
import { getCoverUrl } from '$lib/covers';
import { getLibraryState } from '$lib/library.svelte';
interface Props {
id: number;
name: string;
tracks: Track[];
}
let { id, name, tracks }: Props = $props();
let totalDuration = $derived<number>(
tracks.reduce((acc, track) => acc + Number(track.duration), 0)
);
const library = getLibraryState();
let coverImages = $derived.by(() => {
if (tracks.length === 0) {
return [];
}
if (tracks.length >= 4) {
return tracks.slice(0, 4).map((t) => getCoverUrl(t.hash));
}
if (tracks.length === 1) {
return [getCoverUrl(tracks[0].hash)];
}
if (tracks.length === 2) {
const x = getCoverUrl(tracks[0].hash);
const y = getCoverUrl(tracks[1].hash);
return [x, y, y, x];
}
let _covers: string[] = [];
let i = 0;
while (_covers.length < 4) {
_covers.push(getCoverUrl(tracks[i].hash));
i = (i + 1) % tracks.length;
}
return _covers;
});
const playPlaylist: SubmitFunction = async () => {
return async ({ update }) => {
await update({
invalidateAll: false,
reset: false
});
};
};
// 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>
<div class="overflow-hidden rounded-md border">
<div class="relative grid" class:grid-cols-2={coverImages.length > 1}>
{#each coverImages as cover}
<img class="aspect-square" src={cover} alt="" />
{:else}
<div class="w-full aspect-square animate-pulse bg-secondary"></div>
{/each}
</div>
<div class="space-y-2 p-4">
<div class="relative space-y-2">
<h3 class="font-medium leading-none">{name}</h3>
<div>
<p class="text-xs text-muted-foreground">
{dayjs(totalDuration, 'milliseconds').format('mm:ss')}
</p>
<p class="text-xs text-muted-foreground">
{tracks.length} track{tracks.length !== 1 ? 's' : ''}
</p>
</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>
<form method="POST" action="/playlists/{id}?/delete" use:enhance={deletePlaylist}>
<Button type="submit" variant="outline" size="icon">
<Trash2 />
</Button>
</form>
</div>
</div>
</div>