diff --git a/src/lib/components/DNDGroup.svelte b/src/lib/components/DNDGroup.svelte
index e4a4079..2fd8d4e 100644
--- a/src/lib/components/DNDGroup.svelte
+++ b/src/lib/components/DNDGroup.svelte
@@ -1,6 +1,9 @@
- {#if image}
- {#each items as item, i (item.id)}
-
-

-
-
- {/each}
- {:else}
- {#each items as item, i (item.id)}
-
- {/each}
- {/if}
+ {#each items as item, i (item.id)}
+
+
+ {#if image}
+
+
+
+
+ {:else}
+
+
+ {#if type === 'artists'}
+ {truncate(item.value, 30)}
+ {:else}
+ {truncate(item.value, 45)}
+ {/if}
+
+
+
+ {/if}
+
+
+ {/each}
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
new file mode 100644
index 0000000..715a24f
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
@@ -0,0 +1,13 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
new file mode 100644
index 0000000..e0226fd
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
new file mode 100644
index 0000000..858b8bc
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
@@ -0,0 +1,26 @@
+
+
+
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
new file mode 100644
index 0000000..600ef8c
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
@@ -0,0 +1,16 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte
new file mode 100644
index 0000000..91ecaba
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte
new file mode 100644
index 0000000..44a7b08
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte
new file mode 100644
index 0000000..62acab8
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
new file mode 100644
index 0000000..ef197dc
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/index.ts b/src/lib/components/ui/alert-dialog/index.ts
new file mode 100644
index 0000000..dd20f99
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/index.ts
@@ -0,0 +1,40 @@
+import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
+
+import Title from "./alert-dialog-title.svelte";
+import Action from "./alert-dialog-action.svelte";
+import Cancel from "./alert-dialog-cancel.svelte";
+import Footer from "./alert-dialog-footer.svelte";
+import Header from "./alert-dialog-header.svelte";
+import Overlay from "./alert-dialog-overlay.svelte";
+import Content from "./alert-dialog-content.svelte";
+import Description from "./alert-dialog-description.svelte";
+
+const Root = AlertDialogPrimitive.Root;
+const Trigger = AlertDialogPrimitive.Trigger;
+const Portal = AlertDialogPrimitive.Portal;
+
+export {
+ Root,
+ Title,
+ Action,
+ Cancel,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ //
+ Root as AlertDialog,
+ Title as AlertDialogTitle,
+ Action as AlertDialogAction,
+ Cancel as AlertDialogCancel,
+ Portal as AlertDialogPortal,
+ Footer as AlertDialogFooter,
+ Header as AlertDialogHeader,
+ Trigger as AlertDialogTrigger,
+ Overlay as AlertDialogOverlay,
+ Content as AlertDialogContent,
+ Description as AlertDialogDescription,
+};
diff --git a/src/lib/components/ui/card/card-content.svelte b/src/lib/components/ui/card/card-content.svelte
new file mode 100644
index 0000000..1f52856
--- /dev/null
+++ b/src/lib/components/ui/card/card-content.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte
new file mode 100644
index 0000000..da02664
--- /dev/null
+++ b/src/lib/components/ui/card/card-description.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-footer.svelte b/src/lib/components/ui/card/card-footer.svelte
new file mode 100644
index 0000000..6894149
--- /dev/null
+++ b/src/lib/components/ui/card/card-footer.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-header.svelte b/src/lib/components/ui/card/card-header.svelte
new file mode 100644
index 0000000..1baa92c
--- /dev/null
+++ b/src/lib/components/ui/card/card-header.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-title.svelte b/src/lib/components/ui/card/card-title.svelte
new file mode 100644
index 0000000..a201620
--- /dev/null
+++ b/src/lib/components/ui/card/card-title.svelte
@@ -0,0 +1,25 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card.svelte b/src/lib/components/ui/card/card.svelte
new file mode 100644
index 0000000..c7531d5
--- /dev/null
+++ b/src/lib/components/ui/card/card.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/index.ts b/src/lib/components/ui/card/index.ts
new file mode 100644
index 0000000..0f9084d
--- /dev/null
+++ b/src/lib/components/ui/card/index.ts
@@ -0,0 +1,22 @@
+import Root from "./card.svelte";
+import Content from "./card-content.svelte";
+import Description from "./card-description.svelte";
+import Footer from "./card-footer.svelte";
+import Header from "./card-header.svelte";
+import Title from "./card-title.svelte";
+
+export {
+ Root,
+ Content,
+ Description,
+ Footer,
+ Header,
+ Title,
+ //
+ Root as Card,
+ Content as CardContent,
+ Description as CardDescription,
+ Footer as CardFooter,
+ Header as CardHeader,
+ Title as CardTitle,
+};
diff --git a/src/lib/components/ui/skeleton/index.ts b/src/lib/components/ui/skeleton/index.ts
new file mode 100644
index 0000000..186db21
--- /dev/null
+++ b/src/lib/components/ui/skeleton/index.ts
@@ -0,0 +1,7 @@
+import Root from "./skeleton.svelte";
+
+export {
+ Root,
+ //
+ Root as Skeleton,
+};
diff --git a/src/lib/components/ui/skeleton/skeleton.svelte b/src/lib/components/ui/skeleton/skeleton.svelte
new file mode 100644
index 0000000..1f8bbc5
--- /dev/null
+++ b/src/lib/components/ui/skeleton/skeleton.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/lib/server/PlayerState.svelte.ts b/src/lib/server/PlayerState.svelte.ts
index a8498cd..eb3afa9 100644
--- a/src/lib/server/PlayerState.svelte.ts
+++ b/src/lib/server/PlayerState.svelte.ts
@@ -2,59 +2,91 @@ import type { Player } from '$lib/types';
import { getContext, setContext } from 'svelte';
export class PlayerState {
- players = $state([]);
+ players = $state([]);
- newPlayer(id: string) {
- this.players.push({ id: id, stage: 0, highscore: 0 });
- }
+ newPlayer(id: string) {
+ this.players.push({ id: id, stage: 0, highscore: 0, playing: true });
+ }
- getStage(id: string) {
- const player = this.players.find((player) => player.id === id);
+ getStage(id: string) {
+ const player = this.players.find((player) => player.id === id);
- if (!player) {
- return undefined;
- }
+ if (!player) {
+ return undefined;
+ }
- return player.stage;
- }
+ return player.stage;
+ }
- nextStage(id: string) {
- const player = this.players.find((player) => player.id === id);
+ nextStage(id: string) {
+ const player = this.players.find((player) => player.id === id);
- if (!player) {
- return;
- }
+ if (!player) {
+ return;
+ }
- player.stage += 1;
- }
+ player.stage += 1;
+ }
- score(id: string, win: boolean) {
- const player = this.players.find((player) => player.id === id);
+ restart(id: string) {
+ const player = this.players.find((player) => player.id === id);
- if (!player) {
- return;
- }
+ if (!player) {
+ return;
+ }
- if (win) {
- player.stage += 1;
+ player.stage = 0;
+ player.playing = true;
+ }
- if (player.stage > player.highscore) {
- player.highscore = player.stage;
- }
- } else {
- player.stage = 0;
- }
- }
+ score(id: string, won: boolean) {
+ const player = this.players.find((player) => player.id === id);
- getHighscore(id: string) {
- const player = this.players.find((player) => player.id === id);
+ if (!player) {
+ return;
+ }
- if (!player) {
- return undefined;
- }
+ if (won) {
+ player.stage += 1;
+ return;
+ }
- return player.highscore;
- }
+ player.playing = false;
+
+ if (player.stage > player.highscore) {
+ player.highscore = player.stage;
+ }
+ }
+
+ getHighscore(id: string) {
+ const player = this.players.find((player) => player.id === id);
+
+ if (!player) {
+ return undefined;
+ }
+
+ return player.highscore;
+ }
+
+ getPlaying(id: string) {
+ const player = this.players.find((player) => player.id === id);
+
+ if (!player) {
+ return undefined;
+ }
+
+ return player.playing;
+ }
+
+ setPlaying(id: string, playing: boolean) {
+ const player = this.players.find((player) => player.id === id);
+
+ if (!player) {
+ return;
+ }
+
+ player.playing = playing;
+ }
}
export const playerState = new PlayerState();
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 37d1116..1ef0567 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -8,4 +8,5 @@ export type Player = {
id: string;
stage: number;
highscore: number;
+ playing: boolean;
};
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 61d5240..5d47c69 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -60,3 +60,48 @@ export function shuffleArray(array: T[]): T[] {
return array;
}
+
+export function truncate(text: string, maxLength: number): string {
+ // Return original text if it's shorter than or equal to maxLength
+ if (text.length <= maxLength) {
+ return text;
+ }
+
+ const ellipsis = '…';
+ const tolerance = 5;
+ const targetLength = maxLength - 1; // Account for ellipsis
+
+ // Look for spaces within the tolerance range before targetLength
+ let spaceBeforeIdx = -1;
+ for (let i = targetLength; i >= targetLength - tolerance; i--) {
+ if (text[i] === ' ') {
+ spaceBeforeIdx = i;
+ break;
+ }
+ }
+
+ // Look for spaces within the tolerance range after targetLength
+ let spaceAfterIdx = -1;
+ for (let i = targetLength; i <= targetLength + tolerance && i < text.length; i++) {
+ if (text[i] === ' ') {
+ spaceAfterIdx = i;
+ break;
+ }
+ }
+
+ // Determine the best cutoff point
+ let cutoffIndex = targetLength;
+ if (spaceBeforeIdx !== -1 && spaceAfterIdx !== -1) {
+ // If we found spaces both before and after, use the closest one
+ cutoffIndex =
+ targetLength - spaceBeforeIdx <= spaceAfterIdx - targetLength
+ ? spaceBeforeIdx
+ : spaceAfterIdx;
+ } else if (spaceBeforeIdx !== -1) {
+ cutoffIndex = spaceBeforeIdx;
+ } else if (spaceAfterIdx !== -1) {
+ cutoffIndex = spaceAfterIdx;
+ }
+
+ return text.slice(0, cutoffIndex).trim() + ellipsis;
+}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 9b776b7..5c50b44 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -3,4 +3,6 @@
let { children } = $props();
-{@render children()}
+
+ {@render children()}
+
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts
index 3b3d98d..f2c0afc 100644
--- a/src/routes/+page.server.ts
+++ b/src/routes/+page.server.ts
@@ -4,80 +4,105 @@ import type { PageServerLoad } from './$types';
import type { AlbumSolveState } from '$lib/types';
import { albumState } from '$lib/server/AlbumState.svelte';
import { playerState } from '$lib/server/PlayerState.svelte';
-import { invalidateAll } from '$app/navigation';
const count = 3;
+
export const load: PageServerLoad = async ({ fetch, locals }) => {
- const { session } = locals;
+ const { session } = locals;
- if (!session?.data?.userId) {
- await session.setData({ userId: nanoid() });
- await session.save();
- }
+ if (!session?.data?.userId) {
+ await session.setData({ userId: nanoid() });
+ await session.save();
+ }
- const user = session.data.userId;
- let stage = playerState.getStage(user);
+ const user = session.data.userId;
+ let stage = playerState.getStage(user);
- if (!stage) {
- playerState.newPlayer(user);
- stage = playerState.getStage(user);
- }
+ if (!stage) {
+ playerState.newPlayer(user);
+ stage = playerState.getStage(user);
+ }
- const highscore = playerState.getHighscore(user);
+ const highscore = playerState.getHighscore(user);
- const albumData = await fetch(`/api/getAlbums/${count}`)
- .then((res) => {
- return res.json();
- })
- .then((data) => {
- return data;
- });
+ if (!playerState.getPlaying(user)) {
+ return {
+ stage: stage,
+ highscore: highscore,
+ playing: false
+ };
+ }
- const albumNames = albumData.albums.map((album) => ({ id: nanoid(), value: album.name }));
- const albumImages = albumData.albums.map((album) => ({
- id: nanoid(),
- value: album.images.at(0).url
- }));
- const albumArtists = albumData.albums.map((album) => ({
- id: nanoid(),
- value: album.artists.map((artist) => artist.name).join(', ')
- }));
+ const albumData = fetch(`/api/getAlbums/${count}`)
+ .then((res) => {
+ return res.json();
+ })
+ .then((data) => {
+ const albumNames = data.albums.map((album) => ({ id: nanoid(), value: album.name }));
+ const albumImages = data.albums.map((album) => ({
+ id: nanoid(),
+ value: album.images.at(0).url
+ }));
+ const albumArtists = data.albums.map((album) => ({
+ id: nanoid(),
+ value: album.artists.map((artist) => artist.name).join(', ')
+ }));
- return {
- names: shuffleArray(albumNames),
- images: shuffleArray(albumImages),
- artists: shuffleArray(albumArtists),
- stage: stage,
- highscore: highscore
- };
+ return {
+ names: shuffleArray(albumNames),
+ images: shuffleArray(albumImages),
+ artists: shuffleArray(albumArtists)
+ };
+ });
+
+ return {
+ stage: stage,
+ highscore: highscore,
+ playing: true,
+ streamed: { albums: albumData }
+ };
};
export const actions = {
- default: async ({ request, locals }) => {
- const { session } = locals; // you can access `locals.session`
+ submit: async ({ request, locals }) => {
+ const { session } = locals;
+ if (!session?.data?.userId) {
+ return;
+ }
- if (!session?.data?.userId) {
- return;
- }
+ const user = session.data.userId;
- const user = session.data.userId;
- const data = await request.formData();
+ const data = await request.formData();
- const state: AlbumSolveState[] = [];
+ const state: AlbumSolveState[] = [];
- for (let i = 0; i < count; i++) {
- const name = data.get(`names_${i}`);
- const image = data.get(`images_${i}`);
- const artists = data.get(`artists_${i}`);
+ for (let i = 0; i < count; i++) {
+ const name = data.get(`names_${i}`);
+ const image = data.get(`images_${i}`);
+ const artists = data.get(`artists_${i}`);
- const artistList = artists.split(', ');
+ const artistList = artists.split(', ');
- state.push({ name: name, imageUrl: image, artists: artistList });
- }
+ state.push({ name: name, imageUrl: image, artists: artistList });
+ }
- const solved = albumState.checkSolve(state);
+ const solved = albumState.checkSolve(state);
- playerState.score(user, solved);
- return { loading: false };
- }
+ playerState.score(user, solved);
+
+ return { solved: solved };
+ },
+
+ restart: async ({ locals }) => {
+ const { session } = locals;
+ if (!session?.data?.userId) {
+ return;
+ }
+
+ const user = session.data.userId;
+
+ playerState.restart(user);
+
+ return { solved: undefined };
+ }
};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index ef5b526..60fa13b 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,22 +1,93 @@
-
diff --git a/src/routes/api/checkSolve/+server.ts b/src/routes/api/checkSolve/+server.ts
new file mode 100644
index 0000000..a5c31e5
--- /dev/null
+++ b/src/routes/api/checkSolve/+server.ts
@@ -0,0 +1,12 @@
+import type { AlbumSolveState } from '$lib/types';
+import { albumState } from '$lib/server/AlbumState.svelte';
+import { spotifyAPI } from '$lib/server/Spotify.svelte';
+import { json } from '@sveltejs/kit';
+
+export async function POST({ request }) {
+ const { state }: { state: AlbumSolveState[] } = request.json();
+
+ const albums: SpotifyApi.AlbumObjectSimplified[] = [];
+
+ return json({ albums: albums });
+}