diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..026fc6b --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,122 @@ +import React, { useState, useEffect } from 'react'; +import { useWebSocket } from './hooks/useWebSocket'; +import { useRecorder } from './hooks/useRecorder'; +import { PortCard } from './components/PortCard'; +import { VideoPreview } from './components/VideoPreview'; +import { ConfigPanel } from './components/ConfigPanel'; +import { SCTE35Trigger } from './components/SCTE35Trigger'; +import { PortStatus, RecorderConfig } from './types'; + +export default function App() { + const [ports, setPorts] = useState([]); + const [selectedPort, setSelectedPort] = useState(null); + const [configPort, setConfigPort] = useState(null); + const [configs, setConfigs] = useState>({}); + + const wsUrl = `ws://${window.location.host}/ws`; + const { isConnected, lastMessage } = useWebSocket(wsUrl); + const { startRecording, stopRecording, injectSCTE35 } = useRecorder(); + + // Update ports from WebSocket messages + useEffect(() => { + if (lastMessage?.type === 'port_status' && lastMessage.ports) { + setPorts(lastMessage.ports); + } + }, [lastMessage]); + + // Initial fetch of port statuses + useEffect(() => { + fetch('/api/ports') + .then(r => r.json()) + .then(setPorts) + .catch(console.error); + }, []); + + const handleStartRecording = async (portIndex: number) => { + const config = configs[portIndex] || defaultConfig(portIndex); + await startRecording(portIndex, config); + }; + + const handleSaveConfig = (config: RecorderConfig) => { + setConfigs(prev => ({ ...prev, [config.port_index]: config })); + // If not recording, optionally auto-start? No — just save + }; + + const handleInjectSCTE35 = async (eventId: number, durationSeconds: number) => { + await injectSCTE35(eventId, durationSeconds); + }; + + return ( +
+ {/* Header */} +
+
+

Deltacast SDI Recorder

+
+
+ {isConnected ? 'Connected' : 'Disconnected'} +
+
+
+ +
+ {/* Port Cards Grid - responsive: 1 col mobile, 2 tablet, 4 desktop */} +
+ {ports.map(port => ( + + ))} + {ports.length === 0 && ( +
+ No ports available. Check backend connection. +
+ )} +
+ + {/* Selected Port Detail View */} + {selectedPort !== null && ( +
+
+

Port {selectedPort} Preview

+ +
+
+

Ad Break Control

+ +
+
+ )} +
+ + {/* Config Panel Modal */} + {configPort !== null && ( + setConfigPort(null)} + /> + )} +
+ ); +} + +function defaultConfig(portIndex: number): RecorderConfig { + return { + port_index: portIndex, + codec: 'PRORES', + bitrate: '185M', + quality_profile: 'hq', + recording_path: `/recordings/port_${portIndex}_{timestamp}.mxf`, + srt_enabled: false, + srt_destination: '', + preview_enabled: true, + }; +}