diff --git a/src/screens/deck-review/deck-finished.tsx b/src/screens/deck-review/deck-finished.tsx
index 02deb332..de89fab7 100644
--- a/src/screens/deck-review/deck-finished.tsx
+++ b/src/screens/deck-review/deck-finished.tsx
@@ -26,7 +26,12 @@ const encouragingMessages = [
"Just think of the compounded knowledge you're getting with every review. Your future self thanks you!",
];
-export const DeckFinished = observer(() => {
+type Props = {
+ type: "deck" | "repeat_all";
+};
+
+export const DeckFinished = observer((props: Props) => {
+ const { type } = props;
const reviewStore = useReviewStore();
useMount(() => {
@@ -46,7 +51,11 @@ export const DeckFinished = observer(() => {
alignItems: "center",
})}
>
- You have finished this deck for now 🎉
+
+ {type === "deck"
+ ? `You have finished this deck for now 🎉`
+ : `You have repeated all the cards for today 🎉`}
+
{random(encouragingMessages)} 😊
diff --git a/src/screens/deck-review/deck-preview.tsx b/src/screens/deck-review/deck-preview.tsx
index a29c7642..fd629460 100644
--- a/src/screens/deck-review/deck-preview.tsx
+++ b/src/screens/deck-review/deck-preview.tsx
@@ -22,7 +22,7 @@ export const DeckPreview = observer(() => {
useMainButton(
"Review deck",
() => {
- deckListStore.startReview(reviewStore);
+ deckListStore.startDeckReview(reviewStore);
},
() => deckListStore.canReview,
);
diff --git a/src/screens/deck-review/deck-screen.tsx b/src/screens/deck-review/deck-screen.tsx
index 55d21b58..9739227e 100644
--- a/src/screens/deck-review/deck-screen.tsx
+++ b/src/screens/deck-review/deck-screen.tsx
@@ -9,7 +9,7 @@ export const DeckScreen = observer(() => {
const reviewStore = useReviewStore();
if (reviewStore.isFinished) {
- return ;
+ return ;
} else if (reviewStore.currentCardId) {
return ;
}
diff --git a/src/screens/deck-review/repeat-all-screen.tsx b/src/screens/deck-review/repeat-all-screen.tsx
new file mode 100644
index 00000000..70c7b0c8
--- /dev/null
+++ b/src/screens/deck-review/repeat-all-screen.tsx
@@ -0,0 +1,23 @@
+import { observer } from "mobx-react-lite";
+import { useReviewStore } from "../../store/review-store-context.tsx";
+import { useMount } from "../../lib/react/use-mount.ts";
+import { deckListStore } from "../../store/deck-list-store.ts";
+import { DeckFinished } from "./deck-finished.tsx";
+import { Review } from "./review.tsx";
+import React from "react";
+
+export const RepeatAllScreen = observer(() => {
+ const reviewStore = useReviewStore();
+
+ useMount(() => {
+ reviewStore.startAllRepeatReview(deckListStore.myDecks);
+ });
+
+ if (reviewStore.isFinished) {
+ return ;
+ } else if (reviewStore.currentCardId) {
+ return ;
+ }
+
+ return null;
+});
diff --git a/src/store/deck-list-store.ts b/src/store/deck-list-store.ts
index 8a36c484..64f8cb9e 100644
--- a/src/store/deck-list-store.ts
+++ b/src/store/deck-list-store.ts
@@ -1,4 +1,4 @@
-import { action, makeAutoObservable, runInAction, when } from "mobx";
+import { action, makeAutoObservable, when } from "mobx";
import { fromPromise, IPromiseBasedObservable } from "mobx-utils";
import {
addDeckToMineRequest,
@@ -18,14 +18,23 @@ import { ReviewStore } from "./review-store.ts";
import { reportHandledError } from "../lib/rollbar/rollbar.tsx";
import { UserDbType } from "../../functions/db/user/upsert-user-db.ts";
+export enum StartParamType {
+ RepeatAll = "repeat_all",
+}
+
export type DeckWithCardsWithReviewType = DeckWithCardsDbType & {
cardsToReview: Array;
};
export class DeckListStore {
myInfo?: IPromiseBasedObservable;
+
isSharedDeckLoading = false;
isSharedDeckLoaded = false;
+
+ isReviewAllLoading = false;
+ isReviewAllLoaded = false;
+
skeletonLoaderData = { publicCount: 3, myDecksCount: 3 };
isDeckRemoving = false;
@@ -34,9 +43,9 @@ export class DeckListStore {
makeAutoObservable(this, {}, { autoBind: true });
}
- loadFirstTime(shareId?: string) {
+ loadFirstTime(startParam?: string) {
this.load();
- this.loadSharedDeck(shareId);
+ this.handleStartParam(startParam);
}
load() {
@@ -52,51 +61,76 @@ export class DeckListStore {
}
}
- async loadSharedDeck(shareId?: string) {
- if (!shareId || this.isSharedDeckLoaded) {
+ async handleStartParam(startParam?: string) {
+ if (!startParam) {
return;
}
- this.isSharedDeckLoading = true;
- await when(() => this.myInfo?.state === "fulfilled");
-
- getSharedDeckRequest(shareId)
- .then(
- action((sharedDeck) => {
- assert(this.myInfo?.state === "fulfilled");
- if (
- this.myInfo.value.myDecks.find(
- (myDeck) => myDeck.id === sharedDeck.deck.id,
- )
- ) {
- screenStore.go({ type: "deckMine", deckId: sharedDeck.deck.id });
- return;
- }
-
- if (
- this.publicDecks.find(
- (publicDeck) => publicDeck.id === sharedDeck.deck.id,
- )
- ) {
+ if (startParam === StartParamType.RepeatAll) {
+ if (this.isReviewAllLoaded) {
+ return;
+ }
+
+ this.isReviewAllLoading = true;
+ when(() => this.myInfo?.state === "fulfilled")
+ .then(() => {
+ screenStore.go({ type: "reviewAll" });
+ })
+ .finally(
+ action(() => {
+ this.isReviewAllLoading = false;
+ this.isReviewAllLoaded = true;
+ }),
+ );
+ } else {
+ if (this.isSharedDeckLoaded) {
+ return;
+ }
+
+ this.isSharedDeckLoading = true;
+ await when(() => this.myInfo?.state === "fulfilled");
+
+ getSharedDeckRequest(startParam)
+ .then(
+ action((sharedDeck) => {
+ assert(this.myInfo?.state === "fulfilled");
+ if (
+ this.myInfo.value.myDecks.find(
+ (myDeck) => myDeck.id === sharedDeck.deck.id,
+ )
+ ) {
+ screenStore.go({ type: "deckMine", deckId: sharedDeck.deck.id });
+ return;
+ }
+
+ if (
+ this.publicDecks.find(
+ (publicDeck) => publicDeck.id === sharedDeck.deck.id,
+ )
+ ) {
+ screenStore.go({
+ type: "deckPublic",
+ deckId: sharedDeck.deck.id,
+ });
+ return;
+ }
+
+ this.myInfo.value.publicDecks.push(sharedDeck.deck);
screenStore.go({ type: "deckPublic", deckId: sharedDeck.deck.id });
- return;
- }
-
- this.myInfo.value.publicDecks.push(sharedDeck.deck);
- screenStore.go({ type: "deckPublic", deckId: sharedDeck.deck.id });
- }),
- )
- .catch((e) => {
- reportHandledError("Error while retrieving shared deck", e, {
- shareId,
- });
- })
- .finally(
- action(() => {
- this.isSharedDeckLoading = false;
- this.isSharedDeckLoaded = true;
- }),
- );
+ }),
+ )
+ .catch((e) => {
+ reportHandledError("Error while retrieving shared deck", e, {
+ shareId: startParam,
+ });
+ })
+ .finally(
+ action(() => {
+ this.isSharedDeckLoading = false;
+ this.isSharedDeckLoaded = true;
+ }),
+ );
+ }
}
get canReview() {
@@ -108,7 +142,7 @@ export class DeckListStore {
);
}
- startReview(reviewStore: ReviewStore) {
+ startDeckReview(reviewStore: ReviewStore) {
if (!this.canReview) {
return;
}
@@ -198,7 +232,7 @@ export class DeckListStore {
);
}
- async removeDeck() {
+ removeDeck() {
const deck = this.selectedDeck;
if (!deck) {
return;
@@ -206,17 +240,21 @@ export class DeckListStore {
this.isDeckRemoving = true;
- try {
- await removeDeckFromMine({ deckId: deck.id });
- screenStore.go({ type: "main" });
- this.myInfo = fromPromise(myInfoRequest());
- } catch (e) {
- reportHandledError(`Unable to remove deck ${deck.id}`, e);
- } finally {
- runInAction(() => {
- this.isDeckRemoving = false;
- });
- }
+ removeDeckFromMine({ deckId: deck.id })
+ .then(
+ action(() => {
+ screenStore.go({ type: "main" });
+ this.myInfo = fromPromise(myInfoRequest());
+ }),
+ )
+ .catch((e) => {
+ reportHandledError(`Unable to remove deck ${deck.id}`, e);
+ })
+ .finally(
+ action(() => {
+ this.isDeckRemoving = false;
+ }),
+ );
}
optimisticUpdateSettings(
diff --git a/src/store/review-store.ts b/src/store/review-store.ts
index 2ee2c56d..51935c8d 100644
--- a/src/store/review-store.ts
+++ b/src/store/review-store.ts
@@ -5,7 +5,10 @@ import { assert } from "../lib/typescript/assert.ts";
import { reviewCardsRequest } from "../api/api.ts";
import { ReviewOutcome } from "../../functions/services/review-card.ts";
import { screenStore } from "./screen-store.ts";
-import { deckListStore } from "./deck-list-store.ts";
+import {
+ deckListStore,
+ DeckWithCardsWithReviewType,
+} from "./deck-list-store.ts";
type ReviewResult = {
forgotIds: number[];
@@ -49,6 +52,34 @@ export class ReviewStore {
}
}
+ startAllRepeatReview(myDecks: DeckWithCardsWithReviewType[]) {
+ if (!myDecks.length) {
+ return;
+ }
+
+ myDecks.forEach((deck) => {
+ deck.cardsToReview
+ .filter((card) => card.type === "repeat")
+ .forEach((card) => {
+ this.cardsToReview.push(
+ new CardFormStore(
+ card.id,
+ card.front,
+ card.back,
+ card.example,
+ deck.name,
+ ),
+ );
+ });
+ });
+
+ this.initialCardCount = this.cardsToReview.length;
+ this.currentCardId = this.cardsToReview[0].id;
+ if (this.cardsToReview.length > 1) {
+ this.nextCardId = this.cardsToReview[1].id;
+ }
+ }
+
get currentCard() {
if (!this.currentCardId) {
return null;
diff --git a/src/store/screen-store.ts b/src/store/screen-store.ts
index 28898c91..c6e7407f 100644
--- a/src/store/screen-store.ts
+++ b/src/store/screen-store.ts
@@ -5,6 +5,7 @@ type Route =
| { type: "deckMine"; deckId: number }
| { type: "deckPublic"; deckId: number }
| { type: "deckForm"; deckId?: number }
+ | { type: "reviewAll" }
| { type: "cardQuickAddForm"; deckId: number }
| { type: "userSettings" };