import { useCallback, useEffect, useRef, useState } from 'react' import { GamepadHintItem } from '../components/GamepadPrompt' import type { FollowedChannelInfo, GameInfo } from '../types/t4sd' import FocusableCard from '../components/FocusableCard' interface Props { game: GameInfo onSelectChannel: (ch: FollowedChannelInfo) => void onStartLive: (ch: FollowedChannelInfo) => void onBack: () => void } type LoadState = 'loading' | 'ok' | 'error' export default function CategoryScreen({ game, onSelectChannel, onStartLive, onBack }: Props): JSX.Element { const [streams, setStreams] = useState([]) const [loadState, setLoadState] = useState('loading') const [focusedIndex, setFocusedIndex] = useState(0) const gridRef = useRef(null) const load = useCallback(async () => { setLoadState('loading') try { const data = await window.t4sd.twitch.getTopStreams({ gameId: game.id, limit: 40 }) setStreams(data.streams) setFocusedIndex(0) setLoadState('ok') } catch (err) { console.error('[CategoryScreen] Laden fehlgeschlagen:', err) setLoadState('error') } }, [game.id]) useEffect(() => { void load() }, [load]) const getColumns = useCallback((): number => { const grid = gridRef.current if (!grid) return 4 const cols = getComputedStyle(grid).gridTemplateColumns.split(' ').filter(Boolean).length return Math.max(1, cols) }, []) useEffect(() => { const onKey = (e: KeyboardEvent): void => { if (e.key === 'y' || e.key === 'Y') { void load() return } if (e.key === 'Escape') { e.preventDefault() onBack() return } if (loadState !== 'ok' || streams.length === 0) return const cols = getColumns() const total = streams.length switch (e.key) { case 'ArrowRight': e.preventDefault() setFocusedIndex((i) => Math.min(i + 1, total - 1)) break case 'ArrowLeft': e.preventDefault() if (focusedIndex % cols === 0) onBack() else setFocusedIndex((i) => i - 1) break case 'ArrowDown': e.preventDefault() setFocusedIndex((i) => Math.min(i + cols, total - 1)) break case 'ArrowUp': e.preventDefault() setFocusedIndex((i) => Math.max(i - cols, 0)) break case 'Enter': { e.preventDefault() const ch = streams[focusedIndex] if (ch) onStartLive(ch) break } case 'x': case 'X': { e.preventDefault() const ch = streams[focusedIndex] if (ch) onSelectChannel(ch) break } } } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [loadState, streams, focusedIndex, getColumns, onBack, onSelectChannel, onStartLive, load]) return (

{game.name}

{loadState === 'ok' && (

{streams.length} Streams · Aktualisieren · Kanalseite

)}
{loadState === 'loading' && (

Lade Streams…

)} {loadState === 'error' && (

Fehler beim Laden der Streams.

)} {loadState === 'ok' && streams.length === 0 && (

Keine Live-Streams in dieser Kategorie.

)} {loadState === 'ok' && streams.length > 0 && (
{streams.map((ch, i) => ( setFocusedIndex(i)} onSelect={() => onStartLive(ch)} /> ))}
)}
) }