import { Button, Container, FormControl, FormControlLabel, FormGroup, Grid, InputAdornment, InputLabel, MenuItem, Paper, Select, SelectChangeEvent, Snackbar, Stack, Switch, TextField, Typography } from '@mui/material' import { useContext, useEffect, useMemo, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { Subject, debounceTime, distinctUntilChanged, map, takeWhile } from 'rxjs' import { LanguageUnion, ThemeUnion, setCliArgs, setEnableCustomArgs, setFileRenaming, setFormatSelection, setLanguage, setPathOverriding, setServerAddr, setServerPort, setTheme } from '../features/settings/settingsSlice' import { updated } from '../features/status/statusSlice' import { CliArguments } from '../lib/argsParser' import { I18nContext } from '../providers/i18nProvider' import { RPCClientContext } from '../providers/rpcClientProvider' import { RootState } from '../stores/store' import { validateDomain, validateIP } from '../utils' export default function Settings() { const dispatch = useDispatch() const status = useSelector((state: RootState) => state.status) const settings = useSelector((state: RootState) => state.settings) const [invalidIP, setInvalidIP] = useState(false); const { i18n } = useContext(I18nContext) const { client } = useContext(RPCClientContext) const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), []) const serverAddr$ = useMemo(() => new Subject(), []) const serverPort$ = useMemo(() => new Subject(), []) useEffect(() => { const sub = serverAddr$ .pipe( debounceTime(500), distinctUntilChanged() ) .subscribe(addr => { if (validateIP(addr)) { setInvalidIP(false) dispatch(setServerAddr(addr)) } else if (validateDomain(addr)) { setInvalidIP(false) dispatch(setServerAddr(addr)) } else { setInvalidIP(true) } }) return () => sub.unsubscribe() }, [serverAddr$]) useEffect(() => { const sub = serverPort$ .pipe( debounceTime(500), map(val => Number(val)), takeWhile(val => isFinite(val) && val <= 65535), ) .subscribe(port => { dispatch(setServerPort(port.toString())) }) return () => sub.unsubscribe() }, []) /** * Language toggler handler */ const handleLanguageChange = (event: SelectChangeEvent) => { dispatch(setLanguage(event.target.value as LanguageUnion)); } /** * Theme toggler handler */ const handleThemeChange = (event: SelectChangeEvent) => { dispatch(setTheme(event.target.value as ThemeUnion)); } /** * Send via WebSocket a message to update yt-dlp binary */ const updateBinary = () => { client.updateExecutable().then(() => dispatch(updated())) } return ( {i18n.t('settingsAnchor')} serverAddr$.next(e.currentTarget.value)} InputProps={{ startAdornment: ws://, }} sx={{ mb: 2 }} /> serverPort$.next(e.currentTarget.value)} error={isNaN(Number(settings.serverPort)) || Number(settings.serverPort) > 65535} sx={{ mb: 2 }} /> {i18n.t('languageSelect')} {i18n.t('themeSelect')} dispatch(setCliArgs(cliArgs.toggleNoMTime().toString()))} /> } label={i18n.t('noMTimeCheckbox')} sx={{ mt: 3 }} /> dispatch(setCliArgs(cliArgs.toggleExtractAudio().toString()))} disabled={settings.formatSelection} /> } label={i18n.t('extractAudioCheckbox')} /> { dispatch(setCliArgs(cliArgs.disableExtractAudio().toString())) dispatch(setFormatSelection(!settings.formatSelection)) }} /> } label={i18n.t('formatSelectionEnabler')} /> {i18n.t('overridesAnchor')} { dispatch(setPathOverriding(!settings.pathOverriding)) }} /> } label={i18n.t('pathOverrideOption')} /> { dispatch(setFileRenaming(!settings.fileRenaming)) }} /> } label={i18n.t('filenameOverrideOption')} /> { dispatch(setEnableCustomArgs(!settings.enableCustomArgs)) }} /> } label={i18n.t('customArgs')} /> ); }