Implemented in Live-Game

This commit is contained in:
Mawoka
2022-10-04 15:38:15 +02:00
parent 512fc9e8ea
commit 60822283e3
11 changed files with 338 additions and 144 deletions
+1 -1
View File
@@ -111,7 +111,7 @@ class QuizQuestion(BaseModel):
if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswer: if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswer:
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE") raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE")
if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer: if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer:
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE") raise ValueError("Answer must be from type VotingQuizAnswer if type is VOTING")
return v return v
+18 -9
View File
@@ -11,7 +11,7 @@ import socketio
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from classquiz.config import redis, settings from classquiz.config import redis, settings
from classquiz.db.models import PlayGame, QuizQuestionType, GameSession, GamePlayer, QuizQuestion from classquiz.db.models import PlayGame, QuizQuestionType, GameSession, GamePlayer, QuizQuestion, VotingQuizAnswer
from pydantic import BaseModel, ValidationError, validator from pydantic import BaseModel, ValidationError, validator
from datetime import datetime from datetime import datetime
@@ -113,8 +113,10 @@ async def join_game(sid: str, data: dict):
{"username": data.username, "sid": sid}, {"username": data.username, "sid": sid},
room=redis_res.admin, room=redis_res.admin,
) )
lol = fernet.encrypt(datetime.now().isoformat().encode("utf-8")).decode("utf-8") # +++ Time-Sync +++
await sio.emit("time_sync", lol, room=sid) encrypted_datetime = fernet.encrypt(datetime.now().isoformat().encode("utf-8")).decode("utf-8")
await sio.emit("time_sync", encrypted_datetime, room=sid)
# --- Time-Sync ---
sio.enter_room(sid, data.game_pin) sio.enter_room(sid, data.game_pin)
@@ -183,14 +185,16 @@ class RangeQuizAnswerWithoutSolution(BaseModel):
class ReturnQuestion(QuizQuestion): class ReturnQuestion(QuizQuestion):
answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution | list[VotingQuizAnswer]
@validator("answers") @validator("answers")
def answers_not_none_if_abcd_type(cls, v, values): def answers_not_none_if_abcd_type(cls, v, values):
if values["type"] == QuizQuestionType.ABCD and len(v) == 0: if values["type"] == QuizQuestionType.ABCD and type(v[0]) != ABCDQuizAnswerWithoutSolution:
raise ValueError("Answers can't be none if type is ABCD") raise ValueError("Answers can't be none if type is ABCD")
if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution: if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution:
raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE") raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE")
if values["type"] == QuizQuestionType.VOTING and type(v[0]) != VotingQuizAnswer:
print("Answer must be from type VotingQuizAnswer if type is VOTING")
return v return v
@@ -204,13 +208,17 @@ async def set_question_number(sid, data: str):
game_data.current_question = int(float(data)) game_data.current_question = int(float(data))
await redis.set(f"game:{session['game_pin']}", game_data.json()) await redis.set(f"game:{session['game_pin']}", game_data.json())
await redis.set(f"game:{session['game_pin']}:current_time", datetime.now().isoformat()) await redis.set(f"game:{session['game_pin']}:current_time", datetime.now().isoformat())
# print(game_data.dict(include={"questions"})["questions"][int(float(data))])
temp_return = game_data.dict(include={"questions"})["questions"][int(float(data))]
if game_data.questions[int(float(data))].type == QuizQuestionType.VOTING:
for i in range(len(temp_return["answers"])):
temp_return["answers"][i] = VotingQuizAnswer(**temp_return["answers"][i])
print(temp_return)
await sio.emit( await sio.emit(
"set_question_number", "set_question_number",
{ {
"question_index": int(float(data)), "question_index": int(float(data)),
"question": ReturnQuestion( "question": ReturnQuestion(**temp_return).dict(),
**game_data.dict(include={"questions"})["questions"][int(float(data))]
).dict(),
}, },
room=game_pin, room=game_pin,
) )
@@ -258,6 +266,8 @@ async def submit_answer(sid: str, data: dict):
<= game_data.questions[int(float(data.question_index))].answers.max_correct <= game_data.questions[int(float(data.question_index))].answers.max_correct
): ):
answer_right = True answer_right = True
elif game_data.questions[int(float(data.question_index))].type == QuizQuestionType.VOTING:
answer_right = False
else: else:
raise NotImplementedError raise NotImplementedError
latency = int(float((await sio.get_session(sid))["ping"])) latency = int(float((await sio.get_session(sid))["ping"]))
@@ -317,7 +327,6 @@ async def submit_answer(sid: str, data: dict):
async def get_final_results(sid: str, _data: dict): async def get_final_results(sid: str, _data: dict):
session: dict = await sio.get_session(sid) session: dict = await sio.get_session(sid)
game_data = PlayGame(**json.loads(await redis.get(f"game:{session['game_pin']}"))) game_data = PlayGame(**json.loads(await redis.get(f"game:{session['game_pin']}")))
results = {}
if not session["admin"]: if not session["admin"]:
return return
results = await generate_final_results(game_data, session["game_pin"]) results = await generate_final_results(game_data, session["game_pin"])
+26 -64
View File
@@ -77,7 +77,7 @@ devDependencies:
'@sentry/tracing': 7.11.1 '@sentry/tracing': 7.11.1
'@sveltejs/adapter-auto': 1.0.0-next.80 '@sveltejs/adapter-auto': 1.0.0-next.80
'@sveltejs/adapter-node': 1.0.0-next.86 '@sveltejs/adapter-node': 1.0.0-next.86
'@sveltejs/kit': 1.0.0-next.504_svelte@3.49.0+vite@3.1.2 '@sveltejs/kit': 1.0.0-next.508_svelte@3.49.0+vite@3.1.2
'@tailwindcss/typography': 0.5.4_tailwindcss@3.1.8 '@tailwindcss/typography': 0.5.4_tailwindcss@3.1.8
'@types/canvas-confetti': 1.4.3 '@types/canvas-confetti': 1.4.3
'@types/cookie': 0.5.1 '@types/cookie': 0.5.1
@@ -579,10 +579,10 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sveltejs/kit/1.0.0-next.504_svelte@3.49.0+vite@3.1.2: /@sveltejs/kit/1.0.0-next.508_svelte@3.49.0+vite@3.1.2:
resolution: resolution:
{ {
integrity: sha512-KrhlSHT3aCVnhRgUoN6aGIjIw3nWEdNwfoZcE1x65F5D7Ju/K9D8dQwyal9v0aBAZyN9nFuXxsYGaMLE9sppfw== integrity: sha512-qUnuhuL82meE0lSwrsS/FIvtDoE83Au4SxVLCim3FzoCnJc7bFZ9vYRbRUYxAjfYKRMjKVUrp9iHFObn9LmnUg==
} }
engines: { node: '>=16.14' } engines: { node: '>=16.14' }
hasBin: true hasBin: true
@@ -598,13 +598,12 @@ packages:
kleur: 4.1.5 kleur: 4.1.5
magic-string: 0.26.3 magic-string: 0.26.3
mime: 3.0.0 mime: 3.0.0
node-fetch: 3.2.10
sade: 1.8.1 sade: 1.8.1
set-cookie-parser: 2.5.1 set-cookie-parser: 2.5.1
sirv: 2.0.2 sirv: 2.0.2
svelte: 3.49.0 svelte: 3.49.0
tiny-glob: 0.2.9 tiny-glob: 0.2.9
undici: 5.9.1 undici: 5.11.0
vite: 3.1.2_sass@1.54.4 vite: 3.1.2_sass@1.54.4
transitivePeerDependencies: transitivePeerDependencies:
- diff-match-patch - diff-match-patch
@@ -1398,6 +1397,16 @@ packages:
} }
dev: true dev: true
/busboy/1.6.0:
resolution:
{
integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
}
engines: { node: '>=10.16.0' }
dependencies:
streamsearch: 1.1.0
dev: true
/callsites/3.1.0: /callsites/3.1.0:
resolution: resolution:
{ {
@@ -1717,14 +1726,6 @@ packages:
css-tree: 1.1.3 css-tree: 1.1.3
dev: true dev: true
/data-uri-to-buffer/4.0.0:
resolution:
{
integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
}
engines: { node: '>= 12' }
dev: true
/debug/4.3.4: /debug/4.3.4:
resolution: resolution:
{ {
@@ -2503,17 +2504,6 @@ packages:
svelte: 3.49.0 svelte: 3.49.0
dev: true dev: true
/fetch-blob/3.2.0:
resolution:
{
integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
}
engines: { node: ^12.20 || >= 14.13 }
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
dev: true
/file-entry-cache/6.0.1: /file-entry-cache/6.0.1:
resolution: resolution:
{ {
@@ -2570,16 +2560,6 @@ packages:
} }
dev: true dev: true
/formdata-polyfill/4.0.10:
resolution:
{
integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
}
engines: { node: '>=12.20.0' }
dependencies:
fetch-blob: 3.2.0
dev: true
/fraction.js/4.2.0: /fraction.js/4.2.0:
resolution: resolution:
{ {
@@ -3358,14 +3338,6 @@ packages:
} }
dev: true dev: true
/node-domexception/1.0.0:
resolution:
{
integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
}
engines: { node: '>=10.5.0' }
dev: true
/node-fetch/2.6.7: /node-fetch/2.6.7:
resolution: resolution:
{ {
@@ -3381,18 +3353,6 @@ packages:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
dev: true dev: true
/node-fetch/3.2.10:
resolution:
{
integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==
}
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
dependencies:
data-uri-to-buffer: 4.0.0
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: true
/node-gyp-build/4.5.0: /node-gyp-build/4.5.0:
resolution: resolution:
{ {
@@ -4540,6 +4500,14 @@ packages:
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
dev: true dev: true
/streamsearch/1.1.0:
resolution:
{
integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
}
engines: { node: '>=10.0.0' }
dev: true
/string-width/4.2.3: /string-width/4.2.3:
resolution: resolution:
{ {
@@ -4960,12 +4928,14 @@ packages:
} }
dev: true dev: true
/undici/5.9.1: /undici/5.11.0:
resolution: resolution:
{ {
integrity: sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg== integrity: sha512-oWjWJHzFet0Ow4YZBkyiJwiK5vWqEYoH7BINzJAJOLedZ++JpAlCbUktW2GQ2DS2FpKmxD/JMtWUUWl1BtghGw==
} }
engines: { node: '>=12.18' } engines: { node: '>=12.18' }
dependencies:
busboy: 1.6.0
dev: true dev: true
/update-browserslist-db/1.0.5_browserslist@4.21.3: /update-browserslist-db/1.0.5_browserslist@4.21.3:
@@ -5057,14 +5027,6 @@ packages:
pbf: 3.2.1 pbf: 3.2.1
dev: true dev: true
/web-streams-polyfill/3.2.1:
resolution:
{
integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
}
engines: { node: '>= 8' }
dev: true
/webidl-conversions/3.0.1: /webidl-conversions/3.0.1:
resolution: resolution:
{ {
+29 -11
View File
@@ -114,8 +114,8 @@
{#if selected_question + 1 === quiz_data.questions.length && timer_res === '0' && question_results !== null} {#if selected_question + 1 === quiz_data.questions.length && timer_res === '0' && question_results !== null}
{#if JSON.stringify(final_results) === JSON.stringify([null])} {#if JSON.stringify(final_results) === JSON.stringify([null])}
<button on:click={get_final_results} class="admin-button" <button on:click={get_final_results} class="admin-button"
>Get final results</button >Get final results
> </button>
{/if} {/if}
{:else if timer_res === '0' || selected_question === -1} {:else if timer_res === '0' || selected_question === -1}
{#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} {#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1}
@@ -129,13 +129,13 @@
{/if} {/if}
{#if question_results === null && selected_question !== -1} {#if question_results === null && selected_question !== -1}
<button on:click={get_question_results} class="admin-button" <button on:click={get_question_results} class="admin-button"
>Show results</button >Show results
> </button>
{/if} {/if}
{:else if selected_question !== -1} {:else if selected_question !== -1}
<button on:click={show_solutions} class="admin-button" <button on:click={show_solutions} class="admin-button"
>Stop time and show solutions</button >Stop time and show solutions
> </button>
{:else} {:else}
<!-- <button <!-- <button
on:click={() => { on:click={() => {
@@ -181,20 +181,28 @@
</div> </div>
{/if} {/if}
{#if game_mode === 'kahoot'} {#if game_mode === 'kahoot'}
{#if quiz_data.questions[selected_question].type === QuizQuestionType.ABCD} {#if quiz_data.questions[selected_question].type === QuizQuestionType.ABCD || quiz_data.questions[selected_question].type === QuizQuestionType.VOTING}
<div class="grid grid-cols-2 gap-2 w-full p-4"> <div class="grid grid-cols-2 gap-2 w-full p-4">
{#each quiz_data.questions[selected_question].answers as answer, i} {#each quiz_data.questions[selected_question].answers as answer, i}
<div <div
class="rounded-lg h-fit flex" class="rounded-lg h-fit flex"
style="background-color: {answer.color ?? '#B45309'}" style="background-color: {answer.color ?? '#B45309'}"
class:opacity-50={!answer.right && timer_res === '0'} class:opacity-50={!answer.right &&
timer_res === '0' &&
quiz_data.questions[selected_question].type !==
QuizQuestionType.ABCD}
> >
<img class="w-14 inline-block pl-4" alt="icon" src={kahoot_icons[i]} /> <img class="w-14 inline-block pl-4" alt="icon" src={kahoot_icons[i]} />
<span <span
class="text-center text-2xl px-2 py-4 w-full text-black" class="text-center text-2xl px-2 py-4 w-full text-black"
class:text-4xl={answer.right && timer_res === '0'} class:text-4xl={answer.right &&
class:underline={answer.right && timer_res === '0'} timer_res === '0' &&
>{answer.answer}</span quiz_data.questions[selected_question].type !==
QuizQuestionType.ABCD}
class:underline={answer.right &&
timer_res === '0' &&
quiz_data.questions[selected_question].type !==
QuizQuestionType.ABCD}>{answer.answer}</span
> >
<span class="pl-4 w-10" /> <span class="pl-4 w-10" />
</div> </div>
@@ -221,6 +229,16 @@
<h1 class="text-3xl">{$t('admin_page.no_answers')}</h1> <h1 class="text-3xl">{$t('admin_page.no_answers')}</h1>
</div> </div>
{/if} {/if}
{:else if quiz_data.questions[selected_question].type === QuizQuestionType.VOTING}
{#await import('$lib/play/admin/voting_results.svelte')}
<Spinner />
{:then c}
<svelte:component
this={c.default}
bind:data={question_results}
bind:question={quiz_data.questions[selected_question]}
/>
{/await}
{:else} {:else}
{#await import('$lib/play/admin/results.svelte')} {#await import('$lib/play/admin/results.svelte')}
<Spinner /> <Spinner />
@@ -9,6 +9,7 @@
import { reach } from 'yup'; import { reach } from 'yup';
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { VotingQuestionSchema } from '$lib/yupSchemas'; import { VotingQuestionSchema } from '$lib/yupSchemas';
import { onMount } from 'svelte';
const { t } = getLocalization(); const { t } = getLocalization();
@@ -19,9 +20,19 @@
export let selected_question: number; export let selected_question: number;
export let data: EditorData; export let data: EditorData;
if (!Array.isArray(data.questions[selected_question].answers)) { try {
data.questions[selected_question].answers = []; if (typeof data.questions[selected_question].answers[0].right === 'boolean') {
} data.questions[selected_question].answers = [];
}
} catch {}
/*console.log(data.questions[selected_question].answers, 'moIn!', data.questions[selected_question].answers.length);
onMount(() => {
for (let i = 0; i < data.questions[selected_question].answers; i++) {
console.log(data.questions[selected_question].answers[i], 'iterate');
data.questions[selected_question].answers[i].right = undefined;
}
});*/
</script> </script>
<!-- <!--
@@ -0,0 +1,50 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
import type { Question } from '$lib/quiz_types';
export let data;
export let question: Question;
let quiz_answers = [];
for (const i of question.answers) {
quiz_answers.push(i.answer);
}
console.log('mounted', question);
let sorted_data = {};
for (const i of quiz_answers) {
sorted_data[i] = 0;
}
for (const i of data) {
sorted_data[i.answer] += 1;
}
</script>
<div class="flex justify-center h-full w-full">
<div
class="m-auto grid grid-rows-3 w-fit gap-4"
style="grid-template-columns: repeat({quiz_answers.length}, minmax(0, 1fr));"
>
{#each quiz_answers as answer}
<span class="text-center self-end"
>{#if sorted_data[answer] > 0}{sorted_data[answer]}{/if}</span
>
{/each}
{#each quiz_answers as answer}
<div
class="w-20 bg-black self-end flex justify-center"
style="height: {(sorted_data[answer] * 20) / data.length}rem"
/>
{/each}
{#each quiz_answers as answer}
<div class="w-20">
<p class="-rotate-45">{answer}</p>
</div>
{/each}
</div>
</div>
+8 -1
View File
@@ -113,7 +113,7 @@
</div> </div>
{/if} {/if}
{#if timer_res !== '0'} {#if timer_res !== '0'}
{#if question.type === QuizQuestionType.ABCD} {#if question.type === QuizQuestionType.ABCD || QuizQuestionType.VOTING}
{#if game_mode === 'normal'} {#if game_mode === 'normal'}
<div class="grid grid-cols-2 gap-2 w-full p-4"> <div class="grid grid-cols-2 gap-2 w-full p-4">
{#each question.answers as answer} {#each question.answers as answer}
@@ -202,4 +202,11 @@
{/if} {/if}
</p> </p>
{/if} {/if}
<!--{:else if question.type === QuizQuestionType.VOTING}
{#await import('$lib/play/admin/voting_results.svelte')}
<Spinner />
{:then c}
<svelte:component this={c.default} bind:data={question_results}
bind:question={quiz_data.questions[selected_question]} />
{/await}-->
{/if} {/if}
+59 -43
View File
@@ -91,48 +91,64 @@
{/if} {/if}
</div> </div>
{/if} {/if}
{:else if timer_res === '0'} {:else if question.type === QuizQuestionType.RANGE}
{#await import('svelte-range-slider-pips')} {#if timer_res === '0'}
<Spinner /> {#await import('svelte-range-slider-pips')}
{:then c} <Spinner />
<div class="grayscale pointer-events-none w-full"> {:then c}
<svelte:component <div class="grayscale pointer-events-none w-full">
this={c.default} <svelte:component
bind:values={slider_values} this={c.default}
bind:min={question.answers.min} bind:values={slider_values}
bind:max={question.answers.max} bind:min={question.answers.min}
pips bind:max={question.answers.max}
float pips
all="label" float
/> all="label"
</div> />
{/await} </div>
{:else} {/await}
{#await import('svelte-range-slider-pips')} {:else}
<Spinner /> {#await import('svelte-range-slider-pips')}
{:then c} <Spinner />
<div class:pointer-events-none={selected_answer !== undefined}> {:then c}
<svelte:component <div class:pointer-events-none={selected_answer !== undefined}>
this={c.default} <svelte:component
bind:values={slider_value} this={c.default}
bind:min={question.answers.min} bind:values={slider_value}
bind:max={question.answers.max} bind:min={question.answers.min}
pips bind:max={question.answers.max}
float pips
all="label" float
/> all="label"
</div> />
<div class="flex justify-center"> </div>
<button <div class="flex justify-center">
class="w-1/2 text-3xl bg-amber-700 my-2 disabled:opacity-60 border border-white" <button
disabled={selected_answer !== undefined} class="w-1/2 text-3xl bg-amber-700 my-2 disabled:opacity-60 border border-white"
on:click={() => { disabled={selected_answer !== undefined}
selected_answer = slider_value[0]; on:click={() => {
timer_res = '0'; selected_answer = slider_value[0];
}} timer_res = '0';
>{$t('words.submit')} }}
</button> >{$t('words.submit')}
</div> </button>
{/await} </div>
{/await}
{/if}
{:else if question.type === QuizQuestionType.VOTING}
{#each question.answers as answer, i}
<button
disabled={selected_answer !== undefined || timer_res === '0'}
class="p-2 rounded-lg flex justify-center w-full transition bg-amber-300 my-5 disabled:grayscale text-black"
on:click={() => {
selected_answer = i;
timer_res = '0';
}}>{answer.answer}</button
>
{/each}
{#if timer_res === '0'}
<p>No correct answers, since this is a poll-question</p>
{/if}
{/if} {/if}
</div> </div>
+3
View File
@@ -63,9 +63,12 @@ export const dataSchema = yup.object({
.lowercase(), .lowercase(),
answers: yup.lazy((v) => { answers: yup.lazy((v) => {
if (Array.isArray(v)) { if (Array.isArray(v)) {
console.log(v);
if (typeof v[0].right === 'boolean') { if (typeof v[0].right === 'boolean') {
console.log('Yup, ABCD');
return ABCDQuestionSchema; return ABCDQuestionSchema;
} else { } else {
console.log('Yup, Poll');
return VotingQuestionSchema; return VotingQuestionSchema;
} }
} else { } else {
+1 -12
View File
@@ -9,6 +9,7 @@
import { getLocalization } from '$lib/i18n'; import { getLocalization } from '$lib/i18n';
import { navbarVisible } from '$lib/stores'; import { navbarVisible } from '$lib/stores';
import { QuizQuestionType } from '$lib/quiz_types'; import { QuizQuestionType } from '$lib/quiz_types';
import type { Question } from '$lib/quiz_types';
navbarVisible.set(false); navbarVisible.set(false);
@@ -21,18 +22,6 @@
questions: Question[]; questions: Question[];
} }
interface Question {
question: string;
time: string;
type?: QuizQuestionType;
answers: Answer[];
}
interface Answer {
right: boolean;
answer: string;
}
let responseData = { let responseData = {
open: false open: false
}; };
+129
View File
@@ -0,0 +1,129 @@
<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->
<script lang="ts">
import { navbarVisible } from '$lib/stores';
navbarVisible.set(false);
const data = [
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'Mawoka',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'dewads',
answer: 'A very long answe-thing',
right: false,
time_taken: 1681.631,
score: 0
},
{
username: 'dsadsa',
answer: 'der',
right: false,
time_taken: 2613.532,
score: 0
}
];
const quiz_answers = ['A very long answe-thing', 'dieter', 'moin', 'der'];
let sorted_data = {};
for (const i of quiz_answers) {
sorted_data[i] = 0;
}
for (const i of data) {
sorted_data[i.answer] += 1;
}
</script>
<div class="flex justify-center h-screen w-full">
<div
class="m-auto grid grid-rows-3 w-fit gap-4"
style="grid-template-columns: repeat({quiz_answers.length}, minmax(0, 1fr));"
>
{#each quiz_answers as answer}
<span class="text-center self-end"
>{#if sorted_data[answer] > 0}{sorted_data[answer]}{/if}</span
>
{/each}
{#each quiz_answers as answer}
<div
class="w-20 bg-black self-end flex justify-center"
style="height: {(sorted_data[answer] * 20) / data.length}rem"
/>
{/each}
{#each quiz_answers as answer}
<div class="w-20">
<p class="-rotate-45">{answer}</p>
</div>
{/each}
</div>
</div>