Add frontend/src/App.tsx
This commit is contained in:
parent
6f45131b57
commit
52df4d0e11
1 changed files with 122 additions and 0 deletions
122
frontend/src/App.tsx
Normal file
122
frontend/src/App.tsx
Normal file
|
|
@ -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<PortStatus[]>([]);
|
||||
const [selectedPort, setSelectedPort] = useState<number | null>(null);
|
||||
const [configPort, setConfigPort] = useState<number | null>(null);
|
||||
const [configs, setConfigs] = useState<Record<number, RecorderConfig>>({});
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen bg-gray-900 text-white">
|
||||
{/* Header */}
|
||||
<header className="bg-gray-800 border-b border-gray-700 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold text-blue-400">Deltacast SDI Recorder</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-400' : 'bg-red-400'}`} />
|
||||
<span className="text-sm text-gray-400">{isConnected ? 'Connected' : 'Disconnected'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="p-6">
|
||||
{/* Port Cards Grid - responsive: 1 col mobile, 2 tablet, 4 desktop */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
{ports.map(port => (
|
||||
<PortCard
|
||||
key={port.port_index}
|
||||
port={port}
|
||||
isSelected={selectedPort === port.port_index}
|
||||
onSelect={setSelectedPort}
|
||||
onStartRecording={handleStartRecording}
|
||||
onStopRecording={stopRecording}
|
||||
onOpenConfig={setConfigPort}
|
||||
/>
|
||||
))}
|
||||
{ports.length === 0 && (
|
||||
<div className="col-span-full text-center text-gray-500 py-12">
|
||||
No ports available. Check backend connection.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Selected Port Detail View */}
|
||||
{selectedPort !== null && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-3">Port {selectedPort} Preview</h2>
|
||||
<VideoPreview portIndex={selectedPort} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold mb-3">Ad Break Control</h2>
|
||||
<SCTE35Trigger onInject={handleInjectSCTE35} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* Config Panel Modal */}
|
||||
{configPort !== null && (
|
||||
<ConfigPanel
|
||||
portIndex={configPort}
|
||||
currentConfig={configs[configPort] || null}
|
||||
onSave={handleSaveConfig}
|
||||
onClose={() => setConfigPort(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue