review works!
This commit is contained in:
parent
a1907591c6
commit
561cb49fbf
13 changed files with 266 additions and 91 deletions
|
@ -3,7 +3,10 @@ use std::time::Duration;
|
||||||
use sqlx::{Row, SqlitePool};
|
use sqlx::{Row, SqlitePool};
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
use crate::{kanji::KanjiDb, utils::Ticks};
|
use crate::{
|
||||||
|
kanji::KanjiDb,
|
||||||
|
utils::{Ticks, TICK_MULTIPLIER},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct SrsDb(pub SqlitePool);
|
pub struct SrsDb(pub SqlitePool);
|
||||||
|
|
||||||
|
@ -217,6 +220,8 @@ pub async fn generate_review_batch(
|
||||||
pub async fn update_srs_item(
|
pub async fn update_srs_item(
|
||||||
srs_db: State<'_, SrsDb>,
|
srs_db: State<'_, SrsDb>,
|
||||||
item_id: u32,
|
item_id: u32,
|
||||||
|
delay: i64,
|
||||||
|
new_grade: u32,
|
||||||
correct: bool,
|
correct: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let (success, failure) = match correct {
|
let (success, failure) = match correct {
|
||||||
|
@ -224,17 +229,23 @@ pub async fn update_srs_item(
|
||||||
false => (0, 1),
|
false => (0, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Kanji.Interface/ViewModels/Partial/Srs/SrsReviewViewModel.cs:600
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
UPDATE SrsEntrySet
|
UPDATE SrsEntrySet
|
||||||
SET
|
SET
|
||||||
SuccessCount = SuccessCount + ?,
|
SuccessCount = SuccessCount + ?,
|
||||||
FailureCount = FailureCount + ?
|
FailureCount = FailureCount + ?,
|
||||||
|
NextAnswerDate = NextAnswerDate + ?,
|
||||||
|
CurrentGrade = ?
|
||||||
WHERE ID = ?
|
WHERE ID = ?
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(success)
|
.bind(success)
|
||||||
.bind(failure)
|
.bind(failure)
|
||||||
|
.bind(delay * TICK_MULTIPLIER)
|
||||||
|
.bind(new_grade)
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
.execute(&srs_db.0)
|
.execute(&srs_db.0)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -10,6 +10,8 @@ use sqlx::{
|
||||||
Decode, Encode, Sqlite, Type,
|
Decode, Encode, Sqlite, Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const TICK_MULTIPLIER: i64 = 1_000_000_000;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Ticks(pub i64);
|
pub struct Ticks(pub i64);
|
||||||
|
|
||||||
|
@ -37,7 +39,7 @@ impl Into<Duration> for Ticks {
|
||||||
|
|
||||||
impl From<Duration> for Ticks {
|
impl From<Duration> for Ticks {
|
||||||
fn from(value: Duration) -> Self {
|
fn from(value: Duration) -> Self {
|
||||||
Ticks(value.as_secs() as i64 * 1_000_000_000)
|
Ticks(value.as_secs() as i64 * TICK_MULTIPLIER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default function KanjiDisplay({ kanjiCharacter }: KanjiDisplayProps) {
|
||||||
srsPart = (
|
srsPart = (
|
||||||
<Alert status="info">
|
<Alert status="info">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
<p>This character is being tracked in SRS!</p>
|
<p>You are learning this item!</p>
|
||||||
|
|
||||||
{isValid(nextAnswerDate) && (
|
{isValid(nextAnswerDate) && (
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -3,11 +3,11 @@ import classNames from "classnames";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Badge, Grid, GridItem } from "@chakra-ui/layout";
|
import { Badge, Grid, GridItem } from "@chakra-ui/layout";
|
||||||
import styles from "./KanjiList.module.scss";
|
import styles from "./KanjiList.module.scss";
|
||||||
import { Kanji } from "../types/Kanji";
|
import { Kanji } from "../lib/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";
|
import SearchBar from "./SearchBar";
|
||||||
import GradeBadge from "./utils/GradeBadge";
|
import LevelBadge from "./utils/LevelBadge";
|
||||||
|
|
||||||
export interface KanjiListProps {
|
export interface KanjiListProps {
|
||||||
kanjiList: Kanji[];
|
kanjiList: Kanji[];
|
||||||
|
@ -57,6 +57,8 @@ export function KanjiList({
|
||||||
|
|
||||||
const renderKanjiItem = (kanji: Kanji, active: boolean) => {
|
const renderKanjiItem = (kanji: Kanji, active: boolean) => {
|
||||||
const className = classNames(styles["kanji-link"], active && styles["kanji-link-active"]);
|
const className = classNames(styles["kanji-link"], active && styles["kanji-link-active"]);
|
||||||
|
if (kanji.srs_info) console.log("kanji", kanji);
|
||||||
|
|
||||||
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="auto 1fr" columnGap={4}>
|
<Grid templateRows="repeat(2, 1fr)" templateColumns="auto 1fr" columnGap={4}>
|
||||||
|
@ -65,7 +67,7 @@ export function KanjiList({
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem>{kanji.meanings[0].meaning}</GridItem>
|
<GridItem>{kanji.meanings[0].meaning}</GridItem>
|
||||||
<GridItem className={styles.badges}>
|
<GridItem className={styles.badges}>
|
||||||
<GradeBadge grade={kanji.srs_info?.current_grade} />
|
<LevelBadge grade={kanji.srs_info?.current_grade} />
|
||||||
<Badge>#{kanji.most_used_rank} common</Badge>
|
<Badge>#{kanji.most_used_rank} common</Badge>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { Badge } from "@chakra-ui/react";
|
|
||||||
|
|
||||||
export interface GradeBadgeProps {
|
|
||||||
grade?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function GradeBadge({ grade }: GradeBadgeProps) {
|
|
||||||
if (!grade) return null;
|
|
||||||
|
|
||||||
const badgeInfo = badgeMap.get(grade);
|
|
||||||
if (!badgeInfo) return null;
|
|
||||||
|
|
||||||
const [letter, colorScheme] = badgeInfo;
|
|
||||||
|
|
||||||
return <Badge colorScheme={colorScheme}>{letter}</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"]],
|
|
||||||
]);
|
|
33
src/components/utils/LevelBadge.tsx
Normal file
33
src/components/utils/LevelBadge.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { Badge } from "@chakra-ui/react";
|
||||||
|
import { srsLevels } from "../../lib/srs";
|
||||||
|
|
||||||
|
export interface LevelBadgeProps {
|
||||||
|
grade?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LevelBadge({ grade }: LevelBadgeProps) {
|
||||||
|
if (grade == undefined) return null;
|
||||||
|
|
||||||
|
const levelInfo = srsLevels.get(grade);
|
||||||
|
if (!levelInfo) return null;
|
||||||
|
|
||||||
|
const { color, name } = levelInfo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge backgroundColor={color} color="white">
|
||||||
|
{name}
|
||||||
|
</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"]],
|
||||||
|
]);
|
69
src/data/srslevels.json
Normal file
69
src/data/srslevels.json
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"group": "Set In Stone",
|
||||||
|
"name": "★",
|
||||||
|
"value": 8,
|
||||||
|
"delay": null,
|
||||||
|
"color": "#1C1C1C"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"group": "Assimilating",
|
||||||
|
"name": "A2",
|
||||||
|
"value": 7,
|
||||||
|
"delay": 10368000,
|
||||||
|
"color": "#890062"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Assimilating",
|
||||||
|
"name": "A1",
|
||||||
|
"value": 6,
|
||||||
|
"delay": 2592000,
|
||||||
|
"color": "#890062"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"group": "Bolstering",
|
||||||
|
"name": "B2",
|
||||||
|
"value": 5,
|
||||||
|
"delay": 1209600,
|
||||||
|
"color": "#5C1696"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Bolstering",
|
||||||
|
"name": "B1",
|
||||||
|
"value": 4,
|
||||||
|
"delay": 604800,
|
||||||
|
"color": "#5C1696"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"group": "Committing",
|
||||||
|
"name": "C2",
|
||||||
|
"value": 3,
|
||||||
|
"delay": 259200,
|
||||||
|
"color": "#004E8E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Committing",
|
||||||
|
"name": "C1",
|
||||||
|
"value": 2,
|
||||||
|
"delay": 86400,
|
||||||
|
"color": "#004E8E"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"group": "Discovering",
|
||||||
|
"name": "D2",
|
||||||
|
"value": 1,
|
||||||
|
"delay": 28800,
|
||||||
|
"color": "#1A814D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Discovering",
|
||||||
|
"name": "D1",
|
||||||
|
"value": 0,
|
||||||
|
"delay": 14400,
|
||||||
|
"color": "#1A814D"
|
||||||
|
}
|
||||||
|
]
|
57
src/lib/srs.ts
Normal file
57
src/lib/srs.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
export interface SrsEntry {
|
||||||
|
id: number;
|
||||||
|
associated_kanji: string;
|
||||||
|
current_grade: number;
|
||||||
|
meanings: string[];
|
||||||
|
readings: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SrsLevel {
|
||||||
|
group: string;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
/** In seconds */
|
||||||
|
delay: number | null;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
import srsLevelMap from "../data/srslevels.json";
|
||||||
|
export const srsLevels: Map<number, SrsLevel> = new Map(srsLevelMap.map((v) => [v.value, v]));
|
||||||
|
|
||||||
|
export interface SrsQuestionGroup {
|
||||||
|
srsEntry: SrsEntry;
|
||||||
|
questions: { meaningQuestion: ReviewItem; readingQuestion: ReviewItem };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allQuestionsAnswered(group: SrsQuestionGroup): boolean {
|
||||||
|
return Object.values(group.questions).every((v) => v.isCorrect != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isGroupCorrect(group: SrsQuestionGroup): boolean {
|
||||||
|
return Object.values(group.questions).every((v) => v.isCorrect == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function groupUpdatedLevel(group: SrsQuestionGroup): SrsLevel {
|
||||||
|
const grade = group.srsEntry.current_grade;
|
||||||
|
const modifier = isGroupCorrect(group) ? 1 : -1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
srsLevels.get(grade + modifier) ??
|
||||||
|
// Rip type coercion, but current grade should be pretty much set
|
||||||
|
(srsLevels.get(grade) as SrsLevel)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ReviewItemType {
|
||||||
|
MEANING = "MEANING",
|
||||||
|
READING = "READING",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReviewItem {
|
||||||
|
parent: SrsQuestionGroup;
|
||||||
|
type: ReviewItemType;
|
||||||
|
challenge: string;
|
||||||
|
possibleAnswers: string[];
|
||||||
|
isCorrect: boolean | null;
|
||||||
|
timesRepeated: number;
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import { Box, Flex, Grid, GridItem, LinkBox, Stack } from "@chakra-ui/layout";
|
||||||
import styles from "./KanjiPane.module.scss";
|
import styles from "./KanjiPane.module.scss";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import KanjiDisplay from "../components/KanjiDisplay";
|
import KanjiDisplay from "../components/KanjiDisplay";
|
||||||
import { Kanji } from "../types/Kanji";
|
import { Kanji } from "../lib/kanji";
|
||||||
import { KanjiList } from "../components/KanjiList";
|
import { KanjiList } from "../components/KanjiList";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,6 @@
|
||||||
padding: 64px 0;
|
padding: 64px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-box {
|
|
||||||
}
|
|
||||||
|
|
||||||
.incorrect {
|
.incorrect {
|
||||||
background-color: rgb(255, 202, 202) !important;
|
background-color: rgb(255, 202, 202) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
Input,
|
Input,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputLeftElement,
|
InputLeftElement,
|
||||||
|
InputRightElement,
|
||||||
Progress,
|
Progress,
|
||||||
Spinner,
|
Spinner,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
|
@ -11,12 +12,20 @@ import {
|
||||||
import styles from "./SrsReviewPane.module.scss";
|
import styles from "./SrsReviewPane.module.scss";
|
||||||
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
|
import { ChangeEvent, FormEvent, useEffect, useState } from "react";
|
||||||
import { invoke } from "@tauri-apps/api/tauri";
|
import { invoke } from "@tauri-apps/api/tauri";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, ScrollRestoration, useNavigate } from "react-router-dom";
|
||||||
import { ArrowBackIcon } from "@chakra-ui/icons";
|
import { ArrowBackIcon } from "@chakra-ui/icons";
|
||||||
import ConfirmQuitModal from "../components/utils/ConfirmQuitModal";
|
import ConfirmQuitModal from "../components/utils/ConfirmQuitModal";
|
||||||
import * as _ from "lodash-es";
|
import * as _ from "lodash-es";
|
||||||
import { romajiToKana } from "../lib/kanaHelper";
|
import { romajiToKana } from "../lib/kanaHelper";
|
||||||
import { ReviewItem, ReviewItemType, SrsEntry } from "../types/Srs";
|
import {
|
||||||
|
ReviewItem,
|
||||||
|
ReviewItemType,
|
||||||
|
SrsEntry,
|
||||||
|
SrsQuestionGroup,
|
||||||
|
allQuestionsAnswered,
|
||||||
|
groupUpdatedLevel,
|
||||||
|
isGroupCorrect,
|
||||||
|
} from "../lib/srs";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
const batchSize = 10;
|
const batchSize = 10;
|
||||||
|
@ -37,10 +46,12 @@ function Done() {
|
||||||
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);
|
||||||
|
const [completedQueue, setCompletedQueue] = useState<ReviewItem[]>([]);
|
||||||
|
|
||||||
const [anyProgress, setAnyProgress] = useState(false);
|
const [anyProgress, setAnyProgress] = useState(false);
|
||||||
const [startingSize, setStartingSize] = useState<number | null>(null);
|
const [startingSize, setStartingSize] = useState<number | null>(null);
|
||||||
const [currentAnswer, setCurrentAnswer] = useState("");
|
const [currentAnswer, setCurrentAnswer] = useState("");
|
||||||
const [isIncorrect, setIsIncorrect] = useState(false);
|
const [incorrectTimes, setIncorrectTimes] = useState(0);
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
@ -48,20 +59,36 @@ export function Component() {
|
||||||
if (!reviewQueue) {
|
if (!reviewQueue) {
|
||||||
invoke<SrsEntry[]>("generate_review_batch")
|
invoke<SrsEntry[]>("generate_review_batch")
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const newReviews: ReviewItem[] = result.flatMap((srsEntry) => [
|
const newReviews: ReviewItem[] = result.flatMap((srsEntry) => {
|
||||||
{
|
// L @ breaking type safety, but this is mutually recursive too
|
||||||
associatedId: srsEntry.id,
|
const srsQuestionGroup: SrsQuestionGroup = {
|
||||||
|
srsEntry,
|
||||||
|
questions: {},
|
||||||
|
} as SrsQuestionGroup;
|
||||||
|
|
||||||
|
const meaningQuestion: ReviewItem = {
|
||||||
|
parent: srsQuestionGroup,
|
||||||
type: ReviewItemType.MEANING,
|
type: ReviewItemType.MEANING,
|
||||||
challenge: srsEntry.associated_kanji,
|
challenge: srsEntry.associated_kanji,
|
||||||
possibleAnswers: srsEntry.meanings,
|
possibleAnswers: srsEntry.meanings,
|
||||||
},
|
isCorrect: null,
|
||||||
{
|
timesRepeated: 0,
|
||||||
associatedId: srsEntry.id,
|
};
|
||||||
|
|
||||||
|
const readingQuestion: ReviewItem = {
|
||||||
|
parent: srsQuestionGroup,
|
||||||
type: ReviewItemType.READING,
|
type: ReviewItemType.READING,
|
||||||
challenge: srsEntry.associated_kanji,
|
challenge: srsEntry.associated_kanji,
|
||||||
possibleAnswers: srsEntry.readings,
|
possibleAnswers: srsEntry.readings,
|
||||||
},
|
isCorrect: null,
|
||||||
]);
|
timesRepeated: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
srsQuestionGroup.questions.meaningQuestion = meaningQuestion;
|
||||||
|
srsQuestionGroup.questions.readingQuestion = readingQuestion;
|
||||||
|
|
||||||
|
return [meaningQuestion, readingQuestion];
|
||||||
|
});
|
||||||
const newReviewsShuffled = _.shuffle(newReviews);
|
const newReviewsShuffled = _.shuffle(newReviews);
|
||||||
|
|
||||||
setReviewQueue(newReviewsShuffled);
|
setReviewQueue(newReviewsShuffled);
|
||||||
|
@ -75,7 +102,7 @@ export function Component() {
|
||||||
|
|
||||||
if (!reviewQueue) return <Spinner />;
|
if (!reviewQueue) return <Spinner />;
|
||||||
if (reviewQueue.length == 0) return <Done />;
|
if (reviewQueue.length == 0) return <Done />;
|
||||||
const nextItem = reviewQueue[0];
|
const [nextItem, ...restOfQueue] = reviewQueue;
|
||||||
const possibleAnswers = new Set(nextItem.possibleAnswers);
|
const possibleAnswers = new Set(nextItem.possibleAnswers);
|
||||||
|
|
||||||
const formSubmit = async (evt: FormEvent) => {
|
const formSubmit = async (evt: FormEvent) => {
|
||||||
|
@ -83,28 +110,50 @@ export function Component() {
|
||||||
if (!reviewQueue) return;
|
if (!reviewQueue) return;
|
||||||
|
|
||||||
const isCorrect = possibleAnswers.has(currentAnswer);
|
const isCorrect = possibleAnswers.has(currentAnswer);
|
||||||
|
nextItem.isCorrect =
|
||||||
|
new Map([
|
||||||
|
[null, isCorrect],
|
||||||
|
[false, false],
|
||||||
|
[true, isCorrect],
|
||||||
|
]).get(nextItem.isCorrect) ?? isCorrect;
|
||||||
|
|
||||||
// Update the backend
|
// Figure out if we need to update the backend
|
||||||
const params = { itemId: nextItem.associatedId, correct: isCorrect };
|
if (allQuestionsAnswered(nextItem.parent)) {
|
||||||
const result = await invoke("update_srs_item", params);
|
console.log("SHIET");
|
||||||
console.log("result", result);
|
|
||||||
|
|
||||||
// Check the answer
|
const group = nextItem.parent;
|
||||||
|
const newLevel = groupUpdatedLevel(group);
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
itemId: nextItem.parent.srsEntry.id,
|
||||||
|
correct: isGroupCorrect(nextItem.parent),
|
||||||
|
newGrade: newLevel.value,
|
||||||
|
delay: newLevel.delay,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await invoke("update_srs_item", params);
|
||||||
|
console.log("result", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's wrong this time
|
||||||
if (!isCorrect) {
|
if (!isCorrect) {
|
||||||
setIsIncorrect(true);
|
setCurrentAnswer("");
|
||||||
|
setIncorrectTimes(incorrectTimes + 1);
|
||||||
// push it to the back of the queue
|
|
||||||
const lastItem = reviewQueue[reviewQueue.length - 1];
|
|
||||||
if (!_.isEqual(lastItem, nextItem)) setReviewQueue([...reviewQueue, nextItem]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up for next question!
|
// Set up for next question!
|
||||||
setAnyProgress(true);
|
setAnyProgress(true);
|
||||||
setIsIncorrect(false);
|
setIncorrectTimes(0);
|
||||||
setCurrentAnswer("");
|
setCurrentAnswer("");
|
||||||
const [_currentItem, ...rest] = reviewQueue;
|
|
||||||
setReviewQueue(rest);
|
if (nextItem.isCorrect || nextItem.timesRepeated > 0) {
|
||||||
|
setCompletedQueue([...completedQueue, nextItem]);
|
||||||
|
setReviewQueue(restOfQueue);
|
||||||
|
} else {
|
||||||
|
nextItem.timesRepeated++;
|
||||||
|
setReviewQueue([...restOfQueue, nextItem]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputBox = (kanaInput: boolean) => {
|
const inputBox = (kanaInput: boolean) => {
|
||||||
|
@ -115,8 +164,12 @@ export function Component() {
|
||||||
setCurrentAnswer(newValue);
|
setCurrentAnswer(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const className = classNames(styles["input-box"], isIncorrect && styles["incorrect"]);
|
const className = classNames(styles["input-box"], incorrectTimes > 0 && styles["incorrect"]);
|
||||||
const placeholder = isIncorrect ? "Wrong! Try again..." : "Enter your answer...";
|
const placeholder =
|
||||||
|
{
|
||||||
|
0: "Enter your answer...",
|
||||||
|
1: "Wrong! Try again...",
|
||||||
|
}[incorrectTimes] || `Answer is: ${nextItem.possibleAnswers.join(", ")}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
|
@ -140,13 +193,16 @@ export function Component() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<p>{JSON.stringify(completedQueue.map((x) => x.challenge))}</p>
|
||||||
|
<p>{JSON.stringify(reviewQueue.map((x) => x.challenge))}</p>
|
||||||
|
|
||||||
{startingSize && (
|
{startingSize && (
|
||||||
<Progress
|
<Progress
|
||||||
colorScheme="linkedin"
|
colorScheme="linkedin"
|
||||||
hasStripe
|
hasStripe
|
||||||
isAnimated
|
isAnimated
|
||||||
max={startingSize}
|
max={startingSize}
|
||||||
value={startingSize - reviewQueue.length}
|
value={completedQueue.length}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -182,11 +238,6 @@ export function Component() {
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Debug</summary>
|
|
||||||
<pre>{JSON.stringify(nextItem, null, 2)}</pre>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<div className={styles.container}>{renderInside()}</div>
|
<div className={styles.container}>{renderInside()}</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
export interface SrsEntry {
|
|
||||||
id: number;
|
|
||||||
associated_kanji: string;
|
|
||||||
current_grade: number;
|
|
||||||
meanings: string[];
|
|
||||||
readings: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ReviewItemType {
|
|
||||||
MEANING = "MEANING",
|
|
||||||
READING = "READING",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReviewItem {
|
|
||||||
associatedId: number;
|
|
||||||
type: ReviewItemType;
|
|
||||||
challenge: string;
|
|
||||||
possibleAnswers: string[];
|
|
||||||
}
|
|
Loading…
Reference in a new issue