From 275c5ad2fafc4e2044f82195ae95530ee348088f Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Tue, 14 Apr 2026 09:45:59 -0400 Subject: [PATCH] feat: redesign ConfigPanel with dark modal and custom toggles --- frontend/src/components/ConfigPanel.tsx | 332 +++++++++++++++--------- 1 file changed, 213 insertions(+), 119 deletions(-) diff --git a/frontend/src/components/ConfigPanel.tsx b/frontend/src/components/ConfigPanel.tsx index 843e30e..6d805fb 100644 --- a/frontend/src/components/ConfigPanel.tsx +++ b/frontend/src/components/ConfigPanel.tsx @@ -9,16 +9,14 @@ interface ConfigPanelProps { } export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: ConfigPanelProps) { - // State for each config field const [codec, setCodec] = useState('PRORES'); const [bitrate, setBitrate] = useState('185M'); const [qualityProfile, setQualityProfile] = useState('hq'); - const [recordingPath, setRecordingPath] = useState('/recordings/port_{port_index}_{timestamp}.mxf'); + const [recordingPath, setRecordingPath] = useState(`/recordings/port_${portIndex}_{timestamp}.mxf`); const [srtEnabled, setSrtEnabled] = useState(false); const [srtDestination, setSrtDestination] = useState(''); const [previewEnabled, setPreviewEnabled] = useState(true); - // Initialize from currentConfig if provided useEffect(() => { if (currentConfig) { setCodec(currentConfig.codec); @@ -32,126 +30,222 @@ export function ConfigPanel({ portIndex, currentConfig, onSave, onClose }: Confi }, [currentConfig]); const handleSave = () => { - const config: RecorderConfig = { - port_index: portIndex, - codec, - bitrate, - quality_profile: qualityProfile, - recording_path: recordingPath, - srt_enabled: srtEnabled, - srt_destination: srtDestination, - preview_enabled: previewEnabled, - }; - onSave(config); + onSave({ + port_index: portIndex, codec, bitrate, quality_profile: qualityProfile, + recording_path: recordingPath, srt_enabled: srtEnabled, + srt_destination: srtDestination, preview_enabled: previewEnabled, + }); onClose(); }; + 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, + color: 'var(--text-primary)', outline: 'none', width: '100%', + }; + + const selectStyle: React.CSSProperties = { + ...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)', + }; + + const labelStyle: React.CSSProperties = { + fontFamily: 'var(--font-ui)', fontSize: 10, fontWeight: 700, + letterSpacing: '0.3em', textTransform: 'uppercase', color: 'var(--text-muted)', + display: 'block', marginBottom: 6, + }; + + const sectionTitleStyle: React.CSSProperties = { + fontFamily: 'var(--font-ui)', fontSize: 9, fontWeight: 700, + letterSpacing: '0.35em', textTransform: 'uppercase', color: 'var(--text-muted)', + display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12, + }; + return ( -
-
-

Configure Port {portIndex}

- - {/* Codec Selection */} -
- - -
- - {/* Quality Profile - only show for ProRes */} - {codec === 'PRORES' && ( -
- - +
+
e.stopPropagation()} + style={{ + background: 'var(--bg-panel)', + border: '1px solid var(--border-bright)', + borderRadius: 4, width: '100%', maxWidth: 500, + maxHeight: '90vh', overflowY: 'auto', + boxShadow: '0 24px 80px rgba(0,0,0,0.8)', + animation: 'modalIn 0.2s ease', + }} + > + {/* Header */} +
+
+ + Port Config + + + PORT {portIndex} +
- )} - - {/* Bitrate - only show for DNxHD or H264 */} - {(codec === 'DNXHD' || codec === 'H264') && ( -
- - ) => setBitrate(e.target.value)} - placeholder="e.g., 185M, 50M" - className="w-full bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 mb-3" - /> -
- )} - - {/* Recording Path */} -
- - ) => setRecordingPath(e.target.value)} - className="w-full bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 mb-1" - /> -

Use {'{timestamp}'} and {'{port_index}'} as placeholders

-
- - {/* SRT Enabled Checkbox */} -
- -
- - {/* SRT Destination - only show when SRT enabled */} - {srtEnabled && ( -
- - ) => setSrtDestination(e.target.value)} - placeholder="srt://destination.example.com:9000" - className="w-full bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 mb-3" - /> -
- )} - - {/* Preview Enabled Checkbox */} -
- -
- - {/* Action Buttons */} -
+
+ + {/* Body */} +
+ + {/* Codec section */} +
+
+ Codec +
+
+
+ + +
+ {codec === 'PRORES' && ( +
+ + +
+ )} + {(codec === 'DNXHD' || codec === 'H264') && ( +
+ + setBitrate(e.target.value)} placeholder="185M, 50M..." style={inputStyle} /> +
+ )} +
+ + {/* Output section */} +
+
+ Output +
+
+ + setRecordingPath(e.target.value)} style={inputStyle} /> +

+ Use {timestamp} and {port_index} as tokens +

+
+ + {/* Transport section */} +
+
+ Transport +
+
+ + {srtEnabled && ( +
+ + setSrtDestination(e.target.value)} placeholder="srt://host:9000" style={inputStyle} /> +
+ )} +
+ + {/* Preview section */} +
+
+ Preview +
+
+ +
+ +
+ + {/* Footer */} +
+ + +
+ +
+ + +
+ ); +} + +function ToggleRow({ label, sub, checked, onChange }: { + label: string; sub: string; checked: boolean; onChange: (v: boolean) => void; +}) { + const id = `toggle-${label.replace(/\s+/g, '-').toLowerCase()}`; + return ( +
+
+ {label} + {sub} +
+ +
+ ); +}