Skip to content

Commit

Permalink
Deck categories (#25)
Browse files Browse the repository at this point in the history
* Duplicate deck

* Deck category
  • Loading branch information
kubk authored Dec 14, 2023
1 parent 747ee59 commit 6bbcc65
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 17 deletions.
5 changes: 5 additions & 0 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} 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";

export const healthRequest = () => {
return request<HealthResponse>("/health");
Expand All @@ -46,6 +47,10 @@ export const addDeckToMineRequest = (body: AddDeckToMineRequest) => {
);
};

export const apiDuplicateDeckRequest = (deckId: number) => {
return request<CopyDeckResponse>(`/duplicate-deck?deck_id=${deckId}`, "POST");
};

export const userSettingsRequest = (body: UserSettingsRequest) => {
return request<UserSettingsResponse, UserSettingsRequest>(
"/user-settings",
Expand Down
1 change: 1 addition & 0 deletions src/screens/deck-catalog/deck-added-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DeckAddedLabel = () => {
fontStyle: "normal",
padding: "0 8px",
borderRadius: theme.borderRadius,
backgroundColor: theme.secondaryBgColor,
border: "1px solid " + theme.linkColor,
color: theme.linkColor,
})}
Expand Down
20 changes: 18 additions & 2 deletions src/screens/deck-review/deck-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useMainButton } from "../../lib/telegram/use-main-button.tsx";
import { showConfirm } from "../../lib/telegram/show-confirm.ts";
import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx";
import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx";
import { apiDuplicateDeckRequest } from "../../api/api.ts";

