From de8c467d7f5cf36e21c7318be47fccf66f205b3b Mon Sep 17 00:00:00 2001 From: bronsen Date: Thu, 4 Dec 2025 07:01:32 +0100 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20Add=20link=20"?= =?UTF-8?q?textual"=20in=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3e87d31..b4c7f9d 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,4 @@ This is a little TUI based on [Textual] for entering new Teilchen and for searching for existing Teilchen. +[Textual]: https://textual.textualize.io/ From 65ee1e37c65c156135eb30cbdbcc8f14d0c2ccf5 Mon Sep 17 00:00:00 2001 From: bronsen Date: Thu, 4 Dec 2025 13:21:49 +0100 Subject: [PATCH 2/4] =?UTF-8?q?feat(ui):=20=E2=9C=A8=20Display=20header,?= =?UTF-8?q?=20footer,=20and=20searchbar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/teilchensammler_cli/__init__.py | 8 ++++ src/teilchensammler_cli/main.py | 63 +++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/teilchensammler_cli/__init__.py create mode 100644 src/teilchensammler_cli/main.py diff --git a/src/teilchensammler_cli/__init__.py b/src/teilchensammler_cli/__init__.py new file mode 100644 index 0000000..18235ac --- /dev/null +++ b/src/teilchensammler_cli/__init__.py @@ -0,0 +1,8 @@ +""" +This file exists so the directory becomes a package. +""" + +from .main import main + +if __name__ == "__main__": + main() diff --git a/src/teilchensammler_cli/main.py b/src/teilchensammler_cli/main.py new file mode 100644 index 0000000..3ca3a9b --- /dev/null +++ b/src/teilchensammler_cli/main.py @@ -0,0 +1,63 @@ +""" +This is where the application is implemented. +""" + +from typing import final, override + +from textual.app import App, ComposeResult +from textual.containers import Horizontal +from textual.screen import Screen +from textual.widget import Widget +from textual.widgets import ( + Button, + Footer, + Header, + Input, +) + + +@final +class SearchBar(Widget): + DEFAULT_CSS = """ + #teilchen-input { + width: 4fr; + } + #button-search, #button-add { + width: 1fr; + } + """ + + @override + def compose(self) -> ComposeResult: + with Horizontal(id="search-bar-widget"): + yield Input( + placeholder="Enter Teilchen information: name, description, #tags", + tooltip=( + "This is a free-form field: Enter a name and " + "description any way you like. You should use #hashtags for any " + "meta information." + ), + id="teilchen-input", + type="text", + ) + yield Button("Add", variant="success", id="button-add") + yield Button("Search", variant="default", id="button-search") + + +class AddInventoryScreen(Screen[None]): + @override + def compose(self) -> ComposeResult: + yield Header() + yield SearchBar() + yield Footer() + + +@final +class SammlerApp(App[None]): + def on_mount(self) -> None: + _ = self.push_screen(AddInventoryScreen()) + + +def main() -> None: + app = SammlerApp() + app.run() From 4fbee9dfee50f5adf757aa03e136b39e74bc8a89 Mon Sep 17 00:00:00 2001 From: bronsen Date: Thu, 4 Dec 2025 19:25:18 +0100 Subject: [PATCH 3/4] =?UTF-8?q?feat(ui):=20=E2=9C=A8=20Add=20Datatable=20l?= =?UTF-8?q?ayout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/teilchensammler_cli/main.py | 37 +++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/teilchensammler_cli/main.py b/src/teilchensammler_cli/main.py index 3ca3a9b..44e2c92 100644 --- a/src/teilchensammler_cli/main.py +++ b/src/teilchensammler_cli/main.py @@ -2,14 +2,15 @@ This is where the application is implemented. """ -from typing import final, override +from typing import Literal, final, override from textual.app import App, ComposeResult -from textual.containers import Horizontal +from textual.containers import HorizontalGroup from textual.screen import Screen from textual.widget import Widget from textual.widgets import ( Button, + DataTable, Footer, Header, Input, @@ -42,6 +43,37 @@ class SearchBar(Widget): ) yield Button("Add", variant="success", id="button-add") yield Button("Search", variant="default", id="button-search") +TeilchenDatum = tuple[int, str, str, str, str] +TeilchenHeader = tuple[ + Literal["pk"], + Literal["Name"], + Literal["Description"], + Literal["Number"], + Literal["Tags"], +] + + +FAKE_DATA: list[TeilchenHeader | TeilchenDatum] = [ + ("pk", "Name", "Description", "Number", "Tags"), + (0, "Name0", "Description0", "9000", "#tag0 #tag00 #tag000"), + (1, "Name1", "Description1", "9001", "#tag1 #tag11 #tag111"), + (2, "Name2", "Description2", "9002", "#tag2 #tag22 #tag222"), + (3, "Name3", "Description3", "9003", "#tag3 #tag33 #tag333"), + (4, "Name4", "Description4", "9004", "#tag4 #tag44 #tag444"), + (5, "Name5", "Description5", "9005", "#tag5 #tag55 #tag555"), +] + + +@final +class SearchResults(Widget): + @override + def compose(self) -> ComposeResult: + yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True) + + def on_mount(self) -> None: + table: DataTable[None] = self.query_one(DataTable[None]) + _ = table.add_columns(*FAKE_DATA[0]) # pyright: ignore[reportArgumentType] + _ = table.add_rows(FAKE_DATA[1:]) # pyright: ignore[reportArgumentType] class AddInventoryScreen(Screen[None]): @@ -49,6 +81,7 @@ class AddInventoryScreen(Screen[None]): def compose(self) -> ComposeResult: yield Header() yield SearchBar() + yield SearchResults() yield Footer() From 8999f5a52f9279b7a4ebc9cccd877797b4ddbc49 Mon Sep 17 00:00:00 2001 From: bronsen Date: Thu, 4 Dec 2025 19:28:14 +0100 Subject: [PATCH 4/4] =?UTF-8?q?feat(ui):=20=E2=9C=A8=20Layout=20our=20sear?= =?UTF-8?q?ch=20results=20more=20correctly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/teilchensammler_cli/main.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/teilchensammler_cli/main.py b/src/teilchensammler_cli/main.py index 44e2c92..093e4ed 100644 --- a/src/teilchensammler_cli/main.py +++ b/src/teilchensammler_cli/main.py @@ -14,11 +14,12 @@ from textual.widgets import ( Footer, Header, Input, + Static, ) @final -class SearchBar(Widget): +class SearchBar(Static): DEFAULT_CSS = """ #teilchen-input { width: 4fr; @@ -30,7 +31,7 @@ class SearchBar(Widget): @override def compose(self) -> ComposeResult: - with Horizontal(id="search-bar-widget"): + with HorizontalGroup(id="search-bar-widget"): yield Input( placeholder="Enter Teilchen information: name, description, #tags", tooltip=( @@ -41,8 +42,17 @@ class SearchBar(Widget): id="teilchen-input", type="text", ) - yield Button("Add", variant="success", id="button-add") - yield Button("Search", variant="default", id="button-search") + yield Button( + "Add", variant="success", classes="search-bar-buttons", id="button-add" + ) + yield Button( + "Search", + variant="default", + classes="search-bar-buttons", + id="button-search", + ) + + TeilchenDatum = tuple[int, str, str, str, str] TeilchenHeader = tuple[ Literal["pk"],