/** * Source management component for PodTUI * Add, remove, and configure podcast sources */ import { createSignal, For } from "solid-js" import { useFeedStore } from "../stores/feed" import { SourceType } from "../types/source" import type { PodcastSource } from "../types/source" interface SourceManagerProps { focused?: boolean onClose?: () => void } type FocusArea = "list" | "add" | "url" | "country" | "explicit" | "language" export function SourceManager(props: SourceManagerProps) { const feedStore = useFeedStore() const [selectedIndex, setSelectedIndex] = createSignal(0) const [focusArea, setFocusArea] = createSignal("list") const [newSourceUrl, setNewSourceUrl] = createSignal("") const [newSourceName, setNewSourceName] = createSignal("") const [error, setError] = createSignal(null) const sources = () => feedStore.sources() const handleKeyPress = (key: { name: string; shift?: boolean }) => { if (key.name === "escape") { if (focusArea() !== "list") { setFocusArea("list") setError(null) } else if (props.onClose) { props.onClose() } return } if (key.name === "tab") { const areas: FocusArea[] = [ "list", "country", "language", "explicit", "add", "url", ] const idx = areas.indexOf(focusArea()) const nextIdx = key.shift ? (idx - 1 + areas.length) % areas.length : (idx + 1) % areas.length setFocusArea(areas[nextIdx]) return } if (focusArea() === "list") { if (key.name === "up" || key.name === "k") { setSelectedIndex((i) => Math.max(0, i - 1)) } else if (key.name === "down" || key.name === "j") { setSelectedIndex((i) => Math.min(sources().length - 1, i + 1)) } else if (key.name === "return" || key.name === "enter" || key.name === "space") { const source = sources()[selectedIndex()] if (source) { feedStore.toggleSource(source.id) } } else if (key.name === "d" || key.name === "delete") { const source = sources()[selectedIndex()] if (source) { const removed = feedStore.removeSource(source.id) if (!removed) { setError("Cannot remove default sources") } } } else if (key.name === "a") { setFocusArea("add") } } if (focusArea() === "country") { if (key.name === "enter" || key.name === "return" || key.name === "space") { const source = sources()[selectedIndex()] if (source && source.type === SourceType.API) { const next = source.country === "US" ? "GB" : "US" feedStore.updateSource(source.id, { country: next }) } } } if (focusArea() === "explicit") { if (key.name === "enter" || key.name === "return" || key.name === "space") { const source = sources()[selectedIndex()] if (source && source.type === SourceType.API) { feedStore.updateSource(source.id, { allowExplicit: !source.allowExplicit }) } } } if (focusArea() === "language") { if (key.name === "enter" || key.name === "return" || key.name === "space") { const source = sources()[selectedIndex()] if (source && source.type === SourceType.API) { const next = source.language === "ja_jp" ? "en_us" : "ja_jp" feedStore.updateSource(source.id, { language: next }) } } } } const handleAddSource = () => { const url = newSourceUrl().trim() const name = newSourceName().trim() || `Custom Source` if (!url) { setError("URL is required") return } try { new URL(url) } catch { setError("Invalid URL format") return } feedStore.addSource({ name, type: "rss" as SourceType, baseUrl: url, enabled: true, description: `Custom RSS feed: ${url}`, }) setNewSourceUrl("") setNewSourceName("") setFocusArea("list") setError(null) } const getSourceIcon = (source: PodcastSource) => { if (source.type === SourceType.API) return "[API]" if (source.type === SourceType.RSS) return "[RSS]" return "[?]" } const selectedSource = () => sources()[selectedIndex()] const isApiSource = () => selectedSource()?.type === SourceType.API const sourceCountry = () => selectedSource()?.country || "US" const sourceExplicit = () => selectedSource()?.allowExplicit !== false const sourceLanguage = () => selectedSource()?.language || "en_us" return ( Podcast Sources [Esc] Close Manage where to search for podcasts {/* Source list */} Sources: {(source, index) => ( { setSelectedIndex(index()) setFocusArea("list") feedStore.toggleSource(source.id) }} > {focusArea() === "list" && index() === selectedIndex() ? ">" : " "} {source.enabled ? "[x]" : "[ ]"} {getSourceIcon(source)} {source.name} )} Space/Enter to toggle, d to delete, a to add {/* API settings */} {isApiSource() ? "API Settings" : "API Settings (select an API source)"} Country: {sourceCountry()} Language: {sourceLanguage() === "ja_jp" ? "Japanese" : "English"} Explicit: {sourceExplicit() ? "Yes" : "No"} Enter/Space to toggle focused setting {/* Add new source form */} Add New Source: Name: URL: { setNewSourceUrl(v) setError(null) }} placeholder="https://example.com/feed.rss" focused={props.focused && focusArea() === "url"} width={35} /> [+] Add Source {/* Error message */} {error() && ( {error()} )} Tab to switch sections, Esc to close ) }