import React, { useEffect, useMemo } from 'react' import { Box, Paper, Typography, TextField, MenuItem, FormControlLabel, Switch, Collapse, IconButton, Divider, Alert, } from '@mui/material' import { ExpandMore, ExpandLess } from '@mui/icons-material' import { useOptionsStore } from '../stores/optionsStore' import { useUploadStore } from '../stores/uploadStore' import { useValidationStore } from '../stores/validationStore' import { getFormats } from '../api/client' import { SplitOptions } from '../types' import { useFilterCodecs } from '../hooks/useFilterCodecs' import { useFormatValidation } from '../hooks/useFormatValidation' // ------------------------------------------------------------------------------ // Section component (collapsible) // ------------------------------------------------------------------------------ interface SectionProps { title: string children: React.ReactNode defaultExpanded?: boolean } const Section: React.FC = ({ title, children, defaultExpanded = false }) => { const [expanded, setExpanded] = React.useState(defaultExpanded) return ( setExpanded(!expanded)} > {title} {expanded ? : } {children} ) } // ------------------------------------------------------------------------------ // Main OptionsPanel // ------------------------------------------------------------------------------ export const OptionsPanel: React.FC = () => { const { options, setOptions, containers, codecs, compatibility, setContainers, setCodecs, setCompatibility, } = useOptionsStore() const { hasVideo } = useUploadStore() const { formatError, setFormatError } = useValidationStore() // Fetch formats on mount useEffect(() => { const fetchFormats = async () => { try { const data = await getFormats() setContainers(data.containers || []) setCodecs(data.codecs || []) setCompatibility(data.compatibility || {}) } catch (err) { console.error('Failed to fetch formats:', err) } } fetchFormats() }, [setContainers, setCodecs, setCompatibility]) // -------------------------------------------------------------- // Filter codec options based on selected container // -------------------------------------------------------------- const { filteredAudioCodecs, filteredVideoCodecs } = useFilterCodecs(compatibility, options.container, codecs) // -------------------------------------------------------------- // Validate compatibility // -------------------------------------------------------------- useFormatValidation(options, hasVideo, compatibility, containers, setFormatError) // -------------------------------------------------------------- // Handlers // -------------------------------------------------------------- const handleChange = (field: keyof SplitOptions, value: any) => { setOptions({ [field]: value }) } const handleContainerChange = (e: React.ChangeEvent) => { const value = e.target.value === '' ? null : e.target.value handleChange('container', value) } // -------------------------------------------------------------- // Render // -------------------------------------------------------------- return ( ⚙️ Options {formatError && ( {formatError} )} {/* ------------------------------------------------------------------------ Output Settings ------------------------------------------------------------------------ */}
{/* Container dropdown */} Auto-detect {containers.map((c) => ( {c.name.toUpperCase()} ({c.extension}) ))} {/* Audio Codec dropdown */} handleChange('audio_codec', e.target.value)} fullWidth size="small" helperText="Audio codec (copy = keep original)" > Copy (original) {filteredAudioCodecs.map((c) => ( {c.name.toUpperCase()} ))} {/* Video Codec dropdown */} handleChange('video_codec', e.target.value)} fullWidth size="small" helperText="Video codec (copy = keep original)" disabled={!hasVideo || options.drop_video} > Copy (original) {filteredVideoCodecs.map((c) => ( {c.name.toUpperCase()} ))} {/* Video Quality */} { const val = e.target.value === '' ? null : parseInt(e.target.value, 10) handleChange('video_quality', val) }} fullWidth size="small" disabled={!hasVideo || options.drop_video} helperText="Optional quality value (encoder-specific; e.g., 1-31 for libx264, 0-10 for Theora)" InputProps={{ inputProps: { min: 0, max: 51, step: 1 }, }} /> {/* Drop video (if video present) */} {hasVideo && ( handleChange('drop_video', e.target.checked)} /> } label="Drop video streams" /> )} {/* Drop subtitles */} handleChange('drop_subs', e.target.checked)} /> } label="Drop subtitle streams" />
{/* ------------------------------------------------------------------------ Filename Settings ------------------------------------------------------------------------ */}
handleChange('output_template', e.target.value)} fullWidth size="small" helperText="Placeholders: %tn (track name), %an (author), %al (album), %date, %ext, %num" /> handleChange('number_tracks', e.target.checked)} /> } label="Number tracks (01 - )" /> handleChange('replace_bad_chars', e.target.checked)} /> } label="Replace bad characters" /> handleChange('replacement_char', e.target.value)} size="small" disabled={!options.replace_bad_chars} /> handleChange('bad_chars', e.target.value)} fullWidth size="small" disabled={!options.replace_bad_chars} /> handleChange('skip_existing', e.target.checked)} /> } label="Skip existing files" />
{/* ------------------------------------------------------------------------ Metadata Settings ------------------------------------------------------------------------ */}
handleChange('album', e.target.value)} fullWidth size="small" /> handleChange('comment', e.target.value)} fullWidth size="small" /> handleChange('no_comment', e.target.checked)} /> } label="No comment" /> handleChange('comment_stream', e.target.value === '' ? null : parseInt(e.target.value, 10)) } size="small" disabled={options.no_comment} /> handleChange('merge_comments', e.target.checked)} /> } label="Merge all comments" disabled={options.no_comment} /> handleChange('comment_separator', e.target.value)} size="small" disabled={!options.merge_comments || options.no_comment} />
{/* ------------------------------------------------------------------------ Tracklist Settings ------------------------------------------------------------------------ */}
handleChange('tracklist_format', e.target.value)} fullWidth size="small" helperText="Placeholders: %ts (timestamp), %tn (track name), %an (author), %al (album), %date, %ext" />
) }