From 0f0190a73d4f256b0beb1f04745552243dc07866 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sat, 10 Jun 2023 22:33:42 -0500 Subject: [PATCH] wrong answer --- src-tauri/src/kanji.rs | 3 + src/components/KanjiList.module.scss | 8 +- src/components/KanjiList.tsx | 7 +- src/components/utils/GradeBadge.tsx | 28 +++ src/components/utils/InputBox.tsx | 32 --- src/data/kanadata.json | 325 +++++++++++++++++++++++++++ src/lib/kanaHelper.ts | 2 + src/panes/KanjiPane.tsx | 9 +- src/panes/SrsReviewPane.module.scss | 5 +- src/panes/SrsReviewPane.tsx | 69 +++--- src/types/Kanji.ts | 1 + src/types/Srs.ts | 17 ++ 12 files changed, 427 insertions(+), 79 deletions(-) create mode 100644 src/components/utils/GradeBadge.tsx delete mode 100644 src/components/utils/InputBox.tsx create mode 100644 src/types/Srs.ts diff --git a/src-tauri/src/kanji.rs b/src-tauri/src/kanji.rs index 7b5cc09..449d3f3 100644 --- a/src-tauri/src/kanji.rs +++ b/src-tauri/src/kanji.rs @@ -58,6 +58,7 @@ pub struct GetKanjiResult { #[derive(Debug, Serialize, Deserialize)] pub struct KanjiSrsInfo { id: u32, + current_grade: u32, next_answer_date: EpochMs, associated_kanji: String, } @@ -145,6 +146,7 @@ pub async fn get_kanji( }; let id = row.get("ID"); + let current_grade = row.get("CurrentGrade"); let next_answer_date: i64 = row.get("NextAnswerDate"); let next_answer_date = Ticks(next_answer_date).epoch_ms(); @@ -152,6 +154,7 @@ pub async fn get_kanji( associated_kanji.clone(), KanjiSrsInfo { id, + current_grade, next_answer_date, associated_kanji, }, diff --git a/src/components/KanjiList.module.scss b/src/components/KanjiList.module.scss index a4e0d29..659b2ca 100644 --- a/src/components/KanjiList.module.scss +++ b/src/components/KanjiList.module.scss @@ -41,7 +41,6 @@ $kanjiCharacterSize: 28px; display: flex; flex-direction: column; gap: 4px; - direction: ltr; } @@ -62,3 +61,10 @@ $kanjiCharacterSize: 28px; .search-container { padding: 4px 8px; } + +.badges { + display: flex; + flex-direction: row; + gap: 6px; + align-items: flex-start; +} diff --git a/src/components/KanjiList.tsx b/src/components/KanjiList.tsx index 7906a0d..95ae388 100644 --- a/src/components/KanjiList.tsx +++ b/src/components/KanjiList.tsx @@ -7,6 +7,7 @@ import { Kanji } from "../types/Kanji"; import { Input, Spinner } from "@chakra-ui/react"; import { useCallback, useEffect, useState } from "react"; import SearchBar from "./SearchBar"; +import GradeBadge from "./utils/GradeBadge"; export interface KanjiListProps { kanjiList: Kanji[]; @@ -30,12 +31,13 @@ export function KanjiList({ }, [setLoadingCanary], ); + + // Infinite scroll useEffect(() => { if (loadingCanary && !isLoadingMore) { const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { - console.log("loading more shit"); loadMoreKanji(); setIsLoadingMore(true); } @@ -62,7 +64,8 @@ export function KanjiList({ {kanji.character} {kanji.meanings[0].meaning} - + + #{kanji.most_used_rank} common diff --git a/src/components/utils/GradeBadge.tsx b/src/components/utils/GradeBadge.tsx new file mode 100644 index 0000000..c55eec3 --- /dev/null +++ b/src/components/utils/GradeBadge.tsx @@ -0,0 +1,28 @@ +import { Badge } from "@chakra-ui/react"; + +export interface GradeBadgeProps { + grade?: number; +} + +export default function GradeBadge({ grade }: GradeBadgeProps) { + if (!grade) return null; + + const badgeInfo = badgeMap.get(grade); + if (!badgeInfo) return null; + + const [letter, colorScheme] = badgeInfo; + + return {letter}; +} + +const badgeMap = new Map([ + [8, [<>★, "green"]], + [7, ["A2", "blue"]], + [6, ["A1", "blue"]], + [5, ["B2", "yellow"]], + [4, ["B1", "yellow"]], + [3, ["C2", "orange"]], + [2, ["C1", "orange"]], + [1, ["D2", "red"]], + [0, ["D1", "red"]], +]); diff --git a/src/components/utils/InputBox.tsx b/src/components/utils/InputBox.tsx deleted file mode 100644 index 4d01857..0000000 --- a/src/components/utils/InputBox.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Input as BaseInput, InputProps as BaseInputProps } from "@chakra-ui/react"; -import { romajiToKana } from "../../lib/kanaHelper"; -import { ChangeEvent } from "react"; - -export interface InputProps extends BaseInputProps { - kanaInput?: boolean; - setValue?: (_: string) => void; - defaultValue?: string; -} - -export default function InputBox({ - kanaInput, - - value, - setValue, - - onChange: baseOnChange, - ...props -}: InputProps) { - const onChange = (evt: ChangeEvent) => { - let newValue = evt.target.value; - console.log("hellosu", kanaInput, newValue, romajiToKana(newValue)); - - if (kanaInput == true) newValue = romajiToKana(newValue) ?? newValue; - - setValue?.(newValue); - - if (baseOnChange) baseOnChange(evt); - }; - - return ; -} diff --git a/src/data/kanadata.json b/src/data/kanadata.json index f1e1a89..57a96ee 100644 --- a/src/data/kanadata.json +++ b/src/data/kanadata.json @@ -171,6 +171,331 @@ ["お", "オ"] ], + "kanaToRomaji": { + "2letter": [ + ["イェ", "YE"], + ["いぇ", "ye"], + ["ウィ", "WI"], + ["うぃ", "wi"], + ["ウェ", "WE"], + ["うぇ", "we"], + ["ヴァ", "WA"], + ["ヴぁ", "wa"], + ["ヴィ", "WI"], + ["ヴぃ", "wi"], + ["ヴェ", "WE"], + ["ヴぇ", "we"], + ["ヴォ", "WO"], + ["ヴぉ", "wo"], + ["ファ", "FA"], + ["ふぁ", "fa"], + ["フィ", "FI"], + ["ふぃ", "fi"], + ["フェ", "FE"], + ["ふぇ", "fe"], + ["フォ", "FO"], + ["ふぉ", "fo"], + ["チャ", "CHA"], + ["ちゃ", "cha"], + ["チュ", "CHU"], + ["ちゅ", "chu"], + ["チェ", "CHE"], + ["ちぇ", "che"], + ["チョ", "CHO"], + ["ちょ", "cho"], + ["シャ", "SHA"], + ["しゃ", "sha"], + ["シュ", "SHU"], + ["しゅ", "shu"], + ["シェ", "SHE"], + ["しぇ", "she"], + ["ショ", "SHO"], + ["しょ", "sho"], + ["リャ", "RYA"], + ["りゃ", "rya"], + ["リィ", "RYI"], + ["りぃ", "ryi"], + ["リュ", "RYU"], + ["りゅ", "ryu"], + ["リェ", "RYE"], + ["りぇ", "rye"], + ["リョ", "RYO"], + ["りょ", "ryo"], + ["ヒャ", "HYA"], + ["ひゃ", "hya"], + ["ヒィ", "HYI"], + ["ひぃ", "hyi"], + ["ヒュ", "HYU"], + ["ひゅ", "hyu"], + ["ヒェ", "HYE"], + ["ひぇ", "hye"], + ["ヒョ", "HYO"], + ["ひょ", "hyo"], + ["ビャ", "BYA"], + ["びゃ", "bya"], + ["ビィ", "BYI"], + ["びぃ", "byi"], + ["ビュ", "BYU"], + ["びゅ", "byu"], + ["ビェ", "BYE"], + ["びぇ", "bye"], + ["ビョ", "BYO"], + ["びょ", "byo"], + ["ピャ", "PYA"], + ["ぴゃ", "pya"], + ["ピィ", "PYI"], + ["ぴぃ", "pyi"], + ["ピュ", "PYU"], + ["ぴゅ", "pyu"], + ["ピェ", "PYE"], + ["ぴぇ", "pye"], + ["ピョ", "PYO"], + ["ぴょ", "pyo"], + ["ミャ", "MYA"], + ["みゃ", "mya"], + ["ミィ", "MYI"], + ["みぃ", "myi"], + ["ミュ", "MYU"], + ["みゅ", "myu"], + ["ミェ", "MYE"], + ["みぇ", "mye"], + ["ミョ", "MYO"], + ["みょ", "myo"], + ["キャ", "KYA"], + ["きゃ", "kya"], + ["キィ", "KYI"], + ["きぃ", "kyi"], + ["キュ", "KYU"], + ["きゅ", "kyu"], + ["キェ", "KYE"], + ["きぇ", "kye"], + ["キョ", "KYO"], + ["きょ", "kyo"], + ["ギャ", "GYA"], + ["ぎゃ", "gya"], + ["ギィ", "GYI"], + ["ぎぃ", "gyi"], + ["ギュ", "GYU"], + ["ぎゅ", "gyu"], + ["ギェ", "GYE"], + ["ぎぇ", "gye"], + ["ギョ", "GYO"], + ["ぎょ", "gyo"], + ["ニャ", "NYA"], + ["にゃ", "nya"], + ["ニィ", "NYI"], + ["にぃ", "nyi"], + ["ニュ", "NYU"], + ["にゅ", "nyu"], + ["ニェ", "NYE"], + ["にぇ", "nye"], + ["ニョ", "NYO"], + ["にょ", "nyo"], + ["ジャ", "JA"], + ["じゃ", "ja"], + ["ジィ", "JI"], + ["じぃ", "ji"], + ["ジュ", "JU"], + ["じゅ", "ju"], + ["ジェ", "JE"], + ["じぇ", "je"], + ["ジョ", "JO"], + ["じょ", "jo"], + ["ヂャ", "DYA"], + ["ぢゃ", "dya"], + ["ヂィ", "DYI"], + ["ぢぃ", "dyi"], + ["ヂュ", "DYU"], + ["ぢゅ", "dyu"], + ["ヂェ", "DYE"], + ["ぢぇ", "dye"], + ["ヂョ", "DYO"], + ["ぢょ", "dyo"] + ], + "1letter": [ + ["ア", "A"], + ["あ", "a"], + ["イ", "I"], + ["い", "i"], + ["ウ", "U"], + ["う", "u"], + ["エ", "E"], + ["え", "e"], + ["オ", "O"], + ["お", "o"], + ["カ", "KA"], + ["か", "ka"], + ["キ", "KI"], + ["き", "ki"], + ["ク", "KU"], + ["く", "ku"], + ["ケ", "KE"], + ["け", "ke"], + ["コ", "KO"], + ["こ", "ko"], + ["サ", "SA"], + ["さ", "sa"], + ["シ", "SI"], + ["し", "si"], + ["ス", "SU"], + ["す", "su"], + ["セ", "SE"], + ["せ", "se"], + ["ソ", "SO"], + ["そ", "so"], + ["タ", "TA"], + ["た", "ta"], + ["テ", "TE"], + ["て", "te"], + ["ト", "TO"], + ["と", "to"], + ["ナ", "NA"], + ["な", "na"], + ["ニ", "NI"], + ["に", "ni"], + ["ヌ", "NU"], + ["ぬ", "nu"], + ["ネ", "NE"], + ["ね", "ne"], + ["ノ", "NO"], + ["の", "no"], + ["ハ", "HA"], + ["は", "ha"], + ["ヒ", "HI"], + ["ひ", "hi"], + ["フ", "HU"], + ["ふ", "hu"], + ["ヘ", "HE"], + ["へ", "he"], + ["ホ", "HO"], + ["ほ", "ho"], + ["マ", "MA"], + ["ま", "ma"], + ["ミ", "MI"], + ["み", "mi"], + ["ム", "MU"], + ["む", "mu"], + ["メ", "ME"], + ["め", "me"], + ["モ", "MO"], + ["も", "mo"], + ["ヤ", "YA"], + ["や", "ya"], + ["ユ", "YU"], + ["ゆ", "yu"], + ["ヨ", "YO"], + ["よ", "yo"], + ["ラ", "RA"], + ["ら", "ra"], + ["リ", "RI"], + ["り", "ri"], + ["ル", "RU"], + ["る", "ru"], + ["レ", "RE"], + ["れ", "re"], + ["ロ", "RO"], + ["ろ", "ro"], + ["ワ", "WA"], + ["わ", "wa"], + ["ヲ", "WO"], + ["を", "wo"], + ["ガ", "GA"], + ["が", "ga"], + ["ギ", "GI"], + ["ぎ", "gi"], + ["グ", "GU"], + ["ぐ", "gu"], + ["ゲ", "GE"], + ["げ", "ge"], + ["ゴ", "GO"], + ["ご", "go"], + ["チ", "CHI"], + ["ち", "chi"], + ["ジ", "JI"], + ["じ", "ji"], + ["ダ", "DA"], + ["だ", "da"], + ["ヂ", "DI"], + ["ぢ", "di"], + ["ヅ", "DU"], + ["づ", "du"], + ["デ", "DE"], + ["で", "de"], + ["ド", "DO"], + ["ど", "do"], + ["バ", "BA"], + ["ば", "ba"], + ["ビ", "BI"], + ["び", "bi"], + ["ブ", "BU"], + ["ぶ", "bu"], + ["ベ", "BE"], + ["べ", "be"], + ["ボ", "BO"], + ["ぼ", "bo"], + ["ヴ", "VU"], + ["ヴ", "vu"], + ["パ", "PA"], + ["ぱ", "pa"], + ["ピ", "PI"], + ["ぴ", "pi"], + ["プ", "PU"], + ["ぷ", "pu"], + ["ペ", "PE"], + ["ぺ", "pe"], + ["ポ", "PO"], + ["ぽ", "po"], + ["ザ", "ZA"], + ["ざ", "za"], + ["ジ", "ZI"], + ["じ", "zi"], + ["ズ", "ZU"], + ["ず", "zu"], + ["ゼ", "ZE"], + ["ぜ", "ze"], + ["ゾ", "ZO"], + ["ぞ", "zo"], + ["フ", "FU"], + ["ふ", "fu"], + ["ァ", "A"], + ["ぁ", "a"], + ["ィ", "I"], + ["ぃ", "i"], + ["ゥ", "U"], + ["ぅ", "u"], + ["ェ", "E"], + ["ぇ", "e"], + ["ォ", "O"], + ["ぉ", "o"], + ["ン", "N"], + ["ん", "n"], + ["シ", "SHI"], + ["し", "shi"], + ["ツ", "TSU"], + ["つ", "tsu"], + ["ヅ", "DU"], + ["づ", "du"], + ["ヂ", "DI"], + ["ぢ", "di"], + ["ヮ", "WA"], + ["ゎ", "wa"], + ["ヵ", "KA"], + ["ヵ", "ka"], + ["ヶ", "KE"], + ["ヶ", "ke"], + ["ャ", "YA"], + ["ゃ", "ya"], + ["ィ", "YI"], + ["ぃ", "yi"], + ["ュ", "YU"], + ["ゅ", "yu"], + ["ェ", "YE"], + ["ぇ", "ye"], + ["ョ", "YO"], + ["ょ", "yo"] + ] + }, + "romajiToKana": { "4letter": [ ["XTSU", "ッ"], diff --git a/src/lib/kanaHelper.ts b/src/lib/kanaHelper.ts index 5624004..f01e983 100644 --- a/src/lib/kanaHelper.ts +++ b/src/lib/kanaHelper.ts @@ -1,3 +1,5 @@ +// See https://github.com/Doublevil/Houhou-SRS/blob/master/Kanji.Common/Helpers/KanaHelper.cs + import kanadata from "../data/kanadata.json"; /** diff --git a/src/panes/KanjiPane.tsx b/src/panes/KanjiPane.tsx index ae1e207..3714376 100644 --- a/src/panes/KanjiPane.tsx +++ b/src/panes/KanjiPane.tsx @@ -35,9 +35,10 @@ export function Component() { } const loadMoreKanji = async () => { - const result = await invoke("get_kanji", { options: {skip: kanjiList.length }}); - console.log("invoked result", result); - setKanjiList([...kanjiList, ...result.kanji]) + const result = await invoke("get_kanji", { + options: { skip: kanjiList.length, include_srs_info: true }, + }); + setKanjiList([...kanjiList, ...result.kanji]); }; return ( @@ -60,4 +61,4 @@ export function Component() { ); } -Component.displayName = "KanjiPane"; \ No newline at end of file +Component.displayName = "KanjiPane"; diff --git a/src/panes/SrsReviewPane.module.scss b/src/panes/SrsReviewPane.module.scss index e6164fa..e78ac9f 100644 --- a/src/panes/SrsReviewPane.module.scss +++ b/src/panes/SrsReviewPane.module.scss @@ -18,5 +18,8 @@ } .input-box { - text-align: center; +} + +.incorrect { + background-color: rgb(255, 202, 202) !important; } diff --git a/src/panes/SrsReviewPane.tsx b/src/panes/SrsReviewPane.tsx index 6af4986..43e3fe3 100644 --- a/src/panes/SrsReviewPane.tsx +++ b/src/panes/SrsReviewPane.tsx @@ -4,38 +4,20 @@ import { Input, InputGroup, InputLeftElement, - InputRightElement, Progress, Spinner, useDisclosure, } from "@chakra-ui/react"; import styles from "./SrsReviewPane.module.scss"; -import { useEffect, useState } from "react"; +import { ChangeEvent, FormEvent, useEffect, useState } from "react"; import { invoke } from "@tauri-apps/api/tauri"; import { Link, useNavigate } from "react-router-dom"; -import { ArrowBackIcon, CheckIcon } from "@chakra-ui/icons"; -import { FormEvent } from "react"; +import { ArrowBackIcon } from "@chakra-ui/icons"; import ConfirmQuitModal from "../components/utils/ConfirmQuitModal"; import * as _ from "lodash-es"; -import InputBox from "../components/utils/InputBox"; - -export interface SrsEntry { - associated_kanji: string; - current_grade: number; - meanings: string[]; - readings: string[]; -} - -export enum ReviewItemType { - MEANING = "MEANING", - READING = "READING", -} - -export interface ReviewItem { - type: ReviewItemType; - challenge: string; - possibleAnswers: string[]; -} +import { romajiToKana } from "../lib/kanaHelper"; +import { ReviewItem, ReviewItemType, SrsEntry } from "../types/Srs"; +import classNames from "classnames"; const batchSize = 10; @@ -58,6 +40,7 @@ export function Component() { const [anyProgress, setAnyProgress] = useState(false); const [startingSize, setStartingSize] = useState(null); const [currentAnswer, setCurrentAnswer] = useState(""); + const [isIncorrect, setIsIncorrect] = useState(false); const { isOpen, onOpen, onClose } = useDisclosure(); const navigate = useNavigate(); @@ -65,7 +48,6 @@ export function Component() { if (!reviewQueue) { invoke("generate_review_batch") .then((result) => { - // setReviewBatch(result); const newReviews: ReviewItem[] = result.flatMap((srsEntry) => [ { type: ReviewItemType.MEANING, @@ -89,49 +71,58 @@ export function Component() { } }, [reviewQueue]); + if (!reviewQueue) return ; + if (reviewQueue.length == 0) return ; + const nextItem = reviewQueue[0]; + const possibleAnswers = new Set(nextItem.possibleAnswers); + const formSubmit = (evt: FormEvent) => { evt.preventDefault(); if (!reviewQueue) return; // Check the answer + if (!possibleAnswers.has(currentAnswer)) { + setIsIncorrect(true); + return; + } // Set up for next question! setAnyProgress(true); + setIsIncorrect(false); setCurrentAnswer(""); const [_, ...rest] = reviewQueue; setReviewQueue(rest); }; - if (!reviewQueue) return ; - if (reviewQueue.length == 0) return ; - const nextItem = reviewQueue[0]; - const inputBox = (kanaInput: boolean) => { + const onChange = (evt: ChangeEvent) => { + let newValue = evt.target.value; + if (kanaInput) newValue = romajiToKana(newValue) ?? newValue; + + setCurrentAnswer(newValue); + }; + + const className = classNames(styles["input-box"], isIncorrect && styles["incorrect"]); + const placeholder = isIncorrect ? "Wrong! Try again..." : "Enter your answer..."; + return ( {kanaInput ? "あ" : "A"} - - - - - ); }; const renderInside = () => { - console.log("next item", nextItem); - const kanaInput = nextItem.type == ReviewItemType.READING; return ( diff --git a/src/types/Kanji.ts b/src/types/Kanji.ts index 954a0d7..88bee2a 100644 --- a/src/types/Kanji.ts +++ b/src/types/Kanji.ts @@ -12,6 +12,7 @@ export interface KanjiMeaning { export interface KanjiSrsInfo { id: number; + current_grade: number; next_answer_date: number; associated_kanji: string; } diff --git a/src/types/Srs.ts b/src/types/Srs.ts new file mode 100644 index 0000000..fe29ab3 --- /dev/null +++ b/src/types/Srs.ts @@ -0,0 +1,17 @@ +export interface SrsEntry { + associated_kanji: string; + current_grade: number; + meanings: string[]; + readings: string[]; +} + +export enum ReviewItemType { + MEANING = "MEANING", + READING = "READING", +} + +export interface ReviewItem { + type: ReviewItemType; + challenge: string; + possibleAnswers: string[]; +}