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)} -
- Album Art - -
- {/each} - {:else} - {#each items as item, i (item.id)} -
-

{item.value}

- - -
- {/each} - {/if} + {#each items as item, i (item.id)} +
+ + {#if image} + + Album Art + + + {: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 @@ -
- - - + + + + + {#if data?.highscore && data?.stage && data.highscore === data.stage} + New high score! + {:else} + Maybe next time + {/if} + + + {#if data.stage === 0} + That's tough. 0 right answers. + {:else} + You got it right {data.stage} times. + {/if} + + + + + Try again + + + +
+ +
{ + loading = true; + }} +> + {#await data.streamed.albums} + {#each { length: 2 } as _} +
+ {#each { length: 3 } as _} + + {/each} +
+ {/each} + +
+ {#each { length: 3 } as _} + + {/each} +
+ {:then albums} + {#if albums.names && albums.artists && albums.images} + + + + {:else} + + + + {/if} + {/await}

Stage: {data.stage}

- +

High Score: {data.highscore}

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 }); +}