ui changes
This commit is contained in:
parent
b2ff5d8416
commit
35c7c1c722
11 changed files with 316 additions and 71 deletions
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
@ -1569,9 +1569,11 @@ name = "houhou"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64 0.21.2",
|
||||||
"clap",
|
"clap",
|
||||||
"derivative",
|
"derivative",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"flate2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
|
|
@ -23,6 +23,8 @@ clap = { version = "4.3.2", features = ["derive"] }
|
||||||
sqlx = { version = "0.6.3", features = ["runtime-tokio-rustls", "sqlite"] }
|
sqlx = { version = "0.6.3", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||||
tokio = { version = "1.28.2", features = ["full"] }
|
tokio = { version = "1.28.2", features = ["full"] }
|
||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
|
flate2 = "1.0.26"
|
||||||
|
base64 = "0.21.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, io::Read, path::PathBuf};
|
||||||
|
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
use flate2::{read::GzDecoder, Decompress, FlushDecompress};
|
||||||
use sqlx::{sqlite::SqliteRow, Encode, Row, SqlitePool, Type};
|
use sqlx::{sqlite::SqliteRow, Encode, Row, SqlitePool, Type};
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
|
@ -24,6 +26,9 @@ pub struct GetKanjiOptions {
|
||||||
#[derivative(Default(value = "40"))]
|
#[derivative(Default(value = "40"))]
|
||||||
how_many: u32,
|
how_many: u32,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
include_strokes: bool,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
include_srs_info: bool,
|
include_srs_info: bool,
|
||||||
}
|
}
|
||||||
|
@ -38,9 +43,18 @@ fn default_how_many() -> u32 {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Kanji {
|
pub struct Kanji {
|
||||||
character: String,
|
character: String,
|
||||||
most_used_rank: u32,
|
|
||||||
meanings: Vec<KanjiMeaning>,
|
meanings: Vec<KanjiMeaning>,
|
||||||
srs_info: Option<KanjiSrsInfo>,
|
srs_info: Option<KanjiSrsInfo>,
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
grade: u32,
|
||||||
|
jlpt_level: u32,
|
||||||
|
wanikani_level: u32,
|
||||||
|
newspaper_rank: u32,
|
||||||
|
most_used_rank: u32,
|
||||||
|
|
||||||
|
// Base64-encoded utf8
|
||||||
|
strokes: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -79,9 +93,16 @@ pub async fn get_kanji(
|
||||||
let query_string = format!(
|
let query_string = format!(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
|
Kanji.ID as ID,
|
||||||
Character,
|
Character,
|
||||||
KanjiMeaningSet.Meaning,
|
KanjiMeaningSet.Meaning,
|
||||||
|
|
||||||
|
Grade,
|
||||||
MostUsedRank,
|
MostUsedRank,
|
||||||
|
JlptLevel,
|
||||||
|
WkLevel,
|
||||||
|
NewspaperRank,
|
||||||
|
|
||||||
KanjiMeaningSet.ID as KanjiMeaningID
|
KanjiMeaningSet.ID as KanjiMeaningID
|
||||||
FROM (
|
FROM (
|
||||||
SELECT *
|
SELECT *
|
||||||
|
@ -166,9 +187,16 @@ pub async fn get_kanji(
|
||||||
let mut new_vec: Vec<Kanji> = Vec::with_capacity(result.len());
|
let mut new_vec: Vec<Kanji> = Vec::with_capacity(result.len());
|
||||||
let mut last_character: Option<String> = None;
|
let mut last_character: Option<String> = None;
|
||||||
|
|
||||||
|
// collect multiple meanings for a single kanji
|
||||||
for row in result {
|
for row in result {
|
||||||
|
let id: u32 = row.get("ID");
|
||||||
let character: String = row.get("Character");
|
let character: String = row.get("Character");
|
||||||
|
|
||||||
|
let grade = row.get("Grade");
|
||||||
let most_used_rank = row.get("MostUsedRank");
|
let most_used_rank = row.get("MostUsedRank");
|
||||||
|
let wanikani_level = row.get("WkLevel");
|
||||||
|
let jlpt_level = row.get("JlptLevel");
|
||||||
|
let newspaper_rank = row.get("NewspaperRank");
|
||||||
|
|
||||||
let meaning = KanjiMeaning {
|
let meaning = KanjiMeaning {
|
||||||
id: row.get("KanjiMeaningID"),
|
id: row.get("KanjiMeaningID"),
|
||||||
|
@ -190,11 +218,42 @@ pub async fn get_kanji(
|
||||||
} else {
|
} else {
|
||||||
let srs_info = srs_info_map.remove(&character);
|
let srs_info = srs_info_map.remove(&character);
|
||||||
|
|
||||||
|
// Fetch strokes if requested
|
||||||
|
let mut strokes = None;
|
||||||
|
if opts.include_strokes {
|
||||||
|
let row = sqlx::query(
|
||||||
|
r#"
|
||||||
|
SELECT FramesSvg FROM KanjiStrokes WHERE ID = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&kanji_db.0)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let frames: Vec<u8> = row.get("FramesSvg");
|
||||||
|
let mut frames_decompress = Vec::new();
|
||||||
|
|
||||||
|
let mut gz = GzDecoder::new(&*frames);
|
||||||
|
gz.read_to_end(&mut frames_decompress)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let result = general_purpose::STANDARD.encode(&frames_decompress);
|
||||||
|
strokes = Some(result);
|
||||||
|
}
|
||||||
|
|
||||||
new_vec.push(Kanji {
|
new_vec.push(Kanji {
|
||||||
character,
|
character,
|
||||||
most_used_rank,
|
|
||||||
meanings: vec![meaning],
|
meanings: vec![meaning],
|
||||||
|
|
||||||
|
grade,
|
||||||
|
jlpt_level,
|
||||||
|
wanikani_level,
|
||||||
|
newspaper_rank,
|
||||||
|
most_used_rank,
|
||||||
|
|
||||||
srs_info,
|
srs_info,
|
||||||
|
strokes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,71 @@
|
||||||
$kanjiDisplaySize: 80px;
|
$kanjiDisplaySize: 120px;
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topRow {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanjiInfo {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meanings {
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boxes {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
min-width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
border: 1px solid gray;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
.big {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
width: 50%;
|
||||||
|
margin: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.strokes) {
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: rgb(194, 226, 228);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.display {
|
.display {
|
||||||
border: 1px solid rgb(87, 87, 210);
|
border: 1px solid rgb(87, 87, 210);
|
||||||
|
@ -9,3 +76,11 @@ $kanjiDisplaySize: 80px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vocabSection {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { invoke } from "@tauri-apps/api/tauri";
|
import { invoke } from "@tauri-apps/api/tauri";
|
||||||
import { GetKanjiResult } from "../panes/KanjiPane";
|
import { GetKanjiResult } from "../panes/KanjiPane";
|
||||||
import TimeAgo from "react-timeago";
|
|
||||||
import styles from "./KanjiDisplay.module.scss";
|
import styles from "./KanjiDisplay.module.scss";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Button } from "@chakra-ui/button";
|
|
||||||
import { AddIcon } from "@chakra-ui/icons";
|
|
||||||
import SelectOnClick from "./utils/SelectOnClick";
|
import SelectOnClick from "./utils/SelectOnClick";
|
||||||
import { Alert, AlertIcon } from "@chakra-ui/alert";
|
import classNames from "classnames";
|
||||||
import { isValid } from "date-fns";
|
import Strokes from "./Strokes";
|
||||||
|
import SrsPart from "./SrsPart";
|
||||||
|
|
||||||
interface KanjiDisplayProps {
|
interface KanjiDisplayProps {
|
||||||
kanjiCharacter: string;
|
kanjiCharacter: string;
|
||||||
|
@ -20,7 +18,9 @@ export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
||||||
isLoading,
|
isLoading,
|
||||||
mutate,
|
mutate,
|
||||||
} = useSWR(["get_kanji", kanjiCharacter], ([command, character]) =>
|
} = useSWR(["get_kanji", kanjiCharacter], ([command, character]) =>
|
||||||
invoke<GetKanjiResult>(command, { options: { character, include_srs_info: true } }),
|
invoke<GetKanjiResult>(command, {
|
||||||
|
options: { character, include_strokes: true, include_srs_info: true },
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!kanjiResult || !kanjiResult.kanji)
|
if (!kanjiResult || !kanjiResult.kanji)
|
||||||
|
@ -42,27 +42,6 @@ export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
||||||
mutate();
|
mutate();
|
||||||
};
|
};
|
||||||
|
|
||||||
let srsPart = (
|
|
||||||
<Button onClick={addSrsItem} colorScheme="green">
|
|
||||||
<AddIcon /> Add to SRS
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
if (kanji.srs_info) {
|
|
||||||
const nextAnswerDate = new Date(kanji.srs_info.next_answer_date);
|
|
||||||
srsPart = (
|
|
||||||
<Alert status="info">
|
|
||||||
<AlertIcon />
|
|
||||||
<p>You are learning this item!</p>
|
|
||||||
|
|
||||||
{isValid(nextAnswerDate) && (
|
|
||||||
<p>
|
|
||||||
(Due: <TimeAgo date={nextAnswerDate} />)
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<details>
|
<details>
|
||||||
|
@ -70,11 +49,48 @@ export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
||||||
<pre>{JSON.stringify(kanji, null, 2)}</pre>
|
<pre>{JSON.stringify(kanji, null, 2)}</pre>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.topRow}>
|
||||||
<SelectOnClick className={styles.display}>{kanji.character}</SelectOnClick>
|
<SelectOnClick className={styles.display}>{kanji.character}</SelectOnClick>
|
||||||
|
|
||||||
{kanji.meanings.map((m) => m.meaning).join(", ")}
|
<div className={styles.kanjiInfo}>
|
||||||
|
<div className={styles.meanings}>{kanji.meanings.map((m) => m.meaning).join(", ")}</div>
|
||||||
|
|
||||||
<div>{srsPart}</div>
|
<div className={styles.boxes}>
|
||||||
|
{kanji.strokes && (
|
||||||
|
<Strokes
|
||||||
|
strokeData={kanji.strokes}
|
||||||
|
size={72}
|
||||||
|
className={classNames(styles.box, styles.strokes)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.box}>
|
||||||
|
<span className={styles.big}>{kanji.most_used_rank}</span>
|
||||||
|
<span>most used</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.box}>
|
||||||
|
<span className={styles.big}>N{kanji.jlpt_level}</span>
|
||||||
|
<span>JLPT Level</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{kanji.wanikani_level && (
|
||||||
|
<div className={styles.box}>
|
||||||
|
<span className={styles.big}>Level {kanji.wanikani_level}</span>
|
||||||
|
<span>on Wanikani</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SrsPart srsInfo={kanji.srs_info} addSrsItem={addSrsItem} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.vocabSection}>
|
||||||
|
<h2>Related Vocab</h2>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
53
src/components/SrsPart.tsx
Normal file
53
src/components/SrsPart.tsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import buildFormatter from "react-timeago/es6/formatters/buildFormatter";
|
||||||
|
import shortEnStrings from "react-timeago/es6/language-strings/en-short";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { KanjiSrsInfo } from "../lib/kanji";
|
||||||
|
|
||||||
|
import styles from "./KanjiDisplay.module.scss";
|
||||||
|
import ReactTimeago, { Formatter } from "react-timeago";
|
||||||
|
import LevelBadge from "./utils/LevelBadge";
|
||||||
|
import { AddIcon, StarIcon } from "@chakra-ui/icons";
|
||||||
|
|
||||||
|
export interface SrsPartProps {
|
||||||
|
srsInfo?: KanjiSrsInfo;
|
||||||
|
addSrsItem: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SrsPart({ srsInfo, addSrsItem }: SrsPartProps) {
|
||||||
|
if (!srsInfo) {
|
||||||
|
return (
|
||||||
|
<button type="button" className={classNames(styles.box)} onClick={addSrsItem}>
|
||||||
|
<span className={styles.icon}>
|
||||||
|
<AddIcon />
|
||||||
|
</span>
|
||||||
|
<span>Add to SRS</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srsInfo.next_answer_date == null) {
|
||||||
|
return (
|
||||||
|
<div className={classNames(styles.box)}>
|
||||||
|
<span className={styles.icon}>
|
||||||
|
<StarIcon />
|
||||||
|
</span>
|
||||||
|
<span>Learned</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextAnswerDate = new Date(srsInfo.next_answer_date);
|
||||||
|
const formatter: Formatter = (value, unit, suffix, epochMilliseconds, nextFormatter) => {
|
||||||
|
if (epochMilliseconds < Date.now()) return "now";
|
||||||
|
return buildFormatter(shortEnStrings)(value, unit, suffix, epochMilliseconds);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={classNames(styles.box)}>
|
||||||
|
<span className={styles.big}>
|
||||||
|
<ReactTimeago date={nextAnswerDate} formatter={formatter} />
|
||||||
|
</span>
|
||||||
|
<span>In SRS</span>
|
||||||
|
<LevelBadge grade={srsInfo.current_grade} className={styles.badge} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
57
src/components/Strokes.tsx
Normal file
57
src/components/Strokes.tsx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { DetailsHTMLAttributes } from "react";
|
||||||
|
|
||||||
|
const SVG_CELL_SIZE = 109;
|
||||||
|
|
||||||
|
export interface StrokesProps extends DetailsHTMLAttributes<HTMLDivElement> {
|
||||||
|
strokeData: string;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Strokes({ strokeData, size, style, ...props }: StrokesProps) {
|
||||||
|
const decoded = atob(strokeData);
|
||||||
|
|
||||||
|
const svgWidthMatch = decoded.match(/width="(\d+)px"/);
|
||||||
|
if (!svgWidthMatch) return null;
|
||||||
|
const svgWidth = parseInt(svgWidthMatch[1]);
|
||||||
|
console.log("width", svgWidth);
|
||||||
|
|
||||||
|
const numFrames = Math.round(svgWidth / SVG_CELL_SIZE);
|
||||||
|
console.log("numFrames", numFrames);
|
||||||
|
|
||||||
|
const keyframes = new Array(numFrames)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, idx) => {
|
||||||
|
const percent = Math.round(idx * (100 / (numFrames + 1)));
|
||||||
|
const offset = -size * idx;
|
||||||
|
return `${percent}% {
|
||||||
|
background-position: ${offset}px;
|
||||||
|
}`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const encoded = encodeURIComponent(decoded);
|
||||||
|
const frameDuration = 1;
|
||||||
|
|
||||||
|
const bgHorizontal = size * numFrames;
|
||||||
|
const bgVertical = size;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{`@keyframes kanjiStrokes { ${keyframes} }`}</style>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
backgroundImage: `url("data:image/svg+xml,${encoded}")`,
|
||||||
|
backgroundSize: `${bgHorizontal}px ${bgVertical}px`,
|
||||||
|
animationName: "kanjiStrokes",
|
||||||
|
animationDuration: `${frameDuration * numFrames}s`,
|
||||||
|
animationTimingFunction: "step-end",
|
||||||
|
animationIterationCount: "infinite",
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import { Badge } from "@chakra-ui/react";
|
import { Badge, BadgeProps } from "@chakra-ui/react";
|
||||||
import { srsLevels } from "../../lib/srs";
|
import { srsLevels } from "../../lib/srs";
|
||||||
|
|
||||||
export interface LevelBadgeProps {
|
export interface LevelBadgeProps extends BadgeProps {
|
||||||
grade?: number;
|
grade?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LevelBadge({ grade }: LevelBadgeProps) {
|
export default function LevelBadge({ grade, ...props }: LevelBadgeProps) {
|
||||||
if (grade == undefined) return null;
|
if (grade == undefined) return null;
|
||||||
|
|
||||||
const levelInfo = srsLevels.get(grade);
|
const levelInfo = srsLevels.get(grade);
|
||||||
|
@ -14,20 +14,8 @@ export default function LevelBadge({ grade }: LevelBadgeProps) {
|
||||||
const { color, name } = levelInfo;
|
const { color, name } = levelInfo;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge backgroundColor={color} color="white">
|
<Badge backgroundColor={color} color="white" {...props}>
|
||||||
{name}
|
{name}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const badgeMap = new Map<number, [string | JSX.Element, string]>([
|
|
||||||
[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"]],
|
|
||||||
]);
|
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
export interface Kanji {
|
export interface Kanji {
|
||||||
character: string;
|
character: string;
|
||||||
most_used_rank: number;
|
|
||||||
meanings: KanjiMeaning[];
|
meanings: KanjiMeaning[];
|
||||||
|
|
||||||
|
grade: number;
|
||||||
|
jlpt_level: number;
|
||||||
|
wanikani_level?: number;
|
||||||
|
newspaper_rank: number;
|
||||||
|
most_used_rank: number;
|
||||||
|
|
||||||
srs_info?: KanjiSrsInfo;
|
srs_info?: KanjiSrsInfo;
|
||||||
|
strokes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KanjiMeaning {
|
export interface KanjiMeaning {
|
||||||
|
|
|
@ -16,11 +16,7 @@ export interface GetKanjiResult {
|
||||||
|
|
||||||
export function Component() {
|
export function Component() {
|
||||||
const { selectedKanji } = useParams();
|
const { selectedKanji } = useParams();
|
||||||
const {
|
const { data: baseData, error } = useSWR("get_kanji", () =>
|
||||||
data: baseData,
|
|
||||||
error,
|
|
||||||
isLoading,
|
|
||||||
} = useSWR("get_kanji", () =>
|
|
||||||
invoke<GetKanjiResult>("get_kanji", { options: { include_srs_info: true } }),
|
invoke<GetKanjiResult>("get_kanji", { options: { include_srs_info: true } }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -30,19 +30,6 @@ import classNames from "classnames";
|
||||||
|
|
||||||
const batchSize = 10;
|
const batchSize = 10;
|
||||||
|
|
||||||
function Done() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p>oh Shit you're done!!! poggerse</p>
|
|
||||||
<Link to="/">
|
|
||||||
<Button colorScheme="blue">
|
|
||||||
<ArrowBackIcon /> Return
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Component() {
|
export function Component() {
|
||||||
// null = has not started, (.length == 0) = finished
|
// null = has not started, (.length == 0) = finished
|
||||||
const [reviewQueue, setReviewQueue] = useState<ReviewItem[] | null>(null);
|
const [reviewQueue, setReviewQueue] = useState<ReviewItem[] | null>(null);
|
||||||
|
@ -79,7 +66,7 @@ export function Component() {
|
||||||
parent: srsQuestionGroup,
|
parent: srsQuestionGroup,
|
||||||
type: ReviewItemType.READING,
|
type: ReviewItemType.READING,
|
||||||
challenge: srsEntry.associated_kanji,
|
challenge: srsEntry.associated_kanji,
|
||||||
possibleAnswers: srsEntry.readings,
|
possibleAnswers: srsEntry.readings.map((reading) => reading.replaceAll(/\./g, "")),
|
||||||
isCorrect: null,
|
isCorrect: null,
|
||||||
timesRepeated: 0,
|
timesRepeated: 0,
|
||||||
};
|
};
|
||||||
|
@ -101,7 +88,10 @@ export function Component() {
|
||||||
}, [reviewQueue]);
|
}, [reviewQueue]);
|
||||||
|
|
||||||
if (!reviewQueue) return <Spinner />;
|
if (!reviewQueue) return <Spinner />;
|
||||||
if (reviewQueue.length == 0) return <Done />;
|
|
||||||
|
// Done! Go back to the home page
|
||||||
|
if (reviewQueue.length == 0) return navigate("/");
|
||||||
|
|
||||||
const [nextItem, ...restOfQueue] = reviewQueue;
|
const [nextItem, ...restOfQueue] = reviewQueue;
|
||||||
const possibleAnswers = new Set(nextItem.possibleAnswers);
|
const possibleAnswers = new Set(nextItem.possibleAnswers);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue