wrong answer

This commit is contained in:
Michael Zhang 2023-06-11 15:08:19 -05:00
parent 7f53fb3d27
commit f87230596d
12 changed files with 427 additions and 79 deletions

View file

@ -58,6 +58,7 @@ pub struct GetKanjiResult {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct KanjiSrsInfo { pub struct KanjiSrsInfo {
id: u32, id: u32,
current_grade: u32,
next_answer_date: EpochMs, next_answer_date: EpochMs,
associated_kanji: String, associated_kanji: String,
} }
@ -145,6 +146,7 @@ pub async fn get_kanji(
}; };
let id = row.get("ID"); let id = row.get("ID");
let current_grade = row.get("CurrentGrade");
let next_answer_date: i64 = row.get("NextAnswerDate"); let next_answer_date: i64 = row.get("NextAnswerDate");
let next_answer_date = Ticks(next_answer_date).epoch_ms(); let next_answer_date = Ticks(next_answer_date).epoch_ms();
@ -152,6 +154,7 @@ pub async fn get_kanji(
associated_kanji.clone(), associated_kanji.clone(),
KanjiSrsInfo { KanjiSrsInfo {
id, id,
current_grade,
next_answer_date, next_answer_date,
associated_kanji, associated_kanji,
}, },

View file

@ -41,7 +41,6 @@ $kanjiCharacterSize: 28px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
direction: ltr; direction: ltr;
} }
@ -62,3 +61,10 @@ $kanjiCharacterSize: 28px;
.search-container { .search-container {
padding: 4px 8px; padding: 4px 8px;
} }
.badges {
display: flex;
flex-direction: row;
gap: 6px;
align-items: flex-start;
}

View file

@ -7,6 +7,7 @@ import { Kanji } from "../types/Kanji";
import { Input, Spinner } from "@chakra-ui/react"; import { Input, Spinner } from "@chakra-ui/react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import SearchBar from "./SearchBar"; import SearchBar from "./SearchBar";
import GradeBadge from "./utils/GradeBadge";
export interface KanjiListProps { export interface KanjiListProps {
kanjiList: Kanji[]; kanjiList: Kanji[];
@ -30,12 +31,13 @@ export function KanjiList({
}, },
[setLoadingCanary], [setLoadingCanary],
); );
// Infinite scroll
useEffect(() => { useEffect(() => {
if (loadingCanary && !isLoadingMore) { if (loadingCanary && !isLoadingMore) {
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
for (const entry of entries) { for (const entry of entries) {
if (entry.isIntersecting) { if (entry.isIntersecting) {
console.log("loading more shit");
loadMoreKanji(); loadMoreKanji();
setIsLoadingMore(true); setIsLoadingMore(true);
} }
@ -62,7 +64,8 @@ export function KanjiList({
{kanji.character} {kanji.character}
</GridItem> </GridItem>
<GridItem>{kanji.meanings[0].meaning}</GridItem> <GridItem>{kanji.meanings[0].meaning}</GridItem>
<GridItem> <GridItem className={styles.badges}>
<GradeBadge grade={kanji.srs_info?.current_grade} />
<Badge>#{kanji.most_used_rank} common</Badge> <Badge>#{kanji.most_used_rank} common</Badge>
</GridItem> </GridItem>
</Grid> </Grid>

View file

@ -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 <Badge colorScheme={colorScheme}>{letter}</Badge>;
}
const badgeMap = new Map<number, [string | JSX.Element, string]>([
[8, [<>&#9733;</>, "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"]],
]);

View file

@ -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<HTMLInputElement>) => {
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 <BaseInput value={value} onChange={onChange} {...props} />;
}

View file

@ -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": { "romajiToKana": {
"4letter": [ "4letter": [
["XTSU", "ッ"], ["XTSU", "ッ"],

View file

@ -1,3 +1,5 @@
// See https://github.com/Doublevil/Houhou-SRS/blob/master/Kanji.Common/Helpers/KanaHelper.cs
import kanadata from "../data/kanadata.json"; import kanadata from "../data/kanadata.json";
/** /**

View file

@ -35,9 +35,10 @@ export function Component() {
} }
const loadMoreKanji = async () => { const loadMoreKanji = async () => {
const result = await invoke<GetKanjiResult>("get_kanji", { options: {skip: kanjiList.length }}); const result = await invoke<GetKanjiResult>("get_kanji", {
console.log("invoked result", result); options: { skip: kanjiList.length, include_srs_info: true },
setKanjiList([...kanjiList, ...result.kanji]) });
setKanjiList([...kanjiList, ...result.kanji]);
}; };
return ( return (
@ -60,4 +61,4 @@ export function Component() {
); );
} }
Component.displayName = "KanjiPane"; Component.displayName = "KanjiPane";

View file

@ -18,5 +18,8 @@
} }
.input-box { .input-box {
text-align: center; }
.incorrect {
background-color: rgb(255, 202, 202) !important;
} }

View file

@ -4,38 +4,20 @@ import {
Input, Input,
InputGroup, InputGroup,
InputLeftElement, InputLeftElement,
InputRightElement,
Progress, Progress,
Spinner, Spinner,
useDisclosure, useDisclosure,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import styles from "./SrsReviewPane.module.scss"; 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 { invoke } from "@tauri-apps/api/tauri";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { ArrowBackIcon, CheckIcon } from "@chakra-ui/icons"; import { ArrowBackIcon } from "@chakra-ui/icons";
import { FormEvent } from "react";
import ConfirmQuitModal from "../components/utils/ConfirmQuitModal"; import ConfirmQuitModal from "../components/utils/ConfirmQuitModal";
import * as _ from "lodash-es"; import * as _ from "lodash-es";
import InputBox from "../components/utils/InputBox"; import { romajiToKana } from "../lib/kanaHelper";
import { ReviewItem, ReviewItemType, SrsEntry } from "../types/Srs";
export interface SrsEntry { import classNames from "classnames";
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[];
}
const batchSize = 10; const batchSize = 10;
@ -58,6 +40,7 @@ export function Component() {
const [anyProgress, setAnyProgress] = useState(false); const [anyProgress, setAnyProgress] = useState(false);
const [startingSize, setStartingSize] = useState<number | null>(null); const [startingSize, setStartingSize] = useState<number | null>(null);
const [currentAnswer, setCurrentAnswer] = useState(""); const [currentAnswer, setCurrentAnswer] = useState("");
const [isIncorrect, setIsIncorrect] = useState(false);
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const navigate = useNavigate(); const navigate = useNavigate();
@ -65,7 +48,6 @@ export function Component() {
if (!reviewQueue) { if (!reviewQueue) {
invoke<SrsEntry[]>("generate_review_batch") invoke<SrsEntry[]>("generate_review_batch")
.then((result) => { .then((result) => {
// setReviewBatch(result);
const newReviews: ReviewItem[] = result.flatMap((srsEntry) => [ const newReviews: ReviewItem[] = result.flatMap((srsEntry) => [
{ {
type: ReviewItemType.MEANING, type: ReviewItemType.MEANING,
@ -89,49 +71,58 @@ export function Component() {
} }
}, [reviewQueue]); }, [reviewQueue]);
if (!reviewQueue) return <Spinner />;
if (reviewQueue.length == 0) return <Done />;
const nextItem = reviewQueue[0];
const possibleAnswers = new Set(nextItem.possibleAnswers);
const formSubmit = (evt: FormEvent) => { const formSubmit = (evt: FormEvent) => {
evt.preventDefault(); evt.preventDefault();
if (!reviewQueue) return; if (!reviewQueue) return;
// Check the answer // Check the answer
if (!possibleAnswers.has(currentAnswer)) {
setIsIncorrect(true);
return;
}
// Set up for next question! // Set up for next question!
setAnyProgress(true); setAnyProgress(true);
setIsIncorrect(false);
setCurrentAnswer(""); setCurrentAnswer("");
const [_, ...rest] = reviewQueue; const [_, ...rest] = reviewQueue;
setReviewQueue(rest); setReviewQueue(rest);
}; };
if (!reviewQueue) return <Spinner />;
if (reviewQueue.length == 0) return <Done />;
const nextItem = reviewQueue[0];
const inputBox = (kanaInput: boolean) => { const inputBox = (kanaInput: boolean) => {
const onChange = (evt: ChangeEvent<HTMLInputElement>) => {
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 ( return (
<InputGroup> <InputGroup>
<InputLeftElement>{kanaInput ? "あ" : "A"}</InputLeftElement> <InputLeftElement>{kanaInput ? "あ" : "A"}</InputLeftElement>
<InputBox <Input
kanaInput={kanaInput}
value={currentAnswer} value={currentAnswer}
setValue={setCurrentAnswer} onChange={onChange}
autoFocus autoFocus
className={styles["input-box"]} className={className}
placeholder="Enter your answer..." placeholder={placeholder}
spellCheck={false} spellCheck={false}
backgroundColor={"white"} backgroundColor={"white"}
/> />
<InputRightElement>
<CheckIcon color="green.500" />
</InputRightElement>
</InputGroup> </InputGroup>
); );
}; };
const renderInside = () => { const renderInside = () => {
console.log("next item", nextItem);
const kanaInput = nextItem.type == ReviewItemType.READING; const kanaInput = nextItem.type == ReviewItemType.READING;
return ( return (

View file

@ -12,6 +12,7 @@ export interface KanjiMeaning {
export interface KanjiSrsInfo { export interface KanjiSrsInfo {
id: number; id: number;
current_grade: number;
next_answer_date: number; next_answer_date: number;
associated_kanji: string; associated_kanji: string;
} }

17
src/types/Srs.ts Normal file
View file

@ -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[];
}