This commit is contained in:
nikk gitanes
2021-07-18 13:25:41 +03:00
parent d318db1a12
commit 2fd7db9c18
18 changed files with 64 additions and 1078 deletions

View File

@@ -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,

View File

@@ -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>"
}

View File

@@ -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)
})
}

View File

@@ -1 +0,0 @@
REACT_APP_SERVER_HOST=http://127.0.0.1:8090

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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;
}
`

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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,
],
})}
/>
)
}

View File

@@ -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 }
}

View File

@@ -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}
/>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}