import { useState, useEffect, useCallback, useRef } from "react"; import type { StreamStatus, Channel } from "./types"; import * as api from "./api/client"; import Player from "./components/Player"; import StatusBar from "./components/StatusBar"; import AdminPanel from "./components/AdminPanel"; import ChannelList from "./components/ChannelList"; import "./App.css"; // Detect touch-primary devices (phones/tablets have no hover pointer). const isTouchPrimary = () => window.matchMedia("(pointer: coarse)").matches || navigator.maxTouchPoints > 0; const HIDE_DELAY = 3000; // ms of inactivity before toggle hides again export default function App() { const [status, setStatus] = useState(null); const [channels, setChannels] = useState([]); const [sidebarOpen, setSidebarOpen] = useState(false); const [toggleVisible, setToggleVisible] = useState(false); const hideTimer = useRef | null>(null); const fetchStatus = useCallback(async () => { try { const s = await api.getStatus(); setStatus(s); } catch { // backend unreachable } }, []); const fetchChannels = useCallback(async () => { try { const ch = await api.getChannels(); setChannels(ch); } catch { // backend unreachable } }, []); useEffect(() => { fetchStatus(); fetchChannels(); // Poll faster while live so passive viewers detect channel switches promptly. // Transitioning gets the fastest rate so the UI clears quickly after a switch. const rate = status?.transitioning ? 1000 : status?.live ? 2000 : 5000; const interval = setInterval(fetchStatus, rate); return () => clearInterval(interval); }, [fetchStatus, fetchChannels, status?.live, status?.transitioning]); useEffect(() => { if (status?.playlist_version) { fetchChannels(); } }, [fetchChannels, status?.playlist_version]); // Show the toggle button on interaction, then auto-hide after HIDE_DELAY. // When the sidebar is open we keep it visible so the user can close it. const showToggle = useCallback(() => { setToggleVisible(true); if (hideTimer.current) clearTimeout(hideTimer.current); if (!sidebarOpen) { hideTimer.current = setTimeout(() => setToggleVisible(false), HIDE_DELAY); } }, [sidebarOpen]); // Keep toggle visible while sidebar is open; restart timer when it closes. useEffect(() => { if (sidebarOpen) { if (hideTimer.current) clearTimeout(hideTimer.current); setToggleVisible(true); } else { hideTimer.current = setTimeout(() => setToggleVisible(false), HIDE_DELAY); } return () => { if (hideTimer.current) clearTimeout(hideTimer.current); }; }, [sidebarOpen]); // Wire up interaction listeners on the document. useEffect(() => { const touch = isTouchPrimary(); if (touch) { document.addEventListener("touchstart", showToggle, { passive: true }); return () => document.removeEventListener("touchstart", showToggle); } else { document.addEventListener("mousemove", showToggle); return () => document.removeEventListener("mousemove", showToggle); } }, [showToggle]); const handlePlaylistReloaded = () => { fetchChannels(); }; const handleChannelRefresh = () => { fetchStatus(); }; return (
{/* Fullscreen video background */}
{/* Collapsible right sidebar */} {/* Status banner fixed to the bottom */}
); }