feat: basic song ui + settings

This commit is contained in:
2024-11-24 00:11:36 +01:00
parent 2c9051a265
commit 7ae47d02f2
99 changed files with 3680 additions and 64 deletions

View File

@@ -0,0 +1,8 @@
import { PlayerClient } from '$lib/proto/player.client';
import { protoTransport } from '../hooks.server';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async () => {
// const client = new PlayerClient(protoTransport);
// TODO: Get current song
};

View File

@@ -1,6 +1,31 @@
<script lang="ts">
import '../app.css';
import { ModeWatcher } from 'mode-watcher';
import * as Sidebar from '$lib/components/ui/sidebar';
import AppSidebar from '$lib/components/groove/AppSidebar.svelte';
import Footer from '$lib/components/groove/Footer.svelte';
import { ScrollArea } from '$lib/components/ui/scroll-area';
let { children } = $props();
</script>
{@render children()}
<ModeWatcher />
<Sidebar.Provider>
<AppSidebar />
<div class="flex max-h-screen flex-auto grow flex-col overflow-hidden">
<main class="grow overflow-hidden">
<Sidebar.Trigger />
<ScrollArea class="flex h-full flex-col overflow-auto">
<div class="m-4 flex flex-col">
<!-- <Sidebar.Trigger /> -->
{@render children()}
</div>
</ScrollArea>
</main>
<Footer />
</div>
</Sidebar.Provider>
<svelte:head><title>Groove</title></svelte:head>

View File

@@ -1,2 +1 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<h1>Home</h1>

View File

View File

@@ -0,0 +1 @@
Albums

View File

@@ -0,0 +1,16 @@
import { PlayerClient } from '$lib/proto/player.client';
import { protoTransport } from '../../hooks.server';
import type { Actions } from './$types';
export const actions = {
resume: async () => {
const client = new PlayerClient(protoTransport);
await client.resumeTrack({});
},
pause: async () => {
const client = new PlayerClient(protoTransport);
await client.pauseTrack({});
}
} satisfies Actions;

View File

View File

@@ -0,0 +1 @@
Playlists

View File

@@ -0,0 +1,43 @@
import { SettingsClient } from '$lib/proto/settings.client';
import { fail } from '@sveltejs/kit';
import { protoTransport } from '../../hooks.server';
import type { Actions, PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ depends }) => {
depends('settings:library');
const client = new SettingsClient(protoTransport);
const response = await client.listPaths({});
return {
libraryPaths: response.response.libraryPaths.map((p) => ({
id: p.id,
path: p.path
}))
};
};
export const actions = {
'add-path': async ({ request }) => {
const formData = await request.formData();
const path = formData.get('path')?.toString();
if (!path) {
return fail(400, {
errors: 'Invalid path'
});
}
const client = new SettingsClient(protoTransport);
const response = await client.addPath({
path
});
return {
id: response.response.id
};
}
} satisfies Actions;

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import { Separator } from '$lib/components/ui/separator';
import { Button, buttonVariants } from '$lib/components/ui/button';
import { Plus, Settings } from 'lucide-svelte';
import type { PageServerData } from './$types';
import * as ContextMenu from '$lib/components/ui/context-menu';
import ThemeSwitcher from '$lib/components/groove/ThemeSwitcher.svelte';
import { Input } from '$lib/components/ui/input';
import { enhance } from '$app/forms';
import { Trash, RefreshCw } from 'lucide-svelte';
interface Props {
data: PageServerData;
}
let { data: settings }: Props = $props();
</script>
<h3 class="flex scroll-m-20 items-center text-2xl font-semibold tracking-tight">
<Settings class="mr-2" />Settings
</h3>
<Separator class="mb-5 mt-3" />
<div class="flex flex-col gap-4">
<div class="w-fit space-y-2">
<div class="text-lg font-semibold">
<p>Appearance</p>
<p class="text-sm font-medium leading-none text-muted-foreground">
Change the way Groove looks
</p>
</div>
<ThemeSwitcher />
</div>
<div class="w-fit space-y-2">
<div class="text-lg font-semibold">
<p>Library</p>
<p class="text-sm font-medium leading-none text-muted-foreground">
Add the directories you want to include in your library
</p>
</div>
<form class="flex items-center gap-1" method="POST" action="?/add-path" use:enhance>
<Input name="path" placeholder="~/Music" />
<Button type="submit" class="aspect-square" variant="outline" size="icon"><Plus /></Button>
</form>
<div class="flex flex-col gap-1">
{#each settings.libraryPaths as path}
<div class="group flex items-center justify-between pr-0">
<span>{path.path}</span>
<form class="flex items-center gap-1" method="POST" use:enhance>
<Button
type="submit"
formaction="/settings/path/{path.id}?/refresh"
variant="outline"
size="icon"><RefreshCw /></Button
>
<Button
type="submit"
formaction="/settings/path/{path.id}?/delete"
variant="destructive"
size="icon"><Trash /></Button
>
</form>
</div>
{:else}
<p class="text-sm text-muted-foreground">You have not added a directory yet</p>
{/each}
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
import type { Actions } from './$types';
import { SettingsClient } from '$lib/proto/settings.client';
import { protoTransport } from '../../../../hooks.server';
export const actions = {
refresh: async ({ params }) => {
const client = new SettingsClient(protoTransport);
await client.refreshPath({
id: BigInt(params.id)
});
},
delete: async ({ params }) => {
const client = new SettingsClient(protoTransport);
await client.deletePath({
id: BigInt(params.id)
});
}
} satisfies Actions;

View File

@@ -0,0 +1,20 @@
import { LibraryClient } from '$lib/proto/library.client';
import { protoTransport } from '../../hooks.server';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async () => {
const client = new LibraryClient(protoTransport);
const response = await client.listTracks({});
const tracks = response.response.tracks.map((t) => ({
hash: t.hash,
name: t.name,
artistId: t.artistId,
artistName: t.artistName
}));
return {
songs: tracks
};
};

View File

@@ -0,0 +1,18 @@
<script lang="ts">
import SongListing from '$lib/components/groove/SongListing.svelte';
import type { PageServerData } from './$types';
interface Props {
data: PageServerData;
}
let { data }: Props = $props();
</script>
<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 data.songs as song}
<SongListing {song} />
{/each}
</div>

View File

@@ -0,0 +1,13 @@
import { PlayerClient } from '$lib/proto/player.client';
import { protoTransport } from '../../../hooks.server';
import type { Actions } from './$types';
export const actions = {
play: async ({ params }) => {
const client = new PlayerClient(protoTransport);
await client.playTrack({
hash: params.hash
});
}
} satisfies Actions;