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} -
- Content for Question -
- {/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} -
- icon - {answer.answer} + Content for Question +
+ {/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} -
+ icon + {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 !== ''} +
+ not available { + data.questions[selected_question].image = ''; + }} />
- {/await} -
- {#if data.questions[selected_question].image != undefined && data.questions[selected_question].image !== ''} -
- not available { - data.questions[selected_question].image = ''; - }} - /> -
- {:else if pow_data === undefined} - - - - {:else} - {#await import('$lib/editor/uploader.svelte')} - - {:then c} - - {/await} - {/if} -
-
- - + + {:else} + {#await import('$lib/editor/uploader.svelte')} + + {:then c} + - - -
-
-
- -
-
- {#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}
+ + {#if settings_menu_open} + + {/if} +
+
+ +
+ +
+ + {#if selected_element === null} + + {/if} +
+
+
+
diff --git a/frontend/src/lib/editor/slides/edit_menu.svelte b/frontend/src/lib/editor/slides/edit_menu.svelte new file mode 100644 index 0000000..d79df0a --- /dev/null +++ b/frontend/src/lib/editor/slides/edit_menu.svelte @@ -0,0 +1,205 @@ + + + +
+
+ + {#if opened_dropdown === 'color'} +
+ +
+ {/if} +
+
+ + {#if opened_dropdown === 'font_size'} +
+ +
+ {/if} +
+
+ + +
+
diff --git a/frontend/src/lib/editor/slides/element_selection.svelte b/frontend/src/lib/editor/slides/element_selection.svelte new file mode 100644 index 0000000..581c46b --- /dev/null +++ b/frontend/src/lib/editor/slides/element_selection.svelte @@ -0,0 +1,93 @@ + + + +
+
    + {#each element_list as el} +
  • { + selected_element = el.type; + }} + > +
    +

    {el.name}

    +

    {el.description}

    +
    + {el.shortcut} +
  • + {/each} +
+
diff --git a/frontend/src/lib/editor/slides/icons/x-circle.svg b/frontend/src/lib/editor/slides/icons/x-circle.svg new file mode 100644 index 0000000..89112d8 --- /dev/null +++ b/frontend/src/lib/editor/slides/icons/x-circle.svg @@ -0,0 +1,8 @@ + + + + diff --git a/frontend/src/lib/editor/slides/settings_menu.svelte b/frontend/src/lib/editor/slides/settings_menu.svelte new file mode 100644 index 0000000..88171ef --- /dev/null +++ b/frontend/src/lib/editor/slides/settings_menu.svelte @@ -0,0 +1,39 @@ + + + +
+ + +
diff --git a/frontend/src/lib/editor/slides/types/circle.svelte b/frontend/src/lib/editor/slides/types/circle.svelte new file mode 100644 index 0000000..2a3b06a --- /dev/null +++ b/frontend/src/lib/editor/slides/types/circle.svelte @@ -0,0 +1,20 @@ + + + +
+ {#if editor} + + {/if} +
diff --git a/frontend/src/lib/editor/slides/types/headline.svelte b/frontend/src/lib/editor/slides/types/headline.svelte new file mode 100644 index 0000000..6ef78da --- /dev/null +++ b/frontend/src/lib/editor/slides/types/headline.svelte @@ -0,0 +1,21 @@ + + + +{#if editor} + +{:else} +

+ {data} +

+{/if} diff --git a/frontend/src/lib/editor/slides/types/rectangle.svelte b/frontend/src/lib/editor/slides/types/rectangle.svelte new file mode 100644 index 0000000..502d7c8 --- /dev/null +++ b/frontend/src/lib/editor/slides/types/rectangle.svelte @@ -0,0 +1,20 @@ + + + +
+ {#if editor} + + {/if} +
diff --git a/frontend/src/lib/editor/slides/types/text.svelte b/frontend/src/lib/editor/slides/types/text.svelte new file mode 100644 index 0000000..340fb06 --- /dev/null +++ b/frontend/src/lib/editor/slides/types/text.svelte @@ -0,0 +1,15 @@ + + + +{#if editor} +