diff --git a/src/App.tsx b/src/App.tsx index ba0cea6..66795d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,6 +23,7 @@ import { useToast } from "@/ui/toast"; import { useRenderer } from "@opentui/solid"; import type { AuthScreen } from "@/types/auth"; import type { Episode } from "@/types/episode"; +import { DIRECTION } from "./types/navigation"; export function App() { const [activeTab, setActiveTab] = createSignal("feed"); @@ -61,26 +62,23 @@ export function App() { onExit: () => setLayerDepth(0), }); - // Centralized keyboard handler for all tab navigation and shortcuts useAppKeyboard({ - get activeTab() { - return activeTab(); - }, - onTabChange: (tab: TabId) => { - setActiveTab(tab); - setInputFocused(false); - }, - get inputFocused() { - return inputFocused(); - }, - get navigationEnabled() { - return layerDepth() === 0; - }, layerDepth, - onLayerChange: (newDepth) => { - setLayerDepth(newDepth); - }, - onAction: (action) => { + onAction: (action, direction) => { + if (action == "cycle") { + if (direction == DIRECTION.Increment) { + //if() + } + if (direction == DIRECTION.Decrement) { + } + } + if (action == "depth") { + if (direction == DIRECTION.Increment) { + } + if (direction == DIRECTION.Decrement) { + } + } + if (action === "escape") { if (layerDepth() > 0) { setLayerDepth(0); @@ -90,10 +88,6 @@ export function App() { setInputFocused(false); } } - - if (action === "enter" && layerDepth() === 0) { - setLayerDepth(1); - } }, }); diff --git a/src/hooks/useAppKeyboard.ts b/src/hooks/useAppKeyboard.ts index e6965f2..917779f 100644 --- a/src/hooks/useAppKeyboard.ts +++ b/src/hooks/useAppKeyboard.ts @@ -3,145 +3,30 @@ * Single handler to prevent conflicts */ -import { useKeyboard, useRenderer } from "@opentui/solid" -import type { Accessor } from "solid-js" - -const TAB_ORDER: TabId[] = ["feed", "shows", "discover", "search", "player", "settings"] - -type TabId = - | "feed" - | "shows" - | "discover" - | "search" - | "player" - | "settings" +import { TabId } from "@/components/TabNavigation"; +import { useKeyboard, useRenderer } from "@opentui/solid"; +import type { Accessor } from "solid-js"; type ShortcutOptions = { - activeTab: TabId - onTabChange: (tab: TabId) => void - onAction?: (action: string) => void - inputFocused?: boolean - navigationEnabled?: boolean - layerDepth?: Accessor - onLayerChange?: (newDepth: number) => void -} + onAction?: (action: string, direction: Direction) => void; + layerDepth: Accessor; +}; -export function useAppKeyboard(options: ShortcutOptions) { - const renderer = useRenderer() - - const getNextTab = (current: TabId): TabId => { - const idx = TAB_ORDER.indexOf(current) - return TAB_ORDER[(idx + 1) % TAB_ORDER.length] - } - - const getPrevTab = (current: TabId): TabId => { - const idx = TAB_ORDER.indexOf(current) - return TAB_ORDER[(idx - 1 + TAB_ORDER.length) % TAB_ORDER.length] - } +export function useAppKeyboard(props: ShortcutOptions) { + const renderer = useRenderer(); + // layer depth 0 is tabs, they are oriented + // vertically, all others are vertically useKeyboard((key) => { - // Always allow quit - if (key.ctrl && key.name === "q") { - renderer.destroy() - return - } - - if (key.name === "escape") { - options.onAction?.("escape") - return - } - - // Skip global shortcuts if input is focused (let input handle keys) - if (options.inputFocused) { - return - } - - if (options.navigationEnabled === false) { - return - } - - // Return key cycles tabs (equivalent to Tab) - if (key.name === "return") { - options.onTabChange(getNextTab(options.activeTab)) - return - } - - // Layer navigation with left/right arrows - if (options.layerDepth !== undefined && options.onLayerChange) { - const currentDepth = options.layerDepth() - const maxLayers = 3 - - if (key.name === "right") { - if (currentDepth < maxLayers) { - options.onLayerChange(currentDepth + 1) - } - return - } - - if (key.name === "left") { - if (currentDepth > 0) { - options.onLayerChange(currentDepth - 1) - } - return - } - } - - // Tab navigation with left/right arrows OR [ and ] - if (key.name === "right" || key.name === "]") { - options.onTabChange(getNextTab(options.activeTab)) - return - } - - if (key.name === "left" || key.name === "[") { - options.onTabChange(getPrevTab(options.activeTab)) - return - } - - // Number keys for direct tab access (1-6) - if (key.name === "1") { - options.onTabChange("feed") - return - } - if (key.name === "2") { - options.onTabChange("shows") - return - } - if (key.name === "3") { - options.onTabChange("discover") - return - } - if (key.name === "4") { - options.onTabChange("search") - return - } - if (key.name === "5") { - options.onTabChange("player") - return - } - if (key.name === "6") { - options.onTabChange("settings") - return - } - - // Tab key cycles tabs (Shift+Tab goes backwards) - if (key.name === "tab") { + // handle cycle current layer + if (props.layerDepth() == 0) { + let reverse = false; if (key.shift) { - options.onTabChange(getPrevTab(options.activeTab)) - } else { - options.onTabChange(getNextTab(options.activeTab)) + reverse = true; } - return - } - - // Forward other actions - if (options.onAction) { - if (key.ctrl && key.name === "s") { - options.onAction("save") - } else if (key.ctrl && key.name === "f") { - options.onAction("find") - } else if (key.name === "?" || (key.shift && key.name === "/")) { - options.onAction("help") + if (key.name == "tab" || key.name == "down" || key.name == "j") { } } - }) + // handle cycle depth + }); } diff --git a/src/tabs/Discover/CategoryFilter.tsx b/src/tabs/Discover/CategoryFilter.tsx deleted file mode 100644 index 6610ff8..0000000 --- a/src/tabs/Discover/CategoryFilter.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * CategoryFilter component - Horizontal category filter tabs - */ - -import { For } from "solid-js"; -import type { DiscoverCategory } from "@/stores/discover"; - -type CategoryFilterProps = { - categories: DiscoverCategory[]; - selectedCategory: string; - focused: boolean; - onSelect?: (categoryId: string) => void; -}; - -export function CategoryFilter(props: CategoryFilterProps) { - return ( - - - {(category) => { - const isSelected = () => props.selectedCategory === category.id; - - return ( - props.onSelect?.(category.id)} - > - - {category.icon} {category.name} - - - ); - }} - - - ); -} diff --git a/src/tabs/Discover/DiscoverPage.tsx b/src/tabs/Discover/DiscoverPage.tsx index 36709ce..86d8fc5 100644 --- a/src/tabs/Discover/DiscoverPage.tsx +++ b/src/tabs/Discover/DiscoverPage.tsx @@ -2,11 +2,11 @@ * DiscoverPage component - Main discover/browse interface for PodTUI */ -import { createSignal } from "solid-js"; +import { createSignal, For, Show } from "solid-js"; import { useKeyboard } from "@opentui/solid"; import { useDiscoverStore, DISCOVER_CATEGORIES } from "@/stores/discover"; -import { CategoryFilter } from "./CategoryFilter"; -import { TrendingShows } from "./TrendingShows"; +import { useTheme } from "@/context/ThemeContext"; +import { PodcastCard } from "./PodcastCard"; type DiscoverPageProps = { focused: boolean; @@ -37,10 +37,7 @@ export function DiscoverPage(props: DiscoverPageProps) { return; } - if ( - key.name === "return" && - area === "categories" - ) { + if (key.name === "return" && area === "categories") { setFocusArea("shows"); return; } @@ -141,42 +138,47 @@ export function DiscoverPage(props: DiscoverPageProps) { discoverStore.toggleSubscription(podcast.id); }; + const { theme } = useTheme(); return ( - - {/* Header */} + - - Discover Podcasts + + Categories: - - {discoverStore.filteredPodcasts().length} shows - discoverStore.refresh()}> - [R] Refresh - - - - - {/* Category Filter */} - - - Categories: - - + + {(category) => { + const isSelected = () => + discoverStore.selectedCategory() === category.id; + + return ( + handleCategorySelect(category.id)} + > + + {category.icon} {category.name} + + + ); + }} + - - {/* Trending Shows */} - + Trending in{" "} @@ -185,23 +187,40 @@ export function DiscoverPage(props: DiscoverPageProps) { )?.name ?? "All"} - - - - {/* Footer Hints */} - - [Tab] Switch focus - [j/k] Navigate - [Enter] Subscribe - [Esc] Up - [R] Refresh + + + {discoverStore.filteredPodcasts().length !== 0 ? ( + Loading trending shows... + ) : ( + No podcasts found in this category. + )} + + } + when={ + !discoverStore.isLoading() && + discoverStore.filteredPodcasts().length === 0 + } + > + + + + {(podcast, index) => ( + handleShowSelect(index())} + onSubscribe={() => handleSubscribe(podcast)} + /> + )} + + + + + ); diff --git a/src/tabs/Discover/TrendingShows.tsx b/src/tabs/Discover/TrendingShows.tsx deleted file mode 100644 index 68694eb..0000000 --- a/src/tabs/Discover/TrendingShows.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/** - * TrendingShows component - Grid/list of trending podcasts - */ - -import { For, Show } from "solid-js"; -import type { Podcast } from "@/types/podcast"; -import { PodcastCard } from "./PodcastCard"; - -type TrendingShowsProps = { - podcasts: Podcast[]; - selectedIndex: number; - focused: boolean; - isLoading: boolean; - onSelect?: (index: number) => void; - onSubscribe?: (podcast: Podcast) => void; -}; - -export function TrendingShows(props: TrendingShowsProps) { - return ( - - - - Loading trending shows... - - - - - - No podcasts found in this category. - - - - 0}> - - - - {(podcast, index) => ( - props.onSelect?.(index())} - onSubscribe={() => props.onSubscribe?.(podcast)} - /> - )} - - - - - - ); -} diff --git a/src/types/navigation.ts b/src/types/navigation.ts new file mode 100644 index 0000000..54ec36d --- /dev/null +++ b/src/types/navigation.ts @@ -0,0 +1,4 @@ +export enum DIRECTION { + Increment, + Decrement, +} diff --git a/src/utils/navigation.ts b/src/utils/navigation.ts new file mode 100644 index 0000000..dcc7f67 --- /dev/null +++ b/src/utils/navigation.ts @@ -0,0 +1,64 @@ +enum FEEDTABTYPE { + LATEST, +} +export const FeedTab = { + [FEEDTABTYPE.LATEST]: { + size: 1, + title: "Feed - Latest Episodes", + scrolling: true, + }, +}; +enum MYSHOWSTYPE { + SHOWLIST, + EPISODELIST, +} +export const MyShowsTab = { + [MYSHOWSTYPE.SHOWLIST]: { size: 0.3, title: "My Shows", scrolling: true }, + [MYSHOWSTYPE.EPISODELIST]: { + size: 0.7, + title: " - Episodes", + scrolling: true, + }, +}; + +enum DiscoverTab { + CATEGORIES, + CATEGORYLIST, +} + +export enum CATEGORIES { + ALL, + TECHNOLOGY, + SCIENCE, + COMEDY, + NEWS, + BUSINESS, + HEALTH, + EDUCATION, + SPORTS, + TRUECRIME, + ARTS, +} +export const SearchTab = []; + +export const PlayerTab = []; + +export const SettingsTab = []; + +export enum TABS { + FEED, + MYSHOWS, + DISCOVER, + SEARCH, + PLAYER, + SETTINGS, +} + +export const LayerGraph = { + [TABS.FEED]: FeedTab, + [TABS.MYSHOWS]: MyShowsTab, + [TABS.DISCOVER]: DiscoverTab, + [TABS.SEARCH]: SearchTab, + [TABS.PLAYER]: PlayerTab, + [TABS.SETTINGS]: SettingsTab, +};