/** * MyShowsPage - Two-panel file-explorer style view * Left panel: list of subscribed shows * Right panel: episodes for the selected show */ import { createSignal, For, Show, createMemo, createEffect } from "solid-js"; import { useKeyboard } from "@opentui/solid"; import { useFeedStore } from "@/stores/feed"; import { useDownloadStore } from "@/stores/download"; import { DownloadStatus } from "@/types/episode"; import { format } from "date-fns"; import type { Episode } from "@/types/episode"; import type { Feed } from "@/types/feed"; import { PageProps } from "@/App"; enum MyShowsPaneType { SHOWS = 1, EPISODES = 2, } export const MyShowsPaneCount = 2; export function MyShowsPage(props: PageProps) { const feedStore = useFeedStore(); const downloadStore = useDownloadStore(); const [showIndex, setShowIndex] = createSignal(0); const [episodeIndex, setEpisodeIndex] = createSignal(0); const [isRefreshing, setIsRefreshing] = createSignal(false); /** Threshold: load more when within this many items of the end */ const LOAD_MORE_THRESHOLD = 5; const shows = () => feedStore.getFilteredFeeds(); const selectedShow = createMemo(() => { const s = shows(); const idx = showIndex(); return idx < s.length ? s[idx] : undefined; }); const episodes = createMemo(() => { const show = selectedShow(); if (!show) return []; return [...show.episodes].sort( (a, b) => b.pubDate.getTime() - a.pubDate.getTime(), ); }); // Detect when user navigates near the bottom and load more episodes createEffect(() => { const idx = episodeIndex(); const eps = episodes(); const show = selectedShow(); if (!show || eps.length === 0) return; const nearBottom = idx >= eps.length - LOAD_MORE_THRESHOLD; if ( nearBottom && feedStore.hasMoreEpisodes(show.id) && !feedStore.isLoadingMore() ) { feedStore.loadMoreEpisodes(show.id); } }); const formatDate = (date: Date): string => { return format(date, "MMM d, yyyy"); }; const formatDuration = (seconds: number): string => { const mins = Math.floor(seconds / 60); const hrs = Math.floor(mins / 60); if (hrs > 0) return `${hrs}h ${mins % 60}m`; return `${mins}m`; }; /** Get download status label for an episode */ const downloadLabel = (episodeId: string): string => { const status = downloadStore.getDownloadStatus(episodeId); switch (status) { case DownloadStatus.QUEUED: return "[Q]"; case DownloadStatus.DOWNLOADING: { const pct = downloadStore.getDownloadProgress(episodeId); return `[${pct}%]`; } case DownloadStatus.COMPLETED: return "[DL]"; case DownloadStatus.FAILED: return "[ERR]"; default: return ""; } }; /** Get download status color */ const downloadColor = (episodeId: string): string => { const status = downloadStore.getDownloadStatus(episodeId); switch (status) { case DownloadStatus.QUEUED: return "yellow"; case DownloadStatus.DOWNLOADING: return "cyan"; case DownloadStatus.COMPLETED: return "green"; case DownloadStatus.FAILED: return "red"; default: return "gray"; } }; const handleRefresh = async () => { const show = selectedShow(); if (!show) return; setIsRefreshing(true); await feedStore.refreshFeed(show.id); setIsRefreshing(false); }; const handleUnsubscribe = () => { const show = selectedShow(); if (!show) return; feedStore.removeFeed(show.id); setShowIndex((i) => Math.max(0, i - 1)); setEpisodeIndex(0); }; return ( Refreshing... 0} fallback={ No shows yet. Subscribe from Discover or Search. } > {(feed, index) => ( { setShowIndex(index()); setEpisodeIndex(0); }} > {index() === showIndex() ? ">" : " "} {feed.customName || feed.podcast.title} ({feed.episodes.length}) )} Select a show } > 0} fallback={ No episodes. Press [r] to refresh. } > {(episode, index) => ( setEpisodeIndex(index())} > {index() === episodeIndex() ? ">" : " "} {episode.episodeNumber ? `#${episode.episodeNumber} ` : ""} {episode.title} {formatDate(episode.pubDate)} {formatDuration(episode.duration)} {downloadLabel(episode.id)} )} Loading more episodes... Scroll down for more episodes ); }