diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 026fc6b..aee93aa 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,19 +12,32 @@ export default function App() { const [selectedPort, setSelectedPort] = useState(null); const [configPort, setConfigPort] = useState(null); const [configs, setConfigs] = useState>({}); + const [clock, setClock] = useState(''); const wsUrl = `ws://${window.location.host}/ws`; const { isConnected, lastMessage } = useWebSocket(wsUrl); const { startRecording, stopRecording, injectSCTE35 } = useRecorder(); - // Update ports from WebSocket messages + // Clock + useEffect(() => { + const tick = () => { + const now = new Date(); + const pad = (n: number) => String(n).padStart(2, '0'); + setClock(`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`); + }; + tick(); + const id = setInterval(tick, 1000); + return () => clearInterval(id); + }, []); + + // WebSocket port updates useEffect(() => { if (lastMessage?.type === 'port_status' && lastMessage.ports) { setPorts(lastMessage.ports); } }, [lastMessage]); - // Initial fetch of port statuses + // Initial fetch useEffect(() => { fetch('/api/ports') .then(r => r.json()) @@ -39,71 +52,194 @@ export default function App() { 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); }; + const handleSelectPort = (portIndex: number) => { + setSelectedPort(prev => (prev === portIndex ? null : portIndex)); + }; + + const selectedPortData = ports.find(p => p.port_index === selectedPort) ?? null; + return ( -
- {/* Header */} -
-
-

Deltacast SDI Recorder

-
-
- {isConnected ? 'Connected' : 'Disconnected'} +
+ + {/* ── HEADER ── */} +
+
+ Dragon Encode +
+ Dragon Encode + SDI Recorder Suite
+ +
+
+
+ {isConnected ? 'LIVE' : 'DISCONNECTED'} +
+
+ + {clock} + +
-
- {/* Port Cards Grid - responsive: 1 col mobile, 2 tablet, 4 desktop */} -
- {ports.map(port => ( - - ))} - {ports.length === 0 && ( -
- No ports available. Check backend connection. +
+ + {/* ── SECTION LABEL ── */} +
+ SDI Inputs +
+ + {ports.length} PORT{ports.length !== 1 ? 'S' : ''} + +
+ + {/* ── PORT CARDS ── */} +
+ {ports.length === 0 ? ( +
+ + + No Ports Available + + + Waiting for backend connection... +
+ ) : ( + ports.map(port => ( + + )) )}
- {/* Selected Port Detail View */} + {/* ── DETAIL PANEL ── */} {selectedPort !== null && ( -
-
-

Port {selectedPort} Preview

- +
+ {/* Preview */} +
+
+ + HLS Preview + + + PORT {selectedPort} + +
+
+ +
-
-

Ad Break Control

- + + {/* SCTE-35 */} +
+
+ + Ad Break Injection + + + SCTE-35 / 104 + +
+
+ +
)} +
- {/* Config Panel Modal */} + {/* ── CONFIG MODAL ── */} {configPort !== null && ( setConfigPort(null)} /> )} + +
); }