got kanji with ja and en translation working (not both) and all options at random

This commit is contained in:
Rene Kievits
2025-10-26 02:35:30 +01:00
parent fc341e57af
commit 882328c28e
11 changed files with 799 additions and 221 deletions

View File

@@ -1,71 +1,84 @@
<template lang="pug">
v-container.fill-height.d-flex.justify-center.align-center
v-card.pa-12.rounded-lg.elevation-12
v-card-title.text-center.text-h2.font-weight-bold.mb-4
span.text-primary Kanji Trainer
template(v-if="!allDone")
v-card-title.text-center.text-h2.font-weight-bold.mb-4
span.text-primary Kanji Trainer
v-sheet.d-flex.justify-center.align-center.rounded-lg.py-8.px-4.elevation-12(
:style="{backgroundColor: 'var(--color-kanji)', fontSize: '5rem', fontWeight: 'bold', whiteSpace: 'nowrap'}"
)
span {{ character }}
v-sheet.d-flex.justify-center.align-center.rounded-lg.py-8.px-4.elevation-12(
:style="{backgroundColor: 'var(--color-kanji)', fontSize: '5rem', fontWeight: 'bold', whiteSpace: 'nowrap'}"
)
span {{ character }}
v-card-text.text-center.my-4.rounded-lg(
:style="{ backgroundColor: 'hsl(320, 100%, 50%, 0.1)', fontSize: '1.1rem', fontWeight: '500', color: 'var(--color-kanji)'}"
)
| {{ inputMode === 'kanji' ? 'Kanji' : inputMode === 'writing' ? 'Writing' : inputMode }}
v-card-text.text-center.my-4.rounded-lg(
:style="{ backgroundColor: 'hsl(320, 100%, 50%, 0.1)', fontSize: '1.1rem', fontWeight: '500', color: 'var(--color-kanji)'}"
)
| {{ inputMode === 'kanji' ? 'Kanji' : inputMode === 'writing' ? 'Writing' : inputMode }}
transition(name="alert-grow")
v-alert.mb-4(
v-if="resultMessage"
:type="isCorrect ? 'success' : 'error'"
border="top"
variant="outlined"
density="compact"
) {{ resultMessage }}
transition(name="alert-grow")
v-alert.mb-4(
v-if="resultMessage"
:type="isCorrect ? 'success' : 'error'"
border="top"
variant="outlined"
density="compact"
) {{ resultMessage }}
v-row.align-center
v-col(cols="9")
input#answer-input.customInput(
ref="answerInput"
placeholder="Type your answer"
:disabled="isDisabled"
)
v-col(cols="3")
v-btn(
color="primary"
block
@click="submitAnswer"
) Enter
v-row.align-center
v-col(cols="9")
input#answer-input.customInput(
ref="answerInput"
placeholder="Type your answer"
:disabled="isDisabled"
)
v-col(cols="3")
v-btn(
color="primary"
block
@click="submitAnswer"
) Enter
v-row.justify-space-between
v-col(cols="4")
v-btn(
color="error"
variant="flat"
block
@click="quitSession"
) Quit
v-col(cols="4")
v-btn(
color="secondary"
variant="flat"
block
@click="createReview"
) Skip
v-col(cols="4")
v-btn(
color="success"
variant="flat"
block
@click="submitAnswer"
) Resolve
v-row.justify-space-between
v-col(cols="4")
v-btn(
color="error"
variant="flat"
block
@click="quitSession"
) Quit
v-col(cols="4")
v-btn(
color="secondary"
variant="flat"
block
@click="createReview"
) Skip
v-col(cols="4")
v-btn(
color="success"
variant="flat"
block
@click="submitAnswer"
) Resolve
template(v-else)
v-card-title.text-center.text-h2.font-weight-bold.mb-4
span.text-primary 🎉 Congratulations!
v-card-text.text-center
| You have completed all reviews for today!
v-row.justify-center.mt-6
v-col(cols="6")
v-btn(
color="primary"
block
@click="quitSession"
) Back to Home
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, onBeforeUnmount, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import * as wanakana from 'wanakana'
import { Reviews } from '../composables/subject.ts'
const route = useRoute()
const router = useRouter()
@@ -77,76 +90,62 @@ const isDisabled = ref<boolean>(false)
const isWanakanaBound = ref<boolean>(false)
const inputMode = ref<'meaning' | 'writing' | 'kanji'>('meaning')
const resultMessage = ref<string>('')
const reviews = ref<Reviews | null>(null)
const subject = ref<any>(null)
const allDone = ref(false)
const direction = computed(() => route.query.direction)
const options = computed(() => JSON.parse(route.query.options as string))
function createReview() {
if (!reviews.value) return
const optionsCalculated = ref({
subject: options.value.type === 'writing' ? 'kanji' : options.value.type === 'all' ? ['kanji', 'vocab'] : options.value.type,
mode: options.value.writing && options.value.meaning ? ['reading', 'meaning'] : options.value.writing ? 'reading' : options.value.meaning ? 'meaning' : 'reading',
writingPractice: options.value.type === 'writing',
direction: options.value.direction,
writingMode: options.value.kanji ? 'kanji' : 'kana',
})
const next = reviews.value.nextSubject()
import { nextSubject, correct, incorrect } from '../composables/srs.scoring.ts'
async function createReview() {
console.log(optionsCalculated.value.mode)
const next = await nextSubject(optionsCalculated.value)
if (!next) {
// TODO: Add celebration or summary screen
allDone.value = true
character.value = ''
resultMessage.value = '✅ All done!'
return
}
subject.value = next.subject
inputMode.value = next.mode
allDone.value = false
subject.value = next
if (direction.value === 'jp->en') {
character.value = subject.value.characters
} else {
character.value = subject.value.meanings
.filter((m: any) => m.primary)
.map((m: any) => m.meaning)
.join(', ')
}
if (optionsCalculated.value.direction === 'en->jp' && optionsCalculated.value.writingMode === 'kanji')
inputMode.value = 'kanji'
else if (subject.value.mode === 'jp_en_reading' || optionsCalculated.value.direction === 'en->jp')
inputMode.value = 'writing'
else
inputMode.value = 'meaning'
nextTickWrapper()
character.value = subject.value.subject
isDisabled.value = false
resultMessage.value = ''
isCorrect.value = false
nextTickWrapper()
}
function submitAnswer() {
if (isDisabled.value) {
createReview()
return
}
if (!answerInput.value || !subject.value) return
async function submitAnswer() {
if (!answerInput.value || !subject) return
console.log(subject.value)
const userAnswer = answerInput.value.value.trim()
if (direction.value === 'jp->en')
isCorrect.value = inputMode.value === 'writing'
? subject.value.readings.some((r: any) => r.accepted_answer && r.reading.trim() === userAnswer)
: subject.value.meanings.some((m: any) => m.accepted_answer && m.meaning.trim().toLowerCase() === userAnswer.toLowerCase())
else
isCorrect.value = inputMode.value === 'kanji'
? subject.value.characters.trim() === userAnswer
: (subject.value.readings?.length === 0
? subject.value.readings?.some((r: any) => r.accepted_answer && r.reading.trim() === userAnswer)
: subject.value.characters.trim() === userAnswer)
const getAnswerText = () => {
if (direction.value === 'jp->en')
return inputMode.value === 'writing'
? subject.value.readings.filter((r: any) => r.accepted_answer).map((r: any) => r.reading).join(', ')
: subject.value.meanings.filter((m: any) => m.accepted_answer).map((m: any) => m.meaning).join(', ')
else
return inputMode.value === 'kanji'
? subject.value.characters
: subject.value.readings?.length === 0
? subject.value.characters
: subject.value.readings.filter((r: any) => r.accepted_answer).map((r: any) => r.reading).join(', ')
}
isCorrect.value = subject.value.answers.some(a => a.trim().toLowerCase() === userAnswer)
resultMessage.value = isCorrect.value ? 'Correct: ' : 'Wrong: '
resultMessage.value += getAnswerText()
resultMessage.value += subject.value.answers.join(', ')
if (isCorrect.value) {
await correct()
} else {
await incorrect()
}
answerInput.value.value = ''
isDisabled.value = true
@@ -187,14 +186,6 @@ function nextTickWrapper() {
}
onMounted(async () => {
const mode = options.value.meaning && options.value.writing
? 'both'
: options.value.meaning
? 'meaning'
: 'writing'
reviews.value = await Reviews.getInstance()
await reviews.value.setOptions({ type: 'kanji', mode })
createReview()
window.addEventListener('keydown', handleGlobalKey)
nextTickWrapper()