Merge pull request #139 from mawoka-myblock/135-add-a-new-question-type-voting-questions
This commit is contained in:
+11
-2
@@ -85,24 +85,33 @@ class RangeQuizAnswer(BaseModel):
|
|||||||
max_correct: int
|
max_correct: int
|
||||||
|
|
||||||
|
|
||||||
|
class VotingQuizAnswer(BaseModel):
|
||||||
|
answer: str
|
||||||
|
image: str | None = None
|
||||||
|
color: str | None
|
||||||
|
|
||||||
|
|
||||||
class QuizQuestionType(str, Enum):
|
class QuizQuestionType(str, Enum):
|
||||||
ABCD = "ABCD"
|
ABCD = "ABCD"
|
||||||
RANGE = "RANGE"
|
RANGE = "RANGE"
|
||||||
|
VOTING = "VOTING"
|
||||||
|
|
||||||
|
|
||||||
class QuizQuestion(BaseModel):
|
class QuizQuestion(BaseModel):
|
||||||
question: str
|
question: str
|
||||||
time: str # in Secs
|
time: str # in Secs
|
||||||
type: None | QuizQuestionType = QuizQuestionType.ABCD
|
type: None | QuizQuestionType = QuizQuestionType.ABCD
|
||||||
answers: list[ABCDQuizAnswer] | RangeQuizAnswer
|
answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer]
|
||||||
image: str | None = None
|
image: str | None = None
|
||||||
|
|
||||||
@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]) != ABCDQuizAnswer:
|
||||||
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) != 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:
|
||||||
|
raise ValueError("Answer must be from type VotingQuizAnswer if type is VOTING")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from classquiz.db.models import (
|
|||||||
RangeQuizAnswer,
|
RangeQuizAnswer,
|
||||||
ABCDQuizAnswer,
|
ABCDQuizAnswer,
|
||||||
QuizQuestionType,
|
QuizQuestionType,
|
||||||
|
VotingQuizAnswer,
|
||||||
)
|
)
|
||||||
from classquiz.auth import check_api_key
|
from classquiz.auth import check_api_key
|
||||||
from classquiz.socket_server import ReturnQuestion, sio
|
from classquiz.socket_server import ReturnQuestion, sio
|
||||||
@@ -35,7 +36,7 @@ class _ABCDQuizAnswer(ABCDQuizAnswer):
|
|||||||
|
|
||||||
|
|
||||||
class _QuizQuestion(QuizQuestion):
|
class _QuizQuestion(QuizQuestion):
|
||||||
answers: list[ABCDQuizAnswer] | RangeQuizAnswer
|
answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer]
|
||||||
|
|
||||||
|
|
||||||
class _GetLivePlayGame(PlayGame):
|
class _GetLivePlayGame(PlayGame):
|
||||||
|
|||||||
@@ -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"])
|
||||||
|
|||||||
Generated
+26
-64
@@ -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:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -247,6 +247,18 @@
|
|||||||
numbers between {question.answers.min} and {question
|
numbers between {question.answers.min} and {question
|
||||||
.answers.max} can be selected.
|
.answers.max} can be selected.
|
||||||
</p>
|
</p>
|
||||||
|
{:else if question.type === QuizQuestionType.VOTING}
|
||||||
|
<div class="grid grid-cols-2 gap-4 m-4 p-6">
|
||||||
|
{#each question.answers as answer}
|
||||||
|
<div
|
||||||
|
class="p-1 rounded-lg py-4 dark:bg-gray-500 bg-gray-300"
|
||||||
|
>
|
||||||
|
<h4 class="text-center">
|
||||||
|
{answer.answer}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
|
|||||||
@@ -66,10 +66,10 @@
|
|||||||
<!-- <RangeSlider bind:value={range_arr} bind:min={answer.min} bind:max={answer.max} range={true} slider={lol} /> -->
|
<!-- <RangeSlider bind:value={range_arr} bind:min={answer.min} bind:max={answer.max} range={true} slider={lol} /> -->
|
||||||
|
|
||||||
{#await import('svelte-range-slider-pips')}
|
{#await import('svelte-range-slider-pips')}
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
{:then c}
|
{:then c}
|
||||||
{#await sleep(100)}
|
{#await sleep(100)}
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
{:then _}
|
{:then _}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={c.default}
|
this={c.default}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<!--
|
||||||
|
- 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 { EditorData, VotingAnswer } from '../quiz_types';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { reach } from 'yup';
|
||||||
|
import { getLocalization } from '$lib/i18n';
|
||||||
|
import { VotingQuestionSchema } from '$lib/yupSchemas';
|
||||||
|
|
||||||
|
const { t } = getLocalization();
|
||||||
|
|
||||||
|
const empty_answer: VotingAnswer = {
|
||||||
|
answer: '',
|
||||||
|
image: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
export let selected_question: number;
|
||||||
|
export let data: EditorData;
|
||||||
|
try {
|
||||||
|
if (typeof data.questions[selected_question].answers[0].right === 'boolean') {
|
||||||
|
data.questions[selected_question].answers = [];
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} 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>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4 w-full px-10">
|
||||||
|
{#if Array.isArray(data.questions[selected_question].answers)}
|
||||||
|
{#each data.questions[selected_question].answers as answer, index}
|
||||||
|
<div
|
||||||
|
out:fade={{ duration: 150 }}
|
||||||
|
class="p-4 rounded-lg flex justify-center w-full transition"
|
||||||
|
class:bg-yellow-500={!reach(VotingQuestionSchema, 'answer').isValidSync(
|
||||||
|
answer.answer
|
||||||
|
)}
|
||||||
|
class:dark:bg-gray-500={answer.answer}
|
||||||
|
class:bg-gray-300={answer.answer}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
bind:value={answer.answer}
|
||||||
|
type="text"
|
||||||
|
on:contextmenu|preventDefault={() => {
|
||||||
|
data.questions[selected_question].answers.splice(index, 1);
|
||||||
|
data.questions[selected_question].answers =
|
||||||
|
data.questions[selected_question].answers;
|
||||||
|
}}
|
||||||
|
class="border-b-2 border-dotted w-5/6 text-center rounded-lg"
|
||||||
|
style="background-color: {answer.color ?? 'transparent'}"
|
||||||
|
placeholder="Empty..."
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="rounded-lg p-1"
|
||||||
|
type="color"
|
||||||
|
bind:value={answer.color}
|
||||||
|
on:contextmenu|preventDefault={() => {
|
||||||
|
answer.color = null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#if data.questions[selected_question].answers.length < 4}
|
||||||
|
<button
|
||||||
|
class="p-4 rounded-lg bg-transparent border-gray-500 border-2 hover:bg-gray-300 transition dark:hover:bg-gray-600"
|
||||||
|
type="button"
|
||||||
|
in:fade={{ duration: 150 }}
|
||||||
|
on:click={() => {
|
||||||
|
data.questions[selected_question].answers = [
|
||||||
|
...data.questions[selected_question].answers,
|
||||||
|
{ ...empty_answer }
|
||||||
|
];
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span class="italic text-center">{$t('editor_page.add_an_answer')}</span>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -96,11 +96,11 @@
|
|||||||
use:tippy={{ content: "Click to learn why it's loading so long." }}
|
use:tippy={{ content: "Click to learn why it's loading so long." }}
|
||||||
class="cursor-help"
|
class="cursor-help"
|
||||||
>
|
>
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
{#await import('$lib/editor/uploader.svelte')}
|
{#await import('$lib/editor/uploader.svelte')}
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
{:then c}
|
{:then c}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={c.default}
|
this={c.default}
|
||||||
@@ -146,17 +146,24 @@
|
|||||||
>
|
>
|
||||||
<option value={QuizQuestionType.RANGE}>{$t('words.range')}</option>
|
<option value={QuizQuestionType.RANGE}>{$t('words.range')}</option>
|
||||||
<option value={QuizQuestionType.ABCD}>{$t('words.multiple_choice')}</option>
|
<option value={QuizQuestionType.ABCD}>{$t('words.multiple_choice')}</option>
|
||||||
|
<option value={QuizQuestionType.VOTING}>{$t('words.voting')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center pt-10 w-full">
|
<div class="flex justify-center pt-10 w-full">
|
||||||
{#if data.questions[selected_question].type === QuizQuestionType.ABCD}
|
{#if data.questions[selected_question].type === QuizQuestionType.ABCD}
|
||||||
{#await import('$lib/editor/ABCDEditorPart.svelte')}
|
{#await import('$lib/editor/ABCDEditorPart.svelte')}
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
{:then c}
|
{:then c}
|
||||||
<svelte:component this={c.default} bind:data bind:selected_question />
|
<svelte:component this={c.default} bind:data bind:selected_question />
|
||||||
{/await}
|
{/await}
|
||||||
{:else if data.questions[selected_question].type === QuizQuestionType.RANGE}
|
{:else if data.questions[selected_question].type === QuizQuestionType.RANGE}
|
||||||
<RangeEditor bind:selected_question bind:data />
|
<RangeEditor bind:selected_question bind:data />
|
||||||
|
{:else if data.questions[selected_question].type === QuizQuestionType.VOTING}
|
||||||
|
{#await import('$lib/editor/VotingEditorPart.svelte')}
|
||||||
|
<Spinner my_20={false} />
|
||||||
|
{:then c}
|
||||||
|
<svelte:component this={c.default} bind:data bind:selected_question />
|
||||||
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p class="italic text-center mt-auto pt-4">{$t('editor_page.right_click_to_delete')}</p>
|
<p class="italic text-center mt-auto pt-4">{$t('editor_page.right_click_to_delete')}</p>
|
||||||
|
|||||||
@@ -67,11 +67,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if pow_data === undefined}
|
{:else if pow_data === undefined}
|
||||||
<a href="/docs/pow" target="_blank" class="cursor-help">
|
<a href="/docs/pow" target="_blank" class="cursor-help">
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
{#await import('$lib/editor/uploader.svelte')}
|
{#await import('$lib/editor/uploader.svelte')}
|
||||||
<Spinner />
|
<Spinner my_20={false} />
|
||||||
{:then c}
|
{:then c}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={c.default}
|
this={c.default}
|
||||||
|
|||||||
@@ -207,6 +207,30 @@
|
|||||||
and {question.answers.max_correct} are correct, where numbers between {question
|
and {question.answers.max_correct} are correct, where numbers between {question
|
||||||
.answers.min} and {question.answers.max} can be selected.
|
.answers.min} and {question.answers.max} can be selected.
|
||||||
</p>
|
</p>
|
||||||
|
{:else if question.type === QuizQuestionType.VOTING}
|
||||||
|
{#if Array.isArray(question.answers)}
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
{#each question.answers as answer}
|
||||||
|
<span
|
||||||
|
class="whitespace-nowrap truncate rounded-lg p-0.5 text-sm text-center border border-gray-700"
|
||||||
|
class:dark:bg-gray-500={answer.answer}
|
||||||
|
class:bg-gray-300={answer.answer}
|
||||||
|
class:bg-yellow-500={!reach(
|
||||||
|
ABCDQuestionSchema,
|
||||||
|
'answer'
|
||||||
|
).isValidSync(answer.answer)}
|
||||||
|
use:tippy={{
|
||||||
|
content: answer.answer === '' ? 'Empty...' : answer.answer
|
||||||
|
}}
|
||||||
|
>{#if answer.answer === ''}
|
||||||
|
<i>Empty...</i>
|
||||||
|
{:else}
|
||||||
|
{answer.answer}
|
||||||
|
{/if}</span
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p>Unknown Question Type (shouldn't happen)</p>
|
<p>Unknown Question Type (shouldn't happen)</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export interface QuizData {
|
|||||||
|
|
||||||
export enum QuizQuestionType {
|
export enum QuizQuestionType {
|
||||||
ABCD = 'ABCD', // eslint-disable-line no-unused-vars
|
ABCD = 'ABCD', // eslint-disable-line no-unused-vars
|
||||||
RANGE = 'RANGE' // eslint-disable-line no-unused-vars
|
RANGE = 'RANGE', // eslint-disable-line no-unused-vars
|
||||||
|
VOTING = 'VOTING' // eslint-disable-line no-unused-vars
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RangeQuizAnswer {
|
export interface RangeQuizAnswer {
|
||||||
@@ -33,7 +34,7 @@ export interface Question {
|
|||||||
question: string;
|
question: string;
|
||||||
type?: QuizQuestionType;
|
type?: QuizQuestionType;
|
||||||
image?: string;
|
image?: string;
|
||||||
answers: Answer[] | RangeQuizAnswer;
|
answers: Answer[] | RangeQuizAnswer | VotingAnswer[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Answer {
|
export interface Answer {
|
||||||
@@ -41,6 +42,11 @@ export interface Answer {
|
|||||||
answer: string;
|
answer: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
}
|
}
|
||||||
|
export interface VotingAnswer {
|
||||||
|
answer: string;
|
||||||
|
image?: string;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EditorData {
|
export interface EditorData {
|
||||||
public: boolean;
|
public: boolean;
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ export const ABCDQuestionSchema = yup
|
|||||||
.min(2, 'You need at least 2 answers')
|
.min(2, 'You need at least 2 answers')
|
||||||
.max(16, "You can't have more than 16 answers");
|
.max(16, "You can't have more than 16 answers");
|
||||||
|
|
||||||
|
export const VotingQuestionSchema = yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object({
|
||||||
|
answer: yup.string().required('You need an answer'),
|
||||||
|
image: yup.string().optional().nullable()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.min(2, 'You need at least 2 answers')
|
||||||
|
.max(16, "You can't have more than 16 answers");
|
||||||
|
|
||||||
export const RangeQuestionSchema = yup.object({
|
export const RangeQuestionSchema = yup.object({
|
||||||
min: yup.number(),
|
min: yup.number(),
|
||||||
max: yup.number(),
|
max: yup.number(),
|
||||||
@@ -50,9 +61,17 @@ export const dataSchema = yup.object({
|
|||||||
"The image-url isn't valid"
|
"The image-url isn't valid"
|
||||||
)
|
)
|
||||||
.lowercase(),
|
.lowercase(),
|
||||||
answers: yup.lazy((v) =>
|
answers: yup.lazy((v) => {
|
||||||
Array.isArray(v) ? ABCDQuestionSchema : RangeQuestionSchema
|
if (Array.isArray(v)) {
|
||||||
)
|
if (typeof v[0].right === 'boolean') {
|
||||||
|
return ABCDQuestionSchema;
|
||||||
|
} else {
|
||||||
|
return VotingQuestionSchema;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return RangeQuestionSchema;
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'You need at least one question')
|
.min(1, 'You need at least one question')
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -169,6 +169,17 @@
|
|||||||
and {question.answers.max_correct} are correct, where numbers between {question
|
and {question.answers.max_correct} are correct, where numbers between {question
|
||||||
.answers.min} and {question.answers.max} can be selected.
|
.answers.min} and {question.answers.max} can be selected.
|
||||||
</p>
|
</p>
|
||||||
|
{:else if question.type === QuizQuestionType.VOTING}
|
||||||
|
<div class="grid grid-cols-2 gap-4 m-4 p-6">
|
||||||
|
{#each question.answers as answer, index_answer}
|
||||||
|
<div class="p-1 rounded-lg py-4 dark:bg-gray-500 bg-gray-300">
|
||||||
|
<h4 class="text-center">
|
||||||
|
{quiz.questions[index_question].answers[index_answer]
|
||||||
|
.answer}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</CollapsSection>
|
</CollapsSection>
|
||||||
|
|||||||
Reference in New Issue
Block a user