fix keyboard, finish 05

This commit is contained in:
2026-02-04 01:18:59 -05:00
parent d5ce8452e4
commit bd4747679d
18 changed files with 2432 additions and 193 deletions

View File

@@ -0,0 +1,193 @@
/**
* DiscoverPage component - Main discover/browse interface for PodTUI
*/
import { createSignal } from "solid-js"
import { useKeyboard } from "@opentui/solid"
import { useDiscoverStore, DISCOVER_CATEGORIES } from "../stores/discover"
import { CategoryFilter } from "./CategoryFilter"
import { TrendingShows } from "./TrendingShows"
type DiscoverPageProps = {
focused: boolean
}
type FocusArea = "categories" | "shows"
export function DiscoverPage(props: DiscoverPageProps) {
const discoverStore = useDiscoverStore()
const [focusArea, setFocusArea] = createSignal<FocusArea>("shows")
const [showIndex, setShowIndex] = createSignal(0)
const [categoryIndex, setCategoryIndex] = createSignal(0)
// Keyboard navigation
useKeyboard((key) => {
if (!props.focused) return
const area = focusArea()
// Tab switches focus between categories and shows
if (key.name === "tab") {
if (key.shift) {
setFocusArea((a) => (a === "categories" ? "shows" : "categories"))
} else {
setFocusArea((a) => (a === "categories" ? "shows" : "categories"))
}
return
}
// Category navigation
if (area === "categories") {
if (key.name === "left" || key.name === "h") {
setCategoryIndex((i) => Math.max(0, i - 1))
const cat = DISCOVER_CATEGORIES[categoryIndex()]
if (cat) discoverStore.setSelectedCategory(cat.id)
setShowIndex(0) // Reset show selection when changing category
return
}
if (key.name === "right" || key.name === "l") {
setCategoryIndex((i) => Math.min(DISCOVER_CATEGORIES.length - 1, i + 1))
const cat = DISCOVER_CATEGORIES[categoryIndex()]
if (cat) discoverStore.setSelectedCategory(cat.id)
setShowIndex(0)
return
}
if (key.name === "enter") {
// Select category and move to shows
setFocusArea("shows")
return
}
if (key.name === "down" || key.name === "j") {
setFocusArea("shows")
return
}
}
// Shows navigation
if (area === "shows") {
const shows = discoverStore.filteredPodcasts()
if (key.name === "down" || key.name === "j") {
setShowIndex((i) => Math.min(i + 1, shows.length - 1))
return
}
if (key.name === "up" || key.name === "k") {
const newIndex = showIndex() - 1
if (newIndex < 0) {
setFocusArea("categories")
} else {
setShowIndex(newIndex)
}
return
}
if (key.name === "enter") {
// Subscribe/unsubscribe
const podcast = shows[showIndex()]
if (podcast) {
discoverStore.toggleSubscription(podcast.id)
}
return
}
}
// Refresh with 'r'
if (key.name === "r") {
discoverStore.refresh()
return
}
})
const handleCategorySelect = (categoryId: string) => {
discoverStore.setSelectedCategory(categoryId)
const index = DISCOVER_CATEGORIES.findIndex((c) => c.id === categoryId)
if (index >= 0) setCategoryIndex(index)
setShowIndex(0)
}
const handleShowSelect = (index: number) => {
setShowIndex(index)
setFocusArea("shows")
}
const handleSubscribe = (podcast: { id: string }) => {
discoverStore.toggleSubscription(podcast.id)
}
return (
<box flexDirection="column" height="100%" gap={1}>
{/* Header */}
<box flexDirection="row" justifyContent="space-between" alignItems="center">
<text>
<strong>Discover Podcasts</strong>
</text>
<box flexDirection="row" gap={2}>
<text>
<span fg="gray">
{discoverStore.filteredPodcasts().length} shows
</span>
</text>
<box onMouseDown={() => discoverStore.refresh()}>
<text>
<span fg="cyan">[R] Refresh</span>
</text>
</box>
</box>
</box>
{/* Category Filter */}
<box border padding={1}>
<box flexDirection="column" gap={1}>
<text>
<span fg={focusArea() === "categories" ? "cyan" : "gray"}>
Categories:
</span>
</text>
<CategoryFilter
categories={discoverStore.categories}
selectedCategory={discoverStore.selectedCategory()}
focused={focusArea() === "categories"}
onSelect={handleCategorySelect}
/>
</box>
</box>
{/* Trending Shows */}
<box flexDirection="column" flexGrow={1} border>
<box padding={1} borderBottom>
<text>
<span fg={focusArea() === "shows" ? "cyan" : "gray"}>
Trending in {
DISCOVER_CATEGORIES.find(
(c) => c.id === discoverStore.selectedCategory()
)?.name ?? "All"
}
</span>
</text>
</box>
<TrendingShows
podcasts={discoverStore.filteredPodcasts()}
selectedIndex={showIndex()}
focused={focusArea() === "shows"}
isLoading={discoverStore.isLoading()}
onSelect={handleShowSelect}
onSubscribe={handleSubscribe}
/>
</box>
{/* Footer Hints */}
<box flexDirection="row" gap={2}>
<text>
<span fg="gray">[Tab] Switch focus</span>
</text>
<text>
<span fg="gray">[j/k] Navigate</span>
</text>
<text>
<span fg="gray">[Enter] Subscribe</span>
</text>
<text>
<span fg="gray">[R] Refresh</span>
</text>
</box>
</box>
)
}