exam: warning on refresh and end-exam

warning on end-exam if it isn't the last question - otherwise exam ends without warning
This commit is contained in:
NetMan 2025-12-15 21:05:54 +01:00
parent 97b8d5dab9
commit 59497e8b01
4 changed files with 92 additions and 20 deletions

View file

@ -1,18 +1,12 @@
# nuxt-prawo-jazdy # nuxt-prawo-jazdy
## Setup
This project utilizes `pnpm`, thus it is recommended
```bash
pnpm install
```
## Required ## Required
The [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) project is designed for this one in mind, so use it in conjunction with this - visit it for more details The [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) project is designed for this one in mind, so use it in conjunction with this - visit it for more details
You also need the exam media files from the (Ministry of Infrasture)[https://www.gov.pl/web/infrastruktura/prawo-jazdy] - the latest files should be there. The newest at the moment of me writing this (13th of Decemver 2025) are the (visualisations for questions from November(?) of 2025)[https://www.gov.pl/pliki/mi/pytania_egzaminacyjne_na_prawo_jazdy_11_2025.zip] You also need the exam media files from the (Ministry of Infrasture)[https://www.gov.pl/web/infrastruktura/prawo-jazdy] - the latest files should be there
The newest at the moment of me writing this (December 13th 2025) are the (visualisations for questions from November 2025)[https://www.gov.pl/pliki/mi/pytania_egzaminacyjne_na_prawo_jazdy_11_2025.zip]
# To-do: # To-do:
@ -21,20 +15,41 @@ You also need the exam media files from the (Ministry of Infrasture)[https://www
- [x] come up with how to show results appropriately - [x] come up with how to show results appropriately
- [x] better answer click recognition - [x] better answer click recognition
- [x] beautify website (good for now) - [x] beautify website (good for now)
- [x] <b>Fixed?</b> Needs testing, but should be fine question-mark? - <b>fix pinia middleware between pages, MAJOR ISSUE - finishing exam sometimes redirects to homepage instead of results, help appreciated</b> - [x] <b>Fixed?</b> Needs testing, but should be fine question-mark? - fix pinia middleware between pages, MAJOR ISSUE - finishing exam sometimes redirects to homepage instead of results
- [x] (scrapped - lazy loading)
- [x] question timers - [x] question timers
- [ ] exam (& results?) warning leave message on exit and timer end (and definitely on refresh) - [x] exam (& results?) warning leave message on exit and timer end (and definitely on refresh)
- [ ] add keybinds:
- S - start
- D - nast.pyt
- X - koniec egzaminu (na pewno chcesz zakonczyc egzamin?)
- T - Tak
- N - Nie
- A - A
- B - B
- C - C
- [ ] i18n - pl, en, de, ua (not all questions are available in ua, api handle) - [ ] i18n - pl, en, de, ua (not all questions are available in ua, api handle)
- UI i18n - UI i18n
- db: examstore add language field, api handle languages - db: examstore add language field, api handle languages
- [ ] db: (revise) script for processing, (revise and) share appropriate files - [ ] db: (revise) script for processing, (revise and) share appropriate files
- [ ] clean up js code in exam.vue and result.vue (currently a little bit of a mess)
## Some info ## Some information about the project
My intention is, to share access to test exams free of charge - all data is free of charge and is already available as public information, either on the gov website, or by writing to the MI My intention is, to share access to test exams free of charge, you don't have to pay me - although you can, I greatly appreciate if you donate!
This project is a website mimicking an official driver's license exam (for different categories) with a seperate CDN for media, connected using an ORM to a postgres DB In the future I will host this project publicly `aaS`, and will probably put non-invasive, privacy friendly ads if it gains enough traction
All data used by this software is public information by definition provided in the Polish Constitution - (article 61.)[https://www.sejm.gov.pl/prawo/konst/polski/kon1.htm], and can be acquired by either checking above links on the gov website, or by writing to the Ministry ((if something happened to be missing))[placeholder_for_post_about_missing_points_column]
This project is a website mimicking an official driver's license theoritical exam (for different license categories) with a seperate media server, connected using drizzle ORM to a SQLite database
## Setup
This project utilizes `pnpm`, thus it is recommended
```bash
pnpm install
```
## Development Server ## Development Server

29
components/EndModal.vue Normal file
View file

@ -0,0 +1,29 @@
<script setup lang="ts">
const props = defineProps<{ showModal: boolean }>();
const endModal = useTemplateRef('endModal');
const emit = defineEmits(['hideEndModal', 'endExam']);
watchEffect(() => {
if (props.showModal && endModal.value?.open == false) {
endModal.value?.showModal();
emit('hideEndModal');
}
});
</script>
<template>
<dialog
ref="endModal"
class="flex justify-center items-center backdrop-blur-sm modal"
>
<div class="flex flex-col p-3 bg-base rounded-md gap-3 modal-box min-w-fit">
<h1 class="text-[1.5rem]">Koniec egzaminu</h1>
<div class="*:inline">Czy na pewno chcesz zakończyć egzamin?</div>
<div class="flex flex-row gap-2 justify-around">
<div class="btn btn-lg btn-success" @click="emit('endExam')">Tak</div>
<div class="btn btn-lg btn-error" @click="endModal?.close()">Nie</div>
</div>
</div>
</dialog>
</template>

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ const props = defineProps<{
countBasic: number; countBasic: number;
countAdvanced: number; countAdvanced: number;
now: string | null | undefined; now: string | null | undefined;
@ -13,14 +13,23 @@ const emit = defineEmits<{
endExam: []; endExam: [];
nextQuestion: []; nextQuestion: [];
nextTime: []; nextTime: [];
showEndModal: [];
}>(); }>();
function tryEndExam() {
if (props.ending == false) {
emit('showEndModal');
} else {
emit('endExam');
}
}
</script> </script>
<template> <template>
<div <div
class="flex flex-col items-stretch p-4 gap-10 border-l border-base-300 bg-base-100" class="flex flex-col items-stretch p-4 gap-10 border-l border-base-300 bg-base-100"
> >
<button class="btn btn-warning btn-xl" @click="emit('endExam')"> <button class="btn btn-warning btn-xl" @click="tryEndExam()">
Zakończ egzamin Zakończ egzamin
</button> </button>

View file

@ -10,9 +10,11 @@ const basicStore = useBasicStore();
const advancedStore = useAdvancedStore(); const advancedStore = useAdvancedStore();
await callOnce(() => examStore.mildReset(), { mode: 'navigation' }); await callOnce(() => examStore.mildReset(), { mode: 'navigation' });
useHead({ // eslint-disable-next-line @typescript-eslint/no-explicit-any
title: 'Pytanie 1/20', function preventRefresh(e: any) {
}); e.preventDefault();
e.returnValue = ''; // polyfill for older chrome
}
const now = ref('basic'); const now = ref('basic');
@ -77,6 +79,11 @@ function clickNext() {
} }
onMounted(() => { onMounted(() => {
useHead({
title: 'Pytanie 1/20',
});
window.addEventListener('beforeunload', preventRefresh);
watchEffect(() => { watchEffect(() => {
if (isAfter(nowTime.value, timeEnd)) endExam(); if (isAfter(nowTime.value, timeEnd)) endExam();
}); });
@ -98,6 +105,10 @@ onMounted(() => {
}); });
}); });
onBeforeUnmount(() => {
window.removeEventListener('beforeunload', preventRefresh);
});
const { const {
data: dataBasic, data: dataBasic,
error: errorBasic, error: errorBasic,
@ -196,6 +207,8 @@ function endExam() {
} }
const loading = ref(false); const loading = ref(false);
const showEndModal = ref(false);
</script> </script>
<template> <template>
@ -236,6 +249,7 @@ const loading = ref(false);
:set-basic="time.phase == 'set-basic'" :set-basic="time.phase == 'set-basic'"
:time="time.question" :time="time.question"
:phase="time.phase" :phase="time.phase"
@show-end-modal="showEndModal = true"
@next-question="clickNext()" @next-question="clickNext()"
@end-exam="endExam()" @end-exam="endExam()"
@next-time="changeQuestionTimeAfterNext()" @next-time="changeQuestionTimeAfterNext()"
@ -246,5 +260,10 @@ const loading = ref(false);
An API error occurred: {{ errorBasic }} {{ errorAdvanced }} An API error occurred: {{ errorBasic }} {{ errorAdvanced }}
</div> </div>
<LoadingScreen v-else /> <LoadingScreen v-else />
<EndModal
:show-modal="showEndModal"
@hide-end-modal="showEndModal = false"
@end-exam="endExam()"
/>
</div> </div>
</template> </template>