Files
ko-grammar/docs/.vitepress/theme/components/VocabQuiz.vue
T

278 lines
5.9 KiB
Vue

<template>
<div>
<nav v-if="v">
<div :class="$style.mark">
<button type="button" name="right" @click="mark(true)">✓</button>
<button
type="button"
name="wrong"
@click="mark(false)"
style="font-size: large"
>
↻
</button>
</div>
<div>
<span :class="isKoFirst ? '' : 'spoiler'">
<span v-if="v.audio">
{{ v.ko }}
<AudioButton :src="v.audio" />
</span>
<TTS v-else>
{{ v.ko }}
</TTS>
</span>
<span>{{ " = " }}</span>
<span :class="isKoFirst ? 'spoiler' : ''"> {{ v.en }} </span>
</div>
<details v-if="example" ref="sdetails">
<summary :class="isKoFirst ? '' : 'spoiler'">
<span v-if="example.audio">
{{ example.ko }}
<AudioButton :src="example.audio" />
</span>
<TTS v-else ref="stts">
{{ example.ko }}
</TTS>
</summary>
<span>{{ example.en }}</span>
<blockquote
v-if="v.notes"
v-html="v.notes"
:class="$style.notes"
></blockquote>
</details>
</nav>
<details :class="$style.options">
<summary>Quiz options</summary>
<nav>
<label v-for="c in vCats" :key="c">
<input type="checkbox" :name="c" checked @change="updateCats(c)" />
{{ c }}
</label>
</nav>
<nav>
<span>
<label role="switch">
<span>KR</span>
<input
type="checkbox"
name="front"
role="switch"
v-model="isKoFirst"
/>
<span role="slider"></span>
<span>EN</span>
</label>
</span>
<label>
<input
type="checkbox"
name="autoplay-audio"
v-model="isSentAutoplayAudio"
/>
autoplay sentence audio
</label>
</nav>
</details>
</div>
</template>
<script setup lang="ts">
import { useData } from "vitepress";
import { computed, nextTick, ref, useTemplateRef, watch } from "vue";
import TTS from "./TTS.vue";
const vCats = ref<string[]>([]);
const vChosenCats = ref<string[]>([]);
const vList = ref<IVoc[]>([]);
const vIndex = ref(-1);
const isSentAutoplayAudio = ref(true);
const isKoFirst = ref(true);
const sentenceDetails = useTemplateRef("sdetails");
const stts = useTemplateRef<InstanceType<typeof TTS>>("stts");
const v = computed(() => vList.value[vIndex.value]);
const example = computed(() => {
if (!v.value.examples?.length) return null;
const ex = v.value.examples;
return ex[Math.floor(Math.random() * ex.length)];
});
const MAX_VOC = 5;
interface IVoc {
ko: string;
audio?: string;
en: string;
usages?: IVoc[];
notes?: string;
examples?: IVoc[];
}
const { frontmatter } = useData();
const { vocabulary } = frontmatter.value as {
vocabulary: Record<string, IVoc[]>;
};
vCats.value = Object.keys(vocabulary);
vChosenCats.value = [...vCats.value];
makeVList();
watch(isKoFirst, () => {
makeVList();
});
function makeVList() {
nextTick(() => {
if (sentenceDetails.value) {
sentenceDetails.value.open = false;
}
});
vList.value = Object.entries(vocabulary)
.filter(([k]) => vChosenCats.value.includes(k))
.flatMap(([, v]) => v);
vList.value = vList.value
.map((s) => ({ s, k: Math.random() }))
.sort((a, b) => a.k - b.k)
.map((a) => a.s);
vIndex.value = Math.floor(
Math.random() * Math.min(MAX_VOC, vList.value.length),
);
}
function mark(isRight: boolean) {
nextTick(() => {
if (sentenceDetails.value) {
sentenceDetails.value.open = false;
}
if (isSentAutoplayAudio.value) {
if (stts.value) {
stts.value.playAudio();
} else if (example.value?.audio) {
new Audio(example.value.audio).play();
}
}
});
if (isRight) {
vList.value = [...vList.value.filter((_, i) => i !== vIndex.value)];
vIndex.value = -1;
if (vList.value.length <= 1) {
makeVList();
return;
}
}
const i = Math.floor(
Math.random() * (Math.min(MAX_VOC, vList.value.length) - 1),
);
if (i >= vIndex.value) {
vIndex.value = i + 1;
} else {
vIndex.value = i;
}
}
function updateCats(cat: string) {
const i = vChosenCats.value.indexOf(cat);
if (i === -1) {
vChosenCats.value = [...vChosenCats.value, cat];
} else {
vChosenCats.value = [...vChosenCats.value.filter((_, j) => j !== i)];
}
makeVList();
}
</script>
<style module>
.mark {
float: right;
> * + * {
margin-inline-start: 1em;
}
}
.notes {
p + p {
margin-top: 0.5em !important;
}
}
.options {
label {
white-space: nowrap;
}
* + label {
margin-inline-start: 0.5em;
}
label[role="switch"] {
--slider-width: 50px;
position: relative;
display: inline-block;
width: calc(var(--slider-width) + 4em);
height: 24px;
input[role="switch"] {
opacity: 0;
width: 0;
height: 0;
}
/* The track */
[role="slider"] {
position: absolute;
cursor: pointer;
background-color: #ccc;
border-radius: 24px;
width: var(--slider-width);
height: 100%;
transition: background-color 0.3s;
+ span {
margin-left: calc(var(--slider-width) + 0.5em);
}
}
/* The circular slider */
[role="slider"]::before {
content: "";
position: absolute;
height: 20px;
width: 20px;
left: 4px;
bottom: 2px;
background-color: rgb(87, 87, 87);
border-radius: 50%;
transition: transform 0.3s;
}
/* Toggled state */
input:not(:checked) {
+ [role="slider"] {
/* background-color: #4caf50; */
background-color: gainsboro;
}
+ [role="slider"]::before {
transform: translateX(23px);
}
}
}
}
</style>