'use client'; import { useState, useEffect, useMemo, useCallback } from 'react'; import Link from 'next/link'; import Dropdown from '@/components/Dropdown'; import { useToast } from '@/lib/useToast'; import { ToastContainer } from '@/components/Toast'; import { useActiveSourceLookup, useDebounce, PerformanceMonitor } from '@/lib/performance'; type Stream = { id: number; name: string; obs_source_name: string; url: string; }; type ScreenType = 'large' | 'left' | 'right' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; export default function Home() { const [streams, setStreams] = useState([]); const [activeSources, setActiveSources] = useState>({ large: null, left: null, right: null, topLeft: null, topRight: null, bottomLeft: null, bottomRight: null, }); const [isLoading, setIsLoading] = useState(true); const [openDropdown, setOpenDropdown] = useState(null); const { toasts, removeToast, showSuccess, showError } = useToast(); // Memoized active source lookup for performance const activeSourceIds = useActiveSourceLookup(streams, activeSources); // Debounced API calls to prevent excessive requests const setActiveFunction = useCallback(async (screen: ScreenType, id: number | null) => { if (id) { const selectedStream = streams.find(stream => stream.id === id); try { const endTimer = PerformanceMonitor.startTimer('setActive_api'); const response = await fetch('/api/setActive', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ screen, id }), }); endTimer(); if (!response.ok) { throw new Error('Failed to set active stream'); } showSuccess('Source Updated', `Set ${selectedStream?.name || 'stream'} as active for ${screen}`); } catch (error) { console.error('Error setting active stream:', error); showError('Failed to Update Source', 'Could not set active stream. Please try again.'); // Revert local state on error setActiveSources((prev) => ({ ...prev, [screen]: null, })); } } }, [streams, showError, showSuccess]); const debouncedSetActive = useDebounce(setActiveFunction, 300); const fetchData = useCallback(async () => { const endTimer = PerformanceMonitor.startTimer('fetchData'); try { // Fetch streams and active sources in parallel const [streamsRes, activeRes] = await Promise.all([ fetch('/api/streams'), fetch('/api/getActive') ]); const [streamsData, activeData] = await Promise.all([ streamsRes.json(), activeRes.json() ]); setStreams(streamsData); setActiveSources(activeData); } catch (error) { console.error('Error fetching data:', error); showError('Failed to Load Data', 'Could not fetch streams. Please refresh the page.'); } finally { setIsLoading(false); endTimer(); } }, [showError]); useEffect(() => { fetchData(); }, [fetchData]); const handleSetActive = useCallback(async (screen: ScreenType, id: number | null) => { const selectedStream = streams.find((stream) => stream.id === id); // Update local state immediately for optimistic updates setActiveSources((prev) => ({ ...prev, [screen]: selectedStream?.obs_source_name || null, })); // Debounced backend update debouncedSetActive(screen, id); }, [streams, debouncedSetActive]); const handleToggleDropdown = useCallback((screen: string) => { setOpenDropdown((prev) => (prev === screen ? null : screen)); }, []); // Memoized corner displays to prevent re-renders const cornerDisplays = useMemo(() => [ { screen: 'topLeft' as const, label: 'Top Left' }, { screen: 'topRight' as const, label: 'Top Right' }, { screen: 'bottomLeft' as const, label: 'Bottom Left' }, { screen: 'bottomRight' as const, label: 'Bottom Right' }, ], []); if (isLoading) { return (
Loading streams...
); } return (
{/* Title */}

Stream Control Center

Manage your OBS sources across multiple screen positions

{/* Main Screen */}

Primary Display

handleSetActive('large', id)} label="Select Primary Stream..." isOpen={openDropdown === 'large'} onToggle={() => handleToggleDropdown('large')} />
{/* Side Displays */}

Side Displays

Left Display

handleSetActive('left', id)} label="Select Left Stream..." isOpen={openDropdown === 'left'} onToggle={() => handleToggleDropdown('left')} />

Right Display

handleSetActive('right', id)} label="Select Right Stream..." isOpen={openDropdown === 'right'} onToggle={() => handleToggleDropdown('right')} />
{/* Corner Displays */}

Corner Displays

{cornerDisplays.map(({ screen, label }) => (

{label}

handleSetActive(screen, id)} label="Select Stream..." isOpen={openDropdown === screen} onToggle={() => handleToggleDropdown(screen)} />
))}
{/* Manage Streams Section */} {streams.length > 0 && (

Manage Streams

{streams.map((stream) => (

{stream.name}

{stream.obs_source_name}

✏️ Edit
))}
)} {/* Toast Notifications */}
); }