From 35aaf17de2a769aae9bc791ebf705d4f255d4294 Mon Sep 17 00:00:00 2001 From: James Turk Date: Sat, 4 Jan 2025 04:12:02 -0600 Subject: [PATCH] saving and loading views --- src/tt/cli.py | 6 ++++-- src/tt/controller.py | 37 ++++++++++++++++++++++++++-------- src/tt/db.py | 11 ++++++++++- src/tt/tui.py | 47 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/tt/cli.py b/src/tt/cli.py index f9790c1..684b0b4 100644 --- a/src/tt/cli.py +++ b/src/tt/cli.py @@ -39,8 +39,10 @@ def new( @app.command() -def browse(): - tui.run() +def browse( + view: Annotated[str, typer.Option("-v", "--view", help="saved view")] = "default", +): + tui.run(view) @app.command() diff --git a/src/tt/controller.py b/src/tt/controller.py index 4140f87..e084449 100644 --- a/src/tt/controller.py +++ b/src/tt/controller.py @@ -1,6 +1,14 @@ +import json from datetime import datetime from peewee import fn -from .db import Task, Category, TaskStatus, db +from .db import db, Task, Category, TaskStatus, SavedSearch + + +def category_lookup(category): + category_id = None + if category: + category, _ = Category.get_or_create(name=category) + return category.id def add_task( @@ -15,13 +23,10 @@ def add_task( Returns the created task instance. """ with db.atomic(): - category_id = None - if category: - category, _ = Category.get_or_create(name=category) - category_id = category.id - task = Task.create( - text=text, type=type, status=status, due=due, category_id=category_id - ) + category_id = category_lookup(category) + task = Task.create( + text=text, type=type, status=status, due=due, category_id=category_id + ) return task @@ -30,6 +35,8 @@ def update_task( **kwargs, ) -> Task: with db.atomic(): + if category := kwargs.pop("category", None): + kwargs["category_id"] = category_lookup(category) task = Task.get_by_id(task_id) query = Task.update(kwargs).where(Task.id == task_id) query.execute() @@ -84,3 +91,17 @@ def get_tasks( def get_categories() -> list[Category]: return list(Category.select().order_by(Category.name)) + + +def save_view(name: str, *, filters: dict, sort_string: str) -> SavedSearch: + filters_json = json.dumps(filters) + + return SavedSearch.create(name=name, filters=filters_json, sort_string=sort_string) + + +def get_saved_view_names() -> list[str]: + return [search.name for search in SavedSearch.select()] + + +def get_saved_view(name: str) -> SavedSearch: + return SavedSearch.get(SavedSearch.name == name) diff --git a/src/tt/db.py b/src/tt/db.py index 2da09c0..1b27bcc 100644 --- a/src/tt/db.py +++ b/src/tt/db.py @@ -58,9 +58,18 @@ class Task(BaseModel): return super(Task, self).save(*args, **kwargs) +class SavedSearch(BaseModel): + name = CharField(unique=True) + filters = CharField() + sort_string = CharField() + + def __str__(self): + return self.name + + def initialize_db(): db.connect() - db.create_tables([Category, Task]) + db.create_tables([Category, Task, SavedSearch]) if not Category.select().exists(): Category.create(name="default") db.close() diff --git a/src/tt/tui.py b/src/tt/tui.py index 39fb82a..254010e 100644 --- a/src/tt/tui.py +++ b/src/tt/tui.py @@ -1,3 +1,4 @@ +import json from textual.app import App from textual.screen import ModalScreen from rich.table import Table @@ -10,7 +11,14 @@ from textual.widgets import ( from textual.containers import Container from datetime import datetime -from .controller import get_tasks, add_task, update_task, TaskStatus +from .controller import ( + get_tasks, + add_task, + update_task, + TaskStatus, + save_view, + get_saved_view, +) from .db import initialize_db from .utils import ( remove_rich_tag, @@ -97,19 +105,31 @@ class TT(App): ("a", "add_task", "add task"), ("t", "toggle_cell", "toggle status"), ("d", "delete_task", "delete (must be on row mode)"), + # saved views + ("ctrl+s", "save_view", "save current view"), + ("ctrl+o", "load_view", "load saved view"), # other ("q", "quit", "quit"), ("?", "show_keys", "show keybindings"), ] - def __init__(self): + def __init__(self, default_view="default"): super().__init__() - self.search_query = "" self.filters = {} self.sort_string = "due,status" - self.saved_cursor_pos = (0, 0) + self._load_view(default_view) + self.search_query = "" + self.saved_cursor_pos = (1, 0) self.save_on_move = None + def _load_view(self, name): + try: + saved = get_saved_view(name) + self.filters = json.loads(saved.filters) + self.sort_string = saved.sort_string + except Exception: + self.notify(f"Could not load {name}") + def compose(self): self.header = Header() self.table = DataTable() @@ -271,6 +291,10 @@ class TT(App): self.input_label.update("sort: ") elif mode == "filter": self.input_label.update("filter: ") + elif mode == "save-view": + self.input_label.update("view name: ") + elif mode == "load-view": + self.input_label.update("view name: ") else: raise ValueError(f"unknown mode: {mode}") self.set_focus(self.input_widget) @@ -286,6 +310,12 @@ class TT(App): def action_start_search(self): self._show_input("search", "") + def action_save_view(self): + self._show_input("save-view", "default") + + def action_load_view(self): + self._show_input("load-view", "") + def action_start_filter(self): # filter the currently selected column cur_col = self.table.cursor_column @@ -330,6 +360,11 @@ class TT(App): self.right_status.update(self.sort_string) elif self.mode == "edit": self.apply_change(event.value) + elif self.mode == "save-view": + save_view(event.value, filters=self.filters, sort_string=self.sort_string) + elif self.mode == "load-view": + self._load_view(event.value) + self.refresh_tasks(restore_cursor=False) else: raise ValueError(f"unknown mode: {self.mode}") self._hide_input() @@ -411,9 +446,9 @@ class KeyBindingsScreen(ModalScreen): self.app.pop_screen() -def run(): +def run(default_view): initialize_db() - app = TT() + app = TT(default_view) app.run()