From 37786f56b5e58754628a0aa9107c10345a842c0b Mon Sep 17 00:00:00 2001 From: Maveno Date: Wed, 25 Sep 2024 19:24:27 +0200 Subject: [PATCH] mwe full cycle form input --- bin/ffx.py | 4 +- bin/ffx/ffx_app.py | 67 +++++++++++++ bin/ffx/file_pattern.py | 2 - bin/ffx/model/__init__.py | 0 bin/ffx/model/pattern.py | 10 ++ bin/ffx/model/show.py | 13 +++ bin/ffx/modes_app.py | 39 -------- bin/ffx/show.py | 2 - bin/ffx/show_details_screen.py | 166 +++++++++++++++++++++++++++++++++ bin/ffx/show_new_screen.py | 60 ++++++++++++ bin/ffx/shows_screen.py | 109 ++++++++++++++++------ 11 files changed, 398 insertions(+), 74 deletions(-) create mode 100644 bin/ffx/ffx_app.py delete mode 100644 bin/ffx/file_pattern.py create mode 100644 bin/ffx/model/__init__.py create mode 100644 bin/ffx/model/pattern.py create mode 100644 bin/ffx/model/show.py delete mode 100644 bin/ffx/modes_app.py delete mode 100644 bin/ffx/show.py create mode 100644 bin/ffx/show_details_screen.py create mode 100644 bin/ffx/show_new_screen.py diff --git a/bin/ffx.py b/bin/ffx.py index 58b1fdd..c66056b 100755 --- a/bin/ffx.py +++ b/bin/ffx.py @@ -2,7 +2,7 @@ import os, sys, subprocess, json, click, time, re -from ffx.modes_app import ModesApp +from ffx.ffx_app import FfxApp VERSION='0.1.0' @@ -391,7 +391,7 @@ def unmux(ctx, def shows(ctx): - app = ModesApp(ctx.obj) + app = FfxApp(ctx.obj) app.run() diff --git a/bin/ffx/ffx_app.py b/bin/ffx/ffx_app.py new file mode 100644 index 0000000..771a6c6 --- /dev/null +++ b/bin/ffx/ffx_app.py @@ -0,0 +1,67 @@ +import os + +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Placeholder, Label + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from ffx.model.show import Base, Show +from ffx.model.pattern import Pattern + +from .shows_screen import ShowsScreen +from .warning_screen import WarningScreen +from .dashboard_screen import DashboardScreen +from .settings_screen import SettingsScreen +from .help_screen import HelpScreen + +class FfxApp(App): + + TITLE = "FFX" + + BINDINGS = [ + ("q", "quit()", "Quit"), + # ("d", "switch_mode('dashboard')", "Dashboard"), + # ("s", "switch_mode('settings')", "Settings"), + ("h", "switch_mode('help')", "Help"), + ] + + # MODES = { + # "shows": ShowsScreen, + # "warning": WarningScreen, + # "dashboard": DashboardScreen, + # "settings": SettingsScreen, + # "help": HelpScreen, + # } + + + def __init__(self, context = {}): + super().__init__() + + # Data 'input' variable + self.context = context + + # Initialize DB + homeDir = os.path.expanduser("~") + ffxVarDir = os.path.join(homeDir, '.local', 'var', 'ffx') + if not os.path.exists(ffxVarDir): + os.makedirs(ffxVarDir) + + self.context['database_url'] = f"sqlite:///{os.path.join(ffxVarDir, 'ffx.db')}" + self.context['database_engine'] = create_engine(self.context['database_url']) + self.context['database_session'] = sessionmaker(bind=self.context['database_engine']) + + Base.metadata.create_all(self.context['database_engine']) + + + def on_mount(self) -> None: + + + # Show Shows Screen + #self.switch_mode("shows") + self.push_screen(ShowsScreen()) + + + def getContext(self): + """Data 'output' method""" + return self.context diff --git a/bin/ffx/file_pattern.py b/bin/ffx/file_pattern.py deleted file mode 100644 index 71991b7..0000000 --- a/bin/ffx/file_pattern.py +++ /dev/null @@ -1,2 +0,0 @@ -class FilePattern(): - pass diff --git a/bin/ffx/model/__init__.py b/bin/ffx/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bin/ffx/model/pattern.py b/bin/ffx/model/pattern.py new file mode 100644 index 0000000..d1648af --- /dev/null +++ b/bin/ffx/model/pattern.py @@ -0,0 +1,10 @@ +from sqlalchemy import create_engine, Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship, sessionmaker + +from .show import Base + +class Pattern(Base): + + __tablename__ = 'patterns' + id = Column(Integer, primary_key=True) + pattern = Column(String) diff --git a/bin/ffx/model/show.py b/bin/ffx/model/show.py new file mode 100644 index 0000000..d472f93 --- /dev/null +++ b/bin/ffx/model/show.py @@ -0,0 +1,13 @@ +from sqlalchemy import create_engine, Column, Integer, String, ForeignKey +from sqlalchemy.orm import relationship, sessionmaker + +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + +class Show(Base): + + __tablename__ = 'shows' + id = Column(Integer, primary_key=True) + name = Column(String) + year = Column(Integer) diff --git a/bin/ffx/modes_app.py b/bin/ffx/modes_app.py deleted file mode 100644 index 760c9e9..0000000 --- a/bin/ffx/modes_app.py +++ /dev/null @@ -1,39 +0,0 @@ -from textual.app import App, ComposeResult -from textual.screen import Screen -from textual.widgets import Header, Footer, Placeholder, Label - -from .shows_screen import ShowsScreen -from .warning_screen import WarningScreen -from .dashboard_screen import DashboardScreen -from .settings_screen import SettingsScreen -from .help_screen import HelpScreen - -class ModesApp(App): - - TITLE = "FFX" - - BINDINGS = [ - ("q", "quit()", "Quit"), - # ("d", "switch_mode('dashboard')", "Dashboard"), - # ("s", "switch_mode('settings')", "Settings"), - # ("h", "switch_mode('help')", "Help"), - ] - - MODES = { - "shows": ShowsScreen, - "warning": WarningScreen, - "dashboard": DashboardScreen, - "settings": SettingsScreen, - "help": HelpScreen, - } - - - def __init__(self, context = {}): - super().__init__() - self.context = context - - def on_mount(self) -> None: - self.switch_mode("shows") - - def getContext(self): - return self.context diff --git a/bin/ffx/show.py b/bin/ffx/show.py deleted file mode 100644 index 175052a..0000000 --- a/bin/ffx/show.py +++ /dev/null @@ -1,2 +0,0 @@ -class Show(): - pass diff --git a/bin/ffx/show_details_screen.py b/bin/ffx/show_details_screen.py new file mode 100644 index 0000000..d23ef0b --- /dev/null +++ b/bin/ffx/show_details_screen.py @@ -0,0 +1,166 @@ +import click + +from textual import events +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button, Input +from textual.containers import Grid, Horizontal + +from ffx.model.show import Show + +# class IdInput(Input): +# def on_key(self, event: events.Key) -> None: +# if event == "enter": +# quit() + +class ShowDetailsScreen(Screen): + + CSS = """ + + Grid { + grid-size: 2; + grid-rows: 2 auto; + grid-columns: 30 auto; + height: 100%; + width: 100%; + padding: 1; + } + + Input { + border: none; + } + Button { + border: none; + } + #toplabel { + height: 1; + column-span: 2; + } + + + #two { + column-span: 2; + row-span: 2; + tint: magenta 40%; + } + + .box { + height: 100%; + border: solid green; + } + """ + + #BINDINGS = [ + # #("q", "quit()", "Quit"), + # ("h", "switch_mode('help')", "Help") + #] + + #def action_quit(self): + # quit() + + def __init__(self, show_id = None): + super().__init__() + + self.context = self.app.getContext() + + self.Session = self.context['database_session'] # convenience + + self.show_id = show_id + + def action_submit(self): + quit() + + def compose(self): + + yield Header() + + with Grid(): + + yield Static("New Show" if self.show_id is None else "Show", id="toplabel") + yield Static("ID") + yield Input(type="text", id="id_input") + yield Static("Name") + yield Input(type="text", id="name_input") + yield Static("Year") + yield Input(type="text", id="year_input") + + yield Static("") + yield Static("") + + yield Static("") + yield Static("") + + yield Button("Save", id="save_button") + yield Button("Cancel", id="cancel_button") + + yield Static("", id="output_static") + + + yield Footer() + + def getValues(self): + showId = int(self.query_one("#id_input", Input).value) + showName = self.query_one("#name_input", Input).value + showYear = self.query_one("#year_input", Input).value + return showId, showName, showYear + + + def on_input_submitted(self, event: Input.Submitted) -> None: + + showId, showName, showYear = self.getValues() + + #self.query_one("#output_static", Static).update(f"{showId} - {showName} ({showYear})") + +# if event.input.id == "id_input": +# # Retrieve the entered text +# name = event.value +# # Display the result in the output label +# self.query_one("#output_static", Static).update(f"Hello, {name}!") +# self.set_focus(self.query_one("#name_input", Input)) +# +# if event.input.id == "name_input": +# # Retrieve the entered text +# name = event.value +# # Display the result in the output label +# self.query_one("#output_static", Static).update(f"Yo, {name}!") +# self.set_focus(self.query_one("#year_input", Input)) +# +# if event.input.id == "year_input": +# # Retrieve the entered text +# name = event.value +# # Display the result in the output label +# self.query_one("#output_static", Static).update(f"Ya, {name}!") +# self.set_focus(None) + + # Event handler for button press + def on_button_pressed(self, event: Button.Pressed) -> None: + # Check if the button pressed is the one we are interested in + if event.button.id == "save_button": + + showId, showName, showYear = self.getValues() + self.query_one("#output_static", Static).update(f"{showId} - {showName} ({showYear})") + + self.addShow(showId, showName, showYear) + #TODO: Validation + + # self.app.pop_screen() + self.dismiss(showId) + + if event.button.id == "cancel_button": + self.app.pop_screen() + + def addShow(self, show_id, show_name, show_year): + + try: + s = self.Session() + show = Show(id = show_id, + name = show_name, + year = show_year) + + s.add(show) + s.commit() + except Exception as ex: + click.ClickException(f"ShowDetailsScreen.addShow(): {repr(ex)}") + finally: + s.close() + diff --git a/bin/ffx/show_new_screen.py b/bin/ffx/show_new_screen.py new file mode 100644 index 0000000..7c3ff8b --- /dev/null +++ b/bin/ffx/show_new_screen.py @@ -0,0 +1,60 @@ +import click + +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button +from textual.containers import Grid, Horizontal + +from ffx.model.show import Show + +class ShowNewScreen(Screen): + + CSS = """ + + Grid { + grid-size: 2; + grid-rows: 2 auto; + height: 100%; + width: 100%; + padding: 1; + } + + #top { + height: 1; + } + + + #two { + column-span: 2; + row-span: 2; + tint: magenta 40%; + } + + .box { + height: 100%; + border: solid green; + } + """ + + BINDINGS = [ + ("q", "quit()", "Quit"), + ("h", "switch_mode('help')", "Help") + ] + + def __init__(self): + super().__init__() + + self.context = self.app.getContext() + + self.Session = self.context['database_session'] # convenience + + + def compose(self): + + yield Header() + + with Grid(): + + yield Static("New Show") + + yield Footer() diff --git a/bin/ffx/shows_screen.py b/bin/ffx/shows_screen.py index 18675b9..d801dcc 100644 --- a/bin/ffx/shows_screen.py +++ b/bin/ffx/shows_screen.py @@ -1,9 +1,16 @@ +import click from textual.app import App, ComposeResult from textual.screen import Screen from textual.widgets import Header, Footer, Placeholder, Label, ListView, ListItem, Static, DataTable, Button from textual.containers import Grid, Horizontal +from ffx.model.show import Show + +from .show_details_screen import ShowDetailsScreen +from .show_new_screen import ShowNewScreen +from .help_screen import HelpScreen + class ShowsScreen(Screen): @@ -35,51 +42,95 @@ class ShowsScreen(Screen): """ BINDINGS = [ - ("q", "quit()", "Quit"), - ("e", "switch_mode('settings')", "Edit Show"), - ("n", "switch_mode('dashboard')", "New Show") - # ("h", "switch_mode('help')", "Help"), + #("q", "quit()", "Quit"), + ("e", "switch_mode('show_details')", "Edit Show"), + ("n", "new_show", "New Show"), + #("h", "switch_mode('help')", "Help") ] + # MODES = { + # "show_details": ShowDetailsScreen, + # "show_new": ShowNewScreen, + # "help": HelpScreen, + # } + + def action_new_show(self): + self.app.push_screen(ShowDetailsScreen(), self.handle_new_screen) + + + def handle_new_screen(self, showId): + + showList = self.loadShows() + + for show in showList: + if show[0] == showId: + self.table.add_row(*map(str, show)) + + + def __init__(self): super().__init__() - context = self.app.getContext() - context['dashboard'] = 'dashboard' + self.context = self.app.getContext() + + self.Session = self.context['database_session'] # convenience + + + def loadShows(self): + + try: + s = self.Session() + q = s.query(Show) + + return [(int(s.id), s.name, s.year) for s in q.all()] + + except Exception as ex: + click.ClickException(f"ShowsScreen(): {repr(ex)}") + finally: + s.close() + + + def on_mount(self) -> None: + for show in self.loadShows(): + self.table.add_row(*map(str, show)) # Convert each element to a string before adding + + + #def on_resume(self) -> None: + + # quit() + + # showList = self.loadShows() +# + # tableIdSet = set({int(r[0]) for r in self.table.rows}) + # databaseIdSet = set({int(s[0]) for s in showList}) +# + # missingIdSet = databaseIdSet - tableIdSet + # removedIdSet = tableIdSet - databaseIdSet +# + # for show in showList: + # if show[0] in missingIdSet: + # self.table.add_row(*map(str, show)) + + def compose(self): # Create the DataTable widget - table = DataTable() + self.table = DataTable() # Define the columns with headers - table.add_column("ID", width=10) - table.add_column("Name", width=20) - table.add_column("Age", width=10) - table.add_column("Country", width=20) - - table.cursor_type = 'row' - - # Sample data for rows - data = [ - (1, "John Doe", 28, "USA"), - (2, "Jane Smith", 34, "Canada"), - (3, "Alice Johnson", 29, "UK"), - (4, "Bob Brown", 45, "Australia"), - (5, "Charlie Davis", 23, "USA"), - ] - - # Add rows to the table - for i in range(20): - for row in data: - table.add_row(*map(str, row)) # Convert each element to a string before adding + self.table.add_column("ID", width=10) + self.table.add_column("Name", width=50) + self.table.add_column("Year", width=10) + + self.table.cursor_type = 'row' yield Header() with Grid(): - yield Static("Shows") # , classes="box" + yield Static("Shows") - yield table + yield self.table yield Footer()