267 lines
5.8 KiB
Vue
267 lines
5.8 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'">
|
|
<SentenceAudio :v="v" lang="ko" ref="vaud" />
|
|
</span>
|
|
<span>{{ " = " }}</span>
|
|
<span :class="isKoFirst ? 'spoiler' : ''"> {{ v.en }} </span>
|
|
</div>
|
|
|
|
<details v-if="example || v.notes" ref="sdetails">
|
|
<summary>
|
|
<SentenceAudio
|
|
v-if="example"
|
|
:v="example"
|
|
lang="ko"
|
|
:spoiler="!isKoFirst"
|
|
ref="saud"
|
|
/>
|
|
</summary>
|
|
<SentenceAudio v-if="example" :v="example" lang="en" />
|
|
<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.replace("_", " ") }}
|
|
</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="isAutoplayAudio"
|
|
/>
|
|
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 SentenceAudio, { IVoc } from "./SentenceAudio.vue";
|
|
|
|
const { fmKey = "vocabulary" } = defineProps<{
|
|
fmKey?: string;
|
|
}>();
|
|
|
|
const vCats = ref<string[]>([]);
|
|
const vChosenCats = ref<string[]>([]);
|
|
const vList = ref<IVoc[]>([]);
|
|
const vIndex = ref(-1);
|
|
|
|
const isAutoplayAudio = ref(true);
|
|
const isKoFirst = ref(true);
|
|
|
|
const sentenceDetails = useTemplateRef("sdetails");
|
|
const saud = useTemplateRef<InstanceType<typeof SentenceAudio>>("saud");
|
|
const vaud = useTemplateRef<InstanceType<typeof SentenceAudio>>("vaud");
|
|
|
|
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;
|
|
|
|
const { frontmatter } = useData();
|
|
const vocabulary = frontmatter.value[fmKey] as 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 (isAutoplayAudio.value) {
|
|
nextTick(() => {
|
|
if (saud.value) {
|
|
saud.value.playAudio();
|
|
} else if (vaud.value && isKoFirst.value) {
|
|
vaud.value.playAudio();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
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>
|