272 lines
6.9 KiB
Vue
272 lines
6.9 KiB
Vue
<script lang="ts" setup>
|
|
import { intervalToDuration, addMinutes, addSeconds, isAfter } from 'date-fns';
|
|
import { useNow } from '@vueuse/core';
|
|
import { useExamStore } from '~/store/examStore';
|
|
|
|
definePageMeta({ middleware: 'exam' });
|
|
|
|
const examStore = useExamStore();
|
|
const basicStore = useBasicStore();
|
|
const advancedStore = useAdvancedStore();
|
|
await callOnce(() => examStore.mildReset(), { mode: 'navigation' });
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
function preventRefresh(e: any) {
|
|
e.preventDefault();
|
|
e.returnValue = ''; // polyfill for older chrome
|
|
}
|
|
|
|
const now = ref('basic');
|
|
|
|
const nowTime = useNow();
|
|
const timeEnd = addMinutes(new Date(), 25);
|
|
|
|
const questionEnd = ref(addSeconds(new Date(), 20));
|
|
|
|
const mediaLoaded = ref(false);
|
|
|
|
const time = ref({
|
|
total: computed(() =>
|
|
intervalToDuration({
|
|
start: nowTime.value,
|
|
end: timeEnd,
|
|
}),
|
|
),
|
|
question: computed(() => {
|
|
const interval = intervalToDuration({
|
|
start: nowTime.value,
|
|
end: questionEnd.value,
|
|
});
|
|
if (Object.hasOwn(interval, 'seconds') && interval.seconds != undefined) {
|
|
return interval.seconds;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}),
|
|
phase: 'set-basic',
|
|
});
|
|
|
|
function changeQuestionTimeAfterNext() {
|
|
if (time.value.phase == 'set-basic') {
|
|
time.value.phase = 'start-basic';
|
|
questionEnd.value = addSeconds(new Date(), 15);
|
|
} else if (time.value.phase == 'start-basic') {
|
|
if (now.value == 'basic') {
|
|
time.value.phase = 'set-basic';
|
|
mediaLoaded.value = false;
|
|
questionEnd.value = addSeconds(new Date(), 20);
|
|
} else {
|
|
time.value.phase = 'set-advanced';
|
|
questionEnd.value = addSeconds(new Date(), 50);
|
|
}
|
|
} else if (time.value.phase == 'set-advanced') {
|
|
questionEnd.value = addSeconds(new Date(), 50);
|
|
}
|
|
}
|
|
|
|
function onMediaLoad() {
|
|
mediaLoaded.value = true;
|
|
}
|
|
|
|
function clickNext() {
|
|
if (ending.value) {
|
|
endExam();
|
|
}
|
|
if (time.value.phase != 'set-basic') {
|
|
next();
|
|
}
|
|
changeQuestionTimeAfterNext();
|
|
}
|
|
|
|
onMounted(() => {
|
|
useHead({
|
|
title: `${$t('question')} 1/20`,
|
|
});
|
|
window.addEventListener('beforeunload', preventRefresh);
|
|
|
|
watchEffect(() => {
|
|
if (isAfter(nowTime.value, timeEnd)) endExam();
|
|
});
|
|
|
|
watchEffect(() => {
|
|
if (now.value === 'basic')
|
|
useHead({ title: `${$t('question')} ${countBasic.value + 1}/20` });
|
|
if (now.value === 'advanced')
|
|
useHead({ title: `${$t('question')} ${countAdvanced.value + 1}/12` });
|
|
});
|
|
|
|
watchEffect(() => {
|
|
if (mediaLoaded.value == false && time.value.phase == 'start-basic') {
|
|
questionEnd.value = addSeconds(new Date(), 15);
|
|
}
|
|
if (time.value.question < 0 && ending.value == false) {
|
|
clickNext();
|
|
}
|
|
});
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('beforeunload', preventRefresh);
|
|
});
|
|
|
|
const {
|
|
data: dataBasic,
|
|
error: errorBasic,
|
|
status: statusBasic,
|
|
} = await useLazyFetch<BasicQuestion[]>(`/api/basic`, {
|
|
query: {
|
|
category: examStore.category,
|
|
},
|
|
});
|
|
|
|
const {
|
|
data: dataAdvanced,
|
|
error: errorAdvanced,
|
|
status: statusAdvanced,
|
|
} = await useLazyFetch<AdvancedQuestion[]>(`/api/advanced`, {
|
|
query: {
|
|
category: examStore.category,
|
|
},
|
|
});
|
|
|
|
const countBasic = ref(0);
|
|
const countAdvanced = ref(-1);
|
|
|
|
const answer = ref<string>('');
|
|
|
|
const ending = ref(false);
|
|
|
|
const questionBasic = computed<BasicQuestion | undefined>(() =>
|
|
dataBasic.value?.at(countBasic.value),
|
|
);
|
|
const questionAdvanced = computed<AdvancedQuestion | undefined>(() =>
|
|
dataAdvanced.value?.at(countAdvanced.value),
|
|
);
|
|
|
|
const question = computed(() => {
|
|
if (now.value === 'basic') return questionBasic.value;
|
|
if (now.value === 'advanced') return questionAdvanced.value;
|
|
return null;
|
|
});
|
|
|
|
const result: Ref<ResultEndType> = ref({
|
|
basic: [],
|
|
advanced: [],
|
|
});
|
|
|
|
function next() {
|
|
if (examStore.end == false) {
|
|
result.value[now.value as keyof ResultEndType].push({
|
|
question: question.value,
|
|
chosen_answer: answer.value,
|
|
correct_answer: question.value?.correct_answer ?? null,
|
|
chosen_is_correct: answer.value == question.value?.correct_answer,
|
|
});
|
|
answer.value = '';
|
|
} else {
|
|
console.error('next() incorrectly executed - exam has already ended.');
|
|
}
|
|
if (now.value === 'basic') {
|
|
if (countBasic.value + 1 <= 19) {
|
|
countBasic.value++;
|
|
} else {
|
|
now.value = 'advanced';
|
|
countAdvanced.value++;
|
|
}
|
|
} else if (now.value === 'advanced') {
|
|
if (countAdvanced.value + 1 <= 11) {
|
|
countAdvanced.value++;
|
|
}
|
|
if (countAdvanced.value + 1 >= 12) {
|
|
ending.value = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function endExam() {
|
|
loading.value = true;
|
|
while (ending.value == false) {
|
|
if (time.value.phase != 'set-basic') {
|
|
next();
|
|
}
|
|
changeQuestionTimeAfterNext();
|
|
}
|
|
next();
|
|
basicStore.set(result.value.basic);
|
|
advancedStore.set(result.value.advanced);
|
|
examStore.setEnd(true);
|
|
if (
|
|
basicStore.basic == result.value.basic &&
|
|
advancedStore.advanced == result.value.advanced &&
|
|
examStore.end
|
|
) {
|
|
return navigateTo(`/result`, { replace: true });
|
|
} else {
|
|
return navigateTo(`/anomaly`);
|
|
}
|
|
}
|
|
|
|
const loading = ref(false);
|
|
|
|
const showEndModal = ref(false);
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<!-- as in to transition to the next page -->
|
|
<LoadingScreen v-if="loading" />
|
|
<div v-if="statusBasic === 'success' && statusAdvanced === 'success'">
|
|
<div class="grid grid-cols-4 min-h-dvh">
|
|
<div class="col-span-3 flex flex-col">
|
|
<BarTop
|
|
:points="question?.weight"
|
|
:category="examStore.category"
|
|
:time-remaining="time.total"
|
|
/>
|
|
<MediaBox
|
|
:media-path="question?.media_url"
|
|
:phase="time.phase"
|
|
@mediaload="onMediaLoad()"
|
|
/>
|
|
<QuestionBasic
|
|
v-if="now === 'basic'"
|
|
v-model="answer"
|
|
phase="exam"
|
|
:question="questionBasic"
|
|
/>
|
|
<QuestionAdvanced
|
|
v-else-if="now === 'advanced'"
|
|
v-model="answer"
|
|
phase="exam"
|
|
:question="questionAdvanced"
|
|
/>
|
|
</div>
|
|
<BarRightExam
|
|
:count-basic="countBasic"
|
|
:count-advanced="countAdvanced"
|
|
:now="now"
|
|
:ending="ending"
|
|
:set-basic="time.phase == 'set-basic'"
|
|
:time="time.question"
|
|
:phase="time.phase"
|
|
@show-end-modal="showEndModal = true"
|
|
@next-question="clickNext()"
|
|
@end-exam="endExam()"
|
|
@next-time="changeQuestionTimeAfterNext()"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="statusBasic === 'error' || statusAdvanced === 'error'">
|
|
{{ $t('anAPIErrorOccured') }}:<br />
|
|
<code class="text-sm">{{ errorBasic }}</code>
|
|
<br />
|
|
<code class="text-sm">{{ errorAdvanced }}</code>
|
|
</div>
|
|
<LoadingScreen v-else />
|
|
<EndModal
|
|
:show-modal="showEndModal"
|
|
@hide-end-modal="showEndModal = false"
|
|
@end-exam="endExam()"
|
|
/>
|
|
</div>
|
|
</template>
|