152 lines
5.3 KiB
Python
152 lines
5.3 KiB
Python
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())
|