From ffee61fde338e792b579808558c2e9c4bd5ec5bf Mon Sep 17 00:00:00 2001 From: Mawoka Date: Fri, 24 Jun 2022 17:34:03 +0200 Subject: [PATCH] :sparkles: Big update to the admin-screen and smaller bug-fixes --- classquiz/routers/editor.py | 5 +- frontend/src/app.html | 6 + frontend/src/lib/admin.svelte | 177 ++++++++++++++++++ frontend/src/lib/admin.ts | 56 ++++++ frontend/src/lib/hashcash.ts | 4 +- frontend/src/lib/i18n/locales/en.json | 20 ++- frontend/src/lib/play/end.svelte | 18 +- frontend/src/lib/play/show_results.svelte | 112 ++++++------ frontend/src/lib/stores.ts | 7 - frontend/src/routes/__layout.svelte | 3 - frontend/src/routes/admin.svelte | 208 +++++----------------- frontend/src/routes/overview.svelte | 8 +- frontend/src/routes/play.svelte | 4 +- frontend/src/routes/view/[quiz_id].svelte | 4 +- 14 files changed, 375 insertions(+), 257 deletions(-) create mode 100644 frontend/src/lib/admin.svelte create mode 100644 frontend/src/lib/admin.ts diff --git a/classquiz/routers/editor.py b/classquiz/routers/editor.py index 06df97b..b46cbcc 100644 --- a/classquiz/routers/editor.py +++ b/classquiz/routers/editor.py @@ -154,13 +154,12 @@ async def finish_edit(edit_id: str, quiz_input: QuizInput): mark_image_for_deletion(question.image, i, old_quiz_data) else: raise HTTPException(status_code=400, detail="Image URL(s) aren't valid!") - print(images_to_delete) if session_data.edit: quiz = old_quiz_data meilisearch.index(settings.meilisearch_index).update_documents([await get_meili_data(quiz)]) - if quiz.public and not quiz_input.public: + if not quiz_input.public: meilisearch.index(settings.meilisearch_index).delete_document(str(quiz.id)) - if not quiz.public and quiz_input.public: + else: meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)]) quiz.title = quiz_input.title quiz.public = quiz_input.public diff --git a/frontend/src/app.html b/frontend/src/app.html index 43c1022..0b35b71 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -6,6 +6,12 @@ + + +{#if timer_res === undefined} + Select a question to start! +{:else if !final_results_clicked} +
+ {$t('admin_page.time_left')}: {timer_res} +
+{/if} +
+{#if timer_res === '0'} + {#if question_results === null} +
+ +
+ {:else} +
+
+ + + + + + + + + + {#each question_results as result} + + + + + + {/each} + +
+ {$t('words.username')} + + {$t('words.answer')} + + {$t('words.correct')}? +
+ {result.username} + + {result.answer} + + {#if result.right} + ✅ + {:else} + ❌ + {/if} +
+
+
+ {/if} +{:else if timer_res !== undefined} +
+ +
+{/if} +
+{#if get_question_title(selected_question + 1, quiz_data) !== '' && selected_question + 1 !== 0} +
+ +
+{:else if selected_question + 1 === 0} +
+ +
+{:else if final_results_clicked === false} +
+ +
+{/if} diff --git a/frontend/src/lib/admin.ts b/frontend/src/lib/admin.ts new file mode 100644 index 0000000..c59618a --- /dev/null +++ b/frontend/src/lib/admin.ts @@ -0,0 +1,56 @@ +import type { QuizData } from '$lib/quiz_types'; + +export const get_question_title = (q_number: number, quiz_data: QuizData): string => { + if (q_number - 1 === quiz_data.questions.length) { + return; + } + try { + return quiz_data.questions[q_number].question; + } catch (e) { + return ''; + } +}; + +export const getWinnersSorted = ( + quiz_data: QuizData, + final_results: Array | Array> +) => { + const winners = {}; + const q_count = quiz_data.questions.length; + for (let i = 0; i < q_count; i++) { + const q_res = final_results[i]; + if (q_res === null) { + continue; + } + for (let j = 0; j < q_res.length; j++) { + const res = q_res[j]; + if (res['right']) { + if (winners[res['username']] === undefined) { + winners[res['username']] = 0; + } + winners[res['username']] += 1; + } + } + } + + function sortObjectbyValue(obj) { + const asc = false; + const ret = {}; + Object.keys(obj) + .sort((a, b) => obj[asc ? a : b] - obj[asc ? b : a]) + .forEach((s) => (ret[s] = obj[s])); + return ret; + } + + return sortObjectbyValue(winners); +}; + +export interface Player { + username: string; +} + +export interface PlayerAnswer { + username: string; + answer: string; + right: string; +} diff --git a/frontend/src/lib/hashcash.ts b/frontend/src/lib/hashcash.ts index 4efd03e..118da6d 100644 --- a/frontend/src/lib/hashcash.ts +++ b/frontend/src/lib/hashcash.ts @@ -1,5 +1,4 @@ import { DateTime } from 'luxon'; -import { plausible } from '$lib/stores'; const gen_salt = (l: number): string => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; @@ -47,6 +46,7 @@ export const mint = async ( counter += 1; } const t2 = performance.now(); - plausible.trackEvent('Hashcash', { props: { ms_taken: t2 - t1 } }); + // eslint-disable-next-line no-undef + plausible('Hashcash', { props: { ms_taken: t2 - t1 } }); return `${challenge}:${result}`; }; diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index e07fefd..5e17382 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -13,7 +13,8 @@ }, "overview_page": { "created_at": "Created at", - "question_count": "Question count" + "question_count": "Question count", + "no_quizzes": "Looks like you don't have any quizzes. Wanna change that? Click the \"Create\"-button, or import a quiz from KAHOOT!" }, "edit_page": { "success_update_title": "Successfully updated quiz!", @@ -92,7 +93,11 @@ "screenshot": "Screenshot", "screenshot_plural": "Screenshots", "browser": "Browser", - "view": "View" + "view": "View", + "correct": "Correct", + "result": "", + "result_plural": "", + "count": "" }, "editor": { "time_in_seconds": "Time in seconds", @@ -114,7 +119,9 @@ "get_results": "Get results", "get_results_and_stop_time": "Get results and stop time", "get_final_results": "Get final results", - "export_results": "Export results" + "export_results": "Export results", + "show_next_question": "Show next question", + "start_by_showing_first_question": "Start by showing the first question!" }, "password_reset_page": { "reset_password": "Reset password" @@ -134,5 +141,12 @@ }, "search_page": { "at_least_3_characters": "Enter at least 3 characters..." + }, + "play_page": { + "end_sentence": "That's it! This was the quiz.", + "1st_place": "1st Place", + "2nd_place": "2nd Place", + "3rd place": "3rd Place", + "with_out_of": "with {{correct_questions}} out of {{total_question_count}}" } } diff --git a/frontend/src/lib/play/end.svelte b/frontend/src/lib/play/end.svelte index 61d101a..72e1d8a 100644 --- a/frontend/src/lib/play/end.svelte +++ b/frontend/src/lib/play/end.svelte @@ -1,5 +1,8 @@
-

That's it! This was the quiz.

+

{$t('play_page.end_sentence')}

- 1st Place: {winners_arr[0]} + {$t('play_page.1st_place')}: {winners_arr[0]} with {winners[winners_arr[0]]} out of {quiz_data.questions.length}

{#if winners_arr.length >= 2}

- 2nd Place: {winners_arr[1]} + {$t('play_page.2nd_place')}: {winners_arr[1]} with {winners[winners_arr[1]]} out of {quiz_data.questions.length}

@@ -66,8 +69,13 @@ {#if winners_arr.length >= 3}

- 3rd Place: {winners_arr[2]} - with {winners[winners_arr[2]]} out of {quiz_data.questions.length} + {$t('play_page.3rd place')}: {winners_arr[2]} + + {$t('play_page.with_out_of', { + correct_questions: winners[winners_arr[2]], + total_question_count: quiz_data.questions.length + })} +

{/if} diff --git a/frontend/src/lib/play/show_results.svelte b/frontend/src/lib/play/show_results.svelte index f319a45..124944b 100644 --- a/frontend/src/lib/play/show_results.svelte +++ b/frontend/src/lib/play/show_results.svelte @@ -1,6 +1,8 @@ @@ -23,62 +23,58 @@
-

Results

-
-
-
-
- - - - - - - - - - - {#each game_data.questions[parseInt(question_index)].answers as answer} - - - - - {/each} - -
- Answer - - Count - - Right -
- {answer.answer} - - {data_store[answer.answer]} - - {#if answer.right} - ✅ - {:else} - ❌ - {/if} -
-
-
+

{$t('words.result', { count: 2 })}

+
+
+ + + + + + + + + + {#each game_data.questions[parseInt(question_index)].answers as answer} + + + + + + {/each} + +
+ {$t('words.answer')} + + {$t('words.count')} + + {$t('words.correct')} +
+ {answer.answer} + + {data_store[answer.answer]} + + {#if answer.right} + ✅ + {:else} + ❌ + {/if} +
diff --git a/frontend/src/lib/stores.ts b/frontend/src/lib/stores.ts index cbd3ee6..be1520a 100644 --- a/frontend/src/lib/stores.ts +++ b/frontend/src/lib/stores.ts @@ -8,7 +8,6 @@ export const alertModal = writable({ open: false, title: '', body: '' }); import { goto } from '$app/navigation'; import { page } from '$app/stores'; -import Plausible from 'plausible-tracker'; const URLSearchParamsToObject = (params: URLSearchParams) => { const obj = {}; @@ -42,9 +41,3 @@ export const createQueryParamsStore = (key: string) => { } }; }; - -export const plausible = Plausible({ - domain: 'classquiz.mawoka.eu', - apiHost: 'https://sugar.mawoka.eu.org', - trackLocalhost: true -}); diff --git a/frontend/src/routes/__layout.svelte b/frontend/src/routes/__layout.svelte index 9d85210..dbffb4f 100644 --- a/frontend/src/routes/__layout.svelte +++ b/frontend/src/routes/__layout.svelte @@ -18,7 +18,6 @@ import { initLocalizationContext } from '$lib/i18n'; import { browser } from '$app/env'; import Alert from '$lib/modals/alert.svelte'; - import { plausible } from '$lib/stores'; /* afterNavigate(() => { if (browser) { @@ -33,8 +32,6 @@ });*/ if (browser) { - plausible.enableAutoPageviews(); - plausible.enableAutoOutboundTracking(); pathname.set(window.location.pathname); if ( localStorage.theme === 'dark' || diff --git a/frontend/src/routes/admin.svelte b/frontend/src/routes/admin.svelte index 45534d8..27c9a9d 100644 --- a/frontend/src/routes/admin.svelte +++ b/frontend/src/routes/admin.svelte @@ -28,6 +28,8 @@ import { socket } from '$lib/socket'; import { getLocalization } from '$lib/i18n'; import { navbarVisible } from '$lib/stores'; + import type { PlayerAnswer, Player } from '$lib/admin.ts'; + import SomeAdminScreen from '$lib/admin.svelte'; navbarVisible.set(false); @@ -41,16 +43,6 @@ export let auto_connect: boolean; export let game_token: string; - interface Player { - username: string; - } - - interface PlayerAnswer { - username: string; - answer: string; - right: string; - } - let players: Array = []; let errorMessage = ''; let game_started = false; @@ -59,12 +51,9 @@ let question_results = null; let final_results: Array | Array> = [null]; let success = false; - let selected_question = -1; - let timer_res: string; let dataexport_download_a; let warnToLeave = true; - let shown_question_now: number; const connect = () => { socket.emit('register_as_admin', { game_pin: game_pin, @@ -85,53 +74,6 @@ socket.on('already_registered_as_admin', () => { errorMessage = $t('admin_page.already_registered_as_admin'); }); - const set_question_number = (q_number: number) => { - 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 += 1; - timer(timer_res); - }; - - const get_question_results = () => { - socket.emit('get_question_results', { - game_id: game_token, - question_number: shown_question_now - }); - timer_res = '0'; - }; - - const getWinnersSorted = () => { - let winners = {}; - let q_count = quiz_data.questions.length; - for (let i = 0; i < q_count; i++) { - let q_res = final_results[i]; - if (q_res === null) { - continue; - } - for (let j = 0; j < q_res.length; j++) { - let res = q_res[j]; - if (res['right']) { - if (winners[res['username']] === undefined) { - winners[res['username']] = 0; - } - winners[res['username']] += 1; - } - } - } - - function sortObjectbyValue(obj) { - const asc = false; - const ret = {}; - Object.keys(obj) - .sort((a, b) => obj[asc ? a : b] - obj[asc ? b : a]) - .forEach((s) => (ret[s] = obj[s])); - return ret; - } - - return sortObjectbyValue(winners); - }; socket.on('question_results', (data) => { try { @@ -149,30 +91,6 @@ warnToLeave = true; }); - const get_final_results = () => { - socket.emit('get_final_results', {}); - }; - - socket.on('final_results', (data) => { - // data = JSON.parse(data); - final_results = data; - - console.log(getWinnersSorted()); - }); - - const timer = (time: string) => { - let seconds = Number(time); - let timer_interval = setInterval(() => { - if (timer_res === '0') { - clearInterval(timer_interval); - return; - } else { - seconds--; - } - - timer_res = seconds.toString(); - }, 1000); - }; const confirmUnload = () => { if (warnToLeave) { event.preventDefault(); @@ -180,16 +98,6 @@ } }; - const get_question_title = (q_number: number): string => { - if (q_number - 1 === quiz_data.questions.length) { - return; - } - try { - return quiz_data.questions[q_number].question; - } catch (e) { - return ''; - } - }; const request_answer_export = async () => { await socket.emit('get_export_token'); }; @@ -199,7 +107,18 @@ ClassQuiz - Host - +{#if JSON.stringify(final_results) !== JSON.stringify([null])} +
+ +
+ {#await import('$lib/play/end.svelte') then c} + + {/await} +{/if} {#if !success} @@ -210,84 +129,39 @@ {:else if !game_started} QR code to join the game -

{$t('words.pin')}: {quiz_data.game_pin}

-
    - {#if players.length > 0} - {#each players as player} -
  • - {player.username} - -
  • - {/each} - {/if} -
+

{$t('words.pin')}: {quiz_data.game_pin}

+
+
    + {#if players.length > 0} + {#each players as player} +
  • + {player.username} + +
  • + {/each} + {/if} +
+
{#if players.length > 0} - +
+ +
{/if} {:else} - {#if timer_res === undefined} - Select a question to start! - {:else} - {$t('admin_page.time_left')}: {timer_res} - {/if} -
- {#if timer_res === '0'} - {#if question_results === null} - -
- {:else} -
-
    - {#each question_results as result} -
  • {result.username} - {result.answer} - {result.right}
  • - {/each} -
- {/if} - {:else if timer_res !== undefined} - - {/if} -
- - {#if get_question_title(selected_question + 1) !== ''} - -
- {:else} - - {/if} -{/if} -{#if JSON.stringify(final_results) !== JSON.stringify([null])} - - {#await import('$lib/play/end.svelte') then c} - - {/await} + {/if} + {:else}

- - Looks like you don't have any quizzes. Wanna change that? Click the "Create"-button, - or import a quiz from KAHOOT! + {$t('overview_page.no_quizzes')}

{/if}
diff --git a/frontend/src/routes/play.svelte b/frontend/src/routes/play.svelte index 3c5cfa4..2a69917 100644 --- a/frontend/src/routes/play.svelte +++ b/frontend/src/routes/play.svelte @@ -21,7 +21,7 @@ import ShowTitle from '$lib/play/title.svelte'; import Question from '$lib/play/question.svelte'; import ShowResults from '$lib/play/show_results.svelte'; - import { navbarVisible, plausible } from '$lib/stores'; + import { navbarVisible } from '$lib/stores'; import ShowEndScreen from '$lib/play/end.svelte'; // Exports @@ -66,7 +66,7 @@ console.log('joined_game', data); gameData = JSON.parse(data); // eslint-disable-next-line no-undef - plausible.trackEvent('Joined Game', { props: { quiz_id: gameData.quiz_id } }); + plausible('Joined Game', { props: { quiz_id: gameData.quiz_id } }); }); socket.on('game_not_found', () => { diff --git a/frontend/src/routes/view/[quiz_id].svelte b/frontend/src/routes/view/[quiz_id].svelte index 0596e94..cd7d37c 100644 --- a/frontend/src/routes/view/[quiz_id].svelte +++ b/frontend/src/routes/view/[quiz_id].svelte @@ -28,7 +28,6 @@ import { createTippy } from 'svelte-tippy'; import 'tippy.js/animations/perspective-subtle.css'; import 'tippy.js/dist/tippy.css'; - import { plausible } from '$lib/stores'; const tippy = createTippy({ arrow: true, @@ -80,7 +79,7 @@ } const data = await res.json(); // eslint-disable-next-line no-undef - plausible.trackEvent('Started Game', { props: { quiz_id: id } }); + plausible('Started Game', { props: { quiz_id: id } }); window.location.replace(`/admin?token=${data.game_id}&pin=${data.game_pin}&connect=1`); }; @@ -159,6 +158,7 @@ {:else}