Skip to content

Commit

Permalink
Add deck from folder (#35)
Browse files Browse the repository at this point in the history
Add deck from folder
  • Loading branch information
kubk authored Jan 8, 2024
1 parent 3ef3cc8 commit 1834c3d
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 67 deletions.
34 changes: 31 additions & 3 deletions functions/upsert-deck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,30 @@ import { DatabaseException } from "./db/database-exception.ts";
import { createJsonResponse } from "./lib/json-response/create-json-response.ts";
import {
deckSchema,
deckWithCardsSchema,
DeckWithCardsDbType,
} from "./db/deck/decks-with-cards-schema.ts";
import { addDeckToMineDb } from "./db/deck/add-deck-to-mine-db.ts";
import { createForbiddenRequestResponse } from "./lib/json-response/create-forbidden-request-response.ts";
import { getDeckByIdAndAuthorId } from "./db/deck/get-deck-by-id-and-author-id.ts";
import { shortUniqueId } from "./lib/short-unique-id/short-unique-id.ts";
import { Database } from "./db/databaseTypes.ts";
import { getDeckWithCardsById } from "./db/deck/get-deck-with-cards-by-id-db.ts";
import {
getFoldersWithDecksDb,
UserFoldersDbType,
} from "./db/folder/get-folders-with-decks-db.tsx";
import {
CardToReviewDbType,
getCardsToReviewDb,
} from "./db/deck/get-cards-to-review-db.ts";

const requestSchema = z.object({
id: z.number().nullable().optional(),
title: z.string(),
description: z.string().nullable().optional(),
speakLocale: z.string().nullable().optional(),
speakField: z.string().nullable().optional(),
folderId: z.number().nullable().optional(),
cards: z.array(
z.object({
front: z.string(),
Expand All @@ -35,7 +44,11 @@ const requestSchema = z.object({
});

export type UpsertDeckRequest = z.infer<typeof requestSchema>;
export type UpsertDeckResponse = z.infer<typeof deckWithCardsSchema>;
export type UpsertDeckResponse = {
deck: DeckWithCardsDbType;
folders: UserFoldersDbType[];
cardsToReview: CardToReviewDbType[];
};

type InsertDeckDatabaseType = Database["public"]["Tables"]["deck"]["Insert"];
type DeckRow = Database["public"]["Tables"]["deck"]["Row"];
Expand Down Expand Up @@ -116,15 +129,30 @@ export const onRequestPost = handleError(async ({ request, env }) => {
throw new DatabaseException(createCardsResult.error);
}

// If create deck
if (!input.data.id) {
await addDeckToMineDb(envSafe, {
user_id: user.id,
deck_id: upsertedDeck.id,
});

// If folderId passed - add the new deck to folder
if (input.data.folderId) {
await db.from("deck_folder").upsert({
deck_id: upsertedDeck.id,
folder_id: input.data.folderId,
});
}
}

const [deck, folders, cardsToReview] = await Promise.all([
getDeckWithCardsById(envSafe, upsertedDeck.id),
getFoldersWithDecksDb(envSafe, user.id),
getCardsToReviewDb(envSafe, user.id),
]);

return createJsonResponse<UpsertDeckResponse>(
await getDeckWithCardsById(envSafe, upsertedDeck.id),
{ deck, folders, cardsToReview },
200,
);
});
28 changes: 26 additions & 2 deletions src/screens/deck-form/deck-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,31 @@ export const DeckForm = observer(() => {
}

return (
<Screen title={screen.deckId ? t("edit_deck") : t("add_deck")}>
<Screen
title={screen.deckId ? t("edit_deck") : t("add_deck")}
subtitle={
screen.folder ? (
<div className={css({ textAlign: "center", fontSize: 14 })}>
to folder{" "}
<button
onClick={() => {
assert(screen.folder, "Folder should be defined");
screenStore.go({
type: "folderPreview",
folderId: screen.folder.id,
});
}}
className={cx(
reset.button,
css({ fontSize: "inherit", color: theme.linkColor }),
)}
>
{screen.folder.name}
</button>
</div>
) : undefined
}
>
<Label text={t("title")} isRequired>
<Input field={deckFormStore.form.title} />
</Label>
Expand Down Expand Up @@ -155,7 +179,7 @@ export const DeckForm = observer(() => {
onClick={() => {
assert(deckFormStore.form);
assert(deckFormStore.form.id);
deckListStore.goDeckById(deckFormStore.form.id);
deckListStore.goDeckById(deckFormStore.form.id, "main");
}}
>
{t("deck_preview")}
Expand Down
53 changes: 30 additions & 23 deletions src/screens/deck-form/store/deck-form-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,33 @@ import { isFormValid } from "../../../lib/mobx-form/form-has-error.ts";
const mapUpsertDeckRequestToResponse = (
input: UpsertDeckRequest,
): UpsertDeckResponse => ({
id: input.id || 9999,
available_in: null,
description: input.description ?? null,
created_at: new Date().toISOString(),
name: input.title,
author_id: 9999,
share_id: "share_id_mock",
is_public: false,
speak_locale: null,
speak_field: null,
deck_category: null,
category_id: null,
deck_card: input.cards.map((card) => {
assert(input.id);
return {
id: card.id || 9999,
deck_id: input.id,
created_at: new Date().toISOString(),
example: card.example ?? null,
front: card.front,
back: card.back,
};
}),
folders: [],
cardsToReview: [],
deck: {
id: input.id || 9999,
available_in: null,
description: input.description ?? null,
created_at: new Date().toISOString(),
name: input.title,
author_id: 9999,
share_id: "share_id_mock",
is_public: false,
speak_locale: null,
speak_field: null,
deck_category: null,
category_id: null,
deck_card: input.cards.map((card) => {
assert(input.id);
return {
id: card.id || 9999,
deck_id: input.id,
created_at: new Date().toISOString(),
example: card.example ?? null,
front: card.front,
back: card.back,
};
}),
}
});

const mocks = vi.hoisted(() => {
Expand All @@ -49,6 +53,7 @@ const mocks = vi.hoisted(() => {
vi.mock("./../../../store/screen-store", () => {
return {
screenStore: {
go: () => {},
screen: {
type: "deckForm",
deckId: 1,
Expand Down Expand Up @@ -98,6 +103,8 @@ vi.mock("./../../../store/deck-list-store.ts", () => {
return {
deckListStore: {
replaceDeck: () => {},
updateFolders: () => {},
updateCardsToReview: () => {},
searchDeckById: (id: number) => {
return myDecks.find((deck) => deck.id === id);
},
Expand Down
16 changes: 12 additions & 4 deletions src/screens/deck-form/store/deck-form-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type DeckFormType = {
cards: CardFormType[];
speakingCardsLocale: TextField<string | null>;
speakingCardsField: TextField<DeckSpeakFieldEnum | null>;
folderId?: number;
};

export const createDeckTitleField = (value: string) => {
Expand Down Expand Up @@ -120,6 +121,7 @@ export class DeckFormStore {
cards: [],
speakingCardsLocale: new TextField(null),
speakingCardsField: new TextField(null),
folderId: screen.folder?.id ?? undefined,
};
}
}
Expand Down Expand Up @@ -330,11 +332,17 @@ export class DeckFormStore {
cards: cardsToSend,
speakLocale: this.form.speakingCardsLocale.value,
speakField: this.form.speakingCardsField.value,
folderId: this.form.folderId,
})
.then((response) => {
this.form = createUpdateForm(response.id, response);
deckListStore.replaceDeck(response);
})
.then(
action(({ deck, folders, cardsToReview }) => {
this.form = createUpdateForm(deck.id, deck);
deckListStore.replaceDeck(deck, true);
deckListStore.updateFolders(folders);
deckListStore.updateCardsToReview(cardsToReview);
screenStore.go({ type: "deckForm", deckId: deck.id });
}),
)
.finally(
action(() => {
this.isSending = false;
Expand Down
7 changes: 3 additions & 4 deletions src/screens/deck-list/main-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { Fragment } from "react";
import { observer } from "mobx-react-lite";
import { css, cx } from "@emotion/css";
import { PublicDeck } from "./public-deck.tsx";
Expand Down Expand Up @@ -55,7 +55,7 @@ export const MainScreen = observer(() => {
{deckListStore.myInfo
? deckListStore.myDeckItemsVisible.map((listItem) => {
return (
<>
<Fragment key={listItem.id}>
<MyDeckRow
onClick={() => {
if (listItem.type === "deck") {
Expand All @@ -71,7 +71,6 @@ export const MainScreen = observer(() => {
});
}
}}
key={listItem.id}
item={listItem}
/>
{listItem.type === "folder" &&
Expand All @@ -93,7 +92,7 @@ export const MainScreen = observer(() => {
);
})
: null}
</>
</Fragment>
);
})
: null}
Expand Down
22 changes: 20 additions & 2 deletions src/screens/deck-review/deck-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ export const DeckPreview = observer(() => {
const reviewStore = useReviewStore();

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

useTelegramProgress(() => deckListStore.isDeckCardsLoading);
Expand Down Expand Up @@ -64,7 +71,18 @@ export const DeckPreview = observer(() => {
textAlign: "center",
})}
>
<h3 className={css({ paddingTop: 12 })}>{deck.name}</h3>
<h3
className={css({
paddingTop: 12,
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: 6,
})}
>
<i className={"mdi mdi-cards-outline"} title={t("deck")} />
{deck.name}
</h3>
</div>
<div>
<div>{deck.description}</div>
Expand Down
35 changes: 32 additions & 3 deletions src/screens/folder-review/folder-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const FolderPreview = observer(() => {
t("review_folder"),
() => {
const folder = deckListStore.selectedFolder;
assert(folder);
assert(folder, "Folder should be selected before review");
reviewStore.startFolderReview(
folder.decks,
userStore.isSpeakingCardsEnabled,
Expand Down Expand Up @@ -70,7 +70,18 @@ export const FolderPreview = observer(() => {
textAlign: "center",
})}
>
<h3 className={css({ paddingTop: 12 })}>{folder.name}</h3>
<h3
className={css({
paddingTop: 12,
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: 6,
})}
>
<i className={"mdi mdi-folder-open-outline"} title={t("folder")} />
{folder.name}
</h3>
</div>
<div>
<div>{folder.description}</div>
Expand Down Expand Up @@ -122,6 +133,23 @@ export const FolderPreview = observer(() => {
gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))",
})}
>
{deckListStore.canEditFolder ? (
<ButtonSideAligned
icon={"mdi-plus-circle mdi-24px"}
outline
onClick={() => {
screenStore.go({
type: "deckForm",
folder: {
id: folder.id,
name: folder.name,
},
});
}}
>
{t("add_deck_short")}
</ButtonSideAligned>
) : null}
{deckListStore.canEditFolder ? (
<ButtonSideAligned
icon={"mdi-pencil-circle mdi-24px"}
Expand Down Expand Up @@ -157,9 +185,10 @@ export const FolderPreview = observer(() => {
})}
>
<ListHeader text={t("decks")} />
{folder.decks.map((deck) => {
{folder.decks.map((deck, i) => {
return (
<SettingsRow
key={i}
onClick={() => {
deckListStore.goDeckById(deck.id);
}}
Expand Down
8 changes: 6 additions & 2 deletions src/screens/shared/screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { css } from "@emotion/css";
type Props = {
children: ReactNode;
title: string;
subtitle?: ReactNode;
};

export const Screen = observer((props: Props) => {
const { children, title } = props;
const { children, title, subtitle } = props;
return (
<div
className={css({
Expand All @@ -19,7 +20,10 @@ export const Screen = observer((props: Props) => {
marginBottom: 16,
})}
>
<h3 className={css({ textAlign: "center" })}>{title}</h3>
<div>
<h3 className={css({ textAlign: "center"})}>{title}</h3>
{subtitle}
</div>
{children}
</div>
);
Expand Down
Loading

0 comments on commit 1834c3d

Please sign in to comment.