split into meanings / reading
This commit is contained in:
parent
fb9d068bd0
commit
7ecabbe72f
8 changed files with 183 additions and 48 deletions
28
package-lock.json
generated
28
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"]}>
|
||||
|
|
23
src/components/SearchBar.tsx
Normal file
23
src/components/SearchBar.tsx
Normal 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>
|
||||
);
|
||||
}
|
37
src/lib/ConfirmQuitModal.tsx
Normal file
37
src/lib/ConfirmQuitModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<>
|
||||
<Progress
|
||||
colorScheme="linkedin"
|
||||
hasStripe
|
||||
isAnimated
|
||||
max={startingSize}
|
||||
value={progressValue}
|
||||
/>
|
||||
{startingSize && (
|
||||
<Progress
|
||||
colorScheme="linkedin"
|
||||
hasStripe
|
||||
isAnimated
|
||||
max={startingSize}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue