From 60822283e361c6d9cd5924655c9bbadc7eae7139 Mon Sep 17 00:00:00 2001 From: Mawoka Date: Tue, 4 Oct 2022 15:38:15 +0200 Subject: [PATCH] :sparkles: Implemented in Live-Game --- classquiz/db/models.py | 2 +- classquiz/socket_server/__init__.py | 27 ++-- frontend/pnpm-lock.yaml | 90 ++++-------- frontend/src/lib/admin.svelte | 40 ++++-- .../src/lib/editor/VotingEditorPart.svelte | 17 ++- .../src/lib/play/admin/voting_results.svelte | 50 +++++++ frontend/src/lib/play/question.svelte | 9 +- frontend/src/lib/practice/question.svelte | 102 ++++++++------ frontend/src/lib/yupSchemas.ts | 3 + frontend/src/routes/create/+page.svelte | 13 +- frontend/src/routes/test/+page.svelte | 129 ++++++++++++++++++ 11 files changed, 338 insertions(+), 144 deletions(-) create mode 100644 frontend/src/lib/play/admin/voting_results.svelte create mode 100644 frontend/src/routes/test/+page.svelte diff --git a/classquiz/db/models.py b/classquiz/db/models.py index ef79cea..829f867 100644 --- a/classquiz/db/models.py +++ b/classquiz/db/models.py @@ -111,7 +111,7 @@ class QuizQuestion(BaseModel): if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswer: raise ValueError("Answer must be from type RangeQuizAnswer if type is RANGE") 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 diff --git a/classquiz/socket_server/__init__.py b/classquiz/socket_server/__init__.py index 537f2f7..29e3bfd 100644 --- a/classquiz/socket_server/__init__.py +++ b/classquiz/socket_server/__init__.py @@ -11,7 +11,7 @@ import socketio from cryptography.fernet import Fernet 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 datetime import datetime @@ -113,8 +113,10 @@ async def join_game(sid: str, data: dict): {"username": data.username, "sid": sid}, room=redis_res.admin, ) - lol = fernet.encrypt(datetime.now().isoformat().encode("utf-8")).decode("utf-8") - await sio.emit("time_sync", lol, room=sid) + # +++ Time-Sync +++ + 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) @@ -183,14 +185,16 @@ class RangeQuizAnswerWithoutSolution(BaseModel): class ReturnQuestion(QuizQuestion): - answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution + answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution | list[VotingQuizAnswer] @validator("answers") 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") if values["type"] == QuizQuestionType.RANGE and type(v) != RangeQuizAnswerWithoutSolution: 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 @@ -204,13 +208,17 @@ async def set_question_number(sid, data: str): 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']}: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( "set_question_number", { "question_index": int(float(data)), - "question": ReturnQuestion( - **game_data.dict(include={"questions"})["questions"][int(float(data))] - ).dict(), + "question": ReturnQuestion(**temp_return).dict(), }, 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 ): answer_right = True + elif game_data.questions[int(float(data.question_index))].type == QuizQuestionType.VOTING: + answer_right = False else: raise NotImplementedError 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): session: dict = await sio.get_session(sid) game_data = PlayGame(**json.loads(await redis.get(f"game:{session['game_pin']}"))) - results = {} if not session["admin"]: return results = await generate_final_results(game_data, session["game_pin"]) diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index fcaab13..2e68f12 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -77,7 +77,7 @@ devDependencies: '@sentry/tracing': 7.11.1 '@sveltejs/adapter-auto': 1.0.0-next.80 '@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 '@types/canvas-confetti': 1.4.3 '@types/cookie': 0.5.1 @@ -579,10 +579,10 @@ packages: - supports-color 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: { - integrity: sha512-KrhlSHT3aCVnhRgUoN6aGIjIw3nWEdNwfoZcE1x65F5D7Ju/K9D8dQwyal9v0aBAZyN9nFuXxsYGaMLE9sppfw== + integrity: sha512-qUnuhuL82meE0lSwrsS/FIvtDoE83Au4SxVLCim3FzoCnJc7bFZ9vYRbRUYxAjfYKRMjKVUrp9iHFObn9LmnUg== } engines: { node: '>=16.14' } hasBin: true @@ -598,13 +598,12 @@ packages: kleur: 4.1.5 magic-string: 0.26.3 mime: 3.0.0 - node-fetch: 3.2.10 sade: 1.8.1 set-cookie-parser: 2.5.1 sirv: 2.0.2 svelte: 3.49.0 tiny-glob: 0.2.9 - undici: 5.9.1 + undici: 5.11.0 vite: 3.1.2_sass@1.54.4 transitivePeerDependencies: - diff-match-patch @@ -1398,6 +1397,16 @@ packages: } 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: resolution: { @@ -1717,14 +1726,6 @@ packages: css-tree: 1.1.3 dev: true - /data-uri-to-buffer/4.0.0: - resolution: - { - integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== - } - engines: { node: '>= 12' } - dev: true - /debug/4.3.4: resolution: { @@ -2503,17 +2504,6 @@ packages: svelte: 3.49.0 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: resolution: { @@ -2570,16 +2560,6 @@ packages: } 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: resolution: { @@ -3358,14 +3338,6 @@ packages: } 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: resolution: { @@ -3381,18 +3353,6 @@ packages: whatwg-url: 5.0.0 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: 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' dev: true + /streamsearch/1.1.0: + resolution: + { + integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + } + engines: { node: '>=10.0.0' } + dev: true + /string-width/4.2.3: resolution: { @@ -4960,12 +4928,14 @@ packages: } dev: true - /undici/5.9.1: + /undici/5.11.0: resolution: { - integrity: sha512-6fB3a+SNnWEm4CJbgo0/CWR8RGcOCQP68SF4X0mxtYTq2VNN8T88NYrWVBAeSX+zb7bny2dx2iYhP3XHi00omg== + integrity: sha512-oWjWJHzFet0Ow4YZBkyiJwiK5vWqEYoH7BINzJAJOLedZ++JpAlCbUktW2GQ2DS2FpKmxD/JMtWUUWl1BtghGw== } engines: { node: '>=12.18' } + dependencies: + busboy: 1.6.0 dev: true /update-browserslist-db/1.0.5_browserslist@4.21.3: @@ -5057,14 +5027,6 @@ packages: pbf: 3.2.1 dev: true - /web-streams-polyfill/3.2.1: - resolution: - { - integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== - } - engines: { node: '>= 8' } - dev: true - /webidl-conversions/3.0.1: resolution: { diff --git a/frontend/src/lib/admin.svelte b/frontend/src/lib/admin.svelte index 0662483..8cf5b91 100644 --- a/frontend/src/lib/admin.svelte +++ b/frontend/src/lib/admin.svelte @@ -114,8 +114,8 @@ {#if selected_question + 1 === quiz_data.questions.length && timer_res === '0' && question_results !== null} {#if JSON.stringify(final_results) === JSON.stringify([null])} + >Get final results + {/if} {:else if timer_res === '0' || selected_question === -1} {#if (selected_question + 1 !== quiz_data.questions.length && question_results !== null) || selected_question === -1} @@ -129,13 +129,13 @@ {/if} {#if question_results === null && selected_question !== -1} + >Show results + {/if} {:else if selected_question !== -1} + >Stop time and show solutions + {:else} + + +
+
+ {#each quiz_answers as answer} + {#if sorted_data[answer] > 0}{sorted_data[answer]}{/if} + {/each} + {#each quiz_answers as answer} +
+ {/each} + {#each quiz_answers as answer} +
+

{answer}

+
+ {/each} +
+
diff --git a/frontend/src/lib/play/question.svelte b/frontend/src/lib/play/question.svelte index 287cb5c..701c640 100644 --- a/frontend/src/lib/play/question.svelte +++ b/frontend/src/lib/play/question.svelte @@ -113,7 +113,7 @@
{/if} {#if timer_res !== '0'} - {#if question.type === QuizQuestionType.ABCD} + {#if question.type === QuizQuestionType.ABCD || QuizQuestionType.VOTING} {#if game_mode === 'normal'}
{#each question.answers as answer} @@ -202,4 +202,11 @@ {/if}

{/if} + {/if} diff --git a/frontend/src/lib/practice/question.svelte b/frontend/src/lib/practice/question.svelte index 0722aa0..9479887 100644 --- a/frontend/src/lib/practice/question.svelte +++ b/frontend/src/lib/practice/question.svelte @@ -91,48 +91,64 @@ {/if}
{/if} - {:else if timer_res === '0'} - {#await import('svelte-range-slider-pips')} - - {:then c} -
- -
- {/await} - {:else} - {#await import('svelte-range-slider-pips')} - - {:then c} -
- -
-
- -
- {/await} + {:else if question.type === QuizQuestionType.RANGE} + {#if timer_res === '0'} + {#await import('svelte-range-slider-pips')} + + {:then c} +
+ +
+ {/await} + {:else} + {#await import('svelte-range-slider-pips')} + + {:then c} +
+ +
+
+ +
+ {/await} + {/if} + {:else if question.type === QuizQuestionType.VOTING} + {#each question.answers as answer, i} + + {/each} + {#if timer_res === '0'} +

No correct answers, since this is a poll-question

+ {/if} {/if} diff --git a/frontend/src/lib/yupSchemas.ts b/frontend/src/lib/yupSchemas.ts index 3f81e4a..31111dd 100644 --- a/frontend/src/lib/yupSchemas.ts +++ b/frontend/src/lib/yupSchemas.ts @@ -63,9 +63,12 @@ export const dataSchema = yup.object({ .lowercase(), answers: yup.lazy((v) => { if (Array.isArray(v)) { + console.log(v); if (typeof v[0].right === 'boolean') { + console.log('Yup, ABCD'); return ABCDQuestionSchema; } else { + console.log('Yup, Poll'); return VotingQuestionSchema; } } else { diff --git a/frontend/src/routes/create/+page.svelte b/frontend/src/routes/create/+page.svelte index 80e5597..ed657f4 100644 --- a/frontend/src/routes/create/+page.svelte +++ b/frontend/src/routes/create/+page.svelte @@ -9,6 +9,7 @@ import { getLocalization } from '$lib/i18n'; import { navbarVisible } from '$lib/stores'; import { QuizQuestionType } from '$lib/quiz_types'; + import type { Question } from '$lib/quiz_types'; navbarVisible.set(false); @@ -21,18 +22,6 @@ questions: Question[]; } - interface Question { - question: string; - time: string; - type?: QuizQuestionType; - answers: Answer[]; - } - - interface Answer { - right: boolean; - answer: string; - } - let responseData = { open: false }; diff --git a/frontend/src/routes/test/+page.svelte b/frontend/src/routes/test/+page.svelte new file mode 100644 index 0000000..fc47f52 --- /dev/null +++ b/frontend/src/routes/test/+page.svelte @@ -0,0 +1,129 @@ + + + +
+
+ {#each quiz_answers as answer} + {#if sorted_data[answer] > 0}{sorted_data[answer]}{/if} + {/each} + {#each quiz_answers as answer} +
+ {/each} + {#each quiz_answers as answer} +
+

{answer}

+
+ {/each} +
+