voidcli 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {voidcli-1.1.0 → voidcli-1.2.0}/PKG-INFO +2 -2
  2. {voidcli-1.1.0 → voidcli-1.2.0}/README.md +1 -1
  3. {voidcli-1.1.0 → voidcli-1.2.0}/pyproject.toml +1 -1
  4. {voidcli-1.1.0 → voidcli-1.2.0}/void/controllers/activity.py +17 -0
  5. {voidcli-1.1.0 → voidcli-1.2.0}/void/main.py +11 -0
  6. {voidcli-1.1.0 → voidcli-1.2.0}/void/models/activity.py +25 -0
  7. voidcli-1.2.0/void/views/suspended.py +109 -0
  8. {voidcli-1.1.0 → voidcli-1.2.0}/.gitignore +0 -0
  9. {voidcli-1.1.0 → voidcli-1.2.0}/LICENSE +0 -0
  10. {voidcli-1.1.0 → voidcli-1.2.0}/void/__init__.py +0 -0
  11. {voidcli-1.1.0 → voidcli-1.2.0}/void/controllers/__init__.py +0 -0
  12. {voidcli-1.1.0 → voidcli-1.2.0}/void/controllers/category.py +0 -0
  13. {voidcli-1.1.0 → voidcli-1.2.0}/void/controllers/note.py +0 -0
  14. {voidcli-1.1.0 → voidcli-1.2.0}/void/database/__init__.py +0 -0
  15. {voidcli-1.1.0 → voidcli-1.2.0}/void/database/connection.py +0 -0
  16. {voidcli-1.1.0 → voidcli-1.2.0}/void/database/init_tables.sql +0 -0
  17. {voidcli-1.1.0 → voidcli-1.2.0}/void/models/__init__.py +0 -0
  18. {voidcli-1.1.0 → voidcli-1.2.0}/void/models/category.py +0 -0
  19. {voidcli-1.1.0 → voidcli-1.2.0}/void/models/note.py +0 -0
  20. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/__init__.py +0 -0
  21. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/activity.py +0 -0
  22. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/collection.py +0 -0
  23. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/day_note.py +0 -0
  24. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/note.py +0 -0
  25. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/note_form.py +0 -0
  26. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/note_modal.py +0 -0
  27. {voidcli-1.1.0 → voidcli-1.2.0}/void/views/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: voidcli
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Project-URL: Repository, https://github.com/NelsonUrrutia/void
5
5
  Author-email: Nelson Urrutia <nelurrutia15@gmail.com>
6
6
  License: MIT License
@@ -44,7 +44,7 @@ Description-Content-Type: text/markdown
44
44
  # **V**ital **O**ffline **I**nformation **D**iary
45
45
 
46
46
  ---
