diff --git a/classquiz/db/models.py b/classquiz/db/models.py
index 5b08633..4a30e48 100644
--- a/classquiz/db/models.py
+++ b/classquiz/db/models.py
@@ -112,13 +112,22 @@ class QuizQuestionType(str, Enum):
ABCD = "ABCD"
RANGE = "RANGE"
VOTING = "VOTING"
+ SLIDE = "SLIDE"
+
+
+class SlideElementTypes(str, Enum):
+ TEXT = "TEXT"
+ HEADLINE = "HEADLINE"
+ IMAGE = "IMAGE"
+ RECTANGLE = "RECTANGLE"
+ CIRCLE = "CIRCLE"
class QuizQuestion(BaseModel):
question: str
time: str # in Secs
type: None | QuizQuestionType = QuizQuestionType.ABCD
- answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer]
+ answers: list[ABCDQuizAnswer] | RangeQuizAnswer | list[VotingQuizAnswer] | str
image: str | None = None
@validator("answers")
@@ -129,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.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 6a3b7e7..b0128f8 100644
--- a/classquiz/socket_server/__init__.py
+++ b/classquiz/socket_server/__init__.py
@@ -230,6 +230,8 @@ async def set_question_number(sid, data: str):
await redis.set(f"game:{session['game_pin']}", game_data.json())
await redis.set(f"game:{session['game_pin']}:current_time", datetime.now().isoformat())
temp_return = game_data.dict(include={"questions"})["questions"][int(float(data))]
+ if game_data.questions[int(float(data))].type == QuizQuestionType.SLIDE:
+ return
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])
diff --git a/frontend/package.json b/frontend/package.json
index a359008..16c0f81 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -65,6 +65,7 @@
"luxon": "^3.1.1",
"mapbox-gl": "^2.11.0",
"minisearch": "^5.1.0",
+ "pikaso": "^2.7.4",
"plausible-tracker": "^0.3.8",
"postcss": "^8.4.19",
"postcss-import": "^14.1.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index db415aa..ca64bb9 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -53,6 +53,7 @@ specifiers:
luxon: ^3.1.1
mapbox-gl: ^2.11.0
minisearch: ^5.1.0
+ pikaso: ^2.7.4
plausible-tracker: ^0.3.8
postcss: ^8.4.19
postcss-import: ^14.1.0
@@ -132,6 +133,7 @@ devDependencies:
luxon: 3.1.1
mapbox-gl: 2.11.1
minisearch: 5.1.0
+ pikaso: 2.7.4
plausible-tracker: 0.3.8
postcss: 8.4.20
postcss-import: 14.1.0_postcss@8.4.20
@@ -3433,6 +3435,13 @@ packages:
engines: { node: '>=6' }
dev: true
+ /konva/8.3.14:
+ resolution:
+ {
+ integrity: sha512-6I/TZppgY3Frs//AvZ87YVQLFxLywitb8wLS3qMM+Ih9e4QcB5Yy8br6eq7DdUzxPdbsYTz1FQBHzNxs08M1Tw==
+ }
+ dev: true
+
/levn/0.4.1:
resolution:
{
@@ -3982,6 +3991,17 @@ packages:
engines: { node: '>=0.10.0' }
dev: true
+ /pikaso/2.7.4:
+ resolution:
+ {
+ integrity: sha512-30nqF5+xBUIBKiHHfDfjA3oZChjirN3AmhfWb456iUvTpMY2D51PKjGHfgF2SoQP5Jad7i4Zv+wrQAp7BhcNmQ==
+ }
+ dependencies:
+ deepmerge: 4.2.2
+ konva: 8.3.14
+ typescript: 4.7.4
+ dev: true
+
/plausible-tracker/0.3.8:
resolution:
{
diff --git a/frontend/src/lib/admin.svelte b/frontend/src/lib/admin.svelte
index 1d63a18..7854bb2 100644
--- a/frontend/src/lib/admin.svelte
+++ b/frontend/src/lib/admin.svelte
@@ -27,16 +27,21 @@
let timer_res: string;
let shown_question_now: number;
let final_results_clicked = false;
+ let timer_interval;
export let player_scores;
export const set_question_number = (q_number: number) => {
+ timer_res = '0';
+ clearInterval(timer_interval);
+ // setTimeout(() => {
question_results = null;
socket.emit('set_question_number', q_number.toString());
shown_question_now = q_number;
timer_res = quiz_data.questions[q_number].time;
selected_question = selected_question + 1;
timer(timer_res);
+ // }, 1000);
};
const get_question_results = () => {
socket.emit('get_question_results', {
@@ -53,6 +58,7 @@
const get_final_results = () => {
socket.emit('get_final_results', {});
final_results_clicked = true;
+ timer_res = '0';
};
socket.on('final_results', (data) => {
@@ -75,7 +81,7 @@
const timer = (time: string) => {
let seconds = Number(time);
- let timer_interval = setInterval(() => {
+ timer_interval = setInterval(() => {
if (timer_res === '0') {
clearInterval(timer_interval);
// socket.emit('show_solutions', {});
@@ -87,14 +93,14 @@
timer_res = seconds.toString();
}, 1000);
};
- let circular_prgoress = 0;
+ let circular_progress = 0;
$: {
try {
- circular_prgoress =
+ circular_progress =
1 -
((100 / quiz_data.questions[selected_question].time) * parseInt(timer_res)) / 100;
} catch {
- circular_prgoress = 0;
+ circular_progress = 0;
}
}
@@ -109,7 +115,7 @@
/{quiz_data.questions.length}
- {#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) || quiz_data?.questions?.[selected_question]?.type === QuizQuestionType.SLIDE)}
{#if JSON.stringify(final_results) === JSON.stringify([null])}
{/if}
{#if question_results === null && selected_question !== -1}
-
+ {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
+
+ {:else}
+
+ {/if}
{/if}
{:else if selected_question !== -1}
-
+ {#if quiz_data.questions[selected_question].type === QuizQuestionType.SLIDE}
+
+ {:else}
+
+ {/if}
{:else}
-
-
+ {:then c}
+
+ {/await}
+ {:else}
+
+
+ {@html quiz_data.questions[selected_question].question}
+
+
+
+
+
-
- {#if quiz_data.questions[selected_question].image !== null}
-
-

-
- {/if}
- {#if quiz_data.questions[selected_question].type === QuizQuestionType.ABCD || quiz_data.questions[selected_question].type === QuizQuestionType.VOTING}
-
- {#each quiz_data.questions[selected_question].answers as answer, i}
-
-

-
{answer.answer}
+

+
+ {/if}
+ {#if quiz_data.questions[selected_question].type === QuizQuestionType.ABCD || quiz_data.questions[selected_question].type === QuizQuestionType.VOTING}
+
+ {#each quiz_data.questions[selected_question].answers as answer, i}
+
-
-
- {/each}
-
+

+
{answer.answer}
+
+
+ {/each}
+
+ {/if}
{/if}
{/if}
- {#if timer_res === '0' && JSON.stringify(final_results) === JSON.stringify([null])}
+ {#if timer_res === '0' && JSON.stringify(final_results) === JSON.stringify( [null] ) && quiz_data.questions[selected_question].type !== QuizQuestionType.SLIDE}
{#if question_results === null}{:else if question_results === undefined}
{#if !final_results_clicked}
diff --git a/frontend/src/lib/dashboard/main_slider.svelte b/frontend/src/lib/dashboard/main_slider.svelte
index ec4cef7..04da9d2 100644
--- a/frontend/src/lib/dashboard/main_slider.svelte
+++ b/frontend/src/lib/dashboard/main_slider.svelte
@@ -15,6 +15,7 @@
import StartGamePopup from './start_game.svelte';
import { onMount } from 'svelte';
import viewport from './useViewportAction.js';
+ import Spinner from '$lib/Spinner.svelte';
let copy_toast_open = false;
let start_game = null;
@@ -350,6 +351,28 @@
{/each}
+ {:else if question.type === QuizQuestionType.SLIDE}
+ {
+ visibleImages[i][q] = true;
+ }}
+ on:exitViewport={() => {
+ visibleImages[i][q] = false;
+ }}
+ class="flex justify-center align-middle items-center"
+ >
+ {#await import('$lib/play/admin/slide.svelte')}
+
+ {:then c}
+
+
+
+ {/await}
+
{/if}
diff --git a/frontend/src/lib/editor.svelte b/frontend/src/lib/editor.svelte
index 53d4cc4..a298ebd 100644
--- a/frontend/src/lib/editor.svelte
+++ b/frontend/src/lib/editor.svelte
@@ -37,11 +37,13 @@
}
const validateInput = async (data: EditorData) => {
+ // console.log("input", data)
try {
await dataSchema.validate(data, { abortEarly: false });
schemaInvalid = false;
yupErrorMessage = '';
} catch (err) {
+ console.log('erro!', err.errors);
schemaInvalid = true;
yupErrorMessage = err.errors ? err.errors[0] : '';
}
diff --git a/frontend/src/lib/editor/card.svelte b/frontend/src/lib/editor/card.svelte
index 03b99ea..856e0e2 100644
--- a/frontend/src/lib/editor/card.svelte
+++ b/frontend/src/lib/editor/card.svelte
@@ -51,9 +51,9 @@
*/
-
+
-
+
-
-
- {#await import('$lib/inline-editor.svelte')}
-
- {:then c}
-
-
+ {:then c}
+
+ {/await}
+ {:else}
+
+
+ {#await import('$lib/inline-editor.svelte')}
+
+ {:then c}
+
+
+
+ {/await}
+
+ {#if data.questions[selected_question].image != undefined && data.questions[selected_question].image !== ''}
+
+

{
+ data.questions[selected_question].image = '';
+ }}
/>
- {/await}
-
- {#if data.questions[selected_question].image != undefined && data.questions[selected_question].image !== ''}
-
-

{
- data.questions[selected_question].image = '';
- }}
- />
-
- {:else if pow_data === undefined}
-
-
-
- {:else}
- {#await import('$lib/editor/uploader.svelte')}
-
- {:then c}
-
- {/await}
- {/if}
-
-
-
-
-
-
-
-
-
-
- {#if data.questions[selected_question].type === QuizQuestionType.ABCD}
- {#await import('$lib/editor/ABCDEditorPart.svelte')}
-
- {:then c}
-
- {/await}
- {:else if data.questions[selected_question].type === QuizQuestionType.RANGE}
-
- {:else if data.questions[selected_question].type === QuizQuestionType.VOTING}
- {#await import('$lib/editor/VotingEditorPart.svelte')}
-
- {:then c}
-
{/await}
{/if}
+
+
+
+
+
+ {#if data.questions[selected_question].type === QuizQuestionType.ABCD}
+ {#await import('$lib/editor/ABCDEditorPart.svelte')}
+
+ {:then c}
+
+ {/await}
+ {:else if data.questions[selected_question].type === QuizQuestionType.RANGE}
+
+ {:else if data.questions[selected_question].type === QuizQuestionType.VOTING}
+ {#await import('$lib/editor/VotingEditorPart.svelte')}
+
+ {:then c}
+
+ {/await}
+ {/if}
+
+
+ {$t('editor_page.right_click_to_delete')}
+
-
{$t('editor_page.right_click_to_delete')}
-
+ {/if}
diff --git a/frontend/src/lib/editor/sidebar.svelte b/frontend/src/lib/editor/sidebar.svelte
index c575606..8db676d 100644
--- a/frontend/src/lib/editor/sidebar.svelte
+++ b/frontend/src/lib/editor/sidebar.svelte
@@ -28,6 +28,14 @@
type: QuizQuestionType.ABCD
};
+ const empy_slide: Question = {
+ type: QuizQuestionType.SLIDE,
+ time: '120',
+ question: 'Slide',
+ image: undefined,
+ answers: ''
+ };
+
const setSelectedQuestion = (index: number): void => {
selected_question = index;
if (index === -1) {
@@ -242,15 +250,39 @@
{/each}
+
diff --git a/frontend/src/lib/quiz_types.ts b/frontend/src/lib/quiz_types.ts
index 5793508..8bc619e 100644
--- a/frontend/src/lib/quiz_types.ts
+++ b/frontend/src/lib/quiz_types.ts
@@ -4,6 +4,14 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
+export enum ElementTypes {
+ Text = 'TEXT', // eslint-disable-line no-unused-vars
+ Headline = 'HEADLINE', // eslint-disable-line no-unused-vars
+ Image = 'IMAGE', // eslint-disable-line no-unused-vars
+ Rectangle = 'RECTANGLE', // eslint-disable-line no-unused-vars
+ Circle = 'CIRCLE' // eslint-disable-line no-unused-vars
+}
+
export interface QuizData {
title: string;
description: string;
@@ -19,7 +27,8 @@ export interface QuizData {
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
+ VOTING = 'VOTING', // eslint-disable-line no-unused-vars
+ SLIDE = 'SLIDE' // eslint-disable-line no-unused-vars
}
export interface RangeQuizAnswer {
@@ -34,7 +43,7 @@ export interface Question {
question: string;
type?: QuizQuestionType;
image?: string;
- answers: Answer[] | RangeQuizAnswer | VotingAnswer[];
+ answers: Answer[] | RangeQuizAnswer | VotingAnswer[] | string;
}
export interface Answer {
@@ -42,6 +51,7 @@ export interface Answer {
answer: string;
color?: string;
}
+
export interface VotingAnswer {
answer: string;
image?: string;
diff --git a/frontend/src/lib/yupSchemas.ts b/frontend/src/lib/yupSchemas.ts
index 3f81e4a..664daea 100644
--- a/frontend/src/lib/yupSchemas.ts
+++ b/frontend/src/lib/yupSchemas.ts
@@ -34,6 +34,18 @@ export const RangeQuestionSchema = yup.object({
min_correct: yup.number(),
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 dataSchema = yup.object({
public: yup.boolean().required(),
type: yup.string(),
@@ -63,12 +75,23 @@ export const dataSchema = yup.object({
.lowercase(),
answers: yup.lazy((v) => {
if (Array.isArray(v)) {
- if (typeof v[0].right === 'boolean') {
- return ABCDQuestionSchema;
- } else {
- return VotingQuestionSchema;
+ 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();
+ }
+ } catch {
+ // console.log('SlideQuestionSchema');
+ return yup.string().required();
}
} else {
+ // console.log('RangeQuestionSchema');
return RangeQuestionSchema;
}
})
diff --git a/frontend/src/routes/view/[quiz_id]/+page.svelte b/frontend/src/routes/view/[quiz_id]/+page.svelte
index e8505b3..b91c208 100644
--- a/frontend/src/routes/view/[quiz_id]/+page.svelte
+++ b/frontend/src/routes/view/[quiz_id]/+page.svelte
@@ -11,6 +11,7 @@
import { QuizQuestionType } from '$lib/quiz_types.js';
import StartGamePopup from '$lib/dashboard/start_game.svelte';
import { onMount } from 'svelte';
+ import Spinner from '$lib/Spinner.svelte';
const tippy = createTippy({
arrow: true,
@@ -228,6 +229,14 @@
{/each}
+ {:else if question.type === QuizQuestionType.SLIDE}
+ {#await import('$lib/play/admin/slide.svelte')}
+
+ {:then c}
+
+
+
+ {/await}
{/if}