added full width dialog for cache and info

This commit is contained in:
Daniel Shleifman
2021-05-27 17:21:39 +03:00
parent e3b276f02b
commit 6b5a572918
6 changed files with 382 additions and 88 deletions

View File

@@ -5,6 +5,7 @@
"dependencies": { "dependencies": {
"@material-ui/core": "^4.11.4", "@material-ui/core": "^4.11.4",
"@material-ui/icons": "^4.11.2", "@material-ui/icons": "^4.11.2",
"axios": "^0.21.1",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"fontsource-roboto": "^4.0.0", "fontsource-roboto": "^4.0.0",
"konva": "^8.0.1", "konva": "^8.0.1",

View File

@@ -1,19 +1,15 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useState } from 'react'
import Typography from '@material-ui/core/Typography' import Typography from '@material-ui/core/Typography'
import DialogTitle from '@material-ui/core/DialogTitle' import DialogTitle from '@material-ui/core/DialogTitle'
import DialogContent from '@material-ui/core/DialogContent' import DialogContent from '@material-ui/core/DialogContent'
import { getPeerString, humanizeSize } from 'utils/Utils' import { getPeerString, humanizeSize } from 'utils/Utils'
import { cacheHost } from 'utils/Hosts'
import { Stage, Layer } from 'react-konva' import { Stage, Layer } from 'react-konva'
import Measure from 'react-measure' import Measure from 'react-measure'
import { useUpdateCache, useCreateCacheMap } from 'components/DialogTorrentDetailsContent/customHooks'
import SingleBlock from './SingleBlock' import SingleBlock from './SingleBlock'
export default function DialogCacheInfo({ hash }) { export default function DialogCacheInfo({ hash }) {
const [cache, setCache] = useState({})
const [pMap, setPMap] = useState([])
const timerID = useRef(null)
const componentIsMounted = useRef(true)
const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) const [dimensions, setDimensions] = useState({ width: -1, height: -1 })
const [isShortView, setIsShortView] = useState(true) const [isShortView, setIsShortView] = useState(true)
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@@ -24,6 +20,9 @@ export default function DialogCacheInfo({ hash }) {
stageOffset: null, stageOffset: null,
}) })
const cache = useUpdateCache(hash)
const cacheMap = useCreateCacheMap(cache, () => setIsLoading(false))
const updateStageSettings = (boxHeight, strokeWidth) => { const updateStageSettings = (boxHeight, strokeWidth) => {
setStageSettings({ setStageSettings({
boxHeight, boxHeight,
@@ -36,59 +35,8 @@ export default function DialogCacheInfo({ hash }) {
useEffect(() => { useEffect(() => {
// initializing stageSettings // initializing stageSettings
updateStageSettings(24, 4) updateStageSettings(24, 4)
return () => {
// this function is required to notify "getCache" when NOT to make state update
componentIsMounted.current = false
}
}, []) }, [])
useEffect(() => {
if (hash) {
timerID.current = setInterval(() => {
getCache(hash, value => {
// this is required to avoid memory leak
if (componentIsMounted.current) setCache(value)
})
}, 100)
} else clearInterval(timerID.current)
return () => {
clearInterval(timerID.current)
}
}, [hash])
useEffect(() => {
if (!cache.PiecesCount || !cache.Pieces) return
const { Pieces, PiecesCount, Readers } = cache
const map = []
for (let i = 0; i < PiecesCount; i++) {
const newPiece = { id: i }
const currentPiece = Pieces[i]
if (currentPiece) {
if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) newPiece.isComplete = true
else {
newPiece.inProgress = true
newPiece.percentage = (currentPiece.Size / currentPiece.Length).toFixed(2)
}
}
Readers.forEach(r => {
if (i === r.Reader) newPiece.isActive = true
if (i >= r.Start && i <= r.End) newPiece.isReaderRange = true
})
map.push(newPiece)
}
setPMap(map)
setIsLoading(false)
}, [cache])
const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings
const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1)
@@ -98,7 +46,7 @@ export default function DialogCacheInfo({ hash }) {
preloadPiecesAmount === piecesInOneRow preloadPiecesAmount === piecesInOneRow
? preloadPiecesAmount - 1 ? preloadPiecesAmount - 1
: preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1 : preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1
const amountOfRows = Math.ceil((isShortView ? amountOfBlocksToRenderInShortView : pMap.length) / piecesInOneRow) const amountOfRows = Math.ceil((isShortView ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow)
let activeId = null let activeId = null
return ( return (
@@ -158,7 +106,7 @@ export default function DialogCacheInfo({ hash }) {
height={stageOffset + blockSizeWithMargin * amountOfRows} height={stageOffset + blockSizeWithMargin * amountOfRows}
> >
<Layer> <Layer>
{pMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { {cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => {
const currentRow = Math.floor((isShortView ? id - activeId : id) / piecesInOneRow) const currentRow = Math.floor((isShortView ? id - activeId : id) / piecesInOneRow)
// -------- related only for short view ------- // -------- related only for short view -------
@@ -207,26 +155,6 @@ export default function DialogCacheInfo({ hash }) {
) )
} }
const getCache = (hash, callback) => {
try {
fetch(cacheHost(), {
method: 'post',
body: JSON.stringify({ action: 'get', hash }),
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
},
})
.then(res => res.json())
.then(callback, error => {
callback({})
console.error(error)
})
} catch (e) {
console.error(e)
callback({})
}
}
/* /*
{ {
"Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e", "Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e",

View File

@@ -0,0 +1,74 @@
import { useEffect, useRef, useState } from 'react'
import { cacheHost } from 'utils/Hosts'
import axios from 'axios'
export const useUpdateCache = hash => {
const [cache, setCache] = useState({})
const componentIsMounted = useRef(true)
const timerID = useRef(null)
useEffect(
() => () => {
// this function is required to notify "updateCache" when NOT to make state update
componentIsMounted.current = false
},
[],
)
useEffect(() => {
if (hash) {
timerID.current = setInterval(() => {
const updateCache = newCache => componentIsMounted.current && setCache(newCache)
axios
.post(cacheHost(), { action: 'get', hash })
.then(({ data }) => updateCache(data))
// empty cache if error
.catch(() => updateCache({}))
}, 100)
} else clearInterval(timerID.current)
return () => {
clearInterval(timerID.current)
}
}, [hash])
return cache
}
export const useCreateCacheMap = (cache, callback) => {
const [cacheMap, setCacheMap] = useState([])
useEffect(() => {
if (!cache.PiecesCount || !cache.Pieces) return
const { Pieces, PiecesCount, Readers } = cache
const map = []
for (let i = 0; i < PiecesCount; i++) {
const newPiece = { id: i }
const currentPiece = Pieces[i]
if (currentPiece) {
if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) newPiece.isComplete = true
else {
newPiece.inProgress = true
newPiece.percentage = (currentPiece.Size / currentPiece.Length).toFixed(2)
}
}
Readers.forEach(r => {
if (i === r.Reader) newPiece.isActive = true
if (i >= r.Start && i <= r.End) newPiece.isReaderRange = true
})
map.push(newPiece)
}
setCacheMap(map)
callback && callback()
}, [cache, callback])
return cacheMap
}

View File

@@ -0,0 +1,275 @@
import Button from '@material-ui/core/Button'
import { AppBar, IconButton, makeStyles, Toolbar, Typography } from '@material-ui/core'
import CloseIcon from '@material-ui/icons/Close'
import styled, { css } from 'styled-components'
import { NoImageIcon } from 'icons'
import { getPeerString, humanizeSize } from 'utils/Utils'
import { viewedHost } from 'utils/Hosts'
import { useUpdateCache, useCreateCacheMap } from './customHooks'
const useStyles = makeStyles(theme => ({
appBar: { position: 'relative' },
title: { marginLeft: theme.spacing(2), flex: 1 },
}))
const DialogContent = styled.div`
display: grid;
grid-template-rows: min-content 200px 80px 70px;
`
const Poster = styled.div`
${({ poster }) => css`
height: 400px;
border-radius: 5px;
overflow: hidden;
${poster
? css`
img {
border-radius: 5px;
height: 100%;
}
`
: css`
width: 300px;
display: grid;
place-items: center;
background: #74c39c;
svg {
transform: scale(2.5) translateY(-3px);
}
`}
`}
`
const HeaderSection = styled.section`
padding: 40px;
display: grid;
grid-template-columns: min-content 1fr;
gap: 30px;
`
const TorrentData = styled.div``
const CacheSection = styled.section`
padding: 40px;
background: lightgray;
`
const ButtonSection = styled.section`
box-shadow: 0px 4px 4px -1px rgb(0 0 0 / 30%);
display: flex;
justify-content: space-evenly;
align-items: center;
text-transform: uppercase;
`
const ButtonSectionButton = styled.div`
background: lightblue;
height: 100%;
flex: 1;
display: grid;
place-items: center;
cursor: pointer;
font-size: 15px;
:not(:last-child) {
border-right: 1px solid blue;
}
:hover {
background: red;
}
`
const TorrentFilesSection = styled.div``
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
const classes = useStyles()
const {
poster,
hash,
title,
name,
download_speed: downloadSpeed,
upload_speed: uploadSpeed,
stat_string: statString,
torrent_size: torrentSize,
} = torrent
const cache = useUpdateCache(hash)
const cacheMap = useCreateCacheMap(cache)
const { Capacity, PiecesCount, PiecesLength } = cache
return (
<>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton edge='start' color='inherit' onClick={closeDialog} aria-label='close'>
<CloseIcon />
</IconButton>
<Typography variant='h6' className={classes.title}>
Torrent Details
</Typography>
<Button autoFocus color='inherit' onClick={closeDialog}>
close
</Button>
</Toolbar>
</AppBar>
<DialogContent>
<HeaderSection>
<Poster poster={poster}>{poster ? <img alt='poster' src={poster} /> : <NoImageIcon />}</Poster>
<TorrentData>
<div>hash: {hash}</div>
<div>title: {title}</div>
<div>name: {name}</div>
<div>peers: {getPeerString(torrent)}</div>
<div>loaded: {getPreload(torrent)}</div>
<div>download speed: {humanizeSize(downloadSpeed)}</div>
<div>upload speed: {humanizeSize(uploadSpeed)}</div>
<div>status: {statString}</div>
<div>torrent size: {humanizeSize(torrentSize)}</div>
<div>Capacity: {humanizeSize(Capacity)}</div>
<div>PiecesCount: {PiecesCount}</div>
<div>PiecesLength: {humanizeSize(PiecesLength)}</div>
</TorrentData>
</HeaderSection>
<CacheSection />
<ButtonSection>
<ButtonSectionButton>copy hash</ButtonSectionButton>
<ButtonSectionButton>remove views</ButtonSectionButton>
<ButtonSectionButton>drop torrent</ButtonSectionButton>
<ButtonSectionButton>download playlist</ButtonSectionButton>
<ButtonSectionButton>download playlist after last view</ButtonSectionButton>
</ButtonSection>
<TorrentFilesSection />
</DialogContent>
</>
)
}
function getPreload(torrent) {
if (torrent.preloaded_bytes > 0 && torrent.preload_size > 0 && torrent.preloaded_bytes < torrent.preload_size) {
const progress = ((torrent.preloaded_bytes * 100) / torrent.preload_size).toFixed(2)
return `${humanizeSize(torrent.preloaded_bytes)} / ${humanizeSize(torrent.preload_size)} ${progress}%`
}
if (!torrent.preloaded_bytes) return humanizeSize(0)
return humanizeSize(torrent.preloaded_bytes)
}
function remViews(hash) {
try {
if (hash)
fetch(viewedHost(), {
method: 'post',
body: JSON.stringify({ action: 'rem', hash, file_index: -1 }),
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
},
})
} catch (e) {
console.error(e)
}
}
function getViewed(hash, callback) {
try {
fetch(viewedHost(), {
method: 'post',
body: JSON.stringify({ action: 'list', hash }),
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
},
})
.then(res => res.json())
.then(callback)
} catch (e) {
console.error(e)
}
}
function getPlayableFile(torrent) {
if (!torrent || !torrent.file_stats) return null
return torrent.file_stats.filter(file => extPlayable.includes(getExt(file.path)))
}
function getExt(filename) {
const ext = filename.split('.').pop()
if (ext === filename) return ''
return ext.toLowerCase()
}
const extPlayable = [
// video
'3g2',
'3gp',
'aaf',
'asf',
'avchd',
'avi',
'drc',
'flv',
'iso',
'm2v',
'm2ts',
'm4p',
'm4v',
'mkv',
'mng',
'mov',
'mp2',
'mp4',
'mpe',
'mpeg',
'mpg',
'mpv',
'mxf',
'nsv',
'ogg',
'ogv',
'ts',
'qt',
'rm',
'rmvb',
'roq',
'svi',
'vob',
'webm',
'wmv',
'yuv',
// audio
'aac',
'aiff',
'ape',
'au',
'flac',
'gsm',
'it',
'm3u',
'm4a',
'mid',
'mod',
'mp3',
'mpa',
'pls',
'ra',
's3m',
'sid',
'wav',
'wma',
'xm',
]

View File

@@ -1,18 +1,20 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import 'fontsource-roboto' import 'fontsource-roboto'
import { useEffect, useRef, useState } from 'react' import { forwardRef, useEffect, useRef, useState } from 'react'
import Button from '@material-ui/core/Button' import DialogActions from '@material-ui/core/DialogActions'
import DialogTorrentInfo from 'components/DialogTorrentInfo'
import DialogCacheInfo from 'components/DialogCacheInfo'
import HeightIcon from '@material-ui/icons/Height' 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 DialogActions from '@material-ui/core/DialogActions'
import Dialog from '@material-ui/core/Dialog'
import DataUsageIcon from '@material-ui/icons/DataUsage' import DataUsageIcon from '@material-ui/icons/DataUsage'
import { getPeerString, humanizeSize } from 'utils/Utils' import { getPeerString, humanizeSize } from 'utils/Utils'
import { torrentsHost } from 'utils/Hosts' import { torrentsHost } from 'utils/Hosts'
import { NoImageIcon } from 'icons' import { NoImageIcon } from 'icons'
import DialogTorrentInfo from 'components/DialogTorrentInfo' import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent'
import DialogCacheInfo from 'components/DialogCacheInfo' import Dialog from '@material-ui/core/Dialog'
import Slide from '@material-ui/core/Slide'
import { Button } from '@material-ui/core'
import { import {
StyledButton, StyledButton,
@@ -25,6 +27,9 @@ import {
TorrentCardDetails, TorrentCardDetails,
} from './style' } from './style'
// eslint-disable-next-line react/jsx-props-no-spreading
const Transition = forwardRef((props, ref) => <Slide direction='up' ref={ref} {...props} />)
export default function Torrent({ torrent }) { export default function Torrent({ torrent }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [showCache, setShowCache] = useState(false) const [showCache, setShowCache] = useState(false)
@@ -122,7 +127,11 @@ export default function Torrent({ torrent }) {
</TorrentCardDescription> </TorrentCardDescription>
</TorrentCard> </TorrentCard>
<Dialog open={open} onClose={() => setOpen(false)} aria-labelledby='form-dialog-title' fullWidth maxWidth='lg'> <Dialog open={open} fullScreen TransitionComponent={Transition}>
<DialogTorrentDetailsContent closeDialog={() => setOpen(false)} torrent={torrentLocalComponentValue} />
</Dialog>
{/* <Dialog open={open} fullScreen>
{showCache ? ( {showCache ? (
<DialogCacheInfo hash={torrentLocalComponentValue.hash} /> <DialogCacheInfo hash={torrentLocalComponentValue.hash} />
) : ( ) : (
@@ -133,7 +142,7 @@ export default function Torrent({ torrent }) {
OK OK
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog> */}
</> </>
) )
} }

View File

@@ -2748,6 +2748,13 @@ axe-core@^4.0.2:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.1.tgz#2e50bcf10ee5b819014f6e342e41e45096239e34" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.1.tgz#2e50bcf10ee5b819014f6e342e41e45096239e34"
integrity sha512-evY7DN8qSIbsW2H/TWQ1bX3sXN1d4MNb5Vb4n7BzPuCwRHdkZ1H2eNLuSh73EoQqkGKUtju2G2HCcjCfhvZIAA== integrity sha512-evY7DN8qSIbsW2H/TWQ1bX3sXN1d4MNb5Vb4n7BzPuCwRHdkZ1H2eNLuSh73EoQqkGKUtju2G2HCcjCfhvZIAA==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
axobject-query@^2.2.0: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -5727,7 +5734,7 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
inherits "^2.0.3" inherits "^2.0.3"
readable-stream "^2.3.6" readable-stream "^2.3.6"
follow-redirects@^1.0.0: follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==