mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 21:46:11 +05:00
@@ -34,6 +34,7 @@ OUTPUT="${ROOT}/dist/TorrServer"
|
|||||||
#### Build web
|
#### Build web
|
||||||
echo "Build web"
|
echo "Build web"
|
||||||
cd "${ROOT}/web" || exit 1
|
cd "${ROOT}/web" || exit 1
|
||||||
|
npm install --silent
|
||||||
npm run --silent build-js
|
npm run --silent build-js
|
||||||
cp "${ROOT}/web/dest/index.html" "${ROOT}/server/web/pages/template/pages/"
|
cp "${ROOT}/web/dest/index.html" "${ROOT}/server/web/pages/template/pages/"
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"fontsource-roboto": "^4.0.0",
|
"fontsource-roboto": "^4.0.0",
|
||||||
|
"i18next": "^20.3.1",
|
||||||
|
"i18next-browser-languagedetector": "^6.1.1",
|
||||||
"konva": "^8.0.1",
|
"konva": "^8.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"material-ui-image": "^3.3.2",
|
"material-ui-image": "^3.3.2",
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
"react-copy-to-clipboard": "^5.0.3",
|
"react-copy-to-clipboard": "^5.0.3",
|
||||||
"react-div-100vh": "^0.6.0",
|
"react-div-100vh": "^0.6.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-i18next": "^11.10.0",
|
||||||
"react-konva": "^17.0.2-4",
|
"react-konva": "^17.0.2-4",
|
||||||
"react-measure": "^2.5.2",
|
"react-measure": "^2.5.2",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,21 +11,23 @@ import UploadDialog from 'components/Upload'
|
|||||||
import { CreditCard as CreditCardIcon, List as ListIcon } from '@material-ui/icons'
|
import { CreditCard as CreditCardIcon, List as ListIcon } from '@material-ui/icons'
|
||||||
import List from '@material-ui/core/List'
|
import List from '@material-ui/core/List'
|
||||||
import CloseServer from 'components/CloseServer'
|
import CloseServer from 'components/CloseServer'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { AppSidebarStyle } from './style'
|
import { AppSidebarStyle } from './style'
|
||||||
|
|
||||||
export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
|
export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<AppSidebarStyle isDrawerOpen={isDrawerOpen}>
|
<AppSidebarStyle isDrawerOpen={isDrawerOpen}>
|
||||||
<List>
|
<List>
|
||||||
<AddDialogButton />
|
<AddDialogButton />
|
||||||
<UploadDialog />
|
<UploadDialog />
|
||||||
<RemoveAll />
|
<RemoveAll />
|
||||||
<ListItem button component='a' key='Playlist all torrents' target='_blank' href={playlistAllHost()}>
|
<ListItem button component='a' key={t('PlaylistAll')} target='_blank' href={playlistAllHost()}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<ListIcon />
|
<ListIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary='Playlist all torrents' />
|
<ListItemText primary={t('PlaylistAll')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<CreditCardIcon />
|
<CreditCardIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary='Donate' />
|
<ListItemText primary={t('Donate')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
</AppSidebarStyle>
|
</AppSidebarStyle>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import Sidebar from './Sidebar'
|
|||||||
|
|
||||||
const baseTheme = createMuiTheme({
|
const baseTheme = createMuiTheme({
|
||||||
overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } },
|
overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } },
|
||||||
palette: { primary: { main: '#3fb57a' }, secondary: { main: '#FFA724' }, tonalOffset: 0.2 },
|
palette: { primary: { main: '#00a572' }, secondary: { main: '#ffa724' }, tonalOffset: 0.2 },
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import styled, { css } from 'styled-components'
|
|||||||
|
|
||||||
export const AppWrapper = styled.div`
|
export const AppWrapper = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background: #cbe8d9;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 60px 1fr;
|
grid-template-columns: 60px 1fr;
|
||||||
grid-template-rows: 60px 1fr;
|
grid-template-rows: 60px 1fr;
|
||||||
@@ -17,7 +18,7 @@ export const CenteredGrid = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const AppHeader = styled.div`
|
export const AppHeader = styled.div`
|
||||||
background: #3fb57a;
|
background: #00a572;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
grid-area: head;
|
grid-area: head;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -34,7 +35,7 @@ export const AppSidebarStyle = styled.div`
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms;
|
transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms;
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
background: #fff;
|
background: #eee;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import axios from 'axios'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
import Button from '@material-ui/core/Button'
|
import Button from '@material-ui/core/Button'
|
||||||
import Dialog from '@material-ui/core/Dialog'
|
import Dialog from '@material-ui/core/Dialog'
|
||||||
import DialogActions from '@material-ui/core/DialogActions'
|
import DialogActions from '@material-ui/core/DialogActions'
|
||||||
@@ -8,9 +9,16 @@ import InfoIcon from '@material-ui/icons/Info'
|
|||||||
import ListItem from '@material-ui/core/ListItem'
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
import ListItemText from '@material-ui/core/ListItemText'
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { echoHost } from 'utils/Hosts'
|
||||||
|
|
||||||
export default function AboutDialog() {
|
export default function AboutDialog() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
const [torrServerVersion, setTorrServerVersion] = useState('')
|
||||||
|
useEffect(() => {
|
||||||
|
axios.get(echoHost()).then(({ data }) => setTorrServerVersion(data))
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -18,26 +26,30 @@ export default function AboutDialog() {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<InfoIcon />
|
<InfoIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary='About' />
|
<ListItemText primary={t('About')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Dialog open={open} onClose={() => setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'>
|
<Dialog open={open} onClose={() => setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'>
|
||||||
<DialogTitle id='form-dialog-title'>About</DialogTitle>
|
<DialogTitle id='form-dialog-title'>{t('About')}</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
<center>
|
||||||
|
<h2>TorrServer {torrServerVersion}</h2>
|
||||||
|
<a href='https://github.com/YouROK/TorrServer'>https://github.com/YouROK/TorrServer</a>
|
||||||
|
</center>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<center>
|
<center>
|
||||||
<h2>Thanks to everyone who tested and helped.</h2>
|
<h2>{t('ThanksToEveryone')}</h2>
|
||||||
</center>
|
</center>
|
||||||
<br />
|
<br />
|
||||||
<h2>Special thanks:</h2>
|
<h2>{t('SpecialThanks')}</h2>
|
||||||
<b>Anacrolix Matt Joiner</b> <a href='https://github.com/anacrolix/'>github.com/anacrolix</a>
|
<b>anacrolix Matt Joiner</b> <a href='https://github.com/anacrolix/'>github.com/anacrolix</a>
|
||||||
<br />
|
<br />
|
||||||
<b>tsynik nikk Никита</b> <a href='https://github.com/tsynik'>github.com/tsynik</a>
|
<b>nikk</b> <a href='https://github.com/tsynik'>github.com/tsynik</a>
|
||||||
<br />
|
<br />
|
||||||
<b>dancheskus</b> <a href='https://github.com/dancheskus'>github.com/dancheskus</a>
|
<b>dancheskus</b> <a href='https://github.com/dancheskus'>github.com/dancheskus</a>
|
||||||
<br />
|
<br />
|
||||||
<b>Tw1cker Руслан Пахнев</b> <a href='https://github.com/Nemiroff'>github.com/Nemiroff</a>
|
<b>tw1cker Руслан Пахнев</b> <a href='https://github.com/Nemiroff'>github.com/Nemiroff</a>
|
||||||
<br />
|
<br />
|
||||||
<b>SpAwN_LMG</b>
|
<b>SpAwN_LMG</b>
|
||||||
<br />
|
<br />
|
||||||
@@ -46,7 +58,7 @@ export default function AboutDialog() {
|
|||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setOpen(false)} color='primary' variant='outlined' autoFocus>
|
<Button onClick={() => setOpen(false)} color='primary' variant='outlined' autoFocus>
|
||||||
Close
|
{t('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import DialogContent from '@material-ui/core/DialogContent'
|
|||||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||||
import { torrentsHost } from 'utils/Hosts'
|
import { torrentsHost } from 'utils/Hosts'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function AddDialog({ handleClose }) {
|
export default function AddDialog({ handleClose }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [link, setLink] = useState('')
|
const [link, setLink] = useState('')
|
||||||
const [title, setTitle] = useState('')
|
const [title, setTitle] = useState('')
|
||||||
const [poster, setPoster] = useState('')
|
const [poster, setPoster] = useState('')
|
||||||
@@ -23,17 +25,17 @@ export default function AddDialog({ handleClose }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
|
<Dialog open onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
|
||||||
<DialogTitle id='form-dialog-title'>Add magnet or link to torrent file</DialogTitle>
|
<DialogTitle id='form-dialog-title'>{t('AddMagnetOrLink')}</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField onChange={inputTitle} margin='dense' id='title' label='Title' type='text' fullWidth />
|
<TextField onChange={inputTitle} margin='dense' id='title' label={t('Title')} type='text' fullWidth />
|
||||||
<TextField onChange={inputPoster} margin='dense' id='poster' label='Poster' type='url' fullWidth />
|
<TextField onChange={inputPoster} margin='dense' id='poster' label={t('Poster')} type='url' fullWidth />
|
||||||
<TextField
|
<TextField
|
||||||
onChange={inputMagnet}
|
onChange={inputMagnet}
|
||||||
autoFocus
|
autoFocus
|
||||||
margin='dense'
|
margin='dense'
|
||||||
id='magnet'
|
id='magnet'
|
||||||
label='Magnet or torrent file link'
|
label={t('MagnetOrTorrentFileLink')}
|
||||||
type='text'
|
type='text'
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
@@ -41,11 +43,11 @@ export default function AddDialog({ handleClose }) {
|
|||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color='primary' variant='outlined'>
|
<Button onClick={handleClose} color='primary' variant='outlined'>
|
||||||
Cancel
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button variant='contained' disabled={!link} onClick={handleSave} color='primary'>
|
<Button variant='contained' disabled={!link} onClick={handleSave} color='primary'>
|
||||||
Add
|
{t('Add')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'
|
|||||||
import LibraryAddIcon from '@material-ui/icons/LibraryAdd'
|
import LibraryAddIcon from '@material-ui/icons/LibraryAdd'
|
||||||
import ListItemText from '@material-ui/core/ListItemText'
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
import ListItem from '@material-ui/core/ListItem'
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import AddDialog from './AddDialog'
|
import AddDialog from './AddDialog'
|
||||||
|
|
||||||
export default function AddDialogButton() {
|
export default function AddDialogButton() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||||
|
|
||||||
const handleClickOpen = () => setIsDialogOpen(true)
|
const handleClickOpen = () => setIsDialogOpen(true)
|
||||||
const handleClose = () => setIsDialogOpen(false)
|
const handleClose = () => setIsDialogOpen(false)
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ export default function AddDialogButton() {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LibraryAddIcon />
|
<LibraryAddIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary='Add from link' />
|
<ListItemText primary={t('AddFromLink')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
{isDialogOpen && <AddDialog handleClose={handleClose} />}
|
{isDialogOpen && <AddDialog handleClose={handleClose} />}
|
||||||
|
|||||||
@@ -2,27 +2,29 @@ import { useState } from 'react'
|
|||||||
import { Button, Dialog, DialogActions, DialogTitle, ListItem, ListItemIcon, ListItemText } from '@material-ui/core'
|
import { Button, Dialog, DialogActions, DialogTitle, ListItem, ListItemIcon, ListItemText } from '@material-ui/core'
|
||||||
import { PowerSettingsNew as PowerSettingsNewIcon } from '@material-ui/icons'
|
import { PowerSettingsNew as PowerSettingsNewIcon } from '@material-ui/icons'
|
||||||
import { shutdownHost } from 'utils/Hosts'
|
import { shutdownHost } from 'utils/Hosts'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function CloseServer() {
|
export default function CloseServer() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const closeDialog = () => setOpen(false)
|
const closeDialog = () => setOpen(false)
|
||||||
const openDialog = () => setOpen(true)
|
const openDialog = () => setOpen(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem button key='Close server' onClick={openDialog}>
|
<ListItem button key={t('CloseServer')} onClick={openDialog}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<PowerSettingsNewIcon />
|
<PowerSettingsNewIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
||||||
<ListItemText primary='Close server' />
|
<ListItemText primary={t('CloseServer')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Dialog open={open} onClose={closeDialog}>
|
<Dialog open={open} onClose={closeDialog}>
|
||||||
<DialogTitle>Close server?</DialogTitle>
|
<DialogTitle>{t('CloseServer?')}</DialogTitle>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant='outlined' onClick={closeDialog} color='primary'>
|
<Button variant='outlined' onClick={closeDialog} color='primary'>
|
||||||
Cancel
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -34,7 +36,7 @@ export default function CloseServer() {
|
|||||||
color='primary'
|
color='primary'
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
Ok
|
{t('OK')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Button from '@material-ui/core/Button'
|
|||||||
import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core'
|
import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core'
|
||||||
import CloseIcon from '@material-ui/icons/Close'
|
import CloseIcon from '@material-ui/icons/Close'
|
||||||
import { ArrowBack } from '@material-ui/icons'
|
import { ArrowBack } from '@material-ui/icons'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
appBar: { position: 'relative' },
|
appBar: { position: 'relative' },
|
||||||
@@ -9,6 +10,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
export default function DialogHeader({ title, onClose, onBack }) {
|
export default function DialogHeader({ title, onClose, onBack }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -24,7 +26,7 @@ export default function DialogHeader({ title, onClose, onBack }) {
|
|||||||
|
|
||||||
{onBack && (
|
{onBack && (
|
||||||
<Button autoFocus color='inherit' onClick={onClose}>
|
<Button autoFocus color='inherit' onClick={onClose}>
|
||||||
close
|
{t('Close')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { humanizeSize } from 'utils/Utils'
|
|||||||
import ptt from 'parse-torrent-title'
|
import ptt from 'parse-torrent-title'
|
||||||
import { Button } from '@material-ui/core'
|
import { Button } from '@material-ui/core'
|
||||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { TableStyle, ShortTableWrapper, ShortTable } from './style'
|
import { TableStyle, ShortTableWrapper, ShortTable } from './style'
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ const { memo } = require('react')
|
|||||||
|
|
||||||
const Table = memo(
|
const Table = memo(
|
||||||
({ playableFileList, viewedFileList, selectedSeason, seasonAmount, hash }) => {
|
({ playableFileList, viewedFileList, selectedSeason, seasonAmount, hash }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`)
|
const preloadBuffer = fileId => fetch(`${streamHost()}?link=${hash}&index=${fileId}&preload`)
|
||||||
const getFileLink = (path, id) =>
|
const getFileLink = (path, id) =>
|
||||||
`${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play`
|
`${streamHost()}/${encodeURIComponent(path.split('\\').pop().split('/').pop())}?link=${hash}&index=${id}&play`
|
||||||
@@ -25,13 +27,13 @@ const Table = memo(
|
|||||||
<TableStyle>
|
<TableStyle>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style={{ width: '0' }}>viewed</th>
|
<th style={{ width: '0' }}>{t('Viewed')}</th>
|
||||||
<th>name</th>
|
<th>{t('Name')}</th>
|
||||||
{fileHasSeasonText && seasonAmount?.length === 1 && <th style={{ width: '0' }}>season</th>}
|
{fileHasSeasonText && seasonAmount?.length === 1 && <th style={{ width: '0' }}>{t('Season')}</th>}
|
||||||
{fileHasEpisodeText && <th style={{ width: '0' }}>episode</th>}
|
{fileHasEpisodeText && <th style={{ width: '0' }}>{t('Episode')}</th>}
|
||||||
{fileHasResolutionText && <th style={{ width: '0' }}>resolution</th>}
|
{fileHasResolutionText && <th style={{ width: '0' }}>{t('Resolution')}</th>}
|
||||||
<th style={{ width: '100px' }}>size</th>
|
<th style={{ width: '100px' }}>{t('Size')}</th>
|
||||||
<th style={{ width: '400px' }}>actions</th>
|
<th style={{ width: '400px' }}>{t('Actions')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
@@ -52,18 +54,18 @@ const Table = memo(
|
|||||||
<td data-label='size'>{humanizeSize(length)}</td>
|
<td data-label='size'>{humanizeSize(length)}</td>
|
||||||
<td className='button-cell'>
|
<td className='button-cell'>
|
||||||
<Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'>
|
<Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'>
|
||||||
Preload
|
{t('Preload')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'>
|
<a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'>
|
||||||
<Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'>
|
<Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'>
|
||||||
Open link
|
{t('OpenLink')}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<CopyToClipboard text={link}>
|
<CopyToClipboard text={link}>
|
||||||
<Button variant='outlined' color='primary' size='small'>
|
<Button variant='outlined' color='primary' size='small'>
|
||||||
Copy link
|
{t('CopyLink')}
|
||||||
</Button>
|
</Button>
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</td>
|
</td>
|
||||||
@@ -87,7 +89,7 @@ const Table = memo(
|
|||||||
<div className='short-table-data'>
|
<div className='short-table-data'>
|
||||||
{isViewed && (
|
{isViewed && (
|
||||||
<div className='short-table-field'>
|
<div className='short-table-field'>
|
||||||
<div className='short-table-field-name'>viewed</div>
|
<div className='short-table-field-name'>{t('Viewed')}</div>
|
||||||
<div className='short-table-field-value'>
|
<div className='short-table-field-value'>
|
||||||
<div className='short-table-viewed-indicator' />
|
<div className='short-table-viewed-indicator' />
|
||||||
</div>
|
</div>
|
||||||
@@ -95,41 +97,41 @@ const Table = memo(
|
|||||||
)}
|
)}
|
||||||
{fileHasSeasonText && seasonAmount?.length === 1 && (
|
{fileHasSeasonText && seasonAmount?.length === 1 && (
|
||||||
<div className='short-table-field'>
|
<div className='short-table-field'>
|
||||||
<div className='short-table-field-name'>season</div>
|
<div className='short-table-field-name'>{t('Season')}</div>
|
||||||
<div className='short-table-field-value'>{season}</div>
|
<div className='short-table-field-value'>{season}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{fileHasEpisodeText && (
|
{fileHasEpisodeText && (
|
||||||
<div className='short-table-field'>
|
<div className='short-table-field'>
|
||||||
<div className='short-table-field-name'>epoisode</div>
|
<div className='short-table-field-name'>{t('Episode')}</div>
|
||||||
<div className='short-table-field-value'>{episode}</div>
|
<div className='short-table-field-value'>{episode}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{fileHasResolutionText && (
|
{fileHasResolutionText && (
|
||||||
<div className='short-table-field'>
|
<div className='short-table-field'>
|
||||||
<div className='short-table-field-name'>resolution</div>
|
<div className='short-table-field-name'>{t('Resolution')}</div>
|
||||||
<div className='short-table-field-value'>{resolution}</div>
|
<div className='short-table-field-value'>{resolution}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className='short-table-field'>
|
<div className='short-table-field'>
|
||||||
<div className='short-table-field-name'>size</div>
|
<div className='short-table-field-name'>{t('Size')}</div>
|
||||||
<div className='short-table-field-value'>{humanizeSize(length)}</div>
|
<div className='short-table-field-value'>{humanizeSize(length)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='short-table-buttons'>
|
<div className='short-table-buttons'>
|
||||||
<Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'>
|
<Button onClick={() => preloadBuffer(id)} variant='outlined' color='primary' size='small'>
|
||||||
Preload
|
{t('Preload')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'>
|
<a style={{ textDecoration: 'none' }} href={link} target='_blank' rel='noreferrer'>
|
||||||
<Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'>
|
<Button style={{ width: '100%' }} variant='outlined' color='primary' size='small'>
|
||||||
Open link
|
{t('OpenLink')}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<CopyToClipboard text={link}>
|
<CopyToClipboard text={link}>
|
||||||
<Button variant='outlined' color='primary' size='small'>
|
<Button variant='outlined' color='primary' size='small'>
|
||||||
Copy link
|
{t('CopyLink')}
|
||||||
</Button>
|
</Button>
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const defaultBorderColor = '#eef2f4'
|
export const defaultBorderColor = '#eef2f4'
|
||||||
export const defaultBackgroundColor = '#fff'
|
export const defaultBackgroundColor = '#fff'
|
||||||
export const completeColor = '#3fb57a'
|
export const completeColor = '#00a572'
|
||||||
export const progressColor = '#00d0d0'
|
export const progressColor = '#ffa724'
|
||||||
export const activeColor = '#000'
|
export const activeColor = '#000'
|
||||||
export const rangeColor = '#9a9aff'
|
export const rangeColor = '#9a9aff'
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import { playlistTorrHost, torrentsHost, viewedHost } from 'utils/Hosts'
|
|||||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||||
import { Button } from '@material-ui/core'
|
import { Button } from '@material-ui/core'
|
||||||
import ptt from 'parse-torrent-title'
|
import ptt from 'parse-torrent-title'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { SmallLabel, MainSectionButtonGroup } from './style'
|
import { SmallLabel, MainSectionButtonGroup } from './style'
|
||||||
import { SectionSubName } from '../style'
|
import { SectionSubName } from '../style'
|
||||||
|
|
||||||
const TorrentFunctions = memo(
|
const TorrentFunctions = memo(
|
||||||
({ hash, viewedFileList, playableFileList, name, title, setViewedFileList }) => {
|
({ hash, viewedFileList, playableFileList, name, title, setViewedFileList }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1]
|
const latestViewedFileId = viewedFileList?.[viewedFileList?.length - 1]
|
||||||
const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path
|
const latestViewedFile = playableFileList?.find(({ id }) => id === latestViewedFileId)?.path
|
||||||
const isOnlyOnePlayableFile = playableFileList?.length === 1
|
const isOnlyOnePlayableFile = playableFileList?.length === 1
|
||||||
@@ -24,13 +26,13 @@ const TorrentFunctions = memo(
|
|||||||
<>
|
<>
|
||||||
{!isOnlyOnePlayableFile && !!viewedFileList?.length && (
|
{!isOnlyOnePlayableFile && !!viewedFileList?.length && (
|
||||||
<>
|
<>
|
||||||
<SmallLabel>Download Playlist</SmallLabel>
|
<SmallLabel>{t('DownloadPlaylist')}</SmallLabel>
|
||||||
<SectionSubName mb={10}>
|
<SectionSubName mb={10}>
|
||||||
<strong>Latest file played:</strong> {latestViewedFileData?.title}.
|
<strong>{t('LatestFilePlayed')}</strong> {latestViewedFileData?.title}.
|
||||||
{latestViewedFileData?.season && (
|
{latestViewedFileData?.season && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
Season: {latestViewedFileData?.season}. Episode: {latestViewedFileData?.episode}.
|
{t('Season')}: {latestViewedFileData?.season}. {t('Episode')}: {latestViewedFileData?.episode}.
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SectionSubName>
|
</SectionSubName>
|
||||||
@@ -38,39 +40,39 @@ const TorrentFunctions = memo(
|
|||||||
<MainSectionButtonGroup>
|
<MainSectionButtonGroup>
|
||||||
<a style={{ textDecoration: 'none' }} href={fullPlaylistLink}>
|
<a style={{ textDecoration: 'none' }} href={fullPlaylistLink}>
|
||||||
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
|
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
|
||||||
full
|
{t('Full')}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a style={{ textDecoration: 'none' }} href={partialPlaylistLink}>
|
<a style={{ textDecoration: 'none' }} href={partialPlaylistLink}>
|
||||||
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
|
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
|
||||||
from latest file
|
{t('FromLatestFile')}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
</MainSectionButtonGroup>
|
</MainSectionButtonGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<SmallLabel mb={10}>Torrent State</SmallLabel>
|
<SmallLabel mb={10}>{t('TorrentState')}</SmallLabel>
|
||||||
<MainSectionButtonGroup>
|
<MainSectionButtonGroup>
|
||||||
<Button onClick={() => removeTorrentViews()} variant='contained' color='primary' size='large'>
|
<Button onClick={() => removeTorrentViews()} variant='contained' color='primary' size='large'>
|
||||||
remove views
|
{t('RemoveViews')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => dropTorrent()} variant='contained' color='primary' size='large'>
|
<Button onClick={() => dropTorrent()} variant='contained' color='primary' size='large'>
|
||||||
reset torrent
|
{t('DropTorrent')}
|
||||||
</Button>
|
</Button>
|
||||||
</MainSectionButtonGroup>
|
</MainSectionButtonGroup>
|
||||||
<SmallLabel mb={10}>Info</SmallLabel>
|
<SmallLabel mb={10}>{t('Info')}</SmallLabel>
|
||||||
<MainSectionButtonGroup>
|
<MainSectionButtonGroup>
|
||||||
{(isOnlyOnePlayableFile || !viewedFileList?.length) && (
|
{(isOnlyOnePlayableFile || !viewedFileList?.length) && (
|
||||||
<a style={{ textDecoration: 'none' }} href={fullPlaylistLink}>
|
<a style={{ textDecoration: 'none' }} href={fullPlaylistLink}>
|
||||||
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
|
<Button style={{ width: '100%' }} variant='contained' color='primary' size='large'>
|
||||||
download playlist
|
{t('DownloadPlaylist')}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
<CopyToClipboard text={hash}>
|
<CopyToClipboard text={hash}>
|
||||||
<Button variant='contained' color='primary' size='large'>
|
<Button variant='contained' color='primary' size='large'>
|
||||||
copy hash
|
{t('CopyHash')}
|
||||||
</Button>
|
</Button>
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</MainSectionButtonGroup>
|
</MainSectionButtonGroup>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import axios from 'axios'
|
|||||||
import { viewedHost } from 'utils/Hosts'
|
import { viewedHost } from 'utils/Hosts'
|
||||||
import { GETTING_INFO, IN_DB } from 'torrentStates'
|
import { GETTING_INFO, IN_DB } from 'torrentStates'
|
||||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { useUpdateCache, useGetSettings } from './customHooks'
|
import { useUpdateCache, useGetSettings } from './customHooks'
|
||||||
import DialogHeader from './DialogHeader'
|
import DialogHeader from './DialogHeader'
|
||||||
@@ -37,6 +38,7 @@ const Loader = () => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isDetailedCacheView, setIsDetailedCacheView] = useState(false)
|
const [isDetailedCacheView, setIsDetailedCacheView] = useState(false)
|
||||||
const [viewedFileList, setViewedFileList] = useState()
|
const [viewedFileList, setViewedFileList] = useState()
|
||||||
@@ -99,6 +101,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
}, [hash])
|
}, [hash])
|
||||||
|
|
||||||
const bufferSize = settings?.PreloadBuffer ? Capacity : 33554432 // Default is 32mb if PreloadBuffer is false
|
const bufferSize = settings?.PreloadBuffer ? Capacity : 33554432 // Default is 32mb if PreloadBuffer is false
|
||||||
|
// const bufferSize = Capacity
|
||||||
|
|
||||||
const getTitle = value => {
|
const getTitle = value => {
|
||||||
const torrentParsedName = value && ptt.parse(value)
|
const torrentParsedName = value && ptt.parse(value)
|
||||||
@@ -115,7 +118,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
<>
|
<>
|
||||||
<DialogHeader
|
<DialogHeader
|
||||||
onClose={closeDialog}
|
onClose={closeDialog}
|
||||||
title={isDetailedCacheView ? 'Detailed Cache View' : 'Torrent Details'}
|
title={isDetailedCacheView ? t('DetailedCacheView') : t('TorrentDetails')}
|
||||||
{...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })}
|
{...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -171,10 +174,8 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
|
|
||||||
<CacheSection>
|
<CacheSection>
|
||||||
<SectionHeader>
|
<SectionHeader>
|
||||||
<SectionTitle mb={20}>Buffer</SectionTitle>
|
<SectionTitle mb={20}>{t('Buffer')}</SectionTitle>
|
||||||
{!settings?.PreloadBuffer && (
|
{!settings?.PreloadBuffer && <SectionSubName>{t('BufferNote')}</SectionSubName>}
|
||||||
<SectionSubName>Enable "Preload Buffer" in settings to change buffer size</SectionSubName>
|
|
||||||
)}
|
|
||||||
<LoadingProgress
|
<LoadingProgress
|
||||||
value={Filled}
|
value={Filled}
|
||||||
fullAmount={bufferSize}
|
fullAmount={bufferSize}
|
||||||
@@ -190,16 +191,16 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
size='large'
|
size='large'
|
||||||
onClick={() => setIsDetailedCacheView(true)}
|
onClick={() => setIsDetailedCacheView(true)}
|
||||||
>
|
>
|
||||||
Detailed cache view
|
{t('DetailedCacheView')}
|
||||||
</Button>
|
</Button>
|
||||||
</CacheSection>
|
</CacheSection>
|
||||||
|
|
||||||
<TorrentFilesSection>
|
<TorrentFilesSection>
|
||||||
<SectionTitle mb={20}>Torrent Content</SectionTitle>
|
<SectionTitle mb={20}>{t('TorrentContent')}</SectionTitle>
|
||||||
|
|
||||||
{seasonAmount?.length > 1 && (
|
{seasonAmount?.length > 1 && (
|
||||||
<>
|
<>
|
||||||
<SectionSubName mb={7}>Select Season</SectionSubName>
|
<SectionSubName mb={7}>{t('SelectSeason')}</SectionSubName>
|
||||||
<ButtonGroup style={{ marginBottom: '30px' }} color='primary'>
|
<ButtonGroup style={{ marginBottom: '30px' }} color='primary'>
|
||||||
{seasonAmount.map(season => (
|
{seasonAmount.map(season => (
|
||||||
<Button
|
<Button
|
||||||
@@ -212,7 +213,9 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
))}
|
))}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<SectionTitle mb={20}>Season {selectedSeason}</SectionTitle>
|
<SectionTitle mb={20}>
|
||||||
|
{t('Season')} {selectedSeason}
|
||||||
|
</SectionTitle>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -8,61 +8,81 @@ import {
|
|||||||
Build as BuildIcon,
|
Build as BuildIcon,
|
||||||
} from '@material-ui/icons'
|
} from '@material-ui/icons'
|
||||||
import { getPeerString, humanizeSize } from 'utils/Utils'
|
import { getPeerString, humanizeSize } from 'utils/Utils'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import StatisticsField from './StatisticsField'
|
import StatisticsField from './StatisticsField'
|
||||||
|
|
||||||
export const DownlodSpeedWidget = ({ data }) => (
|
export const DownlodSpeedWidget = ({ data }) => {
|
||||||
<StatisticsField
|
const { t } = useTranslation()
|
||||||
title='Download speed'
|
return (
|
||||||
value={humanizeSize(data) || '0 B'}
|
<StatisticsField
|
||||||
iconBg='#118f00'
|
title={t('DownloadSpeed')}
|
||||||
valueBg='#13a300'
|
value={humanizeSize(data) || '0 B'}
|
||||||
icon={ArrowDownwardIcon}
|
iconBg='#118f00'
|
||||||
/>
|
valueBg='#13a300'
|
||||||
)
|
icon={ArrowDownwardIcon}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const UploadSpeedWidget = ({ data }) => (
|
export const UploadSpeedWidget = ({ data }) => {
|
||||||
<StatisticsField
|
const { t } = useTranslation()
|
||||||
title='Upload speed'
|
return (
|
||||||
value={humanizeSize(data) || '0 B'}
|
<StatisticsField
|
||||||
iconBg='#0146ad'
|
title={t('UploadSpeed')}
|
||||||
valueBg='#0058db'
|
value={humanizeSize(data) || '0 B'}
|
||||||
icon={ArrowUpwardIcon}
|
iconBg='#0146ad'
|
||||||
/>
|
valueBg='#0058db'
|
||||||
)
|
icon={ArrowUpwardIcon}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const PeersWidget = ({ data }) => (
|
export const PeersWidget = ({ data }) => {
|
||||||
<StatisticsField
|
const { t } = useTranslation()
|
||||||
title='Peers'
|
return (
|
||||||
value={getPeerString(data) || '[0] 0 / 0'}
|
<StatisticsField
|
||||||
iconBg='#cdc118'
|
title={t('Peers')}
|
||||||
valueBg='#d8cb18'
|
value={getPeerString(data) || '[0] 0 / 0'}
|
||||||
icon={SwapVerticalCircleIcon}
|
iconBg='#cdc118'
|
||||||
/>
|
valueBg='#d8cb18'
|
||||||
)
|
icon={SwapVerticalCircleIcon}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const PiecesCountWidget = ({ data }) => (
|
export const PiecesCountWidget = ({ data }) => {
|
||||||
<StatisticsField title='Pieces count' value={data} iconBg='#b6c95e' valueBg='#c0d076' icon={WidgetsIcon} />
|
const { t } = useTranslation()
|
||||||
)
|
return <StatisticsField title={t('PiecesCount')} value={data} iconBg='#b6c95e' valueBg='#c0d076' icon={WidgetsIcon} />
|
||||||
export const PiecesLengthWidget = ({ data }) => (
|
}
|
||||||
<StatisticsField
|
|
||||||
title='Pieces length'
|
|
||||||
value={humanizeSize(data)}
|
|
||||||
iconBg='#0982c8'
|
|
||||||
valueBg='#098cd7'
|
|
||||||
icon={PhotoSizeSelectSmallIcon}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
export const StatusWidget = ({ data }) => (
|
|
||||||
<StatisticsField title='Torrent status' value={data} iconBg='#aea25b' valueBg='#b4aa6e' icon={BuildIcon} />
|
|
||||||
)
|
|
||||||
|
|
||||||
export const SizeWidget = ({ data }) => (
|
export const PiecesLengthWidget = ({ data }) => {
|
||||||
<StatisticsField
|
const { t } = useTranslation()
|
||||||
title='Torrent size'
|
return (
|
||||||
value={humanizeSize(data)}
|
<StatisticsField
|
||||||
iconBg='#9b01ad'
|
title={t('PiecesLength')}
|
||||||
valueBg='#ac03bf'
|
value={humanizeSize(data)}
|
||||||
icon={ViewAgendaIcon}
|
iconBg='#0982c8'
|
||||||
/>
|
valueBg='#098cd7'
|
||||||
)
|
icon={PhotoSizeSelectSmallIcon}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusWidget = ({ data }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return <StatisticsField title={t('TorrentStatus')} value={data} iconBg='#aea25b' valueBg='#b4aa6e' icon={BuildIcon} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SizeWidget = ({ data }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
return (
|
||||||
|
<StatisticsField
|
||||||
|
title={t('TorrentSize')}
|
||||||
|
value={humanizeSize(data)}
|
||||||
|
iconBg='#9b01ad'
|
||||||
|
valueBg='#ac03bf'
|
||||||
|
icon={ViewAgendaIcon}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,22 +6,23 @@ import DialogActions from '@material-ui/core/DialogActions'
|
|||||||
import List from '@material-ui/core/List'
|
import List from '@material-ui/core/List'
|
||||||
import ButtonGroup from '@material-ui/core/ButtonGroup'
|
import ButtonGroup from '@material-ui/core/ButtonGroup'
|
||||||
import Button from '@material-ui/core/Button'
|
import Button from '@material-ui/core/Button'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const donateFrame =
|
const donateFrame =
|
||||||
'<iframe src="https://yoomoney.ru/quickpay/shop-widget?writer=seller&targets=TorrServer Donate&targets-hint=&default-sum=200&button-text=14&payment-type-choice=on&mobile-payment-type-choice=on&comment=on&hint=&successURL=&quickpay=shop&account=410013733697114" width="100%" height="302" frameborder="0" allowtransparency="true" scrolling="no"></iframe>'
|
'<iframe src="https://yoomoney.ru/quickpay/shop-widget?writer=seller&targets=TorrServer Donate&targets-hint=&default-sum=200&button-text=14&payment-type-choice=on&mobile-payment-type-choice=on&comment=on&hint=&successURL=&quickpay=shop&account=410013733697114" width="320" height="320" frameborder="0" allowtransparency="true" scrolling="no"></iframe>'
|
||||||
|
|
||||||
export default function DonateDialog({ onClose }) {
|
export default function DonateDialog({ onClose }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open onClose={onClose} aria-labelledby='form-dialog-title' fullWidth>
|
<Dialog open onClose={onClose} aria-labelledby='form-dialog-title' fullWidth>
|
||||||
<DialogTitle id='form-dialog-title'>Donate</DialogTitle>
|
<DialogTitle id='form-dialog-title'>{t('Donate')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<List>
|
<List>
|
||||||
<ListItem key='DonateLinks'>
|
<ListItem key='DonateLinks'>
|
||||||
<ButtonGroup variant='outlined' color='primary' aria-label='contained primary button group'>
|
<ButtonGroup variant='outlined' color='primary' aria-label='contained primary button group'>
|
||||||
<Button onClick={() => window.open('https://www.paypal.com/paypalme/yourok', '_blank')}>PayPal</Button>
|
<Button onClick={() => window.open('https://www.paypal.com/paypalme/yourok', '_blank')}>PayPal</Button>
|
||||||
<Button onClick={() => window.open('https://yoomoney.ru/to/410013733697114', '_blank')}>
|
<Button onClick={() => window.open('https://yoomoney.ru/to/410013733697114', '_blank')}>IO.Money</Button>
|
||||||
Yandex.Money
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<ListItem key='DonateForm'>
|
<ListItem key='DonateForm'>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ListItemText from '@material-ui/core/ListItemText'
|
|||||||
import DeleteIcon from '@material-ui/icons/Delete'
|
import DeleteIcon from '@material-ui/icons/Delete'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { torrentsHost } from 'utils/Hosts'
|
import { torrentsHost } from 'utils/Hosts'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const fnRemoveAll = () => {
|
const fnRemoveAll = () => {
|
||||||
fetch(torrentsHost(), {
|
fetch(torrentsHost(), {
|
||||||
@@ -31,25 +32,25 @@ const fnRemoveAll = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RemoveAll() {
|
export default function RemoveAll() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const closeDialog = () => setOpen(false)
|
const closeDialog = () => setOpen(false)
|
||||||
const openDialog = () => setOpen(true)
|
const openDialog = () => setOpen(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem button key='Remove all' onClick={openDialog}>
|
<ListItem button key={t('RemoveAll')} onClick={openDialog}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
||||||
<ListItemText primary='Remove all' />
|
<ListItemText primary={t('RemoveAll')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Dialog open={open} onClose={closeDialog}>
|
<Dialog open={open} onClose={closeDialog}>
|
||||||
<DialogTitle>Delete Torrent?</DialogTitle>
|
<DialogTitle>{t('DeleteTorrents?')}</DialogTitle>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant='outlined' onClick={closeDialog} color='primary'>
|
<Button variant='outlined' onClick={closeDialog} color='primary'>
|
||||||
Cancel
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -61,7 +62,7 @@ export default function RemoveAll() {
|
|||||||
color='primary'
|
color='primary'
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
Ok
|
{t('OK')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import axios from 'axios'
|
||||||
import ListItem from '@material-ui/core/ListItem'
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
import ListItemText from '@material-ui/core/ListItemText'
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
@@ -11,9 +12,10 @@ import DialogActions from '@material-ui/core/DialogActions'
|
|||||||
import Button from '@material-ui/core/Button'
|
import Button from '@material-ui/core/Button'
|
||||||
import { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'
|
import { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'
|
||||||
import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts'
|
import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts'
|
||||||
import axios from 'axios'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function SettingsDialog() {
|
export default function SettingsDialog() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [settings, setSets] = useState({})
|
const [settings, setSets] = useState({})
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
@@ -49,7 +51,16 @@ export default function SettingsDialog() {
|
|||||||
if (type === 'number' || type === 'select-one') {
|
if (type === 'number' || type === 'select-one') {
|
||||||
sets[id] = Number(value)
|
sets[id] = Number(value)
|
||||||
} else if (type === 'checkbox') {
|
} else if (type === 'checkbox') {
|
||||||
sets[id] = Boolean(checked)
|
if (
|
||||||
|
id === 'DisableTCP' ||
|
||||||
|
id === 'DisableUTP' ||
|
||||||
|
id === 'DisableUPNP' ||
|
||||||
|
id === 'DisableDHT' ||
|
||||||
|
id === 'DisablePEX' ||
|
||||||
|
id === 'DisableUpload'
|
||||||
|
)
|
||||||
|
sets[id] = Boolean(!checked)
|
||||||
|
else sets[id] = Boolean(checked)
|
||||||
} else if (type === 'url') {
|
} else if (type === 'url') {
|
||||||
sets[id] = value
|
sets[id] = value
|
||||||
}
|
}
|
||||||
@@ -82,21 +93,21 @@ export default function SettingsDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ListItem button key='Settings' onClick={handleClickOpen}>
|
<ListItem button key={t('Settings')} onClick={handleClickOpen}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary='Settings' />
|
<ListItemText primary={t('Settings')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<Dialog open={open} onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
|
<Dialog open={open} onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
|
||||||
<DialogTitle id='form-dialog-title'>Settings</DialogTitle>
|
<DialogTitle id='form-dialog-title'>{t('Settings')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
onChange={onInputHost}
|
onChange={onInputHost}
|
||||||
margin='dense'
|
margin='dense'
|
||||||
id='TorrServerHost'
|
id='TorrServerHost'
|
||||||
label='Host'
|
label={t('Host')}
|
||||||
value={tsHost}
|
value={tsHost}
|
||||||
type='url'
|
type='url'
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -107,162 +118,170 @@ export default function SettingsDialog() {
|
|||||||
onChange={inputForm}
|
onChange={inputForm}
|
||||||
margin='dense'
|
margin='dense'
|
||||||
id='CacheSize'
|
id='CacheSize'
|
||||||
label='Cache size'
|
label={t('CacheSize')}
|
||||||
value={CacheSize}
|
value={CacheSize}
|
||||||
type='number'
|
type='number'
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<br />
|
||||||
control={<Switch checked={PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />}
|
|
||||||
label='Preload buffer'
|
|
||||||
/>
|
|
||||||
<TextField
|
<TextField
|
||||||
onChange={inputForm}
|
onChange={inputForm}
|
||||||
margin='dense'
|
margin='dense'
|
||||||
id='ReaderReadAHead'
|
id='ReaderReadAHead'
|
||||||
label='Reader readahead'
|
label={t('ReaderReadAHead')}
|
||||||
value={ReaderReadAHead}
|
value={ReaderReadAHead}
|
||||||
type='number'
|
type='number'
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
|
||||||
<InputLabel htmlFor='RetrackersMode'>Retracker mode</InputLabel>
|
|
||||||
<Select onChange={inputForm} type='number' native id='RetrackersMode' value={RetrackersMode}>
|
|
||||||
<option value={0}>Don't add retrackers</option>
|
|
||||||
<option value={1}>Add retrackers</option>
|
|
||||||
<option value={2}>Remove retrackers</option>
|
|
||||||
<option value={3}>Replace retrackers</option>
|
|
||||||
</Select>
|
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='TorrentDisconnectTimeout'
|
|
||||||
label='Torrent disconnect timeout'
|
|
||||||
value={TorrentDisconnectTimeout}
|
|
||||||
type='number'
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Switch checked={EnableIPv6} onChange={inputForm} id='EnableIPv6' color='primary' />}
|
control={<Switch checked={PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />}
|
||||||
label='Enable IPv6'
|
label={t('PreloadBuffer')}
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={ForceEncrypt} onChange={inputForm} id='ForceEncrypt' color='primary' />}
|
|
||||||
label='Force encrypt'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={DisableTCP} onChange={inputForm} id='DisableTCP' color='primary' />}
|
|
||||||
label='Disable TCP'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={DisableUTP} onChange={inputForm} id='DisableUTP' color='primary' />}
|
|
||||||
label='Disable UTP'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={DisableUPNP} onChange={inputForm} id='DisableUPNP' color='primary' />}
|
|
||||||
label='Disable UPNP'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={DisableDHT} onChange={inputForm} id='DisableDHT' color='primary' />}
|
|
||||||
label='Disable DHT'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={DisablePEX} onChange={inputForm} id='DisablePEX' color='primary' />}
|
|
||||||
label='Disable PEX'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormControlLabel
|
|
||||||
control={<Switch checked={DisableUpload} onChange={inputForm} id='DisableUpload' color='primary' />}
|
|
||||||
label='Disable upload'
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='DownloadRateLimit'
|
|
||||||
label='Download rate limit'
|
|
||||||
value={DownloadRateLimit}
|
|
||||||
type='number'
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='UploadRateLimit'
|
|
||||||
label='Upload rate limit'
|
|
||||||
value={UploadRateLimit}
|
|
||||||
type='number'
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='ConnectionsLimit'
|
|
||||||
label='Connections limit'
|
|
||||||
value={ConnectionsLimit}
|
|
||||||
type='number'
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='DhtConnectionLimit'
|
|
||||||
label='Dht connection limit'
|
|
||||||
value={DhtConnectionLimit}
|
|
||||||
type='number'
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
onChange={inputForm}
|
|
||||||
margin='dense'
|
|
||||||
id='PeersListenPort'
|
|
||||||
label='Peers listen port'
|
|
||||||
value={PeersListenPort}
|
|
||||||
type='number'
|
|
||||||
fullWidth
|
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Switch checked={UseDisk} onChange={inputForm} id='UseDisk' color='primary' />}
|
control={<Switch checked={UseDisk} onChange={inputForm} id='UseDisk' color='primary' />}
|
||||||
label='Use disk'
|
label={t('UseDisk')}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Switch checked={RemoveCacheOnDrop} onChange={inputForm} id='RemoveCacheOnDrop' color='primary' />
|
<Switch checked={RemoveCacheOnDrop} onChange={inputForm} id='RemoveCacheOnDrop' color='primary' />
|
||||||
}
|
}
|
||||||
label='Remove cache from disk on drop torrent'
|
label={t('RemoveCacheOnDrop')}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<small>If disabled, remove cache on delete torrent</small>
|
<small>{t('RemoveCacheOnDropDesc')}</small>
|
||||||
<br />
|
<br />
|
||||||
<TextField
|
<TextField
|
||||||
onChange={inputForm}
|
onChange={inputForm}
|
||||||
margin='dense'
|
margin='dense'
|
||||||
id='TorrentsSavePath'
|
id='TorrentsSavePath'
|
||||||
label='Torrents save path'
|
label={t('TorrentsSavePath')}
|
||||||
value={TorrentsSavePath}
|
value={TorrentsSavePath}
|
||||||
type='url'
|
type='url'
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={EnableIPv6} onChange={inputForm} id='EnableIPv6' color='primary' />}
|
||||||
|
label={t('EnableIPv6')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={!DisableTCP} onChange={inputForm} id='DisableTCP' color='primary' />}
|
||||||
|
label={t('TCP')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={!DisableUTP} onChange={inputForm} id='DisableUTP' color='primary' />}
|
||||||
|
label={t('UTP')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={!DisablePEX} onChange={inputForm} id='DisablePEX' color='primary' />}
|
||||||
|
label={t('PEX')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={ForceEncrypt} onChange={inputForm} id='ForceEncrypt' color='primary' />}
|
||||||
|
label={t('ForceEncrypt')}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextField
|
||||||
|
onChange={inputForm}
|
||||||
|
margin='dense'
|
||||||
|
id='TorrentDisconnectTimeout'
|
||||||
|
label={t('TorrentDisconnectTimeout')}
|
||||||
|
value={TorrentDisconnectTimeout}
|
||||||
|
type='number'
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<TextField
|
||||||
|
onChange={inputForm}
|
||||||
|
margin='dense'
|
||||||
|
id='ConnectionsLimit'
|
||||||
|
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 />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} color='primary' variant='outlined'>
|
<Button onClick={handleClose} color='primary' variant='outlined'>
|
||||||
Cancel
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button onClick={handleSave} color='primary' variant='outlined'>
|
<Button onClick={handleSave} color='primary' variant='outlined'>
|
||||||
Save
|
{t('Save')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import Slide from '@material-ui/core/Slide'
|
|||||||
import { Button, DialogActions, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core'
|
import { Button, DialogActions, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import ptt from 'parse-torrent-title'
|
import ptt from 'parse-torrent-title'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardPoster } from './style'
|
import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardPoster } from './style'
|
||||||
|
|
||||||
const Transition = forwardRef((props, ref) => <Slide direction='up' ref={ref} {...props} />)
|
const Transition = forwardRef((props, ref) => <Slide direction='up' ref={ref} {...props} />)
|
||||||
|
|
||||||
export default function Torrent({ torrent }) {
|
export default function Torrent({ torrent }) {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [isDetailedInfoOpened, setIsDetailedInfoOpened] = useState(false)
|
const [isDetailedInfoOpened, setIsDetailedInfoOpened] = useState(false)
|
||||||
const [isDeleteTorrentOpened, setIsDeleteTorrentOpened] = useState(false)
|
const [isDeleteTorrentOpened, setIsDeleteTorrentOpened] = useState(false)
|
||||||
|
|
||||||
@@ -44,41 +46,41 @@ export default function Torrent({ torrent }) {
|
|||||||
<TorrentCardButtons>
|
<TorrentCardButtons>
|
||||||
<StyledButton onClick={openDetailedInfo}>
|
<StyledButton onClick={openDetailedInfo}>
|
||||||
<UnfoldMoreIcon />
|
<UnfoldMoreIcon />
|
||||||
<span>Details</span>
|
<span>{t('Details')}</span>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
|
||||||
<StyledButton onClick={() => dropTorrent(torrent)}>
|
<StyledButton onClick={() => dropTorrent(torrent)}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
<span>Drop</span>
|
<span>{t('Drop')}</span>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
|
||||||
<StyledButton onClick={openDeleteTorrentAlert}>
|
<StyledButton onClick={openDeleteTorrentAlert}>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
<span>Delete</span>
|
<span>{t('Delete')}</span>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</TorrentCardButtons>
|
</TorrentCardButtons>
|
||||||
|
|
||||||
<TorrentCardDescription>
|
<TorrentCardDescription>
|
||||||
<div className='description-title-wrapper'>
|
<div className='description-title-wrapper'>
|
||||||
<div className='description-section-name'>Name</div>
|
<div className='description-section-name'>{t('Name')}</div>
|
||||||
<div className='description-torrent-title'>{shortenText(parsedTitle, 100)}</div>
|
<div className='description-torrent-title'>{shortenText(parsedTitle, 100)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='description-statistics-wrapper'>
|
<div className='description-statistics-wrapper'>
|
||||||
<div className='description-statistics-element-wrapper'>
|
<div className='description-statistics-element-wrapper'>
|
||||||
<div className='description-section-name'>Size</div>
|
<div className='description-section-name'>{t('Size')}</div>
|
||||||
<div className='description-statistics-element-value'>{torrentSize > 0 && humanizeSize(torrentSize)}</div>
|
<div className='description-statistics-element-value'>{torrentSize > 0 && humanizeSize(torrentSize)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='description-statistics-element-wrapper'>
|
<div className='description-statistics-element-wrapper'>
|
||||||
<div className='description-section-name'>Speed</div>
|
<div className='description-section-name'>{t('Speed')}</div>
|
||||||
<div className='description-statistics-element-value'>
|
<div className='description-statistics-element-value'>
|
||||||
{downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'}
|
{downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='description-statistics-element-wrapper'>
|
<div className='description-statistics-element-wrapper'>
|
||||||
<div className='description-section-name'>Peers</div>
|
<div className='description-section-name'>{t('Peers')}</div>
|
||||||
<div className='description-statistics-element-value'>{getPeerString(torrent) || '---'}</div>
|
<div className='description-statistics-element-value'>{getPeerString(torrent) || '---'}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,10 +99,10 @@ export default function Torrent({ torrent }) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={isDeleteTorrentOpened} onClose={closeDeleteTorrentAlert}>
|
<Dialog open={isDeleteTorrentOpened} onClose={closeDeleteTorrentAlert}>
|
||||||
<DialogTitle>Delete Torrent?</DialogTitle>
|
<DialogTitle>{t('DeleteTorrent?')}</DialogTitle>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant='outlined' onClick={closeDeleteTorrentAlert} color='primary'>
|
<Button variant='outlined' onClick={closeDeleteTorrentAlert} color='primary'>
|
||||||
Cancel
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -112,7 +114,7 @@ export default function Torrent({ torrent }) {
|
|||||||
color='primary'
|
color='primary'
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
Ok
|
{t('OK')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const TorrentCard = styled.div`
|
|||||||
grid-template-areas: 'poster description buttons';
|
grid-template-areas: 'poster description buttons';
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #3fb57a;
|
background: #00a572;
|
||||||
box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
|
box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
|
||||||
|
|
||||||
@media (max-width: 1260px), (max-height: 500px) {
|
@media (max-width: 1260px), (max-height: 500px) {
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import TorrentCard from 'components/TorrentCard'
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||||
import { TorrentListWrapper, CenteredGrid } from 'App/style'
|
import { TorrentListWrapper, CenteredGrid } from 'App/style'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function TorrentList() {
|
export default function TorrentList() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const [torrents, setTorrents] = useState([])
|
const [torrents, setTorrents] = useState([])
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isOffline, setIsOffline] = useState(true)
|
const [isOffline, setIsOffline] = useState(true)
|
||||||
@@ -39,9 +41,9 @@ export default function TorrentList() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
) : isOffline ? (
|
) : isOffline ? (
|
||||||
<Typography>Offline</Typography>
|
<Typography>{t('Offline')}</Typography>
|
||||||
) : (
|
) : (
|
||||||
!torrents.length && <Typography>No torrents added</Typography>
|
!torrents.length && <Typography>{t('NoTorrentsAdded')}</Typography>
|
||||||
)}
|
)}
|
||||||
</CenteredGrid>
|
</CenteredGrid>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import ListItem from '@material-ui/core/ListItem'
|
|||||||
import PublishIcon from '@material-ui/icons/Publish'
|
import PublishIcon from '@material-ui/icons/Publish'
|
||||||
import { torrentUploadHost } from 'utils/Hosts'
|
import { torrentUploadHost } from 'utils/Hosts'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function UploadDialog() {
|
export default function UploadDialog() {
|
||||||
|
const { t } = useTranslation()
|
||||||
const handleCapture = ({ target: { files } }) => {
|
const handleCapture = ({ target: { files } }) => {
|
||||||
const [file] = files
|
const [file] = files
|
||||||
const data = new FormData()
|
const data = new FormData()
|
||||||
@@ -13,18 +15,17 @@ export default function UploadDialog() {
|
|||||||
data.append('file', file)
|
data.append('file', file)
|
||||||
axios.post(torrentUploadHost(), data)
|
axios.post(torrentUploadHost(), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor='raised-button-file'>
|
<label htmlFor='raised-button-file'>
|
||||||
<input onChange={handleCapture} accept='*/*' type='file' style={{ display: 'none' }} id='raised-button-file' />
|
<input onChange={handleCapture} accept='*/*' type='file' style={{ display: 'none' }} id='raised-button-file' />
|
||||||
|
|
||||||
<ListItem button variant='raised' type='submit' component='span' key='Upload file'>
|
<ListItem button variant='raised' type='submit' component='span' key={t('UploadFile')}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<PublishIcon />
|
<PublishIcon />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
||||||
<ListItemText primary='Upload file' />
|
<ListItemText primary={t('UploadFile')} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,40 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
|
import { I18nextProvider } from 'react-i18next'
|
||||||
|
import i18n from 'i18next'
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector'
|
||||||
|
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
|
i18n.use(LanguageDetector).init({
|
||||||
|
lng: 'en', // default
|
||||||
|
fallbackLng: 'en', // use en if detected lng is not available
|
||||||
|
keySeparator: false, // we do not use keys in form messages.welcome
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // react already safes from xss
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
en: {
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
translations: require('./locales/en/translation.json'),
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
translations: require('./locales/ru/translation.json'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ns: ['translations'],
|
||||||
|
defaultNS: 'translations',
|
||||||
|
})
|
||||||
|
|
||||||
|
i18n.languages = ['en', 'ru']
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<I18nextProvider i18n={i18n}>
|
||||||
|
<App />
|
||||||
|
</I18nextProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
)
|
)
|
||||||
|
|||||||
89
web/src/locales/en/translation.json
Normal file
89
web/src/locales/en/translation.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"About": "About",
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Add": "Add",
|
||||||
|
"AddFromLink": "Add from Link",
|
||||||
|
"AddMagnetOrLink": "Add magnet or link to torrent file",
|
||||||
|
"AddRetrackers": "Add retrackers",
|
||||||
|
"Buffer": "Preload Buffer / Cache",
|
||||||
|
"BufferNote": "Enable "Preload Buffer" in settings to see cache loading progress",
|
||||||
|
"CacheSize": "Cache Size (Megabytes)",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Close": "Close",
|
||||||
|
"CloseServer": "Close Server",
|
||||||
|
"CloseServer?": "Close server?",
|
||||||
|
"ConnectionsLimit": "Connections Limit",
|
||||||
|
"CopyHash": "Copy Hash",
|
||||||
|
"CopyLink": "Copy link",
|
||||||
|
"Delete": "Delete",
|
||||||
|
"DeleteTorrent?": "Delete Torrent?",
|
||||||
|
"DeleteTorrents?": "Delete All Torrents?",
|
||||||
|
"DetailedCacheView": "Detailed Cache View",
|
||||||
|
"Details": "Details",
|
||||||
|
"DHT": "DHT (Distributed Hash Table)",
|
||||||
|
"DhtConnectionLimit": "DHT Connection Limit",
|
||||||
|
"Donate": "Donate",
|
||||||
|
"DontAddRetrackers": "Don't add retrackers",
|
||||||
|
"DownloadPlaylist": "Download Playlist",
|
||||||
|
"DownloadRateLimit": "Download Rate Limit (Kilobytes)",
|
||||||
|
"DownloadSpeed": "Download speed",
|
||||||
|
"Drop": "Drop",
|
||||||
|
"DropTorrent": "Reset Torrent",
|
||||||
|
"EnableIPv6": "IPv6",
|
||||||
|
"Episode": "Episode",
|
||||||
|
"ForceEncrypt": "Force Encrypt Headers",
|
||||||
|
"FromLatestFile": "From Latest File",
|
||||||
|
"Full": "Full",
|
||||||
|
"Host": "Host",
|
||||||
|
"Info": "Info",
|
||||||
|
"LatestFilePlayed": "Latest file played:",
|
||||||
|
"MagnetOrTorrentFileLink": "Magnet or torrent file link",
|
||||||
|
"Name": "Name",
|
||||||
|
"NoTorrentsAdded": "No torrents added",
|
||||||
|
"Offline": "Offline",
|
||||||
|
"OK": "OK",
|
||||||
|
"OpenLink": "Open link",
|
||||||
|
"Peers": "Peers",
|
||||||
|
"PeersListenPort": "Peers Listen Port",
|
||||||
|
"PEX": "PEX (Peer Exchange)",
|
||||||
|
"PiecesCount": "Pieces count",
|
||||||
|
"PiecesLength": "Pieces length",
|
||||||
|
"PlaylistAll": "Playlist All",
|
||||||
|
"Poster": "Poster",
|
||||||
|
"Preload": "Preload",
|
||||||
|
"PreloadBuffer": "Preload Buffer",
|
||||||
|
"ReaderReadAHead": "Reader Read Ahead (5-100%)",
|
||||||
|
"RemoveAll": "Remove All",
|
||||||
|
"RemoveCacheOnDrop": "Remove Cache from Disk on Drop Torrent",
|
||||||
|
"RemoveCacheOnDropDesc": "If disabled, remove cache on delete torrent.",
|
||||||
|
"RemoveRetrackers": "Remove retrackers",
|
||||||
|
"RemoveViews": "Remove View States",
|
||||||
|
"ReplaceRetrackers": "Replace retrackers",
|
||||||
|
"Resolution": "Resolution",
|
||||||
|
"RetrackersMode": "Retrackers Mode",
|
||||||
|
"Save": "Save",
|
||||||
|
"Season": "Season",
|
||||||
|
"SelectSeason": "Select Season",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"Size": "Size",
|
||||||
|
"SpecialThanks": "Special Thanks:",
|
||||||
|
"Speed": "Speed",
|
||||||
|
"TCP": "TCP (Transmission Control Protocol)",
|
||||||
|
"ThanksToEveryone": "Thanks to everyone who tested and helped.",
|
||||||
|
"Title": "Title",
|
||||||
|
"TorrentContent": "Torrent Content",
|
||||||
|
"TorrentDetails": "Torrent Details",
|
||||||
|
"TorrentDisconnectTimeout": "Torrent Disconnect Timeout",
|
||||||
|
"TorrentSize": "Torrent size",
|
||||||
|
"TorrentsSavePath": "Torrents Save Path",
|
||||||
|
"TorrentState": "Torrent State",
|
||||||
|
"TorrentStatus": "Torrent Status",
|
||||||
|
"Upload": "Upload (not recommended to disable)",
|
||||||
|
"UploadFile": "Upload File",
|
||||||
|
"UploadRateLimit": "Upload Rate Limit (Kilobytes)",
|
||||||
|
"UploadSpeed": "Upload speed",
|
||||||
|
"UPNP": "UPnP (Universal Plug and Play)",
|
||||||
|
"UseDisk": "Use Disk",
|
||||||
|
"UTP": "μTP (Micro Transport Protocol)",
|
||||||
|
"Viewed": "Viewed"
|
||||||
|
}
|
||||||
89
web/src/locales/ru/translation.json
Normal file
89
web/src/locales/ru/translation.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"About": "О сервере",
|
||||||
|
"Actions": "Действия",
|
||||||
|
"Add": "Добавить",
|
||||||
|
"AddFromLink": "Добавить",
|
||||||
|
"AddMagnetOrLink": "Добавьте magnet или ссылку на торрент",
|
||||||
|
"AddRetrackers": "Добавлять",
|
||||||
|
"Buffer": "Предзагрузка / Кеш",
|
||||||
|
"BufferNote": "Включите «Наполнять кеш перед началом воспроизведения» в настройках для показа заполнения кеша",
|
||||||
|
"CacheSize": "Размер кеша (Мегабайты)",
|
||||||
|
"Cancel": "Отмена",
|
||||||
|
"Close": "Закрыть",
|
||||||
|
"CloseServer": "Выкл. сервер",
|
||||||
|
"CloseServer?": "Остановить сервер?",
|
||||||
|
"ConnectionsLimit": "Торрент-соединения (рек. 20-25)",
|
||||||
|
"CopyHash": "Скопировать хеш",
|
||||||
|
"CopyLink": "Копировать",
|
||||||
|
"Delete": "Удалить",
|
||||||
|
"DeleteTorrent?": "Удалить торрент?",
|
||||||
|
"DeleteTorrents?": "Удалить все торренты?",
|
||||||
|
"DetailedCacheView": "Информация о заполнении кеша",
|
||||||
|
"Details": "Инфо",
|
||||||
|
"DHT": "DHT (Distributed Hash Table)",
|
||||||
|
"DhtConnectionLimit": "Лимит подключений DHT",
|
||||||
|
"Donate": "Поддержка",
|
||||||
|
"DontAddRetrackers": "Ничего не делать",
|
||||||
|
"DownloadPlaylist": "Скачать плейлист",
|
||||||
|
"DownloadRateLimit": "Ограничение скорости загрузки (Килобайты)",
|
||||||
|
"DownloadSpeed": "Скорость загрузки",
|
||||||
|
"Drop": "Сброс",
|
||||||
|
"DropTorrent": "Сбросить торрент",
|
||||||
|
"EnableIPv6": "IPv6",
|
||||||
|
"Episode": "Серия",
|
||||||
|
"ForceEncrypt": "Принудительное шифрование заголовков",
|
||||||
|
"FromLatestFile": "C последнего файла",
|
||||||
|
"Full": "Полный",
|
||||||
|
"Host": "Хост",
|
||||||
|
"Info": "Инфо",
|
||||||
|
"LatestFilePlayed": "Последний воспроизведенный файл:",
|
||||||
|
"MagnetOrTorrentFileLink": "Ссылка на файл торрента или magnet-ссылка",
|
||||||
|
"Name": "Название",
|
||||||
|
"NoTorrentsAdded": "Нет торрентов",
|
||||||
|
"Offline": "Сервер не доступен",
|
||||||
|
"OK": "OK",
|
||||||
|
"OpenLink": "Открыть",
|
||||||
|
"Peers": "Подкл./Пиры",
|
||||||
|
"PeersListenPort": "Порт для входящих подключений",
|
||||||
|
"PEX": "PEX (Peer Exchange)",
|
||||||
|
"PiecesCount": "Кол-во блоков",
|
||||||
|
"PiecesLength": "Размер блока",
|
||||||
|
"PlaylistAll": "Плейлист всех",
|
||||||
|
"Poster": "Постер",
|
||||||
|
"Preload": "Предзагр.",
|
||||||
|
"PreloadBuffer": "Наполнять кеш перед началом воспроизведения",
|
||||||
|
"ReaderReadAHead": "Кеш предзагрузки (5-100%, рек. 95%)",
|
||||||
|
"RemoveAll": "Удалить все",
|
||||||
|
"RemoveCacheOnDrop": "Очищать кеш на диске при отключении торрента",
|
||||||
|
"RemoveCacheOnDropDesc": "Если отключено, кэш очищается при удалении торрента.",
|
||||||
|
"RemoveRetrackers": "Удалять",
|
||||||
|
"RemoveViews": "Очистить просмотры",
|
||||||
|
"ReplaceRetrackers": "Заменять",
|
||||||
|
"Resolution": "Разреш.",
|
||||||
|
"RetrackersMode": "Ретрекеры",
|
||||||
|
"Save": "Сохранить",
|
||||||
|
"Season": "Сезон",
|
||||||
|
"SelectSeason": "Выбор сезона",
|
||||||
|
"Settings": "Настройки",
|
||||||
|
"Size": "Размер",
|
||||||
|
"SpecialThanks": "Отдельное спасибо:",
|
||||||
|
"Speed": "Скорость",
|
||||||
|
"TCP": "TCP (Transmission Control Protocol)",
|
||||||
|
"ThanksToEveryone": "Спасибо всем, кто тестировал и помогал!",
|
||||||
|
"Title": "Название",
|
||||||
|
"TorrentContent": "Содержимое торрента",
|
||||||
|
"TorrentDetails": "Информация о торренте",
|
||||||
|
"TorrentDisconnectTimeout": "Тайм-аут отключения торрента (секунды)",
|
||||||
|
"TorrentSize": "Размер торрента",
|
||||||
|
"TorrentsSavePath": "Путь хранения кеша",
|
||||||
|
"TorrentState": "Данные торрента",
|
||||||
|
"TorrentStatus": "Состояние",
|
||||||
|
"Upload": "Отдача (не рекомендуется отключать)",
|
||||||
|
"UploadFile": "Загрузить файл",
|
||||||
|
"UploadRateLimit": "Ограничение скорости отдачи (Килобайты)",
|
||||||
|
"UploadSpeed": "Скорость отдачи",
|
||||||
|
"UPNP": "UPnP (Universal Plug and Play)",
|
||||||
|
"UseDisk": "Использовать кеш на диске",
|
||||||
|
"UTP": "μTP (Micro Transport Protocol)",
|
||||||
|
"Viewed": "Просм."
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user