diff --git a/src/tt/tui/columns.py b/src/tt/tui/columns.py index 7ddfaa3..c92ae81 100644 --- a/src/tt/tui/columns.py +++ b/src/tt/tui/columns.py @@ -3,7 +3,7 @@ from ..utils import ( get_color_enum, get_colored_date, ) -from .modals import ChoiceModal, DateModal +from .modals import ChoiceModal, DateModal, TagModal class NotifyValidationError(Exception): @@ -32,6 +32,7 @@ class TableColumnConfig: self.read_only = read_only def preprocess(self, val): + """from UI -> DB""" return val # no-op def start_change(self, app, current_value): @@ -42,6 +43,7 @@ class TableColumnConfig: app._show_input("edit", current_value) def format_for_display(self, val): + """from DB -> UI""" val = str(val) if "\n" in val: val = val.split("\n")[0] + ELLIPSIS @@ -67,6 +69,20 @@ class EnumColumnConfig(TableColumnConfig): app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change) +class TagColumnConfig(TableColumnConfig): + def __init__(self, field: str, display_name: str, **kwargs): + super().__init__(field, display_name, **kwargs) + + def preprocess(self, val): + return val + + def format_for_display(self, val): + return ", ".join(val) + + def start_change(self, app, current_value): + app.push_screen(TagModal(current_value), app.apply_change) + + class DateColumnConfig(TableColumnConfig): def preprocess(self, val): try: @@ -88,4 +104,5 @@ def get_col_cls(field_type): "text": TableColumnConfig, "enum": EnumColumnConfig, "date": DateColumnConfig, + "tag": TagColumnConfig, }[field_type] diff --git a/src/tt/tui/modals.py b/src/tt/tui/modals.py index cfcc127..7a15448 100644 --- a/src/tt/tui/modals.py +++ b/src/tt/tui/modals.py @@ -1,7 +1,7 @@ import datetime from textual.screen import ModalScreen from textual.binding import Binding -from textual.widgets import Label +from textual.widgets import Label, Input from textual.containers import Horizontal, Vertical from textual.reactive import reactive from ..utils import get_color_enum @@ -132,6 +132,82 @@ class ChoiceModal(ModalScreen): self.dismiss(self.enum_by_idx[idx]) +class TagModal(ModalScreen): + CSS = """ + TagModal { + align: center middle; + background: $primary 30%; + } + TagModal Vertical { + border: double teal; + width: 38; + } + TagModal Label.hints { + border: solid grey; + height: 4; + } + TagModal Label { + height: 1; + } + TagEditor #tageditor { + width: 100%; + } + TagModal Label#selected { + background: white; + } + """ + # TODO: float hints to bottom + + BINDINGS = [ + # ("j,tab", "cursor_down", "Down"), + # ("k,shift+tab", "cursor_up", "Up"), + # TODO: add clear + Binding("enter", "select", "Select", priority=True), + ("escape", "cancel", "cancel"), + ] + + def __init__(self, current_val): + if isinstance(current_val, str): + # FIXME: shouldn't happen + current_val = current_val.split(", ") + self._tags = current_val + self.sel_idx = 0 + super().__init__() + + def compose(self): + self.input = Input() + with Vertical(): + yield self.input + for tag in self._tags: + yield Label(" - " + tag) + yield Label( + """(h/j/k/l) move +(enter) confirm (esc) quit""", + classes="hints", + ) + + def action_cursor_down(self): + self._move_cursor(1) + + def action_cursor_up(self): + self._move_cursor(-1) + + async def action_select(self): + # on first submit: add, second: submit + if tag := self.input.value: + if tag in self._tags: + self._tags.remove(tag) + else: + self._tags.append(self.input.value) + await self.recompose() + self.input.focus() + else: + self.dismiss(self._tags) + + def action_cancel(self): + self.app.pop_screen() + + class DateModal(ModalScreen): CSS = """ DateModal {