quizzy 0.2.0__tar.gz → 0.3.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,6 +13,8 @@ jobs:
13
13
  - uses: astral-sh/setup-uv@v4
14
14
  with:
15
15
  version: "0.5.9"
16
+ - name: Check lockfile
17
+ run: uv lock --check
16
18
  - name: Ruff lint
17
19
  run: uv run ruff check
18
20
  - name: Ruff format
@@ -5,16 +5,19 @@ repos:
5
5
  - id: check-yaml
6
6
  - id: end-of-file-fixer
7
7
  - id: trailing-whitespace
8
- - repo: https://github.com/psf/black
9
- rev: "24.10.0"
10
- hooks:
11
- - id: black
12
8
  - repo: https://github.com/charliermarsh/ruff-pre-commit
13
9
  # Ruff version.
14
10
  rev: "v0.8.3"
15
11
  hooks:
16
- - id: ruff
12
+ - id: ruff
13
+ args: [ --fix ]
14
+ - id: ruff-format
17
15
  - repo: https://github.com/pycqa/isort
18
16
  rev: "5.13.2"
19
17
  hooks:
20
18
  - id: isort
19
+ - repo: https://github.com/astral-sh/uv-pre-commit
20
+ rev: 0.5.10
21
+ hooks:
22
+ # Update the uv lockfile
23
+ - id: uv-lock
@@ -0,0 +1,19 @@
1
+
2
+ # Changelog
3
+
4
+ ## v0.3.1
5
+
6
+ * Fixed the lockfile for this version
7
+
8
+ ## v0.3.0
9
+
10
+ * Add *Escape* keybinding to dismiss question and answer modals
11
+ * Add score modifier buttons
12
+
13
+ ## v0.2.0
14
+
15
+ * Added support for Markdown syntax in questions and answers
16
+
17
+ ## v0.1.0
18
+
19
+ * Initial release with basic functionality
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quizzy
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: A Python TUI quiz app
5
5
  Author-email: Jonas Ehrlich <jonas.ehrlich@gmail.com>
6
6
  License-Expression: MIT
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "quizzy"
4
- version = "0.2.0"
4
+ version = "0.3.1"
5
5
  description = "A Python TUI quiz app"
6
6
  authors = [{ name = "Jonas Ehrlich", email = "jonas.ehrlich@gmail.com" }]
7
7
  readme = "README.md"
@@ -3,10 +3,13 @@ from __future__ import annotations
3
3
  import argparse
4
4
  import pathlib
5
5
 
6
- from textual import app, containers, log, message, reactive, screen, widgets
6
+ from textual import app, binding, containers, log, message, reactive, screen, widgets
7
7
 
8
8
  from quizzy import __version__, models
9
9
 
10
+ NoCorrectAnswerType = type("NoCorrectAnswerType", (object,), {})
11
+ NoCorrectAnswer = NoCorrectAnswerType()
12
+
10
13
 
11
14
  def get_arg_parser() -> argparse.ArgumentParser:
12
15
  parser = argparse.ArgumentParser(prog=__name__.split(".")[0], description="A terminal quiz app")
@@ -15,9 +18,16 @@ def get_arg_parser() -> argparse.ArgumentParser:
15
18
  return parser
16
19
 
17
20
 
18
- class AnswerScreen(screen.ModalScreen[models.Team | None]):
21
+ QuestionScreenResult = models.Team | NoCorrectAnswerType | None
22
+
23
+
24
+ class AnswerScreen(screen.ModalScreen[QuestionScreenResult], can_focus=True):
19
25
  NOONE_ANSWERED_ID = "__noone-answered"
20
26
 
27
+ BINDINGS = [
28
+ binding.Binding("escape", "no_correct_answer", "Dismiss", key_display="Esc"),
29
+ ]
30
+
21
31
  def __init__(self, category: str, question: models.Question, teams: list[models.Team]) -> None:
22
32
  super().__init__(classes="question-answer-screen")
23
33
  self.category = category
@@ -32,7 +42,7 @@ class AnswerScreen(screen.ModalScreen[models.Team | None]):
32
42
  answer_widget = widgets.Markdown(self.question.answer, id="answer")
33
43
  answer_widget.border_title = "Answer"
34
44
 
