temp keyboard handling

This commit is contained in:
2026-02-20 23:42:29 -05:00
parent 1e6618211a
commit b45e7bf538
6 changed files with 210 additions and 6 deletions

View File

@@ -2,13 +2,14 @@
* DiscoverPage component - Main discover/browse interface for PodTUI * DiscoverPage component - Main discover/browse interface for PodTUI
*/ */
import { createSignal, For, Show } from "solid-js"; import { createSignal, For, Show, onMount } from "solid-js";
import { useKeyboard } from "@opentui/solid"; import { useKeyboard } from "@opentui/solid";
import { useDiscoverStore, DISCOVER_CATEGORIES } from "@/stores/discover"; import { useDiscoverStore, DISCOVER_CATEGORIES } from "@/stores/discover";
import { useTheme } from "@/context/ThemeContext"; import { useTheme } from "@/context/ThemeContext";
import { PodcastCard } from "./PodcastCard"; import { PodcastCard } from "./PodcastCard";
import { SelectableBox, SelectableText } from "@/components/Selectable"; import { SelectableBox, SelectableText } from "@/components/Selectable";
import { useNavigation } from "@/context/NavigationContext"; import { useNavigation } from "@/context/NavigationContext";
import { KeybindProvider, useKeybinds } from "@/context/KeybindContext";
enum DiscoverPagePaneType { enum DiscoverPagePaneType {
CATEGORIES = 1, CATEGORIES = 1,
@@ -21,6 +22,36 @@ export function DiscoverPage() {
const [showIndex, setShowIndex] = createSignal(0); const [showIndex, setShowIndex] = createSignal(0);
const [categoryIndex, setCategoryIndex] = createSignal(0); const [categoryIndex, setCategoryIndex] = createSignal(0);
const nav = useNavigation(); const nav = useNavigation();
const keybind = useKeybinds();
onMount(() => {
useKeyboard(
(keyEvent: any) => {
const isDown = keybind.match("down", keyEvent);
const isUp = keybind.match("up", keyEvent);
const isEnter = keyEvent.name === "Enter" || keyEvent.name === " ";
const isSpace = keyEvent.name === " ";
if (isEnter || isSpace) {
const filteredPodcasts = discoverStore.filteredPodcasts();
if (filteredPodcasts.length > 0 && showIndex() < filteredPodcasts.length) {
setShowIndex(showIndex() + 1);
}
return;
}
const filteredPodcasts = discoverStore.filteredPodcasts();
if (filteredPodcasts.length === 0) return;
if (isDown && showIndex() < filteredPodcasts.length - 1) {
setShowIndex(showIndex() + 1);
} else if (isUp && showIndex() > 0) {
setShowIndex(showIndex() - 1);
}
},
{ release: false },
);
});
const handleCategorySelect = (categoryId: string) => { const handleCategorySelect = (categoryId: string) => {
discoverStore.setSelectedCategory(categoryId); discoverStore.setSelectedCategory(categoryId);

View File

@@ -3,7 +3,7 @@
* Reverse chronological order, grouped by date * Reverse chronological order, grouped by date
*/ */
import { createSignal, For, Show } from "solid-js"; import { createSignal, For, Show, onMount } from "solid-js";
import { useFeedStore } from "@/stores/feed"; import { useFeedStore } from "@/stores/feed";
import { format } from "date-fns"; import { format } from "date-fns";
import type { Episode } from "@/types/episode"; import type { Episode } from "@/types/episode";
@@ -13,6 +13,8 @@ import { SelectableBox, SelectableText } from "@/components/Selectable";
import { useNavigation } from "@/context/NavigationContext"; import { useNavigation } from "@/context/NavigationContext";
import { LoadingIndicator } from "@/components/LoadingIndicator"; import { LoadingIndicator } from "@/components/LoadingIndicator";
import { TABS } from "@/utils/navigation"; import { TABS } from "@/utils/navigation";
import { useKeyboard } from "@opentui/solid";
import { KeybindProvider, useKeybinds } from "@/context/KeybindContext";
enum FeedPaneType { enum FeedPaneType {
FEED = 1, FEED = 1,
@@ -29,6 +31,37 @@ export function FeedPage() {
string | undefined string | undefined
>(); >();
const allEpisodes = () => feedStore.getAllEpisodesChronological(); const allEpisodes = () => feedStore.getAllEpisodesChronological();
const keybind = useKeybinds();
const [focusedIndex, setFocusedIndex] = createSignal(0);
onMount(() => {
useKeyboard(
(keyEvent: any) => {
const isDown = keybind.match("down", keyEvent);
const isUp = keybind.match("up", keyEvent);
const isEnter = keyEvent.name === "Enter" || keyEvent.name === " ";
const isSpace = keyEvent.name === " ";
if (isEnter || isSpace) {
const episodes = allEpisodes();
if (episodes.length > 0 && episodes[focusedIndex()]) {
setSelectedEpisodeID(episodes[focusedIndex()].episode.id);
}
return;
}
const episodes = allEpisodes();
if (episodes.length === 0) return;
if (isDown && focusedIndex() < episodes.length - 1) {
setFocusedIndex(focusedIndex() + 1);
} else if (isUp && focusedIndex() > 0) {
setFocusedIndex(focusedIndex() - 1);
}
},
{ release: false },
);
});
const formatDate = (date: Date): string => { const formatDate = (date: Date): string => {
return format(date, "MMM d, yyyy"); return format(date, "MMM d, yyyy");
@@ -105,6 +138,13 @@ export function FeedPage() {
} }
return false; return false;
}; };
const isFocused = () => {
const episodes = allEpisodes();
const currentIndex = episodes.findIndex(
(e: any) => e.episode.id === item.episode.id,
);
return currentIndex === focusedIndex();
};
return ( return (
<SelectableBox <SelectableBox
selected={isSelected} selected={isSelected}
@@ -115,7 +155,11 @@ export function FeedPage() {
paddingTop={0} paddingTop={0}
paddingBottom={0} paddingBottom={0}
onMouseDown={() => { onMouseDown={() => {
// Selection is handled by App's keyboard navigation setSelectedEpisodeID(item.episode.id);
const episodes = allEpisodes();
setFocusedIndex(
episodes.findIndex((e: any) => e.episode.id === item.episode.id),
);
}} }}
> >
<SelectableText selected={isSelected} primary> <SelectableText selected={isSelected} primary>

View File

@@ -4,7 +4,8 @@
* Right panel: episodes for the selected show * Right panel: episodes for the selected show
*/ */
import { createSignal, For, Show, createMemo, createEffect } from "solid-js"; import { createSignal, For, Show, createMemo, createEffect, onMount } from "solid-js";
import { useKeyboard } from "@opentui/solid";
import { useFeedStore } from "@/stores/feed"; import { useFeedStore } from "@/stores/feed";
import { useDownloadStore } from "@/stores/download"; import { useDownloadStore } from "@/stores/download";
import { DownloadStatus } from "@/types/episode"; import { DownloadStatus } from "@/types/episode";
@@ -32,6 +33,50 @@ export function MyShowsPage() {
const mutedColor = () => theme.muted || theme.text; const mutedColor = () => theme.muted || theme.text;
const nav = useNavigation(); const nav = useNavigation();
onMount(() => {
useKeyboard(
(keyEvent: any) => {
const isDown =
keyEvent.key === "j" || keyEvent.key === "ArrowDown";
const isUp =
keyEvent.key === "k" || keyEvent.key === "ArrowUp";
const isSelect =
keyEvent.key === "Enter" || keyEvent.key === " ";
const shows = feedStore.getFilteredFeeds();
const episodesList = episodes();
const selected = selectedShow();
if (isSelect) {
if (shows.length > 0 && showIndex() < shows.length) {
setShowIndex(showIndex() + 1);
}
if (episodesList.length > 0 && episodeIndex() < episodesList.length) {
setEpisodeIndex(episodeIndex() + 1);
}
return;
}
if (shows.length > 0) {
if (isDown && showIndex() < shows.length - 1) {
setShowIndex(showIndex() + 1);
} else if (isUp && showIndex() > 0) {
setShowIndex(showIndex() - 1);
}
}
if (episodesList.length > 0) {
if (isDown && episodeIndex() < episodesList.length - 1) {
setEpisodeIndex(episodeIndex() + 1);
} else if (isUp && episodeIndex() > 0) {
setEpisodeIndex(episodeIndex() - 1);
}
}
},
{ release: false },
);
});
/** Threshold: load more when within this many items of the end */ /** Threshold: load more when within this many items of the end */
const LOAD_MORE_THRESHOLD = 5; const LOAD_MORE_THRESHOLD = 5;

View File

@@ -4,6 +4,8 @@ import { useAudio } from "@/hooks/useAudio";
import { useAppStore } from "@/stores/app"; import { useAppStore } from "@/stores/app";
import { useTheme } from "@/context/ThemeContext"; import { useTheme } from "@/context/ThemeContext";
import { useNavigation } from "@/context/NavigationContext"; import { useNavigation } from "@/context/NavigationContext";
import { useKeyboard } from "@opentui/solid";
import { onMount } from "solid-js";
enum PlayerPaneType { enum PlayerPaneType {
PLAYER = 1, PLAYER = 1,
@@ -15,6 +17,32 @@ export function PlayerPage() {
const { theme } = useTheme(); const { theme } = useTheme();
const nav = useNavigation(); const nav = useNavigation();
onMount(() => {
useKeyboard(
(keyEvent: any) => {
const isNext = keyEvent.key === "l" || keyEvent.key === "ArrowRight";
const isPrev = keyEvent.key === "h" || keyEvent.key === "ArrowLeft";
const isPlayPause = keyEvent.key === " " || keyEvent.key === "Enter";
if (isPlayPause) {
audio.togglePlayback();
return;
}
if (isNext) {
audio.seek(audio.currentEpisode()?.duration ?? 0);
return;
}
if (isPrev) {
audio.seek(0);
return;
}
},
{ release: false },
);
});
const progressPercent = () => { const progressPercent = () => {
const d = audio.duration(); const d = audio.duration();
if (d <= 0) return 0; if (d <= 0) return 0;

View File

@@ -2,7 +2,7 @@
* SearchPage component - Main search interface for PodTUI * SearchPage component - Main search interface for PodTUI
*/ */
import { createSignal, createEffect, Show } from "solid-js"; import { createSignal, createEffect, Show, onMount } from "solid-js";
import { useKeyboard } from "@opentui/solid"; import { useKeyboard } from "@opentui/solid";
import { useSearchStore } from "@/stores/search"; import { useSearchStore } from "@/stores/search";
import { SearchResults } from "./SearchResults"; import { SearchResults } from "./SearchResults";
@@ -27,6 +27,37 @@ export function SearchPage() {
const { theme } = useTheme(); const { theme } = useTheme();
const nav = useNavigation(); const nav = useNavigation();
onMount(() => {
useKeyboard(
(keyEvent: any) => {
const isDown =
keyEvent.key === "j" || keyEvent.key === "ArrowDown";
const isUp =
keyEvent.key === "k" || keyEvent.key === "ArrowUp";
const isSelect =
keyEvent.key === "Enter" || keyEvent.key === " ";
if (isSelect) {
const results = searchStore.results();
if (results.length > 0 && resultIndex() < results.length) {
setResultIndex(resultIndex() + 1);
}
return;
}
const results = searchStore.results();
if (results.length === 0) return;
if (isDown && resultIndex() < results.length - 1) {
setResultIndex(resultIndex() + 1);
} else if (isUp && resultIndex() > 0) {
setResultIndex(resultIndex() - 1);
}
},
{ release: false },
);
});
const handleSearch = async () => { const handleSearch = async () => {
const query = inputValue().trim(); const query = inputValue().trim();
if (query) { if (query) {

View File

@@ -1,4 +1,4 @@
import { createSignal, For } from "solid-js"; import { createSignal, For, onMount } from "solid-js";
import { useKeyboard } from "@opentui/solid"; import { useKeyboard } from "@opentui/solid";
import { SourceManager } from "./SourceManager"; import { SourceManager } from "./SourceManager";
import { useTheme } from "@/context/ThemeContext"; import { useTheme } from "@/context/ThemeContext";
@@ -33,6 +33,31 @@ export function SettingsPage() {
return nav.activeDepth() === depth; return nav.activeDepth() === depth;
}; };
onMount(() => {
useKeyboard(
(keyEvent: any) => {
const isDown =
keyEvent.key === "j" || keyEvent.key === "ArrowDown";
const isUp =
keyEvent.key === "k" || keyEvent.key === "ArrowUp";
const isSelect =
keyEvent.key === "Enter" || keyEvent.key === " ";
if (isSelect) {
nav.setActiveDepth((nav.activeDepth() % SettingsPaneCount) + 1);
return;
}
const nextDepth = isDown
? (nav.activeDepth() % SettingsPaneCount) + 1
: (nav.activeDepth() - 2 + SettingsPaneCount) % SettingsPaneCount + 1;
nav.setActiveDepth(nextDepth);
},
{ release: false },
);
});
return ( return (
<box flexDirection="column" gap={1} height="100%" width="100%"> <box flexDirection="column" gap={1} height="100%" width="100%">
<box flexDirection="row" gap={1}> <box flexDirection="row" gap={1}>