mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 13:36:09 +05:00
refactor
This commit is contained in:
@@ -28,7 +28,7 @@ const Sidebar = ({ isDrawerOpen, setIsDonationDialogOpen, isOffline, isLoading }
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
<SettingsDialog />
|
<SettingsDialog isOffline={isOffline} isLoading={isLoading} />
|
||||||
|
|
||||||
<AboutDialog />
|
<AboutDialog />
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts'
|
import { settingsHost } from 'utils/Hosts'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Header } from 'style/DialogStyles'
|
import { Header } from 'style/DialogStyles'
|
||||||
@@ -26,94 +26,26 @@ import AppBar from '@material-ui/core/AppBar'
|
|||||||
import Tabs from '@material-ui/core/Tabs'
|
import Tabs from '@material-ui/core/Tabs'
|
||||||
import Tab from '@material-ui/core/Tab'
|
import Tab from '@material-ui/core/Tab'
|
||||||
import SwipeableViews from 'react-swipeable-views'
|
import SwipeableViews from 'react-swipeable-views'
|
||||||
import styled, { css } from 'styled-components'
|
|
||||||
import { USBIcon, RAMIcon } from 'icons'
|
import { USBIcon, RAMIcon } from 'icons'
|
||||||
|
|
||||||
const FooterSection = styled.div`
|
import {
|
||||||
padding: 20px;
|
FooterSection,
|
||||||
display: flex;
|
Divider,
|
||||||
align-items: center;
|
PreloadCacheValue,
|
||||||
justify-content: space-between;
|
MainSettingsContent,
|
||||||
background: #e8e5eb;
|
SecondarySettingsContent,
|
||||||
|
StorageButton,
|
||||||
> :last-child > :not(:last-child) {
|
StorageIconWrapper,
|
||||||
margin-right: 10px;
|
CacheSizeSettings,
|
||||||
}
|
CacheStorageSelector,
|
||||||
`
|
CacheStorageSettings,
|
||||||
const Divider = styled.div`
|
SettingSection,
|
||||||
height: 1px;
|
SettingLabel,
|
||||||
background-color: rgba(0, 0, 0, 0.12);
|
SettingSectionLabel,
|
||||||
margin: 30px 0;
|
PreloadCachePercentage,
|
||||||
`
|
cacheBeforeReaderColor,
|
||||||
|
cacheAfterReaderColor,
|
||||||
const PreloadCachePercentage = styled.div.attrs(
|
} from './style'
|
||||||
({
|
|
||||||
value,
|
|
||||||
// theme: {
|
|
||||||
// dialogTorrentDetailsContent: { gradientEndColor },
|
|
||||||
// },
|
|
||||||
}) => {
|
|
||||||
const gradientStartColor = 'lightblue'
|
|
||||||
const gradientEndColor = 'orangered'
|
|
||||||
|
|
||||||
return {
|
|
||||||
// this block is here according to styled-components recomendation about fast changable components
|
|
||||||
style: {
|
|
||||||
background: `linear-gradient(to right, ${gradientEndColor} 0%, ${gradientEndColor} ${value}%, ${gradientStartColor} ${value}%, ${gradientStartColor} 100%)`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)`
|
|
||||||
${({ label, isPreloadEnabled }) => css`
|
|
||||||
border: 1px solid;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: #000;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
:before {
|
|
||||||
content: '${label}';
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
${isPreloadEnabled &&
|
|
||||||
css`
|
|
||||||
:after {
|
|
||||||
content: '';
|
|
||||||
width: 100%;
|
|
||||||
height: 3px;
|
|
||||||
background: green;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
const PreloadCacheValue = styled.div`
|
|
||||||
${({ color }) => css`
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: max-content 100px 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
:not(:last-child) {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:before {
|
|
||||||
content: '';
|
|
||||||
background: ${color};
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
const a11yProps = index => ({
|
const a11yProps = index => ({
|
||||||
id: `full-width-tab-${index}`,
|
id: `full-width-tab-${index}`,
|
||||||
@@ -126,103 +58,57 @@ const TabPanel = ({ children, value, index, ...other }) => (
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
const MainSettingsContent = styled.div`
|
const defaultSettings = {
|
||||||
min-height: 500px;
|
CacheSize: 96,
|
||||||
display: grid;
|
ReaderReadAHead: 95,
|
||||||
grid-template-columns: repeat(2, 1fr);
|
UseDisk: false,
|
||||||
gap: 40px;
|
UploadRateLimit: 0,
|
||||||
padding: 20px;
|
TorrentsSavePath: '',
|
||||||
|
ConnectionsLimit: 23,
|
||||||
@media (max-width: 930px) {
|
DhtConnectionLimit: 500,
|
||||||
grid-template-columns: 1fr;
|
DisableDHT: false,
|
||||||
}
|
DisablePEX: false,
|
||||||
`
|
DisableTCP: false,
|
||||||
const SecondarySettingsContent = styled.div`
|
DisableUPNP: false,
|
||||||
min-height: 500px;
|
DisableUTP: true,
|
||||||
padding: 20px;
|
DisableUpload: false,
|
||||||
`
|
DownloadRateLimit: 0,
|
||||||
|
EnableDebug: false,
|
||||||
const StorageButton = styled.div`
|
EnableIPv6: false,
|
||||||
display: grid;
|
ForceEncrypt: false,
|
||||||
place-items: center;
|
PeersListenPort: 0,
|
||||||
gap: 10px;
|
PreloadBuffer: false,
|
||||||
`
|
RemoveCacheOnDrop: false,
|
||||||
|
RetrackersMode: 1,
|
||||||
const StorageIconWrapper = styled.div`
|
Strategy: 0,
|
||||||
${({ selected }) => css`
|
TorrentDisconnectTimeout: 30,
|
||||||
width: 150px;
|
}
|
||||||
height: 150px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: ${selected ? 'blue' : 'lightgray'};
|
|
||||||
transition: 0.2s;
|
|
||||||
|
|
||||||
${!selected &&
|
|
||||||
css`
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
background: orangered;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
transform: rotate(-45deg) scale(0.75);
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
const CacheSizeSettings = styled.div``
|
|
||||||
const CacheStorageSelector = styled.div`
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: max-content 1fr;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-areas: 'label label';
|
|
||||||
place-items: center;
|
|
||||||
|
|
||||||
@media (max-width: 930px) {
|
|
||||||
grid-template-columns: repeat(2, max-content);
|
|
||||||
column-gap: 30px;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const CacheStorageSettings = styled.div``
|
|
||||||
|
|
||||||
const SettingSection = styled.section``
|
|
||||||
const SettingLabel = styled.div``
|
|
||||||
const SettingSectionLabel = styled.div`
|
|
||||||
font-size: 25px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default function SettingsDialog({ handleClose }) {
|
export default function SettingsDialog({ handleClose }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const fullScreen = useMediaQuery('@media (max-width:930px)')
|
const fullScreen = useMediaQuery('@media (max-width:930px)')
|
||||||
|
|
||||||
const [settings, setSets] = useState({})
|
const [settings, setSettings] = useState()
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const [tsHost, setTSHost] = useState(getTorrServerHost())
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
axios
|
||||||
.post(settingsHost(), { action: 'get' })
|
.post(settingsHost(), { action: 'get' })
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
setSets({ ...data, CacheSize: data.CacheSize / (1024 * 1024) })
|
setSettings({ ...data, CacheSize: data.CacheSize / (1024 * 1024) })
|
||||||
setShow(true)
|
setShow(true)
|
||||||
})
|
})
|
||||||
.catch(() => setShow(false))
|
.catch(() => setShow(false))
|
||||||
}, [tsHost])
|
}, [])
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
handleClose()
|
handleClose()
|
||||||
const sets = JSON.parse(JSON.stringify(settings))
|
const sets = JSON.parse(JSON.stringify(settings))
|
||||||
sets.CacheSize *= 1024 * 1024
|
sets.CacheSize = cacheSize * 1024 * 1024
|
||||||
|
sets.ReaderReadAHead = cachePercentage
|
||||||
axios.post(settingsHost(), { action: 'set', sets })
|
axios.post(settingsHost(), { action: 'set', sets })
|
||||||
}
|
}
|
||||||
const onInputHost = ({ target: { value } }) => {
|
|
||||||
const host = value.replace(/\/$/gi, '')
|
|
||||||
setTorrServerHost(host)
|
|
||||||
setTSHost(host)
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputForm = ({ target: { type, value, checked, id } }) => {
|
const inputForm = ({ target: { type, value, checked, id } }) => {
|
||||||
const sets = JSON.parse(JSON.stringify(settings))
|
const sets = JSON.parse(JSON.stringify(settings))
|
||||||
@@ -242,7 +128,7 @@ export default function SettingsDialog({ handleClose }) {
|
|||||||
} else if (type === 'url') {
|
} else if (type === 'url') {
|
||||||
sets[id] = value
|
sets[id] = value
|
||||||
}
|
}
|
||||||
setSets(sets)
|
setSettings(sets)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -267,7 +153,9 @@ export default function SettingsDialog({ handleClose }) {
|
|||||||
UseDisk,
|
UseDisk,
|
||||||
TorrentsSavePath,
|
TorrentsSavePath,
|
||||||
RemoveCacheOnDrop,
|
RemoveCacheOnDrop,
|
||||||
} = settings
|
} = settings || {}
|
||||||
|
|
||||||
|
const updateSettings = newProps => setSettings({ ...settings, ...newProps })
|
||||||
|
|
||||||
const { direction } = useTheme()
|
const { direction } = useTheme()
|
||||||
const [selectedTab, setSelectedTab] = useState(0)
|
const [selectedTab, setSelectedTab] = useState(0)
|
||||||
@@ -276,14 +164,16 @@ export default function SettingsDialog({ handleClose }) {
|
|||||||
|
|
||||||
const handleChangeIndex = index => setSelectedTab(index)
|
const handleChangeIndex = index => setSelectedTab(index)
|
||||||
|
|
||||||
const [cacheSize, setCacheSize] = useState(96)
|
const [cacheSize, setCacheSize] = useState(32)
|
||||||
const [cachePercentage, setCachePercentage] = useState(95)
|
const [cachePercentage, setCachePercentage] = useState(40)
|
||||||
const [isProMode, setIsProMode] = useState(JSON.parse(localStorage.getItem('isProMode')) || false)
|
const [isProMode, setIsProMode] = useState(JSON.parse(localStorage.getItem('isProMode')) || false)
|
||||||
const [isRamSelected, setIsRamSelected] = useState(true)
|
|
||||||
|
|
||||||
const handleSliderChange = (_, newValue) => {
|
useEffect(() => {
|
||||||
setCacheSize(newValue)
|
if (!CacheSize || !ReaderReadAHead) return
|
||||||
}
|
|
||||||
|
setCacheSize(CacheSize)
|
||||||
|
setCachePercentage(ReaderReadAHead)
|
||||||
|
}, [CacheSize, ReaderReadAHead])
|
||||||
|
|
||||||
const handleBlur = ({ target: { value } }) => {
|
const handleBlur = ({ target: { value } }) => {
|
||||||
if (value < 32) return setCacheSize(32)
|
if (value < 32) return setCacheSize(32)
|
||||||
@@ -300,305 +190,316 @@ export default function SettingsDialog({ handleClose }) {
|
|||||||
<Dialog open onClose={handleClose} fullScreen={fullScreen} fullWidth maxWidth='md'>
|
<Dialog open onClose={handleClose} fullScreen={fullScreen} fullWidth maxWidth='md'>
|
||||||
<Header>{t('Settings')}</Header>
|
<Header>{t('Settings')}</Header>
|
||||||
|
|
||||||
<>
|
{settings ? (
|
||||||
<AppBar position='static' color='default'>
|
<>
|
||||||
<Tabs
|
<AppBar position='static' color='default'>
|
||||||
value={selectedTab}
|
<Tabs
|
||||||
onChange={handleChange}
|
value={selectedTab}
|
||||||
indicatorColor='primary'
|
onChange={handleChange}
|
||||||
textColor='primary'
|
indicatorColor='primary'
|
||||||
variant='fullWidth'
|
textColor='primary'
|
||||||
|
variant='fullWidth'
|
||||||
|
>
|
||||||
|
<Tab label='Основные' {...a11yProps(0)} />
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
disabled={!isProMode}
|
||||||
|
label={isProMode ? 'Дополнительные' : 'Дополнительные (включите pro mode)'}
|
||||||
|
{...a11yProps(1)}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</AppBar>
|
||||||
|
|
||||||
|
<SwipeableViews
|
||||||
|
axis={direction === 'rtl' ? 'x-reverse' : 'x'}
|
||||||
|
index={selectedTab}
|
||||||
|
onChangeIndex={handleChangeIndex}
|
||||||
>
|
>
|
||||||
<Tab label='Основные' {...a11yProps(0)} />
|
<TabPanel value={selectedTab} index={0} dir={direction}>
|
||||||
|
<MainSettingsContent>
|
||||||
|
<CacheSizeSettings>
|
||||||
|
<SettingSectionLabel>Настройки кеша</SettingSectionLabel>
|
||||||
|
|
||||||
<Tab
|
<PreloadCachePercentage
|
||||||
disabled={!isProMode}
|
value={100 - cachePercentage}
|
||||||
label={isProMode ? 'Дополнительные' : 'Дополнительные (включите pro mode)'}
|
label={`Кеш ${cacheSize} МБ`}
|
||||||
{...a11yProps(1)}
|
isPreloadEnabled={PreloadBuffer}
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
</AppBar>
|
|
||||||
|
|
||||||
<SwipeableViews
|
|
||||||
axis={direction === 'rtl' ? 'x-reverse' : 'x'}
|
|
||||||
index={selectedTab}
|
|
||||||
onChangeIndex={handleChangeIndex}
|
|
||||||
>
|
|
||||||
<TabPanel value={selectedTab} index={0} dir={direction}>
|
|
||||||
<MainSettingsContent>
|
|
||||||
<CacheSizeSettings>
|
|
||||||
<SettingSectionLabel>Настройки кеша</SettingSectionLabel>
|
|
||||||
|
|
||||||
<PreloadCachePercentage
|
|
||||||
value={100 - cachePercentage}
|
|
||||||
label={`Кеш ${cacheSize} МБ`}
|
|
||||||
isPreloadEnabled={PreloadBuffer}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PreloadCacheValue color='orangered'>
|
|
||||||
<div>
|
|
||||||
{100 - cachePercentage}% ({Math.round((cacheSize / 100) * (100 - cachePercentage))} МБ)
|
|
||||||
</div>
|
|
||||||
<div>От кеша будет оставаться позади воспроизводимого блока</div>
|
|
||||||
</PreloadCacheValue>
|
|
||||||
|
|
||||||
<PreloadCacheValue color='lightblue'>
|
|
||||||
<div>
|
|
||||||
{cachePercentage}% ({Math.round((cacheSize / 100) * cachePercentage)} МБ)
|
|
||||||
</div>
|
|
||||||
<div>От кеша будет спереди от воспроизводимого блока</div>
|
|
||||||
</PreloadCacheValue>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<SettingSection>
|
|
||||||
<SettingLabel>Размер кеша</SettingLabel>
|
|
||||||
|
|
||||||
<Grid container spacing={2} alignItems='center'>
|
|
||||||
<Grid item xs>
|
|
||||||
<Slider min={32} max={1024} value={cacheSize} onChange={handleSliderChange} step={8} />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{isProMode && (
|
|
||||||
<Grid item>
|
|
||||||
<Input
|
|
||||||
value={cacheSize}
|
|
||||||
margin='dense'
|
|
||||||
onChange={handleInputChange}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
style={{ width: '65px' }}
|
|
||||||
inputProps={{
|
|
||||||
step: 8,
|
|
||||||
min: 32,
|
|
||||||
max: 20000,
|
|
||||||
type: 'number',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</SettingSection>
|
|
||||||
|
|
||||||
<SettingSection>
|
|
||||||
<SettingLabel>Кеш предзагрузки</SettingLabel>
|
|
||||||
|
|
||||||
<Grid container spacing={2} alignItems='center'>
|
|
||||||
<Grid item xs>
|
|
||||||
<Slider
|
|
||||||
min={40}
|
|
||||||
max={95}
|
|
||||||
value={cachePercentage}
|
|
||||||
onChange={(_, newValue) => setCachePercentage(newValue)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{isProMode && (
|
|
||||||
<Grid item>
|
|
||||||
<Input
|
|
||||||
value={cachePercentage}
|
|
||||||
margin='dense'
|
|
||||||
onChange={({ target: { value } }) => setCachePercentage(value === '' ? '' : Number(value))}
|
|
||||||
onBlur={({ target: { value } }) => {
|
|
||||||
if (value < 0) return setCachePercentage(0)
|
|
||||||
if (value > 100) return setCachePercentage(100)
|
|
||||||
}}
|
|
||||||
style={{ width: '65px' }}
|
|
||||||
inputProps={{
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
type: 'number',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</SettingSection>
|
|
||||||
|
|
||||||
<SettingSection>
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />}
|
|
||||||
label={t('PreloadBuffer')}
|
|
||||||
/>
|
/>
|
||||||
</SettingSection>
|
|
||||||
</CacheSizeSettings>
|
|
||||||
|
|
||||||
{isRamSelected ? (
|
<PreloadCacheValue color={cacheBeforeReaderColor}>
|
||||||
<CacheStorageSelector>
|
<div>
|
||||||
<SettingSectionLabel style={{ placeSelf: 'start', gridArea: 'label' }}>
|
{100 - cachePercentage}% ({Math.round((cacheSize / 100) * (100 - cachePercentage))} МБ)
|
||||||
Место хранения кеша
|
</div>
|
||||||
</SettingSectionLabel>
|
<div>От кеша будет оставаться позади воспроизводимого блока</div>
|
||||||
|
</PreloadCacheValue>
|
||||||
|
|
||||||
<StorageButton>
|
<PreloadCacheValue color={cacheAfterReaderColor}>
|
||||||
<StorageIconWrapper selected>
|
<div>
|
||||||
<RAMIcon />
|
{cachePercentage}% ({Math.round((cacheSize / 100) * cachePercentage)} МБ)
|
||||||
</StorageIconWrapper>
|
</div>
|
||||||
<div>Оперативная память</div>
|
<div>От кеша будет спереди от воспроизводимого блока</div>
|
||||||
</StorageButton>
|
</PreloadCacheValue>
|
||||||
|
|
||||||
<StorageButton>
|
<Divider />
|
||||||
<StorageIconWrapper onClick={() => setIsRamSelected(false)}>
|
|
||||||
<USBIcon />
|
|
||||||
</StorageIconWrapper>
|
|
||||||
<div>Диск</div>
|
|
||||||
</StorageButton>
|
|
||||||
</CacheStorageSelector>
|
|
||||||
) : (
|
|
||||||
<CacheStorageSettings>
|
|
||||||
<SettingSectionLabel>Место хранения кеша</SettingSectionLabel>
|
|
||||||
|
|
||||||
<ButtonGroup fullWidth color='primary'>
|
<SettingSection>
|
||||||
<Button onClick={() => setIsRamSelected(true)}>
|
<SettingLabel>Размер кеша</SettingLabel>
|
||||||
<div>
|
|
||||||
<RAMIcon width='50px' />
|
|
||||||
<div>Оперативная память</div>
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button variant='contained'>
|
<Grid container spacing={2} alignItems='center'>
|
||||||
<div>
|
<Grid item xs>
|
||||||
<USBIcon width='50px' color='white' />
|
<Slider
|
||||||
<div>Диск</div>
|
min={32}
|
||||||
</div>
|
max={1024}
|
||||||
</Button>
|
value={cacheSize}
|
||||||
</ButtonGroup>
|
onChange={(_, newValue) => setCacheSize(newValue)}
|
||||||
|
step={8}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{isProMode && (
|
||||||
|
<Grid item>
|
||||||
|
<Input
|
||||||
|
value={cacheSize}
|
||||||
|
margin='dense'
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
style={{ width: '65px' }}
|
||||||
|
inputProps={{ step: 8, min: 32, max: 20000, type: 'number' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
|
<SettingSection>
|
||||||
|
<SettingLabel>Кеш предзагрузки</SettingLabel>
|
||||||
|
|
||||||
|
<Grid container spacing={2} alignItems='center'>
|
||||||
|
<Grid item xs>
|
||||||
|
<Slider
|
||||||
|
min={40}
|
||||||
|
max={95}
|
||||||
|
value={cachePercentage}
|
||||||
|
onChange={(_, newValue) => setCachePercentage(newValue)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{isProMode && (
|
||||||
|
<Grid item>
|
||||||
|
<Input
|
||||||
|
value={cachePercentage}
|
||||||
|
margin='dense'
|
||||||
|
onChange={({ target: { value } }) => setCachePercentage(value === '' ? '' : Number(value))}
|
||||||
|
onBlur={({ target: { value } }) => {
|
||||||
|
if (value < 0) return setCachePercentage(0)
|
||||||
|
if (value > 100) return setCachePercentage(100)
|
||||||
|
}}
|
||||||
|
style={{ width: '65px' }}
|
||||||
|
inputProps={{ min: 0, max: 100, type: 'number' }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</SettingSection>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Switch checked={RemoveCacheOnDrop} onChange={inputForm} id='RemoveCacheOnDrop' color='primary' />
|
<Switch checked={!!PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />
|
||||||
}
|
}
|
||||||
label={t('RemoveCacheOnDrop')}
|
label={t('PreloadBuffer')}
|
||||||
/>
|
/>
|
||||||
<small>{t('RemoveCacheOnDropDesc')}</small>
|
</CacheSizeSettings>
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='TorrentsSavePath'
|
|
||||||
label={t('TorrentsSavePath')}
|
|
||||||
value={TorrentsSavePath}
|
|
||||||
type='url'
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</CacheStorageSettings>
|
|
||||||
)}
|
|
||||||
</MainSettingsContent>
|
|
||||||
</TabPanel>
|
|
||||||
|
|
||||||
<TabPanel value={selectedTab} index={1} dir={direction}>
|
{UseDisk ? (
|
||||||
<SecondarySettingsContent>
|
<CacheStorageSettings>
|
||||||
<SettingSectionLabel>Дополнительные настройки</SettingSectionLabel>
|
<SettingSectionLabel>Место хранения кеша</SettingSectionLabel>
|
||||||
|
|
||||||
<FormControlLabel
|
<div style={{ display: 'grid', gridAutoFlow: 'column' }}>
|
||||||
control={<Switch checked={EnableIPv6} onChange={inputForm} id='EnableIPv6' color='primary' />}
|
<StorageButton small onClick={() => updateSettings({ UseDisk: false })}>
|
||||||
label={t('EnableIPv6')}
|
<StorageIconWrapper small>
|
||||||
/>
|
<RAMIcon color='#323637' />
|
||||||
<br />
|
</StorageIconWrapper>
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={!DisableTCP} onChange={inputForm} id='DisableTCP' color='primary' />}
|
<div>Оперативная память</div>
|
||||||
label={t('TCP')}
|
</StorageButton>
|
||||||
/>
|
|
||||||
<br />
|
<StorageButton small selected>
|
||||||
<FormControlLabel
|
<StorageIconWrapper small selected>
|
||||||
control={<Switch checked={!DisableUTP} onChange={inputForm} id='DisableUTP' color='primary' />}
|
<USBIcon color='#dee3e5' />
|
||||||
label={t('UTP')}
|
</StorageIconWrapper>
|
||||||
/>
|
|
||||||
<br />
|
<div>Диск</div>
|
||||||
<FormControlLabel
|
</StorageButton>
|
||||||
control={<Switch checked={!DisablePEX} onChange={inputForm} id='DisablePEX' color='primary' />}
|
</div>
|
||||||
label={t('PEX')}
|
|
||||||
/>
|
<FormControlLabel
|
||||||
<br />
|
control={
|
||||||
<FormControlLabel
|
<Switch
|
||||||
control={<Switch checked={ForceEncrypt} onChange={inputForm} id='ForceEncrypt' color='primary' />}
|
checked={RemoveCacheOnDrop}
|
||||||
label={t('ForceEncrypt')}
|
onChange={inputForm}
|
||||||
/>
|
id='RemoveCacheOnDrop'
|
||||||
<br />
|
color='primary'
|
||||||
<TextField
|
/>
|
||||||
onChange={inputForm}
|
}
|
||||||
margin='dense'
|
label={t('RemoveCacheOnDrop')}
|
||||||
id='TorrentDisconnectTimeout'
|
/>
|
||||||
label={t('TorrentDisconnectTimeout')}
|
<small>{t('RemoveCacheOnDropDesc')}</small>
|
||||||
value={TorrentDisconnectTimeout}
|
|
||||||
type='number'
|
<TextField
|
||||||
fullWidth
|
onChange={inputForm}
|
||||||
/>
|
margin='dense'
|
||||||
<br />
|
id='TorrentsSavePath'
|
||||||
<TextField
|
label={t('TorrentsSavePath')}
|
||||||
onChange={inputForm}
|
value={TorrentsSavePath}
|
||||||
margin='dense'
|
type='url'
|
||||||
id='ConnectionsLimit'
|
fullWidth
|
||||||
label={t('ConnectionsLimit')}
|
/>
|
||||||
value={ConnectionsLimit}
|
</CacheStorageSettings>
|
||||||
type='number'
|
) : (
|
||||||
fullWidth
|
<CacheStorageSelector>
|
||||||
/>
|
<SettingSectionLabel style={{ placeSelf: 'start', gridArea: 'label' }}>
|
||||||
<br />
|
Место хранения кеша
|
||||||
<FormControlLabel
|
</SettingSectionLabel>
|
||||||
control={<Switch checked={!DisableDHT} onChange={inputForm} id='DisableDHT' color='primary' />}
|
|
||||||
label={t('DHT')}
|
<StorageButton selected>
|
||||||
/>
|
<StorageIconWrapper selected>
|
||||||
<br />
|
<RAMIcon color='#dee3e5' />
|
||||||
<TextField
|
</StorageIconWrapper>
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
<div>Оперативная память</div>
|
||||||
id='DhtConnectionLimit'
|
</StorageButton>
|
||||||
label={t('DhtConnectionLimit')}
|
|
||||||
value={DhtConnectionLimit}
|
<StorageButton onClick={() => updateSettings({ UseDisk: true })}>
|
||||||
type='number'
|
<StorageIconWrapper>
|
||||||
fullWidth
|
<USBIcon color='#323637' />
|
||||||
/>
|
</StorageIconWrapper>
|
||||||
<br />
|
|
||||||
<TextField
|
<div>Диск</div>
|
||||||
onChange={inputForm}
|
</StorageButton>
|
||||||
margin='dense'
|
</CacheStorageSelector>
|
||||||
id='DownloadRateLimit'
|
)}
|
||||||
label={t('DownloadRateLimit')}
|
</MainSettingsContent>
|
||||||
value={DownloadRateLimit}
|
</TabPanel>
|
||||||
type='number'
|
|
||||||
fullWidth
|
<TabPanel value={selectedTab} index={1} dir={direction}>
|
||||||
/>
|
<SecondarySettingsContent>
|
||||||
<br />
|
<SettingSectionLabel>Дополнительные настройки</SettingSectionLabel>
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={!DisableUpload} onChange={inputForm} id='DisableUpload' color='primary' />}
|
<FormControlLabel
|
||||||
label={t('Upload')}
|
control={<Switch checked={EnableIPv6} onChange={inputForm} id='EnableIPv6' color='primary' />}
|
||||||
/>
|
label={t('EnableIPv6')}
|
||||||
<br />
|
/>
|
||||||
<TextField
|
<br />
|
||||||
onChange={inputForm}
|
<FormControlLabel
|
||||||
margin='dense'
|
control={<Switch checked={!DisableTCP} onChange={inputForm} id='DisableTCP' color='primary' />}
|
||||||
id='UploadRateLimit'
|
label={t('TCP')}
|
||||||
label={t('UploadRateLimit')}
|
/>
|
||||||
value={UploadRateLimit}
|
<br />
|
||||||
type='number'
|
<FormControlLabel
|
||||||
fullWidth
|
control={<Switch checked={!DisableUTP} onChange={inputForm} id='DisableUTP' color='primary' />}
|
||||||
/>
|
label={t('UTP')}
|
||||||
<br />
|
/>
|
||||||
<TextField
|
<br />
|
||||||
onChange={inputForm}
|
<FormControlLabel
|
||||||
margin='dense'
|
control={<Switch checked={!DisablePEX} onChange={inputForm} id='DisablePEX' color='primary' />}
|
||||||
id='PeersListenPort'
|
label={t('PEX')}
|
||||||
label={t('PeersListenPort')}
|
/>
|
||||||
value={PeersListenPort}
|
<br />
|
||||||
type='number'
|
<FormControlLabel
|
||||||
fullWidth
|
control={<Switch checked={ForceEncrypt} onChange={inputForm} id='ForceEncrypt' color='primary' />}
|
||||||
/>
|
label={t('ForceEncrypt')}
|
||||||
<br />
|
/>
|
||||||
<FormControlLabel
|
<br />
|
||||||
control={<Switch checked={!DisableUPNP} onChange={inputForm} id='DisableUPNP' color='primary' />}
|
<TextField
|
||||||
label={t('UPNP')}
|
onChange={inputForm}
|
||||||
/>
|
margin='dense'
|
||||||
<br />
|
id='TorrentDisconnectTimeout'
|
||||||
<InputLabel htmlFor='RetrackersMode'>{t('RetrackersMode')}</InputLabel>
|
label={t('TorrentDisconnectTimeout')}
|
||||||
<Select onChange={inputForm} type='number' native id='RetrackersMode' value={RetrackersMode}>
|
value={TorrentDisconnectTimeout}
|
||||||
<option value={0}>{t('DontAddRetrackers')}</option>
|
type='number'
|
||||||
<option value={1}>{t('AddRetrackers')}</option>
|
fullWidth
|
||||||
<option value={2}>{t('RemoveRetrackers')}</option>
|
/>
|
||||||
<option value={3}>{t('ReplaceRetrackers')}</option>
|
<br />
|
||||||
</Select>
|
<TextField
|
||||||
<br />
|
onChange={inputForm}
|
||||||
</SecondarySettingsContent>
|
margin='dense'
|
||||||
</TabPanel>
|
id='ConnectionsLimit'
|
||||||
</SwipeableViews>
|
label={t('ConnectionsLimit')}
|
||||||
</>
|
value={ConnectionsLimit}
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={!DisableDHT} onChange={inputForm} id='DisableDHT' color='primary' />}
|
||||||
|
label={t('DHT')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextField
|
||||||
|
onChange={inputForm}
|
||||||
|
margin='dense'
|
||||||
|
id='DhtConnectionLimit'
|
||||||
|
label={t('DhtConnectionLimit')}
|
||||||
|
value={DhtConnectionLimit}
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextField
|
||||||
|
onChange={inputForm}
|
||||||
|
margin='dense'
|
||||||
|
id='DownloadRateLimit'
|
||||||
|
label={t('DownloadRateLimit')}
|
||||||
|
value={DownloadRateLimit}
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={!DisableUpload} onChange={inputForm} id='DisableUpload' color='primary' />}
|
||||||
|
label={t('Upload')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextField
|
||||||
|
onChange={inputForm}
|
||||||
|
margin='dense'
|
||||||
|
id='UploadRateLimit'
|
||||||
|
label={t('UploadRateLimit')}
|
||||||
|
value={UploadRateLimit}
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextField
|
||||||
|
onChange={inputForm}
|
||||||
|
margin='dense'
|
||||||
|
id='PeersListenPort'
|
||||||
|
label={t('PeersListenPort')}
|
||||||
|
value={PeersListenPort}
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={!DisableUPNP} onChange={inputForm} id='DisableUPNP' color='primary' />}
|
||||||
|
label={t('UPNP')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<InputLabel htmlFor='RetrackersMode'>{t('RetrackersMode')}</InputLabel>
|
||||||
|
<Select onChange={inputForm} type='number' native id='RetrackersMode' value={RetrackersMode}>
|
||||||
|
<option value={0}>{t('DontAddRetrackers')}</option>
|
||||||
|
<option value={1}>{t('AddRetrackers')}</option>
|
||||||
|
<option value={2}>{t('RemoveRetrackers')}</option>
|
||||||
|
<option value={3}>{t('ReplaceRetrackers')}</option>
|
||||||
|
</Select>
|
||||||
|
<br />
|
||||||
|
</SecondarySettingsContent>
|
||||||
|
</TabPanel>
|
||||||
|
</SwipeableViews>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'loading...'
|
||||||
|
)}
|
||||||
{/* <DialogTitle id='form-dialog-title'>{t('Settings')}</DialogTitle>
|
{/* <DialogTitle id='form-dialog-title'>{t('Settings')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -805,6 +706,18 @@ export default function SettingsDialog({ handleClose }) {
|
|||||||
{t('Cancel')}
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setCacheSize(defaultSettings.CacheSize)
|
||||||
|
setCachePercentage(defaultSettings.ReaderReadAHead)
|
||||||
|
updateSettings(defaultSettings)
|
||||||
|
}}
|
||||||
|
color='secondary'
|
||||||
|
variant='outlined'
|
||||||
|
>
|
||||||
|
Reset to default
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button variant='contained' onClick={handleSave} color='primary'>
|
<Button variant='contained' onClick={handleSave} color='primary'>
|
||||||
{t('Save')}
|
{t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import SettingsDialog from './SettingsDialog'
|
import SettingsDialog from './SettingsDialog'
|
||||||
|
|
||||||
export default function SettingsDialogButton() {
|
export default function SettingsDialogButton({ isOffline, isLoading }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export default function SettingsDialogButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ListItem button onClick={handleClickOpen}>
|
<ListItem disabled={isOffline || isLoading} button onClick={handleClickOpen}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|||||||
161
web/src/components/Settings/style.js
Normal file
161
web/src/components/Settings/style.js
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import styled, { css } from 'styled-components'
|
||||||
|
import { mainColors } from 'style/colors'
|
||||||
|
|
||||||
|
export const cacheBeforeReaderColor = '#b3dfc9'
|
||||||
|
export const cacheAfterReaderColor = mainColors.light.primary
|
||||||
|
|
||||||
|
export const FooterSection = styled.div`
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #e8e5eb;
|
||||||
|
|
||||||
|
> :last-child > :not(:last-child) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const Divider = styled.div`
|
||||||
|
height: 1px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.12);
|
||||||
|
margin: 30px 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PreloadCacheValue = styled.div`
|
||||||
|
${({ color }) => css`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content 100px 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
:not(:last-child) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:before {
|
||||||
|
content: '';
|
||||||
|
background: ${color};
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const MainSettingsContent = styled.div`
|
||||||
|
min-height: 500px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 40px;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
@media (max-width: 930px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const SecondarySettingsContent = styled.div`
|
||||||
|
min-height: 500px;
|
||||||
|
padding: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const StorageButton = styled.div`
|
||||||
|
${({ small, selected }) => css`
|
||||||
|
transition: 0.2s;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
${!selected &&
|
||||||
|
css`
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
filter: brightness(0.8);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
|
||||||
|
${small
|
||||||
|
? css`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
`}
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const StorageIconWrapper = styled.div`
|
||||||
|
${({ selected, small }) => css`
|
||||||
|
width: ${small ? '60px' : '150px'};
|
||||||
|
height: ${small ? '60px' : '150px'};
|
||||||
|
border-radius: 50%;
|
||||||
|
background: ${selected ? '#323637' : '#dee3e5'};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transform: rotate(-45deg) scale(0.75);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const CacheSizeSettings = styled.div``
|
||||||
|
export const CacheStorageSelector = styled.div`
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: max-content 1fr;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-areas: 'label label';
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
@media (max-width: 930px) {
|
||||||
|
grid-template-columns: repeat(2, max-content);
|
||||||
|
column-gap: 30px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const CacheStorageSettings = styled.div``
|
||||||
|
|
||||||
|
export const SettingSection = styled.section``
|
||||||
|
export const SettingLabel = styled.div``
|
||||||
|
export const SettingSectionLabel = styled.div`
|
||||||
|
font-size: 25px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const PreloadCachePercentage = styled.div.attrs(({ value }) => ({
|
||||||
|
// this block is here according to styled-components recomendation about fast changable components
|
||||||
|
style: {
|
||||||
|
background: `linear-gradient(to right, ${cacheBeforeReaderColor} 0%, ${cacheBeforeReaderColor} ${value}%, ${cacheAfterReaderColor} ${value}%, ${cacheAfterReaderColor} 100%)`,
|
||||||
|
},
|
||||||
|
}))`
|
||||||
|
${({ label, isPreloadEnabled }) => css`
|
||||||
|
border: 1px solid #323637;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #000;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
:before {
|
||||||
|
content: '${label}';
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${isPreloadEnabled &&
|
||||||
|
css`
|
||||||
|
:after {
|
||||||
|
content: '';
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: #323637;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`}
|
||||||
|
`
|
||||||
Reference in New Issue
Block a user