Compare commits

...

5 Commits

Author SHA1 Message Date
3wc
eb33dfb99f Don't lose filter after moving facts 2023-10-27 16:48:03 +01:00
3wc
b06cf1fc62 Reformatting 2023-10-27 16:40:25 +01:00
3wc
36f324e5ba Code refactor 2023-10-27 16:39:58 +01:00
3wc
2b7689c840 Style datatable cursor 2023-10-27 16:29:12 +01:00
3wc
cbbf952787 Improve filtering, add "move facts" 2023-10-27 16:28:46 +01:00
3 changed files with 129 additions and 95 deletions

View File

@ -4,40 +4,15 @@ from textual.widgets import Header, Footer, DataTable, Input
from textual.containers import Horizontal, Vertical
from textual.coordinate import Coordinate
from textual.screen import Screen
from textual.reactive import reactive
from .db import DatabaseManager, Category, Activity
class ActivitiesScreen(Screen):
BINDINGS = [
("q", "quit", "Quit"),
("s", "sort", "Sort"),
("r", "refresh", "Refresh"),
("d", "delete", "Delete activity"),
("/", "filter", "Search"),
Binding(key="escape", action="cancelfilter", show=False),
]
class ListScreen(Screen):
def __init__(self, db_manager):
self.db_manager = db_manager
super().__init__()
def _refresh(self, filter_query=None):
self.table.clear()
# List activities with the count of facts
activities = Activity.list_activities(self.db_manager, filter_query)
self.table.add_rows([[
activity.category_id,
activity.category_name,
activity.id,
activity.name,
activity.facts_count,
] for activity in activities])
self.table.sort(*self.sort)
def compose(self) -> ComposeResult:
"""create child widgets for the app."""
yield Header()
@ -47,32 +22,80 @@ class ActivitiesScreen(Screen):
yield Input(id="filter")
yield Footer()
def on_mount(self) -> None:
self.table = self.query_one(DataTable)
self.table.cursor_type = "row"
self.columns = self.table.add_columns("category id","category","activity id","activity","entries")
self.sort = (self.columns[1], self.columns[3])
self._refresh()
def action_refresh(self) -> None:
self._refresh()
def action_sort(self) -> None:
self.table.cursor_type = "column"
def on_data_table_column_selected(self, event):
self.sort = (event.column_key,)
event.data_table.sort(*self.sort)
event.data_table.cursor_type = "row"
def action_filter(self) -> None:
filter_input = self.query_one("#filter")
filter_input.display = True
self._refresh(filter_input.value)
filter_input.focus()
print(filter_input)
def on_input_submitted(self, event):
self.table.focus()
def action_cancelfilter(self) -> None:
filter_input = self.query_one("#filter")
filter_input.display = False
filter_input.clear()
self.table.focus()
self._refresh()
def on_input_changed(self, event):
self._refresh(event.value)
class ActivitiesScreen(ListScreen):
BINDINGS = [
("q", "quit", "Quit"),
("s", "sort", "Sort"),
("r", "refresh", "Refresh"),
("/", "filter", "Search"),
("d", "delete", "Delete activity"),
("f", "move_facts", "Move facts"),
Binding(key="escape", action="cancelfilter", show=False),
]
def _refresh(self, filter_query=None):
self.table.clear()
# List activities with the count of facts
activities = Activity.list_activities(self.db_manager, filter_query)
self.table.add_rows(
[
[
activity.category_id,
activity.category_name,
activity.id,
activity.name,
activity.facts_count,
]
for activity in activities
]
)
self.table.sort(*self.sort)
def on_mount(self) -> None:
self.table = self.query_one(DataTable)
self.table.cursor_type = "row"
self.columns = self.table.add_columns(
"category id", "category", "activity id", "activity", "entries"
)
self.sort = (self.columns[1], self.columns[3])
self._refresh()
def action_delete(self) -> None:
# get the keys for the row and column under the cursor.
# get the keys for the row and column under the cursor.
row_key, _ = self.table.coordinate_to_cell_key(self.table.cursor_coordinate)
activity_id = self.table.get_cell_at(
@ -85,81 +108,66 @@ class ActivitiesScreen(Screen):
# supply the row key to `remove_row` to delete the row.
self.table.remove_row(row_key)
def on_data_table_column_selected(self, event):
self.sort = (event.column_key,)
event.data_table.sort(*self.sort)
event.data_table.cursor_type = "row"
def action_move_facts(self) -> None:
row_idx: int = self.table.cursor_row
row_cells = self.table.get_row_at(row_idx)
self.move_from_activity = Activity.get_by_id(self.db_manager, row_cells[2])
for col_idx, cell_value in enumerate(row_cells):
cell_coordinate = Coordinate(row_idx, col_idx)
self.table.update_cell_at(
cell_coordinate,
f"[red]{cell_value}[/red]",
)
self.table.move_cursor(row=self.table.cursor_coordinate[0] + 1)
def on_input_changed(self, event):
self._refresh(event.value)
def on_data_table_row_selected(self, event):
if getattr(self, "move_from_activity", None) is not None:
move_to_activity = Activity.get_by_id(
self.db_manager, self.table.get_cell_at(Coordinate(event.cursor_row, 2))
)
self.move_from_activity.move_facts(move_to_activity)
filter_input = self.query_one("#filter")
self._refresh(filter_input.value)
del self.move_from_activity
class CategoriesScreen(Screen):
class CategoriesScreen(ListScreen):
BINDINGS = [
("q", "quit", "Quit"),
("s", "sort", "Sort"),
("r", "refresh", "Refresh"),
("d", "delete", "Delete category"),
("/", "filter", "Search"),
("d", "delete", "Delete category"),
Binding(key="escape", action="cancelfilter", show=False),
]
filtering = reactive(False)
filter_query = reactive("")
def __init__(self, db_manager):
self.db_manager = db_manager
super().__init__()
def _refresh(self, filter_query=None):
self.table.clear()
categories = Category.list_categories(self.db_manager,
filter_query=filter_query)
categories = Category.list_categories(
self.db_manager, filter_query=filter_query
)
self.table.add_rows([[
category.id,
category.name,
category.activity_count,
] for category in categories])
self.table.add_rows(
[
[
category.id,
category.name,
category.activity_count,
]
for category in categories
]
)
self.table.sort(self.sort)
def compose(self) -> ComposeResult:
"""create child widgets for the app."""
yield Header()
with Vertical():
yield DataTable()
with Horizontal():
yield Input(id="filter")
yield Footer()
def on_mount(self) -> None:
self.table = self.query_one(DataTable)
self.table.cursor_type = "row"
self.columns = self.table.add_columns("category id","category","activities")
self.columns = self.table.add_columns("category id", "category", "activities")
self.sort = self.columns[1]
self._refresh()
def action_refresh(self) -> None:
self._refresh()
def action_sort(self) -> None:
self.table.cursor_type = "column"
def action_filter(self) -> None:
filter_input = self.query_one("#filter")
filter_input.display = True
filter_input.focus()
print(filter_input)
def action_cancelfilter(self) -> None:
filter_input = self.query_one("#filter")
filter_input.display = False
self._refresh()
def on_input_changed(self, event):
self._refresh(event.value)
def action_delete(self) -> None:
# get the keys for the row and column under the cursor.
row_key, _ = self.table.coordinate_to_cell_key(self.table.cursor_coordinate)
@ -173,11 +181,6 @@ class CategoriesScreen(Screen):
# supply the row key to `remove_row` to delete the row.
self.table.remove_row(row_key)
def on_data_table_column_selected(self, event):
""" Handle column selection for sort """
self.sort = event.column_key
event.data_table.sort(self.sort)
event.data_table.cursor_type = "row"
# class KimaiScreen(Screen):
# def compose(self) -> ComposeResult:
@ -191,7 +194,7 @@ class CategoriesScreen(Screen):
class HamsterToolsApp(App):
CSS_PATH = 'app.tcss'
CSS_PATH = "app.tcss"
BINDINGS = [
("a", "switch_mode('activities')", "Activities"),
("c", "switch_mode('categories')", "Categories"),
@ -200,7 +203,7 @@ class HamsterToolsApp(App):
]
def __init__(self):
self.db_manager = DatabaseManager('hamster-testing.db')
self.db_manager = DatabaseManager("hamster-testing.db")
self.MODES = {
"categories": CategoriesScreen(self.db_manager),

15
hamstertools/app.tcss Normal file
View File

@ -0,0 +1,15 @@
DataTable {
height: 90%;
}
DataTable .datatable--cursor {
background: grey;
}
DataTable:focus .datatable--cursor {
background: orange;
}
#filter {
display: none;
}

View File

@ -92,6 +92,22 @@ class Activity(BaseORM):
self.category_name = category_name
self.facts_count = facts_count
def move_facts(self, to_activity):
cursor = self.db_manager.get_cursor()
print(f"moving from {self.id} to {to_activity.id}")
cursor.execute("""
UPDATE
facts
SET
activity_id = ?
WHERE
activity_id = ?
""", (to_activity.id, self.id))
self.conn.commit()
@staticmethod
def list_activities(db_manager, filter_query=None):
cursor = db_manager.get_cursor()
@ -121,7 +137,7 @@ class Activity(BaseORM):
"""
if filter_query is not None:
cursor.execute(sql, ("%{}%".format(filter_query),) * 2 )
cursor.execute(sql, ("%{}%".format(filter_query),) * 2)
else:
cursor.execute(sql)