import sys from textual import on, work from textual.app import App, ComposeResult from textual.binding import Binding from textual.widgets import DataTable, Footer, Header, TabbedContent, TabPane import library_push import portfolio from tui.books_pane import BooksPane from tui.dialogs import ConfirmDialog from tui.games_pane import GamesPane 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"), ("games", "Games"), ("profile", "Profile"), ("experience", "Experience"), ("leadership", "Leadership"), ("projects", "Projects"), ("themes", "Themes"), ] def tab_key(number: int) -> str: return str(number % 10) 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("P", "push_library", "Push to website"), *[Binding(tab_key(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 Games", id="games"): yield GamesPane() with TabPane("6 Profile", id="profile"): yield ProfilePane() with TabPane("7 Experience", id="experience"): yield RecordListPane(portfolio.EXPERIENCE) with TabPane("8 Leadership", id="leadership"): yield RecordListPane(portfolio.LEADERSHIP) with TabPane("9 Projects", id="projects"): yield ProjectsPane() with TabPane("0 Themes", id="themes"): yield ThemesPane() yield Footer() def on_mount(self): self.focus_active_table() def action_push_library(self): self.notify("Comparing the library with the website...") self.prepare_push() @work(thread=True, exclusive=True, group="push") def prepare_push(self): try: plan = library_push.plan_push() except (OSError, ValueError) as error: self.call_from_thread(self.notify, str(error), title="Push", severity="error", timeout=10) return self.call_from_thread(self.confirm_push, plan) def confirm_push(self, plan: library_push.PushPlan): def confirmed(should_push: bool | None): if should_push: self.push_library(plan) self.push_screen(ConfirmDialog(plan.describe(), "Push", "primary"), confirmed) @work(thread=True, exclusive=True, group="push") def push_library(self, plan: library_push.PushPlan): progress = lambda message: self.call_from_thread(self.notify, message) result = library_push.run_push(plan, progress) for message in result.errors[:5]: self.call_from_thread(self.notify, message, title="Push", severity="warning", timeout=10) summary = f"Pushed {result.pushed} and removed {result.removed} on {plan.target.url}" severity = "warning" if result.errors else "information" self.call_from_thread(self.notify, f"{summary}, {len(result.errors)} failed", title="Push", severity=severity, timeout=10) 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())