Skip to content

Commit 94e69ae

Browse files
committed
feat: re-implement move deck
improve debug modal, refactoring
1 parent 2076549 commit 94e69ae

File tree

12 files changed

+248
-58
lines changed

12 files changed

+248
-58
lines changed

src/app/deck/DebugDeckModal.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { useStatesOf } from "@/logic/card/hooks/useStatesOf";
2+
import { useSubDecks } from "@/logic/deck/hooks/useSubDecks";
3+
import { useSuperDecks } from "@/logic/deck/hooks/useSuperDecks";
24
import { NoteType } from "@/logic/note/note";
35
import { Anchor, Modal, Stack, Text } from "@mantine/core";
46
import { State } from "fsrs.js";
@@ -19,6 +21,11 @@ function DebugDeckModal({
1921
cards,
2022
}: DebugDeckModalProps) {
2123
const states = useStatesOf(cards);
24+
25+
const [superDecks] = useSuperDecks(deck);
26+
27+
const [subDecks] = useSubDecks(deck);
28+
2229
return (
2330
<Modal opened={opened} onClose={() => setOpened(false)} title="Debug">
2431
<Stack justify="space-between">
@@ -32,14 +39,20 @@ function DebugDeckModal({
3239
</Text>
3340
<Text fz="xs">
3441
<b>SubDecks: </b>
35-
{deck.subDecks.map((subDeckId) => (
36-
<span key={subDeckId}>
37-
<Anchor href={"/deck/" + subDeckId}>{subDeckId}</Anchor>,{" "}
42+
{subDecks?.map((s) => (
43+
<span key={s.id}>
44+
<Anchor href={"/deck/" + s.id}>{s.name}</Anchor>,{" "}
3845
</span>
3946
))}
4047
</Text>
4148
<Text fz="xs">
42-
<b>SuperDecks: </b>"{deck.superDecks}"
49+
<b>SuperDecks: </b>"
50+
{superDecks?.map((s) => (
51+
<span key={s.id}>
52+
<Anchor href={"/deck/" + s.id}>{s.name}</Anchor>,{" "}
53+
</span>
54+
))}
55+
"
4356
</Text>
4457
<Text fz="xs">
4558
<b>Cards: </b>"

src/app/deck/DeckMenu.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { useCallback, useState } from "react";
2222
import { useTranslation } from "react-i18next";
2323
import { useNavigate } from "react-router-dom";
2424
import DebugDeckModal from "./DebugDeckModal";
25+
import MoveDeckModal from "./MoveDeckModal";
2526
import RenameDeckModal from "./RenameDeckModal";
2627

2728
interface DeckMenuProps {
@@ -45,11 +46,12 @@ function DeckMenu({
4546
const [developerMode] = useSetting("developerMode");
4647

4748
const [deleteModalOpened, setDeleteModalOpened] = useState<boolean>(false);
49+
const [moveModalOpened, setMoveModalOpened] = useState<boolean>(false);
4850
const [renameModalOpened, setRenameModalOpened] = useState<boolean>(false);
4951
const [importModalOpened, setImportModalOpened] = useState<boolean>(false);
5052
const [debugModalOpened, setDebugModalOpened] = useState<boolean>(false);
5153

52-
const tryDeleteDeck = useCallback(async () => {
54+
const handleDelete = useCallback(async () => {
5355
if (!deck) {
5456
return;
5557
}
@@ -107,7 +109,7 @@ function DeckMenu({
107109
<Menu.Item
108110
leftSection={<IconArrowsExchange size={16} />}
109111
rightSection={showShortcutHints && <Kbd>m</Kbd>}
110-
disabled
112+
onClick={() => setMoveModalOpened(true)}
111113
>
112114
{t("deck.menu.move")}
113115
</Menu.Item>
@@ -160,7 +162,7 @@ function DeckMenu({
160162
{deck && cards && (
161163
<>
162164
<DangerousConfirmModal
163-
dangerousAction={() => tryDeleteDeck()}
165+
dangerousAction={() => handleDelete()}
164166
dangerousDependencies={[deck]}
165167
dangerousTitle={"Delete Deck"}
166168
dangerousDescription={
@@ -174,6 +176,11 @@ function DeckMenu({
174176
opened={renameModalOpened}
175177
setOpened={setRenameModalOpened}
176178
/>
179+
<MoveDeckModal
180+
deck={deck}
181+
opened={moveModalOpened}
182+
setOpened={setMoveModalOpened}
183+
/>
177184
<DebugDeckModal
178185
deck={deck}
179186
cards={cards}

src/app/deck/MoveDeckModal.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { successfullyMovedCardTo } from "@/components/Notification/Notification";
2+
import { Deck } from "@/logic/deck/deck";
3+
import { useDecks } from "@/logic/deck/hooks/useDecks";
4+
import { moveDeck } from "@/logic/deck/moveDeck";
5+
import { Button, Group, Modal, Select, Stack, Text } from "@mantine/core";
6+
import { IconArrowsExchange } from "@tabler/icons-react";
7+
import { useState } from "react";
8+
9+
interface MoveDeckModalProps {
10+
deck: Deck;
11+
opened: boolean;
12+
setOpened: Function;
13+
}
14+
15+
export default function MoveDeckModal({
16+
deck,
17+
opened,
18+
setOpened,
19+
}: MoveDeckModalProps) {
20+
const oldSuperDeck = deck.superDecks
21+
? deck.superDecks[deck.superDecks.length - 1]
22+
: null;
23+
24+
const [decks, areDecksReady] = useDecks((decks) =>
25+
decks?.filter((d) => d.id !== oldSuperDeck)
26+
);
27+
const [newDeckID, setNewDeckID] = useState<string | null>(null);
28+
29+
return (
30+
<Modal title={"Move Deck"} opened={opened} onClose={() => setOpened(false)}>
31+
<Stack>
32+
<Select
33+
searchable
34+
label="Move To"
35+
nothingFoundMessage="No Decks Found"
36+
disabled={!areDecksReady}
37+
//withinPortal
38+
data={
39+
decks?.map((deck) => ({
40+
value: deck.id,
41+
label: deck.name,
42+
})) ?? []
43+
}
44+
value={newDeckID}
45+
onChange={(value) => {
46+
setNewDeckID(value);
47+
}}
48+
/>
49+
{decks?.length === 0 && (
50+
<Text fz="sm">
51+
It seems like there are no other valid decks to move this deck to.
52+
Try creating another one.
53+
</Text>
54+
)}
55+
<Group justify="flex-end">
56+
<Button
57+
onClick={() => {
58+
const newDeck = decks?.find((deck) => deck.id === newDeckID);
59+
if (newDeck !== undefined) {
60+
moveDeck(deck.id, newDeck.id);
61+
successfullyMovedCardTo(newDeck.name);
62+
setOpened(false);
63+
} else {
64+
}
65+
}}
66+
leftSection={<IconArrowsExchange />}
67+
disabled={
68+
!areDecksReady || !newDeckID || newDeckID === oldSuperDeck
69+
}
70+
>
71+
Move Deck
72+
</Button>
73+
</Group>
74+
</Stack>
75+
</Modal>
76+
);
77+
}

src/app/editor/MoveCardModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { successfullyMovedTo } from "@/components/Notification/Notification";
1+
import { successfullyMovedCardTo } from "@/components/Notification/Notification";
22
import { Card } from "@/logic/card/card";
33
import { moveCard } from "@/logic/card/moveCard";
44
import { useDecks } from "@/logic/deck/hooks/useDecks";
@@ -56,7 +56,7 @@ export default function MoveCardModal({
5656
const newDeck = decks?.find((deck) => deck.id === newDeckID);
5757
if (newDeck !== undefined) {
5858
moveCard(card, newDeck);
59-
successfullyMovedTo(newDeck.name);
59+
successfullyMovedCardTo(newDeck.name);
6060
setOpened(false);
6161
} else {
6262
}

src/components/Notification/Notification.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function successfullyAdded() {
3131
});
3232
}
3333

34-
export function successfullyMovedTo(deckName: string) {
34+
export function successfullyMovedCardTo(deckName: string) {
3535
return notifications.show({
3636
title: "Card Moved",
3737
message: `Card moved to ${deckName}!`,
@@ -43,6 +43,18 @@ export function successfullyMovedTo(deckName: string) {
4343
});
4444
}
4545

46+
export function successfullyMovedDeckTo(deckName: string) {
47+
return notifications.show({
48+
title: "Deck Moved",
49+
message: `Deck moved to ${deckName}!`,
50+
autoClose: 1000,
51+
color: "teal",
52+
withCloseButton: false,
53+
icon: <IconArrowsExchange />,
54+
className: classes,
55+
});
56+
}
57+
4658
export function successfullyDeleted(type: "card" | "deck" | "note") {
4759
return notifications.show({
4860
title: { card: "Card Deleted", deck: "Deck Deleted", note: "Note Deleted" }[

src/logic/deck/moveDeck.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { db } from "../db";
2+
3+
export async function moveDeck(deckId: string, newSuperDeckId: string) {
4+
return db.transaction("rw", db.decks, async () => {
5+
// Get the deck with the given id
6+
const deck = await db.decks.get(deckId);
7+
if (!deck) {
8+
throw new Error(`Deck with id ${deckId} not found`);
9+
}
10+
11+
// Get the new superdeck with the given id
12+
const newSuperDeck = await db.decks.get(newSuperDeckId);
13+
if (!newSuperDeck) {
14+
throw new Error(`Deck with id ${newSuperDeckId} not found`);
15+
} else {
16+
db.decks.update(newSuperDeck.id, {
17+
subDecks: [...newSuperDeck.subDecks, deckId],
18+
});
19+
}
20+
21+
if (deck.superDecks) {
22+
const oldSuperDeck = await db.decks.get(
23+
deck.superDecks[deck.superDecks.length - 1]
24+
);
25+
26+
// remove deck from old superdeck
27+
if (oldSuperDeck) {
28+
if (oldSuperDeck.id === newSuperDeckId) {
29+
console.warn(
30+
"Tried to move deck to its current superdeck. Ignoring."
31+
);
32+
return;
33+
}
34+
35+
await db.decks.update(oldSuperDeck.id, {
36+
subDecks: oldSuperDeck.subDecks.filter(
37+
(subDeckId) => subDeckId !== deckId
38+
),
39+
});
40+
}
41+
}
42+
await db.decks.update(deck, {
43+
superDecks: [...(newSuperDeck.superDecks ?? []), newSuperDeckId],
44+
});
45+
});
46+
}

src/logic/type-implementations/double-sided/DoubleSidedNote.tsx

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,20 @@
11
import DoubleSidedCardEditor from "@/app/editor/NoteEditor/DoubleSidedCardEditor";
22
import { NoteEditorProps, NoteTypeAdapter } from "@/logic/NoteTypeAdapter";
33
import { Card, HTMLtoPreviewString } from "@/logic/card/card";
4-
import { createCardSkeleton } from "@/logic/card/createCardSkeleton";
54
import { deleteCard } from "@/logic/card/deleteCard";
6-
import { newCard } from "@/logic/card/newCard";
75
import { db } from "@/logic/db";
8-
import { Deck } from "@/logic/deck/deck";
96
import { NoteContent } from "@/logic/note/NoteContent";
10-
import { newNote } from "@/logic/note/newNote";
11-
import { NoteType } from "@/logic/note/note";
12-
import { Note } from "@/logic/note/note";
13-
import { updateNoteContent } from "@/logic/note/updateNoteContent";
7+
import { Note, NoteType } from "@/logic/note/note";
148
import common from "@/style/CommonStyles.module.css";
159
import { Divider, Stack, Title } from "@mantine/core";
10+
import createDoubleSidedNote from "./createDoubleSidedNote";
11+
import { updateDoubleSidedNote } from "./updateDoubleSidedNote";
1612

1713
export const DoubleSidedNoteTypeAdapter: NoteTypeAdapter<NoteType.DoubleSided> =
1814
{
19-
async createNote(params: { field1: string; field2: string }, deck: Deck) {
20-
function createDoubleSidedCard(
21-
noteId: string,
22-
frontIsField1: boolean,
23-
front: string
24-
) {
25-
return {
26-
...createCardSkeleton(),
27-
note: noteId,
28-
preview: HTMLtoPreviewString(front),
29-
content: {
30-
type: NoteType.DoubleSided,
31-
frontIsField1: frontIsField1,
32-
},
33-
};
34-
}
35-
return db.transaction("rw", db.notes, db.decks, db.cards, async () => {
36-
const noteId = await newNote(deck, {
37-
type: NoteType.DoubleSided,
38-
field1: params.field1,
39-
field2: params.field2,
40-
});
41-
await newCard(createDoubleSidedCard(noteId, true, params.field1), deck);
42-
await newCard(
43-
createDoubleSidedCard(noteId, false, params.field2),
44-
deck
45-
);
46-
});
47-
},
15+
createNote: createDoubleSidedNote,
4816

49-
async updateNote(
50-
params: { field1: string; field2: string },
51-
existingNote: Note<NoteType.DoubleSided>
52-
) {
53-
return db.transaction("rw", db.notes, db.cards, async () => {
54-
await updateNoteContent(existingNote.id, {
55-
type: NoteType.DoubleSided,
56-
field1: params.field1,
57-
field2: params.field2,
58-
});
59-
});
60-
},
17+
updateNote: updateDoubleSidedNote,
6118

6219
displayQuestion(
6320
card: Card<NoteType.DoubleSided>,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { HTMLtoPreviewString } from "@/logic/card/card";
2+
import { createCardSkeleton } from "@/logic/card/createCardSkeleton";
3+
import { newCard } from "@/logic/card/newCard";
4+
import { db } from "@/logic/db";
5+
import { Deck } from "@/logic/deck/deck";
6+
import { newNote } from "@/logic/note/newNote";
7+
import { NoteType } from "@/logic/note/note";
8+
9+
export default async function createDoubleSidedNote(
10+
params: { field1: string; field2: string },
11+
deck: Deck
12+
) {
13+
function createDoubleSidedCard(
14+
noteId: string,
15+
frontIsField1: boolean,
16+
front: string
17+
) {
18+
return {
19+
...createCardSkeleton(),
20+
note: noteId,
21+
preview: HTMLtoPreviewString(front),
22+
content: {
23+
type: NoteType.DoubleSided,
24+
frontIsField1: frontIsField1,
25+
},
26+
};
27+
}
28+
return db.transaction("rw", db.notes, db.decks, db.cards, async () => {
29+
const noteId = await newNote(deck, {
30+
type: NoteType.DoubleSided,
31+
field1: params.field1,
32+
field2: params.field2,
33+
});
34+
await newCard(createDoubleSidedCard(noteId, true, params.field1), deck);
35+
await newCard(createDoubleSidedCard(noteId, false, params.field2), deck);
36+
});
37+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

src/logic/type-implementations/double-sided/displayDoubleSidedNote.tsx

Whitespace-only changes.

0 commit comments

Comments
 (0)