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

@@ -14,6 +14,7 @@
"dependencies": {
"@fontsource/roboto": "5.2.7",
"@mdi/font": "7.4.47",
"axios": "^1.12.2",
"pinia": "^3.0.3",
"pug": "^3.0.3",
"vue": "^3.5.21",

86
client/pnpm-lock.yaml generated
View File

@@ -14,6 +14,9 @@ importers:
'@mdi/font':
specifier: 7.4.47
version: 7.4.47
axios:
specifier: ^1.12.2
version: 1.12.2
pinia:
specifier: ^3.0.3
version: 3.0.3(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))
@@ -800,6 +803,12 @@ packages:
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
engines: {node: '>=20.19.0'}
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.12.2:
resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==}
babel-walk@3.0.0-canary-5:
resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==}
engines: {node: '>= 10.0.0'}
@@ -897,6 +906,10 @@ packages:
colorjs.io@0.5.2:
resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==}
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
comment-parser@1.4.1:
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
engines: {node: '>= 12.0.0'}
@@ -948,6 +961,10 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
@@ -987,6 +1004,10 @@ packages:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
es-set-tostringtag@2.1.0:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
esbuild@0.25.10:
resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
engines: {node: '>=18'}
@@ -1218,6 +1239,19 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
form-data@4.0.4:
resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==}
engines: {node: '>= 6'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -1440,6 +1474,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1597,6 +1639,9 @@ packages:
promise@7.3.1:
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
pug-attrs@3.0.0:
resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==}
@@ -2793,6 +2838,16 @@ snapshots:
'@babel/parser': 7.28.4
ast-kit: 2.1.3
asynckit@0.4.0: {}
axios@1.12.2:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.4
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
babel-walk@3.0.0-canary-5:
dependencies:
'@babel/types': 7.28.4
@@ -2891,6 +2946,10 @@ snapshots:
colorjs.io@0.5.2: {}
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
comment-parser@1.4.1: {}
concat-map@0.0.1: {}
@@ -2930,6 +2989,8 @@ snapshots:
deep-is@0.1.4: {}
delayed-stream@1.0.0: {}
detect-libc@1.0.3:
optional: true
@@ -2957,6 +3018,13 @@ snapshots:
dependencies:
es-errors: 1.3.0
es-set-tostringtag@2.1.0:
dependencies:
es-errors: 1.3.0
get-intrinsic: 1.3.0
has-tostringtag: 1.0.2
hasown: 2.0.2
esbuild@0.25.10:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.10
@@ -3274,6 +3342,16 @@ snapshots:
flatted@3.3.3: {}
follow-redirects@1.15.11: {}
form-data@4.0.4:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
es-set-tostringtag: 2.1.0
hasown: 2.0.2
mime-types: 2.1.35
fsevents@2.3.3:
optional: true
@@ -3465,6 +3543,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -3613,6 +3697,8 @@ snapshots:
dependencies:
asap: 2.0.6
proxy-from-env@1.1.0: {}
pug-attrs@3.0.0:
dependencies:
constantinople: 4.0.1

View File

@@ -0,0 +1,152 @@
import axios from 'axios'
type SubjectType = 'kanji' | 'vocab' | 'both'
type LearningDirection = 'jp->en' | 'en->jp'
type WritingMode = 'kana' | 'kanji'
interface SubjectOptions {
subject: SubjectType
direction?: LearningDirection
writingMode?: WritingMode
mode?: 'meaning' | 'reading'
writingPractice: boolean
}
interface ReviewItem {
type: 'kanji' | 'vocab'
subject: string
answers: string[]
pronunciation_audios?: string[],
mode: string,
}
let currentReviewItem: any = {}
let currentSubjectOptions: SubjectOptions
/**
* Handles logic for when a subject answer is correct.
*
* @param id - The unique identifier of the subject item.
* @param subjectOptions - Configuration describing what kind of subject
* and learning mode is being used (kanji, vocab, etc.).
* @returns void
*/
const correct = async () => (
await axios.post('/api/v1/review/correct', {
itemId: currentReviewItem.id,
currentSubjectOptions,
}, {
headers: {
'Content-Type': 'application/json',
},
})
)
/**
* Handles logic for when a subject answer is incorrect
*
* @param id - The unique identifier of the subject item.
* @param subjectOptions - Configuration describing what kind of subject
* and learning mode is being used (kanji, vocab, etc.).
* @returns void
*/
const incorrect = async () => {
await axios.post('/api/v1/review/incorrect', {
itemId: currentReviewItem.id,
currentSubjectOptions,
}, {
headers: {
'Content-Type': 'application/json',
},
})
}
/**
* Fetches the next subject from the server
*
* @param subjectOptions - Configuration describing what kind of subject
* and learning mode is being used (kanji, vocab, etc.).
*
* @returns
*/
const nextSubject = async (subjectOptions: SubjectOptions): Promise<ReviewItem | null> => {
const res = await axios.post('/api/v1/review/', { subjectOptions }, {
headers: { 'Content-Type': 'application/json' },
})
const item = res.data
if (!item || item.message === 'No reviews due') return null
const mode: 'writing_practice' | 'jp_en_meaning' | 'jp_en_reading' | 'en_jp_writing' = item._selectedMode
currentReviewItem = item
currentSubjectOptions = { ...subjectOptions, mode }
let subjectText = ''
let answers: string[] = []
let pronunciation_audios: string[] | undefined = item.pronunciation_audios
if (mode === 'writing_practice') {
subjectText = item.meanings?.filter(m => m.primary).map(m => m.meaning).join(', ') || ''
answers = [item.characters]
} else if (mode === 'jp_en_meaning') {
subjectText = item.characters
answers = item.meanings?.filter(m => m.accepted_answer).map(m => m.meaning) || []
} else if (mode === 'jp_en_reading') {
subjectText = item.characters
answers = item.readings?.filter(r => r.accepted_answer).map(r => r.reading) || []
} else if (mode === 'en_jp_writing') {
subjectText = item.meanings?.filter(m => m.primary).map(m => m.meaning).join(', ') || ''
if (subjectOptions.writingMode === 'kanji') {
answers = [item.characters]
} else if (subjectOptions.writingMode === 'kana') {
answers = item.readings?.filter(r => r.accepted_answer).map(r => r.reading) || []
}
}
console.log({
type: item.type,
subject: subjectText,
answers,
pronunciation_audios,
mode,
})
return {
type: item.type,
subject: subjectText,
answers,
pronunciation_audios,
mode,
}
}
const getStatistics = async () => {
const res = await axios.get('/api/v1/review/stats', {
headers: {
'Content-Type': 'application/json',
},
})
return res.data
}
const getSubjectStatistics = async (id: string) => {
const res = await axios.get(`/api/v1/review/stats/${id}`, {
headers: {
'Content-Type': 'application/json',
},
})
return res.data
}
export {
correct,
incorrect,
nextSubject,
getStatistics,
getSubjectStatistics,
}

