From 7614420d0cd53731013121f2fc0da59a61bc725d Mon Sep 17 00:00:00 2001
From: Mawoka
Date: Thu, 29 Dec 2022 16:16:09 +0100
Subject: [PATCH] :sparkles: Added new Question-Type: TextAnswers!
---
classquiz/db/models.py | 16 +--
classquiz/socket_server/__init__.py | 17 ++-
frontend/src/lib/admin.svelte | 15 ++
frontend/src/lib/editor/TextEditorPart.svelte | 136 ++++++++++++++++++
frontend/src/lib/editor/card.svelte | 7 +
frontend/src/lib/play/question.svelte | 17 +++
frontend/src/lib/practice/question.svelte | 31 +++-
frontend/src/lib/quiz_types.ts | 10 +-
frontend/src/lib/yupSchemas.ts | 45 +++---
.../src/routes/view/[quiz_id]/+page.svelte | 2 +-
10 files changed, 257 insertions(+), 39 deletions(-)
create mode 100644 frontend/src/lib/editor/TextEditorPart.svelte
diff --git a/classquiz/db/models.py b/classquiz/db/models.py
index f977539..1955e14 100644
--- a/classquiz/db/models.py
+++ b/classquiz/db/models.py
@@ -115,21 +115,19 @@ class QuizQuestionType(str, Enum):
RANGE = "RANGE"
VOTING = "VOTING"
SLIDE = "SLIDE"
-
-
-class SlideElementTypes(str, Enum):
TEXT = "TEXT"
- HEADLINE = "HEADLINE"
- IMAGE = "IMAGE"
- RECTANGLE = "RECTANGLE"
- CIRCLE = "CIRCLE"
+
+
+class TextQuizAnswer(BaseModel):
+ answer: str
+ case_sensitive: bool
class QuizQuestion(BaseModel):
question: str
time: str # in Secs
type: None | QuizQuestionType = QuizQuestionType.ABCD
- answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer] | str
+ answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[TextQuizAnswer] | list[VotingQuizAnswer] | str
image: str | None = None
@validator("answers")
@@ -140,6 +138,8 @@ class QuizQuestion(BaseModel):
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 VotingQuizAnswer if type is VOTING")
+ if values["type"] == QuizQuestionType.TEXT and type(v[0]) != TextQuizAnswer:
+ raise ValueError("Answer must be from type TextQuizAnswer if type is TEXT")
if values["type"] == QuizQuestionType.SLIDE and type(v[0]) != str:
raise ValueError("Answer must be from type SlideElement if type is SLIDE")
return v
diff --git a/classquiz/socket_server/__init__.py b/classquiz/socket_server/__init__.py
index 4c7dfda..6e9a9c4 100644
--- a/classquiz/socket_server/__init__.py
+++ b/classquiz/socket_server/__init__.py
@@ -209,6 +209,7 @@ class RangeQuizAnswerWithoutSolution(BaseModel):
class ReturnQuestion(QuizQuestion):
answers: list[ABCDQuizAnswerWithoutSolution] | RangeQuizAnswerWithoutSolution | list[VotingQuizAnswer]
+ type: QuizQuestionType = QuizQuestionType.ABCD
@validator("answers")
def answers_not_none_if_abcd_type(cls, v, values):
@@ -226,7 +227,7 @@ class ReturnQuestion(QuizQuestion):
async def set_question_number(sid, data: str):
# data is just a number (as a str) of the question
session = await sio.get_session(sid)
- print("set_question_number", data, session)
+ # print("set_question_number", data, session)
if session["admin"]:
game_pin = session["game_pin"]
game_data = PlayGame.parse_raw(await redis.get(f"game:{session['game_pin']}"))
@@ -246,7 +247,8 @@ async def set_question_number(sid, data: str):
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("emitting")
+ temp_return["type"] = game_data.questions[int(float(data))].type
+ # print("emitting")
await sio.emit(
"set_question_number",
{
@@ -301,6 +303,17 @@ async def submit_answer(sid: str, data: dict):
answer_right = True
elif game_data.questions[int(float(data.question_index))].type == QuizQuestionType.VOTING:
answer_right = False
+ elif game_data.questions[int(float(data.question_index))].type == QuizQuestionType.TEXT:
+ answer_right = False
+ for q in game_data.questions[int(float(data.question_index))].answers:
+ if q.case_sensitive:
+ if data.answer == q.answer:
+ answer_right = True
+ break
+ else:
+ if data.answer.lower() == q.answer.lower():
+ answer_right = True
+ break
else:
raise NotImplementedError
latency = int(float((await sio.get_session(sid))["ping"]))
diff --git a/frontend/src/lib/admin.svelte b/frontend/src/lib/admin.svelte
index 2904734..6511a71 100644
--- a/frontend/src/lib/admin.svelte
+++ b/frontend/src/lib/admin.svelte
@@ -248,6 +248,21 @@
{/each}
+ {:else if quiz_data.questions[selected_question].type === QuizQuestionType.TEXT}
+ {#if timer_res === '0'}
+
+ {#each quiz_data.questions[selected_question].answers as answer, i}
+
+ {answer.answer}
+
+
+ {/each}
+
+ {:else}
+ Enter your answer into the input field!
+ {/if}
{/if}
{/if}
{/if}
diff --git a/frontend/src/lib/editor/TextEditorPart.svelte b/frontend/src/lib/editor/TextEditorPart.svelte
new file mode 100644
index 0000000..389ad91
--- /dev/null
+++ b/frontend/src/lib/editor/TextEditorPart.svelte
@@ -0,0 +1,136 @@
+
+
+
+
+ {#if Array.isArray(data.questions[selected_question].answers)}
+ {#each data.questions[selected_question].answers as answer, index}
+
+
+
+
+
+ {/each}
+ {/if}
+ {#if data.questions[selected_question].answers.length < 4}
+
+ {/if}
+
diff --git a/frontend/src/lib/editor/card.svelte b/frontend/src/lib/editor/card.svelte
index 355b000..6b4293c 100644
--- a/frontend/src/lib/editor/card.svelte
+++ b/frontend/src/lib/editor/card.svelte
@@ -182,6 +182,7 @@
+
@@ -199,6 +200,12 @@
{:then c}
{/await}
+ {:else if data.questions[selected_question].type === QuizQuestionType.TEXT}
+ {#await import('$lib/editor/TextEditorPart.svelte')}
+
+ {:then c}
+
+ {/await}
{/if}
diff --git a/frontend/src/lib/play/question.svelte b/frontend/src/lib/play/question.svelte
index 1f24217..ae715f0 100644
--- a/frontend/src/lib/play/question.svelte
+++ b/frontend/src/lib/play/question.svelte
@@ -72,6 +72,8 @@
});
};
+ let text_input = '';
+
let slider_value = [0];
if (question.type === QuizQuestionType.RANGE) {
slider_value[0] = (question.answers.max - question.answers.min) / 2 + question.answers.min;
@@ -187,6 +189,21 @@
{/await}
+ {:else if question.type === QuizQuestionType.TEXT}
+
+
+
+
+
{/if}
{:else if question.type === QuizQuestionType.ABCD}
{#if solution === undefined}
diff --git a/frontend/src/lib/practice/question.svelte b/frontend/src/lib/practice/question.svelte
index 37118e1..9b22577 100644
--- a/frontend/src/lib/practice/question.svelte
+++ b/frontend/src/lib/practice/question.svelte
@@ -39,6 +39,7 @@
}
let slider_values = [question.answers.min_correct ?? 0, question.answers.max_correct ?? 0];
+ let text_input;
timer(question.time);
@@ -125,7 +126,7 @@
{/await}
+ {:else if question.type === QuizQuestionType.TEXT}
+ {#if timer_res === '0'}
+ {#each question.answers as answer, i}
+
+ {answer.answer}
+
+ {/each}
+ {:else}
+
+
+
+
+
+
+ {/if}
{/if}
diff --git a/frontend/src/lib/quiz_types.ts b/frontend/src/lib/quiz_types.ts
index 45d623e..91a6f0f 100644
--- a/frontend/src/lib/quiz_types.ts
+++ b/frontend/src/lib/quiz_types.ts
@@ -29,7 +29,8 @@ export enum QuizQuestionType {
ABCD = 'ABCD', // eslint-disable-line no-unused-vars
RANGE = 'RANGE', // eslint-disable-line no-unused-vars
VOTING = 'VOTING', // eslint-disable-line no-unused-vars
- SLIDE = 'SLIDE' // eslint-disable-line no-unused-vars
+ SLIDE = 'SLIDE', // eslint-disable-line no-unused-vars
+ TEXT = 'TEXT' // eslint-disable-line no-unused-vars
}
export interface RangeQuizAnswer {
@@ -39,12 +40,17 @@ export interface RangeQuizAnswer {
max_correct: number;
}
+export interface TextQuizAnswer {
+ answer: string;
+ case_sensitive: boolean;
+}
+
export interface Question {
time: string;
question: string;
type?: QuizQuestionType;
image?: string;
- answers: Answer[] | RangeQuizAnswer | VotingAnswer[] | string;
+ answers: Answer[] | RangeQuizAnswer | VotingAnswer[] | string | TextQuizAnswer[];
}
export interface Answer {
diff --git a/frontend/src/lib/yupSchemas.ts b/frontend/src/lib/yupSchemas.ts
index 3c0eddf..d904873 100644
--- a/frontend/src/lib/yupSchemas.ts
+++ b/frontend/src/lib/yupSchemas.ts
@@ -35,17 +35,17 @@ export const RangeQuestionSchema = yup.object({
max_correct: yup.number()
});
-export const SlideQuestionSchema = yup.array().of(
- yup.object({
- type: yup.string().required(),
- x: yup.number(),
- y: yup.number(),
- height: yup.number(),
- width: yup.number(),
- data: yup.string().optional(),
- id: yup.number().required()
- })
-);
+export const TextQuestionSchema = yup
+ .array()
+ .of(
+ yup.object({
+ case_sensitive: yup.boolean().required(),
+ answer: yup.string().required('You need an answer')
+ })
+ )
+ .min(2, 'You need at least 2 answers')
+ .max(16, "You can't have more than 16 answers");
+
export const dataSchema = yup.object({
public: yup.boolean().required(),
type: yup.string(),
@@ -75,20 +75,15 @@ export const dataSchema = yup.object({
.lowercase(),
answers: yup.lazy((v) => {
if (Array.isArray(v)) {
- try {
- if (typeof v[0].right === 'boolean') {
- console.log('ABCDQuestionSchema');
- return ABCDQuestionSchema;
- } else if (v[0].answer !== undefined) {
- console.log('VotingQuestionSchema');
- return VotingQuestionSchema;
- } else {
- console.log('SlideQuestionSchema');
- return yup.string().required().nullable();
- }
- } catch {
- console.log('SlideQuestionSchema');
- return yup.string().required("The slide mustn't be empty").nullable();
+ if (typeof v[0].right === 'boolean') {
+ console.log('ABCDQuestionSchema');
+ return ABCDQuestionSchema;
+ } else if (typeof v[0].case_sensitive === 'boolean') {
+ console.log('TextQuestionSchema');
+ return TextQuestionSchema;
+ } else if (v[0].answer !== undefined) {
+ console.log('VotingQuestionSchema');
+ return VotingQuestionSchema;
}
} else if (typeof v === 'string' || v instanceof String) {
return yup.string().required("The slide mustn't be empty").nullable();
diff --git a/frontend/src/routes/view/[quiz_id]/+page.svelte b/frontend/src/routes/view/[quiz_id]/+page.svelte
index b91c208..ebb0666 100644
--- a/frontend/src/routes/view/[quiz_id]/+page.svelte
+++ b/frontend/src/routes/view/[quiz_id]/+page.svelte
@@ -218,7 +218,7 @@
and {question.answers.max_correct} are correct, where numbers between {question
.answers.min} and {question.answers.max} can be selected.
- {:else if question.type === QuizQuestionType.VOTING}
+ {:else if question.type === QuizQuestionType.VOTING || question.type === QuizQuestionType.TEXT}
{#each question.answers as answer, index_answer}