From 684c3de18696632d6239674ed24a42955c078c15 Mon Sep 17 00:00:00 2001 From: Egor Gorbachev <7gorbachevm@gmail.com> Date: Sun, 31 Mar 2024 17:12:15 +0700 Subject: [PATCH] Display folders in catalog (#26) --- src/api/api.ts | 22 ++- src/screens/deck-catalog/deck-added-label.tsx | 7 +- src/screens/deck-catalog/deck-catalog.tsx | 40 ++-- .../deck-catalog/store/deck-catalog-store.ts | 40 ++-- src/screens/deck-catalog/translations.ts | 17 ++ src/screens/deck-form/deck-form.tsx | 6 +- src/screens/deck-list/main-screen.tsx | 4 +- src/screens/deck-list/public-deck.tsx | 2 +- src/screens/deck-review/deck-preview.tsx | 9 +- src/screens/folder-review/folder-preview.tsx | 16 +- src/screens/shared/card/card.tsx | 2 +- src/store/deck-list-store.test.ts | 4 +- src/store/deck-list-store.ts | 182 ++++++++++++++---- src/translations/t.ts | 19 +- src/ui/deck-category-logo.tsx | 3 + src/ui/deck-list-item-with-description.tsx | 20 +- 16 files changed, 261 insertions(+), 132 deletions(-) create mode 100644 src/screens/deck-catalog/translations.ts diff --git a/src/api/api.ts b/src/api/api.ts index 526e8142..2924eb81 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -23,7 +23,6 @@ import { RemoveDeckFromMineRequest, RemoveDeckFromMineResponse, } from "../../functions/remove-deck-from-mine.ts"; -import { DeckCatalogResponse } from "../../functions/catalog-decks.ts"; import { DeckWithCardsResponse } from "../../functions/deck-with-cards.ts"; import { CopyDeckResponse } from "../../functions/duplicate-deck.ts"; import { DeckCategoryResponse } from "../../functions/deck-categories.ts"; @@ -44,6 +43,9 @@ import { import { DuplicateFolderResponse } from "../../functions/duplicate-folder.ts"; import { MyStatisticsResponse } from "../../functions/my-statistics.ts"; import { AllPlansResponse } from "../../functions/plans.ts"; +import { DeckCatalogResponse } from "../../functions/catalog.ts"; +import { FolderWithDecksWithCardsResponse } from "../../functions/folder-with-decks-cards.ts"; +import { AddFolderToMineRequest } from "../../functions/add-folder-to-mine.ts"; export const healthRequest = () => { return request("/health"); @@ -57,6 +59,12 @@ export const getSharedDeckRequest = (shareId?: string) => { return request(`/get-shared-deck?share_id=${shareId}`); }; +export const getFolderWithDecksCards = (folderId?: number) => { + return request( + `/folder-with-decks-cards?folder_id=${folderId}`, + ); +}; + export const addDeckToMineRequest = (body: AddDeckToMineRequest) => { return request( "/add-deck-to-mine", @@ -65,6 +73,14 @@ export const addDeckToMineRequest = (body: AddDeckToMineRequest) => { ); }; +export const addFolderToMineRequest = (body: AddFolderToMineRequest) => { + return request( + "/add-folder-to-mine", + "POST", + body, + ); +}; + export const getDeckAccessesOfDeckRequest = ( filters: { deckId: string } | { folderId: string }, ) => { @@ -127,8 +143,8 @@ export const removeDeckFromMineRequest = (body: RemoveDeckFromMineRequest) => { ); }; -export const deckCatalogRequest = () => { - return request("/catalog-decks"); +export const catalogGetRequest = () => { + return request("/catalog"); }; export const deckWithCardsRequest = (deckId: number) => { diff --git a/src/screens/deck-catalog/deck-added-label.tsx b/src/screens/deck-catalog/deck-added-label.tsx index ddc154ed..b99ab1c4 100644 --- a/src/screens/deck-catalog/deck-added-label.tsx +++ b/src/screens/deck-catalog/deck-added-label.tsx @@ -16,12 +16,7 @@ export const DeckAddedLabel = () => { })} > ); diff --git a/src/screens/deck-catalog/deck-catalog.tsx b/src/screens/deck-catalog/deck-catalog.tsx index ccc15679..8d2cf60e 100644 --- a/src/screens/deck-catalog/deck-catalog.tsx +++ b/src/screens/deck-catalog/deck-catalog.tsx @@ -7,10 +7,7 @@ import { useDeckCatalogStore } from "./store/deck-catalog-store-context.tsx"; import { useMount } from "../../lib/react/use-mount.ts"; import { theme } from "../../ui/theme.tsx"; import { Select } from "../../ui/select.tsx"; -import { - DeckLanguage, - languageFilterToNativeName, -} from "./store/deck-catalog-store.ts"; +import { DeckLanguage } from "./store/deck-catalog-store.ts"; import { DeckListItemWithDescription } from "../../ui/deck-list-item-with-description.tsx"; import { range } from "../../lib/array/range.ts"; import { DeckLoading } from "../shared/deck-loading.tsx"; @@ -21,6 +18,7 @@ import { t, translateCategory } from "../../translations/t.ts"; import { enumValues } from "../../lib/typescript/enum-values.ts"; import { Screen } from "../shared/screen.tsx"; import { Flex } from "../../ui/flex.tsx"; +import { languageFilterToNativeName } from "./translations.ts"; export const DeckCatalog = observer(() => { const store = useDeckCatalogStore(); @@ -69,29 +67,43 @@ export const DeckCatalog = observer(() => { {(() => { - if (store.decks?.state === "pending") { + if (store.catalog?.state === "pending") { return range(5).map((i) => ); } - if (store.decks?.state === "fulfilled") { - const filteredDecks = store.filteredDecks; + if (store.catalog?.state === "fulfilled") { + const filteredCatalogItems = store.filteredCatalogItems; - if (filteredDecks.length === 0) { + if (filteredCatalogItems.length === 0) { return ; } const myDeckIds = deckListStore.myDecks.map((deck) => deck.id); + const myFoldersIds = deckListStore.myFoldersAsDecks.map( + (folder) => folder.id, + ); - return filteredDecks.map((deck) => { - const isMine = myDeckIds.includes(deck.id); + return filteredCatalogItems.map((item) => { + const isMineFolder = + item.type === "folder" + ? myFoldersIds.includes(item.data.id) + : false; + const isMineDeck = + item.type === "deck" ? myDeckIds.includes(item.data.id) : false; + const isAdded = isMineDeck || isMineFolder; return ( : undefined} - deck={deck} + key={item.data.id} + titleRightSlot={isAdded ? : undefined} + catalogItem={item.data} onClick={() => { - deckListStore.openDeckFromCatalog(deck, isMine); + if (item.type === "deck") { + deckListStore.openDeckFromCatalog(item.data, isMineDeck); + } + if (item.type === "folder") { + deckListStore.openFolderFromCatalog(item.data); + } }} /> ); diff --git a/src/screens/deck-catalog/store/deck-catalog-store.ts b/src/screens/deck-catalog/store/deck-catalog-store.ts index d6526ec9..65a4a58d 100644 --- a/src/screens/deck-catalog/store/deck-catalog-store.ts +++ b/src/screens/deck-catalog/store/deck-catalog-store.ts @@ -1,15 +1,17 @@ import { makeAutoObservable } from "mobx"; -import { deckCatalogRequest, deckCategoriesRequest } from "../../../api/api.ts"; +import { catalogGetRequest, deckCategoriesRequest } from "../../../api/api.ts"; import { fromPromise, IPromiseBasedObservable, } from "../../../lib/mobx-from-promise/from-promise.ts"; -import { DeckCatalogResponse } from "../../../../functions/catalog-decks.ts"; import { TextField } from "mobx-form-lite"; import { cachePromise } from "../../../lib/cache/cache-promise.ts"; import { DeckCategoryResponse } from "../../../../functions/deck-categories.ts"; -import { t } from "../../../translations/t.ts"; import { persistableField } from "../../../lib/mobx-form-lite-persistable/persistable-field.ts"; +import { + CatalogItem, + DeckCatalogResponse, +} from "../../../../functions/catalog.ts"; export enum DeckLanguage { Any = "any", @@ -18,26 +20,11 @@ export enum DeckLanguage { Russian = "ru", } -export const languageFilterToNativeName = (str: DeckLanguage): string => { - switch (str) { - case DeckLanguage.Any: - return t("any_language"); - case DeckLanguage.English: - return "English"; - case DeckLanguage.Russian: - return "Русский"; - case DeckLanguage.Spanish: - return "Español"; - default: - return str satisfies never; - } -}; - -const decksCached = cachePromise(); +const catalogCached = cachePromise(); const categoriesCached = cachePromise(); export class DeckCatalogStore { - decks?: IPromiseBasedObservable; + catalog?: IPromiseBasedObservable; filters = { language: persistableField(new TextField(DeckLanguage.Any), "catalogLn"), categoryId: new TextField(""), @@ -49,24 +36,25 @@ export class DeckCatalogStore { } load() { - this.decks = fromPromise(decksCached(deckCatalogRequest())); + this.catalog = fromPromise(catalogCached(catalogGetRequest())); this.categories = fromPromise(categoriesCached(deckCategoriesRequest())); } - get filteredDecks() { - if (this.decks?.state !== "fulfilled") { + get filteredCatalogItems(): CatalogItem[] { + if (this.catalog?.state !== "fulfilled") { return []; } const language = this.filters.language.value; const categoryId = this.filters.categoryId.value; - return this.decks.value.decks.filter((deck) => { - if (language !== DeckLanguage.Any && deck.available_in !== language) { + return this.catalog.value.filter((catalogItem) => { + const item = catalogItem.data; + if (language !== DeckLanguage.Any && item.available_in !== language) { return false; } - if (!!categoryId && deck.category_id !== categoryId) { + if (!!categoryId && item.category_id !== categoryId) { return false; } diff --git a/src/screens/deck-catalog/translations.ts b/src/screens/deck-catalog/translations.ts new file mode 100644 index 00000000..17fbc64a --- /dev/null +++ b/src/screens/deck-catalog/translations.ts @@ -0,0 +1,17 @@ +import { t } from "../../translations/t.ts"; +import { DeckLanguage } from "./store/deck-catalog-store.ts"; + +export const languageFilterToNativeName = (str: DeckLanguage): string => { + switch (str) { + case DeckLanguage.Any: + return t("any_language"); + case DeckLanguage.English: + return "English"; + case DeckLanguage.Russian: + return "Русский"; + case DeckLanguage.Spanish: + return "Español"; + default: + return str satisfies never; + } +}; diff --git a/src/screens/deck-form/deck-form.tsx b/src/screens/deck-form/deck-form.tsx index 6d319fa6..7a1cd734 100644 --- a/src/screens/deck-form/deck-form.tsx +++ b/src/screens/deck-form/deck-form.tsx @@ -46,7 +46,7 @@ export const DeckForm = observer(() => { ); useBackButton(() => { deckFormStore.onDeckBack(() => { - screenStore.go({ type: "main" }) + screenStore.go({ type: "main" }); }); }); useTelegramProgress(() => deckFormStore.isSending); @@ -62,7 +62,7 @@ export const DeckForm = observer(() => { subtitle={ screen.folder ? (
- {t('folder')}{" "} + {t("folder")}{" "}