export const DeckPreview = observer(() => {
const reviewStore = useReviewStore();
Expand Down Expand Up @@ -110,7 +111,7 @@ export const DeckPreview = observer(() => {
gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
})}
>
{deckListStore.myId && deck.author_id === deckListStore.myId ? (
{deckListStore.canEditDeck(deck) ? (
<ButtonSideAligned
icon={"mdi-plus-circle mdi-24px"}
outline
Expand All @@ -124,7 +125,22 @@ export const DeckPreview = observer(() => {
Add card
</ButtonSideAligned>
) : null}
{deckListStore.myId && deck.author_id === deckListStore.myId ? (
{deckListStore.user?.is_admin && (
<ButtonSideAligned
icon={"mdi-content-duplicate mdi-24px"}
outline
onClick={() => {
showConfirm("Are you sure to duplicate this deck?").then(() => {
apiDuplicateDeckRequest(deck.id).then(() => {
screenStore.go({ type: "main" });
});
});
}}
>
Duplicate
</ButtonSideAligned>
)}
{deckListStore.canEditDeck(deck) ? (
<ButtonSideAligned
icon={"mdi-pencil-circle mdi-24px"}
outline
Expand Down
23 changes: 14 additions & 9 deletions src/store/deck-form-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,23 @@ vi.mock("./deck-list-store.ts", () => {
},
];

const myDecks = [
{
id: 1,
cardsToReview: deckCardsMock.slice(0, 2),
share_id: null,
deck_card: deckCardsMock,
name: "Test",
},
] as DeckWithCardsWithReviewType[];

return {
deckListStore: {
replaceDeck: () => {},
myDecks: [
{
id: 1,
cardsToReview: deckCardsMock.slice(0, 2),
share_id: null,
deck_card: deckCardsMock,
name: "Test",
},
] as DeckWithCardsWithReviewType[],
searchDeckById: (id: number) => {
return myDecks.find((deck) => deck.id === id);
},
myDecks: myDecks
},
};
});
Expand Down
4 changes: 1 addition & 3 deletions src/store/deck-form-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ export class DeckFormStore {
assert(screen.type === "deckForm");

if (screen.deckId) {
const deck = deckListStore.myDecks.find(
(myDeck) => myDeck.id === screen.deckId,
);
const deck = deckListStore.searchDeckById(screen.deckId);
assert(deck, "Deck not found in deckListStore");
this.form = createUpdateForm(screen.deckId, deck);
} else {
Expand Down
1 change: 1 addition & 0 deletions src/store/deck-list-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ vi.mock("../api/api.ts", () => {
last_name: "Testov",
last_reminded_date: null,
is_speaking_card_enabled: false,
is_admin: false,
username: "test",
},
cardsToReview: [
Expand Down
29 changes: 26 additions & 3 deletions src/store/deck-list-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ export class DeckListStore {
isDeckCardsLoading = false;

constructor() {
makeAutoObservable(this, {}, { autoBind: true });
makeAutoObservable(
this,
{
canEditDeck: false,
searchDeckById: false,
},
{ autoBind: true },
);
}

loadFirstTime(startParam?: string) {
Expand Down Expand Up @@ -188,6 +195,15 @@ export class DeckListStore {
return this.user?.id;
}

canEditDeck(deck: DeckWithCardsWithReviewType) {
const isAdmin = this.user?.is_admin ?? false;
if (isAdmin) {
return true;
}

return deckListStore.myId && deck.author_id === deckListStore.myId;
}

openDeckFromCatalog(deck: DeckWithCardsDbType, isMine: boolean) {
assert(this.myInfo);
if (isMine) {
Expand All @@ -211,15 +227,22 @@ export class DeckListStore {
);
}

searchDeckById(deckId: number) {
if (!this.myInfo) {
return null;
}
const decksToSearch = this.myInfo.myDecks.concat(this.publicDecks);
return decksToSearch.find((deck) => deck.id === deckId);
}

get selectedDeck(): DeckWithCardsWithReviewType | null {
const screen = screenStore.screen;
assert(screen.type === "deckPublic" || screen.type === "deckMine");
if (!screen.deckId || !this.myInfo) {
return null;
}

const decksToSearch = this.myInfo.myDecks.concat(this.publicDecks);
const deck = decksToSearch.find((deck) => deck.id === screen.deckId);
const deck = this.searchDeckById(screen.deckId);
if (!deck) {
return null;
}
Expand Down
45 changes: 45 additions & 0 deletions src/ui/deck-category-logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import { css } from "@emotion/css";
import WebApp from "@twa-dev/sdk";

// Windows doesn't support flag emojis, so we replace them with images
export const replaceFlagEmojiOnWindows = (logo: string) => {
switch (logo) {
case "🇬🇧":
return "gb";
default:
return null;
}
};

const supportsEmojiFlag = WebApp.platform !== "tdesktop";

type Props = { logo: string; categoryName: string };

export const DeckCategoryLogo = (props: Props) => {
const { logo, categoryName } = props;

if (supportsEmojiFlag) {
return logo;
}

const replacedFlag = replaceFlagEmojiOnWindows(logo);

return (
<span
className={css({ marginRight: 6 })}
title={`Deck category is ${categoryName}`}
>
{replacedFlag ? (
<img
src={`https://flagcdn.com/16x12/${replacedFlag}.png`}
width="16"
height="12"
alt={logo}
/>
) : (
logo
)}
</span>
);
};
9 changes: 9 additions & 0 deletions src/ui/deck-list-item-with-description.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import { css } from "@emotion/css";
import { theme } from "./theme.tsx";
import LinesEllipsis from "react-lines-ellipsis";
import React from "react";
import { DeckCategoryLogo } from "./deck-category-logo.tsx";

type Props = {
deck: {
id: number;
name: string;
description: string | null;
available_in: string | null;
deck_category?: { name: string; logo: string | null } | null;
};
onClick: () => void;
titleRightSlot?: React.ReactNode;
Expand Down Expand Up @@ -41,6 +44,12 @@ export const DeckListItemWithDescription = observer((props: Props) => {
position: "relative",
})}
>
{deck.deck_category?.logo ? (
<DeckCategoryLogo
logo={deck.deck_category.logo}
categoryName={deck.deck_category.name}
/>
) : null}
{deck.name}
{titleRightSlot}
</div>
Expand Down

0 comments on commit 6bbcc65

Please sign in to comment.