Replace content CLI with Textual TUI

This commit is contained in:
SirBlob committed 2026-11-01 20:46:34 -05:00
1 parent 0d58ddd430
commit 737700a9a8
28 files changed
+2403 -167

No files matched your search

+110
View File
@@ -0,0 +1,110 @@
import sys
from textual import on
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import DataTable, Footer, Header, TabbedContent, TabPane
import portfolio
from tui.books_pane import BooksPane
from tui.media_pane import MediaPane
from tui.music_pane import MusicPane
from tui.profile_pane import ProfilePane
from tui.projects_pane import ProjectsPane
from tui.record_pane import RecordListPane
from tui.themes import default_theme_id, load_site_themes
from tui.themes_pane import ThemesPane
TABS = [
("music", "Music"),
("shows", "Shows"),
("movies", "Movies"),
("books", "Books"),
("profile", "Profile"),
("experience", "Experience"),
("leadership", "Leadership"),
("projects", "Projects"),
("themes", "Themes"),
]
class ContentApp(App):
TITLE = "sirblob@engineering"
SUB_TITLE = "~/content"
CSS_PATH = "tui/app.tcss"
BINDINGS = [
Binding("q", "quit", "Quit"),
Binding("t", "next_theme", "Theme"),
*[Binding(str(number), f"show_tab('{tab_id}')", label, show=False) for number, (tab_id, label) in enumerate(TABS, start=1)],
]
def __init__(self):
super().__init__()
self.site_theme_ids: list[str] = []
self.reload_site_themes(default_theme_id())
def reload_site_themes(self, show_theme_id: str | None = None):
self.site_theme_ids = []
for theme in load_site_themes():
self.register_theme(theme)
self.site_theme_ids.append(theme.name)
if not self.site_theme_ids:
return
target = show_theme_id if show_theme_id in self.site_theme_ids else self.site_theme_ids[0]
if target == self.theme:
fallback = next(theme for theme in self.available_themes if theme != target)
self.theme = fallback
self.theme = target
def action_next_theme(self):
if not self.site_theme_ids:
return
current = self.site_theme_ids.index(self.theme) if self.theme in self.site_theme_ids else -1
self.theme = self.site_theme_ids[(current + 1) % len(self.site_theme_ids)]
self.notify(f"Theme: {self.theme}")
def compose(self) -> ComposeResult:
yield Header()
with TabbedContent(initial="music"):
with TabPane("1 Music", id="music"):
yield MusicPane()
with TabPane("2 Shows", id="shows"):
yield MediaPane("show")
with TabPane("3 Movies", id="movies"):
yield MediaPane("movie")
with TabPane("4 Books", id="books"):
yield BooksPane()
with TabPane("5 Profile", id="profile"):
yield ProfilePane()
with TabPane("6 Experience", id="experience"):
yield RecordListPane(portfolio.EXPERIENCE)
with TabPane("7 Leadership", id="leadership"):
yield RecordListPane(portfolio.LEADERSHIP)
with TabPane("8 Projects", id="projects"):
yield ProjectsPane()
with TabPane("9 Themes", id="themes"):
yield ThemesPane()
yield Footer()
def on_mount(self):
self.focus_active_table()
def action_show_tab(self, tab_id: str):
self.query_one(TabbedContent).active = tab_id
@on(TabbedContent.TabActivated)
def tab_changed(self):
self.focus_active_table()
def focus_active_table(self):
tabs = self.query_one(TabbedContent)
tabs.get_pane(tabs.active).query_one(DataTable).focus()
def main() -> int:
ContentApp().run()
return 0
if __name__ == "__main__":
sys.exit(main())