mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 21:46:11 +05:00
fix web
This commit is contained in:
@@ -24,7 +24,6 @@ func ParseFile(file multipart.File) (*torrent.TorrentSpec, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
|
||||
mag := minfo.Magnet(nil, &info)
|
||||
return &torrent.TorrentSpec{
|
||||
InfoBytes: minfo.InfoBytes,
|
||||
@@ -106,7 +105,6 @@ func fromHttp(link string) (*torrent.TorrentSpec, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
|
||||
mag := minfo.Magnet(nil, &info)
|
||||
|
||||
return &torrent.TorrentSpec{
|
||||
@@ -130,7 +128,6 @@ func fromFile(path string) (*torrent.TorrentSpec, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// mag := minfo.Magnet(info.Name, minfo.HashInfoBytes())
|
||||
mag := minfo.Magnet(nil, &info)
|
||||
return &torrent.TorrentSpec{
|
||||
InfoBytes: minfo.InfoBytes,
|
||||
|
||||
@@ -28,7 +28,6 @@ func getTorrents(c *gin.Context) {
|
||||
mi := metainfo.MetaInfo{
|
||||
AnnounceList: ts.Trackers,
|
||||
}
|
||||
// mag := mi.Magnet(ts.DisplayName, ts.InfoHash)
|
||||
mag := mi.Magnet(&ts.InfoHash, &metainfo.Info{Name: ts.DisplayName})
|
||||
http += "<p><a href='" + mag.String() + "'>magnet:?xt=urn:btih:" + mag.InfoHash.HexString() + "</a></p>"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png"><link rel="manifest" href="/site.webmanifest"><meta name="msapplication-TileColor" content="#da532c"><meta name="theme-color" content="#ffffff"><link rel="preconnect" href="https://fonts.gstatic.com"><link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600&display=swap" rel="stylesheet"><meta name="viewport" content="width=device-width,shrink-to-fit=no"><meta name="description" content="TorrServer - torrent to http stream"/><title>TorrServer</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://cdn.lordicon.com/libs/frhvbuzj/lord-icon-2.0.2.js"></script><script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-app.js"></script><script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-analytics.js"></script><script>const firebaseConfig={apiKey:"AIzaSyDivIsadtzAmp3SIY4yArNcFugUmr63rvo",authDomain:"torrserve.firebaseapp.com",databaseURL:"https://torrserve.firebaseio.com",projectId:"torrserve",storageBucket:"torrserve.appspot.com",messagingSenderId:"400168070412",appId:"1:400168070412:web:82c8e43dd7fc8f807aed29",measurementId:"G-T4RC2BFRSF"};firebase.initializeApp(firebaseConfig),firebase.analytics()</script><script>!function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++){var i=t[f];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var f=this.webpackJsonptorrserver_web=this.webpackJsonptorrserver_web||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var a=0;a<f.length;a++)r(f[a]);var p=i;t()}([])</script><script src="/static/js/2.9044ecaa.chunk.js"></script><script src="/static/js/main.2dd9f580.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png"><link rel="manifest" href="/site.webmanifest"><meta name="msapplication-TileColor" content="#da532c"><meta name="theme-color" content="#ffffff"><link rel="preconnect" href="https://fonts.gstatic.com"><link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600&display=swap" rel="stylesheet"><meta name="viewport" content="width=device-width,shrink-to-fit=no"><meta name="description" content="TorrServer - torrent to http stream"/><title>TorrServer</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://cdn.lordicon.com/libs/frhvbuzj/lord-icon-2.0.2.js"></script><script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-app.js"></script><script src="https://www.gstatic.com/firebasejs/8.1.2/firebase-analytics.js"></script><script>const firebaseConfig={apiKey:"AIzaSyDivIsadtzAmp3SIY4yArNcFugUmr63rvo",authDomain:"torrserve.firebaseapp.com",databaseURL:"https://torrserve.firebaseio.com",projectId:"torrserve",storageBucket:"torrserve.appspot.com",messagingSenderId:"400168070412",appId:"1:400168070412:web:82c8e43dd7fc8f807aed29",measurementId:"G-T4RC2BFRSF"};firebase.initializeApp(firebaseConfig),firebase.analytics()</script><script>!function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++){var i=t[f];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var f=this.webpackJsonptorrserver_web=this.webpackJsonptorrserver_web||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var a=0;a<f.length;a++)r(f[a]);var p=i;t()}([])</script><script src="/static/js/2.9044ecaa.chunk.js"></script><script src="/static/js/main.2dd9f580.chunk.js"></script></body></html>
|
||||
@@ -9,6 +9,51 @@ func RouteWebPages(route *gin.RouterGroup) {
|
||||
c.Data(200, "text/html; charset=utf-8", Indexhtml)
|
||||
})
|
||||
|
||||
route.GET("/favicon-16x16.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Favicon16x16png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/main.2dd9f580.chunk.js.map", func(c *gin.Context) {
|
||||
c.Data(200, "application/json", Staticjsmain2dd9f580chunkjsmap)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/android-chrome-512x512.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Androidchrome512x512png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/apple-touch-icon.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Appletouchiconpng)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/index.html", func(c *gin.Context) {
|
||||
c.Data(200, "text/html; charset=utf-8", Indexhtml)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/main.2dd9f580.chunk.js", func(c *gin.Context) {
|
||||
c.Data(200, "application/javascript", Staticjsmain2dd9f580chunkjs)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/runtime-main.8bda5920.js", func(c *gin.Context) {
|
||||
c.Data(200, "application/javascript", Staticjsruntimemain8bda5920js)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/runtime-main.8bda5920.js.map", func(c *gin.Context) {
|
||||
c.Data(200, "application/json", Staticjsruntimemain8bda5920jsmap)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/android-chrome-192x192.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Androidchrome192x192png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/asset-manifest.json", func(c *gin.Context) {
|
||||
c.Data(200, "application/json", Assetmanifestjson)
|
||||
})
|
||||
@@ -19,71 +64,11 @@ func RouteWebPages(route *gin.RouterGroup) {
|
||||
})
|
||||
|
||||
|
||||
route.GET("/mstile-150x150.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Mstile150x150png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/2.9044ecaa.chunk.js", func(c *gin.Context) {
|
||||
c.Data(200, "application/javascript", Staticjs29044ecaachunkjs)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/android-chrome-512x512.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Androidchrome512x512png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/favicon-16x16.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Favicon16x16png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/index.html", func(c *gin.Context) {
|
||||
c.Data(200, "text/html; charset=utf-8", Indexhtml)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/runtime-main.8bda5920.js", func(c *gin.Context) {
|
||||
c.Data(200, "application/javascript", Staticjsruntimemain8bda5920js)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/android-chrome-192x192.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Androidchrome192x192png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/favicon-32x32.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Favicon32x32png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Data(200, "image/x-icon", Faviconico)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/main.2dd9f580.chunk.js", func(c *gin.Context) {
|
||||
c.Data(200, "application/javascript", Staticjsmain2dd9f580chunkjs)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/runtime-main.8bda5920.js.map", func(c *gin.Context) {
|
||||
c.Data(200, "application/json", Staticjsruntimemain8bda5920jsmap)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/apple-touch-icon.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Appletouchiconpng)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/site.webmanifest", func(c *gin.Context) {
|
||||
c.Data(200, "application/manifest+json", Sitewebmanifest)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/2.9044ecaa.chunk.js.LICENSE.txt", func(c *gin.Context) {
|
||||
c.Data(200, "text/plain; charset=utf-8", Staticjs29044ecaachunkjsLICENSEtxt)
|
||||
})
|
||||
@@ -94,8 +79,23 @@ func RouteWebPages(route *gin.RouterGroup) {
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/main.2dd9f580.chunk.js.map", func(c *gin.Context) {
|
||||
c.Data(200, "application/json", Staticjsmain2dd9f580chunkjsmap)
|
||||
route.GET("/favicon.ico", func(c *gin.Context) {
|
||||
c.Data(200, "image/x-icon", Faviconico)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/mstile-150x150.png", func(c *gin.Context) {
|
||||
c.Data(200, "image/png", Mstile150x150png)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/site.webmanifest", func(c *gin.Context) {
|
||||
c.Data(200, "application/manifest+json", Sitewebmanifest)
|
||||
})
|
||||
|
||||
|
||||
route.GET("/static/js/2.9044ecaa.chunk.js", func(c *gin.Context) {
|
||||
c.Data(200, "application/javascript", Staticjs29044ecaachunkjs)
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
REACT_APP_SERVER_HOST=http://127.0.0.1:8090
|
||||
@@ -1,2 +1,2 @@
|
||||
REACT_APP_SERVER_HOST=
|
||||
REACT_APP_TMDB_API_KEY=
|
||||
REACT_APP_TMDB_API_KEY=
|
||||
@@ -1,65 +0,0 @@
|
||||
import { playlistAllHost } from 'utils/Hosts'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import AddDialogButton from 'components/Add'
|
||||
import RemoveAll from 'components/RemoveAll'
|
||||
import SettingsDialog from 'components/Settings'
|
||||
import AboutDialog from 'components/About'
|
||||
import UploadDialog from 'components/Upload'
|
||||
import { CreditCard as CreditCardIcon, List as ListIcon, Language as LanguageIcon } from '@material-ui/icons'
|
||||
import List from '@material-ui/core/List'
|
||||
import CloseServer from 'components/CloseServer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useChangeLanguage from 'utils/useChangeLanguage'
|
||||
|
||||
import { AppSidebarStyle } from './style'
|
||||
|
||||
export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
|
||||
const [currentLang, changeLang] = useChangeLanguage()
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<AppSidebarStyle isDrawerOpen={isDrawerOpen}>
|
||||
<List>
|
||||
<AddDialogButton />
|
||||
<UploadDialog />
|
||||
<RemoveAll />
|
||||
<ListItem button component='a' target='_blank' href={playlistAllHost()}>
|
||||
<ListItemIcon>
|
||||
<ListIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('PlaylistAll')} />
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<Divider />
|
||||
|
||||
<List>
|
||||
<SettingsDialog />
|
||||
|
||||
<ListItem button onClick={() => (currentLang === 'en' ? changeLang('ru') : changeLang('en'))}>
|
||||
<ListItemIcon>
|
||||
<LanguageIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('ChooseLanguage')} />
|
||||
</ListItem>
|
||||
|
||||
<AboutDialog />
|
||||
<CloseServer />
|
||||
</List>
|
||||
|
||||
<Divider />
|
||||
|
||||
<List>
|
||||
<ListItem button onClick={() => setIsDonationDialogOpen(true)}>
|
||||
<ListItemIcon>
|
||||
<CreditCardIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('Donate')} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</AppSidebarStyle>
|
||||
)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||
import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { Menu as MenuIcon, Close as CloseIcon } from '@material-ui/icons'
|
||||
import { echoHost } from 'utils/Hosts'
|
||||
import TorrentList from 'components/TorrentList'
|
||||
import DonateSnackbar from 'components/Donate'
|
||||
import DonateDialog from 'components/Donate/DonateDialog'
|
||||
import Div100vh from 'react-div-100vh'
|
||||
import axios from 'axios'
|
||||
|
||||
import { AppWrapper, AppHeader } from './style'
|
||||
import Sidebar from './Sidebar'
|
||||
|
||||
const baseTheme = createMuiTheme({
|
||||
overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } },
|
||||
palette: { primary: { main: '#00a572' }, secondary: { main: '#ffa724' }, tonalOffset: 0.2 },
|
||||
})
|
||||
|
||||
export default function App() {
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
||||
const [isDonationDialogOpen, setIsDonationDialogOpen] = useState(false)
|
||||
const [torrServerVersion, setTorrServerVersion] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(echoHost()).then(({ data }) => setTorrServerVersion(data))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<MuiThemeProvider theme={baseTheme}>
|
||||
<CssBaseline />
|
||||
|
||||
{/* Div100vh - iOS WebKit fix */}
|
||||
<Div100vh>
|
||||
<AppWrapper>
|
||||
<AppHeader>
|
||||
<IconButton
|
||||
style={{ marginRight: '20px' }}
|
||||
color='inherit'
|
||||
onClick={() => setIsDrawerOpen(!isDrawerOpen)}
|
||||
edge='start'
|
||||
>
|
||||
{isDrawerOpen ? <CloseIcon /> : <MenuIcon />}
|
||||
</IconButton>
|
||||
|
||||
<Typography variant='h6' noWrap>
|
||||
TorrServer {torrServerVersion}
|
||||
</Typography>
|
||||
</AppHeader>
|
||||
|
||||
<Sidebar isDrawerOpen={isDrawerOpen} setIsDonationDialogOpen={setIsDonationDialogOpen} />
|
||||
|
||||
<TorrentList />
|
||||
|
||||
{isDonationDialogOpen && <DonateDialog onClose={() => setIsDonationDialogOpen(false)} />}
|
||||
{!JSON.parse(localStorage.getItem('snackbarIsClosed')) && <DonateSnackbar />}
|
||||
</AppWrapper>
|
||||
</Div100vh>
|
||||
</MuiThemeProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
export const AppWrapper = styled.div`
|
||||
height: 100%;
|
||||
background: #cbe8d9;
|
||||
display: grid;
|
||||
grid-template-columns: 60px 1fr;
|
||||
grid-template-rows: 60px 1fr;
|
||||
grid-template-areas:
|
||||
'head head'
|
||||
'side content';
|
||||
`
|
||||
|
||||
export const CenteredGrid = styled.div`
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
`
|
||||
|
||||
export const AppHeader = styled.div`
|
||||
background: #00a572;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
grid-area: head;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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%);
|
||||
padding: 0 24px;
|
||||
z-index: 3;
|
||||
`
|
||||
export const AppSidebarStyle = styled.div`
|
||||
${({ isDrawerOpen }) => css`
|
||||
grid-area: side;
|
||||
width: ${isDrawerOpen ? '400%' : '100%'};
|
||||
z-index: 2;
|
||||
overflow-x: hidden;
|
||||
transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
||||
background: #eee;
|
||||
white-space: nowrap;
|
||||
`}
|
||||
`
|
||||
export const TorrentListWrapper = styled.div`
|
||||
grid-area: content;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
|
||||
display: grid;
|
||||
place-content: start;
|
||||
grid-template-columns: repeat(auto-fit, minmax(max-content, 570px));
|
||||
gap: 20px;
|
||||
|
||||
@media (max-width: 1260px), (max-height: 500px) {
|
||||
padding: 10px;
|
||||
gap: 15px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
`
|
||||
@@ -1,67 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import InfoIcon from '@material-ui/icons/Info'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { echoHost } from 'utils/Hosts'
|
||||
|
||||
export default function AboutDialog() {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [torrServerVersion, setTorrServerVersion] = useState('')
|
||||
useEffect(() => {
|
||||
axios.get(echoHost()).then(({ data }) => setTorrServerVersion(data))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ListItem button key='Settings' onClick={() => setOpen(true)}>
|
||||
<ListItemIcon>
|
||||
<InfoIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('About')} />
|
||||
</ListItem>
|
||||
|
||||
<Dialog open={open} onClose={() => setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'>
|
||||
<DialogTitle id='form-dialog-title'>{t('About')}</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<center>
|
||||
<h2>TorrServer {torrServerVersion}</h2>
|
||||
<a href='https://github.com/YouROK/TorrServer'>https://github.com/YouROK/TorrServer</a>
|
||||
</center>
|
||||
<DialogContent>
|
||||
<center>
|
||||
<h2>{t('ThanksToEveryone')}</h2>
|
||||
</center>
|
||||
<br />
|
||||
<h2>{t('SpecialThanks')}</h2>
|
||||
<b>anacrolix Matt Joiner</b> <a href='https://github.com/anacrolix/'>github.com/anacrolix</a>
|
||||
<br />
|
||||
<b>nikk</b> <a href='https://github.com/tsynik'>github.com/tsynik</a>
|
||||
<br />
|
||||
<b>dancheskus</b> <a href='https://github.com/dancheskus'>github.com/dancheskus</a>
|
||||
<br />
|
||||
<b>tw1cker Руслан Пахнев</b> <a href='https://github.com/Nemiroff'>github.com/Nemiroff</a>
|
||||
<br />
|
||||
<b>SpAwN_LMG</b>
|
||||
<br />
|
||||
</DialogContent>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpen(false)} color='primary' variant='outlined' autoFocus>
|
||||
{t('Close')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import { Stage, Layer } from 'react-konva'
|
||||
import Measure from 'react-measure'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import SingleBlock from './SingleBlock'
|
||||
import getShortCacheMap from './getShortCacheMap'
|
||||
|
||||
const ScrollNotification = styled.div`
|
||||
margin-top: 10px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
align-self: center;
|
||||
`
|
||||
|
||||
export default function DefaultSnake({ isMini, cacheMap, preloadPiecesAmount }) {
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
|
||||
const [stageSettings, setStageSettings] = useState({
|
||||
boxHeight: null,
|
||||
strokeWidth: null,
|
||||
marginBetweenBlocks: null,
|
||||
stageOffset: null,
|
||||
})
|
||||
const updateStageSettings = (boxHeight, strokeWidth) => {
|
||||
setStageSettings({
|
||||
boxHeight,
|
||||
strokeWidth,
|
||||
marginBetweenBlocks: strokeWidth,
|
||||
stageOffset: strokeWidth * 2,
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// initializing stageSettings
|
||||
if (isMini) return dimensions.width < 500 ? updateStageSettings(20, 3) : updateStageSettings(24, 4)
|
||||
updateStageSettings(12, 2)
|
||||
}, [isMini, dimensions.width])
|
||||
|
||||
const miniCacheMaxHeight = 340
|
||||
|
||||
const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings
|
||||
|
||||
const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks
|
||||
const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin)
|
||||
|
||||
const shortCacheMap = isMini ? getShortCacheMap({ cacheMap, preloadPiecesAmount, piecesInOneRow }) : []
|
||||
|
||||
const amountOfRows = Math.ceil((isMini ? shortCacheMap.length : cacheMap.length) / piecesInOneRow)
|
||||
|
||||
const getItemCoordinates = blockOrder => {
|
||||
const currentRow = Math.floor(blockOrder / piecesInOneRow)
|
||||
const x = (blockOrder % piecesInOneRow) * blockSizeWithMargin || 0
|
||||
const y = currentRow * blockSizeWithMargin || 0
|
||||
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
return (
|
||||
<Measure bounds onResize={({ bounds }) => setDimensions(bounds)}>
|
||||
{({ measureRef }) => (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<DialogContent
|
||||
ref={measureRef}
|
||||
{...(isMini
|
||||
? { style: { padding: 0, maxHeight: `${miniCacheMaxHeight}px`, overflow: 'auto' } }
|
||||
: { style: { padding: 0 } })}
|
||||
>
|
||||
<Stage
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
offset={{ x: -stageOffset, y: -stageOffset }}
|
||||
width={stageOffset + blockSizeWithMargin * piecesInOneRow || 0}
|
||||
height={stageOffset + blockSizeWithMargin * amountOfRows || 0}
|
||||
>
|
||||
<Layer>
|
||||
{isMini
|
||||
? shortCacheMap.map(({ percentage, isComplete, inProgress, isActive, isReaderRange }, i) => {
|
||||
const { x, y } = getItemCoordinates(i)
|
||||
|
||||
return (
|
||||
<SingleBlock
|
||||
key={uuidv4()}
|
||||
x={x}
|
||||
y={y}
|
||||
percentage={percentage}
|
||||
inProgress={inProgress}
|
||||
isComplete={isComplete}
|
||||
isReaderRange={isReaderRange}
|
||||
isActive={isActive}
|
||||
boxHeight={boxHeight}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => {
|
||||
const { x, y } = getItemCoordinates(id)
|
||||
|
||||
return (
|
||||
<SingleBlock
|
||||
key={uuidv4()}
|
||||
x={x}
|
||||
y={y}
|
||||
percentage={percentage}
|
||||
inProgress={inProgress}
|
||||
isComplete={isComplete}
|
||||
isReaderRange={isReaderRange}
|
||||
isActive={isActive}
|
||||
boxHeight={boxHeight}
|
||||
strokeWidth={strokeWidth}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Layer>
|
||||
</Stage>
|
||||
</DialogContent>
|
||||
|
||||
{isMini &&
|
||||
(stageOffset + blockSizeWithMargin * amountOfRows || 0) >= miniCacheMaxHeight &&
|
||||
dimensions.height >= miniCacheMaxHeight && <ScrollNotification>scroll down</ScrollNotification>}
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { FixedSizeGrid as Grid } from 'react-window'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
|
||||
import { getLargeSnakeColors } from './colors'
|
||||
|
||||
const Cell = ({ columnIndex, rowIndex, style, data }) => {
|
||||
const { columnCount, cacheMap, gutterSize, borderSize, pieces } = data
|
||||
const itemIndex = rowIndex * columnCount + columnIndex
|
||||
|
||||
const { borderColor, backgroundColor } = getLargeSnakeColors(cacheMap[itemIndex] || {})
|
||||
|
||||
const newStyle = {
|
||||
...style,
|
||||
left: style.left + gutterSize,
|
||||
top: style.top + gutterSize,
|
||||
width: style.width - gutterSize,
|
||||
height: style.height - gutterSize,
|
||||
border: `${borderSize}px solid ${borderColor}`,
|
||||
display: itemIndex >= pieces ? 'none' : null,
|
||||
background: backgroundColor,
|
||||
}
|
||||
|
||||
return <div style={newStyle} />
|
||||
}
|
||||
|
||||
const gutterSize = 2
|
||||
const borderSize = 1
|
||||
const pieceSize = 12
|
||||
const pieceSizeWithSpacing = pieceSize + gutterSize
|
||||
|
||||
export default function LargeSnake({ cacheMap }) {
|
||||
const pieces = cacheMap.length
|
||||
|
||||
return (
|
||||
<div style={{ height: '60vh', overflow: 'hidden' }}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => {
|
||||
const columnCount = Math.floor(width / (gutterSize + pieceSize)) - 1
|
||||
const rowCount = pieces / columnCount + 1
|
||||
|
||||
return (
|
||||
<Grid
|
||||
columnCount={columnCount}
|
||||
rowCount={rowCount}
|
||||
columnWidth={pieceSizeWithSpacing}
|
||||
rowHeight={pieceSizeWithSpacing}
|
||||
height={height}
|
||||
width={width}
|
||||
itemData={{ columnCount, cacheMap, gutterSize, borderSize, pieces }}
|
||||
>
|
||||
{Cell}
|
||||
</Grid>
|
||||
)
|
||||
}}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Rect } from 'react-konva'
|
||||
|
||||
import { activeColor, completeColor, defaultBorderColor, progressColor, rangeColor } from './colors'
|
||||
|
||||
export default function SingleBlock({
|
||||
x,
|
||||
y,
|
||||
percentage,
|
||||
isActive = false,
|
||||
inProgress = false,
|
||||
isReaderRange = false,
|
||||
isComplete = false,
|
||||
boxHeight,
|
||||
strokeWidth,
|
||||
}) {
|
||||
const strokeColor = isActive
|
||||
? activeColor
|
||||
: isComplete
|
||||
? completeColor
|
||||
: inProgress
|
||||
? progressColor
|
||||
: isReaderRange
|
||||
? rangeColor
|
||||
: defaultBorderColor
|
||||
const backgroundColor = inProgress ? progressColor : defaultBorderColor
|
||||
const percentageProgressColor = completeColor
|
||||
const processCompletedColor = completeColor
|
||||
|
||||
return (
|
||||
<Rect
|
||||
x={x}
|
||||
y={y}
|
||||
stroke={strokeColor}
|
||||
strokeWidth={strokeWidth}
|
||||
height={boxHeight}
|
||||
width={boxHeight}
|
||||
fillAfterStrokeEnabled
|
||||
preventDefault={false}
|
||||
{...(isComplete
|
||||
? { fill: processCompletedColor }
|
||||
: inProgress && {
|
||||
fillLinearGradientStartPointY: boxHeight,
|
||||
fillLinearGradientEndPointY: 0,
|
||||
fillLinearGradientColorStops: [
|
||||
0,
|
||||
percentageProgressColor,
|
||||
percentage,
|
||||
percentageProgressColor,
|
||||
percentage,
|
||||
backgroundColor,
|
||||
],
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
export const defaultBorderColor = '#eef2f4'
|
||||
export const defaultBackgroundColor = '#fff'
|
||||
export const completeColor = '#00a572'
|
||||
export const progressColor = '#ffa724'
|
||||
export const activeColor = '#000'
|
||||
export const rangeColor = '#9a9aff'
|
||||
|
||||
export const getLargeSnakeColors = ({ isActive, isComplete, inProgress, isReaderRange, percentage }) => {
|
||||
const gradientBackgroundColor = inProgress ? progressColor : defaultBackgroundColor
|
||||
const gradient = `linear-gradient(to top, ${completeColor} 0%, ${completeColor} ${
|
||||
percentage * 100
|
||||
}%, ${gradientBackgroundColor} ${percentage * 100}%, ${gradientBackgroundColor} 100%)`
|
||||
|
||||
const borderColor = isActive
|
||||
? activeColor
|
||||
: isComplete
|
||||
? completeColor
|
||||
: inProgress
|
||||
? progressColor
|
||||
: isReaderRange
|
||||
? rangeColor
|
||||
: defaultBorderColor
|
||||
const backgroundColor = isComplete ? completeColor : inProgress ? gradient : defaultBackgroundColor
|
||||
|
||||
return { borderColor, backgroundColor }
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import {
|
||||
ArrowDownward as ArrowDownwardIcon,
|
||||
ArrowUpward as ArrowUpwardIcon,
|
||||
SwapVerticalCircle as SwapVerticalCircleIcon,
|
||||
ViewAgenda as ViewAgendaIcon,
|
||||
Widgets as WidgetsIcon,
|
||||
PhotoSizeSelectSmall as PhotoSizeSelectSmallIcon,
|
||||
Build as BuildIcon,
|
||||
} from '@material-ui/icons'
|
||||
import { getPeerString, humanizeSize } from 'utils/Utils'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import StatisticsField from './StatisticsField'
|
||||
|
||||
export const DownlodSpeedWidget = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<StatisticsField
|
||||
title={t('DownloadSpeed')}
|
||||
value={humanizeSize(data) || '0 B'}
|
||||
iconBg='#118f00'
|
||||
valueBg='#13a300'
|
||||
icon={ArrowDownwardIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const UploadSpeedWidget = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<StatisticsField
|
||||
title={t('UploadSpeed')}
|
||||
value={humanizeSize(data) || '0 B'}
|
||||
iconBg='#0146ad'
|
||||
valueBg='#0058db'
|
||||
icon={ArrowUpwardIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const PeersWidget = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<StatisticsField
|
||||
title={t('Peers')}
|
||||
value={getPeerString(data) || '[0] 0 / 0'}
|
||||
iconBg='#cdc118'
|
||||
valueBg='#d8cb18'
|
||||
icon={SwapVerticalCircleIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const PiecesCountWidget = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
return <StatisticsField title={t('PiecesCount')} value={data} iconBg='#b6c95e' valueBg='#c0d076' icon={WidgetsIcon} />
|
||||
}
|
||||
|
||||
export const PiecesLengthWidget = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<StatisticsField
|
||||
title={t('PiecesLength')}
|
||||
value={humanizeSize(data)}
|
||||
iconBg='#0982c8'
|
||||
valueBg='#098cd7'
|
||||
icon={PhotoSizeSelectSmallIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const StatusWidget = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
let i18nd = data
|
||||
if (data.toLowerCase() === 'torrent added')
|
||||
i18nd = t('TorrentAdded')
|
||||
else if (data.toLowerCase() === 'torrent getting info')
|
||||
i18nd = t('TorrentGettingInfo')
|
||||
else if (data.toLowerCase() === 'torrent preload')
|
||||
i18nd = t('TorrentPreload')
|
||||
else if (data.toLowerCase() === 'torrent working')
|
||||
i18nd = t('TorrentWorking')
|
||||
else if (data.toLowerCase() === 'torrent closed')
|
||||
i18nd = t('TorrentClosed')
|
||||
else if (data.toLowerCase() === 'torrent in db')
|
||||
i18nd = t('TorrentInDb')
|
||||
return <StatisticsField title={t('TorrentStatus')} value={i18nd} 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
import axios from 'axios'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import { useEffect, useState } from 'react'
|
||||
import SettingsIcon from '@material-ui/icons/Settings'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'
|
||||
import { settingsHost, setTorrServerHost, getTorrServerHost } from 'utils/Hosts'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function SettingsDialog() {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [settings, setSets] = useState({})
|
||||
const [show, setShow] = useState(false)
|
||||
const [tsHost, setTSHost] = useState(getTorrServerHost())
|
||||
|
||||
const handleClickOpen = () => setOpen(true)
|
||||
const handleClose = () => setOpen(false)
|
||||
const handleSave = () => {
|
||||
setOpen(false)
|
||||
const sets = JSON.parse(JSON.stringify(settings))
|
||||
sets.CacheSize *= 1024 * 1024
|
||||
axios.post(settingsHost(), { action: 'set', sets })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
axios
|
||||
.post(settingsHost(), { action: 'get' })
|
||||
.then(({ data }) => {
|
||||
setSets({ ...data, CacheSize: data.CacheSize / (1024 * 1024) })
|
||||
setShow(true)
|
||||
})
|
||||
.catch(() => setShow(false))
|
||||
}, [tsHost])
|
||||
|
||||
const onInputHost = event => {
|
||||
const host = event.target.value
|
||||
setTorrServerHost(host)
|
||||
setTSHost(host)
|
||||
}
|
||||
|
||||
const inputForm = ({ target: { type, value, checked, id } }) => {
|
||||
const sets = JSON.parse(JSON.stringify(settings))
|
||||
if (type === 'number' || type === 'select-one') {
|
||||
sets[id] = Number(value)
|
||||
} else if (type === 'checkbox') {
|
||||
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') {
|
||||
sets[id] = value
|
||||
}
|
||||
setSets(sets)
|
||||
}
|
||||
|
||||
const {
|
||||
CacheSize,
|
||||
PreloadBuffer,
|
||||
ReaderReadAHead,
|
||||
RetrackersMode,
|
||||
TorrentDisconnectTimeout,
|
||||
EnableIPv6,
|
||||
ForceEncrypt,
|
||||
DisableTCP,
|
||||
DisableUTP,
|
||||
DisableUPNP,
|
||||
DisableDHT,
|
||||
DisablePEX,
|
||||
DisableUpload,
|
||||
DownloadRateLimit,
|
||||
UploadRateLimit,
|
||||
ConnectionsLimit,
|
||||
DhtConnectionLimit,
|
||||
PeersListenPort,
|
||||
UseDisk,
|
||||
TorrentsSavePath,
|
||||
RemoveCacheOnDrop,
|
||||
} = settings
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ListItem button key={t('Settings')} onClick={handleClickOpen}>
|
||||
<ListItemIcon>
|
||||
<SettingsIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t('Settings')} />
|
||||
</ListItem>
|
||||
|
||||
<Dialog open={open} onClose={handleClose} aria-labelledby='form-dialog-title' fullWidth>
|
||||
<DialogTitle id='form-dialog-title'>{t('Settings')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
onChange={onInputHost}
|
||||
margin='dense'
|
||||
id='TorrServerHost'
|
||||
label={t('Host')}
|
||||
value={tsHost}
|
||||
type='url'
|
||||
fullWidth
|
||||
/>
|
||||
{show && (
|
||||
<>
|
||||
<TextField
|
||||
onChange={inputForm}
|
||||
margin='dense'
|
||||
id='CacheSize'
|
||||
label={t('CacheSize')}
|
||||
value={CacheSize}
|
||||
type='number'
|
||||
fullWidth
|
||||
/>
|
||||
<br />
|
||||
<TextField
|
||||
onChange={inputForm}
|
||||
margin='dense'
|
||||
id='ReaderReadAHead'
|
||||
label={t('ReaderReadAHead')}
|
||||
value={ReaderReadAHead}
|
||||
type='number'
|
||||
fullWidth
|
||||
/>
|
||||
<br />
|
||||
<FormControlLabel
|
||||
control={<Switch checked={PreloadBuffer} onChange={inputForm} id='PreloadBuffer' color='primary' />}
|
||||
label={t('PreloadBuffer')}
|
||||
/>
|
||||
<br />
|
||||
<FormControlLabel
|
||||
control={<Switch checked={UseDisk} onChange={inputForm} id='UseDisk' color='primary' />}
|
||||
label={t('UseDisk')}
|
||||
/>
|
||||
<br />
|
||||
<small>{t('UseDiskDesc')}</small>
|
||||
<br />
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={RemoveCacheOnDrop} onChange={inputForm} id='RemoveCacheOnDrop' color='primary' />
|
||||
}
|
||||
label={t('RemoveCacheOnDrop')}
|
||||
/>
|
||||
<br />
|
||||
<small>{t('RemoveCacheOnDropDesc')}</small>
|
||||
<br />
|
||||
<TextField
|
||||
onChange={inputForm}
|
||||
margin='dense'
|
||||
id='TorrentsSavePath'
|
||||
label={t('TorrentsSavePath')}
|
||||
value={TorrentsSavePath}
|
||||
type='url'
|
||||
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>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color='primary' variant='outlined'>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
|
||||
<Button onClick={handleSave} color='primary' variant='outlined'>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Typography } from '@material-ui/core'
|
||||
import { torrentsHost } from 'utils/Hosts'
|
||||
import TorrentCard from 'components/TorrentCard'
|
||||
import axios from 'axios'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import { TorrentListWrapper, CenteredGrid } from 'App/style'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function TorrentList() {
|
||||
const { t } = useTranslation()
|
||||
const [torrents, setTorrents] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isOffline, setIsOffline] = useState(true)
|
||||
const timerID = useRef(-1)
|
||||
|
||||
useEffect(() => {
|
||||
timerID.current = setInterval(() => {
|
||||
// getting torrent list
|
||||
axios
|
||||
.post(torrentsHost(), { action: 'list' })
|
||||
.then(({ data }) => {
|
||||
// updating torrent list
|
||||
setTorrents(data)
|
||||
setIsOffline(false)
|
||||
})
|
||||
.catch(() => {
|
||||
// resetting torrent list
|
||||
setTorrents([])
|
||||
setIsOffline(true)
|
||||
})
|
||||
.finally(() => setIsLoading(false))
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(timerID.current)
|
||||
}, [])
|
||||
|
||||
if (isLoading || isOffline || !torrents.length) {
|
||||
return (
|
||||
<CenteredGrid>
|
||||
{isLoading ? (
|
||||
<CircularProgress />
|
||||
) : isOffline ? (
|
||||
<Typography>{t('Offline')}</Typography>
|
||||
) : (
|
||||
!torrents.length && <Typography>{t('NoTorrentsAdded')}</Typography>
|
||||
)}
|
||||
</CenteredGrid>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TorrentListWrapper>
|
||||
{torrents.map(torrent => (
|
||||
<TorrentCard key={torrent.hash} torrent={torrent} />
|
||||
))}
|
||||
</TorrentListWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||
import ListItemText from '@material-ui/core/ListItemText'
|
||||
import ListItem from '@material-ui/core/ListItem'
|
||||
import PublishIcon from '@material-ui/icons/Publish'
|
||||
import { torrentUploadHost } from 'utils/Hosts'
|
||||
import axios from 'axios'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function UploadDialog() {
|
||||
const { t } = useTranslation()
|
||||
const handleCapture = ({ target: { files } }) => {
|
||||
const [file] = files
|
||||
const data = new FormData()
|
||||
data.append('save', 'true')
|
||||
data.append('file', file)
|
||||
axios.post(torrentUploadHost(), data)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor='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={t('UploadFile')}>
|
||||
<ListItemIcon>
|
||||
<PublishIcon />
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText primary={t('UploadFile')} />
|
||||
</ListItem>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user