Skip to content
This repository was archived by the owner on Sep 16, 2024. It is now read-only.

Commit 70fb3bb

Browse files
committed
feat: add keybindings to review screen
1 parent 115021b commit 70fb3bb

File tree

5 files changed

+123
-85
lines changed

5 files changed

+123
-85
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Once the TUI is displayed, you can:
7373

7474
After you've added some flashcards, select a deck in the deck tree and press `ctrl+s` to begin the review process.
7575

76+
> In the review screen you can use `space`/`enter` to show the answer and `1`, `2`, `3` to mark the question as `Bad`, `Good` or `Easy`.
77+
7678
### Other commands
7779

7880
memotica provides commands to export and import your flashcards, decks and review information in the form of CSV files. To see all the available options run:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "memotica"
3-
version = "0.4.9"
3+
version = "0.5.0"
44
description = "An easy, fast, and minimalist space repition application for the terminal."
55
authors = ["dnlzrgz <[email protected]>"]
66
license = "MIT"

src/memotica/global.tcss

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ ConfirmationModal {
6464
background: $panel 70%;
6565
}
6666

67+
.review-screen {
68+
align: center middle;
69+
70+
& > .review__question,
71+
& > .review__answer {
72+
border: round $secondary;
73+
padding: 1;
74+
width: 100%;
75+
}
76+
77+
& > .review__show,
78+
& > .review__assestment {
79+
align: center middle;
80+
layout: horizontal;
81+
}
82+
}
83+
6784
.modal {
6885
background: $background;
6986
border: round $secondary;
@@ -155,22 +172,6 @@ ConfirmationModal {
155172
align: center middle
156173
}
157174

158-
.review__screen {
159-
align: center middle;
160-
}
161-
162-
.review__screen__question,
163-
.review__screen__front,
164-
.review__screen__back {
165-
border: round $secondary;
166-
padding: 1;
167-
}
168-
169-
.review__screen__controls {
170-
layout: horizontal;
171-
align: center middle;
172-
}
173-
174175
.hide {
175176
display: none;
176177
}

src/memotica/modals/help_modal.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,10 @@
2828
- `enter`: Select a flashcard.
2929
- `backspace`: Delete the selected flashcard.
3030
- `ctrl+e`: Edit the selected flashcard.
31+
32+
### Review
33+
34+
- `enter`/`space`: Show the answer.
35+
- `1`: Marks question as Wrong.
36+
- `2`: Marks question as Good.
37+
- `3`: Marks question as Easy.

src/memotica/review_screen.py

Lines changed: 96 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from collections import deque
2+
from enum import Enum, auto
3+
from textual import events
24
from textual.app import ComposeResult
35
from textual.binding import Binding
46
from textual.containers import Container
@@ -10,6 +12,12 @@
1012
from memotica.sm2 import sm2
1113

1214

15+
class ReviewStatus(Enum):
16+
LOADING = auto()
17+
SHOW_QUESTION = auto()
18+
SHOW_ANSWER = auto()
19+
20+
1321
class ReviewScreen(Screen):
1422
BINDINGS = [
1523
Binding(
@@ -22,79 +30,113 @@ class ReviewScreen(Screen):
2230
Binding("ctrl+n", "disable_binding", "Nothing", show=False, priority=True),
2331
]
2432

25-
front_content: reactive[str] = reactive("", recompose=True)
26-
back_content: reactive[str] = reactive("", recompose=True)
33+
review_status: reactive[ReviewStatus] = reactive(ReviewStatus.LOADING)
2734

2835
def __init__(self, reviews: list[Review], *args, **kwargs):
2936
super().__init__(*args, **kwargs)
3037
self.review_queue = deque(reviews)
31-
self.current_review = self.review_queue.popleft()
32-
33-
if self.current_review.reversed:
34-
self.front_content = self.current_review.flashcard.back
35-
self.back_content = self.current_review.flashcard.front
36-
else:
37-
self.front_content = self.current_review.flashcard.front
38-
self.back_content = self.current_review.flashcard.back
38+
self.loading = True
3939

4040
def compose(self) -> ComposeResult:
4141
yield Container(
4242
Markdown(
43-
self.front_content,
44-
classes="review__screen__question",
45-
),
46-
Container(
47-
Button("Show", variant="primary", id="show"),
48-
classes="review__screen__controls",
43+
"",
44+
classes="review__question",
4945
),
50-
classes="review__screen review__screen--question",
51-
)
52-
53-
yield Container(
5446
Markdown(
55-
self.front_content,
56-
classes="review__screen__front",
47+
"",
48+
classes="review__answer hide",
5749
),
58-
Markdown(
59-
self.back_content,
60-
classes="review__screen__back",
50+
Container(
51+
Button("Show", variant="primary", id="show"),
52+
classes="review__show",
6153
),
6254
Container(
63-
Button("Wrong", variant="error"),
64-
Button("Good", variant="warning"),
65-
Button("Easy", variant="success"),
66-
classes="review__screen__controls",
55+
Button("Wrong", variant="error", id="wrong"),
56+
Button("Good", variant="warning", id="good"),
57+
Button("Easy", variant="success", id="easy"),
58+
classes="review__assestment hide",
6759
),
68-
classes="review__screen review__screen--answer hide",
60+
classes="review-screen",
6961
)
7062

7163
yield Footer()
7264

73-
def action_disable_binding(self) -> None:
74-
return None
75-
76-
def on_button_pressed(self, event: Button.Pressed) -> None:
77-
self.answer = self.query(".review__screen--answer")
78-
self.question = self.query(".review__screen--question")
65+
def on_mount(self) -> None:
66+
self.question = self.query(".review__question").only_one()
67+
self.answer = self.query(".review__answer").only_one()
68+
self.show_button = self.query(".review__show").only_one()
69+
self.assestment_buttons = self.query(".review__assestment").only_one()
7970

80-
button_label = f"{event.button.label}"
71+
self.load_next()
8172

82-
if button_label == "Show":
83-
self.answer.remove_class("hide")
84-
self.question.add_class("hide")
73+
def on_button_pressed(self, event: Button.Pressed) -> None:
74+
button_id = event.button.id
75+
if button_id == "show":
76+
self.review_status = ReviewStatus.SHOW_ANSWER
8577
else:
86-
if button_label == "Easy":
78+
if button_id == "easy":
8779
self.update_review(5)
88-
elif button_label == "Good":
80+
elif button_id == "good":
8981
self.update_review(3)
9082
else:
9183
self.update_review()
9284

93-
self.load_next_review()
94-
self.answer.add_class("hide")
95-
self.question.remove_class("hide")
85+
self.load_next()
86+
87+
def on_key(self, event: events.Key) -> None:
88+
key = event.key
89+
if (
90+
key == "space" or key == "enter"
91+
) and self.review_status == ReviewStatus.SHOW_QUESTION:
92+
self.review_status = ReviewStatus.SHOW_ANSWER
93+
94+
if key in ["1", "2", "3"] and self.review_status == ReviewStatus.SHOW_ANSWER:
95+
if key == "1":
96+
self.update_review()
97+
elif key == "2":
98+
self.update_review(3)
99+
elif key == "3":
100+
self.update_review(5)
101+
102+
self.load_next()
96103

97-
def load_next_review(self) -> None:
104+
def action_disable_binding(self) -> None:
105+
return None
106+
107+
def update_review(self, q: int = 0) -> None:
108+
(n, ef, i) = sm2(
109+
self.current_question.repetitions,
110+
self.current_question.ef,
111+
self.current_question.interval,
112+
q,
113+
)
114+
115+
self.post_message(UpdateReview(self.current_question.id, n, ef, i))
116+
117+
if q < 3:
118+
self.current_question.repetitions = n
119+
self.current_question.ef = ef
120+
self.current_question.interval = i
121+
122+
self.review_queue.append(self.current_question)
123+
124+
def watch_review_status(self, _: ReviewStatus, new_status: ReviewStatus) -> None:
125+
if new_status == ReviewStatus.LOADING:
126+
self.loading = True
127+
else:
128+
self.loading = False
129+
130+
if new_status == ReviewStatus.SHOW_QUESTION:
131+
self.show_button.remove_class("hide")
132+
self.answer.add_class("hide")
133+
self.assestment_buttons.add_class("hide")
134+
else:
135+
self.show_button.add_class("hide")
136+
self.answer.remove_class("hide")
137+
self.assestment_buttons.remove_class("hide")
138+
139+
def load_next(self) -> None:
98140
if not self.review_queue:
99141
self.notify(
100142
"There are no more cards to review. Well done!",
@@ -104,28 +146,14 @@ def load_next_review(self) -> None:
104146
self.app.pop_screen()
105147
return
106148

107-
self.current_review = self.review_queue.popleft()
149+
self.review_status = ReviewStatus.LOADING
108150

109-
if self.current_review.reversed:
110-
self.front_content = self.current_review.flashcard.back
111-
self.back_content = self.current_review.flashcard.front
151+
self.current_question = self.review_queue.popleft()
152+
if self.current_question.reversed:
153+
self.question.update(self.current_question.flashcard.back)
154+
self.answer.update(self.current_question.flashcard.front)
112155
else:
113-
self.front_content = self.current_review.flashcard.front
114-
self.back_content = self.current_review.flashcard.back
115-
116-
def update_review(self, q: int = 0) -> None:
117-
(n, ef, i) = sm2(
118-
self.current_review.repetitions,
119-
self.current_review.ef,
120-
self.current_review.interval,
121-
q,
122-
)
123-
124-
self.post_message(UpdateReview(self.current_review.id, n, ef, i))
125-
126-
if q < 3:
127-
self.current_review.repetitions = n
128-
self.current_review.ef = ef
129-
self.current_review.interval = i
156+
self.question.update(self.current_question.flashcard.front)
157+
self.answer.update(self.current_question.flashcard.back)
130158

131-
self.review_queue.append(self.current_review)
159+
self.review_status = ReviewStatus.SHOW_QUESTION

0 commit comments

Comments
 (0)