254 lines
7.7 KiB
Python
254 lines
7.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Category Selection Modal for YouTube TUI
|
|
"""
|
|
|
|
import logging
|
|
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
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
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
|
|
)
|
|
|
|
logger.debug(f"load_categories: categories={categories}")
|
|
|
|
for category in categories:
|
|
# Extract folder name for display
|
|
from pathlib import Path
|
|
|
|
folder_name = Path(category).name if Path(category).name else "Root"
|
|
category_id = Path(category).name.replace(" ", "-").replace("/", "-")
|
|
logger.debug(
|
|
f"load_categories: category={category}, folder_name={folder_name}, category_id={category_id}"
|
|
)
|
|
|
|
# Create a custom widget for the item
|
|
item = ListItem(
|
|
Static(f" {folder_name}"), id=f"category-{category_id}"
|
|
)
|
|
list_view.append(item)
|
|
|
|
# Highlight first item
|
|
if list_view.children:
|
|
list_view.children[0].add_class("--highlight")
|
|
self.selected_index = 0
|
|
logger.debug(
|
|
f"load_categories: first item highlighted, selected_index={self.selected_index}"
|
|
)
|
|
|
|
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)
|
|
|
|
# Debug logging
|
|
logger.debug(
|
|
f"action_select_category: selected_index={self.selected_index}, children_count={len(list_view.children)}"
|
|
)
|
|
|
|
if list_view.children and 0 <= self.selected_index < len(list_view.children):
|
|
# Get the selected item
|
|
item = list_view.children[self.selected_index]
|
|
category_id = item.id
|
|
logger.debug(f"action_select_category: item.id={category_id}")
|
|
if category_id and category_id.startswith("category-"):
|
|
self.selected_category = category_id.replace("category-", "")
|
|
logger.debug(
|
|
f"action_select_category: selected_category={self.selected_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()
|