split into meanings / reading

This commit is contained in:
Michael Zhang 2023-06-11 15:08:18 -05:00
parent fb9d068bd0
commit 7ecabbe72f
8 changed files with 183 additions and 48 deletions

28
package-lock.json generated
View file

@ -13,10 +13,12 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@tauri-apps/api": "^1.3.0",
"@types/lodash-es": "^4.17.7",
"classnames": "^2.3.2",
"date-fns": "^2.30.0",
"flowbite": "^1.6.5",
"framer-motion": "^10.12.16",
"lodash-es": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.11.2",
@ -2017,6 +2019,14 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg=="
},
"node_modules/@types/lodash-es": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz",
"integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.mergewith": {
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz",
@ -2949,6 +2959,11 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.mergewith": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
@ -5493,6 +5508,14 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg=="
},
"@types/lodash-es": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz",
"integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==",
"requires": {
"@types/lodash": "*"
}
},
"@types/lodash.mergewith": {
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz",
@ -6197,6 +6220,11 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash.mergewith": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",

View file

@ -19,6 +19,7 @@
"date-fns": "^2.30.0",
"flowbite": "^1.6.5",
"framer-motion": "^10.12.16",
"lodash-es": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.11.2",
@ -29,6 +30,7 @@
"devDependencies": {
"@tauri-apps/cli": "^1.3.1",
"@types/node": "^18.7.10",
"@types/lodash-es": "^4.17.7",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-timeago": "^4.1.3",

View file

@ -124,6 +124,7 @@ fn default_batch_size() -> u32 {
pub struct SrsEntry {
current_grade: u32,
meanings: Vec<String>,
readings: Vec<String>,
associated_kanji: String,
}
@ -153,9 +154,14 @@ pub async fn generate_review_batch(
.map(|row| {
let meanings: String = row.get("Meanings");
let meanings = meanings.split(",").map(|s| s.to_owned()).collect();
let readings: String = row.get("Readings");
let readings = readings.split(",").map(|s| s.to_owned()).collect();
SrsEntry {
current_grade: row.get("CurrentGrade"),
meanings,
readings,
associated_kanji: row.get("AssociatedKanji"),
}
})

View file

@ -1,3 +1,5 @@
$kanjiCharacterSize: 28px;
.kanji-list {
display: inline-flex;
flex-direction: column;
@ -20,6 +22,16 @@
}
}
.kanji-link-character {
font-size: $kanjiCharacterSize;
line-height: $kanjiCharacterSize;
vertical-align: middle;
text-align: center;
display: flex;
align-items: center;
}
.kanji-list-scroll {
direction: rtl;
overflow-y: scroll;

View file

@ -6,6 +6,7 @@ import styles from "./KanjiList.module.scss";
import { Kanji } from "../types/Kanji";
import { Input, Spinner } from "@chakra-ui/react";
import { useCallback, useEffect, useState } from "react";
import SearchBar from "./SearchBar";
export interface KanjiListProps {
kanjiList: Kanji[];
@ -56,8 +57,8 @@ export function KanjiList({
const className = classNames(styles["kanji-link"], active && styles["kanji-link-active"]);
return (
<Link key={kanji.character} className={className} to={`/kanji/${kanji.character}`}>
<Grid templateRows="repeat(2, 1fr)" templateColumns="1fr 3fr">
<GridItem rowSpan={2} style={{ fontSize: "24px", textAlign: "center" }}>
<Grid templateRows="repeat(2, 1fr)" templateColumns="auto 1fr" columnGap={4}>
<GridItem rowSpan={2} className={styles["kanji-link-character"]}>
{kanji.character}
</GridItem>
<GridItem>{kanji.meanings[0].meaning}</GridItem>
@ -72,7 +73,7 @@ export function KanjiList({
return (
<>
<div className={styles["search-container"]}>
<Input autoFocus placeholder="Search..." />
<SearchBar />
</div>
<small className={styles["result-count"]}>

View file

@ -0,0 +1,23 @@
import { SearchIcon } from "@chakra-ui/icons";
import { Input, InputGroup, InputRightElement, Spinner } from "@chakra-ui/react";
import { FormEvent, useState } from "react";
export default function SearchBar() {
const [status, setStatus] = useState("idle");
const onSubmit = (evt: FormEvent) => {
evt.preventDefault();
setStatus("loading");
};
return (
<form onSubmit={onSubmit}>
<InputGroup>
<Input autoFocus placeholder="Search..." />
<InputRightElement>
{{ idle: <SearchIcon />, loading: <Spinner /> }[status]}
</InputRightElement>
</InputGroup>
</form>
);
}

View file

@ -0,0 +1,37 @@
import {
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from "@chakra-ui/react";
import { Link } from "react-router-dom";
export default function ConfirmQuitModal({ isOpen, onClose }) {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Confirm Quit</ModalHeader>
<ModalCloseButton />
<ModalBody>
Are you sure you want to go back? Your current progress into this batch will not be saved.
</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={onClose}>
Cancel
</Button>
<Link to="/">
<Button colorScheme="red" mr={3}>
Close
</Button>
</Link>
</ModalFooter>
</ModalContent>
</Modal>
);
}

View file

@ -18,15 +18,31 @@ import {
import styles from "./SrsReviewPane.module.scss";
import { useEffect, useState } from "react";
import { invoke } from "@tauri-apps/api/tauri";
import { Link } from "react-router-dom";
import { Link, useNavigate } from "react-router-dom";
import { ArrowBackIcon, CheckIcon } from "@chakra-ui/icons";
import { FormEvent } from "react";
import ConfirmQuitModal from "../lib/ConfirmQuitModal";
import * as _ from "lodash-es";
export interface SrsEntry {
associated_kanji: string;
current_grade: number;
meanings: string[];
readings: string[];
}
const startingSize = 10;
export enum ReviewItemType {
MEANING,
READING,
}
export interface ReviewItem {
type: ReviewItemType;
challenge: string;
possibleAnswers: string[];
}
const batchSize = 10;
function Done() {
return (
@ -42,54 +58,77 @@ function Done() {
}
export default function SrsReviewPane() {
const [reviewBatch, setReviewBatch] = useState<SrsEntry[] | null>(null);
// null = has not started, (.length == 0) = finished
const [reviewQueue, setReviewQueue] = useState<ReviewItem[] | null>(null);
const [anyProgress, setAnyProgress] = useState(false);
const [startingSize, setStartingSize] = useState<number | null>(null);
const [currentAnswer, setCurrentAnswer] = useState("");
const { isOpen, onOpen, onClose } = useDisclosure();
const navigate = useNavigate();
useEffect(() => {
if (!reviewBatch) {
if (!reviewQueue) {
invoke<SrsEntry[]>("generate_review_batch")
.then((result) => {
console.log(result);
setReviewBatch(result);
// setReviewBatch(result);
const newReviews: ReviewItem[] = result.flatMap((srsEntry) => [
{
type: ReviewItemType.MEANING,
challenge: srsEntry.associated_kanji,
possibleAnswers: srsEntry.meanings,
},
{
type: ReviewItemType.READING,
challenge: srsEntry.associated_kanji,
possibleAnswers: srsEntry.readings,
},
]);
const newReviewsShuffled = _.shuffle(newReviews);
setReviewQueue(newReviewsShuffled);
setStartingSize(newReviews.length);
})
.catch((err) => {
console.error("fuck!", err);
});
}
}, [reviewBatch, setReviewBatch]);
}, [reviewQueue]);
const formSubmit = (evt: FormEvent) => {
evt.preventDefault();
if (reviewBatch == null) return;
if (!reviewQueue) return;
// Check the answer
// Set up for next question!
setAnyProgress(true);
setCurrentAnswer("");
const [_, ...rest] = reviewBatch;
setReviewBatch(rest);
const [_, ...rest] = reviewQueue;
setReviewQueue(rest);
};
const renderInside = () => {
if (!reviewBatch) return <Spinner />;
if (!reviewQueue) return <Spinner />;
if (reviewBatch.length == 0) return <Done />;
if (reviewQueue.length == 0) return <Done />;
const progressValue = startingSize - reviewBatch.length;
const nextItem = reviewBatch[0];
const nextItem = reviewQueue[0];
console.log("next item", nextItem);
return (
<>
{startingSize && (
<Progress
colorScheme="linkedin"
hasStripe
isAnimated
max={startingSize}
value={progressValue}
value={startingSize - reviewQueue.length}
/>
)}
<h1 className={styles["test-word"]}>{nextItem.associated_kanji}</h1>
<h1 className={styles["test-word"]}>{nextItem.challenge}</h1>
<details>
<summary>Debug</summary>
@ -117,38 +156,25 @@ export default function SrsReviewPane() {
);
};
const quit = () => {
if (!reviewQueue || !anyProgress) {
return navigate("/");
}
onOpen();
};
return (
<main className={styles.main}>
<Container>
<Button onClick={onOpen}>
<Button onClick={quit}>
<ArrowBackIcon />
Back
</Button>
<div className={styles.container}>{renderInside()}</div>
</Container>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Confirm Quit</ModalHeader>
<ModalCloseButton />
<ModalBody>
Are you sure you want to go back? Your current progress into this batch will not be
saved.
</ModalBody>
<ModalFooter>
<Button variant="ghost" onClick={onClose}>
Cancel
</Button>
<Link to="/">
<Button colorScheme="red" mr={3}>
Close
</Button>
</Link>
</ModalFooter>
</ModalContent>
</Modal>
<ConfirmQuitModal isOpen={isOpen} onClose={onClose} />
</main>
);
}