diff --git a/web/.eslintcache b/web/.eslintcache
index 3d3d2f9..3d15384 100644
--- a/web/.eslintcache
+++ b/web/.eslintcache
@@ -1 +1 @@
-[{"/Users/daniel/Desktop/TorrServer/web/src/index.js":"1","/Users/daniel/Desktop/TorrServer/web/src/App.js":"2","/Users/daniel/Desktop/TorrServer/web/src/components/Appbar.js":"3","/Users/daniel/Desktop/TorrServer/web/src/components/RemoveAll.js":"4","/Users/daniel/Desktop/TorrServer/web/src/components/TorrentList.js":"5","/Users/daniel/Desktop/TorrServer/web/src/components/Settings.js":"6","/Users/daniel/Desktop/TorrServer/web/src/components/Donate.js":"7","/Users/daniel/Desktop/TorrServer/web/src/components/About.js":"8","/Users/daniel/Desktop/TorrServer/web/src/components/Upload.js":"9","/Users/daniel/Desktop/TorrServer/web/src/utils/Hosts.js":"10","/Users/daniel/Desktop/TorrServer/web/src/components/Torrent.js":"11","/Users/daniel/Desktop/TorrServer/web/src/utils/Utils.js":"12","/Users/daniel/Desktop/TorrServer/web/src/components/DialogTorrentInfo.js":"13","/Users/daniel/Desktop/TorrServer/web/src/components/DialogCacheInfo.js":"14","/Users/daniel/Desktop/TorrServer/web/src/components/Add/index.js":"15","/Users/daniel/Desktop/TorrServer/web/src/components/Add/AddDialog.js":"16"},{"size":224,"mtime":1621760841794,"results":"17","hashOfConfig":"18"},{"size":832,"mtime":1621776351526,"results":"19","hashOfConfig":"18"},{"size":6564,"mtime":1621839435540,"results":"20","hashOfConfig":"18"},{"size":1325,"mtime":1621776762321,"results":"21","hashOfConfig":"18"},{"size":1568,"mtime":1621760841794,"results":"22","hashOfConfig":"18"},{"size":9039,"mtime":1621760841794,"results":"23","hashOfConfig":"18"},{"size":3634,"mtime":1621760841793,"results":"24","hashOfConfig":"18"},{"size":2278,"mtime":1621760841792,"results":"25","hashOfConfig":"18"},{"size":1348,"mtime":1621776545759,"results":"26","hashOfConfig":"18"},{"size":796,"mtime":1621840218860,"results":"27","hashOfConfig":"18"},{"size":5999,"mtime":1621760841794,"results":"28","hashOfConfig":"18"},{"size":419,"mtime":1621760841795,"results":"29","hashOfConfig":"18"},{"size":7486,"mtime":1621760841793,"results":"30","hashOfConfig":"18"},{"size":6709,"mtime":1621760841793,"results":"31","hashOfConfig":"18"},{"size":880,"mtime":1621840105311,"results":"32","hashOfConfig":"18"},{"size":2440,"mtime":1621840079751,"results":"33","hashOfConfig":"18"},{"filePath":"34","messages":"35","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},"x6sab4",{"filePath":"37","messages":"38","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"39","messages":"40","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"47","usedDeprecatedRules":"36"},{"filePath":"48","messages":"49","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"50","messages":"51","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"52","messages":"53","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"54","messages":"55","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"58","messages":"59","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"36"},{"filePath":"60","messages":"61","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"62","usedDeprecatedRules":"36"},{"filePath":"63","messages":"64","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"65","usedDeprecatedRules":"36"},{"filePath":"66","messages":"67","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"68","messages":"69","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/daniel/Desktop/TorrServer/web/src/index.js",[],["70","71"],"/Users/daniel/Desktop/TorrServer/web/src/App.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Appbar.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/RemoveAll.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/TorrentList.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Settings.js",["72"],"import ListItem from '@material-ui/core/ListItem'\nimport ListItemIcon from '@material-ui/core/ListItemIcon'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport React, { useEffect } from 'react'\nimport SettingsIcon from '@material-ui/icons/Settings'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport TextField from '@material-ui/core/TextField'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport Button from '@material-ui/core/Button'\nimport { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'\nimport { settingsHost, setTorrServerHost, torrserverHost } from '../utils/Hosts'\n\nexport default function SettingsDialog() {\n const [open, setOpen] = React.useState(false)\n const [settings, setSets] = React.useState({})\n const [show, setShow] = React.useState(false)\n const [tsHost, setTSHost] = React.useState(torrserverHost ? torrserverHost : window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''))\n\n const handleClickOpen = () => {\n setOpen(true)\n }\n const handleClose = () => {\n setOpen(false)\n }\n const handleCloseSave = () => {\n setOpen(false)\n let sets = JSON.parse(JSON.stringify(settings))\n sets.CacheSize *= 1024 * 1024\n sets.PreloadBufferSize *= 1024 * 1024\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'set', sets: sets }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n }\n\n useEffect(() => {\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get' }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n json.CacheSize /= 1024 * 1024\n json.PreloadBufferSize /= 1024 * 1024\n setSets(json)\n setShow(true)\n },\n (error) => {\n setShow(false)\n console.log(error)\n }\n )\n .catch((e) => {\n setShow(false)\n console.log(e)\n })\n }, [tsHost])\n\n const onInputHost = (event) => {\n let host = event.target.value\n setTorrServerHost(host)\n setTSHost(host)\n }\n\n const inputForm = (event) => {\n let sets = JSON.parse(JSON.stringify(settings))\n if (event.target.type === 'number' || event.target.type === 'select-one') {\n sets[event.target.id] = Number(event.target.value)\n } else if (event.target.type === 'checkbox') {\n sets[event.target.id] = Boolean(event.target.checked)\n } else if (event.target.type === 'url') {\n sets[event.target.id] = event.target.value\n }\n setSets(sets)\n }\n\n return (\n
\n \n \n \n \n \n \n \n Settings \n \n \n {show && (\n <>\n \n } label=\"Preload buffer\" />\n \n \n Retracker mode \n \n Don't add retrackers \n Add retrackers \n Remove retrackers \n Replace retrackers \n \n \n } label=\"Enable IPv6\" />\n \n } label=\"Force encrypt\" />\n \n } label=\"Disable TCP\" />\n \n } label=\"Disable UTP\" />\n \n } label=\"Disable UPNP\" />\n \n } label=\"Disable DHT\" />\n \n } label=\"Disable PEX\" />\n \n } label=\"Disable upload\" />\n \n \n \n \n \n \n \n } label=\"Use disk\" />\n \n \n >\n )}\n \n \n \n Cancel\n \n \n Save\n \n \n \n
\n )\n}\n\n","/Users/daniel/Desktop/TorrServer/web/src/components/Donate.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/About.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Upload.js",[],"/Users/daniel/Desktop/TorrServer/web/src/utils/Hosts.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Torrent.js",[],"/Users/daniel/Desktop/TorrServer/web/src/utils/Utils.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/DialogTorrentInfo.js",["73","74","75","76","77"],"import React, { useEffect } from 'react'\nimport Typography from '@material-ui/core/Typography'\nimport { Button, ButtonGroup, Grid, List, ListItem } from '@material-ui/core'\nimport CachedIcon from '@material-ui/icons/Cached'\nimport LinearProgress from '@material-ui/core/LinearProgress';\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport { playlistTorrHost, streamHost, viewedHost } from '../utils/Hosts'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\n\nconst style = {\n width100: {\n width: '100%',\n },\n width80: {\n width: '80%',\n },\n poster: {\n display: 'flex',\n flexDirection: 'row',\n borderRadius: '5px',\n },\n}\n\nexport default function DialogTorrentInfo(props) {\n const [torrent, setTorrent] = React.useState(props.torrent)\n const [viewed, setViewed] = React.useState(null)\n const [progress, setProgress] = React.useState(-1)\n\n useEffect(() => {\n setTorrent(props.torrent)\n if(torrent.stat==2)\n setProgress(torrent.preloaded_bytes * 100 / torrent.preload_size)\n getViewed(props.torrent.hash,(list) => {\n if (list) {\n let lst = list.map((itm) => itm.file_index)\n setViewed(lst)\n }else\n setViewed(null)\n })\n }, [props.torrent, props.open])\n\n return (\n \n
\n \n {torrent.poster && } \n \n {torrent.title} {torrent.name && torrent.name !== torrent.title && ' | ' + torrent.name}\n \n Peers: {getPeerString(torrent)}\n \n Loaded: {getPreload(torrent)}\n \n Speed: {humanizeSize(torrent.download_speed)}\n \n Status: {torrent.stat_string}\n \n \n \n \n {torrent.stat==2 && }\n \n
\n \n \n \n \n Playlist\n \n \n Playlist after last view\n \n {\n remViews(torrent.hash)\n setViewed(null)\n }} >\n Remove views\n \n \n \n {getPlayableFile(torrent) &&\n getPlayableFile(torrent).map((file) => (\n \n \n \n {file.path.split('\\\\').pop().split('/').pop()} | {humanizeSize(file.length)} {viewed && viewed.indexOf(file.id)!=-1 && \"| ✓\"}\n \n \n fetch(streamHost() + '?link=' + torrent.hash + '&index=' + file.id + '&preload')}>\n \n Preload \n \n \n ))}\n
\n \n
\n )\n}\n\nfunction remViews(hash){\n try {\n if (hash)\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'rem', hash: hash, file_index:-1 }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getViewed(hash, callback) {\n try {\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'list', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback(null)\n }\n )\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getPlayableFile(torrent){\n if (!torrent || !torrent.file_stats)\n return null\n return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path)))\n}\n\nfunction getExt(filename){\n const ext = filename.split('.').pop()\n if (ext == filename)\n return ''\n return ext.toLowerCase()\n}\n\nfunction getPreload(torrent) {\n if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) {\n let progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2)\n return humanizeSize(torrent.preloaded_bytes) + ' / ' + humanizeSize(torrent.preload_size) + ' ' + progress + '%'\n }\n\n if (!torrent.preloaded_bytes) return humanizeSize(0)\n\n return humanizeSize(torrent.preloaded_bytes)\n}\n\nconst extPlayable = [\n// video\n \"3g2\",\n \"3gp\",\n \"aaf\",\n \"asf\",\n \"avchd\",\n \"avi\",\n \"drc\",\n \"flv\",\n \"iso\",\n \"m2v\",\n \"m2ts\",\n \"m4p\",\n \"m4v\",\n \"mkv\",\n \"mng\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"mpe\",\n \"mpeg\",\n \"mpg\",\n \"mpv\",\n \"mxf\",\n \"nsv\",\n \"ogg\",\n \"ogv\",\n \"ts\",\n \"qt\",\n \"rm\",\n \"rmvb\",\n \"roq\",\n \"svi\",\n \"vob\",\n \"webm\",\n \"wmv\",\n \"yuv\",\n// audio\n \"aac\",\n \"aiff\",\n \"ape\",\n \"au\",\n \"flac\",\n \"gsm\",\n \"it\",\n \"m3u\",\n \"m4a\",\n \"mid\",\n \"mod\",\n \"mp3\",\n \"mpa\",\n \"pls\",\n \"ra\",\n \"s3m\",\n \"sid\",\n \"wav\",\n \"wma\",\n \"xm\"\n]\n","/Users/daniel/Desktop/TorrServer/web/src/components/DialogCacheInfo.js",["78","79","80","81","82"],"import React, { useEffect, useRef } from 'react'\nimport Typography from '@material-ui/core/Typography'\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport { cacheHost } from '../utils/Hosts'\n\nconst style = {\n cache: {\n paddingLeft: \"6px\",\n paddingRight: \"2px\",\n lineHeight: \"11px\",\n },\n piece: {\n width: \"12px\",\n height: \"12px\",\n backgroundColor: \"#eef2f4\",\n border: \"1px solid #eef2f4\",\n display: \"inline-block\",\n marginRight: \"1px\",\n },\n pieceComplete: {\n backgroundColor: \"#3fb57a\",\n borderColor: \"#3fb57a\",\n },\n pieceLoading: {\n backgroundColor: \"#00d0d0\",\n borderColor: \"#00d0d0\",\n },\n readerRange: {\n borderColor: \"#9a9aff !important\",\n },\n pieceReader: {\n borderColor: \"#000000 !important\",\n },\n pieceProgress: {\n position: \"relative\",\n zIndex: \"1\",\n backgroundColor: \"#009090\",\n\n left: \"-1px\",\n top: \"-1px\",\n width: \"12px\",\n },\n}\n\nexport default function DialogCacheInfo(props) {\n const [hash] = React.useState(props.hash)\n const [cache, setCache] = React.useState({})\n const timerID = useRef(-1)\n const [pMap, setPMap] = React.useState([])\n\n useEffect(() => {\n if (hash)\n timerID.current = setInterval(() => {\n getCache(hash, (cache) => {\n setCache(cache)\n })\n }, 100)\n else clearInterval(timerID.current)\n\n return () => {\n clearInterval(timerID.current)\n }\n }, [hash, props.open])\n\n useEffect(()=>{\n if (cache && cache.PiecesCount && cache.Pieces){\n var map = [];\n for (let i = 0; i < cache.PiecesCount; i++) {\n var reader = 0\n var cls = \"piece\"\n var prc = 0\n if (cache.Pieces[i]) {\n if (cache.Pieces[i].Completed && cache.Pieces[i].Size >= cache.Pieces[i].Length)\n cls += \" piece-complete\"\n else\n cls += \" piece-loading\"\n prc = (cache.Pieces[i].Size / cache.Pieces[i].Length * 100).toFixed(2)\n }\n\n cache.Readers.forEach((r, k) => {\n if (i >= r.Start && i <= r.End && i !== r.Reader)\n cls += \" reader-range\"\n if (i === r.Reader) {\n cls += \" piece-reader\"\n }\n })\n map.push({\n prc: prc,\n class: cls,\n info: i,\n reader: reader,\n })\n }\n setPMap(map)\n }\n },[cache.Pieces])\n\n return (\n \n
\n \n Hash {cache.Hash}\n \n Capacity {humanizeSize(cache.Capacity)}\n \n Filled {humanizeSize(cache.Filled)}\n \n Torrent size {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)}\n \n Pieces length {humanizeSize(cache.PiecesLength)}\n \n Pieces count {cache.PiecesCount}\n \n Peers: {getPeerString(cache.Torrent)}\n \n Download speed {cache.Torrent && cache.Torrent.download_speed ? humanizeSize(cache.Torrent.download_speed) + '/sec' : ''}\n \n Upload speed {cache.Torrent && cache.Torrent.upload_speed ? humanizeSize(cache.Torrent.upload_speed) + '/sec' : ''}\n \n Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}\n \n \n
\n \n {pMap.map((itm) =>
{itm.prc>0 && itm.prc<100 && (
)} )}\n
\n \n
\n )\n}\n\nfunction getCacheMap(cache) {\n if (!cache || !cache.PiecesCount) return ''\n var html = ''\n for (let i = 0; i < cache.PiecesCount; i++) {\n html += \"\"\n }\n }\n cache.Readers.forEach((r,k)=> {\n if (i >= r.Start && i <= r.End && i !== r.Reader)\n html += ' reader-range'\n if (i === r.Reader) {\n html += ' piece-reader'\n info += ' reader'\n }\n })\n html += \"' title='\" + info + \"'>\"\n html += prcDiv\n html += \" \"\n }\n return html\n}\n\nfunction getCache(hash, callback) {\n try {\n fetch(cacheHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback({})\n console.error(error)\n }\n )\n } catch (e) {\n console.error(e)\n callback({})\n }\n}\n/*\n{\n\t\"Hash\": \"41e36c8de915d80db83fc134bee4e7e2d292657e\",\n\t\"Capacity\": 209715200,\n\t\"Filled\": 2914808,\n\t\"PiecesLength\": 4194304,\n\t\"PiecesCount\": 2065,\n\t\"DownloadSpeed\": 32770.860273455524,\n\t\"Pieces\": {\n\t\t\"2064\": {\n\t\t\t\"Id\": 2064,\n\t\t\t\"Length\": 2914808,\n\t\t\t\"Size\": 162296,\n\t\t\t\"Completed\": false\n\t\t}\n\t}\n}\n */\n","/Users/daniel/Desktop/TorrServer/web/src/components/Add/index.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Add/AddDialog.js",[],{"ruleId":"83","replacedBy":"84"},{"ruleId":"85","replacedBy":"86"},{"ruleId":"87","severity":1,"message":"88","line":105,"column":29,"nodeType":"89","endLine":105,"endColumn":35},{"ruleId":"90","severity":1,"message":"91","line":33,"column":24,"nodeType":"92","messageId":"93","endLine":33,"endColumn":26},{"ruleId":"94","severity":1,"message":"95","line":42,"column":8,"nodeType":"96","endLine":42,"endColumn":35,"suggestions":"97"},{"ruleId":"90","severity":1,"message":"91","line":63,"column":30,"nodeType":"92","messageId":"93","endLine":63,"endColumn":32},{"ruleId":"90","severity":1,"message":"98","line":91,"column":152,"nodeType":"92","messageId":"93","endLine":91,"endColumn":154},{"ruleId":"90","severity":1,"message":"91","line":154,"column":13,"nodeType":"92","messageId":"93","endLine":154,"endColumn":15},{"ruleId":"99","severity":1,"message":"100","line":9,"column":7,"nodeType":"101","messageId":"102","endLine":9,"endColumn":12},{"ruleId":"103","severity":1,"message":"104","line":83,"column":39,"nodeType":"105","messageId":"106","endLine":89,"endColumn":18},{"ruleId":"94","severity":1,"message":"107","line":99,"column":7,"nodeType":"96","endLine":99,"endColumn":21,"suggestions":"108"},{"ruleId":"99","severity":1,"message":"109","line":135,"column":10,"nodeType":"101","messageId":"102","endLine":135,"endColumn":21},{"ruleId":"103","severity":1,"message":"110","line":154,"column":31,"nodeType":"105","messageId":"106","endLine":161,"endColumn":10},"no-native-reassign",["111"],"no-negated-in-lhs",["112"],"jsx-a11y/heading-has-content","Headings must have content and the content must be accessible by a screen reader.","JSXOpeningElement","eqeqeq","Expected '===' and instead saw '=='.","BinaryExpression","unexpected","react-hooks/exhaustive-deps","React Hook useEffect has missing dependencies: 'torrent.preload_size', 'torrent.preloaded_bytes', and 'torrent.stat'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setProgress' needs the current value of 'torrent.preloaded_bytes'.","ArrayExpression",["113"],"Expected '!==' and instead saw '!='.","no-unused-vars","'style' is assigned a value but never used.","Identifier","unusedVar","no-loop-func","Function declared in a loop contains unsafe references to variable(s) 'cls', 'cls'.","ArrowFunctionExpression","unsafeRefs","React Hook useEffect has a missing dependency: 'cache'. Either include it or remove the dependency array.",["114"],"'getCacheMap' is defined but never used.","Function declared in a loop contains unsafe references to variable(s) 'html', 'html'.","no-global-assign","no-unsafe-negation",{"desc":"115","fix":"116"},{"desc":"117","fix":"118"},"Update the dependencies array to be: [props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]",{"range":"119","text":"120"},"Update the dependencies array to be: [cache, cache.Pieces]",{"range":"121","text":"122"},[1372,1399],"[props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]",[2893,2907],"[cache, cache.Pieces]"]
\ No newline at end of file
+[{"/Users/daniel/Desktop/TorrServer/web/src/index.js":"1","/Users/daniel/Desktop/TorrServer/web/src/App.js":"2","/Users/daniel/Desktop/TorrServer/web/src/components/RemoveAll.js":"3","/Users/daniel/Desktop/TorrServer/web/src/components/TorrentList.js":"4","/Users/daniel/Desktop/TorrServer/web/src/components/Settings.js":"5","/Users/daniel/Desktop/TorrServer/web/src/components/Donate.js":"6","/Users/daniel/Desktop/TorrServer/web/src/components/About.js":"7","/Users/daniel/Desktop/TorrServer/web/src/components/Upload.js":"8","/Users/daniel/Desktop/TorrServer/web/src/utils/Hosts.js":"9","/Users/daniel/Desktop/TorrServer/web/src/utils/Utils.js":"10","/Users/daniel/Desktop/TorrServer/web/src/components/DialogTorrentInfo.js":"11","/Users/daniel/Desktop/TorrServer/web/src/components/DialogCacheInfo.js":"12","/Users/daniel/Desktop/TorrServer/web/src/components/Add/index.js":"13","/Users/daniel/Desktop/TorrServer/web/src/components/Torrent/index.js":"14","/Users/daniel/Desktop/TorrServer/web/src/components/Add/AddDialog.js":"15","/Users/daniel/Desktop/TorrServer/web/src/components/Torrent/style.js":"16","/Users/daniel/Desktop/TorrServer/web/src/icons/index.js":"17","/Users/daniel/Desktop/TorrServer/web/src/components/Appbar/index.js":"18","/Users/daniel/Desktop/TorrServer/web/src/components/Appbar/useStyles.js":"19"},{"size":224,"mtime":1621760841794,"results":"20","hashOfConfig":"21"},{"size":753,"mtime":1621864551980,"results":"22","hashOfConfig":"21"},{"size":1325,"mtime":1621854678811,"results":"23","hashOfConfig":"21"},{"size":1618,"mtime":1621864039516,"results":"24","hashOfConfig":"21"},{"size":9039,"mtime":1621760841794,"results":"25","hashOfConfig":"21"},{"size":3848,"mtime":1621864449045,"results":"26","hashOfConfig":"21"},{"size":2278,"mtime":1621760841792,"results":"27","hashOfConfig":"21"},{"size":1348,"mtime":1621776545759,"results":"28","hashOfConfig":"21"},{"size":796,"mtime":1621864827984,"results":"29","hashOfConfig":"21"},{"size":419,"mtime":1621760841795,"results":"30","hashOfConfig":"21"},{"size":7486,"mtime":1621760841793,"results":"31","hashOfConfig":"21"},{"size":6709,"mtime":1621760841793,"results":"32","hashOfConfig":"21"},{"size":880,"mtime":1621854678809,"results":"33","hashOfConfig":"21"},{"size":6309,"mtime":1621864815460,"results":"34","hashOfConfig":"21"},{"size":2440,"mtime":1621854678808,"results":"35","hashOfConfig":"21"},{"size":2139,"mtime":1621856125233,"results":"36","hashOfConfig":"21"},{"size":2468,"mtime":1621854721593,"results":"37","hashOfConfig":"21"},{"size":4751,"mtime":1621864635792,"results":"38","hashOfConfig":"21"},{"size":1728,"mtime":1621864630062,"results":"39","hashOfConfig":"21"},{"filePath":"40","messages":"41","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},"x6sab4",{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"51","usedDeprecatedRules":"42"},{"filePath":"52","messages":"53","errorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"54","usedDeprecatedRules":"42"},{"filePath":"55","messages":"56","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"57","messages":"58","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"59","messages":"60","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"61","messages":"62","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"63","messages":"64","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"65","usedDeprecatedRules":"42"},{"filePath":"66","messages":"67","errorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"68","usedDeprecatedRules":"42"},{"filePath":"69","messages":"70","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"71","messages":"72","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"73","messages":"74","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"75","messages":"76","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"77","messages":"78","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"42"},{"filePath":"79","messages":"80","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"81","messages":"82","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/daniel/Desktop/TorrServer/web/src/index.js",[],["83","84"],"/Users/daniel/Desktop/TorrServer/web/src/App.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/RemoveAll.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/TorrentList.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Settings.js",["85"],"import ListItem from '@material-ui/core/ListItem'\nimport ListItemIcon from '@material-ui/core/ListItemIcon'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport React, { useEffect } from 'react'\nimport SettingsIcon from '@material-ui/icons/Settings'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport TextField from '@material-ui/core/TextField'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport Button from '@material-ui/core/Button'\nimport { FormControlLabel, InputLabel, Select, Switch } from '@material-ui/core'\nimport { settingsHost, setTorrServerHost, torrserverHost } from '../utils/Hosts'\n\nexport default function SettingsDialog() {\n const [open, setOpen] = React.useState(false)\n const [settings, setSets] = React.useState({})\n const [show, setShow] = React.useState(false)\n const [tsHost, setTSHost] = React.useState(torrserverHost ? torrserverHost : window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''))\n\n const handleClickOpen = () => {\n setOpen(true)\n }\n const handleClose = () => {\n setOpen(false)\n }\n const handleCloseSave = () => {\n setOpen(false)\n let sets = JSON.parse(JSON.stringify(settings))\n sets.CacheSize *= 1024 * 1024\n sets.PreloadBufferSize *= 1024 * 1024\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'set', sets: sets }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n }\n\n useEffect(() => {\n fetch(settingsHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get' }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n json.CacheSize /= 1024 * 1024\n json.PreloadBufferSize /= 1024 * 1024\n setSets(json)\n setShow(true)\n },\n (error) => {\n setShow(false)\n console.log(error)\n }\n )\n .catch((e) => {\n setShow(false)\n console.log(e)\n })\n }, [tsHost])\n\n const onInputHost = (event) => {\n let host = event.target.value\n setTorrServerHost(host)\n setTSHost(host)\n }\n\n const inputForm = (event) => {\n let sets = JSON.parse(JSON.stringify(settings))\n if (event.target.type === 'number' || event.target.type === 'select-one') {\n sets[event.target.id] = Number(event.target.value)\n } else if (event.target.type === 'checkbox') {\n sets[event.target.id] = Boolean(event.target.checked)\n } else if (event.target.type === 'url') {\n sets[event.target.id] = event.target.value\n }\n setSets(sets)\n }\n\n return (\n \n \n \n \n \n \n \n \n Settings \n \n \n {show && (\n <>\n \n } label=\"Preload buffer\" />\n \n \n Retracker mode \n \n Don't add retrackers \n Add retrackers \n Remove retrackers \n Replace retrackers \n \n \n } label=\"Enable IPv6\" />\n \n } label=\"Force encrypt\" />\n \n } label=\"Disable TCP\" />\n \n } label=\"Disable UTP\" />\n \n } label=\"Disable UPNP\" />\n \n } label=\"Disable DHT\" />\n \n } label=\"Disable PEX\" />\n \n } label=\"Disable upload\" />\n \n \n \n \n \n \n \n } label=\"Use disk\" />\n \n \n >\n )}\n \n \n \n Cancel\n \n \n Save\n \n \n \n
\n )\n}\n\n","/Users/daniel/Desktop/TorrServer/web/src/components/Donate.js",["86","87"],"import React from 'react'\nimport ListItem from '@material-ui/core/ListItem'\nimport ListItemIcon from '@material-ui/core/ListItemIcon'\nimport ListItemText from '@material-ui/core/ListItemText'\nimport Dialog from '@material-ui/core/Dialog'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport DialogActions from '@material-ui/core/DialogActions'\nimport Button from '@material-ui/core/Button'\nimport Snackbar from '@material-ui/core/Snackbar'\nimport IconButton from '@material-ui/core/IconButton'\nimport CreditCardIcon from '@material-ui/icons/CreditCard'\nimport List from '@material-ui/core/List'\nimport ButtonGroup from '@material-ui/core/ButtonGroup'\n\nconst donateFrame =\n ''\n\nexport default function DonateDialog() {\n const [open, setOpen] = React.useState(false)\n const [snakeOpen, setSnakeOpen] = React.useState(true)\n\n // NOT USED FOR NOW\n // const handleClickOpen = () => {\n // setOpen(true)\n // }\n const handleClose = () => {\n setOpen(false)\n }\n\n return (\n \n {/* !!!!!!!!!!! Should be removed or moved to sidebar because it is not visible. It is hiddent behind header */}\n {/*
\n \n \n \n \n */}\n {/* !!!!!!!!!!!!!!!!!!!! */}\n\n
\n Donate \n \n \n \n \n window.open('https://www.paypal.com/paypalme/yourok', '_blank')}>PayPal \n window.open('https://yoomoney.ru/to/410013733697114', '_blank')}>Yandex.Money \n \n \n \n
\n \n
\n \n \n \n Ok\n \n \n \n\n
{ setSnakeOpen(false) }}\n autoHideDuration={6000}\n message=\"Donate?\"\n action={\n \n {\n setSnakeOpen(false)\n setOpen(true)\n }}>\n \n \n \n }\n />\n \n )\n}\n","/Users/daniel/Desktop/TorrServer/web/src/components/About.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Upload.js",[],"/Users/daniel/Desktop/TorrServer/web/src/utils/Hosts.js",[],"/Users/daniel/Desktop/TorrServer/web/src/utils/Utils.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/DialogTorrentInfo.js",["88","89","90","91","92"],"import React, { useEffect } from 'react'\nimport Typography from '@material-ui/core/Typography'\nimport { Button, ButtonGroup, Grid, List, ListItem } from '@material-ui/core'\nimport CachedIcon from '@material-ui/icons/Cached'\nimport LinearProgress from '@material-ui/core/LinearProgress';\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport { playlistTorrHost, streamHost, viewedHost } from '../utils/Hosts'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\n\nconst style = {\n width100: {\n width: '100%',\n },\n width80: {\n width: '80%',\n },\n poster: {\n display: 'flex',\n flexDirection: 'row',\n borderRadius: '5px',\n },\n}\n\nexport default function DialogTorrentInfo(props) {\n const [torrent, setTorrent] = React.useState(props.torrent)\n const [viewed, setViewed] = React.useState(null)\n const [progress, setProgress] = React.useState(-1)\n\n useEffect(() => {\n setTorrent(props.torrent)\n if(torrent.stat==2)\n setProgress(torrent.preloaded_bytes * 100 / torrent.preload_size)\n getViewed(props.torrent.hash,(list) => {\n if (list) {\n let lst = list.map((itm) => itm.file_index)\n setViewed(lst)\n }else\n setViewed(null)\n })\n }, [props.torrent, props.open])\n\n return (\n \n
\n \n {torrent.poster && } \n \n {torrent.title} {torrent.name && torrent.name !== torrent.title && ' | ' + torrent.name}\n \n Peers: {getPeerString(torrent)}\n \n Loaded: {getPreload(torrent)}\n \n Speed: {humanizeSize(torrent.download_speed)}\n \n Status: {torrent.stat_string}\n \n \n \n \n {torrent.stat==2 && }\n \n
\n \n \n \n \n Playlist\n \n \n Playlist after last view\n \n {\n remViews(torrent.hash)\n setViewed(null)\n }} >\n Remove views\n \n \n \n {getPlayableFile(torrent) &&\n getPlayableFile(torrent).map((file) => (\n \n \n \n {file.path.split('\\\\').pop().split('/').pop()} | {humanizeSize(file.length)} {viewed && viewed.indexOf(file.id)!=-1 && \"| ✓\"}\n \n \n fetch(streamHost() + '?link=' + torrent.hash + '&index=' + file.id + '&preload')}>\n \n Preload \n \n \n ))}\n
\n \n
\n )\n}\n\nfunction remViews(hash){\n try {\n if (hash)\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'rem', hash: hash, file_index:-1 }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getViewed(hash, callback) {\n try {\n fetch(viewedHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'list', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback(null)\n }\n )\n } catch (e) {\n console.error(e)\n }\n}\n\nfunction getPlayableFile(torrent){\n if (!torrent || !torrent.file_stats)\n return null\n return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path)))\n}\n\nfunction getExt(filename){\n const ext = filename.split('.').pop()\n if (ext == filename)\n return ''\n return ext.toLowerCase()\n}\n\nfunction getPreload(torrent) {\n if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) {\n let progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2)\n return humanizeSize(torrent.preloaded_bytes) + ' / ' + humanizeSize(torrent.preload_size) + ' ' + progress + '%'\n }\n\n if (!torrent.preloaded_bytes) return humanizeSize(0)\n\n return humanizeSize(torrent.preloaded_bytes)\n}\n\nconst extPlayable = [\n// video\n \"3g2\",\n \"3gp\",\n \"aaf\",\n \"asf\",\n \"avchd\",\n \"avi\",\n \"drc\",\n \"flv\",\n \"iso\",\n \"m2v\",\n \"m2ts\",\n \"m4p\",\n \"m4v\",\n \"mkv\",\n \"mng\",\n \"mov\",\n \"mp2\",\n \"mp4\",\n \"mpe\",\n \"mpeg\",\n \"mpg\",\n \"mpv\",\n \"mxf\",\n \"nsv\",\n \"ogg\",\n \"ogv\",\n \"ts\",\n \"qt\",\n \"rm\",\n \"rmvb\",\n \"roq\",\n \"svi\",\n \"vob\",\n \"webm\",\n \"wmv\",\n \"yuv\",\n// audio\n \"aac\",\n \"aiff\",\n \"ape\",\n \"au\",\n \"flac\",\n \"gsm\",\n \"it\",\n \"m3u\",\n \"m4a\",\n \"mid\",\n \"mod\",\n \"mp3\",\n \"mpa\",\n \"pls\",\n \"ra\",\n \"s3m\",\n \"sid\",\n \"wav\",\n \"wma\",\n \"xm\"\n]\n","/Users/daniel/Desktop/TorrServer/web/src/components/DialogCacheInfo.js",["93","94","95","96","97"],"import React, { useEffect, useRef } from 'react'\nimport Typography from '@material-ui/core/Typography'\n\nimport { getPeerString, humanizeSize } from '../utils/Utils'\nimport DialogTitle from '@material-ui/core/DialogTitle'\nimport DialogContent from '@material-ui/core/DialogContent'\nimport { cacheHost } from '../utils/Hosts'\n\nconst style = {\n cache: {\n paddingLeft: \"6px\",\n paddingRight: \"2px\",\n lineHeight: \"11px\",\n },\n piece: {\n width: \"12px\",\n height: \"12px\",\n backgroundColor: \"#eef2f4\",\n border: \"1px solid #eef2f4\",\n display: \"inline-block\",\n marginRight: \"1px\",\n },\n pieceComplete: {\n backgroundColor: \"#3fb57a\",\n borderColor: \"#3fb57a\",\n },\n pieceLoading: {\n backgroundColor: \"#00d0d0\",\n borderColor: \"#00d0d0\",\n },\n readerRange: {\n borderColor: \"#9a9aff !important\",\n },\n pieceReader: {\n borderColor: \"#000000 !important\",\n },\n pieceProgress: {\n position: \"relative\",\n zIndex: \"1\",\n backgroundColor: \"#009090\",\n\n left: \"-1px\",\n top: \"-1px\",\n width: \"12px\",\n },\n}\n\nexport default function DialogCacheInfo(props) {\n const [hash] = React.useState(props.hash)\n const [cache, setCache] = React.useState({})\n const timerID = useRef(-1)\n const [pMap, setPMap] = React.useState([])\n\n useEffect(() => {\n if (hash)\n timerID.current = setInterval(() => {\n getCache(hash, (cache) => {\n setCache(cache)\n })\n }, 100)\n else clearInterval(timerID.current)\n\n return () => {\n clearInterval(timerID.current)\n }\n }, [hash, props.open])\n\n useEffect(()=>{\n if (cache && cache.PiecesCount && cache.Pieces){\n var map = [];\n for (let i = 0; i < cache.PiecesCount; i++) {\n var reader = 0\n var cls = \"piece\"\n var prc = 0\n if (cache.Pieces[i]) {\n if (cache.Pieces[i].Completed && cache.Pieces[i].Size >= cache.Pieces[i].Length)\n cls += \" piece-complete\"\n else\n cls += \" piece-loading\"\n prc = (cache.Pieces[i].Size / cache.Pieces[i].Length * 100).toFixed(2)\n }\n\n cache.Readers.forEach((r, k) => {\n if (i >= r.Start && i <= r.End && i !== r.Reader)\n cls += \" reader-range\"\n if (i === r.Reader) {\n cls += \" piece-reader\"\n }\n })\n map.push({\n prc: prc,\n class: cls,\n info: i,\n reader: reader,\n })\n }\n setPMap(map)\n }\n },[cache.Pieces])\n\n return (\n \n
\n \n Hash {cache.Hash}\n \n Capacity {humanizeSize(cache.Capacity)}\n \n Filled {humanizeSize(cache.Filled)}\n \n Torrent size {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)}\n \n Pieces length {humanizeSize(cache.PiecesLength)}\n \n Pieces count {cache.PiecesCount}\n \n Peers: {getPeerString(cache.Torrent)}\n \n Download speed {cache.Torrent && cache.Torrent.download_speed ? humanizeSize(cache.Torrent.download_speed) + '/sec' : ''}\n \n Upload speed {cache.Torrent && cache.Torrent.upload_speed ? humanizeSize(cache.Torrent.upload_speed) + '/sec' : ''}\n \n Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}\n \n \n
\n \n {pMap.map((itm) =>
{itm.prc>0 && itm.prc<100 && (
)} )}\n
\n \n
\n )\n}\n\nfunction getCacheMap(cache) {\n if (!cache || !cache.PiecesCount) return ''\n var html = ''\n for (let i = 0; i < cache.PiecesCount; i++) {\n html += \"\"\n }\n }\n cache.Readers.forEach((r,k)=> {\n if (i >= r.Start && i <= r.End && i !== r.Reader)\n html += ' reader-range'\n if (i === r.Reader) {\n html += ' piece-reader'\n info += ' reader'\n }\n })\n html += \"' title='\" + info + \"'>\"\n html += prcDiv\n html += \" \"\n }\n return html\n}\n\nfunction getCache(hash, callback) {\n try {\n fetch(cacheHost(), {\n method: 'post',\n body: JSON.stringify({ action: 'get', hash: hash }),\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json',\n },\n })\n .then((res) => res.json())\n .then(\n (json) => {\n callback(json)\n },\n (error) => {\n callback({})\n console.error(error)\n }\n )\n } catch (e) {\n console.error(e)\n callback({})\n }\n}\n/*\n{\n\t\"Hash\": \"41e36c8de915d80db83fc134bee4e7e2d292657e\",\n\t\"Capacity\": 209715200,\n\t\"Filled\": 2914808,\n\t\"PiecesLength\": 4194304,\n\t\"PiecesCount\": 2065,\n\t\"DownloadSpeed\": 32770.860273455524,\n\t\"Pieces\": {\n\t\t\"2064\": {\n\t\t\t\"Id\": 2064,\n\t\t\t\"Length\": 2914808,\n\t\t\t\"Size\": 162296,\n\t\t\t\"Completed\": false\n\t\t}\n\t}\n}\n */\n","/Users/daniel/Desktop/TorrServer/web/src/components/Add/index.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Torrent/index.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Add/AddDialog.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Torrent/style.js",[],"/Users/daniel/Desktop/TorrServer/web/src/icons/index.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Appbar/index.js",[],"/Users/daniel/Desktop/TorrServer/web/src/components/Appbar/useStyles.js",[],{"ruleId":"98","replacedBy":"99"},{"ruleId":"100","replacedBy":"101"},{"ruleId":"102","severity":1,"message":"103","line":105,"column":29,"nodeType":"104","endLine":105,"endColumn":35},{"ruleId":"105","severity":1,"message":"106","line":3,"column":8,"nodeType":"107","messageId":"108","endLine":3,"endColumn":20},{"ruleId":"105","severity":1,"message":"109","line":4,"column":8,"nodeType":"107","messageId":"108","endLine":4,"endColumn":20},{"ruleId":"110","severity":1,"message":"111","line":33,"column":24,"nodeType":"112","messageId":"113","endLine":33,"endColumn":26},{"ruleId":"114","severity":1,"message":"115","line":42,"column":8,"nodeType":"116","endLine":42,"endColumn":35,"suggestions":"117"},{"ruleId":"110","severity":1,"message":"111","line":63,"column":30,"nodeType":"112","messageId":"113","endLine":63,"endColumn":32},{"ruleId":"110","severity":1,"message":"118","line":91,"column":152,"nodeType":"112","messageId":"113","endLine":91,"endColumn":154},{"ruleId":"110","severity":1,"message":"111","line":154,"column":13,"nodeType":"112","messageId":"113","endLine":154,"endColumn":15},{"ruleId":"105","severity":1,"message":"119","line":9,"column":7,"nodeType":"107","messageId":"108","endLine":9,"endColumn":12},{"ruleId":"120","severity":1,"message":"121","line":83,"column":39,"nodeType":"122","messageId":"123","endLine":89,"endColumn":18},{"ruleId":"114","severity":1,"message":"124","line":99,"column":7,"nodeType":"116","endLine":99,"endColumn":21,"suggestions":"125"},{"ruleId":"105","severity":1,"message":"126","line":135,"column":10,"nodeType":"107","messageId":"108","endLine":135,"endColumn":21},{"ruleId":"120","severity":1,"message":"127","line":154,"column":31,"nodeType":"122","messageId":"123","endLine":161,"endColumn":10},"no-native-reassign",["128"],"no-negated-in-lhs",["129"],"jsx-a11y/heading-has-content","Headings must have content and the content must be accessible by a screen reader.","JSXOpeningElement","no-unused-vars","'ListItemIcon' is defined but never used.","Identifier","unusedVar","'ListItemText' is defined but never used.","eqeqeq","Expected '===' and instead saw '=='.","BinaryExpression","unexpected","react-hooks/exhaustive-deps","React Hook useEffect has missing dependencies: 'torrent.preload_size', 'torrent.preloaded_bytes', and 'torrent.stat'. Either include them or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setProgress' needs the current value of 'torrent.preloaded_bytes'.","ArrayExpression",["130"],"Expected '!==' and instead saw '!='.","'style' is assigned a value but never used.","no-loop-func","Function declared in a loop contains unsafe references to variable(s) 'cls', 'cls'.","ArrowFunctionExpression","unsafeRefs","React Hook useEffect has a missing dependency: 'cache'. Either include it or remove the dependency array.",["131"],"'getCacheMap' is defined but never used.","Function declared in a loop contains unsafe references to variable(s) 'html', 'html'.","no-global-assign","no-unsafe-negation",{"desc":"132","fix":"133"},{"desc":"134","fix":"135"},"Update the dependencies array to be: [props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]",{"range":"136","text":"137"},"Update the dependencies array to be: [cache, cache.Pieces]",{"range":"138","text":"139"},[1372,1399],"[props.torrent, props.open, torrent.stat, torrent.preloaded_bytes, torrent.preload_size]",[2893,2907],"[cache, cache.Pieces]"]
\ No newline at end of file
diff --git a/web/package-lock.json b/web/package-lock.json
index 7c22399..a6a77ea 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -15,7 +15,8 @@
"material-ui-image": "^3.3.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
- "react-scripts": "4.0.1"
+ "react-scripts": "4.0.1",
+ "styled-components": "^5.3.0"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
@@ -1248,6 +1249,29 @@
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+ "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+ "dependencies": {
+ "@emotion/memoize": "0.7.4"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
+ },
+ "node_modules/@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
"node_modules/@eslint/eslintrc": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
@@ -4215,6 +4239,25 @@
"resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz",
"integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw=="
},
+ "node_modules/babel-plugin-styled-components": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz",
+ "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.0.0",
+ "@babel/helper-module-imports": "^7.0.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11"
+ },
+ "peerDependencies": {
+ "styled-components": ">= 2"
+ }
+ },
+ "node_modules/babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
"node_modules/babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
@@ -5083,6 +5126,11 @@
"node": ">=10"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -5893,6 +5941,14 @@
"node": ">=6.0.0"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@@ -6004,6 +6060,16 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "node_modules/css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -18880,6 +18946,11 @@
"node": ">=8"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"node_modules/shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -19673,6 +19744,35 @@
"node": ">= 8.9.0"
}
},
+ "node_modules/styled-components": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.0.tgz",
+ "integrity": "sha512-bPJKwZCHjJPf/hwTJl6TbkSZg/3evha+XPEizrZUGb535jLImwDUdjTNxXqjjaASt2M4qO4AVfoHJNe3XB/tpQ==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^0.8.8",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0",
+ "react-is": ">= 16.8.0"
+ }
+ },
"node_modules/stylehacks": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz",
@@ -23957,6 +24057,29 @@
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
+ "@emotion/is-prop-valid": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+ "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+ "requires": {
+ "@emotion/memoize": "0.7.4"
+ }
+ },
+ "@emotion/memoize": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+ "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw=="
+ },
+ "@emotion/stylis": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
+ "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ=="
+ },
+ "@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
"@eslint/eslintrc": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
@@ -26467,6 +26590,22 @@
"resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz",
"integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw=="
},
+ "babel-plugin-styled-components": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz",
+ "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==",
+ "requires": {
+ "@babel/helper-annotate-as-pure": "^7.0.0",
+ "@babel/helper-module-imports": "^7.0.0",
+ "babel-plugin-syntax-jsx": "^6.18.0",
+ "lodash": "^4.17.11"
+ }
+ },
+ "babel-plugin-syntax-jsx": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
+ "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
+ },
"babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
@@ -27254,6 +27393,11 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg=="
},
+ "camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
+ },
"caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -27950,6 +28094,11 @@
"postcss": "^7.0.5"
}
},
+ "css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
+ },
"css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
@@ -28033,6 +28182,16 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -38640,6 +38799,11 @@
"kind-of": "^6.0.2"
}
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -39326,6 +39490,23 @@
"schema-utils": "^2.7.0"
}
},
+ "styled-components": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.0.tgz",
+ "integrity": "sha512-bPJKwZCHjJPf/hwTJl6TbkSZg/3evha+XPEizrZUGb535jLImwDUdjTNxXqjjaASt2M4qO4AVfoHJNe3XB/tpQ==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.0.0",
+ "@babel/traverse": "^7.4.5",
+ "@emotion/is-prop-valid": "^0.8.8",
+ "@emotion/stylis": "^0.8.4",
+ "@emotion/unitless": "^0.7.4",
+ "babel-plugin-styled-components": ">= 1.12.0",
+ "css-to-react-native": "^3.0.0",
+ "hoist-non-react-statics": "^3.0.0",
+ "shallowequal": "^1.1.0",
+ "supports-color": "^5.5.0"
+ }
+ },
"stylehacks": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz",
diff --git a/web/package.json b/web/package.json
index 8f9b4ba..b5c9d4e 100644
--- a/web/package.json
+++ b/web/package.json
@@ -9,7 +9,8 @@
"material-ui-image": "^3.3.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
- "react-scripts": "4.0.1"
+ "react-scripts": "4.0.1",
+ "styled-components": "^5.3.0"
},
"scripts": {
"start": "react-scripts start",
diff --git a/web/src/App.js b/web/src/App.js
index e8529d7..9609b91 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -1,6 +1,5 @@
-import React from 'react'
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'
const baseTheme = createMuiTheme({
@@ -26,11 +25,9 @@ const baseTheme = createMuiTheme({
export default function App() {
return (
-
-
-
-
-
-
+
+
+
+
)
}
diff --git a/web/src/components/Appbar.js b/web/src/components/Appbar/index.js
similarity index 59%
rename from web/src/components/Appbar.js
rename to web/src/components/Appbar/index.js
index 38d7741..75708cb 100644
--- a/web/src/components/Appbar.js
+++ b/web/src/components/Appbar/index.js
@@ -1,11 +1,10 @@
-import React, { useEffect } from 'react'
+import { useEffect, useState } from 'react'
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 AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import List from '@material-ui/core/List'
-import CssBaseline from '@material-ui/core/CssBaseline'
import Typography from '@material-ui/core/Typography'
import Divider from '@material-ui/core/Divider'
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 PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew'
-import TorrentList from './TorrentList'
-import { Box } from '@material-ui/core'
+import TorrentList from '../TorrentList'
-import AddDialogButton from './Add'
-import RemoveAll from './RemoveAll'
-import SettingsDialog from './Settings'
-import AboutDialog from './About'
-import { playlistAllHost, shutdownHost, torrserverHost } from '../utils/Hosts'
-import DonateDialog from './Donate'
-import UploadDialog from './Upload'
-
-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),
- },
-}))
+import AddDialogButton from '../Add'
+import RemoveAll from '../RemoveAll'
+import SettingsDialog from '../Settings'
+import AboutDialog from '../About'
+import { playlistAllHost, shutdownHost, torrserverHost } from '../../utils/Hosts'
+import DonateDialog from '../Donate'
+import UploadDialog from '../Upload'
+import useStyles from './useStyles'
export default function MiniDrawer() {
const classes = useStyles()
const theme = useTheme()
- const [open, setOpen] = React.useState(false)
- const [tsVersion, setTSVersion] = React.useState('')
+ const [open, setOpen] = useState(false)
+ const [tsVersion, setTSVersion] = useState('')
const handleDrawerOpen = () => {
setOpen(true)
@@ -118,7 +53,6 @@ export default function MiniDrawer() {
return (
-
+
- {theme.direction === 'rtl' ? : }
+
+ {theme.direction === 'rtl' ? : }
+
+
+
@@ -170,10 +109,11 @@ export default function MiniDrawer() {
+
+
-
fetch(shutdownHost())}>
@@ -182,12 +122,15 @@ export default function MiniDrawer() {
-
+
-
+
+
+
+
)
}
diff --git a/web/src/components/Appbar/useStyles.js b/web/src/components/Appbar/useStyles.js
new file mode 100644
index 0000000..d0cdb59
--- /dev/null
+++ b/web/src/components/Appbar/useStyles.js
@@ -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),
+ },
+}))
\ No newline at end of file
diff --git a/web/src/components/Donate.js b/web/src/components/Donate.js
index e612d6e..35bfe94 100644
--- a/web/src/components/Donate.js
+++ b/web/src/components/Donate.js
@@ -20,21 +20,25 @@ export default function DonateDialog() {
const [open, setOpen] = React.useState(false)
const [snakeOpen, setSnakeOpen] = React.useState(true)
- const handleClickOpen = () => {
- setOpen(true)
- }
+ // NOT USED FOR NOW
+ // const handleClickOpen = () => {
+ // setOpen(true)
+ // }
const handleClose = () => {
setOpen(false)
}
return (
-
+ {/* !!!!!!!!!!! Should be removed or moved to sidebar because it is not visible. It is hiddent behind header */}
+ {/*
-
+ */}
+ {/* !!!!!!!!!!!!!!!!!!!! */}
+
Donate
@@ -63,12 +67,12 @@ export default function DonateDialog() {
horizontal: 'center',
}}
open={snakeOpen}
- onClose={()=>{setSnakeOpen(false)}}
+ onClose={() => { setSnakeOpen(false) }}
autoHideDuration={6000}
message="Donate?"
action={
- {
+ {
setSnakeOpen(false)
setOpen(true)
}}>
diff --git a/web/src/components/Torrent.js b/web/src/components/Torrent/index.js
similarity index 56%
rename from web/src/components/Torrent.js
rename to web/src/components/Torrent/index.js
index 167565b..1d905a3 100644
--- a/web/src/components/Torrent.js
+++ b/web/src/components/Torrent/index.js
@@ -1,27 +1,27 @@
-import React, { useEffect, useRef } from 'react'
-import ButtonGroup from '@material-ui/core/ButtonGroup'
+import { useEffect, useRef, useState } from 'react'
import Button from '@material-ui/core/Button'
import 'fontsource-roboto'
+import HeightIcon from '@material-ui/icons/Height';
import CloseIcon from '@material-ui/icons/Close';
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 Dialog from '@material-ui/core/Dialog'
-import { getPeerString, humanizeSize } from '../utils/Utils'
+import { getPeerString, humanizeSize } from '../../utils/Utils'
-import DialogTorrentInfo from './DialogTorrentInfo'
-import { torrentsHost } from '../utils/Hosts'
-import DialogCacheInfo from './DialogCacheInfo'
+import DialogTorrentInfo from '../DialogTorrentInfo'
+import { torrentsHost } from '../../utils/Hosts'
+import DialogCacheInfo from '../DialogCacheInfo'
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) {
- const [open, setOpen] = React.useState(false)
- const [showCache, setShowCache] = React.useState(false)
- const [torrent, setTorrent] = React.useState(props.torrent)
+ const [open, setOpen] = useState(false)
+ const [showCache, setShowCache] = useState(false)
+ const [torrent, setTorrent] = useState(props.torrent)
const timerID = useRef(-1)
useEffect(() => {
@@ -43,61 +43,74 @@ export default function Torrent(props) {
}
}, [torrent.hash, open])
+ const { title, name, poster, torrent_size, download_speed } = torrent
+
return (
-
-
-
- {
- setShowCache(false)
- setOpen(true)
- }}
- >
- {torrent.poster &&
-
- }
-
- {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) : '' }
-
-
-
+
+
+
+ {poster
+ ?
+ : }
+
+
+
+ {
setShowCache(true)
setOpen(true)
}}
>
- Cache
-
- {
- dropTorrent(torrent)
- }}
+ Cache
+
+
+ dropTorrent(torrent)}
>
- Drop
-
- {
- deleteTorrent(torrent)
- }}
+ Drop
+
+
+ deleteTorrent(torrent)}
>
- Delete
-
-
-
+ Delete
+
+
+
{
+ setShowCache(false)
+ setOpen(true)
+ }}
+ >
+
+ Details
+
+
+
+
+ Name
+ {title || name}
+
+ Size
+ {torrent_size > 0 && humanizeSize(torrent_size)}
+
+ Download speed
+ {download_speed > 0 ? humanizeSize(download_speed) : '---'}
+
+ Peers
+ {getPeerString(torrent) || '---'}
+
+
+
{
- setOpen(false)
- }}
+ onClose={() => setOpen(false)}
aria-labelledby="form-dialog-title"
- fullWidth={true}
+ fullWidth
maxWidth={'lg'}
>
{!showCache ? : }
@@ -105,15 +118,13 @@ export default function Torrent(props) {
{
- setOpen(false)
- }}
+ onClick={() => setOpen(false)}
>
OK
-
+ >
)
}
diff --git a/web/src/components/Torrent/style.js b/web/src/components/Torrent/style.js
new file mode 100644
index 0000000..68874f4
--- /dev/null
+++ b/web/src/components/Torrent/style.js
@@ -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;
+ }
+`
\ No newline at end of file
diff --git a/web/src/components/TorrentList.js b/web/src/components/TorrentList.js
index cd6cdca..e33fb05 100644
--- a/web/src/components/TorrentList.js
+++ b/web/src/components/TorrentList.js
@@ -1,13 +1,18 @@
-import React, { useEffect, useRef } from 'react'
-import Container from '@material-ui/core/Container'
+import styled from 'styled-components';
+import { useEffect, useRef, useState } from 'react'
import Torrent from './Torrent'
-import List from '@material-ui/core/List'
import { Typography } from '@material-ui/core'
import { torrentsHost } from '../utils/Hosts'
-export default function TorrentList(props, onChange) {
- const [torrents, setTorrents] = React.useState([])
- const [offline, setOffline] = React.useState(true)
+const TorrentListWrapper = styled.div`
+ display: grid;
+ 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)
useEffect(() => {
@@ -25,9 +30,11 @@ export default function TorrentList(props, onChange) {
}, [])
return (
-
- {!offline ? {torrents && torrents.map((torrent) => )}
: Offline }
-
+
+ {offline ? Offline : (
+ torrents && torrents.map(torrent => )
+ )}
+
)
}
diff --git a/web/src/icons/index.js b/web/src/icons/index.js
new file mode 100644
index 0000000..7ccab70
--- /dev/null
+++ b/web/src/icons/index.js
@@ -0,0 +1,11 @@
+export const NoImageIcon = () => (
+
+
+
+
+
+
+
+)
\ No newline at end of file