refactored fields; added date
This commit is contained in:
parent
9c55adb88e
commit
0d6c20cff6
@ -14,7 +14,7 @@ from ..utils import (
|
|||||||
get_text_from_editor,
|
get_text_from_editor,
|
||||||
)
|
)
|
||||||
from .keymodal import KeyModal
|
from .keymodal import KeyModal
|
||||||
from .modals import ChoiceModal #DateModal
|
from .modals import ChoiceModal
|
||||||
|
|
||||||
ELLIPSIS = "…"
|
ELLIPSIS = "…"
|
||||||
|
|
||||||
@ -23,20 +23,6 @@ class NotifyValidationError(Exception):
|
|||||||
"""will notify and continue if raised"""
|
"""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:
|
class TableColumnConfig:
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -45,8 +31,6 @@ class TableColumnConfig:
|
|||||||
display_name: str,
|
display_name: str,
|
||||||
*,
|
*,
|
||||||
default=None,
|
default=None,
|
||||||
enum=None,
|
|
||||||
preprocessor=None,
|
|
||||||
enable_editor=False,
|
enable_editor=False,
|
||||||
filterable=True,
|
filterable=True,
|
||||||
read_only=False,
|
read_only=False,
|
||||||
@ -54,16 +38,45 @@ class TableColumnConfig:
|
|||||||
self.field = field
|
self.field = field
|
||||||
self.display_name = display_name
|
self.display_name = display_name
|
||||||
self.default = default
|
self.default = default
|
||||||
self.enum = enum
|
|
||||||
self.enable_editor = enable_editor
|
self.enable_editor = enable_editor
|
||||||
self.filterable = filterable
|
self.filterable = filterable
|
||||||
self.read_only = read_only
|
self.read_only = read_only
|
||||||
if preprocessor:
|
|
||||||
self.preprocessor = preprocessor
|
def preprocess(self, val):
|
||||||
elif self.enum:
|
return val # no-op
|
||||||
self.preprocessor = _enum_preprocessor(self.enum)
|
|
||||||
|
def start_change(self, app, current_value):
|
||||||
|
if current_value.endswith(ELLIPSIS):
|
||||||
|
app.action_start_edit()
|
||||||
else:
|
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):
|
class TableEditor(App):
|
||||||
@ -341,16 +354,10 @@ class TableEditor(App):
|
|||||||
current_value = self.table.get_cell_at(
|
current_value = self.table.get_cell_at(
|
||||||
(self.table.cursor_row, self.table.cursor_column)
|
(self.table.cursor_row, self.table.cursor_column)
|
||||||
)
|
)
|
||||||
|
current_value = remove_rich_tag(current_value)
|
||||||
|
|
||||||
if cconf.enum:
|
# delegate to start_change, which will call back to self
|
||||||
self.push_screen(ChoiceModal(cconf.enum, current_value),
|
cconf.start_change(self, 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)
|
|
||||||
|
|
||||||
def action_start_edit(self):
|
def action_start_edit(self):
|
||||||
cconf = self._active_column_config()
|
cconf = self._active_column_config()
|
||||||
@ -401,7 +408,7 @@ class TableEditor(App):
|
|||||||
|
|
||||||
# preprocess/validate the field being saved
|
# preprocess/validate the field being saved
|
||||||
try:
|
try:
|
||||||
update_data[field] = cconf.preprocessor(new_value)
|
update_data[field] = cconf.preprocess(new_value)
|
||||||
self.update_item_callback(item_id, **update_data)
|
self.update_item_callback(item_id, **update_data)
|
||||||
self.refresh_data()
|
self.refresh_data()
|
||||||
except NotifyValidationError as e:
|
except NotifyValidationError as e:
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
from textual.screen import ModalScreen
|
from textual.screen import ModalScreen
|
||||||
from textual.containers import Grid
|
|
||||||
from textual.binding import Binding
|
from textual.binding import Binding
|
||||||
|
|
||||||
from textual.widgets import RadioSet, RadioButton, Label
|
from textual.widgets import RadioSet, RadioButton, Label
|
||||||
|
|
||||||
class ChoiceModal(ModalScreen):
|
|
||||||
|
|
||||||
|
class ChoiceModal(ModalScreen):
|
||||||
CSS = """
|
CSS = """
|
||||||
ChoiceModal {
|
ChoiceModal {
|
||||||
align: center middle;
|
align: center middle;
|
||||||
@ -19,10 +17,9 @@ class ChoiceModal(ModalScreen):
|
|||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("j", "cursor_down", "Down"),
|
("j", "cursor_down", "Down"),
|
||||||
("k", "cursor_up", "Up"),
|
("k", "cursor_up", "Up"),
|
||||||
# TODO: get this to work as override
|
|
||||||
Binding("enter", "select", "Select", priority=True),
|
Binding("enter", "select", "Select", priority=True),
|
||||||
("c", "select", "Select"),
|
("c", "select", "Select"),
|
||||||
("escape", "cancel", "cancel")
|
("escape", "cancel", "cancel"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, enum, selected):
|
def __init__(self, enum, selected):
|
||||||
@ -34,7 +31,8 @@ class ChoiceModal(ModalScreen):
|
|||||||
yield Label(f"{self._enum.__name__}")
|
yield Label(f"{self._enum.__name__}")
|
||||||
yield RadioSet(
|
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
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,3 +50,105 @@ class ChoiceModal(ModalScreen):
|
|||||||
|
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
self.app.pop_screen()
|
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 (
|
from .editor import (
|
||||||
TableEditor,
|
TableEditor,
|
||||||
TableColumnConfig,
|
TableColumnConfig,
|
||||||
|
EnumColumnConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ class TaskGenEditor(TableEditor):
|
|||||||
TABLE_CONFIG = (
|
TABLE_CONFIG = (
|
||||||
TableColumnConfig("id", "ID"),
|
TableColumnConfig("id", "ID"),
|
||||||
TableColumnConfig("template", "Template", default="recur {val}"),
|
TableColumnConfig("template", "Template", default="recur {val}"),
|
||||||
TableColumnConfig(
|
EnumColumnConfig(
|
||||||
"type",
|
"type",
|
||||||
"Type",
|
"Type",
|
||||||
default=GeneratorType.DAYS_BETWEEN.value,
|
default=GeneratorType.DAYS_BETWEEN.value,
|
||||||
|
@ -15,34 +15,40 @@ from ..utils import (
|
|||||||
get_colored_category,
|
get_colored_category,
|
||||||
get_colored_status,
|
get_colored_status,
|
||||||
get_colored_date,
|
get_colored_date,
|
||||||
|
remove_rich_tag,
|
||||||
)
|
)
|
||||||
from .editor import (
|
from .editor import (
|
||||||
TableEditor,
|
TableEditor,
|
||||||
TableColumnConfig,
|
TableColumnConfig,
|
||||||
|
EnumColumnConfig,
|
||||||
NotifyValidationError,
|
NotifyValidationError,
|
||||||
ELLIPSIS,
|
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):
|
def start_change(self, app, current_value):
|
||||||
try:
|
app.push_screen(DateModal(current_value), app.apply_change)
|
||||||
return datetime.strptime(val, "%Y-%m-%d")
|
|
||||||
except ValueError:
|
|
||||||
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
|
|
||||||
|
|
||||||
|
|
||||||
class TT(TableEditor):
|
class TT(TableEditor):
|
||||||
TABLE_CONFIG = (
|
TABLE_CONFIG = (
|
||||||
TableColumnConfig("id", "ID"),
|
TableColumnConfig("id", "ID"),
|
||||||
TableColumnConfig("text", "Task", default="new task", enable_editor=True),
|
TableColumnConfig("text", "Task", default="new task", enable_editor=True),
|
||||||
TableColumnConfig(
|
EnumColumnConfig(
|
||||||
"status",
|
"status",
|
||||||
"Status",
|
"Status",
|
||||||
default="zero",
|
|
||||||
enum=TaskStatus,
|
enum=TaskStatus,
|
||||||
|
default="zero",
|
||||||
),
|
),
|
||||||
TableColumnConfig("type", "Type", default=""),
|
TableColumnConfig("type", "Type", default=""),
|
||||||
TableColumnConfig("due", "Due", default="", preprocessor=due_preprocessor),
|
DateColumnConfig("due", "Due", default=""),
|
||||||
TableColumnConfig("category", "Category", default="main"),
|
TableColumnConfig("category", "Category", default="main"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user