diff --git a/src/api/create-cached-card-input-modes-request.ts b/src/api/create-cached-card-input-modes-request.ts
new file mode 100644
index 00000000..3ab061df
--- /dev/null
+++ b/src/api/create-cached-card-input-modes-request.ts
@@ -0,0 +1,8 @@
+import { RequestStore } from "../lib/mobx-request/request-store.ts";
+import { cardInputModeListRequest } from "./api.ts";
+
+export const createCachedCardInputModesRequest = () => {
+ return new RequestStore(cardInputModeListRequest, {
+ cacheId: "cardInputModeList",
+ });
+};
diff --git a/src/screens/ai-mass-creation/store/ai-mass-creation-store.test.ts b/src/screens/ai-mass-creation/store/ai-mass-creation-store.test.ts
index 09698866..41495552 100644
--- a/src/screens/ai-mass-creation/store/ai-mass-creation-store.test.ts
+++ b/src/screens/ai-mass-creation/store/ai-mass-creation-store.test.ts
@@ -1,4 +1,4 @@
-import { test, vi, expect } from "vitest";
+import { expect, test, vi } from "vitest";
import { AiMassCreationStore } from "./ai-mass-creation-store.ts";
import { when } from "mobx";
import { isFormValid } from "mobx-form-lite";
diff --git a/src/screens/app.tsx b/src/screens/app.tsx
index b4cdd2b3..3ae338a6 100644
--- a/src/screens/app.tsx
+++ b/src/screens/app.tsx
@@ -40,7 +40,6 @@ import { SnackbarProviderWrapper } from "./shared/snackbar/snackbar-provider-wra
import { Debug } from "./debug/debug.tsx";
import { BrowserHeader } from "./shared/browser-platform/browser-header.tsx";
import { BrowserMainButton } from "./shared/browser-platform/browser-main-button.tsx";
-import { CardInputModeScreen } from "./card-input-mode/card-input-mode-screen.tsx";
export const App = observer(() => {
useRestoreFullScreenExpand();
@@ -170,11 +169,6 @@ export const App = observer(() => {
)}
- {screenStore.screen.type === "cardInputMode" && (
-
-
-
- )}
);
diff --git a/src/screens/card-input-mode/card-input-mode-screen.tsx b/src/screens/card-input-mode/card-input-mode-screen.tsx
index deecfacf..e634f458 100644
--- a/src/screens/card-input-mode/card-input-mode-screen.tsx
+++ b/src/screens/card-input-mode/card-input-mode-screen.tsx
@@ -1,7 +1,6 @@
import { observer } from "mobx-react-lite";
import { Screen } from "../shared/screen.tsx";
import { useBackButton } from "../../lib/platform/use-back-button.ts";
-import { screenStore } from "../../store/screen-store.ts";
import { useState } from "react";
import { CardInputModeStore } from "./store/card-input-mode-store.ts";
import { useMount } from "../../lib/react/use-mount.ts";
@@ -10,12 +9,16 @@ import { RadioList } from "../../ui/radio-list/radio-list.tsx";
import { useMainButton } from "../../lib/platform/use-main-button.ts";
import { useProgress } from "../../lib/platform/use-progress.tsx";
import { t } from "../../translations/t.ts";
+import { DeckFormStore } from "../deck-form/deck-form/store/deck-form-store.ts";
-export const CardInputModeScreen = observer(() => {
- const [store] = useState(() => new CardInputModeStore());
+type Props = { deckFormStore: DeckFormStore };
+
+export const CardInputModeScreen = observer((props: Props) => {
+ const { deckFormStore } = props;
+ const [store] = useState(() => new CardInputModeStore(deckFormStore));
useBackButton(() => {
- screenStore.back();
+ deckFormStore.quitInnerScreen();
});
useMount(() => {
@@ -31,7 +34,7 @@ export const CardInputModeScreen = observer(() => {
});
return (
-
+
{store.cardInputModesRequest.result.status === "loading" ? (
) : null}
diff --git a/src/screens/card-input-mode/store/card-input-mode-store.ts b/src/screens/card-input-mode/store/card-input-mode-store.ts
index f1886b51..265514a7 100644
--- a/src/screens/card-input-mode/store/card-input-mode-store.ts
+++ b/src/screens/card-input-mode/store/card-input-mode-store.ts
@@ -1,36 +1,28 @@
import { RequestStore } from "../../../lib/mobx-request/request-store.ts";
-import {
- cardInputModeListRequest,
- deckChangeInputModeRequest,
-} from "../../../api/api.ts";
+import { deckChangeInputModeRequest } from "../../../api/api.ts";
import { makeAutoObservable } from "mobx";
import { TextField } from "mobx-form-lite";
-import { screenStore } from "../../../store/screen-store.ts";
import { assert } from "../../../lib/typescript/assert.ts";
import { notifyError, notifySuccess } from "../../shared/snackbar/snackbar.tsx";
import { deckListStore } from "../../../store/deck-list-store.ts";
import { t } from "../../../translations/t.ts";
-
-export const createCachedCardInputModesRequest = () => {
- return new RequestStore(cardInputModeListRequest, {
- cacheId: "cardInputModeList",
- });
-};
+import { DeckFormStore } from "../../deck-form/deck-form/store/deck-form-store.ts";
+import { createCachedCardInputModesRequest } from "../../../api/create-cached-card-input-modes-request.ts";
export class CardInputModeStore {
cardInputModesRequest = createCachedCardInputModesRequest();
deckChangeInputModeRequest = new RequestStore(deckChangeInputModeRequest);
modeId = new TextField(null);
- constructor() {
+ constructor(private deckFormStore: DeckFormStore) {
makeAutoObservable(this, {}, { autoBind: true });
}
load() {
- const { screen } = screenStore;
- assert(screen.type === "cardInputMode");
+ assert(this.deckFormStore.deckForm, "Deck form should be loaded");
+ const cardInputModeId = this.deckFormStore.deckForm.cardInputModeId;
- this.modeId.onChange(screen.cardInputModeId);
+ this.modeId.onChange(cardInputModeId);
this.cardInputModesRequest.execute();
}
@@ -39,11 +31,12 @@ export class CardInputModeStore {
return;
}
- const { screen } = screenStore;
- assert(screen.type === "cardInputMode");
+ assert(this.deckFormStore.deckForm, "Deck form should be loaded");
+ const deckId = this.deckFormStore.deckForm.id;
+ assert(deckId, "Deck id should be defined");
const result = await this.deckChangeInputModeRequest.execute({
- deckId: screen.deckId,
+ deckId: deckId,
cardInputModeId: this.modeId.value,
});
@@ -55,11 +48,8 @@ export class CardInputModeStore {
return;
}
- deckListStore.updateDeckCardInputMode(screen.deckId, this.modeId.value);
+ deckListStore.updateDeckCardInputMode(deckId, this.modeId.value);
notifySuccess(t("card_input_mode_changed"));
- screenStore.go({
- type: "deckForm",
- deckId: screen.deckId,
- });
+ this.deckFormStore.quitInnerScreen();
}
}
diff --git a/src/screens/deck-form/card-form/generated-card-form-view.tsx b/src/screens/deck-form/card-form/generated-card-form-view.tsx
index fa637825..e0ac3f96 100644
--- a/src/screens/deck-form/card-form/generated-card-form-view.tsx
+++ b/src/screens/deck-form/card-form/generated-card-form-view.tsx
@@ -14,9 +14,6 @@ import { Input } from "../../../ui/input.tsx";
import { HintTransparent } from "../../../ui/hint-transparent.tsx";
import { CardRowLoading } from "../../shared/card-row-loading.tsx";
import { CardRow } from "../../../ui/card-row.tsx";
-import { css } from "@emotion/css";
-import { theme } from "../../../ui/theme.tsx";
-import { screenStore } from "../../../store/screen-store.ts";
type Props = { cardFormStore: CardFormStoreInterface };
@@ -68,21 +65,6 @@ export const GeneratedCardFormView = observer((props: Props) => {
return (
{inputMode.title}
- {
- screenStore.go({
- type: "cardInputMode",
- cardInputModeId: cardInputModeId,
- deckId: localStore.deckId,
- });
- }}
- className={css({
- cursor: "pointer",
- color: theme.linkColor,
- })}
- >
- {t("edit")}
-
);
})()
diff --git a/src/screens/deck-form/card-form/manual-card-form-view.tsx b/src/screens/deck-form/card-form/manual-card-form-view.tsx
index 74ebee30..e30321b5 100644
--- a/src/screens/deck-form/card-form/manual-card-form-view.tsx
+++ b/src/screens/deck-form/card-form/manual-card-form-view.tsx
@@ -25,6 +25,7 @@ import { ButtonGrid } from "../../../ui/button-grid.tsx";
import { boolNarrow } from "../../../lib/typescript/bool-narrow.ts";
import { CardAnswerErrors } from "./card-answer-errors.tsx";
import { css } from "@emotion/css";
+import { screenStore } from "../../../store/screen-store.ts";
type Props = { cardFormStore: CardFormStoreInterface };
@@ -40,6 +41,13 @@ export const ManualCardFormView = observer((props: Props) => {
useProgress(() => cardFormStore.isSending);
useBackButton(() => {
+
+ const screen = screenStore.screen;
+ // Avoid duplicated 'deckForm' in the router history
+ if (screen.type === "deckForm" && screen.cardId) {
+ screenStore.back();
+ }
+
cardFormStore.onBackCard();
});
diff --git a/src/screens/deck-form/card-form/store/ai-generated-card-form-store.ts b/src/screens/deck-form/card-form/store/ai-generated-card-form-store.ts
index a821e074..45a78ef9 100644
--- a/src/screens/deck-form/card-form/store/ai-generated-card-form-store.ts
+++ b/src/screens/deck-form/card-form/store/ai-generated-card-form-store.ts
@@ -1,5 +1,4 @@
import { createCardSideField } from "../../deck-form/store/deck-form-store.ts";
-import { createCachedCardInputModesRequest } from "../../../card-input-mode/store/card-input-mode-store.ts";
import { RequestStore } from "../../../../lib/mobx-request/request-store.ts";
import { makeAutoObservable } from "mobx";
import { formTouchAll, isFormValid } from "mobx-form-lite";
@@ -8,6 +7,7 @@ import { assert } from "../../../../lib/typescript/assert.ts";
import { notifyError } from "../../../shared/snackbar/snackbar.tsx";
import { aiSingleCardGenerateRequest } from "../../../../api/api.ts";
import { deckListStore } from "../../../../store/deck-list-store.ts";
+import { createCachedCardInputModesRequest } from "../../../../api/create-cached-card-input-modes-request.ts";
export class AiGeneratedCardFormStore {
form = {
@@ -26,7 +26,6 @@ export class AiGeneratedCardFormStore {
return;
}
-
const result = await this.aiSingleCardGenerateRequest.execute({
text: this.form.prompt.value,
deckId: this.deckId,
@@ -47,7 +46,11 @@ export class AiGeneratedCardFormStore {
const { card } = result.data.data;
deckListStore.addCardOptimistic(card);
- screenStore.go({ type: "deckForm", deckId: card.deck_id, cardId: card.id });
+ screenStore.goOnce({
+ type: "deckForm",
+ deckId: card.deck_id,
+ cardId: card.id,
+ });
}
get deckId() {
diff --git a/src/screens/deck-form/deck-form/deck-form-screen.tsx b/src/screens/deck-form/deck-form/deck-form-screen.tsx
index f1dfd30f..88634e35 100644
--- a/src/screens/deck-form/deck-form/deck-form-screen.tsx
+++ b/src/screens/deck-form/deck-form/deck-form-screen.tsx
@@ -6,6 +6,7 @@ import { CardList } from "./card-list.tsx";
import { CardFormWrapper } from "../card-form/card-form-wrapper.tsx";
import { PreventTelegramSwipeDownClosingIos } from "../../../lib/platform/telegram/prevent-telegram-swipe-down-closing.tsx";
import { SpeakingCards } from "./speaking-cards.tsx";
+import { CardInputModeScreen } from "../../card-input-mode/card-input-mode-screen.tsx";
export const DeckFormScreen = observer(() => {
const deckFormStore = useDeckFormStore();
@@ -34,6 +35,18 @@ export const DeckFormScreen = observer(() => {
);
}
- // No PreventTelegramSwipeDownClosingIos because the description textarea usually requires scroll
- return ;
+ if (deckFormStore.deckFormScreen === "cardInputMode") {
+ return (
+
+
+
+ );
+ }
+
+ if (deckFormStore.deckFormScreen === "deckForm") {
+ // No PreventTelegramSwipeDownClosingIos because the description textarea usually requires scroll
+ return ;
+ }
+
+ return deckFormStore.deckFormScreen satisfies never;
});
diff --git a/src/screens/deck-form/deck-form/deck-form.tsx b/src/screens/deck-form/deck-form/deck-form.tsx
index 0ef21212..9251cf73 100644
--- a/src/screens/deck-form/deck-form/deck-form.tsx
+++ b/src/screens/deck-form/deck-form/deck-form.tsx
@@ -46,6 +46,7 @@ export const DeckForm = observer(() => {
useProgress(() => deckFormStore.isSending);
if (!deckFormStore.deckForm) {
+ console.log("Deck form is not loaded");
return null;
}
@@ -174,19 +175,7 @@ export const DeckForm = observer(() => {
/>
),
onClick: () => {
- if (
- !deckFormStore.deckForm ||
- !isFormValid(deckFormStore.deckForm)
- ) {
- return;
- }
- const deckId = deckFormStore.deckForm.id;
- assert(deckId, "Deck id should be defined");
- screenStore.go({
- type: "cardInputMode",
- deckId: deckId,
- cardInputModeId: deckFormStore.deckForm.cardInputModeId,
- });
+ deckFormStore.goCardInputMode();
},
}
: undefined,
diff --git a/src/screens/deck-form/deck-form/store/deck-form-store.ts b/src/screens/deck-form/deck-form/store/deck-form-store.ts
index efda0118..68081520 100644
--- a/src/screens/deck-form/deck-form/store/deck-form-store.ts
+++ b/src/screens/deck-form/deck-form/store/deck-form-store.ts
@@ -177,7 +177,7 @@ export class DeckFormStore implements CardFormStoreInterface {
deckForm?: DeckFormType;
upsertDeckRequest = new RequestStore(upsertDeckRequest);
cardInnerScreen = new TextField(null);
- deckInnerScreen?: "cardList" | "speakingCards";
+ deckInnerScreen?: "cardList" | "speakingCards" | "cardInputMode";
cardFilter = {
text: new TextField(""),
sortBy: new TextField("createdAt"),
@@ -203,10 +203,6 @@ export class DeckFormStore implements CardFormStoreInterface {
}
loadForm() {
- if (this.deckForm) {
- return;
- }
-
const screen = screenStore.screen;
assert(screen.type === "deckForm");
@@ -254,6 +250,17 @@ export class DeckFormStore implements CardFormStoreInterface {
this.deckInnerScreen = "cardList";
}
+ goCardInputMode() {
+ if (!this.deckForm) {
+ return;
+ }
+ if (!isFormValid(this.deckForm)) {
+ formTouchAll(this.deckForm);
+ return;
+ }
+ this.deckInnerScreen = "cardInputMode";
+ }
+
quitInnerScreen() {
this.deckInnerScreen = undefined;
}
diff --git a/src/screens/deck-list/view-more-decks-toggle.tsx b/src/screens/deck-list/view-more-decks-toggle.tsx
index 52814a47..da38ae8a 100644
--- a/src/screens/deck-list/view-more-decks-toggle.tsx
+++ b/src/screens/deck-list/view-more-decks-toggle.tsx
@@ -26,7 +26,7 @@ export const ViewMoreDecksToggle = observer(() => {
)}
onClick={deckListStore.isMyDecksExpanded.toggle}
>
-
+
diff --git a/src/screens/deck-review/deck-preview.tsx b/src/screens/deck-review/deck-preview.tsx
index 7de78294..d6390cc2 100644
--- a/src/screens/deck-review/deck-preview.tsx
+++ b/src/screens/deck-review/deck-preview.tsx
@@ -119,7 +119,7 @@ export const DeckPreview = observer(() => {
icon={"mdi-plus-circle mdi-24px"}
outline
onClick={() => {
- screenStore.go({
+ screenStore.goOnce({
type: "cardQuickAddForm",
deckId: deck.id,
});
diff --git a/src/screens/deck-review/review-deck-name.tsx b/src/screens/deck-review/review-deck-name.tsx
index 3e70a16a..51a92b99 100644
--- a/src/screens/deck-review/review-deck-name.tsx
+++ b/src/screens/deck-review/review-deck-name.tsx
@@ -2,7 +2,7 @@ import { useReviewStore } from "./store/review-store-context.tsx";
import { css } from "@emotion/css";
import { theme } from "../../ui/theme.tsx";
import React from "react";
-import { m, AnimatePresence } from "framer-motion";
+import { AnimatePresence, m } from "framer-motion";
import { observer } from "mobx-react-lite";
import { LazyLoadFramerMotion } from "../../lib/framer-motion/lazy-load-framer-motion.tsx";
diff --git a/src/screens/plans/format-paid-until.test.ts b/src/screens/plans/format-paid-until.test.ts
index 9d8fcecc..744803c2 100644
--- a/src/screens/plans/format-paid-until.test.ts
+++ b/src/screens/plans/format-paid-until.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi } from "vitest";
+import { describe, expect, it, vi } from "vitest";
import { formatPaidUntil } from "./format-paid-until.tsx";
describe("formatPaidUntil", () => {
diff --git a/src/store/screen-store.test.ts b/src/store/screen-store.test.ts
new file mode 100644
index 00000000..178f0e2a
--- /dev/null
+++ b/src/store/screen-store.test.ts
@@ -0,0 +1,31 @@
+import { expect, test } from "vitest";
+import { ScreenStore } from "./screen-store.ts";
+
+test("screen store - push", () => {
+ const store = new ScreenStore();
+ expect(store.screen).toEqual({ type: "main" });
+ store.go({ type: "plans" });
+ expect(store.screen).toEqual({ type: "plans" });
+ store.back();
+ expect(store.screen).toEqual({ type: "main" });
+});
+
+test("screen store - push 1", () => {
+ const store = new ScreenStore();
+ expect(store.screen).toEqual({ type: "main" });
+ store.goOnce({ type: "plans" });
+ expect(store.screen).toEqual({ type: "plans" });
+ store.go({ type: "deckCatalog" });
+ expect(store.screen).toEqual({ type: "deckCatalog" });
+ store.back();
+ expect(store.screen).toEqual({ type: "main" });
+});
+
+test("screen store - push 2", () => {
+ const store = new ScreenStore();
+ expect(store.screen).toEqual({ type: "main" });
+ store.goOnce({ type: "plans" });
+ expect(store.screen).toEqual({ type: "plans" });
+ store.back();
+ expect(store.screen).toEqual({ type: "main" });
+});
diff --git a/src/store/screen-store.ts b/src/store/screen-store.ts
index 877cefad..d3bd0e59 100644
--- a/src/store/screen-store.ts
+++ b/src/store/screen-store.ts
@@ -22,11 +22,6 @@ type Route =
| { type: "shareDeck"; deckId: number; shareId: string }
| { type: "shareFolder"; folderId: number; shareId: string }
| { type: "aiMassCreation"; deckId: number; deckTitle: string | null }
- | {
- type: "cardInputMode";
- deckId: number;
- cardInputModeId: string | null;
- }
| { type: "plans" }
| { type: "debug" }
| { type: "componentCatalog" }
@@ -34,27 +29,40 @@ type Route =
| { type: "userStatistics" }
| { type: "userSettings" };
-export type RouteType = Route["type"];
-
export class ScreenStore {
- history: Route[] = [{ type: "main" }];
+ private history: Route[] = [{ type: "main" }];
+ private onceRoute?: Route;
constructor() {
makeAutoObservable(this, {}, { autoBind: true });
makeLoggable(this);
}
- go(historyData: Route) {
- this.history.push(historyData);
+ go(route: Route) {
+ if (this.onceRoute) {
+ this.onceRoute = undefined;
+ }
+ this.history.push(route);
+ }
+
+ goOnce(route: Route) {
+ this.onceRoute = route;
}
back() {
+ if (this.onceRoute) {
+ this.onceRoute = undefined;
+ return;
+ }
if (this.history.length > 1) {
this.history.pop();
}
}
get screen(): Route {
+ if (this.onceRoute) {
+ return this.onceRoute;
+ }
return this.history[this.history.length - 1];
}
diff --git a/src/ui/dropdown.tsx b/src/ui/dropdown.tsx
index 35b04f69..de2ff407 100644
--- a/src/ui/dropdown.tsx
+++ b/src/ui/dropdown.tsx
@@ -1,4 +1,4 @@
-import React, { useRef, useEffect, ReactNode, useState } from "react";
+import React, { ReactNode, useEffect, useRef, useState } from "react";
import { css, cx } from "@emotion/css";
import { theme } from "./theme.tsx";
import { tapScale } from "../lib/animations/tap-scale.ts";