159 lines
5.3 KiB
Python
159 lines
5.3 KiB
Python
from textual.app import App, ComposeResult
|
|
from textual.screen import Screen
|
|
from textual.widgets import Label, Input, Button, Header, Footer, DataTable
|
|
from textual.containers import Vertical, Horizontal
|
|
from textual.binding import Binding
|
|
from .engine import PenTracker, Pen
|
|
# --- TUI SCREENS ---
|
|
|
|
class PenFormScreen(Screen):
|
|
"""A screen for adding or editing a pen."""
|
|
|
|
def __init__(self, tracker, existing_pen=None):
|
|
super().__init__()
|
|
self.tracker = tracker
|
|
self.existing_pen = existing_pen
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Vertical(id="form-container"):
|
|
yield Label("📝 NEW PEN" if not self.existing_pen else "✏️ EDIT PEN")
|
|
|
|
for header in self.tracker.headers:
|
|
field = self.tracker.key_map.get(header, header)
|
|
val = getattr(self.existing_pen, field) if self.existing_pen else ""
|
|
yield Input(value=val, placeholder=header, id=header)
|
|
|
|
with Horizontal():
|
|
yield Button("Save", variant="success", id="save_cmd")
|
|
yield Button("Cancel", variant="error", id="cancel_cmd")
|
|
|
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
if event.button.id == "cancel_cmd":
|
|
self.dismiss()
|
|
return
|
|
|
|
new_data = {}
|
|
for header in self.tracker.headers:
|
|
try:
|
|
input_widget = self.query_one(f"#{header}", Input)
|
|
new_data[header] = input_widget.value if input_widget.value.strip() else "N/A"
|
|
except Exception:
|
|
new_data[header] = "N/A"
|
|
|
|
# Map to Pen fields
|
|
mapped_data = {self.tracker.key_map.get(k, k): v for k, v in new_data.items()}
|
|
new_pen = Pen(**mapped_data)
|
|
|
|
if self.existing_pen is not None and self.existing_pen in self.tracker.pens:
|
|
idx = self.tracker.pens.index(self.existing_pen)
|
|
self.tracker.pens[idx] = new_pen
|
|
else:
|
|
self.tracker.pens.append(new_pen)
|
|
|
|
# save_data() handles the sorting internally
|
|
self.tracker.save_data()
|
|
self.dismiss(new_pen)
|
|
|
|
class PenTrackerApp(App):
|
|
"""The Main TUI Application."""
|
|
|
|
CSS = """
|
|
#form-container {
|
|
width: 60%;
|
|
height: auto;
|
|
border: heavy white;
|
|
padding: 1 2;
|
|
margin: 5 10;
|
|
background: $panel;
|
|
}
|
|
Label {
|
|
width: 100%;
|
|
text-align: center;
|
|
text-style: bold;
|
|
margin-bottom: 1;
|
|
}
|
|
Input {
|
|
margin-bottom: 1;
|
|
}
|
|
Horizontal {
|
|
align: center middle;
|
|
height: auto;
|
|
}
|
|
Button {
|
|
margin: 0 1;
|
|
}
|
|
DataTable {
|
|
height: 1fr;
|
|
}
|
|
"""
|
|
|
|
BINDINGS = [
|
|
Binding("d", "delete_selected", "Delete Selected"),
|
|
Binding("a", "add_new", "Add New Pen"),
|
|
Binding("q", "quit", "Quit"),
|
|
]
|
|
|
|
def compose(self) -> ComposeResult:
|
|
yield Header()
|
|
yield DataTable(id="pen_table")
|
|
yield Footer()
|
|
|
|
def on_mount(self) -> None:
|
|
self.tracker = PenTracker()
|
|
self._refresh_table()
|
|
|
|
def _refresh_table(self):
|
|
table = self.query_one("#pen_table", DataTable)
|
|
table.clear(columns=True)
|
|
|
|
display_cols = ['Make', 'Model', 'Nib', 'Nib-Material', 'Body','Cap', 'Current-Ink', 'Inked-date', 'Notes']
|
|
table.add_columns(*display_cols)
|
|
|
|
for idx, pen in enumerate(self.tracker.pens):
|
|
row_values = [getattr(pen, c.replace('-','_')) for c in display_cols]
|
|
# We use the index as the row key to track items accurately
|
|
table.add_row(*row_values, key=str(idx))
|
|
|
|
def action_add_new(self) -> None:
|
|
form = PenFormScreen(self.tracker)
|
|
self.push_screen(form, self.handle_form_result)
|
|
|
|
def action_edit_selected(self, index_str: str):
|
|
try:
|
|
idx = int(index_str)
|
|
existing_pen = self.tracker.pens[idx]
|
|
form = PenFormScreen(self.tracker, existing_pen=existing_pen)
|
|
self.push_screen(form, self.handle_form_result)
|
|
except (ValueError, IndexError):
|
|
pass
|
|
|
|
def handle_form_result(self, updated_pen_data) -> None:
|
|
if updated_pen_data:
|
|
self._refresh_table()
|
|
self.notify("Collection Updated & Sorted!", title="Success")
|
|
|
|
def action_delete_selected(self) -> None:
|
|
table = self.query_one("#pen_table", DataTable)
|
|
try:
|
|
# Accessing via the table's current cursor
|
|
table = self.query_one("#pen_table", DataTable)
|
|
row_node = table.get_row_at_cursor()
|
|
idx = int(row_node.key.value)
|
|
removed = self.tracker.pens.pop(idx)
|
|
self.tracker.save_data()
|
|
self._refresh_table()
|
|
self.notify(f"Deleted {removed.Make}", title="Removed")
|
|
except Exception:
|
|
self.notify("No pen selected to delete", title="Error", severity="error")
|
|
|
|
def on_data_table_cell_selected(self, event: DataTable.CellSelected) -> None:
|
|
try:
|
|
idx = int(event.cell_key.row_key.value)
|
|
self.action_edit_selected(str(idx))
|
|
except (AttributeError, ValueError):
|
|
self.notify("Error selecting row", title="Error", severity="error")
|
|
|
|
def main():
|
|
# This is the entry point defined in pyproject.toml
|
|
app = PenTrackerApp()
|
|
app.run()
|