diff --git a/src/components/Selectable.tsx b/src/components/Selectable.tsx index 77436e7..3a590e9 100644 --- a/src/components/Selectable.tsx +++ b/src/components/Selectable.tsx @@ -6,14 +6,12 @@ export function SelectableBox({ selected, children, ...props -}: { - selected: () => boolean; - children: JSXElement; -} & BoxOptions) { +}: { selected: () => boolean; children: JSXElement } & BoxOptions) { const { theme } = useTheme(); return ( Categories: - - - {(category) => { - const isSelected = () => - discoverStore.selectedCategory() === category.id; + + + {(category) => { + const isSelected = () => + discoverStore.selectedCategory() === category.id; - return ( - handleCategorySelect(category.id)} - > - - {category.icon} {category.name} - - - ); - }} - - + return ( + handleCategorySelect(category.id)} + > + + {category.icon} {category.name} + + + ); + }} + + props.selected} flexDirection="column" padding={1} - backgroundColor={props.selected ? theme.backgroundElement : undefined} onMouseDown={props.onSelect} > - {/* Title Row */} - - {props.podcast.title} - + props.selected}> + {props.podcast.title} + - [+] + [+] {/* Author */} - by {props.podcast.author} + props.selected} + fg={theme.textMuted} + > + by {props.podcast.author} + {/* Description */} - + props.selected} + fg={theme.text} + > {props.podcast.description!.length > 80 ? props.podcast.description!.slice(0, 80) + "..." : props.podcast.description} - + - {/* Categories and Subscribe Button */} - - - 0}> - - {(cat) => [{cat}]} - - - - - - - - {props.podcast.isSubscribed ? "[Unsubscribe]" : "[Subscribe]"} - - + />**/} + + 0}> + + {(cat) => [{cat}]} + - + + + + + {props.podcast.isSubscribed ? "[Unsubscribe]" : "[Subscribe]"} + + + + ); } diff --git a/src/pages/Feed/FeedDetail.tsx b/src/pages/Feed/FeedDetail.tsx index 83edd53..5cb801d 100644 --- a/src/pages/Feed/FeedDetail.tsx +++ b/src/pages/Feed/FeedDetail.tsx @@ -9,6 +9,7 @@ import type { Feed } from "@/types/feed"; import type { Episode } from "@/types/episode"; import { format } from "date-fns"; import { useTheme } from "@/context/ThemeContext"; +import { SelectableBox, SelectableText } from "@/components/Selectable"; interface FeedDetailProps { feed: Feed; @@ -139,11 +140,11 @@ export function FeedDetail(props: FeedDetailProps) { {(episode, index) => ( - index() === selectedIndex()} flexDirection="column" gap={0} padding={1} - backgroundColor={index() === selectedIndex() ? theme.backgroundElement : undefined} onMouseDown={() => { setSelectedIndex(index()); if (props.onPlayEpisode) { @@ -151,20 +152,24 @@ export function FeedDetail(props: FeedDetailProps) { } }} > - - - {index() === selectedIndex() ? ">" : " "} - - - {episode.episodeNumber ? `#${episode.episodeNumber} - ` : ""} - {episode.title} - - + index() === selectedIndex()} + fg={theme.primary} + > + {index() === selectedIndex() ? ">" : " "} + + index() === selectedIndex()} + fg={theme.text} + > + {episode.episodeNumber ? `#${episode.episodeNumber} - ` : ""} + {episode.title} + {formatDate(episode.pubDate)} {formatDuration(episode.duration)} - + )} diff --git a/src/pages/Feed/FeedItem.tsx b/src/pages/Feed/FeedItem.tsx index f055e8e..d4bced8 100644 --- a/src/pages/Feed/FeedItem.tsx +++ b/src/pages/Feed/FeedItem.tsx @@ -6,6 +6,7 @@ import type { Feed, FeedVisibility } from "@/types/feed"; import { format } from "date-fns"; import { useTheme } from "@/context/ThemeContext"; +import { SelectableBox, SelectableText } from "@/components/Selectable"; interface FeedItemProps { feed: Feed; @@ -43,70 +44,111 @@ export function FeedItem(props: FeedItemProps) { if (props.compact) { // Compact single-line view return ( - props.isSelected} flexDirection="row" gap={1} - backgroundColor={props.isSelected ? theme.backgroundElement : undefined} paddingLeft={1} paddingRight={1} + onMouseDown={() => {}} > - + props.isSelected} + fg={theme.primary} + > {props.isSelected ? ">" : " "} - - {visibilityIcon()} - + + props.isSelected} + fg={visibilityColor()} + > + {visibilityIcon()} + + props.isSelected} + fg={theme.text} + > {props.feed.customName || props.feed.podcast.title} - + {props.showEpisodeCount && ( - ({episodeCount()}) + props.isSelected} + fg={theme.textMuted} + > + ({episodeCount()}) + )} - + ); } // Full view with details return ( - props.isSelected} flexDirection="column" gap={0} - border={props.isSelected} - borderColor={props.isSelected ? theme.primary : undefined} - backgroundColor={props.isSelected ? theme.primary : undefined} padding={1} + onMouseDown={() => {}} > {/* Title row */} - + props.isSelected} + fg={theme.primary} + > {props.isSelected ? ">" : " "} - - {visibilityIcon()} - {pinnedIndicator()} - + + props.isSelected} + fg={visibilityColor()} + > + {visibilityIcon()} + + props.isSelected} + fg={theme.warning} + > + {pinnedIndicator()} + + props.isSelected} + fg={theme.text} + > {props.feed.customName || props.feed.podcast.title} - + {props.showEpisodeCount && ( - + props.isSelected} + fg={theme.textMuted} + > {episodeCount()} episodes ({unplayedCount()} new) - + )} {props.showLastUpdated && ( - + props.isSelected} + fg={theme.textMuted} + > Updated: {formatDate(props.feed.lastUpdated)} - + )} {props.feed.podcast.description && ( - - - {props.feed.podcast.description.slice(0, 60)} - {props.feed.podcast.description.length > 60 ? "..." : ""} - - + props.isSelected} + paddingLeft={4} + paddingTop={0} + fg={theme.textMuted} + > + {props.feed.podcast.description.slice(0, 60)} + {props.feed.podcast.description.length > 60 ? "..." : ""} + )} - + ); } diff --git a/src/pages/Feed/FeedPage.tsx b/src/pages/Feed/FeedPage.tsx index 9c40ebe..1b1aee4 100644 --- a/src/pages/Feed/FeedPage.tsx +++ b/src/pages/Feed/FeedPage.tsx @@ -10,6 +10,7 @@ import type { Episode } from "@/types/episode"; import type { Feed } from "@/types/feed"; import { useTheme } from "@/context/ThemeContext"; import { PageProps } from "@/App"; +import { SelectableBox, SelectableText } from "@/components/Selectable"; enum FeedPaneType { FEED = 1, @@ -27,6 +28,18 @@ export function FeedPage(props: PageProps) { return format(date, "MMM d, yyyy"); }; + const episodesByDate = () => { + const groups: Record = {}; + const sortedEpisodes = allEpisodes(); + + for (const episode of sortedEpisodes) { + const dateKey = formatDate(new Date(episode.episode.pubDate)); + groups[dateKey] = episode; + } + + return groups; + }; + const formatDuration = (seconds: number): string => { const mins = Math.floor(seconds / 60); const hrs = Math.floor(mins / 60); @@ -63,36 +76,43 @@ export function FeedPage(props: PageProps) { } > - {/**TODO: figure out wtf to do here **/} - - - {(item, index) => ( - setSelectedIndex(index())} - > - - - {index() === selectedIndex() ? ">" : " "} - - - {item.episode.title} - + + b.localeCompare(a))}> + {([date, episode], groupIndex) => ( + <> + + {date} - - {item.feed.podcast.title} - {formatDate(item.episode.pubDate)} - {formatDuration(item.episode.duration)} - - + groupIndex() === selectedIndex()} + flexDirection="column" + gap={0} + paddingLeft={1} + paddingRight={1} + paddingTop={0} + paddingBottom={0} + onMouseDown={() => setSelectedIndex(groupIndex())} + > + groupIndex() === selectedIndex()}> + {groupIndex() === selectedIndex() ? ">" : " "} + + groupIndex() === selectedIndex()} + fg={theme.text} + > + {episode.episode.title} + + + {episode.feed.podcast.title} + + {formatDate(episode.episode.pubDate)} + + + {formatDuration(episode.episode.duration)} + + + + )} diff --git a/src/pages/Search/ResultCard.tsx b/src/pages/Search/ResultCard.tsx index 5f6f060..15dec65 100644 --- a/src/pages/Search/ResultCard.tsx +++ b/src/pages/Search/ResultCard.tsx @@ -2,6 +2,7 @@ import { Show } from "solid-js"; import type { SearchResult } from "@/types/source"; import { SourceBadge } from "./SourceBadge"; import { useTheme } from "@/context/ThemeContext"; +import { SelectableBox, SelectableText } from "@/components/Selectable"; type ResultCardProps = { result: SearchResult; @@ -15,12 +16,10 @@ export function ResultCard(props: ResultCardProps) { const podcast = () => props.result.podcast; return ( - props.selected} flexDirection="column" padding={1} - border={props.selected} - borderColor={props.selected ? theme.primary : undefined} - backgroundColor={props.selected ? theme.backgroundElement : undefined} onMouseDown={props.onSelect} > - + props.selected} + fg={theme.primary} + > {podcast().title} - + - by {podcast().author} + props.selected} + fg={theme.textMuted} + > + by {podcast().author} + {(description) => ( - + props.selected} + fg={theme.text} + > {description().length > 120 ? description().slice(0, 120) + "..." : description()} - + )} @@ -80,6 +90,6 @@ export function ResultCard(props: ResultCardProps) { [+] Add to Feeds - + ); } diff --git a/src/pages/Search/SearchHistory.tsx b/src/pages/Search/SearchHistory.tsx index 6b3d338..f0ffc49 100644 --- a/src/pages/Search/SearchHistory.tsx +++ b/src/pages/Search/SearchHistory.tsx @@ -4,6 +4,7 @@ import { For, Show } from "solid-js" import { useTheme } from "@/context/ThemeContext" +import { SelectableBox, SelectableText } from "@/components/Selectable" type SearchHistoryProps = { history: string[] @@ -52,23 +53,31 @@ export function SearchHistory(props: SearchHistoryProps) { const isSelected = () => index() === props.selectedIndex && props.focused return ( - handleSearchClick(index(), query)} > - - {">"} - {query} - + + {">"} + + + {query} + handleRemoveClick(query)} padding={0}> [x] - + ) }} diff --git a/src/pages/Settings/SourceManager.tsx b/src/pages/Settings/SourceManager.tsx index ea9850b..bfd02db 100644 --- a/src/pages/Settings/SourceManager.tsx +++ b/src/pages/Settings/SourceManager.tsx @@ -8,6 +8,7 @@ import { useFeedStore } from "@/stores/feed"; import { useTheme } from "@/context/ThemeContext"; import { SourceType } from "@/types/source"; import type { PodcastSource } from "@/types/source"; +import { SelectableBox, SelectableText } from "@/components/Selectable"; interface SourceManagerProps { focused?: boolean; @@ -183,24 +184,20 @@ export function SourceManager(props: SourceManagerProps) { Sources: - - - {(source, index) => ( - { - setSelectedIndex(index()); - setFocusArea("list"); - feedStore.toggleSource(source.id); - }} - > + + + {(source, index) => ( + focusArea() === "list" && index() === selectedIndex()} + flexDirection="row" + gap={1} + padding={0} + onMouseDown={() => { + setSelectedIndex(index()); + setFocusArea("list"); + feedStore.toggleSource(source.id); + }} + > " : " "} - - {source.enabled ? "[x]" : "[ ]"} - - {getSourceIcon(source)} - focusArea() === "list" && index() === selectedIndex()} + fg={theme.text} > {source.name} - - + + )} diff --git a/src/ui/command.tsx b/src/ui/command.tsx index af8ae6d..1113aac 100644 --- a/src/ui/command.tsx +++ b/src/ui/command.tsx @@ -15,6 +15,7 @@ import { useDialog } from "./dialog"; import { useTheme } from "../context/ThemeContext"; import { TextAttributes } from "@opentui/core"; import { emit } from "../utils/event-bus"; +import { SelectableBox, SelectableText } from "@/components/Selectable"; /** * Command option for the command palette. @@ -281,37 +282,53 @@ function CommandDialog(props: { {(option, index) => ( - index() === selectedIndex()} + flexDirection="column" padding={1} + onMouseDown={() => { + setSelectedIndex(index()); + const selectedOption = filteredOptions()[index()]; + if (selectedOption) { + selectedOption.onSelect?.(dialog); + dialog.clear(); + } + }} > - - index() === selectedIndex()} + fg={ + index() === selectedIndex() + ? theme.selectedListItemText + : theme.text + } + attributes={ + index() === selectedIndex() + ? TextAttributes.BOLD + : undefined + } + > + {option.title} + + + index() === selectedIndex()} + fg={theme.textMuted} > - {option.title} - - - {option.footer} - - + {option.footer} + + - {option.description} + index() === selectedIndex()} + fg={theme.textMuted} + > + {option.description} + - + )}