35
- whoanswered = containers.HorizontalGroup(
45
+ who_answered = containers.HorizontalGroup(
36
46
  *[
37
47
  containers.Vertical(widgets.Button(team.name, id=team_id, variant="primary"))
38
48
  for team_id, team in self.teams.items()
@@ -40,12 +50,12 @@ class AnswerScreen(screen.ModalScreen[models.Team | None]):
40
50
  id="who-answered",
41
51
  classes="horizontal-100",
42
52
  )
43
- whoanswered.border_title = "Who Answered Correctly?"
53
+ who_answered.border_title = "Who Answered Correctly?"
44
54
 
45
55
  container = containers.Grid(
46
56
  question_widget,
47
57
  answer_widget,
48
- whoanswered,
58
+ who_answered,
49
59
  containers.Horizontal(
50
60
  widgets.Button(
51
61
  "😭 No one answered correctly 😭", id=self.NOONE_ANSWERED_ID, variant="error", classes="button-100"
@@ -57,18 +67,25 @@ class AnswerScreen(screen.ModalScreen[models.Team | None]):
57
67
  )
58
68
 
59
69
  container.border_title = f"{self.category} - {self.question.value} points"
70
+ yield widgets.Footer()
60
71
  yield container
61
72
 
73
+ def action_no_correct_answer(self) -> None:
74
+ self.dismiss(NoCorrectAnswer)
75
+
62
76
  def on_button_pressed(self, event: widgets.Button.Pressed) -> None:
63
77
  if event.button.id == self.NOONE_ANSWERED_ID:
64
- self.dismiss(None)
78
+ self.dismiss(NoCorrectAnswer)
65
79
  elif event.button.id in self.teams:
66
80
  team = self.teams[event.button.id]
67
81
  self.dismiss(team)
68
82
 
69
83
 
70
- class QuestionScreen(screen.ModalScreen[models.Team | None]):
84
+ class QuestionScreen(screen.ModalScreen[QuestionScreenResult], can_focus=True):
71
85
  SHOW_ANSWER_ID = "show-answer"
86
+ BINDINGS = [
87
+ binding.Binding("escape", "dismiss(None)", "Dismiss"),
88
+ ]
72
89
 
73
90
  def __init__(self, category: str, question: models.Question, teams: list[models.Team]) -> None:
74
91
  super().__init__(classes="question-answer-screen")
@@ -91,17 +108,22 @@ class QuestionScreen(screen.ModalScreen[models.Team | None]):
91
108
  )
92
109
 
93
110
  container.border_title = f"{self.category} - {self.question.value} points"
111
+ yield widgets.Footer()
94
112
  yield container
95
113
 
96
114
  def on_button_pressed(self, event: widgets.Button.Pressed) -> None:
97
- def dismiss(team: models.Team | None) -> None:
115
+ def dismiss(team: QuestionScreenResult) -> None:
98
116
  self.dismiss(team)
99
117
 
100
118
  if event.button.id == self.SHOW_ANSWER_ID:
119
+ event.stop()
101
120
  self.app.push_screen(AnswerScreen(self.category, self.question, self.teams), dismiss)
102
121
 
103
122
 
104
- class TeamScore(containers.Vertical):
123
+ class TeamScore(containers.Horizontal):
124
+ MODIFIER_BUTTON_VALUE = 100
125
+ _ADD_BUTTON_ID = f"add-{MODIFIER_BUTTON_VALUE}"
126
+ _SUBTRACT_BUTTON_ID = f"subtract-{MODIFIER_BUTTON_VALUE}"
105
127
  score = reactive.reactive(0, recompose=True)
106
128
 
107
129
  def __init__(self, team: models.Team) -> None:
@@ -112,6 +134,22 @@ class TeamScore(containers.Vertical):
112
134
 
113
135
  def compose(self) -> app.ComposeResult:
114
136
  yield widgets.Static(str(self.score))
137
+ yield containers.Horizontal(
138
+ widgets.Button("+ 100", id=self._ADD_BUTTON_ID, variant="success"),
139
+ widgets.Button("- 100", id=self._SUBTRACT_BUTTON_ID, variant="error"),
140
+ classes="modifier-buttons-container",
141
+ )
142
+
143
+ def on_button_pressed(self, event: widgets.Button.Pressed) -> None:
144
+ if event.button.id == self._ADD_BUTTON_ID:
145
+ self.score += self.MODIFIER_BUTTON_VALUE
146
+ event.stop()
147
+ elif event.button.id == self._SUBTRACT_BUTTON_ID:
148
+ if self.score <= self.MODIFIER_BUTTON_VALUE:
149
+ self.score = 0
150
+ else:
151
+ self.score -= self.MODIFIER_BUTTON_VALUE
152
+ event.stop()
115
153
 
116
154
  def watch_score(self, score: int) -> None:
117
155
  """
@@ -152,13 +190,14 @@ class QuestionButton(widgets.Button):
152
190
  self.disabled = question.answered
153
191
 
154
192
  def on_click(self) -> None:
155
- # First, disable the button to prevent multiple clicks
156
- self.disabled = True
157
- self.question.answered = True
158
-
159
- def wait_for_result(team: models.Team | None) -> None:
193
+ def wait_for_result(team: QuestionScreenResult) -> None:
160
194
  if team is None:
161
- log("question-button: No-one answered the question")
195
+ return
196
+ # First, disable the button to prevent multiple clicks
197
+ self.disabled = True
198
+ self.question.answered = True
199
+ if isinstance(team, NoCorrectAnswerType):
200
+ log("question-button: No one answered the question")
162
201
  else:
163
202
  log(f"question-button: {team.id} answered the question")
164
203
  self.post_message(self.Answered(team, self.question.value))
@@ -206,3 +245,6 @@ class QuizzyApp(app.App[None]):
206
245
 
207
246
  def on_question_button_answered(self, event: QuestionButton.Answered) -> None:
208
247
  self.scoreboard_widget.update_team_score(event.team.id, event.value)
248
+
249
+ def on_mount(self) -> None:
250
+ self.theme = "textual-light"
@@ -1,8 +1,28 @@
1
1
  TeamScore {
2
- border: solid $primary;
2
+ border: round $primary;
3
3
  align: center top;
4
4
  height: 3;
5
5
  margin: 1;
6
+
7
+ Static {
8
+ max-width: 50%;
9
+ }
10
+
11
+ Horizontal.modifier-buttons-container {
12
+ align: right middle;
13
+
14
+ Button {
15
+ margin: 0 2;
16
+ padding: 0;
17
+ min-width: 8;
18
+ border: none;
19
+ width: 8;
20
+ &:focus {
21
+ /* Disable reversing for text of focussed buttons in the team score row */
22
+ text-style: bold;
23
+ }
24
+ }
25
+ }
6
26
  }
7
27
 
8
28
 
@@ -15,20 +35,20 @@ TeamScore {
15
35
  height: 80%;
16
36
  min-height: 40;
17
37
  padding: 0 2;
18
- border: solid $primary;
38
+ border: round $primary;
19
39
  background: $surface;
20
40
  content-align: center middle;
21
41
 
22
42
  #question {
23
43
  content-align: center middle;
24
44
  padding: 2;
25
- border: solid $foreground 80%;
45
+ border: round $foreground 80%;
26
46
  }
27
47
 
28
48
  #answer {
29
49
  content-align: center middle;
30
50
  padding: 2;
31
- border: solid $success 80%;
51
+ border: round $success 80%;
32
52
 
33
53
  }
34
54
  }
@@ -59,7 +79,7 @@ AnswerScreen {
59
79
  grid-rows: 40% 40% 5 4;
60
80
 
61
81
  #who-answered {
62
- border: solid $foreground 80%;
82
+ border: round $foreground 80%;
63
83
  Button {
64
84
  margin: 0 1;
65
85
  width: 100%;
@@ -840,7 +840,7 @@ wheels = [
840
840
 
841
841
  [[package]]
842
842
  name = "quizzy"
843
- version = "0.2.0"
843
+ version = "0.3.1"
844
844
  source = { editable = "." }
845
845
  dependencies = [
846
846
  { name = "pydantic" },
quizzy-0.2.0/CHANGELOG.md DELETED
@@ -1,9 +0,0 @@
1
- # Changelog
2
-
3
- ## v0.2.0
4
-
5
- * Added support for Markdown syntax in questions and answers
6
-
7
- ## v0.1.0
8
-
9
- * Initial release with basic functionality
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes