from rich.text import Text from textual.app import ComposeResult from textual.binding import Binding from textual.widgets import DataTable import imdb import media from tui.content_pane import ContentPane from tui.dialogs import ConfirmDialog, FormDialog, FormField STATUS_STYLES = {"watching": "blue", "watched": "green"} def rating_text(item: dict) -> str: return f"{item['rating']}/10" if item.get("rating") is not None else "-" class MediaPane(ContentPane): BINDINGS = [ Binding("a", "add", "Add"), Binding("e", "rate", "Rate"), Binding("s", "status", "Watching"), Binding("g", "collection", "Group"), Binding("d", "delete", "Delete"), Binding("r", "reload", "Reload"), ] content_file = "about.json" def __init__(self, kind: str, **kwargs): super().__init__(**kwargs) self.kind = kind self.label = media.MEDIA_KINDS[kind]["label"] self.border_subtitle = media.MEDIA_KINDS[kind]["key"] def compose(self) -> ComposeResult: yield DataTable(cursor_type="row", zebra_stripes=True) def on_mount(self): table = self.table() table.add_column("Status", key="status") table.add_column("Rating", key="rating") table.add_column("Title", key="title") table.add_column("Group", key="collection") table.add_column("IMDb", key="imdb") self.reload() def reload(self, focus_key: str | None = None): table = self.table() table.clear() for item in media.list_items(self.kind): identifier = imdb.imdb_id(item.get("url", "")) or item.get("name", "") status = media.status_of(item) table.add_row( Text(status, style=STATUS_STYLES[status]), rating_text(item), item.get("name", ""), item.get("collection", ""), identifier, key=identifier, ) self.restore_cursor(focus_key) def selected_item(self) -> dict | None: identifier = self.selected_key() return next((item for item in media.list_items(self.kind) if imdb.imdb_id(item.get("url", "")) == identifier), None) def collection_hint(self) -> str: names = media.collection_names(self.kind) return f"Existing: {', '.join(names)}" if names else "e.g. Star Wars" def action_add(self): fields = [ FormField("link", "IMDb link or ID", placeholder="https://www.imdb.com/title/tt0816692/", required=True), FormField("status", "Status", kind="select", options=media.STATUSES, value="watched"), FormField("rating", "Your rating (0 to 10, optional)", kind="rating"), FormField("collection", "Group (optional, same name groups titles together)", placeholder=self.collection_hint()), FormField("name", "Name (looked up on OMDb when empty)"), ] self.app.push_screen(FormDialog(f"Add a {self.label}", fields, "Add"), self.add_item) def add_item(self, values: dict | None): if not values: return identifier = imdb.imdb_id(values["link"]) def added(item: dict): self.reload(identifier) self.notify(f"Added {item['name']} ({media.status_of(item)})") self.run_in_background( f"Adding {identifier or values['link']}...", lambda: media.add_item(self.kind, values["link"], values["rating"], values["name"], values["status"], values["collection"]), added, ) def action_rate(self): item = self.selected_item() if not item: return identifier = imdb.imdb_id(item["url"]) current = "" if item.get("rating") is None else str(item["rating"]) fields = [FormField("rating", f"Rating for {item['name']} (empty to clear)", kind="rating", value=current)] def rated(values: dict | None): if values is not None: self.run_now(lambda: media.rate_item(self.kind, identifier, values["rating"]), f"Updated {item['name']}", identifier) self.app.push_screen(FormDialog("Change rating", fields), rated) def action_status(self): item = self.selected_item() if not item: return identifier = imdb.imdb_id(item["url"]) next_status = "watched" if media.status_of(item) == "watching" else "watching" self.run_now(lambda: media.set_status(self.kind, identifier, next_status), f"{item['name']} is now {next_status}", identifier) def action_collection(self): item = self.selected_item() if not item: return identifier = imdb.imdb_id(item["url"]) fields = [FormField("collection", f"Group for {item['name']} (empty to remove)", value=item.get("collection", ""), placeholder=self.collection_hint())] def grouped(values: dict | None): if values is not None: collection = values["collection"] message = f"Added {item['name']} to {collection}" if collection else f"Removed {item['name']} from its group" self.run_now(lambda: media.set_collection(self.kind, identifier, collection), message, identifier) self.app.push_screen(FormDialog("Group titles", fields), grouped) def action_delete(self): identifier = self.selected_key() if not identifier: return name = self.table().get_cell(identifier, "title") def confirmed(should_delete: bool | None): if should_delete: self.run_now(lambda: media.remove_item(self.kind, identifier), f"Removed {name}") self.app.push_screen(ConfirmDialog(f"Remove {name}?"), confirmed)