187 lines
5.1 KiB
Python
187 lines
5.1 KiB
Python
"""
|
|
Search History Screen for YouTube TUI
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from textual.app import ComposeResult
|
|
from textual.containers import Container
|
|
from textual.screen import ModalScreen
|
|
from textual.widgets import Footer, Header, ListItem, ListView, Static
|
|
|
|
|
|
class SearchHistoryScreen(ModalScreen):
|
|
"""Screen showing search history"""
|
|
|
|
CSS = """
|
|
SearchHistoryScreen {
|
|
align: center middle;
|
|
}
|
|
|
|
#history-container {
|
|
width: 70%;
|
|
height: 70%;
|
|
border: solid #555555;
|
|
background: $surface;
|
|
padding: 1;
|
|
}
|
|
|
|
#history-title {
|
|
width: 100%;
|
|
height: 3;
|
|
dock: top;
|
|
background: $primary;
|
|
content-align: center middle;
|
|
color: $text;
|
|
}
|
|
|
|
ListView {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
ListItem {
|
|
height: 3;
|
|
padding: 0 1;
|
|
}
|
|
|
|
ListItem:hover {
|
|
background: $primary-darken-2;
|
|
}
|
|
|
|
ListItem.--highlight {
|
|
background: $primary;
|
|
}
|
|
|
|
.history-item {
|
|
height: 3;
|
|
}
|
|
|
|
.history-time {
|
|
color: $text-muted;
|
|
}
|
|
|
|
#clear-btn {
|
|
margin: 1 1;
|
|
}
|
|
"""
|
|
|
|
BINDINGS = [
|
|
("escape", "close_history", "Close"),
|
|
("q", "close_history", "Close"),
|
|
("d", "delete_selected", "Delete Selected"),
|
|
("c", "clear_all", "Clear All"),
|
|
]
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.history_file = (
|
|
Path.home() / ".config" / "youtube_cli" / "search_history.json"
|
|
)
|
|
self.search_history = []
|
|
|
|
def compose(self) -> ComposeResult:
|
|
"""Compose the history screen"""
|
|
yield Header()
|
|
yield Container(
|
|
Static("Search History", id="history-title"),
|
|
ListView(id="history-list"),
|
|
id="history-container",
|
|
)
|
|
yield Footer()
|
|
|
|
def on_mount(self) -> None:
|
|
"""Load history on mount"""
|
|
self.load_history()
|
|
|
|
def load_history(self) -> None:
|
|
"""Load search history from file"""
|
|
self.search_history = []
|
|
|
|
if self.history_file.exists():
|
|
try:
|
|
with open(self.history_file, "r") as f:
|
|
self.search_history = json.load(f)
|
|
except Exception:
|
|
self.search_history = []
|
|
|
|
# Reverse to show newest first
|
|
self.search_history = list(reversed(self.search_history))
|
|
|
|
list_view = self.query_one("#history-list", ListView)
|
|
list_view.clear()
|
|
|
|
for item in self.search_history:
|
|
search_term = item.get("search_term", "")
|
|
timestamp = item.get("timestamp", "")
|
|
|
|
# Format timestamp
|
|
if timestamp:
|
|
try:
|
|
from datetime import datetime
|
|
|
|
dt = datetime.fromisoformat(timestamp)
|
|
time_str = dt.strftime("%Y-%m-%d %H:%M")
|
|
except Exception:
|
|
time_str = timestamp
|
|
else:
|
|
time_str = "Unknown time"
|
|
|
|
list_view.append(
|
|
ListItem(Static(f"[bold]{search_term}[/bold]\n[dim]{time_str}[/dim]"))
|
|
)
|
|
|
|
def action_delete_selected(self) -> None:
|
|
"""Delete selected history item"""
|
|
list_view = self.query_one("#history-list", ListView)
|
|
if list_view.children:
|
|
# Get the selected item
|
|
selected_index = list_view.index
|
|
if (
|
|
selected_index is not None
|
|
and selected_index >= 0
|
|
and selected_index < len(self.search_history)
|
|
):
|
|
# Remove from history
|
|
del self.search_history[selected_index]
|
|
self.save_history()
|
|
self.load_history()
|
|
|
|
def action_clear_all(self) -> None:
|
|
"""Clear all history"""
|
|
self.search_history = []
|
|
self.save_history()
|
|
self.load_history()
|
|
self.notify("Search history cleared", timeout=2)
|
|
|
|
def save_history(self) -> None:
|
|
"""Save history to file"""
|
|
try:
|
|
# Ensure directory exists
|
|
self.history_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with open(self.history_file, "w") as f:
|
|
json.dump(list(reversed(self.search_history)), f, indent=2)
|
|
except Exception as e:
|
|
self.notify(f"Error saving history: {e}", severity="error", timeout=3)
|
|
|
|
def action_close_history(self) -> None:
|
|
"""Close the history screen"""
|
|
self.app.pop_screen()
|
|
|
|
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
|
"""Handle item selection"""
|
|
# Use event.index directly, ignore list_view
|
|
item_index = event.index
|
|
|
|
if 0 <= item_index < len(self.search_history):
|
|
# Get the search term
|
|
search_term = self.search_history[item_index].get("search_term", "")
|
|
|
|
# Push search screen with the term
|
|
self.app.push_screen("search")
|
|
# Note: We'd need to expose the search screen to set the term
|
|
# For now, just notify
|
|
self.notify(f"Selected: {search_term}", timeout=2)
|