47
- ![VOID: wellcome screen, void activities, void collection, void day](https://github.com/NelsonUrrutia/void/blob/main/assets/void-banner.png)
47
+ ![VOID: welcome screen, void activities, void collection, void day](https://raw.githubusercontent.com/NelsonUrrutia/void/main/assets/void-banner.png)
48
48
 
49
49
  > **VOID** is an offline diary to be used in the terminal.
50
50
 
@@ -12,7 +12,7 @@
12
12
  # **V**ital **O**ffline **I**nformation **D**iary
13
13
 
14
14
  ---
15
- ![VOID: wellcome screen, void activities, void collection, void day](https://github.com/NelsonUrrutia/void/blob/main/assets/void-banner.png)
15
+ ![VOID: welcome screen, void activities, void collection, void day](https://raw.githubusercontent.com/NelsonUrrutia/void/main/assets/void-banner.png)
16
16
 
17
17
  > **VOID** is an offline diary to be used in the terminal.
18
18
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "voidcli"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = ""
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -42,3 +42,20 @@ class ActivityController:
42
42
  def suspend_activity(self, activity_id):
43
43
  with Activity() as ac:
44
44
  ac.suspend_activity(activity_id)
45
+
46
+ def get_suspended_activities(self) -> list[tuple]:
47
+ with Activity() as ac:
48
+ raw_activities = ac.get_suspended_activities()
49
+ activities = []
50
+ for item in raw_activities:
51
+ activities.append((item['id'], item['activity'], item['category']))
52
+ return activities
53
+
54
+ def restore_activity(self, activity_id) -> bool:
55
+ """Return False when an active activity already uses that name in its category."""
56
+ with Activity() as ac:
57
+ try:
58
+ ac.restore_activity(activity_id)
59
+ except sqlite3.IntegrityError:
60
+ return False
61
+ return True
@@ -8,6 +8,7 @@ from void.views.collection import CollectionView
8
8
  from void.views.day_note import DayNote
9
9
  from void.views.note import NoteView
10
10
  from void.views.note_form import NoteForm
11
+ from void.views.suspended import SuspendedView
11
12
  from void.views.welcome import WelcomeScreen
12
13
 
13
14
 
@@ -159,6 +160,8 @@ class VoidApp(App):
159
160
  with TabPane("VOID ACTIVITIES", id="void_activities"):
160
161
  yield ActivitiesView()
161
162
 
163
+ with TabPane("VOID SUSPENDED", id="void_suspended"):
164
+ yield SuspendedView()
162
165
 
163
166
  yield Footer()
164
167
 
@@ -171,6 +174,14 @@ class VoidApp(App):
171
174
  @on(ActivitiesView.Changed)
172
175
  async def on_activities_changed(self) -> None:
173
176
  await self.query_one(NoteView).recompose()
177
+ await self.query_one(DayNote).recompose()
178
+ self.query_one(SuspendedView).update_suspended()
179
+
180
+ @on(SuspendedView.Restored)
181
+ async def on_activity_restored(self) -> None:
182
+ await self.query_one(NoteView).recompose()
183
+ await self.query_one(DayNote).recompose()
184
+ self.query_one(ActivitiesView).update_activities()
174
185
 
175
186
  def on_mount(self) -> None:
176
187
  self.title = "VOID"
@@ -66,3 +66,28 @@ class Activity:
66
66
  WHERE id = ?
67
67
  """
68
68
  db.execute(sql, data)
69
+
70
+ def get_suspended_activities(self):
71
+ with Connection() as db:
72
+ sql = """
73
+ SELECT
74
+ activities.id as "id",
75
+ activities.name as "activity",
76
+ categories.name as "category"
77
+ FROM activities
78
+ INNER JOIN
79
+ categories on categories.id = activities.category_id
80
+ WHERE activities.is_active = 0
81
+ ORDER BY categories.name, activities.name"""
82
+ result = db.querying(sql)
83
+ return result
84
+
85
+ def restore_activity(self, activity_id):
86
+ with Connection() as db:
87
+ data = (activity_id,)
88
+ sql = """
89
+ UPDATE activities
90
+ SET is_active = 1
91
+ WHERE id = ?
92
+ """
93
+ db.execute(sql, data)
@@ -0,0 +1,109 @@
1
+ from typing import override
2
+
3
+ from textual import on
4
+ from textual.app import ComposeResult
5
+ from textual.containers import Horizontal, Vertical
6
+ from textual.message import Message
7
+ from textual.widgets import Button, DataTable, Label, Static
8
+
9
+ from void.controllers.activity import ActivityController
10
+
11
+
12
+ class SuspendedView(Static):
13
+
14
+ class Restored(Message):
15
+ """Posted when an activity is restored, so the other views can re-render."""
16
+
17
+ DEFAULT_CSS = """
18
+ #suspended_module{
19
+ height: 1fr;
20
+ }
21
+
22
+ #selected_activity{
23
+ color: $text-muted;
24
+ margin: 1 0 0 0;
25
+ }
26
+
27
+ #suspended_cta_container{
28
+ height: auto;
29
+ margin: 1 0;
30
+ }
31
+
32
+ #suspended_cta_container Button{
33
+ margin: 0 1 0 0;
34
+ }
35
+
36
+ #suspended_table{
37
+ height: 1fr;
38
+ }
39
+ """
40
+
41
+ @override
42
+ def __init__(self) -> None:
43
+ super().__init__()
44
+ self.ctrl = ActivityController()
45
+ self.selected_id: int | None = None
46
+
47
+ @override
48
+ def compose(self) -> ComposeResult:
49
+ with Vertical(classes="header"):
50
+ yield Label("VOID SUSPENDED", classes="module_title")
51
+ yield Label(self.counter_text(), id="counter")
52
+ with Vertical(classes="main_container", id="suspended_module"):
53
+ yield Label("SUSPENDED ACTIVITIES", classes="module_title")
54
+ yield Label(self.selected_text(), id="selected_activity")
55
+ with Horizontal(id="suspended_cta_container"):
56
+ yield Button(label="RESTORE", flat=True, id="restore_activity", variant="success", disabled=True)
57
+ yield Button(label="CLEAR", flat=True, id="clear_selection", variant="default")
58
+ yield DataTable(id="suspended_table", cursor_type="row")
59
+
60
+ def on_mount(self) -> None:
61
+ self.restore_btn = self.query_one("#restore_activity", Button)
62
+ self.suspended_table = self.query_one("#suspended_table", DataTable)
63
+ self.suspended_table.add_columns("ID", "ACTIVITY", "CATEGORY")
64
+ self.suspended_table.add_rows(self.ctrl.get_suspended_activities())
65
+
66
+ def counter_text(self) -> str:
67
+ total = len(self.ctrl.get_suspended_activities())
68
+ activities = "activity" if total == 1 else "activities"
69
+ return f"{total} suspended {activities}"
70
+
71
+ def selected_text(self, name: str | None = None) -> str:
72
+ if name is None:
73
+ return "Select an activity to restore it."
74
+ return f"Selected: {name}"
75
+
76
+ def update_suspended(self) -> None:
77
+ self.suspended_table.clear()
78
+ self.suspended_table.add_rows(self.ctrl.get_suspended_activities())
79
+ self.query_one("#counter", Label).update(self.counter_text())
80
+ self.clear_selection()
81
+
82
+ @on(DataTable.RowSelected, "#suspended_table")
83
+ def on_suspended_row_selected(self, event: DataTable.RowSelected) -> None:
84
+ row = event.data_table.get_row(event.row_key)
85
+ self.selected_id = row[0]
86
+ self.query_one("#selected_activity", Label).update(self.selected_text(f"{row[1]} ({row[2]})"))
87
+ self.restore_btn.disabled = False
88
+
89
+ @on(Button.Pressed, "#clear_selection")
90
+ def on_clear_selection(self) -> None:
91
+ self.clear_selection()
92
+
93
+ @on(Button.Pressed, "#restore_activity")
94
+ def on_restore_activity(self) -> None:
95
+ if self.selected_id is None:
96
+ return
97
+
98
+ if not self.ctrl.restore_activity(self.selected_id):
99
+ self.notify("An active activity with that name already exists in its category", severity="error")
100
+ return
101
+
102
+ self.notify("Activity restored", severity="information")
103
+ self.update_suspended()
104
+ self.post_message(self.Restored())
105
+
106
+ def clear_selection(self) -> None:
107
+ self.selected_id = None
108
+ self.query_one("#selected_activity", Label).update(self.selected_text())
109
+ self.restore_btn.disabled = True
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes