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