fix srs delay date

This commit is contained in:
Michael Zhang 2023-06-14 19:07:38 -05:00
parent f94f0c365a
commit 9438b0aadb
7 changed files with 159 additions and 52 deletions

View file

@ -204,15 +204,18 @@ pub async fn generate_review_batch(
) -> Result<Vec<SrsEntry>, String> {
let opts = options.unwrap_or_default();
let utc_now = Ticks::now().map_err(|err| err.to_string())?;
let result = sqlx::query(
r#"
SELECT * FROM SrsEntrySet
WHERE AssociatedKanji IS NOT NULL
AND CurrentGrade < 8
AND NextAnswerDate <= ?
ORDER BY RANDOM()
LIMIT ?
"#,
)
.bind(utc_now)
.bind(opts.batch_size)
.fetch_all(&srs_db.0)
.await
@ -256,6 +259,8 @@ pub async fn update_srs_item(
};
// Kanji.Interface/ViewModels/Partial/Srs/SrsReviewViewModel.cs:600
let utc_now = Ticks::now().map_err(|err| err.to_string())?;
let new_answer_date = utc_now + Duration::from_secs(delay as u64);
sqlx::query(
r#"
@ -263,14 +268,14 @@ pub async fn update_srs_item(
SET
SuccessCount = SuccessCount + ?,
FailureCount = FailureCount + ?,
NextAnswerDate = NextAnswerDate + ?,
NextAnswerDate = ?,
CurrentGrade = ?
WHERE ID = ?
"#,
)
.bind(success)
.bind(failure)
.bind(delay * TICK_MULTIPLIER)
.bind(new_answer_date)
.bind(new_grade)
.bind(item_id)
.execute(&srs_db.0)

View file

@ -39,7 +39,7 @@ export default function DashboardItemStats({ srsStats }: DashboardItemStatsProps
<div className={styles.level} key={level.name}>
<h3>{level.name}</h3>
{grades.get(level.value)}
{grades.get(level.value) ?? 0}
</div>
))}
</div>

View file

