fix srs delay date
This commit is contained in:
parent
f94f0c365a
commit
9438b0aadb
7 changed files with 159 additions and 52 deletions
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
88
src/components/srsReview/InputBox.tsx
Normal file
88
src/components/srsReview/InputBox.tsx
Normal 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>
|
||||
);
|
||||
}
|
7
src/components/srsReview/SrsReview.module.scss
Normal file
7
src/components/srsReview/SrsReview.module.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
:not(.activatedQuestion) > .question {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
background-color: rgb(255, 202, 202) !important;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue