diff --git a/package-lock.json b/package-lock.json index f502bad..c13e7e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index a967666..bff6698 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src-tauri/src/srs.rs b/src-tauri/src/srs.rs index ee6c7ec..6586f04 100644 --- a/src-tauri/src/srs.rs +++ b/src-tauri/src/srs.rs @@ -124,6 +124,7 @@ fn default_batch_size() -> u32 { pub struct SrsEntry { current_grade: u32, meanings: Vec, + readings: Vec, 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"), } }) diff --git a/src/components/KanjiList.module.scss b/src/components/KanjiList.module.scss index b677847..a4e0d29 100644 --- a/src/components/KanjiList.module.scss +++ b/src/components/KanjiList.module.scss @@ -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; diff --git a/src/components/KanjiList.tsx b/src/components/KanjiList.tsx index 2ae2f87..7906a0d 100644 --- a/src/components/KanjiList.tsx +++ b/src/components/KanjiList.tsx @@ -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 ( - - + + {kanji.character} {kanji.meanings[0].meaning} @@ -72,7 +73,7 @@ export function KanjiList({ return ( <>
- +
diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..1d92348 --- /dev/null +++ b/src/components/SearchBar.tsx @@ -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 ( +
+ + + + {{ idle: , loading: }[status]} + + +
+ ); +} diff --git a/src/lib/ConfirmQuitModal.tsx b/src/lib/ConfirmQuitModal.tsx new file mode 100644 index 0000000..76cd245 --- /dev/null +++ b/src/lib/ConfirmQuitModal.tsx @@ -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 ( + + + + Confirm Quit + + + Are you sure you want to go back? Your current progress into this batch will not be saved. + + + + + + + + + + + ); +} diff --git a/src/panes/SrsReviewPane.tsx b/src/panes/SrsReviewPane.tsx index 51809dd..16f9f8e 100644 --- a/src/panes/SrsReviewPane.tsx +++ b/src/panes/SrsReviewPane.tsx @@ -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(null); + // null = has not started, (.length == 0) = finished + const [reviewQueue, setReviewQueue] = useState(null); + const [anyProgress, setAnyProgress] = useState(false); + const [startingSize, setStartingSize] = useState(null); const [currentAnswer, setCurrentAnswer] = useState(""); const { isOpen, onOpen, onClose } = useDisclosure(); + const navigate = useNavigate(); useEffect(() => { - if (!reviewBatch) { + if (!reviewQueue) { invoke("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 ; + if (!reviewQueue) return ; - if (reviewBatch.length == 0) return ; + if (reviewQueue.length == 0) return ; - const progressValue = startingSize - reviewBatch.length; - const nextItem = reviewBatch[0]; + const nextItem = reviewQueue[0]; + + console.log("next item", nextItem); return ( <> - + {startingSize && ( + + )} -

{nextItem.associated_kanji}

+

{nextItem.challenge}

Debug @@ -117,38 +156,25 @@ export default function SrsReviewPane() { ); }; + const quit = () => { + if (!reviewQueue || !anyProgress) { + return navigate("/"); + } + + onOpen(); + }; + return (
-
{renderInside()}
- - - - Confirm Quit - - - Are you sure you want to go back? Your current progress into this batch will not be - saved. - - - - - - - - - - +
); }