@ -38,9 +38,17 @@ export default function SrsPart({ srsInfo, addSrsItem }: SrsPartProps) {
const nextAnswerDate = new Date(srsInfo.next_answer_date);
const formatter: Formatter = (value, unit, suffix, epochMilliseconds, nextFormatter) => {
if (epochMilliseconds < Date.now()) return "now";
return buildFormatter(shortEnStrings)(value, unit, suffix, epochMilliseconds);
if (epochMilliseconds < Date.now()) return <>now</>;
return buildFormatter(shortEnStrings)(
value,
unit,
suffix,
epochMilliseconds,
nextFormatter,
() => Date.now(),
);
};
return (
<div className={classNames(styles.box)}>
<span className={styles.big}>

View file

@ -0,0 +1,88 @@
import { ChangeEvent, FormEvent, useCallback, useEffect, useState } from "react";
import { romajiToKana } from "../../lib/kanaHelper";
import classNames from "classnames";
import { Grid, GridItem, Input, InputGroup, InputLeftElement } from "@chakra-ui/react";
import styles from "./SrsReview.module.scss";
import { ReviewItemType } from "../../lib/srs";
export interface InputBoxProps {
type: ReviewItemType;
answer: string;
setAnswer: (_: string) => void;
incorrectTimes: number;
submit: (_: FormEvent) => Promise<void>;
}
const displayParams: { [key in ReviewItemType]: [string, string] } = {
[ReviewItemType.MEANING]: ["meaning", "A"],
[ReviewItemType.READING]: ["reading", "あ"],
};
export default function InputBox({
type,
answer,
setAnswer,
incorrectTimes,
submit,
}: InputBoxProps) {
const [focusedBox, setFocusedBox] = useState<HTMLInputElement | null>(null);
const kanaInput = type == ReviewItemType.READING;
const placeholder = incorrectTimes == 0 ? "Enter your answer..." : "Nope, try again...";
useEffect(() => {
if (focusedBox) focusedBox.focus();
}, [focusedBox]);
const focusedBoxRef = useCallback(
(node: HTMLInputElement | null) => {
if (node) setFocusedBox(node);
},
[type],
);
return (
<Grid templateColumns="repeat(2, 1fr)" gap="4">
{Object.values(ReviewItemType).map((thisType: ReviewItemType) => {
const [question, indicator] = displayParams[thisType];
const enabled = type == thisType;
const onChange = (evt: ChangeEvent<HTMLInputElement>) => {
if (!enabled) return;
let newValue = evt.target.value;
if (kanaInput) newValue = romajiToKana(newValue) ?? newValue;
setAnswer(newValue);
};
const inputClassName = classNames(
styles.inputBox,
enabled && incorrectTimes > 0 && styles.incorrect,
);
return (
<GridItem className={classNames(enabled && styles.activatedQuestion)} key={thisType}>
<p className={styles.question}>What is the {question}?</p>
<form onSubmit={submit}>
<InputGroup>
<InputLeftElement>{indicator}</InputLeftElement>
<Input
value={enabled ? answer : ""}
onChange={onChange}
autoFocus={enabled}
className={inputClassName}
placeholder={enabled ? placeholder : ""}
spellCheck={false}
backgroundColor={"white"}
disabled={!enabled}
ref={(node) => enabled && focusedBoxRef(node)}
/>
</InputGroup>
</form>
</GridItem>
);
})}
</Grid>
);
}

View file

@ -0,0 +1,7 @@
:not(.activatedQuestion) > .question {
color: rgba(0, 0, 0, 0.5);
}
.incorrect {
background-color: rgb(255, 202, 202) !important;
}

View file

@ -17,6 +17,24 @@
padding: 64px 0;
}
.incorrect {
background-color: rgb(255, 202, 202) !important;
.needHelp {
margin-top: 16px;
summary {
cursor: pointer;
}
}
.possibleAnswers {
padding-left: 12px;
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-start;
}
.possibleAnswer {
border: 1px solid rgba(0, 0, 0, 0.25);
font-size: 1.1em;
padding: 4px 8px;
}

View file

@ -27,6 +27,8 @@ import {
isGroupCorrect,
} from "../lib/srs";
import classNames from "classnames";
import InputBox from "../components/srsReview/InputBox";
import SelectOnClick from "../components/utils/SelectOnClick";
const batchSize = 10;
@ -90,7 +92,10 @@ export function Component() {
if (!reviewQueue) return <Spinner />;
// Done! Go back to the home page
if (reviewQueue.length == 0) return navigate("/");
if (reviewQueue.length == 0) {
navigate("/");
return <></>;
}
const [nextItem, ...restOfQueue] = reviewQueue;
const possibleAnswers = new Set(nextItem.possibleAnswers);
@ -143,41 +148,7 @@ export function Component() {
}
};
const inputBox = (kanaInput: boolean) => {
const onChange = (evt: ChangeEvent<HTMLInputElement>) => {
let newValue = evt.target.value;
if (kanaInput) newValue = romajiToKana(newValue) ?? newValue;
setCurrentAnswer(newValue);
};
const className = classNames(styles["input-box"], incorrectTimes > 0 && styles["incorrect"]);
const placeholder =
{
0: "Enter your answer...",
1: "Wrong! Try again...",
}[incorrectTimes] || `Answer is: ${nextItem.possibleAnswers.join(", ")}`;
return (
<InputGroup>
<InputLeftElement>{kanaInput ? "あ" : "A"}</InputLeftElement>
<Input
value={currentAnswer}
onChange={onChange}
autoFocus
className={className}
placeholder={placeholder}
spellCheck={false}
backgroundColor={"white"}
/>
</InputGroup>
);
};
const renderInside = () => {
const kanaInput = nextItem.type == ReviewItemType.READING;
return (
<>
{startingSize && (
@ -192,16 +163,26 @@ export function Component() {
<h1 className={styles["test-word"]}>{nextItem.challenge}</h1>
<form onSubmit={formSubmit}>
{
{
[ReviewItemType.MEANING]: "What is the meaning?",
[ReviewItemType.READING]: "What is the reading?",
}[nextItem.type]
}
<InputBox
submit={formSubmit}
type={nextItem.type}
answer={currentAnswer}
setAnswer={setCurrentAnswer}
incorrectTimes={incorrectTimes}
/>
{inputBox(kanaInput)}
</form>
{incorrectTimes > 0 && (
<details className={styles.needHelp}>
<summary>Need help?</summary>
<div className={styles.possibleAnswers}>
{[...possibleAnswers].map((answer) => (
<SelectOnClick className={styles.possibleAnswer} key={answer}>
{answer}
</SelectOnClick>
))}
</div>
</details>
)}
</>
);
};
@ -216,7 +197,7 @@ export function Component() {
return (
<main className={styles.main}>
<Container>
<Container maxW="3xl">
<Button onClick={quit}>
<ArrowBackIcon />
Back