refactored fields; added date
This commit is contained in:
parent
9c55adb88e
commit
0d6c20cff6
@ -14,7 +14,7 @@ from ..utils import (
|
||||
get_text_from_editor,
|
||||
)
|
||||
from .keymodal import KeyModal
|
||||
from .modals import ChoiceModal #DateModal
|
||||
from .modals import ChoiceModal
|
||||
|
||||
ELLIPSIS = "…"
|
||||
|
||||
@ -23,20 +23,6 @@ class NotifyValidationError(Exception):
|
||||
"""will notify and continue if raised"""
|
||||
|
||||
|
||||
def _enum_preprocessor(enumCls):
|
||||
"""generate a default preprocessor to enforce enums"""
|
||||
|
||||
def preprocessor(val):
|
||||
try:
|
||||
enumCls(val)
|
||||
return val
|
||||
except ValueError:
|
||||
raise NotifyValidationError(
|
||||
f"Invalid value {val}. Use: {[s.value for s in enumCls]}"
|
||||
)
|
||||
|
||||
return preprocessor
|
||||
|
||||
|
||||
class TableColumnConfig:
|
||||
def __init__(
|
||||
@ -45,8 +31,6 @@ class TableColumnConfig:
|
||||
display_name: str,
|
||||
*,
|
||||
default=None,
|
||||
enum=None,
|
||||
preprocessor=None,
|
||||
enable_editor=False,
|
||||
filterable=True,
|
||||
read_only=False,
|
||||
@ -54,16 +38,45 @@ class TableColumnConfig:
|
||||
self.field = field
|
||||
self.display_name = display_name
|
||||
self.default = default
|
||||
self.enum = enum
|
||||
self.enable_editor = enable_editor
|
||||
self.filterable = filterable
|
||||
self.read_only = read_only
|
||||
if preprocessor:
|
||||
self.preprocessor = preprocessor
|
||||
elif self.enum:
|
||||
self.preprocessor = _enum_preprocessor(self.enum)
|
||||
|
||||
def preprocess(self, val):
|
||||
return val # no-op
|
||||
|
||||
def start_change(self, app, current_value):
|
||||
if current_value.endswith(ELLIPSIS):
|
||||
app.action_start_edit()
|
||||
else:
|
||||
self.preprocessor = lambda x: x
|
||||
# default edit mode
|
||||
app._show_input("edit", current_value)
|
||||
|
||||
|
||||
class EnumColumnConfig(TableColumnConfig):
|
||||
def __init__(
|
||||
self,
|
||||
field: str,
|
||||
display_name: str,
|
||||
enum,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(field, display_name, **kwargs)
|
||||
self.enumCls = enum
|
||||
|
||||
def preprocess(self, val):
|
||||
try:
|
||||
self.enumCls(val)
|
||||
return val
|
||||
except ValueError:
|
||||
raise NotifyValidationError(
|
||||
f"Invalid value {val}. Use: {[s.value for s in self.enumCls]}"
|
||||
)
|
||||
|
||||
def start_change(self, app, current_value):
|
||||
# a weird hack? pass app here and correct modal gets pushed
|
||||
app.push_screen(ChoiceModal(self.enumCls, current_value), app.apply_change)
|
||||
|
||||
|
||||
|
||||
class TableEditor(App):
|
||||
@ -341,16 +354,10 @@ class TableEditor(App):
|
||||
current_value = self.table.get_cell_at(
|
||||
(self.table.cursor_row, self.table.cursor_column)
|
||||
)
|
||||
current_value = remove_rich_tag(current_value)
|
||||
|
||||
if cconf.enum:
|
||||
self.push_screen(ChoiceModal(cconf.enum, current_value),
|
||||
self.apply_change)
|
||||
elif current_value.endswith(ELLIPSIS):
|
||||
self.action_start_edit()
|
||||
else:
|
||||
# default edit mode
|
||||
current_value = remove_rich_tag(current_value)
|
||||
self._show_input("edit", current_value)
|
||||
# delegate to start_change, which will call back to self
|
||||
cconf.start_change(self, current_value)
|
||||
|
||||
def action_start_edit(self):
|
||||
cconf = self._active_column_config()
|
||||
@ -401,7 +408,7 @@ class TableEditor(App):
|
||||
|
||||
# preprocess/validate the field being saved
|
||||
try:
|
||||
update_data[field] = cconf.preprocessor(new_value)
|
||||
update_data[field] = cconf.preprocess(new_value)
|
||||
self.update_item_callback(item_id, **update_data)
|
||||
self.refresh_data()
|
||||
except NotifyValidationError as e:
|
||||
|
@ -1,11 +1,9 @@
|
||||
from textual.screen import ModalScreen
|
||||
from textual.containers import Grid
|
||||
from textual.binding import Binding
|
||||
|
||||
from textual.widgets import RadioSet, RadioButton, Label
|
||||
|
||||
class ChoiceModal(ModalScreen):
|
||||
|
||||
class ChoiceModal(ModalScreen):
|
||||
CSS = """
|
||||
ChoiceModal {
|
||||
align: center middle;
|
||||
@ -19,10 +17,9 @@ class ChoiceModal(ModalScreen):
|
||||
BINDINGS = [
|
||||
("j", "cursor_down", "Down"),
|
||||
("k", "cursor_up", "Up"),
|
||||
# TODO: get this to work as override
|
||||
Binding("enter", "select", "Select", priority=True),
|
||||
("c", "select", "Select"),
|
||||
("escape", "cancel", "cancel")
|
||||
("escape", "cancel", "cancel"),
|
||||
]
|
||||
|
||||
def __init__(self, enum, selected):
|
||||
@ -34,7 +31,8 @@ class ChoiceModal(ModalScreen):
|
||||
yield Label(f"{self._enum.__name__}")
|
||||
yield RadioSet(
|
||||
*[
|
||||
RadioButton(str(e.value), value=self.selected==str(e.value)) for e in self._enum
|
||||
RadioButton(str(e.value), value=self.selected == str(e.value))
|
||||
for e in self._enum
|
||||
]
|
||||
)
|
||||
|
||||
@ -43,7 +41,7 @@ class ChoiceModal(ModalScreen):
|
||||
|
||||
def action_cursor_up(self):
|
||||
self.query_one(RadioSet).action_previous_button()
|
||||
|
||||
|
||||
def action_select(self):
|
||||
rs = self.query_one(RadioSet)
|
||||
rs.action_toggle_button()
|
||||
@ -52,3 +50,105 @@ class ChoiceModal(ModalScreen):
|
||||
|
||||
def action_cancel(self):
|
||||
self.app.pop_screen()
|
||||
|
||||
|
||||
class DateModal(ModalScreen):
|
||||
CSS = """
|
||||
DateModal {
|
||||
layout: horizontal;
|
||||
align: center middle;
|
||||
background: $primary 30%;
|
||||
}
|
||||
DateModal Label {
|
||||
border: solid grey;
|
||||
}
|
||||
DateModal Label.selected-date {
|
||||
border: solid green;
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
("j", "cursor_down", "Down"),
|
||||
("k", "cursor_up", "Up"),
|
||||
("h", "cursor_left", "Left"),
|
||||
("l", "cursor_right", "Right"),
|
||||
# ("0,1,2,3,4,5,6,7,8,9", "num_entry", "#"),
|
||||
Binding("enter", "select", "Select", priority=True),
|
||||
("escape", "cancel", "cancel"),
|
||||
]
|
||||
|
||||
def __init__(self, date):
|
||||
self.pieces = [int(p) for p in date.split('-')]
|
||||
self.selected = 1 # start on month
|
||||
super().__init__()
|
||||
|
||||
def compose(self):
|
||||
for idx, piece in enumerate(self.pieces):
|
||||
yield Label(str(piece), classes="selected-date" if idx == self.selected else "")
|
||||
|
||||
def action_cursor_left(self):
|
||||
# cycle Y/M/D
|
||||
self.selected = (self.selected - 1) % 3
|
||||
self._update_highlight()
|
||||
|
||||
def action_cursor_right(self):
|
||||
self.selected = (self.selected + 1) % 3
|
||||
self._update_highlight()
|
||||
|
||||
def _update_highlight(self):
|
||||
for idx, lbl in enumerate(self.query("Label")):
|
||||
if idx == self.selected:
|
||||
lbl.add_class("selected-date")
|
||||
else:
|
||||
lbl.remove_class("selected-date")
|
||||
|
||||
|
||||
def max_for(self, piece):
|
||||
days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
if piece == 0:
|
||||
return 3000
|
||||
elif piece == 1:
|
||||
return 12
|
||||
else:
|
||||
# -1 for offset array
|
||||
return days_in_month[self.pieces[1]-1]
|
||||
|
||||
def _move_piece(self, by):
|
||||
cur_value = self.pieces[self.selected]
|
||||
cur_value += by
|
||||
if cur_value == 0:
|
||||
cur_value = self.max_for(self.selected)
|
||||
if cur_value > self.max_for(self.selected):
|
||||
cur_value = 1
|
||||
self.pieces[self.selected] = cur_value
|
||||
cur_label = self.query("Label")[self.selected]
|
||||
cur_label.update(str(cur_value))
|
||||
|
||||
def action_cursor_down(self):
|
||||
self._move_piece(-1)
|
||||
|
||||
def action_cursor_up(self):
|
||||
self._move_piece(1)
|
||||
|
||||
def on_key(self, event) -> None:
|
||||
key = event.key
|
||||
if key in "0123456789":
|
||||
cur_value = self.pieces[self.selected]
|
||||
# If we can append the values like 1+2 = 12
|
||||
# then do that, otherwise reset the value
|
||||
# so (for months) 1+3 = 3
|
||||
appended = int(str(cur_value) + key)
|
||||
if appended <= self.max_for(self.selected):
|
||||
cur_value = appended
|
||||
else:
|
||||
cur_value = int(key)
|
||||
self.pieces[self.selected] = cur_value
|
||||
self._move_piece(0)
|
||||
event.prevent_default()
|
||||
|
||||
def action_select(self):
|
||||
date = "-".join(str(p) for p in self.pieces)
|
||||
self.dismiss(date)
|
||||
|
||||
def action_cancel(self):
|
||||
self.app.pop_screen()
|
||||
|
@ -11,6 +11,7 @@ from ..db import GeneratorType
|
||||
from .editor import (
|
||||
TableEditor,
|
||||
TableColumnConfig,
|
||||
EnumColumnConfig,
|
||||
)
|
||||
|
||||
|
||||
@ -18,7 +19,7 @@ class TaskGenEditor(TableEditor):
|
||||
TABLE_CONFIG = (
|
||||
TableColumnConfig("id", "ID"),
|
||||
TableColumnConfig("template", "Template", default="recur {val}"),
|
||||
TableColumnConfig(
|
||||
EnumColumnConfig(
|
||||
"type",
|
||||
"Type",
|
||||
default=GeneratorType.DAYS_BETWEEN.value,
|
||||
|
@ -15,34 +15,40 @@ from ..utils import (
|
||||
get_colored_category,
|
||||
get_colored_status,
|
||||
get_colored_date,
|
||||
remove_rich_tag,
|
||||
)
|
||||
from .editor import (
|
||||
TableEditor,
|
||||
TableColumnConfig,
|
||||
EnumColumnConfig,
|
||||
NotifyValidationError,
|
||||
ELLIPSIS,
|
||||
)
|
||||
from .modals import DateModal
|
||||
|
||||
class DateColumnConfig(TableColumnConfig):
|
||||
def preprocess(self, val):
|
||||
try:
|
||||
return datetime.strptime(val, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
|
||||
|
||||
def due_preprocessor(val):
|
||||
try:
|
||||
return datetime.strptime(val, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
|
||||
def start_change(self, app, current_value):
|
||||
app.push_screen(DateModal(current_value), app.apply_change)
|
||||
|
||||
|
||||
class TT(TableEditor):
|
||||
TABLE_CONFIG = (
|
||||
TableColumnConfig("id", "ID"),
|
||||
TableColumnConfig("text", "Task", default="new task", enable_editor=True),
|
||||
TableColumnConfig(
|
||||
EnumColumnConfig(
|
||||
"status",
|
||||
"Status",
|
||||
default="zero",
|
||||
enum=TaskStatus,
|
||||
default="zero",
|
||||
),
|
||||
TableColumnConfig("type", "Type", default=""),
|
||||
TableColumnConfig("due", "Due", default="", preprocessor=due_preprocessor),
|
||||
DateColumnConfig("due", "Due", default=""),
|
||||
TableColumnConfig("category", "Category", default="main"),
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user