Skip to content

Commit

Permalink
Display folders in catalog (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
kubk authored Mar 31, 2024
1 parent e7ba58d commit c485244
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 132 deletions.
22 changes: 19 additions & 3 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<HealthResponse>("/health");
Expand All @@ -57,6 +59,12 @@ export const getSharedDeckRequest = (shareId?: string) => {
return request<GetSharedDeckResponse>(`/get-shared-deck?share_id=${shareId}`);
};

export const getFolderWithDecksCards = (folderId?: number) => {
return request<FolderWithDecksWithCardsResponse>(
`/folder-with-decks-cards?folder_id=${folderId}`,
);
};

export const addDeckToMineRequest = (body: AddDeckToMineRequest) => {
return request<AddDeckToMineResponse, AddDeckToMineRequest>(
"/add-deck-to-mine",
Expand All @@ -65,6 +73,14 @@ export const addDeckToMineRequest = (body: AddDeckToMineRequest) => {
);
};

export const addFolderToMineRequest = (body: AddFolderToMineRequest) => {
return request<AddFolderToMineRequest, AddFolderToMineRequest>(
"/add-folder-to-mine",
"POST",
body,
);
};

export const getDeckAccessesOfDeckRequest = (
filters: { deckId: string } | { folderId: string },
) => {
Expand Down Expand Up @@ -127,8 +143,8 @@ export const removeDeckFromMineRequest = (body: RemoveDeckFromMineRequest) => {
);
};

export const deckCatalogRequest = () => {
return request<DeckCatalogResponse>("/catalog-decks");
export const catalogGetRequest = () => {
return request<DeckCatalogResponse>("/catalog");
};

export const deckWithCardsRequest = (deckId: number) => {
Expand Down
7 changes: 1 addition & 6 deletions src/screens/deck-catalog/deck-added-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ export const DeckAddedLabel = () => {
})}
>
<i
className={cx(
"mdi mdi-check-circle",
css({
color: theme.linkColor,
}),
)}
className={cx("mdi mdi-check-circle", css({ color: theme.linkColor }))}
/>
</div>
);
Expand Down
40 changes: 26 additions & 14 deletions src/screens/deck-catalog/deck-catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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();
Expand Down Expand Up @@ -69,29 +67,43 @@ export const DeckCatalog = observer(() => {
</Flex>

{(() => {
if (store.decks?.state === "pending") {
if (store.catalog?.state === "pending") {
return range(5).map((i) => <DeckLoading key={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 <NoDecksMatchingFilters />;
}

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 (
<DeckListItemWithDescription
key={deck.id}
titleRightSlot={isMine ? <DeckAddedLabel /> : undefined}
deck={deck}
key={item.data.id}
titleRightSlot={isAdded ? <DeckAddedLabel /> : 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);
}
}}
/>
);
Expand Down
40 changes: 14 additions & 26 deletions src/screens/deck-catalog/store/deck-catalog-store.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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<DeckCatalogResponse>();
const catalogCached = cachePromise<DeckCatalogResponse>();
const categoriesCached = cachePromise<DeckCategoryResponse>();

export class DeckCatalogStore {
decks?: IPromiseBasedObservable<DeckCatalogResponse>;
catalog?: IPromiseBasedObservable<DeckCatalogResponse>;
filters = {
language: persistableField(new TextField(DeckLanguage.Any), "catalogLn"),
categoryId: new TextField(""),
Expand All @@ -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;
}

Expand Down
17 changes: 17 additions & 0 deletions src/screens/deck-catalog/translations.ts
Original file line number Diff line number Diff line change
@@ -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;
}
};
6 changes: 3 additions & 3 deletions src/screens/deck-form/deck-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const DeckForm = observer(() => {
);
useBackButton(() => {
deckFormStore.onDeckBack(() => {
screenStore.go({ type: "main" })
screenStore.go({ type: "main" });
});
});
useTelegramProgress(() => deckFormStore.isSending);
Expand All @@ -62,7 +62,7 @@ export const DeckForm = observer(() => {
subtitle={
screen.folder ? (
<div className={css({ textAlign: "center", fontSize: 14 })}>
{t('folder')}{" "}
{t("folder")}{" "}
<button
onClick={() => {
deckFormStore.onDeckBack(() => {
Expand All @@ -71,7 +71,7 @@ export const DeckForm = observer(() => {
type: "folderPreview",
folderId: screen.folder.id,
});
})
});
}}
className={cx(
reset.button,
Expand Down
4 changes: 1 addition & 3 deletions src/screens/deck-list/main-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import React, { Fragment } from "react";
import { observer } from "mobx-react-lite";
import { css, cx } from "@emotion/css";
import { PublicDeck } from "./public-deck.tsx";
import {
DeckRowWithCardsToReview
} from "../shared/deck-row-with-cards-to-review/deck-row-with-cards-to-review.tsx";
import { DeckRowWithCardsToReview } from "../shared/deck-row-with-cards-to-review/deck-row-with-cards-to-review.tsx";
import { deckListStore } from "../../store/deck-list-store.ts";
import { useMount } from "../../lib/react/use-mount.ts";
import { Hint } from "../../ui/hint.tsx";
Expand Down
2 changes: 1 addition & 1 deletion src/screens/deck-list/public-deck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const PublicDeck = observer((props: Props) => {

return (
<DeckListItemWithDescription
deck={deck}
catalogItem={deck}
onClick={() => {
deckListStore.openDeckFromCatalog(deck, false);
}}
Expand Down
9 changes: 1 addition & 8 deletions src/screens/deck-review/deck-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,7 @@ export const DeckPreview = observer(() => {
const reviewStore = useReviewStore();

useBackButton(() => {
const screen = screenStore.screen;
if ("backScreen" in screen && screen.backScreen) {
// backScreen is RouteType here
// @ts-ignore
screenStore.go({ type: screen.backScreen });
} else {
screenStore.back();
}
screenStore.back();
});

useTelegramProgress(() => deckListStore.isDeckCardsLoading);
Expand Down
16 changes: 7 additions & 9 deletions src/screens/folder-review/folder-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.ts
import { t } from "../../translations/t.ts";
import { useReviewStore } from "../deck-review/store/review-store-context.tsx";
import { ListHeader } from "../../ui/list-header.tsx";
import { assert } from "../../lib/typescript/assert.ts";
import { DeckRowWithCardsToReview } from "../shared/deck-row-with-cards-to-review/deck-row-with-cards-to-review.tsx";
import { ButtonGrid } from "../../ui/button-grid.tsx";
import { DeckFolderDescription } from "../shared/deck-folder-description.tsx";
Expand All @@ -26,18 +25,16 @@ export const FolderPreview = observer(() => {
const reviewStore = useReviewStore();

useBackButton(() => {
screenStore.go({ type: "main" });
screenStore.back();
});

useTelegramProgress(() => deckListStore.isDeckCardsLoading);
useTelegramProgress(() => deckListStore.isCatalogItemLoading);
useScrollToTopOnMount();

useMainButton(
t("review_folder"),
() => {
const folder = deckListStore.selectedFolder;
assert(folder, "Folder should be selected before review");
reviewStore.startFolderReview(folder.decks);
deckListStore.reviewFolder(reviewStore);
},
() => deckListStore.isFolderReviewVisible,
);
Expand Down Expand Up @@ -82,7 +79,7 @@ export const FolderPreview = observer(() => {
<div>
<DeckFolderDescription deck={folder} />
</div>
{!deckListStore.isDeckCardsLoading && (
{!deckListStore.isCatalogFolderLoading && (
<div
className={css({
display: "flex",
Expand Down Expand Up @@ -208,9 +205,10 @@ export const FolderPreview = observer(() => {
/>
);
})}
{folder.cardsToReview.length === 0 && (
{folder.cardsToReview.length === 0 &&
!deckListStore.isCatalogItemLoading ? (
<Hint>{t("no_cards_to_review_in_deck")}</Hint>
)}
) : null}
</Flex>
</Flex>
);
Expand Down
2 changes: 1 addition & 1 deletion src/screens/shared/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export const Card = observer((props: Props) => {
color: theme.textColor,
})}
>
<div className={css({ wordBreak: 'break-word' })}>
<div className={css({ wordBreak: "break-word" })}>
<CardFieldView text={card.front} />{" "}
<CardSpeaker card={card} type={"front"} />
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/store/deck-list-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ vi.mock("../lib/telegram/storage-adapter.ts", () => {
};
});

vi.mock('../ui/notify-payment.ts', () => {
vi.mock("../ui/notify-payment.ts", () => {
return {
notifyPaymentFailed: () => {},
notifyPaymentSuccess: () => {},
};
})
});

vi.mock("../api/api.ts", () => {
return {
Expand Down
Loading

0 comments on commit c485244

Please sign in to comment.