Simple frontend added, tests are required
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Collapse,
|
||||
IconButton,
|
||||
Divider,
|
||||
} from '@mui/material'
|
||||
import { ExpandMore, ExpandLess } from '@mui/icons-material'
|
||||
import { useOptionsStore } from '../stores/optionsStore'
|
||||
|
||||
interface SectionProps {
|
||||
title: string
|
||||
children: React.ReactNode
|
||||
defaultExpanded?: boolean
|
||||
}
|
||||
|
||||
const Section: React.FC<SectionProps> = ({ title, children, defaultExpanded = false }) => {
|
||||
const [expanded, setExpanded] = React.useState(defaultExpanded)
|
||||
|
||||
return (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
cursor: 'pointer',
|
||||
py: 1,
|
||||
}}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<IconButton size="small">{expanded ? <ExpandLess /> : <ExpandMore />}</IconButton>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Collapse in={expanded}>
|
||||
<Box sx={{ pt: 2, pb: 1 }}>{children}</Box>
|
||||
</Collapse>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const OptionsPanel: React.FC = () => {
|
||||
const { options, setOptions } = useOptionsStore()
|
||||
|
||||
const handleChange = (field: string, value: any) => {
|
||||
setOptions({ [field]: value })
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
⚙️ Options
|
||||
</Typography>
|
||||
|
||||
{/* Output Section */}
|
||||
<Section title="Output Settings" defaultExpanded>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField
|
||||
label="Format"
|
||||
select
|
||||
value={options.format}
|
||||
onChange={(e) => handleChange('format', e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
>
|
||||
<MenuItem value="mp3">MP3</MenuItem>
|
||||
<MenuItem value="m4a">M4A</MenuItem>
|
||||
<MenuItem value="mkv">MKV</MenuItem>
|
||||
<MenuItem value="mp4">MP4</MenuItem>
|
||||
<MenuItem value="ogg">OGG</MenuItem>
|
||||
<MenuItem value="opus">OPUS</MenuItem>
|
||||
<MenuItem value="flac">FLAC</MenuItem>
|
||||
<MenuItem value="wav">WAV</MenuItem>
|
||||
<MenuItem value="aac">AAC</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
label="Transcode to"
|
||||
select
|
||||
value={options.transcode_to || ''}
|
||||
onChange={(e) => handleChange('transcode_to', e.target.value || undefined)}
|
||||
fullWidth
|
||||
size="small"
|
||||
>
|
||||
<MenuItem value="">Copy (no transcoding)</MenuItem>
|
||||
<MenuItem value="libmp3lame">MP3 (LAME)</MenuItem>
|
||||
<MenuItem value="aac">AAC</MenuItem>
|
||||
<MenuItem value="libopus">OPUS</MenuItem>
|
||||
</TextField>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.drop_video}
|
||||
onChange={(e) => handleChange('drop_video', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Drop video streams"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.drop_subs}
|
||||
onChange={(e) => handleChange('drop_subs', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Drop subtitle streams"
|
||||
/>
|
||||
</Box>
|
||||
</Section>
|
||||
|
||||
{/* Filename Section */}
|
||||
<Section title="Filename Settings">
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField
|
||||
label="Output template"
|
||||
value={options.output_template}
|
||||
onChange={(e) => handleChange('output_template', e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
helperText="Placeholders: %tn (track name), %an (author), %al (album), %date, %ext, %num"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.number_tracks}
|
||||
onChange={(e) => handleChange('number_tracks', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Number tracks (01 - )"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.replace_bad_chars}
|
||||
onChange={(e) => handleChange('replace_bad_chars', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Replace bad characters"
|
||||
/>
|
||||
<TextField
|
||||
label="Replacement character"
|
||||
value={options.replacement_char}
|
||||
onChange={(e) => handleChange('replacement_char', e.target.value)}
|
||||
size="small"
|
||||
disabled={!options.replace_bad_chars}
|
||||
/>
|
||||
<TextField
|
||||
label="Bad characters list"
|
||||
value={options.bad_chars}
|
||||
onChange={(e) => handleChange('bad_chars', e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
disabled={!options.replace_bad_chars}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.skip_existing}
|
||||
onChange={(e) => handleChange('skip_existing', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Skip existing files"
|
||||
/>
|
||||
</Box>
|
||||
</Section>
|
||||
|
||||
{/* Metadata Section */}
|
||||
<Section title="Metadata Settings">
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<TextField
|
||||
label="Album"
|
||||
value={options.album}
|
||||
onChange={(e) => handleChange('album', e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
/>
|
||||
<TextField
|
||||
label="Comment"
|
||||
value={options.comment}
|
||||
onChange={(e) => handleChange('comment', e.target.value)}
|
||||
fullWidth
|
||||
size="small"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.no_comment}
|
||||
onChange={(e) => handleChange('no_comment', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="No comment"
|
||||
/>
|
||||
<TextField
|
||||
label="Comment stream index"
|
||||
type="number"
|
||||
value={options.comment_stream ?? ''}
|
||||
onChange={(e) =>
|
||||
handleChange('comment_stream', e.target.value === '' ? null : parseInt(e.target.value))
|
||||
}
|
||||
size="small"
|
||||
disabled={options.no_comment}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={options.merge_comments}
|
||||
onChange={(e) => handleChange('merge_comments', e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Merge all comments"
|
||||
disabled={options.no_comment}
|
||||
/>
|
||||
<TextField
|
||||
label="Comment separator"
|
||||
value={options.comment_separator}
|
||||
onChange={(e) => handleChange('comment_separator', e.target.value)}
|
||||
size="small"
|
||||
disabled={!options.merge_comments || options.no_comment}
|
||||
/>
|
||||
</Box>
|
||||
</Section>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user