Merge pull request #51 from dancheskus/adaptive-design

Adaptive design
This commit is contained in:
YouROK
2021-05-24 19:33:04 +03:00
committed by GitHub
16 changed files with 23484 additions and 388 deletions

File diff suppressed because one or more lines are too long

22981
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,8 @@
"material-ui-image": "^3.3.0", "material-ui-image": "^3.3.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-scripts": "4.0.1" "react-scripts": "4.0.1",
"styled-components": "^5.3.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
@@ -47,5 +48,10 @@
"gulp-inline-source": "^4.0.0", "gulp-inline-source": "^4.0.0",
"gulp-replace": "^1.0.0", "gulp-replace": "^1.0.0",
"prettier": "2.2.1" "prettier": "2.2.1"
} },
"description": "",
"main": "gulpfile.js",
"keywords": [],
"author": "",
"license": "ISC"
} }

View File

@@ -1,6 +1,5 @@
import React from 'react'
import CssBaseline from '@material-ui/core/CssBaseline' import CssBaseline from '@material-ui/core/CssBaseline'
import Appbar from './components/Appbar.js' import Appbar from './components/Appbar/index.js'
import { createMuiTheme, MuiThemeProvider } from '@material-ui/core' import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'
const baseTheme = createMuiTheme({ const baseTheme = createMuiTheme({
@@ -26,11 +25,9 @@ const baseTheme = createMuiTheme({
export default function App() { export default function App() {
return ( return (
<React.Fragment> <MuiThemeProvider theme={baseTheme}>
<MuiThemeProvider theme={baseTheme}> <CssBaseline />
<CssBaseline /> <Appbar />
<Appbar /> </MuiThemeProvider>
</MuiThemeProvider>
</React.Fragment>
) )
} }

View File

@@ -1,93 +0,0 @@
import React from 'react'
import Button from '@material-ui/core/Button'
import TextField from '@material-ui/core/TextField'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogContentText from '@material-ui/core/DialogContentText'
import DialogTitle from '@material-ui/core/DialogTitle'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import LibraryAddIcon from '@material-ui/icons/LibraryAdd'
import ListItemText from '@material-ui/core/ListItemText'
import ListItem from '@material-ui/core/ListItem'
import { torrentsHost } from '../utils/Hosts'
export default function AddDialog() {
const [open, setOpen] = React.useState(false)
const [magnet, setMagnet] = React.useState('')
const [title, setTitle] = React.useState('')
const [poster, setPoster] = React.useState('')
const handleClickOpen = () => {
setOpen(true)
}
const inputMagnet = (event) => {
setMagnet(event.target.value)
}
const inputTitle = (event) => {
setTitle(event.target.value)
}
const inputPoster = (event) => {
setPoster(event.target.value)
}
const handleCloseSave = () => {
try {
if (!magnet) return
fetch(torrentsHost(), {
method: 'post',
body: JSON.stringify({
action: 'add',
link: magnet,
title: title,
poster: poster,
save_to_db: true,
}),
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
},
})
setOpen(false)
} catch (e) {
console.log(e)
}
}
const handleClose = () => {
setOpen(false)
}
return (
<div>
<ListItem button key="Add" onClick={handleClickOpen}>
<ListItemIcon>
<LibraryAddIcon />
</ListItemIcon>
<ListItemText primary="Add" />
</ListItem>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" fullWidth={true}>
<DialogTitle id="form-dialog-title">Add Magnet</DialogTitle>
<DialogContent>
<DialogContentText>Add magnet or link to torrent file:</DialogContentText>
<TextField onChange={inputTitle} margin="dense" id="title" label="Title" type="text" fullWidth />
<TextField onChange={inputPoster} margin="dense" id="poster" label="Poster" type="url" fullWidth />
<TextField onChange={inputMagnet} autoFocus margin="dense" id="magnet" label="Magnet" type="text" fullWidth />
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary" variant="outlined">
Cancel
</Button>
<Button onClick={handleCloseSave} color="primary" variant="outlined">
Add
</Button>
</DialogActions>
</Dialog>
</div>
)
}

View File

@@ -0,0 +1,64 @@
import { useState } from 'react'
import Button from '@material-ui/core/Button'
import TextField from '@material-ui/core/TextField'
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 { torrentsHost } from '../../utils/Hosts'
export default function AddDialog({ handleClose }) {
const [magnet, setMagnet] = useState('')
const [title, setTitle] = useState('')
const [poster, setPoster] = useState('')
const inputMagnet = ({ target: { value } }) => setMagnet(value)
const inputTitle = ({ target: { value } }) => setTitle(value)
const inputPoster = ({ target: { value } }) => setPoster(value)
const handleCloseSave = () => {
try {
if (!magnet) return
fetch(torrentsHost(), {
method: 'post',
body: JSON.stringify({
action: 'add',
link: magnet,
title: title,
poster: poster,
save_to_db: true,
}),
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
},
})
handleClose()
} catch (e) {
console.log(e)
}
}
return (
<Dialog open onClose={handleClose} aria-labelledby="form-dialog-title" fullWidth>
<DialogTitle id="form-dialog-title">Add magnet or link to torrent file</DialogTitle>
<DialogContent>
<TextField onChange={inputTitle} margin="dense" id="title" label="Title" type="text" fullWidth />
<TextField onChange={inputPoster} margin="dense" id="poster" label="Poster" type="url" fullWidth />
<TextField onChange={inputMagnet} autoFocus margin="dense" id="magnet" label="Magnet or torrent file link" type="text" fullWidth />
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary" variant="outlined">
Cancel
</Button>
<Button disabled={!magnet} onClick={handleCloseSave} color="primary" variant="outlined">
Add
</Button>
</DialogActions>
</Dialog>
)
}

View File

@@ -0,0 +1,26 @@
import { useState } from 'react'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import LibraryAddIcon from '@material-ui/icons/LibraryAdd'
import ListItemText from '@material-ui/core/ListItemText'
import ListItem from '@material-ui/core/ListItem'
import AddDialog from './AddDialog'
export default function AddDialogButton() {
const [isDialogOpen, setIsDialogOpen] = useState(false)
const handleClickOpen = () => setIsDialogOpen(true)
const handleClose = () => setIsDialogOpen(false)
return (
<div>
<ListItem button key="Add" onClick={handleClickOpen}>
<ListItemIcon>
<LibraryAddIcon />
</ListItemIcon>
<ListItemText primary="Add from link" />
</ListItem>
{isDialogOpen && <AddDialog handleClose={handleClose} />}
</div>
)
}

View File

@@ -1,11 +1,10 @@
import React, { useEffect } from 'react' import { useEffect, useState } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
import { makeStyles, useTheme } from '@material-ui/core/styles' import { useTheme } from '@material-ui/core/styles'
import Drawer from '@material-ui/core/Drawer' import Drawer from '@material-ui/core/Drawer'
import AppBar from '@material-ui/core/AppBar' import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar' import Toolbar from '@material-ui/core/Toolbar'
import List from '@material-ui/core/List' import List from '@material-ui/core/List'
import CssBaseline from '@material-ui/core/CssBaseline'
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography'
import Divider from '@material-ui/core/Divider' import Divider from '@material-ui/core/Divider'
import IconButton from '@material-ui/core/IconButton' import IconButton from '@material-ui/core/IconButton'
@@ -19,86 +18,22 @@ import ListItemText from '@material-ui/core/ListItemText'
import ListIcon from '@material-ui/icons/List' import ListIcon from '@material-ui/icons/List'
import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew' import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew'
import TorrentList from './TorrentList' import TorrentList from '../TorrentList'
import { Box } from '@material-ui/core'
import AddDialog from './Add' import AddDialogButton from '../Add'
import RemoveAll from './RemoveAll' import RemoveAll from '../RemoveAll'
import SettingsDialog from './Settings' import SettingsDialog from '../Settings'
import AboutDialog from './About' import AboutDialog from '../About'
import { playlistAllHost, shutdownHost, torrserverHost } from '../utils/Hosts' import { playlistAllHost, shutdownHost, torrserverHost } from '../../utils/Hosts'
import DonateDialog from './Donate' import DonateDialog from '../Donate'
import UploadDialog from './Upload' import UploadDialog from '../Upload'
import useStyles from './useStyles'
const drawerWidth = 240
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
hide: {
display: 'none',
},
drawer: {
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
},
drawerOpen: {
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerClose: {
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: theme.spacing(7) + 1,
[theme.breakpoints.up('sm')]: {
width: theme.spacing(9) + 1,
},
},
toolbar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
}))
export default function MiniDrawer() { export default function MiniDrawer() {
const classes = useStyles() const classes = useStyles()
const theme = useTheme() const theme = useTheme()
const [open, setOpen] = React.useState(false) const [open, setOpen] = useState(false)
const [tsVersion, setTSVersion] = React.useState('') const [tsVersion, setTSVersion] = useState('')
const handleDrawerOpen = () => { const handleDrawerOpen = () => {
setOpen(true) setOpen(true)
@@ -118,7 +53,6 @@ export default function MiniDrawer() {
return ( return (
<div className={classes.root}> <div className={classes.root}>
<CssBaseline />
<AppBar <AppBar
position="fixed" position="fixed"
className={clsx(classes.appBar, { className={clsx(classes.appBar, {
@@ -142,6 +76,7 @@ export default function MiniDrawer() {
</Typography> </Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Drawer <Drawer
variant="permanent" variant="permanent"
className={clsx(classes.drawer, { className={clsx(classes.drawer, {
@@ -156,11 +91,15 @@ export default function MiniDrawer() {
}} }}
> >
<div className={classes.toolbar}> <div className={classes.toolbar}>
<IconButton onClick={handleDrawerClose}>{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}</IconButton> <IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronRightIcon /> : <ChevronLeftIcon />}
</IconButton>
</div> </div>
<Divider /> <Divider />
<List> <List>
<AddDialog /> <AddDialogButton />
<UploadDialog /> <UploadDialog />
<RemoveAll /> <RemoveAll />
<ListItem button component="a" key="Playlist all torrents" target="_blank" href={playlistAllHost()}> <ListItem button component="a" key="Playlist all torrents" target="_blank" href={playlistAllHost()}>
@@ -170,10 +109,11 @@ export default function MiniDrawer() {
<ListItemText primary="Playlist all torrents" /> <ListItemText primary="Playlist all torrents" />
</ListItem> </ListItem>
</List> </List>
<Divider /> <Divider />
<List> <List>
<SettingsDialog /> <SettingsDialog />
<DonateDialog />
<AboutDialog /> <AboutDialog />
<ListItem button key="Close server" onClick={() => fetch(shutdownHost())}> <ListItem button key="Close server" onClick={() => fetch(shutdownHost())}>
<ListItemIcon> <ListItemIcon>
@@ -182,12 +122,15 @@ export default function MiniDrawer() {
<ListItemText primary="Close server" /> <ListItemText primary="Close server" />
</ListItem> </ListItem>
</List> </List>
<Divider />
</Drawer> </Drawer>
<main className={classes.content}> <main className={classes.content}>
<Box m="5em" /> <div className={classes.toolbar} />
<TorrentList /> <TorrentList />
</main> </main>
<DonateDialog />
</div> </div>
) )
} }

View File

@@ -0,0 +1,65 @@
import { makeStyles } from '@material-ui/core/styles'
const drawerWidth = 240
export default makeStyles((theme) => ({
root: {
display: 'flex',
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginRight: 36,
},
hide: {
display: 'none',
},
drawer: {
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
},
drawerOpen: {
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerClose: {
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: theme.spacing(7) + 1,
[theme.breakpoints.up('sm')]: {
width: theme.spacing(9) + 1,
},
},
toolbar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
}))

View File

@@ -6,44 +6,44 @@ import DialogTitle from '@material-ui/core/DialogTitle'
import DialogContent from '@material-ui/core/DialogContent' import DialogContent from '@material-ui/core/DialogContent'
import { cacheHost } from '../utils/Hosts' import { cacheHost } from '../utils/Hosts'
const style = { // const style = {
cache: { // cache: {
paddingLeft: "6px", // paddingLeft: "6px",
paddingRight: "2px", // paddingRight: "2px",
lineHeight: "11px", // lineHeight: "11px",
}, // },
piece: { // piece: {
width: "12px", // width: "12px",
height: "12px", // height: "12px",
backgroundColor: "#eef2f4", // backgroundColor: "#eef2f4",
border: "1px solid #eef2f4", // border: "1px solid #eef2f4",
display: "inline-block", // display: "inline-block",
marginRight: "1px", // marginRight: "1px",
}, // },
pieceComplete: { // pieceComplete: {
backgroundColor: "#3fb57a", // backgroundColor: "#3fb57a",
borderColor: "#3fb57a", // borderColor: "#3fb57a",
}, // },
pieceLoading: { // pieceLoading: {
backgroundColor: "#00d0d0", // backgroundColor: "#00d0d0",
borderColor: "#00d0d0", // borderColor: "#00d0d0",
}, // },
readerRange: { // readerRange: {
borderColor: "#9a9aff !important", // borderColor: "#9a9aff !important",
}, // },
pieceReader: { // pieceReader: {
borderColor: "#000000 !important", // borderColor: "#000000 !important",
}, // },
pieceProgress: { // pieceProgress: {
position: "relative", // position: "relative",
zIndex: "1", // zIndex: "1",
backgroundColor: "#009090", // backgroundColor: "#009090",
left: "-1px", // left: "-1px",
top: "-1px", // top: "-1px",
width: "12px", // width: "12px",
}, // },
} // }
export default function DialogCacheInfo(props) { export default function DialogCacheInfo(props) {
const [hash] = React.useState(props.hash) const [hash] = React.useState(props.hash)
@@ -65,8 +65,8 @@ export default function DialogCacheInfo(props) {
} }
}, [hash, props.open]) }, [hash, props.open])
useEffect(()=>{ useEffect(() => {
if (cache && cache.PiecesCount && cache.Pieces){ if (cache && cache.PiecesCount && cache.Pieces) {
var map = []; var map = [];
for (let i = 0; i < cache.PiecesCount; i++) { for (let i = 0; i < cache.PiecesCount; i++) {
var reader = 0 var reader = 0
@@ -80,7 +80,7 @@ export default function DialogCacheInfo(props) {
prc = (cache.Pieces[i].Size / cache.Pieces[i].Length * 100).toFixed(2) prc = (cache.Pieces[i].Size / cache.Pieces[i].Length * 100).toFixed(2)
} }
cache.Readers.forEach((r, k) => { cache.Readers.forEach(r => {
if (i >= r.Start && i <= r.End && i !== r.Reader) if (i >= r.Start && i <= r.End && i !== r.Reader)
cls += " reader-range" cls += " reader-range"
if (i === r.Reader) { if (i === r.Reader) {
@@ -96,12 +96,12 @@ export default function DialogCacheInfo(props) {
} }
setPMap(map) setPMap(map)
} }
},[cache.Pieces]) }, [cache.Pieces])
return ( return (
<div> <div>
<DialogTitle id="form-dialog-title"> <DialogTitle id="form-dialog-title">
<Typography fullWidth> <Typography>
<b>Hash </b> {cache.Hash} <b>Hash </b> {cache.Hash}
<br /> <br />
<b>Capacity </b> {humanizeSize(cache.Capacity)} <b>Capacity </b> {humanizeSize(cache.Capacity)}
@@ -123,48 +123,55 @@ export default function DialogCacheInfo(props) {
<b>Status </b> {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string} <b>Status </b> {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}
</Typography> </Typography>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<div className="cache"> <div className="cache">
{pMap.map((itm) => <span className={itm.class} title={itm.info}>{itm.prc>0 && itm.prc<100 && (<div className="piece-progress" style={{height: itm.prc/100*12+"px"}}></div>)}</span>)} {pMap.map(itm => (
<span key={itm.info} className={itm.class} title={itm.info}>
{itm.prc > 0 && itm.prc < 100 && (
<div className="piece-progress" style={{ height: itm.prc / 100 * 12 + "px" }}></div>
)}
</span>
))}
</div> </div>
</DialogContent> </DialogContent>
</div> </div>
) )
} }
function getCacheMap(cache) { // function getCacheMap(cache) {
if (!cache || !cache.PiecesCount) return '' // if (!cache || !cache.PiecesCount) return ''
var html = '' // var html = ''
for (let i = 0; i < cache.PiecesCount; i++) { // for (let i = 0; i < cache.PiecesCount; i++) {
html += "<span class='piece" // html += "<span class='piece"
let info = i // let info = i
var prcDiv = "" // var prcDiv = ""
if (cache.Pieces && cache.Pieces[i]) { // if (cache.Pieces && cache.Pieces[i]) {
let prc = (cache.Pieces[i].Size/cache.Pieces[i].Length*100).toFixed(2) // let prc = (cache.Pieces[i].Size / cache.Pieces[i].Length * 100).toFixed(2)
let piece = cache.Pieces[i] // let piece = cache.Pieces[i]
if (piece.Completed && piece.Size >= piece.Length) { // if (piece.Completed && piece.Size >= piece.Length) {
html += ' piece-complete' // html += ' piece-complete'
info += ' 100%' // info += ' 100%'
}else { // } else {
html += ' piece-loading' // html += ' piece-loading'
info += ' ' + prc + '%' // info += ' ' + prc + '%'
prcDiv = "<div class='piece-progress' style='height: "+prc+"%;'></div>" // prcDiv = "<div class='piece-progress' style='height: " + prc + "%;'></div>"
} // }
} // }
cache.Readers.forEach((r,k)=> { // cache.Readers.forEach((r, k) => {
if (i >= r.Start && i <= r.End && i !== r.Reader) // if (i >= r.Start && i <= r.End && i !== r.Reader)
html += ' reader-range' // html += ' reader-range'
if (i === r.Reader) { // if (i === r.Reader) {
html += ' piece-reader' // html += ' piece-reader'
info += ' reader' // info += ' reader'
} // }
}) // })
html += "' title='" + info + "'>" // html += "' title='" + info + "'>"
html += prcDiv // html += prcDiv
html += "</span>" // html += "</span>"
} // }
return html // return html
} // }
function getCache(hash, callback) { function getCache(hash, callback) {
try { try {
@@ -193,19 +200,19 @@ function getCache(hash, callback) {
} }
/* /*
{ {
"Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e", "Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e",
"Capacity": 209715200, "Capacity": 209715200,
"Filled": 2914808, "Filled": 2914808,
"PiecesLength": 4194304, "PiecesLength": 4194304,
"PiecesCount": 2065, "PiecesCount": 2065,
"DownloadSpeed": 32770.860273455524, "DownloadSpeed": 32770.860273455524,
"Pieces": { "Pieces": {
"2064": { "2064": {
"Id": 2064, "Id": 2064,
"Length": 2914808, "Length": 2914808,
"Size": 162296, "Size": 162296,
"Completed": false "Completed": false
} }
} }
} }
*/ */

View File

@@ -20,21 +20,25 @@ export default function DonateDialog() {
const [open, setOpen] = React.useState(false) const [open, setOpen] = React.useState(false)
const [snakeOpen, setSnakeOpen] = React.useState(true) const [snakeOpen, setSnakeOpen] = React.useState(true)
const handleClickOpen = () => { // NOT USED FOR NOW
setOpen(true) // const handleClickOpen = () => {
} // setOpen(true)
// }
const handleClose = () => { const handleClose = () => {
setOpen(false) setOpen(false)
} }
return ( return (
<div> <div>
<ListItem button key="Donate" onClick={handleClickOpen}> {/* !!!!!!!!!!! Should be removed or moved to sidebar because it is not visible. It is hiddent behind header */}
{/* <ListItem button key="Donate" onClick={handleClickOpen}>
<ListItemIcon> <ListItemIcon>
<CreditCardIcon /> <CreditCardIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Donate" /> <ListItemText primary="Donate" />
</ListItem> </ListItem> */}
{/* !!!!!!!!!!!!!!!!!!!! */}
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" fullWidth> <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title" fullWidth>
<DialogTitle id="form-dialog-title">Donate</DialogTitle> <DialogTitle id="form-dialog-title">Donate</DialogTitle>
<DialogContent> <DialogContent>
@@ -63,12 +67,12 @@ export default function DonateDialog() {
horizontal: 'center', horizontal: 'center',
}} }}
open={snakeOpen} open={snakeOpen}
onClose={()=>{setSnakeOpen(false)}} onClose={() => { setSnakeOpen(false) }}
autoHideDuration={6000} autoHideDuration={6000}
message="Donate?" message="Donate?"
action={ action={
<React.Fragment> <React.Fragment>
<IconButton size="small" aria-label="close" color="inherit" onClick={()=>{ <IconButton size="small" aria-label="close" color="inherit" onClick={() => {
setSnakeOpen(false) setSnakeOpen(false)
setOpen(true) setOpen(true)
}}> }}>

View File

@@ -5,30 +5,31 @@ import ListItemText from '@material-ui/core/ListItemText'
import DeleteIcon from '@material-ui/icons/Delete' import DeleteIcon from '@material-ui/icons/Delete'
import { torrentsHost } from '../utils/Hosts' import { torrentsHost } from '../utils/Hosts'
export default function RemoveAll() { const fnRemoveAll = () => {
const fnRemoveAll = () => { fetch(torrentsHost(), {
fetch(torrentsHost(), { method: 'post',
method: 'post', body: JSON.stringify({ action: 'list' }),
body: JSON.stringify({ action: 'list' }), headers: {
headers: { Accept: 'application/json, text/plain, */*',
Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json',
'Content-Type': 'application/json', },
}, })
}) .then((res) => res.json())
.then((res) => res.json()) .then((json) => {
.then((json) => { json.forEach((torr) => {
json.forEach((torr) => { fetch(torrentsHost(), {
fetch(torrentsHost(), { method: 'post',
method: 'post', body: JSON.stringify({ action: 'rem', hash: torr.hash }),
body: JSON.stringify({ action: 'rem', hash: torr.hash }), headers: {
headers: { Accept: 'application/json, text/plain, */*',
Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json',
'Content-Type': 'application/json', },
},
})
}) })
}) })
} })
}
export default function RemoveAll() {
return ( return (
<ListItem button key="Remove all" onClick={fnRemoveAll}> <ListItem button key="Remove all" onClick={fnRemoveAll}>
<ListItemIcon> <ListItemIcon>

View File

@@ -1,27 +1,27 @@
import React, { useEffect, useRef } from 'react' import { useEffect, useRef, useState } from 'react'
import ButtonGroup from '@material-ui/core/ButtonGroup'
import Button from '@material-ui/core/Button' import Button from '@material-ui/core/Button'
import 'fontsource-roboto' import 'fontsource-roboto'
import HeightIcon from '@material-ui/icons/Height';
import CloseIcon from '@material-ui/icons/Close'; import CloseIcon from '@material-ui/icons/Close';
import DeleteIcon from '@material-ui/icons/Delete' import DeleteIcon from '@material-ui/icons/Delete'
import Typography from '@material-ui/core/Typography'
import ListItem from '@material-ui/core/ListItem'
import DialogActions from '@material-ui/core/DialogActions' import DialogActions from '@material-ui/core/DialogActions'
import Dialog from '@material-ui/core/Dialog' import Dialog from '@material-ui/core/Dialog'
import { getPeerString, humanizeSize } from '../utils/Utils' import { getPeerString, humanizeSize } from '../../utils/Utils'
import DialogTorrentInfo from './DialogTorrentInfo' import DialogTorrentInfo from '../DialogTorrentInfo'
import { torrentsHost } from '../utils/Hosts' import { torrentsHost } from '../../utils/Hosts'
import DialogCacheInfo from './DialogCacheInfo' import DialogCacheInfo from '../DialogCacheInfo'
import DataUsageIcon from '@material-ui/icons/DataUsage' import DataUsageIcon from '@material-ui/icons/DataUsage'
import { NoImageIcon } from '../../icons';
import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardDescriptionContent, TorrentCardDescriptionLabel, TorrentCardPoster } from './style';
export default function Torrent(props) { export default function Torrent(props) {
const [open, setOpen] = React.useState(false) const [open, setOpen] = useState(false)
const [showCache, setShowCache] = React.useState(false) const [showCache, setShowCache] = useState(false)
const [torrent, setTorrent] = React.useState(props.torrent) const [torrent, setTorrent] = useState(props.torrent)
const timerID = useRef(-1) const timerID = useRef(-1)
useEffect(() => { useEffect(() => {
@@ -43,61 +43,74 @@ export default function Torrent(props) {
} }
}, [torrent.hash, open]) }, [torrent.hash, open])
const { title, name, poster, torrent_size, download_speed } = torrent
return ( return (
<div> <>
<ListItem>
<ButtonGroup style={{width:'100%',boxShadow:'2px 2px 2px gray'}} disableElevation variant="contained" color="primary"> <TorrentCard>
<Button <TorrentCardPoster isPoster={poster}>
style={{width: '100%', justifyContent:'start'}} {poster
onClick={() => { ? <img src={poster} alt="poster" />
setShowCache(false) : <NoImageIcon />}
setOpen(true) </TorrentCardPoster>
}}
> <TorrentCardButtons>
{torrent.poster && <StyledButton
<img src={torrent.poster} alt="" align="left" style={{width: 'auto',height:'100px',margin:'0 10px 0 0',borderRadius:'5px'}}/>
}
<Typography>
{torrent.title ? torrent.title : torrent.name}
{torrent.torrent_size > 0 ? ' | ' + humanizeSize(torrent.torrent_size) : ''}
{torrent.download_speed > 0 ? ' | ' + humanizeSize(torrent.download_speed) + '/sec' : ''}
{getPeerString(torrent) ? ' | ' + getPeerString(torrent) : '' }
</Typography>
</Button>
<Button
onClick={() => { onClick={() => {
setShowCache(true) setShowCache(true)
setOpen(true) setOpen(true)
}} }}
> >
<DataUsageIcon /> <DataUsageIcon />
<Typography>Cache</Typography> Cache
</Button> </StyledButton>
<Button
onClick={() => { <StyledButton
dropTorrent(torrent) onClick={() => dropTorrent(torrent)}
}}
> >
<CloseIcon /> <CloseIcon />
<Typography>Drop</Typography> Drop
</Button> </StyledButton>
<Button
onClick={() => { <StyledButton
deleteTorrent(torrent) onClick={() => deleteTorrent(torrent)}
}}
> >
<DeleteIcon /> <DeleteIcon />
<Typography>Delete</Typography> Delete
</Button> </StyledButton>
</ButtonGroup>
</ListItem> <StyledButton
onClick={() => {
setShowCache(false)
setOpen(true)
}}
>
<HeightIcon />
Details
</StyledButton>
</TorrentCardButtons>
<TorrentCardDescription>
<TorrentCardDescriptionLabel>Name</TorrentCardDescriptionLabel>
<TorrentCardDescriptionContent>{title || name}</TorrentCardDescriptionContent>
<TorrentCardDescriptionLabel>Size</TorrentCardDescriptionLabel>
<TorrentCardDescriptionContent>{torrent_size > 0 && humanizeSize(torrent_size)}</TorrentCardDescriptionContent>
<TorrentCardDescriptionLabel>Download speed</TorrentCardDescriptionLabel>
<TorrentCardDescriptionContent>{download_speed > 0 ? humanizeSize(download_speed) : '---'}</TorrentCardDescriptionContent>
<TorrentCardDescriptionLabel>Peers</TorrentCardDescriptionLabel>
<TorrentCardDescriptionContent>{getPeerString(torrent) || '---'}</TorrentCardDescriptionContent>
</TorrentCardDescription>
</TorrentCard>
<Dialog <Dialog
open={open} open={open}
onClose={() => { onClose={() => setOpen(false)}
setOpen(false)
}}
aria-labelledby="form-dialog-title" aria-labelledby="form-dialog-title"
fullWidth={true} fullWidth
maxWidth={'lg'} maxWidth={'lg'}
> >
{!showCache ? <DialogTorrentInfo torrent={(open, torrent)} /> : <DialogCacheInfo hash={(open, torrent.hash)} />} {!showCache ? <DialogTorrentInfo torrent={(open, torrent)} /> : <DialogCacheInfo hash={(open, torrent.hash)} />}
@@ -105,15 +118,13 @@ export default function Torrent(props) {
<Button <Button
variant="outlined" variant="outlined"
color="primary" color="primary"
onClick={() => { onClick={() => setOpen(false)}
setOpen(false)
}}
> >
OK OK
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</div> </>
) )
} }

View File

@@ -0,0 +1,98 @@
import styled, { css } from 'styled-components';
export const TorrentCard = styled.div`
border: 1px solid;
border-radius: 5px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: 175px minmax(min-content, 1fr);
grid-template-areas:
"poster buttons"
"description description";
gap: 10px;
padding: 10px;
background: #3fb57a;
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%);
`
export const TorrentCardPoster = styled.div`
grid-area: poster;
border-radius: 5px;
overflow: hidden;
text-align: center;
${({ isPoster }) => isPoster ? css`
img {
height: 100%;
border-radius: 5px;
}
`: css`
display: grid;
place-items: center;
background: #74c39c;
border: 1px solid;
svg {
transform: translateY(-3px);
}
`};
`
export const TorrentCardButtons = styled.div`
grid-area: buttons;
display: grid;
gap: 5px;
`
export const TorrentCardDescription = styled.div`
grid-area: description;
background: #74c39c;
border-radius: 5px;
padding: 5px;
`
export const TorrentCardDescriptionLabel = styled.div`
text-transform: uppercase;
font-size: 10px;
font-weight: 500;
letter-spacing: 0.4px;
color: #216e47;
`
export const TorrentCardDescriptionContent = styled.div`
margin-left: 5px;
margin-bottom: 10px;
`
export const StyledButton = styled.button`
border-radius: 5px;
border: none;
cursor: pointer;
transition: 0.2s;
display: flex;
align-items: center;
text-transform: uppercase;
background: #216e47;
color: #fff;
font-size: 1rem;
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
letter-spacing: 0.009em;
> :first-child {
margin-right: 10px;
}
@media (max-width: 600px) {
font-size: 0.7rem;
> :first-child {
margin-right: 15px;
}
}
:hover {
background: #2a7e54;
}
`

View File

@@ -1,13 +1,18 @@
import React, { useEffect, useRef } from 'react' import styled from 'styled-components';
import Container from '@material-ui/core/Container' import { useEffect, useRef, useState } from 'react'
import Torrent from './Torrent' import Torrent from './Torrent'
import List from '@material-ui/core/List'
import { Typography } from '@material-ui/core' import { Typography } from '@material-ui/core'
import { torrentsHost } from '../utils/Hosts' import { torrentsHost } from '../utils/Hosts'
export default function TorrentList(props, onChange) { const TorrentListWrapper = styled.div`
const [torrents, setTorrents] = React.useState([]) display: grid;
const [offline, setOffline] = React.useState(true) grid-template-columns: repeat(auto-fit, minmax(250px, 350px));
gap: 30px;
`
export default function TorrentList() {
const [torrents, setTorrents] = useState([])
const [offline, setOffline] = useState(true)
const timerID = useRef(-1) const timerID = useRef(-1)
useEffect(() => { useEffect(() => {
@@ -25,9 +30,11 @@ export default function TorrentList(props, onChange) {
}, []) }, [])
return ( return (
<React.Fragment> <TorrentListWrapper>
<Container maxWidth="lg">{!offline ? <List>{torrents && torrents.map((torrent) => <Torrent key={torrent.hash} torrent={torrent} />)}</List> : <Typography>Offline</Typography>}</Container> {offline ? <Typography>Offline</Typography> : (
</React.Fragment> torrents && torrents.map(torrent => <Torrent key={torrent.hash} torrent={torrent} />)
)}
</TorrentListWrapper>
) )
} }

11
web/src/icons/index.js Normal file
View File

@@ -0,0 +1,11 @@
export const NoImageIcon = () => (
<svg height='80px' width='80px' fill="#248a57"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enableBackground="new 0 0 100 100" xmlSpace="preserve">
<g>
<path d="M18.293,93.801c0.066,0.376,0.284,0.718,0.597,0.937c0.313,0.219,0.708,0.307,1.085,0.241l70.058-12.353 c0.376-0.066,0.718-0.284,0.937-0.597c0.219-0.313,0.307-0.708,0.24-1.085l-9.502-53.891c-0.139-0.79-0.892-1.317-1.682-1.178 l-19.402,3.421L47.997,14.16c0.241-0.706,0.375-1.456,0.375-2.229c0-0.399-0.035-0.804-0.106-1.209C47.671,7.363,44.757,5,41.455,5 c-0.4,0-0.804,0.035-1.209,0.106h0c-3.359,0.595-5.723,3.509-5.723,6.812c0,0.4,0.035,0.804,0.106,1.209 c0.178,1.005,0.567,1.918,1.109,2.709l-6.875,19.061L9.968,38.228c-0.79,0.139-1.317,0.892-1.177,1.682L18.293,93.801z M40.75,7.966L40.75,7.966c0.239-0.042,0.474-0.062,0.705-0.062c1.909,0,3.612,1.373,3.953,3.324v0 c0.042,0.238,0.062,0.473,0.062,0.704c0,1.908-1.373,3.612-3.323,3.953h0.001c-0.238,0.042-0.473,0.062-0.705,0.062 c-1.908,0-3.612-1.373-3.953-3.323c-0.042-0.238-0.062-0.473-0.062-0.705C37.427,10.01,38.799,8.306,40.75,7.966z M38.059,17.96 c1.012,0.569,2.17,0.89,3.383,0.89c0.399,0,0.804-0.034,1.208-0.106h0.001c1.48-0.263,2.766-0.976,3.743-1.974l10.935,13.108 L32.16,34.315L38.059,17.96z M29.978,37.648c0.136-0.004,0.268-0.029,0.396-0.07l29.75-5.246c0.134-0.006,0.266-0.027,0.395-0.07 l18.582-3.277l8.998,51.031L20.9,91.867l-8.998-51.032L29.978,37.648z"></path>
<path d="M49.984,75.561c0.809,0,1.627-0.065,2.449-0.199l0.001,0c7.425-1.213,12.701-7.627,12.701-14.919 c0-0.809-0.065-1.627-0.199-2.449c-1.213-7.425-7.626-12.701-14.919-12.701c-0.808,0-1.627,0.065-2.45,0.199 c-7.425,1.213-12.701,7.626-12.701,14.918c0,0.808,0.065,1.627,0.199,2.449C36.278,70.284,42.692,75.561,49.984,75.561z M51.967,72.496c-0.668,0.109-1.33,0.161-1.983,0.161c-5.883,0-11.079-4.265-12.053-10.265c-0.109-0.668-0.161-1.33-0.161-1.983 c0-2.108,0.555-4.123,1.534-5.892l19.693,14.176C57.206,70.645,54.782,72.039,51.967,72.496z M48.034,48.357L48.034,48.357 c0.668-0.109,1.329-0.161,1.983-0.161c5.882,0,11.079,4.265,12.053,10.265c0.109,0.667,0.161,1.329,0.161,1.983 c0,2.109-0.556,4.127-1.536,5.897L41.001,52.163C42.791,50.21,45.217,48.814,48.034,48.357z"></path>
<polygon points="47.567,45.492 47.567,45.492 47.568,45.491 "></polygon>
</g>
</svg>
)