ui i18n english, fix locale routes, theme toggle
minor: - change background for category list for dark theme compatibility - try to 'fix' video playback errors in console
This commit is contained in:
parent
19508f1148
commit
ccccf038d9
15 changed files with 205 additions and 101 deletions
11
app.vue
11
app.vue
|
|
@ -1,5 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
const examStore = useExamStore();
|
||||
const { setLocale } = useI18n();
|
||||
|
||||
setLocale(examStore.lang as 'pl' | 'en' | 'de' | 'ua');
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div :data-theme="themeStore.theme" class="min-h-dvh">
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,12 @@ watchEffect(() => {
|
|||
<h1 class="text-[1.5rem]">{{ $t('examEnd') }}</h1>
|
||||
<div class="*:inline">{{ $t('doYouReallyWantToEndExam') }}</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 class="btn btn-lg btn-success" @click="emit('endExam')">
|
||||
{{ $t('yes') }}
|
||||
</div>
|
||||
<div class="btn btn-lg btn-error" @click="endModal?.close()">
|
||||
{{ $t('no') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import { joinURL } from 'ufo';
|
|||
const runtimeConfig = useRuntimeConfig();
|
||||
const cdnUrl = runtimeConfig.public.cdn_url;
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const emit = defineEmits(['mediaload']);
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -25,10 +23,12 @@ const media = computed(() => {
|
|||
return { name: dotSplit?.join('.'), type };
|
||||
});
|
||||
|
||||
const video = ref();
|
||||
const video = useTemplateRef('video');
|
||||
|
||||
function onVideoLoad() {
|
||||
if (route.path === '/exam') {
|
||||
if (props.phase != 'result') {
|
||||
const videoPlayInterval = setInterval(() => {
|
||||
if (video.value) {
|
||||
video.value.play();
|
||||
let duration = video.value.duration;
|
||||
if (isNaN(duration) || duration == Infinity) {
|
||||
|
|
@ -37,6 +37,9 @@ function onVideoLoad() {
|
|||
setTimeout(() => {
|
||||
emit('mediaload');
|
||||
}, duration * 1000);
|
||||
clearInterval(videoPlayInterval);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -44,8 +47,11 @@ function onVideoLoad() {
|
|||
<template>
|
||||
<div
|
||||
class="select-none flex-auto w-full *:object-contain *:w-full *:h-full *:max-h-full relative *:absolute"
|
||||
:class="route.path === '/exam' ? 'z-[-1]' : ''"
|
||||
>
|
||||
<div
|
||||
class="w-full h-full opacity-0"
|
||||
:class="phase != 'result' ? 'z-10' : 'z-[-10]'"
|
||||
></div>
|
||||
<img v-if="phase == 'set-basic'" src="/placeholder.svg" alt="placeholder" />
|
||||
<NuxtImg
|
||||
v-else-if="media.type === 'image'"
|
||||
|
|
@ -59,7 +65,7 @@ function onVideoLoad() {
|
|||
v-else-if="media.type === 'video'"
|
||||
:key="`${mediaPath}-video`"
|
||||
ref="video"
|
||||
:controls="route.path === '/result'"
|
||||
:controls="phase == 'result'"
|
||||
@canplaythrough="onVideoLoad()"
|
||||
>
|
||||
<source :src="joinURL(cdnUrl, media.name + '.mp4')" type="video/mp4" />
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ onKeyStroke(['N', 'n'], () => {
|
|||
<div>
|
||||
<div class="flex flex-row justify-around">
|
||||
<input
|
||||
v-for="[element, value] of Object.entries({ TAK: true, NIE: false })"
|
||||
v-for="[element, value] of Object.entries({ yes: true, no: false })"
|
||||
:id="`odp_${element}`"
|
||||
:ref="`${value}-button`"
|
||||
:key="`btn_answer_${element}`"
|
||||
|
|
@ -44,7 +44,7 @@ onKeyStroke(['N', 'n'], () => {
|
|||
name="tak_nie"
|
||||
:value="value.toString()"
|
||||
class="btn btn-primary btn-xl"
|
||||
:aria-label="element"
|
||||
:aria-label="$t(element).toLocaleUpperCase()"
|
||||
:class="
|
||||
phase == 'exam'
|
||||
? answer == null
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
"B": "B",
|
||||
"C": "C"
|
||||
},
|
||||
"yes": "Tak",
|
||||
"no": "Nie",
|
||||
"theme": "Motyw",
|
||||
"light": "Jasny",
|
||||
"dark": "Ciemny",
|
||||
"auto": "Automatyczny",
|
||||
"endExam": "Zakończ egzamin",
|
||||
"examEnd": "Koniec egzaminu",
|
||||
"basicQuestions": "Pytania podstawowe",
|
||||
|
|
|
|||
|
|
@ -1,73 +1,79 @@
|
|||
{
|
||||
"mainTitle": "Test na prawo jazdy",
|
||||
"loading": "Ładowanie",
|
||||
"keybinds": "Skróty klawiszowe",
|
||||
"mainTitle": "Driving exam",
|
||||
"loading": "Loading",
|
||||
"keybinds": "Keybinds",
|
||||
"bindedKeys": {
|
||||
"S": "niebieski przycisk start",
|
||||
"D": "następne pytanie",
|
||||
"X": "zakończ egzamin",
|
||||
"T / Y": "Tak",
|
||||
"N": "Nie",
|
||||
"S": "blue \"start\" button",
|
||||
"D": "next question",
|
||||
"X": "finish exam",
|
||||
"T / Y": "Yes",
|
||||
"N": "No",
|
||||
"A": "A",
|
||||
"B": "B",
|
||||
"C": "C"
|
||||
},
|
||||
"endExam": "Zakończ egzamin",
|
||||
"examEnd": "Koniec egzaminu",
|
||||
"basicQuestions": "Pytania podstawowe",
|
||||
"advancedQuestions": "Pytania specjalistyczne",
|
||||
"timeToGetAcquaintedWithTheQuestion": "Czas na zapoznanie się z pytaniem",
|
||||
"timeForAnswer": "Czas na udzielenie odpowiedzi",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"theme": "Theme",
|
||||
"light": "Light",
|
||||
"dark": "Dark",
|
||||
"auto": "Auto",
|
||||
"endExam": "Finish exam",
|
||||
"examEnd": "Exam finished",
|
||||
"basicQuestions": "Basic questions",
|
||||
"advancedQuestions": "Advanced questions",
|
||||
"timeToGetAcquaintedWithTheQuestion": "Time to get acquainted with the question",
|
||||
"timeForAnswer": "Time to answer",
|
||||
"startBtn": "START",
|
||||
"second": "s",
|
||||
"nextQuestion": "Następne pytanie",
|
||||
"goBackToHomePage": "Wróć na stronę główną",
|
||||
"points": "Punkty",
|
||||
"pointValue": "Wartość punktowa",
|
||||
"currentCategory": "Aktualna kategoria",
|
||||
"timeToExamEnd": "Czas do końca egzaminu",
|
||||
"result": "Wynik",
|
||||
"startAgain": "Rozpocznij jeszcze raz",
|
||||
"viewAnswers": "Przejrzyj odpowiedzi",
|
||||
"doYouReallyWantToEndExam": "Czy na pewno chcesz zakończyć egzamin?",
|
||||
"questionWithoutVisual": "Pytanie bez wizualizacji",
|
||||
"categoryWord": "Kategoria",
|
||||
"anAnomalyHasOccured": "Nastąpiła anomalia",
|
||||
"redirectFrom": "Przekierowanie z",
|
||||
"end": "Koniec",
|
||||
"question": "Pytanie",
|
||||
"anAPIErrorOccured": "Wystąpił błąd z API",
|
||||
"positive": "pozytywny",
|
||||
"negative": "negatywny",
|
||||
"theoreticalExam": "Egzamin teorytyczny",
|
||||
"nextQuestion": "Next question",
|
||||
"goBackToHomePage": "Back to homepage",
|
||||
"points": "Points",
|
||||
"pointValue": "Point value",
|
||||
"currentCategory": "Current category",
|
||||
"timeToExamEnd": "Time until the exam's finished",
|
||||
"result": "Result",
|
||||
"startAgain": "Start again",
|
||||
"viewAnswers": "View answers",
|
||||
"doYouReallyWantToEndExam": "Are you sure you want to finish the exam?",
|
||||
"questionWithoutVisual": "Question without visualization",
|
||||
"categoryWord": "Category",
|
||||
"anAnomalyHasOccured": "An anomaly has occured",
|
||||
"redirectFrom": "Redirecting from",
|
||||
"end": "End",
|
||||
"question": "Question",
|
||||
"anAPIErrorOccured": "An API error has occured",
|
||||
"positive": "positive",
|
||||
"negative": "negative",
|
||||
"theoreticalExam": "Theoretical exam",
|
||||
"category": {
|
||||
"description": {
|
||||
"A": "motocykle bez ograniczeń mocy",
|
||||
"B": "⭐ samochody osobowe do 3,5 t",
|
||||
"C": "pojazdy ciężarowe powyżej 3,5 t",
|
||||
"D": "autobusy",
|
||||
"T": "ciągniki rolnicze i pojazdy wolnobieżne",
|
||||
"AM": "motorowery i lekkie czterokołowce",
|
||||
"A1": "motocykle do 125 cm³ i 11 kW",
|
||||
"A2": "motocykle do 35 kW",
|
||||
"B1": "czterokołowce (np. quady)",
|
||||
"C1": "pojazdy od 3,5 t do 7,5 t",
|
||||
"D1": "autobusy do 16 pasażerów",
|
||||
"PT": "tramwaje"
|
||||
"A": "motorcycle without limited power",
|
||||
"B": "⭐ passenger cars up to 3,5 t",
|
||||
"C": "trucks above 3,5 t",
|
||||
"D": "buses",
|
||||
"T": "agricultural tractor and low-speed vehicles",
|
||||
"AM": "motobikes and light quadricycles",
|
||||
"A1": "motorcycles up to 125 cm³ and 11 kW",
|
||||
"A2": "motorcycles up to 35 kW",
|
||||
"B1": "quadricycles (e.g quads)",
|
||||
"C1": "vehicles from 3,5 t to 7,5 t",
|
||||
"D1": "buses up to 16 passengers",
|
||||
"PT": "trams"
|
||||
},
|
||||
"age": {
|
||||
"A": "(24 lata; lub 20 lat jeśli masz kat. A2 min. 2 lata)",
|
||||
"B": "(18 lat)",
|
||||
"C": "(21 lat; lub 18 lat z kwalifikacją wstępną)",
|
||||
"D": "(24 lata; lub 21 lat z kwalifikacją wstępną)",
|
||||
"T": "(16 lat)",
|
||||
"AM": "(14 lat)",
|
||||
"A1": "(16 lat)",
|
||||
"A2": "(18 lat)",
|
||||
"B1": "(16 lat)",
|
||||
"C1": "(18 lat)",
|
||||
"D1": "(21 lat; lub 18 lat z kwalifikacją wstępną)",
|
||||
"PT": "(21 lat)"
|
||||
"A": "(24 years; or 20 years if you've had category A2 for at least 2 years)",
|
||||
"B": "(18 years)",
|
||||
"C": "(21 years; or 18 years with preliminary qualification)",
|
||||
"D": "(24 years; or 21 years with preliminary qualification)",
|
||||
"T": "(16 years)",
|
||||
"AM": "(14 years)",
|
||||
"A1": "(16 years)",
|
||||
"A2": "(18 years)",
|
||||
"B1": "(16 years)",
|
||||
"C1": "(18 years)",
|
||||
"D1": "(21 years; or 18 years with preliminary qualification)",
|
||||
"PT": "(21 years)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
"B": "B",
|
||||
"C": "C"
|
||||
},
|
||||
"yes": "Tak",
|
||||
"no": "Nie",
|
||||
"theme": "Motyw",
|
||||
"light": "Jasny",
|
||||
"dark": "Ciemny",
|
||||
"auto": "Automatyczny",
|
||||
"endExam": "Zakończ egzamin",
|
||||
"examEnd": "Koniec egzaminu",
|
||||
"basicQuestions": "Pytania podstawowe",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
"B": "B",
|
||||
"C": "C"
|
||||
},
|
||||
"yes": "Tak",
|
||||
"no": "Nie",
|
||||
"theme": "Motyw",
|
||||
"light": "Jasny",
|
||||
"dark": "Ciemny",
|
||||
"auto": "Automatyczny",
|
||||
"endExam": "Zakończ egzamin",
|
||||
"examEnd": "Koniec egzaminu",
|
||||
"basicQuestions": "Pytania podstawowe",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
export default defineNuxtRouteMiddleware(async () => {
|
||||
const examStore = useExamStore();
|
||||
const localePath = useLocalePath();
|
||||
|
||||
if (examStore.end) {
|
||||
return '/result';
|
||||
return localePath('result');
|
||||
}
|
||||
if (examStore.category === '') {
|
||||
return '/anomaly';
|
||||
return localePath('anomaly');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export default defineNuxtRouteMiddleware(async () => {
|
||||
const examStore = useExamStore();
|
||||
const localePath = useLocalePath();
|
||||
if (!examStore.end || examStore.category === '') {
|
||||
return '/anomaly';
|
||||
return localePath('anomaly');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const advancedStore = useAdvancedStore();
|
|||
<br />
|
||||
{{ $t('advancedQuestions') }}:
|
||||
<code class="text-xs">{{ advancedStore.advanced }}</code> <br />
|
||||
<NuxtLink to="/" class="btn btn-primary">
|
||||
<NuxtLink :to="$localePath('index')" class="btn btn-primary">
|
||||
{{ $t('goBackToHomePage') }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ function next() {
|
|||
}
|
||||
}
|
||||
|
||||
const localePath = useLocalePath();
|
||||
|
||||
function endExam() {
|
||||
loading.value = true;
|
||||
while (ending.value == false) {
|
||||
|
|
@ -200,9 +202,9 @@ function endExam() {
|
|||
advancedStore.advanced == result.value.advanced &&
|
||||
examStore.end
|
||||
) {
|
||||
return navigateTo(`/result`, { replace: true });
|
||||
return navigateTo(localePath(`result`), { replace: true });
|
||||
} else {
|
||||
return navigateTo(`/anomaly`);
|
||||
return navigateTo(localePath(`anomaly`));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ const loading = ref(false);
|
|||
const examStore = useExamStore();
|
||||
await callOnce(() => examStore.resetExam(), { mode: 'navigation' });
|
||||
|
||||
const localePath = useLocalePath();
|
||||
|
||||
function setAndGo(category: string) {
|
||||
loading.value = true;
|
||||
examStore.setCategory(category);
|
||||
while (true) {
|
||||
if (examStore.category === category) {
|
||||
return navigateTo('/exam');
|
||||
return navigateTo(localePath('exam'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +33,27 @@ function changeLanguage() {
|
|||
examStore.setLang(langSelect.value);
|
||||
setLocale(langSelect.value as 'pl' | 'en' | 'de' | 'ua');
|
||||
}
|
||||
|
||||
const dark = ref(false);
|
||||
|
||||
function getTheme() {
|
||||
if (
|
||||
window.matchMedia &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
) {
|
||||
dark.value = true;
|
||||
} else {
|
||||
dark.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(getTheme);
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
function themeAuto() {
|
||||
themeStore.set('');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -50,8 +73,24 @@ function changeLanguage() {
|
|||
<option value="ua">Ukrainian (Українська)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<label class="flex cursor-pointer gap-2 text-lg items-center">
|
||||
{{ $t('theme') }}: {{ $t('light') }}
|
||||
<input
|
||||
v-model="dark"
|
||||
type="checkbox"
|
||||
value="dark"
|
||||
class="toggle theme-controller"
|
||||
@change="themeStore.set(dark ? 'dark' : 'light')"
|
||||
/>
|
||||
{{ $t('dark') }}
|
||||
</label>
|
||||
<div class="btn btn-soft btn-sm" @click="themeAuto()">
|
||||
{{ $t('auto') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col flex-wrap gap-2 items-start p-4 bg-slate-100 border-1 border-slate-500 rounded-xl w-fit"
|
||||
class="flex flex-col flex-wrap gap-2 items-start p-4 bg-base-300 border-1 border-slate-500 rounded-xl w-fit"
|
||||
>
|
||||
<div
|
||||
v-for="category in categories"
|
||||
|
|
|
|||
|
|
@ -89,16 +89,18 @@ function changeCount(num: number) {
|
|||
}
|
||||
}
|
||||
|
||||
const localePath = useLocalePath();
|
||||
|
||||
async function again() {
|
||||
loading.value = true;
|
||||
await examStore.mildReset();
|
||||
return await navigateTo('/exam');
|
||||
return await navigateTo(localePath('exam'));
|
||||
}
|
||||
|
||||
async function home() {
|
||||
loading.value = true;
|
||||
await examStore.resetExam();
|
||||
return await navigateTo('/');
|
||||
return await navigateTo(localePath('index'));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -117,6 +119,7 @@ async function home() {
|
|||
<div class="col-span-3 flex flex-col">
|
||||
<BarTop :points="question?.weight" :category="examStore.category" />
|
||||
<MediaBox :media-path="question?.media_url" phase="" />
|
||||
<div>
|
||||
<QuestionBasic
|
||||
v-if="now === 'basic'"
|
||||
v-model="answer"
|
||||
|
|
@ -132,6 +135,7 @@ async function home() {
|
|||
class="select-none z-[-1]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<BarRightResult
|
||||
:result="{
|
||||
basic: basicStore.basic,
|
||||
|
|
|
|||
13
store/themeStore.ts
Normal file
13
store/themeStore.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export const useThemeStore = defineStore('themeStore', {
|
||||
state: () => ({
|
||||
theme: '',
|
||||
}),
|
||||
actions: {
|
||||
async set(theme: string) {
|
||||
this.theme = theme;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: piniaPluginPersistedstate.localStorage(),
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue