mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 13:36:09 +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>"
|
||||
}
|
||||
|
||||
@@ -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,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