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

Commit

Permalink
feat: add keybindings to review screen
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlzrgz committed Jul 14, 2024
1 parent 115021b commit 70fb3bb
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 85 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Once the TUI is displayed, you can:

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

> 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`.
### Other commands

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:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "memotica"
version = "0.4.9"
version = "0.5.0"
description = "An easy, fast, and minimalist space repition application for the terminal."
authors = ["dnlzrgz <24715931+dnlzrgz@users.noreply.github.com>"]
license = "MIT"
Expand Down
33 changes: 17 additions & 16 deletions src/memotica/global.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ ConfirmationModal {
background: $panel 70%;
}

.review-screen {
align: center middle;

& > .review__question,
& > .review__answer {
border: round $secondary;
padding: 1;
width: 100%;
}

& > .review__show,
& > .review__assestment {
align: center middle;
layout: horizontal;
}
}

.modal {
background: $background;
border: round $secondary;
Expand Down Expand Up @@ -155,22 +172,6 @@ ConfirmationModal {
align: center middle
}

.review__screen {
align: center middle;
}

.review__screen__question,
.review__screen__front,
.review__screen__back {
border: round $secondary;
padding: 1;
}

.review__screen__controls {
layout: horizontal;
align: center middle;
}

.hide {
display: none;
}
7 changes: 7 additions & 0 deletions src/memotica/modals/help_modal.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@
- `enter`: Select a flashcard.
- `backspace`: Delete the selected flashcard.
- `ctrl+e`: Edit the selected flashcard.

### Review

- `enter`/`space`: Show the answer.
- `1`: Marks question as Wrong.
- `2`: Marks question as Good.
- `3`: Marks question as Easy.
164 changes: 96 additions & 68 deletions src/memotica/review_screen.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from collections import deque
from enum import Enum, auto
from textual import events
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Container
Expand All @@ -10,6 +12,12 @@
from memotica.sm2 import sm2


class ReviewStatus(Enum):
LOADING = auto()
SHOW_QUESTION = auto()
SHOW_ANSWER = auto()


class ReviewScreen(Screen):
BINDINGS = [
Binding(
Expand All @@ -22,79 +30,113 @@ class ReviewScreen(Screen):
Binding("ctrl+n", "disable_binding", "Nothing", show=False, priority=True),
]

front_content: reactive[str] = reactive("", recompose=True)
back_content: reactive[str] = reactive("", recompose=True)
review_status: reactive[ReviewStatus] = reactive(ReviewStatus.LOADING)

def __init__(self, reviews: list[Review], *args, **kwargs):
super().__init__(*args, **kwargs)
self.review_queue = deque(reviews)
self.current_review = self.review_queue.popleft()

if self.current_review.reversed:
self.front_content = self.current_review.flashcard.back
self.back_content = self.current_review.flashcard.front
else:
self.front_content = self.current_review.flashcard.front
self.back_content = self.current_review.flashcard.back
self.loading = True

def compose(self) -> ComposeResult:
yield Container(
Markdown(
self.front_content,
classes="review__screen__question",
),
Container(
Button("Show", variant="primary", id="show"),
classes="review__screen__controls",
"",
classes="review__question",
),
classes="review__screen review__screen--question",
)

yield Container(
Markdown(
self.front_content,
classes="review__screen__front",
"",
classes="review__answer hide",
),
Markdown(
self.back_content,
classes="review__screen__back",
Container(
Button("Show", variant="primary", id="show"),
classes="review__show",
),
Container(
Button("Wrong", variant="error"),
Button("Good", variant="warning"),
Button("Easy", variant="success"),
classes="review__screen__controls",
Button("Wrong", variant="error", id="wrong"),
Button("Good", variant="warning", id="good"),
Button("Easy", variant="success", id="easy"),
classes="review__assestment hide",
),
classes="review__screen review__screen--answer hide",
classes="review-screen",
)

yield Footer()

def action_disable_binding(self) -> None:
return None

def on_button_pressed(self, event: Button.Pressed) -> None:
self.answer = self.query(".review__screen--answer")
self.question = self.query(".review__screen--question")
def on_mount(self) -> None:
self.question = self.query(".review__question").only_one()
self.answer = self.query(".review__answer").only_one()
self.show_button = self.query(".review__show").only_one()
self.assestment_buttons = self.query(".review__assestment").only_one()

button_label = f"{event.button.label}"
self.load_next()

if button_label == "Show":
self.answer.remove_class("hide")
self.question.add_class("hide")
def on_button_pressed(self, event: Button.Pressed) -> None:
button_id = event.button.id
if button_id == "show":
self.review_status = ReviewStatus.SHOW_ANSWER
else:
if button_label == "Easy":
if button_id == "easy":
self.update_review(5)
elif button_label == "Good":
elif button_id == "good":
self.update_review(3)
else:
self.update_review()

self.load_next_review()
self.answer.add_class("hide")
self.question.remove_class("hide")
self.load_next()

def on_key(self, event: events.Key) -> None:
key = event.key
if (
key == "space" or key == "enter"
) and self.review_status == ReviewStatus.SHOW_QUESTION:
self.review_status = ReviewStatus.SHOW_ANSWER

if key in ["1", "2", "3"] and self.review_status == ReviewStatus.SHOW_ANSWER:
if key == "1":
self.update_review()
elif key == "2":
self.update_review(3)
elif key == "3":
self.update_review(5)

self.load_next()

def load_next_review(self) -> None:
def action_disable_binding(self) -> None:
return None

def update_review(self, q: int = 0) -> None:
(n, ef, i) = sm2(
self.current_question.repetitions,
self.current_question.ef,
self.current_question.interval,
q,
)

self.post_message(UpdateReview(self.current_question.id, n, ef, i))

if q < 3:
self.current_question.repetitions = n
self.current_question.ef = ef
self.current_question.interval = i

self.review_queue.append(self.current_question)

def watch_review_status(self, _: ReviewStatus, new_status: ReviewStatus) -> None:
if new_status == ReviewStatus.LOADING:
self.loading = True
else:
self.loading = False

if new_status == ReviewStatus.SHOW_QUESTION:
self.show_button.remove_class("hide")
self.answer.add_class("hide")
self.assestment_buttons.add_class("hide")
else:
self.show_button.add_class("hide")
self.answer.remove_class("hide")
self.assestment_buttons.remove_class("hide")

def load_next(self) -> None:
if not self.review_queue:
self.notify(
"There are no more cards to review. Well done!",
Expand All @@ -104,28 +146,14 @@ def load_next_review(self) -> None:
self.app.pop_screen()
return

self.current_review = self.review_queue.popleft()
self.review_status = ReviewStatus.LOADING

if self.current_review.reversed:
self.front_content = self.current_review.flashcard.back
self.back_content = self.current_review.flashcard.front
self.current_question = self.review_queue.popleft()
if self.current_question.reversed:
self.question.update(self.current_question.flashcard.back)
self.answer.update(self.current_question.flashcard.front)
else:
self.front_content = self.current_review.flashcard.front
self.back_content = self.current_review.flashcard.back

def update_review(self, q: int = 0) -> None:
(n, ef, i) = sm2(
self.current_review.repetitions,
self.current_review.ef,
self.current_review.interval,
q,
)

self.post_message(UpdateReview(self.current_review.id, n, ef, i))

if q < 3:
self.current_review.repetitions = n
self.current_review.ef = ef
self.current_review.interval = i
self.question.update(self.current_question.flashcard.front)
self.answer.update(self.current_question.flashcard.back)

self.review_queue.append(self.current_review)
self.review_status = ReviewStatus.SHOW_QUESTION

0 comments on commit 70fb3bb

Please sign in to comment.