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/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@tauri-apps/api": "^1.3.0", "@tauri-apps/api": "^1.3.0",
"@types/lodash-es": "^4.17.7",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"flowbite": "^1.6.5", "flowbite": "^1.6.5",
"framer-motion": "^10.12.16", "framer-motion": "^10.12.16",
"lodash-es": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^6.11.2", "react-router": "^6.11.2",
@ -2017,6 +2019,14 @@
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==" "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": { "node_modules/@types/lodash.mergewith": {
"version": "4.6.7", "version": "4.6.7",
"resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", "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", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" "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": { "node_modules/lodash.mergewith": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "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", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==" "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": { "@types/lodash.mergewith": {
"version": "4.6.7", "version": "4.6.7",
"resolved": "https://registry.npmjs.org/@types/lodash.mergewith/-/lodash.mergewith-4.6.7.tgz", "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", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" "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": { "lodash.mergewith": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",

View file

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

View file

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

View file

@ -1,3 +1,5 @@
$kanjiCharacterSize: 28px;
.kanji-list { .kanji-list {
display: inline-flex; display: inline-flex;
flex-direction: column; 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 { .kanji-list-scroll {
direction: rtl; direction: rtl;
overflow-y: scroll; overflow-y: scroll;

View file

@ -6,6 +6,7 @@ import styles from "./KanjiList.module.scss";
import { Kanji } from "../types/Kanji"; 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";
export interface KanjiListProps { export interface KanjiListProps {
kanjiList: Kanji[]; kanjiList: Kanji[];
@ -56,8 +57,8 @@ export function KanjiList({
const className = classNames(styles["kanji-link"], active && styles["kanji-link-active"]); const className = classNames(styles["kanji-link"], active && styles["kanji-link-active"]);
return ( return (
<Link key={kanji.character} className={className} to={`/kanji/${kanji.character}`}> <Link key={kanji.character} className={className} to={`/kanji/${kanji.character}`}>
<Grid templateRows="repeat(2, 1fr)" templateColumns="1fr 3fr"> <Grid templateRows="repeat(2, 1fr)" templateColumns="auto 1fr" columnGap={4}>
<GridItem rowSpan={2} style={{ fontSize: "24px", textAlign: "center" }}> <GridItem rowSpan={2} className={styles["kanji-link-character"]}>
{kanji.character} {kanji.character}
</GridItem> </GridItem>
<GridItem>{kanji.meanings[0].meaning}</GridItem> <GridItem>{kanji.meanings[0].meaning}</GridItem>
@ -72,7 +73,7 @@ export function KanjiList({
return ( return (
<> <>
<div className={styles["search-container"]}> <div className={styles["search-container"]}>
<Input autoFocus placeholder="Search..." /> <SearchBar />
</div> </div>
<small className={styles["result-count"]}> <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 styles from "./SrsReviewPane.module.scss";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { invoke } from "@tauri-apps/api/tauri"; 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 { ArrowBackIcon, CheckIcon } from "@chakra-ui/icons";
import { FormEvent } from "react"; import { FormEvent } from "react";
import ConfirmQuitModal from "../lib/ConfirmQuitModal";
import * as _ from "lodash-es";
export interface SrsEntry { export interface SrsEntry {
associated_kanji: string; 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() { function Done() {
return ( return (
@ -42,54 +58,77 @@ function Done() {
} }
export default function SrsReviewPane() { 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 [currentAnswer, setCurrentAnswer] = useState("");
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
if (!reviewBatch) { if (!reviewQueue) {
invoke<SrsEntry[]>("generate_review_batch") invoke<SrsEntry[]>("generate_review_batch")
.then((result) => { .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) => { .catch((err) => {
console.error("fuck!", err); console.error("fuck!", err);
}); });
} }
}, [reviewBatch, setReviewBatch]); }, [reviewQueue]);
const formSubmit = (evt: FormEvent) => { const formSubmit = (evt: FormEvent) => {
evt.preventDefault(); evt.preventDefault();
if (reviewBatch == null) return; if (!reviewQueue) return;
// Check the answer // Check the answer
// Set up for next question! // Set up for next question!
setAnyProgress(true);
setCurrentAnswer(""); setCurrentAnswer("");
const [_, ...rest] = reviewBatch; const [_, ...rest] = reviewQueue;
setReviewBatch(rest); setReviewQueue(rest);
}; };
const renderInside = () => { 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 = reviewQueue[0];
const nextItem = reviewBatch[0];
console.log("next item", nextItem);
return ( return (
<> <>
<Progress {startingSize && (
colorScheme="linkedin" <Progress
hasStripe colorScheme="linkedin"
isAnimated hasStripe
max={startingSize} isAnimated
value={progressValue} max={startingSize}
/> value={startingSize - reviewQueue.length}
/>
)}
<h1 className={styles["test-word"]}>{nextItem.associated_kanji}</h1> <h1 className={styles["test-word"]}>{nextItem.challenge}</h1>
<details> <details>
<summary>Debug</summary> <summary>Debug</summary>
@ -117,38 +156,25 @@ export default function SrsReviewPane() {
); );
}; };
const quit = () => {
if (!reviewQueue || !anyProgress) {
return navigate("/");
}
onOpen();
};
return ( return (
<main className={styles.main}> <main className={styles.main}>
<Container> <Container>
<Button onClick={onOpen}> <Button onClick={quit}>
<ArrowBackIcon /> <ArrowBackIcon />
Back Back
</Button> </Button>
<div className={styles.container}>{renderInside()}</div> <div className={styles.container}>{renderInside()}</div>
</Container> </Container>
<Modal isOpen={isOpen} onClose={onClose}> <ConfirmQuitModal 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>
</main> </main>
); );
} }