feat: multi-destination SRT in ConfigPanel

This commit is contained in:
Zac Gaetano 2026-04-14 10:03:26 -04:00
parent e39241d7b1
commit 7cf45ceee8

View file

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { RecorderConfig, CodecType } from '../types';
import { RecorderConfig, CodecType, SRTDestination } from '../types';
interface ConfigPanelProps {
portIndex: number;
@ -14,7 +14,7 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi
const [qualityProfile, setQualityProfile] = useState('hq');
const [recordingPath, setRecordingPath] = useState(`/recordings/port_${portIndex}_{timestamp}.mxf`);
const [srtEnabled, setSrtEnabled] = useState(false);
const [srtDestination, setSrtDestination] = useState('');
const [srtDestinations, setSrtDestinations] = useState<SRTDestination[]>([]);
const [previewEnabled, setPreviewEnabled] = useState(true);
useEffect(() => {
@ -24,20 +24,41 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi
setQualityProfile(currentConfig.quality_profile);
setRecordingPath(currentConfig.recording_path);
setSrtEnabled(currentConfig.srt_enabled);
setSrtDestination(currentConfig.srt_destination);
setSrtDestinations(currentConfig.srt_destinations ?? []);
setPreviewEnabled(currentConfig.preview_enabled);
}
}, [currentConfig]);
const handleSave = () => {
onSave({
port_index: portIndex, codec, bitrate, quality_profile: qualityProfile,
recording_path: recordingPath, srt_enabled: srtEnabled,
srt_destination: srtDestination, preview_enabled: previewEnabled,
port_index: portIndex,
codec,
bitrate,
quality_profile: qualityProfile,
recording_path: recordingPath,
srt_enabled: srtEnabled,
srt_destination: srtDestinations[0]?.url ?? '',
srt_destinations: srtDestinations,
preview_enabled: previewEnabled,
});
onClose();
};
const addDestination = () => {
setSrtDestinations(prev => [
...prev,
{ url: '', label: `Dest ${prev.length + 1}`, enabled: true },
]);
};
const removeDestination = (idx: number) => {
setSrtDestinations(prev => prev.filter((_, i) => i !== idx));
};
const updateDestination = (idx: number, field: keyof SRTDestination, value: string | boolean) => {
setSrtDestinations(prev => prev.map((d, i) => i === idx ? { ...d, [field]: value } : d));
};
const inputStyle: React.CSSProperties = {
background: 'rgba(0,0,0,0.35)', border: '1px solid var(--border)', borderRadius: 3,
padding: '9px 12px', fontFamily: 'var(--font-mono)', fontSize: 13,
@ -45,12 +66,10 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi
};
const selectStyle: React.CSSProperties = {
...inputStyle,
cursor: 'pointer', appearance: 'none' as const,
...inputStyle, cursor: 'pointer', appearance: 'none' as const,
backgroundImage: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='%237a8fa8' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E\")",
backgroundRepeat: 'no-repeat', backgroundPosition: 'right 12px center',
paddingRight: 32,
background: 'rgba(0,0,0,0.35)',
paddingRight: 32, background: 'rgba(0,0,0,0.35)',
};
const labelStyle: React.CSSProperties = {
@ -80,7 +99,7 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi
style={{
background: 'var(--bg-panel)',
border: '1px solid var(--border-bright)',
borderRadius: 4, width: '100%', maxWidth: 500,
borderRadius: 4, width: '100%', maxWidth: 520,
maxHeight: '90vh', overflowY: 'auto',
boxShadow: '0 24px 80px rgba(0,0,0,0.8)',
animation: 'modalIn 0.2s ease',
@ -157,7 +176,7 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi
{/* Transport section */}
<div>
<div style={sectionTitleStyle}>
Transport
SRT Transport
<div style={{ flex: 1, height: 1, background: 'var(--border)' }} />
</div>
<ToggleRow
@ -166,10 +185,91 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi
checked={srtEnabled}
onChange={setSrtEnabled}
/>
{srtEnabled && (
<div style={{ marginTop: 10 }}>
<label style={labelStyle}>SRT Destination</label>
<input type="text" value={srtDestination} onChange={e => setSrtDestination(e.target.value)} placeholder="srt://host:9000" style={inputStyle} />
<div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 10 }}>
{srtDestinations.map((dest, idx) => (
<div
key={idx}
style={{
background: 'rgba(0,0,0,0.25)',
border: '1px solid var(--border)',
borderRadius: 3,
padding: '12px 14px',
display: 'flex',
flexDirection: 'column',
gap: 8,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
<span style={{ fontFamily: 'var(--font-ui)', fontSize: 10, fontWeight: 700, letterSpacing: '0.25em', textTransform: 'uppercase', color: 'var(--accent-amber)' }}>
Destination {idx + 1}
</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input
type="checkbox"
checked={dest.enabled}
onChange={e => updateDestination(idx, 'enabled', e.target.checked)}
style={{ accentColor: 'var(--dragon-blue)', width: 14, height: 14 }}
/>
<span style={{ fontFamily: 'var(--font-ui)', fontSize: 10, color: 'var(--text-muted)', letterSpacing: '0.1em' }}>Active</span>
</label>
<button
onClick={() => removeDestination(idx)}
style={{
background: 'transparent', border: 'none',
color: 'rgba(255,32,32,0.6)', cursor: 'pointer',
fontSize: 14, lineHeight: 1, padding: '2px 4px',
}}
title="Remove destination"
></button>
</div>
</div>
<div>
<label style={labelStyle}>Label</label>
<input
type="text"
value={dest.label}
onChange={e => updateDestination(idx, 'label', e.target.value)}
placeholder={`Destination ${idx + 1}`}
style={inputStyle}
/>
</div>
<div>
<label style={labelStyle}>SRT URL</label>
<input
type="text"
value={dest.url}
onChange={e => updateDestination(idx, 'url', e.target.value)}
placeholder="srt://host:9000"
style={inputStyle}
/>
</div>
</div>
))}
<button
onClick={addDestination}
style={{
background: 'rgba(26,58,255,0.08)',
border: '1px dashed rgba(26,58,255,0.35)',
color: 'rgba(122,164,255,0.7)',
fontFamily: 'var(--font-ui)', fontSize: 11, fontWeight: 600,
letterSpacing: '0.15em', textTransform: 'uppercase',
padding: '10px 14px', borderRadius: 3, cursor: 'pointer',
width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
}}
>
<span style={{ fontSize: 16, lineHeight: 1 }}>+</span>
Add Destination
</button>
{srtDestinations.length === 0 && (
<p style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-muted)', textAlign: 'center' }}>
No destinations configured click Add Destination
</p>
)}
</div>
)}
</div>