#!/usr/bin/env python3 """ Category Selection Modal for YouTube TUI """ from typing import Optional from textual.app import ComposeResult from textual.containers import Container, Vertical from textual.screen import ModalScreen from textual.widgets import ( Button, Footer, Header, Input, ListItem, ListView, Static, ) from youtube_tui.services.youtube import YouTubeService class CategorySelectionModal(ModalScreen): """Modal for selecting a download category""" CSS = """ CategorySelectionModal { align: center middle; } #modal-container { width: 60%; height: auto; border: solid #555555; background: $surface; padding: 1; } #modal-title { width: 100%; height: 3; dock: top; background: $primary; content-align: center middle; color: $text; } #categories-container { width: 100%; height: 20; margin: 1 0; } #custom-input { width: 100%; margin: 1 0; } #modal-actions { width: 100%; height: auto; dock: bottom; margin-top: 1; } Button { width: 15; margin: 1 1; } ListItem { height: 3; padding: 0 1; } ListItem:hover { background: $primary-darken-2; } ListItem.--highlight { background: $primary; } """ BINDINGS = [ ("escape", "close_modal", "Cancel"), ("enter", "select_category", "Select"), ("up", "cursor_up", "Cursor Up"), ("down", "cursor_down", "Cursor Down"), ] def __init__(self) -> None: super().__init__() self.youtube_service = YouTubeService() self.selected_category: Optional[str] = None self.selected_index = 0 def compose(self) -> ComposeResult: """Compose the modal""" yield Header() yield Container( Static("Select Download Category", id="modal-title"), Vertical( Static("Available Categories:", id="categories-label"), ListView(id="categories-list"), Static("Or type custom folder name:", id="custom-label"), Input(placeholder="Enter custom folder name...", id="custom-input"), id="categories-container", ), Container( Button("Select", id="select-btn"), Button("Cancel", id="cancel-btn"), id="modal-actions", ), id="modal-container", ) yield Footer() def on_mount(self) -> None: """Called when modal is mounted""" self.load_categories() self.update_status("Use arrow keys to select, Enter to confirm") def load_categories(self) -> None: """Load available categories into the list""" list_view = self.query_one("#categories-list", ListView) list_view.clear() try: # Note: This is called from on_mount which is sync # In a real async context, this should be awaited categories: list = self.youtube_service.cli.get_categories( self.youtube_service.cli.config ) for category in categories: # Extract folder name for display from pathlib import Path folder_name = Path(category).name if Path(category).name else "Root" # Create a custom widget for the item item = ListItem(Static(f" {folder_name}"), id=f"category-{category}") list_view.append(item) # Highlight first item if list_view.children: list_view.children[0].add_class("--highlight") self.selected_index = 0 except Exception as e: list_view.append( ListItem(Static(f"[red]Error loading categories: {e}[/red]")) ) def update_status(self, message: str) -> None: """Update the modal status""" # We could add a status line if needed pass def action_select_category(self) -> None: """Select the current category""" list_view = self.query_one("#categories-list", ListView) if list_view.children and 0 <= self.selected_index < len(list_view.children): # Get the selected category item = list_view.children[self.selected_index] category_id = item.id if category_id and category_id.startswith("category-"): self.selected_category = category_id.replace("category-", "") self.dismiss(self.selected_category) return # Check custom input custom_input = self.query_one("#custom-input", Input) custom_name = custom_input.value.strip() if custom_name: self.selected_category = custom_name self.dismiss(self.selected_category) return # No valid selection self.update_status("[red]Please select a category or enter a custom name[/red]") def action_cursor_up(self) -> None: """Move cursor up""" list_view = self.query_one("#categories-list", ListView) if list_view.children: # Remove highlight from current item if 0 <= self.selected_index < len(list_view.children): list_view.children[self.selected_index].remove_class("--highlight") # Move up self.selected_index = max(0, self.selected_index - 1) # Highlight new item list_view.children[self.selected_index].add_class("--highlight") def action_cursor_down(self) -> None: """Move cursor down""" list_view = self.query_one("#categories-list", ListView) if list_view.children: # Remove highlight from current item if 0 <= self.selected_index < len(list_view.children): list_view.children[self.selected_index].remove_class("--highlight") # Move down self.selected_index = min( len(list_view.children) - 1, self.selected_index + 1 ) # Highlight new item list_view.children[self.selected_index].add_class("--highlight") def action_close_modal(self) -> None: """Close modal without selecting""" self.selected_category = None self.dismiss(None) def on_button_pressed(self, event: Button.Pressed) -> None: """Handle button presses""" if event.button.id == "select-btn": self.action_select_category() elif event.button.id == "cancel-btn": self.action_close_modal() def on_input_submitted(self, event: Input.Submitted) -> None: """Handle enter key in custom input""" self.action_select_category()