View File

@@ -1,64 +1,73 @@
<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 Full Trainer
template(v-if="!finished")
v-card-title.text-center.text-h2.font-weight-bold.mb-4
span.text-primary Full Trainer
v-sheet.d-flex.justify-center.align-center.rounded-lg.py-8.px-4.elevation-12(
:style="{backgroundColor: 'var(--color-all)', 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: subjectColor, 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-all)'}"
)
| {{ inputMode === 'kanji' ? 'Kanji' : inputMode === 'writing' ? 'Writing' : inputMode }}
v-card-text.text-center.my-4.rounded-lg(
:style="{ backgroundColor: subjectColor + '20', fontSize: '1.1rem', fontWeight: '500', color: subjectColor }"
)
| {{ 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-6.text-success
span.text-success.text-gradient Well Done!
v-card-text.text-center.text-h5
| Youve completed all cards for this session.
v-row.justify-center.mt-6
v-col(cols="6")
v-btn(color="primary" block @click="quitSession") Return Home
</template>
<script setup lang="ts">
@@ -83,17 +92,36 @@ const subject = ref<any>(null)
const direction = computed(() => route.query.direction)
const options = computed(() => JSON.parse(route.query.options as string))
const finished = ref<boolean>(false)
const subjectType = ref<string>('')
const subjectColor = computed(() => {
if (!subject.value) return 'var(--color-all)'
console.log(subjectType.value)
switch (subjectType.value) {
case 'kanji':
return 'var(--color-kanji)'
case 'vocab':
return 'var(--color-vocab)'
default:
return 'var(--color-all)'
}
})
function createReview() {
if (!reviews.value) return
const next = reviews.value.nextSubject()
if (!next) {
// TODO: Add celebration or summary screen
finished.value = true
return
}
subject.value = next.subject
inputMode.value = next.mode
subjectType.value = next.type
if (direction.value === 'jp->en') {
character.value = subject.value.characters
@@ -128,8 +156,8 @@ function submitAnswer() {
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)
? subject.value.characters.trim() === userAnswer
: subject.value.readings?.some((r: any) => r.accepted_answer && r.reading.trim() === userAnswer))
const getAnswerText = () => {
@@ -294,4 +322,9 @@ $accent-color: hsl(320, 100%, 50%);
box-shadow: 0 0 0 1px $error-color !important;
}
}
v-sheet,
v-card-text {
transition: background-color 0.4s ease, color 0.4s ease;
}
</style>

View File

@@ -155,8 +155,8 @@ const stats = ref<{ kanjiCount: number, vocabCount: number }>({
})
const options = ref({
meaning: true,
writing: true,
writing: false,
meaning: false,
kanji: false,
})
@@ -170,7 +170,13 @@ function _startTraining(type: 'kanji' | 'vocab' | 'all' | 'writing') {
path: '/' + type,
query: {
direction: direction.value,
options: JSON.stringify(options.value),
options: JSON.stringify({
direction: direction.value,
type: type,
writing: options.value.writing,
meaning: options.value.meaning,
kanji: options.value.kanji,
}),
}
})
}

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()

View File

@@ -128,9 +128,8 @@ function submitAnswer() {
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)
? subject.value.characters.trim() === userAnswer
: subject.value.readings?.some((r: any) => r.accepted_answer && r.reading.trim() === userAnswer))
const getAnswerText = () => {
if (direction.value === 'jp->en')