mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 13:36:09 +05:00
added full width dialog for cache and info
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.4",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"axios": "^0.21.1",
|
||||
"clsx": "^1.1.1",
|
||||
"fontsource-roboto": "^4.0.0",
|
||||
"konva": "^8.0.1",
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import { getPeerString, humanizeSize } from 'utils/Utils'
|
||||
import { cacheHost } from 'utils/Hosts'
|
||||
import { Stage, Layer } from 'react-konva'
|
||||
import Measure from 'react-measure'
|
||||
import { useUpdateCache, useCreateCacheMap } from 'components/DialogTorrentDetailsContent/customHooks'
|
||||
|
||||
import SingleBlock from './SingleBlock'
|
||||
|
||||
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 [isShortView, setIsShortView] = useState(true)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@@ -24,6 +20,9 @@ export default function DialogCacheInfo({ hash }) {
|
||||
stageOffset: null,
|
||||
})
|
||||
|
||||
const cache = useUpdateCache(hash)
|
||||
const cacheMap = useCreateCacheMap(cache, () => setIsLoading(false))
|
||||
|
||||
const updateStageSettings = (boxHeight, strokeWidth) => {
|
||||
setStageSettings({
|
||||
boxHeight,
|
||||
@@ -36,59 +35,8 @@ export default function DialogCacheInfo({ hash }) {
|
||||
useEffect(() => {
|
||||
// initializing stageSettings
|
||||
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 preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1)
|
||||
@@ -98,7 +46,7 @@ export default function DialogCacheInfo({ hash }) {
|
||||
preloadPiecesAmount === piecesInOneRow
|
||||
? preloadPiecesAmount - 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
|
||||
|
||||
return (
|
||||
@@ -158,7 +106,7 @@ export default function DialogCacheInfo({ hash }) {
|
||||
height={stageOffset + blockSizeWithMargin * amountOfRows}
|
||||
>
|
||||
<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)
|
||||
|
||||
// -------- 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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
275
web/src/components/DialogTorrentDetailsContent/index.jsx
Normal file
275
web/src/components/DialogTorrentDetailsContent/index.jsx
Normal 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',
|
||||
]
|
||||
@@ -1,18 +1,20 @@
|
||||
/* eslint-disable camelcase */
|
||||
import 'fontsource-roboto'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import { forwardRef, useEffect, useRef, useState } from 'react'
|
||||
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 CloseIcon from '@material-ui/icons/Close'
|
||||
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 { getPeerString, humanizeSize } from 'utils/Utils'
|
||||
import { torrentsHost } from 'utils/Hosts'
|
||||
import { NoImageIcon } from 'icons'
|
||||
import DialogTorrentInfo from 'components/DialogTorrentInfo'
|
||||
import DialogCacheInfo from 'components/DialogCacheInfo'
|
||||
import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import Slide from '@material-ui/core/Slide'
|
||||
import { Button } from '@material-ui/core'
|
||||
|
||||
import {
|
||||
StyledButton,
|
||||
@@ -25,6 +27,9 @@ import {
|
||||
TorrentCardDetails,
|
||||
} 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 }) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [showCache, setShowCache] = useState(false)
|
||||
@@ -122,7 +127,11 @@ export default function Torrent({ torrent }) {
|
||||
</TorrentCardDescription>
|
||||
</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 ? (
|
||||
<DialogCacheInfo hash={torrentLocalComponentValue.hash} />
|
||||
) : (
|
||||
@@ -133,7 +142,7 @@ export default function Torrent({ torrent }) {
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Dialog> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2748,6 +2748,13 @@ axe-core@^4.0.2:
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.1.tgz#2e50bcf10ee5b819014f6e342e41e45096239e34"
|
||||
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:
|
||||
version "2.2.0"
|
||||
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"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
|
||||
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
|
||||
|
||||
Reference in New Issue
Block a user