diff --git a/.vscode/settings.json b/.vscode/settings.json index 71b05b6..9bb319d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,8 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[svg]": { + "editor.defaultFormatter": "jock.svg" + }, + "svg.preview.background": "dark-transparent" } diff --git a/src/lib/AudioMultipleChoiceQuestionComponent.svelte b/src/lib/AudioMultipleChoiceQuestionComponent.svelte new file mode 100644 index 0000000..bf51c1b --- /dev/null +++ b/src/lib/AudioMultipleChoiceQuestionComponent.svelte @@ -0,0 +1,78 @@ + + +
+ {#if showQuestion} +
+
{question.data.question}
+
+ {/if} + {#if showPlayer} +
+ +
+ {/if} + {#if showQuestion} +
+ {#each _choices as choice} +
+ {choice} +
+ {/each} +
+ {/if} +
+ + diff --git a/src/lib/AudioPlayerComponent.svelte b/src/lib/AudioPlayerComponent.svelte new file mode 100644 index 0000000..f0451f1 --- /dev/null +++ b/src/lib/AudioPlayerComponent.svelte @@ -0,0 +1,217 @@ + + +
+ + + + +
+
+ {format(time)} +
{ + const div = e.currentTarget; + + function seek(e: PointerEvent) { + const { left, width } = div.getBoundingClientRect(); + + let p = (e.clientX - left) / width; + if (p < 0) p = 0; + if (p > 1) p = 1; + + time = p * duration; + } + + seek(e); + + window.addEventListener("pointermove", seek); + + window.addEventListener( + "pointerup", + () => { + window.removeEventListener("pointermove", seek); + }, + { + once: true + } + ); + }} + > +
+
+ {duration ? format(duration) : "--:--"} +
+
+
+
+
+
{ + const div = e.currentTarget; + + function seek(e: PointerEvent) { + const { left, width } = div.getBoundingClientRect(); + + let p = (e.clientX - left) / width; + if (p < 0) p = 0; + if (p > 1) p = 1; + + setVolume(p); + localStorage.setItem(saveKey, p.toString()); + } + + seek(e); + + window.addEventListener("pointermove", seek); + + window.addEventListener( + "pointerup", + () => { + window.removeEventListener("pointermove", seek); + }, + { + once: true + } + ); + }} + > +
+
+ {volume}% +
+
+
+ + diff --git a/src/lib/AudioQuestionComponent.svelte b/src/lib/AudioQuestionComponent.svelte new file mode 100644 index 0000000..eb170ca --- /dev/null +++ b/src/lib/AudioQuestionComponent.svelte @@ -0,0 +1,34 @@ + + +
+ {#if showQuestion || showAnswer} +
+
{question.data.question}
+
+ {/if} + {#if showPlayer} +
+ +
+ {/if} + {#if showAnswer} +
+ {question.data.answer} +
+ {/if} +
diff --git a/src/lib/games/games.ts b/src/lib/games/games.ts index 03993ae..755c8f6 100644 --- a/src/lib/games/games.ts +++ b/src/lib/games/games.ts @@ -46,18 +46,27 @@ const games: Games = [ }, { points: 400, - type: "SIMPLE", + type: "AUDIO", data: { question: "Question 4?", + audio: "music.mp3", answer: "Answer 4" } }, { points: 500, - type: "SIMPLE", + type: "AUDIO_MULTIPLE_CHOICE", data: { question: "Question 5?", - answer: "Answer 5" + audio: "music.mp3", + choices: [ + "Choice 1", + "Choice 2", + "Choice 3", + "Choice 4", + "Choice 5", + "Choice 6" + ] } } ] @@ -1224,7 +1233,12 @@ const games: Games = [ } ]; -export type QuestionType = "SIMPLE" | "MULTIPLE_CHOICE" | "IMAGE"; +export type QuestionType = + | "SIMPLE" + | "MULTIPLE_CHOICE" + | "IMAGE" + | "AUDIO" + | "AUDIO_MULTIPLE_CHOICE"; export type Question = { points: number; @@ -1259,6 +1273,24 @@ export type ImageQuestion = Question & { }; }; +export type AudioQuestion = Question & { + type: "AUDIO"; + data: { + question: string; + audio: string; + answer: string; + }; +}; + +export type AudioMultipleChoiceQuestion = Question & { + type: "AUDIO_MULTIPLE_CHOICE"; + data: { + question: string; + audio: string; + choices: string[]; + }; +}; + export function isSimpleQuestion(question: Question): question is SimpleQuestion { return (question as SimpleQuestion).type === "SIMPLE"; } @@ -1268,10 +1300,24 @@ export function isMultipleChoiceQuestion(question: Question): question is Multip export function isImageQuestion(question: Question): question is ImageQuestion { return (question as ImageQuestion).type === "IMAGE"; } +export function isAudioQuestion(question: Question): question is AudioQuestion { + return (question as AudioQuestion).type === "AUDIO"; +} +export function isAudioMultipleChoiceQuestion( + question: Question +): question is AudioMultipleChoiceQuestion { + return (question as AudioMultipleChoiceQuestion).type === "AUDIO_MULTIPLE_CHOICE"; +} export type Category = { name: string; - questions: (SimpleQuestion | MultipleChoiceQuestion | ImageQuestion)[]; + questions: ( + | SimpleQuestion + | MultipleChoiceQuestion + | ImageQuestion + | AudioQuestion + | AudioMultipleChoiceQuestion + )[]; }; export type Wall = { diff --git a/src/routes/connected/display/running/[game]/[wall]/[category]/[question]/+page.svelte b/src/routes/connected/display/running/[game]/[wall]/[category]/[question]/+page.svelte index 36acc3b..42626a5 100644 --- a/src/routes/connected/display/running/[game]/[wall]/[category]/[question]/+page.svelte +++ b/src/routes/connected/display/running/[game]/[wall]/[category]/[question]/+page.svelte @@ -3,12 +3,20 @@ import { page } from "$app/state"; import { error } from "@sveltejs/kit"; import SimpleQuestionComponent from "$lib/SimpleQuestionComponent.svelte"; - import { isImageQuestion, isMultipleChoiceQuestion, isSimpleQuestion } from "$lib/games/games"; + import { + isAudioMultipleChoiceQuestion, + isAudioQuestion, + isImageQuestion, + isMultipleChoiceQuestion, + isSimpleQuestion + } from "$lib/games/games"; import ws from "$lib/websocket.svelte"; import { MessageType } from "$lib/MessageType"; import { untrack } from "svelte"; import MultipleChoiceQuestionComponent from "$lib/MultipleChoiceQuestionComponent.svelte"; import ImageQuestionComponent from "$lib/ImageQuestionComponent.svelte"; + import AudioQuestionComponent from "$lib/AudioQuestionComponent.svelte"; + import AudioMultipleChoiceQuestionComponent from "$lib/AudioMultipleChoiceQuestionComponent.svelte"; console.log("wall:", page.params.wall); @@ -126,6 +134,15 @@ {:else if isImageQuestion(question)} + {:else if isAudioQuestion(question)} + + {:else if isAudioMultipleChoiceQuestion(question)} + {:else}

Type of question unknown

{/if} diff --git a/src/routes/connected/games/[game]/+page.svelte b/src/routes/connected/games/[game]/+page.svelte index 1ea175c..9c11d16 100644 --- a/src/routes/connected/games/[game]/+page.svelte +++ b/src/routes/connected/games/[game]/+page.svelte @@ -6,7 +6,9 @@ isMultipleChoiceQuestion, isSimpleQuestion, isImageQuestion, - type Game + type Game, + isAudioQuestion, + isAudioMultipleChoiceQuestion } from "$lib/games/games"; import ws from "$lib/websocket.svelte"; import { page } from "$app/state"; @@ -18,6 +20,8 @@ import type { VisitedQuestions } from "$lib/Types.js"; import MultipleChoiceQuestionComponent from "$lib/MultipleChoiceQuestionComponent.svelte"; import ImageQuestionComponent from "$lib/ImageQuestionComponent.svelte"; + import AudioQuestionComponent from "$lib/AudioQuestionComponent.svelte"; + import AudioMultipleChoiceQuestionComponent from "$lib/AudioMultipleChoiceQuestionComponent.svelte"; let startDisabled = $state(true); @@ -366,6 +370,20 @@ showQuestion={true} isBuzzed={false} /> + {:else if isAudioQuestion(gameManager.question)} + + {:else if isAudioMultipleChoiceQuestion(gameManager.question)} + {:else}

Type of question unknown

{/if} diff --git a/static/images/pause.svg b/static/images/pause.svg new file mode 100644 index 0000000..e7f05f0 --- /dev/null +++ b/static/images/pause.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/static/images/play.svg b/static/images/play.svg new file mode 100644 index 0000000..9357bd5 --- /dev/null +++ b/static/images/play.svg @@ -0,0 +1,10 @@ + + + + diff --git a/static/sounds/music.mp3 b/static/sounds/music.mp3 new file mode 100644 index 0000000..c19c9b3 Binary files /dev/null and b/static/sounds/music.mp3 differ