From a0effd7a2d6b4ecad29568f471baf863b1f1aa9a Mon Sep 17 00:00:00 2001 From: h16nning Date: Thu, 8 Feb 2024 18:30:56 +0100 Subject: [PATCH 1/4] Switch to fsrs Might be still buggy or have placeholder values, not everything tested yet temporary ui to debug --- package-lock.json | 9 + package.json | 1 + ...dModal.module.css => DebugCard.module.css} | 0 .../DebugCardModal/DebugCardModal.tsx | 80 +--- .../DebugCardModal/DebugCardTable.tsx | 107 +++++ src/components/deck/DebugDeckModal.tsx | 22 +- src/components/deck/DeckMenu.tsx | 5 +- src/components/deck/DeckPreview.tsx | 18 +- src/components/deck/DeckView.tsx | 5 +- .../deck/HeroDeckSection/HeroDeckSection.tsx | 29 +- .../FinishedLearningView.tsx | 11 +- .../learning/LearnView/LearnView.tsx | 71 ++-- .../learning/LearnView/LearnViewFooter.tsx | 84 ++-- .../learning/LearnView/LearnViewHeader.tsx | 65 +-- .../LearnViewCurrentCardStateIndicator.tsx | 52 ++- .../RemainingCardsIndicator.module.css | 17 + .../RemainingCardsIndicator.tsx | 28 ++ .../RemainingCardsStats.module.css | 16 - .../RemainingCardsStats.tsx | 26 -- src/logic/CardScheduler.ts | 3 + src/logic/SpacedRepetition.ts | 111 ------ src/logic/card.tsx | 125 +++--- src/logic/learn.ts | 372 ++++++++---------- src/style/StyleProvider.ts | 2 + 24 files changed, 560 insertions(+), 699 deletions(-) rename src/components/DebugCardModal/{DebugCardModal.module.css => DebugCard.module.css} (100%) create mode 100644 src/components/DebugCardModal/DebugCardTable.tsx create mode 100644 src/components/learning/RemainingCardsIndicator/RemainingCardsIndicator.module.css create mode 100644 src/components/learning/RemainingCardsIndicator/RemainingCardsIndicator.tsx delete mode 100644 src/components/learning/RemainingCardsStats/RemainingCardsStats.module.css delete mode 100644 src/components/learning/RemainingCardsStats/RemainingCardsStats.tsx create mode 100644 src/logic/CardScheduler.ts delete mode 100644 src/logic/SpacedRepetition.ts diff --git a/package-lock.json b/package-lock.json index d5d71af..a521490 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "dexie-react-hooks": "^1.1.1", "embla-carousel-react": "^7.0.9", "firebase": "^9.18.0", + "fsrs.js": "^1.2.2", "html-to-text": "^9.0.4", "i18next": "^23.7.11", "react": "^18.2.0", @@ -4319,6 +4320,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fsrs.js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/fsrs.js/-/fsrs.js-1.2.2.tgz", + "integrity": "sha512-LFFPn33/EBbqJoOKmtCfE+Y3LyijQO7ytf/3N6+BlPP/QxI6nlrX6n7IeeKVkjxXVrKxUUvFXTR5K6keSztphg==", + "engines": { + "node": ">=10" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/package.json b/package.json index fa8c791..c597c43 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "dexie-react-hooks": "^1.1.1", "embla-carousel-react": "^7.0.9", "firebase": "^9.18.0", + "fsrs.js": "^1.2.2", "html-to-text": "^9.0.4", "i18next": "^23.7.11", "react": "^18.2.0", diff --git a/src/components/DebugCardModal/DebugCardModal.module.css b/src/components/DebugCardModal/DebugCard.module.css similarity index 100% rename from src/components/DebugCardModal/DebugCardModal.module.css rename to src/components/DebugCardModal/DebugCard.module.css diff --git a/src/components/DebugCardModal/DebugCardModal.tsx b/src/components/DebugCardModal/DebugCardModal.tsx index a842b57..d4444dd 100644 --- a/src/components/DebugCardModal/DebugCardModal.tsx +++ b/src/components/DebugCardModal/DebugCardModal.tsx @@ -10,6 +10,7 @@ import { Space, } from "@mantine/core"; import { Card, CardType } from "../../logic/card"; +import DebugCardTable from "./DebugCardTable"; interface DebugCardModalProps { opened: boolean; @@ -27,84 +28,7 @@ function DebugCardModal({ opened, setOpened, card }: DebugCardModalProps) { title="Debug" > - {card ? ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {card.history.map((repetition) => ( - - - - - - - - - - - - - - ))} - -
Card Type:{card.content.type}
ID:{card.id}
Content:{JSON.stringify(card.content)}
Decks: - {card.deck},{" "} -
DueDate:{card.dueDate?.toLocaleString()}
Model
Interval:{card.model.interval}
Learned:{card.model.learned ? "true" : "false"}
Repetitions:{card.model.repetitions}
Ease Factor:{card.model.easeFactor}
History
Date: - {(typeof repetition.date === "number" - ? new Date(repetition.date) - : repetition.date - ).toLocaleString()} -
Result: {repetition.result}
- ) : ( - - This card could not be found. - - )} + diff --git a/src/components/DebugCardModal/DebugCardTable.tsx b/src/components/DebugCardModal/DebugCardTable.tsx new file mode 100644 index 0000000..66b54a2 --- /dev/null +++ b/src/components/DebugCardModal/DebugCardTable.tsx @@ -0,0 +1,107 @@ +import classes from "./DebugCard.module.css"; +import { Fragment } from "react"; +import { Card, CardType } from "../../logic/card"; +import { Anchor, Space, Text } from "@mantine/core"; + +export default function DebugCardTable({ + card, +}: { card: Card | undefined }) { + return card ? ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {card.history.map((repetition) => ( + + + + + + + + + + + + + + ))} + +
Card Type:{card.content.type}
ID:{card.id}
Content:{JSON.stringify(card.content)}
Decks: + {card.deck},{" "} +
FSRS Model
Due: + {card.model.due.toLocaleTimeString() + + " " + + card.model.due.toDateString()} +
Stability:{card.model.stability}
Difficulty:{card.model.difficulty}
Elapsed Days:{card.model.elapsed_days}
Scheduled Days:{card.model.scheduled_days}
Repetitions:{card.model.reps}
Lapses:{card.model.lapses}
State:{card.model.state}
Last Review:{card.model.last_review.toDateString()}
History
Date: + {(typeof repetition.date === "number" + ? new Date(repetition.date) + : repetition.date + ).toLocaleString()} +
Result: {repetition.result}
+ ) : ( + + This card could not be found. + + ); +} diff --git a/src/components/deck/DebugDeckModal.tsx b/src/components/deck/DebugDeckModal.tsx index 7767ab5..6650730 100644 --- a/src/components/deck/DebugDeckModal.tsx +++ b/src/components/deck/DebugDeckModal.tsx @@ -1,14 +1,14 @@ import React from "react"; import { Anchor, Button, Group, Modal, Stack, Text } from "@mantine/core"; import { Deck } from "../../logic/deck"; -import { Card, CardsStats, CardType } from "../../logic/card"; +import { Card, CardType, useStatesOf } from "../../logic/card"; +import { State } from "fsrs.js"; interface DebugDeckModalProps { opened: boolean; setOpened: Function; deck?: Deck; cards: Card[]; - stats: CardsStats; } function DebugDeckModal({ @@ -16,8 +16,8 @@ function DebugDeckModal({ setOpened, deck, cards, - stats, }: DebugDeckModalProps) { + const states = useStatesOf(cards); return ( - New Cards: - {stats.newCards} + New: + {states[State.New]} - Learning Cards: - {stats.learningCards} + Learning: + {states[State.Learning]} - Due Cards: - {stats.dueCards} + Review: + {states[State.Review]} + + + Relearning: + {states[State.Relearning]}
) : ( diff --git a/src/components/deck/DeckMenu.tsx b/src/components/deck/DeckMenu.tsx index 7253cb0..6410fe8 100644 --- a/src/components/deck/DeckMenu.tsx +++ b/src/components/deck/DeckMenu.tsx @@ -15,7 +15,7 @@ import DangerousConfirmModal from "../custom/DangerousConfirmModal"; import { useNavigate } from "react-router-dom"; import RenameCardModal from "../editcard/RenameCardModal"; import DebugDeckModal from "./DebugDeckModal"; -import { Card, CardsStats, CardType } from "../../logic/card"; +import { Card, CardType } from "../../logic/card"; import { useSetting } from "../../logic/Settings"; import ImportModal from "../settings/importexport/ImportModal"; @@ -24,7 +24,6 @@ interface DeckMenuProps { isDeckReady: boolean; cards?: Card[]; areCardsReady: boolean; - stats: CardsStats; setDeckOptionsOpened: Function; } @@ -34,7 +33,6 @@ function DeckMenu({ setDeckOptionsOpened, cards, areCardsReady, - stats, }: DeckMenuProps) { const navigate = useNavigate(); @@ -137,7 +135,6 @@ function DeckMenu({ diff --git a/src/components/deck/DeckPreview.tsx b/src/components/deck/DeckPreview.tsx index c004329..5e56155 100644 --- a/src/components/deck/DeckPreview.tsx +++ b/src/components/deck/DeckPreview.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Deck } from "../../logic/deck"; import { useNavigate } from "react-router-dom"; import ListButton from "../custom/ListButton/ListButton"; -import { useCardsOf, useStatsOf } from "../../logic/card"; +import { useCardsOf, useSimplifiedStatesOf } from "../../logic/card"; import { useTranslation } from "react-i18next"; type DeckPreviewProps = { @@ -15,7 +15,7 @@ export default function DeckPreview({ deck, i }: DeckPreviewProps) { const navigate = useNavigate(); const [t] = useTranslation(); const [cards] = useCardsOf(deck); - const stats = useStatsOf(cards); + const states = useSimplifiedStatesOf(cards); return ( {deck.name} - {stats.dueCards && stats.dueCards > 0 ? ( + {states.review > 0 ? ( - {t("deck.review-cards-label", { count: stats.dueCards })} + {t("deck.review-cards-label", { count: -1 })} ) : ( <> )} - {stats.newCards && stats.newCards > 0 ? ( + {states.new > 0 ? ( - {t("deck.new-cards-label", { count: stats.newCards })} + {t("deck.new-cards-label", { count: states.new })} ) : ( <> )} - {stats.learningCards && stats.learningCards > 0 ? ( + {states.learning > 0 ? ( - {t("deck.learning-cards-label", { count: stats.learningCards })} + {t("deck.learning-cards-label", { + count: states.learning, + })} ) : ( <> diff --git a/src/components/deck/DeckView.tsx b/src/components/deck/DeckView.tsx index 262e8b5..cb68e20 100644 --- a/src/components/deck/DeckView.tsx +++ b/src/components/deck/DeckView.tsx @@ -8,7 +8,7 @@ import { useDeckFromUrl, useSuperDecks } from "../../logic/deck"; import DeckOptionsModal from "./DeckOptionsModal"; import MissingObject from "../custom/MissingObject"; import SuperDecksBreadcrumbs from "../SuperDecksBreadcrumbs/SuperDecksBreadcrumbs"; -import { useCardsOf, useStatsOf } from "../../logic/card"; +import { useCardsOf } from "../../logic/card"; import HeroDeckSection from "./HeroDeckSection/HeroDeckSection"; import { useDocumentTitle } from "@mantine/hooks"; import { useScrollResetOnLocationChange } from "../../logic/ui"; @@ -20,7 +20,6 @@ function DeckView() { const [deck, isDeckReady] = useDeckFromUrl(); const [superDecks] = useSuperDecks(deck); const [cards, areCardsReady] = useCardsOf(deck); - const stats = useStatsOf(cards); useScrollResetOnLocationChange(); @@ -71,7 +70,6 @@ function DeckView() { isDeckReady={isDeckReady} cards={cards} areCardsReady={areCardsReady} - stats={stats} setDeckOptionsOpened={setDeckOptionsOpened} /> @@ -96,7 +94,6 @@ function DeckView() { isDeckReady={isDeckReady} cards={cards} areCardsReady={areCardsReady} - stats={stats} /> diff --git a/src/components/deck/HeroDeckSection/HeroDeckSection.tsx b/src/components/deck/HeroDeckSection/HeroDeckSection.tsx index dca216d..a00074c 100644 --- a/src/components/deck/HeroDeckSection/HeroDeckSection.tsx +++ b/src/components/deck/HeroDeckSection/HeroDeckSection.tsx @@ -1,6 +1,6 @@ import classes from "./HeroDeckSection.module.css"; -import React from "react"; +import React, { useState } from "react"; import { Button, Group, Paper, Stack, Text, Title } from "@mantine/core"; import { Deck } from "../../../logic/deck"; import { @@ -10,33 +10,27 @@ import { IconSparkles, } from "@tabler/icons-react"; import { useNavigate } from "react-router-dom"; -import { Card, CardsStats, CardType } from "../../../logic/card"; +import { Card, CardType, useSimplifiedStatesOf } from "../../../logic/card"; import Stat from "../../custom/Stat/Stat"; import { useTranslation } from "react-i18next"; +import { State } from "fsrs.js"; interface HeroDeckSectionProps { deck?: Deck; cards?: Card[]; - stats: CardsStats; isDeckReady: boolean; areCardsReady: boolean; } -function HeroDeckSection({ - deck, - cards, - areCardsReady, - stats, -}: HeroDeckSectionProps) { +function HeroDeckSection({ deck, cards, areCardsReady }: HeroDeckSectionProps) { const navigate = useNavigate(); const [t] = useTranslation(); function isDone() { - return ( - stats.newCards === 0 && stats.learningCards === 0 && stats.dueCards === 0 - ); + return false; //FIXME 33 } + const states = useSimplifiedStatesOf(cards); return ( {areCardsReady && @@ -73,19 +67,19 @@ function HeroDeckSection({