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> {
|
) -> Result<Vec<SrsEntry>, String> {
|
||||||
let opts = options.unwrap_or_default();
|
let opts = options.unwrap_or_default();
|
||||||
|
|
||||||
|
let utc_now = Ticks::now().map_err(|err| err.to_string())?;
|
||||||
let result = sqlx::query(
|
let result = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT * FROM SrsEntrySet
|
SELECT * FROM SrsEntrySet
|
||||||
WHERE AssociatedKanji IS NOT NULL
|
WHERE AssociatedKanji IS NOT NULL
|
||||||
AND CurrentGrade < 8
|
AND CurrentGrade < 8
|
||||||
|
AND NextAnswerDate <= ?
|
||||||
ORDER BY RANDOM()
|
ORDER BY RANDOM()
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
|
.bind(utc_now)
|
||||||
.bind(opts.batch_size)
|
.bind(opts.batch_size)
|
||||||
.fetch_all(&srs_db.0)
|
.fetch_all(&srs_db.0)
|
||||||
.await
|
.await
|
||||||
|
@ -256,6 +259,8 @@ pub async fn update_srs_item(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Kanji.Interface/ViewModels/Partial/Srs/SrsReviewViewModel.cs:600
|
// 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(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
|
@ -263,14 +268,14 @@ pub async fn update_srs_item(
|
||||||
SET
|
SET
|
||||||
SuccessCount = SuccessCount + ?,
|
SuccessCount = SuccessCount + ?,
|
||||||
FailureCount = FailureCount + ?,
|
FailureCount = FailureCount + ?,
|
||||||
NextAnswerDate = NextAnswerDate + ?,
|
NextAnswerDate = ?,
|
||||||
CurrentGrade = ?
|
CurrentGrade = ?
|
||||||
WHERE ID = ?
|
WHERE ID = ?
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(success)
|
.bind(success)
|
||||||
.bind(failure)
|
.bind(failure)
|
||||||
.bind(delay * TICK_MULTIPLIER)
|
.bind(new_answer_date)
|
||||||
.bind(new_grade)
|
.bind(new_grade)
|
||||||
.bind(item_id)
|
.bind(item_id)
|
||||||
.execute(&srs_db.0)
|
.execute(&srs_db.0)
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default function DashboardItemStats({ srsStats }: DashboardItemStatsProps
|
||||||
<div className={styles.level} key={level.name}>
|
<div className={styles.level} key={level.name}>
|
||||||
<h3>{level.name}</h3>
|
<h3>{level.name}</h3>
|
||||||
|
|
||||||
{grades.get(level.value)}
|
{grades.get(level.value) ?? 0}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,9 +38,17 @@ export default function SrsPart({ srsInfo, addSrsItem }: SrsPartProps) {
|
||||||
|
|
||||||
const nextAnswerDate = new Date(srsInfo.next_answer_date);
|
const nextAnswerDate = new Date(srsInfo.next_answer_date);
|
||||||
const formatter: Formatter = (value, unit, suffix, epochMilliseconds, nextFormatter) => {
|
const formatter: Formatter = (value, unit, suffix, epochMilliseconds, nextFormatter) => {
|
||||||
if (epochMilliseconds < Date.now()) return "now";
|
if (epochMilliseconds < Date.now()) return <>now</>;
|
||||||
return buildFormatter(shortEnStrings)(value, unit, suffix, epochMilliseconds);
|
return buildFormatter(shortEnStrings)(
|
||||||
|
value,
|
||||||
|
unit,
|
||||||
|
suffix,
|
||||||
|
epochMilliseconds,
|
||||||
|
nextFormatter,
|
||||||
|
() => Date.now(),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.box)}>
|
<div className={classNames(styles.box)}>
|
||||||
<span className={styles.big}>
|
<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;
|
padding: 64px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.incorrect {
|
.needHelp {
|
||||||
background-color: rgb(255, 202, 202) !important;
|
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,
|
isGroupCorrect,
|
||||||
} from "../lib/srs";
|
} from "../lib/srs";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import InputBox from "../components/srsReview/InputBox";
|
||||||
|
import SelectOnClick from "../components/utils/SelectOnClick";
|
||||||
|
|
||||||
const batchSize = 10;
|
const batchSize = 10;
|
||||||
|
|
||||||
|
@ -90,7 +92,10 @@ export function Component() {
|
||||||
if (!reviewQueue) return <Spinner />;
|
if (!reviewQueue) return <Spinner />;
|
||||||
|
|
||||||
// Done! Go back to the home page
|
// Done! Go back to the home page
|
||||||
if (reviewQueue.length == 0) return navigate("/");
|
if (reviewQueue.length == 0) {
|
||||||
|
navigate("/");
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
const [nextItem, ...restOfQueue] = reviewQueue;
|
const [nextItem, ...restOfQueue] = reviewQueue;
|
||||||
const possibleAnswers = new Set(nextItem.possibleAnswers);
|
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 renderInside = () => {
|
||||||
const kanaInput = nextItem.type == ReviewItemType.READING;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{startingSize && (
|
{startingSize && (
|
||||||
|
@ -192,16 +163,26 @@ export function Component() {
|
||||||
|
|
||||||
<h1 className={styles["test-word"]}>{nextItem.challenge}</h1>
|
<h1 className={styles["test-word"]}>{nextItem.challenge}</h1>
|
||||||
|
|
||||||
<form onSubmit={formSubmit}>
|
<InputBox
|
||||||
{
|
submit={formSubmit}
|
||||||
{
|
type={nextItem.type}
|
||||||
[ReviewItemType.MEANING]: "What is the meaning?",
|
answer={currentAnswer}
|
||||||
[ReviewItemType.READING]: "What is the reading?",
|
setAnswer={setCurrentAnswer}
|
||||||
}[nextItem.type]
|
incorrectTimes={incorrectTimes}
|
||||||
}
|
/>
|
||||||
|
|
||||||
{inputBox(kanaInput)}
|
{incorrectTimes > 0 && (
|
||||||
</form>
|
<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 (
|
return (
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<Container>
|
<Container maxW="3xl">
|
||||||
<Button onClick={quit}>
|
<Button onClick={quit}>
|
||||||
<ArrowBackIcon />
|
<ArrowBackIcon />
|
||||||
Back
|
Back
|
||||||
|
|
Loading…
Reference in a new issue