From 09794bdefd7e8c0c488e33d53093db1ce6098169 Mon Sep 17 00:00:00 2001 From: whidix Date: Sun, 8 Mar 2026 19:59:58 +0100 Subject: [PATCH] feat: add progression tracking to game sessions and implement tutorial modal --- src/routes/(admin)/admin/+page.server.ts | 45 ++++++++--- src/routes/(admin)/admin/+page.svelte | 25 ++++++ .../game/play/[sessionCode]/+layout.svelte | 80 ++++++++++++++++++- .../play/[sessionCode]/tutorial/+page.svelte | 29 ------- 4 files changed, 135 insertions(+), 44 deletions(-) delete mode 100644 src/routes/(game)/game/play/[sessionCode]/tutorial/+page.svelte diff --git a/src/routes/(admin)/admin/+page.server.ts b/src/routes/(admin)/admin/+page.server.ts index b75ff84..876512a 100644 --- a/src/routes/(admin)/admin/+page.server.ts +++ b/src/routes/(admin)/admin/+page.server.ts @@ -23,6 +23,11 @@ type Session = { completedAt?: string; playerCount?: number; expiresAt: string; + progression?: { + currentStep: number; + completedSteps: number; + totalSteps: number; + }; }; type ResolutionMetric = { @@ -98,22 +103,38 @@ export const load: PageServerLoad = async () => { const currentAndIncomingRaw = await db.query.gameSession.findMany({ where: and(eq(gameSession.isActive, 1), gt(gameSession.expiresAt, new Date())), with: { - escapeGame: true, - players: true + escapeGame: { + with: { + steps: true + } + }, + players: true, + progress: true } }); const currentAndIncomingSessions: Session[] = currentAndIncomingRaw - .map((session) => ({ - id: session.id, - code: session.code, - gameName: session.escapeGame.title, - isActive: session.isActive === 1, - startedAt: session.startedAt ? new Date(session.startedAt).toISOString() : undefined, - completedAt: session.completedAt ? new Date(session.completedAt).toISOString() : undefined, - playerCount: session.players.length, - expiresAt: session.expiresAt.toISOString() - })) + .map((session) => { + const totalSteps = session.escapeGame.steps.length; + const completedSteps = session.progress.filter((p) => p.completedAt !== null).length; + const currentStep = completedSteps + 1; + + return { + id: session.id, + code: session.code, + gameName: session.escapeGame.title, + isActive: session.isActive === 1, + startedAt: session.startedAt ? new Date(session.startedAt).toISOString() : undefined, + completedAt: session.completedAt ? new Date(session.completedAt).toISOString() : undefined, + playerCount: session.players.length, + expiresAt: session.expiresAt.toISOString(), + progression: totalSteps > 0 ? { + currentStep, + completedSteps, + totalSteps + } : undefined + }; + }) .sort((a, b) => { const aIsCurrent = Boolean(a.startedAt) && !a.completedAt; const bIsCurrent = Boolean(b.startedAt) && !b.completedAt; diff --git a/src/routes/(admin)/admin/+page.svelte b/src/routes/(admin)/admin/+page.svelte index 40cf939..379e00d 100644 --- a/src/routes/(admin)/admin/+page.svelte +++ b/src/routes/(admin)/admin/+page.svelte @@ -96,6 +96,9 @@ {$t.admin.players} + + Progression + {$t.admin.expires} @@ -122,6 +125,28 @@ {session.playerCount || 0} + + {#if session.progression} +
+
+ + Step {session.progression.currentStep} of {session.progression.totalSteps} + + + {Math.round((session.progression.completedSteps / session.progression.totalSteps) * 100)}% + +
+
+
+
+
+ {:else} + No progress + {/if} + {new Date(session.expiresAt).toLocaleString()} diff --git a/src/routes/(game)/game/play/[sessionCode]/+layout.svelte b/src/routes/(game)/game/play/[sessionCode]/+layout.svelte index aa3e79b..ef86570 100644 --- a/src/routes/(game)/game/play/[sessionCode]/+layout.svelte +++ b/src/routes/(game)/game/play/[sessionCode]/+layout.svelte @@ -7,6 +7,7 @@ let { data, children }: { data: LayoutData; children: import('svelte').Snippet } = $props(); let inventoryOpen = $state(false); + let tutorialOpen = $state(false); let dragStartY = $state(0); let dragCurrentY = $state(0); @@ -23,6 +24,14 @@ dragCurrentY = 0; }; + const toggleTutorial = () => { + tutorialOpen = !tutorialOpen; + }; + + const closeTutorial = () => { + tutorialOpen = false; + }; + const handleTouchStart = (e: TouchEvent) => { dragStartY = e.touches[0].clientY; dragCurrentY = e.touches[0].clientY; @@ -71,15 +80,16 @@ - - + {#if data.previousStepId}
@@ -186,4 +196,68 @@ {/if} + + {#if tutorialOpen} +
{ + if (e.key === 'Escape') closeTutorial(); + }} + role="button" + tabindex={0} + >
+ + {/if} {/if} diff --git a/src/routes/(game)/game/play/[sessionCode]/tutorial/+page.svelte b/src/routes/(game)/game/play/[sessionCode]/tutorial/+page.svelte deleted file mode 100644 index 7d2b0ee..0000000 --- a/src/routes/(game)/game/play/[sessionCode]/tutorial/+page.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -
-
-

{$t.gameplay.tutorialTitle}

-

{$t.gameplay.tutorialIntro}

- -
-
- 1 -

{$t.gameplay.tutorialPrevious}

-
-
- 2 -

{$t.gameplay.tutorialInventory}

-
-
- 3 -

{$t.gameplay.tutorialNext}

-
-
- -

- {$t.gameplay.tutorial}: {$t.gameplay.inventory}, {$t.gameplay.previous}, {$t.gameplay.next} -

-
-