mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 21:46:11 +05:00
added different page for detailed cache view
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
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 { ArrowBack } from '@material-ui/icons'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(theme => ({
|
||||||
|
appBar: { position: 'relative' },
|
||||||
|
title: { marginLeft: theme.spacing(2), flex: 1 },
|
||||||
|
}))
|
||||||
|
|
||||||
|
export default function DialogHeader({ title, onClose, onBack }) {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar className={classes.appBar}>
|
||||||
|
<Toolbar>
|
||||||
|
<IconButton edge='start' color='inherit' onClick={onBack || onClose} aria-label='close'>
|
||||||
|
{onBack ? <ArrowBack /> : <CloseIcon />}
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Typography variant='h6' className={classes.title}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{onBack && (
|
||||||
|
<Button autoFocus color='inherit' onClick={onClose}>
|
||||||
|
close
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Rect } from 'react-konva'
|
||||||
|
|
||||||
|
export default function SingleBlock({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
percentage,
|
||||||
|
isActive = false,
|
||||||
|
inProgress = false,
|
||||||
|
isReaderRange = false,
|
||||||
|
isComplete = false,
|
||||||
|
boxHeight,
|
||||||
|
strokeWidth,
|
||||||
|
}) {
|
||||||
|
const strokeColor = isActive
|
||||||
|
? '#000'
|
||||||
|
: isComplete
|
||||||
|
? '#3fb57a'
|
||||||
|
: inProgress
|
||||||
|
? '#00d0d0'
|
||||||
|
: isReaderRange
|
||||||
|
? '#9a9aff'
|
||||||
|
: '#eef2f4'
|
||||||
|
const backgroundColor = inProgress ? '#00d0d0' : '#eef2f4'
|
||||||
|
const percentageProgressColor = '#3fb57a'
|
||||||
|
const processCompletedColor = '#3fb57a'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Rect
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
stroke={strokeColor}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
height={boxHeight}
|
||||||
|
width={boxHeight}
|
||||||
|
fillAfterStrokeEnabled
|
||||||
|
preventDefault={false}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...(isComplete
|
||||||
|
? { fill: processCompletedColor }
|
||||||
|
: inProgress && {
|
||||||
|
fillLinearGradientStartPointY: boxHeight,
|
||||||
|
fillLinearGradientEndPointY: 0,
|
||||||
|
fillLinearGradientColorStops: [
|
||||||
|
0,
|
||||||
|
percentageProgressColor,
|
||||||
|
percentage,
|
||||||
|
percentageProgressColor,
|
||||||
|
percentage,
|
||||||
|
backgroundColor,
|
||||||
|
],
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
100
web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx
Normal file
100
web/src/components/DialogTorrentDetailsContent/TorrentCache.jsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import DialogContent from '@material-ui/core/DialogContent'
|
||||||
|
import { Stage, Layer } from 'react-konva'
|
||||||
|
import Measure from 'react-measure'
|
||||||
|
|
||||||
|
import SingleBlock from './SingleBlock'
|
||||||
|
|
||||||
|
export default function TorrentCache({ cache, cacheMap, isMini }) {
|
||||||
|
const [dimensions, setDimensions] = useState({ width: -1, height: -1 })
|
||||||
|
const [stageSettings, setStageSettings] = useState({
|
||||||
|
boxHeight: null,
|
||||||
|
strokeWidth: null,
|
||||||
|
marginBetweenBlocks: null,
|
||||||
|
stageOffset: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateStageSettings = (boxHeight, strokeWidth) => {
|
||||||
|
setStageSettings({
|
||||||
|
boxHeight,
|
||||||
|
strokeWidth,
|
||||||
|
marginBetweenBlocks: strokeWidth,
|
||||||
|
stageOffset: strokeWidth * 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// initializing stageSettings
|
||||||
|
isMini ? updateStageSettings(24, 4) : updateStageSettings(12, 2)
|
||||||
|
}, [isMini])
|
||||||
|
|
||||||
|
const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings
|
||||||
|
const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1)
|
||||||
|
const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks
|
||||||
|
const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin)
|
||||||
|
const amountOfBlocksToRenderInShortView =
|
||||||
|
preloadPiecesAmount === piecesInOneRow
|
||||||
|
? preloadPiecesAmount - 1
|
||||||
|
: preloadPiecesAmount + piecesInOneRow - (preloadPiecesAmount % piecesInOneRow) - 1
|
||||||
|
const amountOfRows = Math.ceil((isMini ? amountOfBlocksToRenderInShortView : cacheMap.length) / piecesInOneRow)
|
||||||
|
let activeId = null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Measure bounds onResize={contentRect => setDimensions(contentRect.bounds)}>
|
||||||
|
{({ measureRef }) => (
|
||||||
|
<div ref={measureRef}>
|
||||||
|
<DialogContent style={{ padding: 0 }}>
|
||||||
|
<Stage
|
||||||
|
style={{ display: 'flex', justifyContent: 'center' }}
|
||||||
|
offset={{ x: -stageOffset, y: -stageOffset }}
|
||||||
|
width={stageOffset + blockSizeWithMargin * piecesInOneRow || 0}
|
||||||
|
height={stageOffset + blockSizeWithMargin * amountOfRows || 0}
|
||||||
|
>
|
||||||
|
<Layer>
|
||||||
|
{cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => {
|
||||||
|
const currentRow = Math.floor((isMini ? id - activeId : id) / piecesInOneRow)
|
||||||
|
|
||||||
|
// -------- related only for short view -------
|
||||||
|
if (isActive) activeId = id
|
||||||
|
const shouldBeRendered =
|
||||||
|
isActive || (id - activeId <= amountOfBlocksToRenderInShortView && id - activeId >= 0)
|
||||||
|
// --------------------------------------------
|
||||||
|
|
||||||
|
return isMini ? (
|
||||||
|
shouldBeRendered && (
|
||||||
|
<SingleBlock
|
||||||
|
key={id}
|
||||||
|
x={((id - activeId) % piecesInOneRow) * blockSizeWithMargin}
|
||||||
|
y={currentRow * blockSizeWithMargin}
|
||||||
|
percentage={percentage}
|
||||||
|
inProgress={inProgress}
|
||||||
|
isComplete={isComplete}
|
||||||
|
isReaderRange={isReaderRange}
|
||||||
|
isActive={isActive}
|
||||||
|
boxHeight={boxHeight}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<SingleBlock
|
||||||
|
key={id}
|
||||||
|
x={(id % piecesInOneRow) * blockSizeWithMargin}
|
||||||
|
y={currentRow * blockSizeWithMargin}
|
||||||
|
percentage={percentage}
|
||||||
|
inProgress={inProgress}
|
||||||
|
isComplete={isComplete}
|
||||||
|
isReaderRange={isReaderRange}
|
||||||
|
isActive={isActive}
|
||||||
|
boxHeight={boxHeight}
|
||||||
|
strokeWidth={strokeWidth}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Layer>
|
||||||
|
</Stage>
|
||||||
|
</DialogContent>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Measure>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
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 styled, { css } from 'styled-components'
|
||||||
import { NoImageIcon } from 'icons'
|
import { NoImageIcon } from 'icons'
|
||||||
import { getPeerString, humanizeSize } from 'utils/Utils'
|
import { getPeerString, humanizeSize } from 'utils/Utils'
|
||||||
import { viewedHost } from 'utils/Hosts'
|
import { viewedHost } from 'utils/Hosts'
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Button } from '@material-ui/core'
|
||||||
|
|
||||||
import { useUpdateCache, useCreateCacheMap } from './customHooks'
|
import { useUpdateCache, useCreateCacheMap } from './customHooks'
|
||||||
|
import DialogHeader from './DialogHeader'
|
||||||
|
import TorrentCache from './TorrentCache'
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const DialogContentGrid = styled.div`
|
||||||
appBar: { position: 'relative' },
|
|
||||||
title: { marginLeft: theme.spacing(2), flex: 1 },
|
|
||||||
}))
|
|
||||||
|
|
||||||
const DialogContent = styled.div`
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: min-content 200px 80px 70px;
|
grid-template-rows: min-content min-content 80px min-content;
|
||||||
`
|
`
|
||||||
const Poster = styled.div`
|
const Poster = styled.div`
|
||||||
${({ poster }) => css`
|
${({ poster }) => css`
|
||||||
@@ -44,11 +39,12 @@ const Poster = styled.div`
|
|||||||
`}
|
`}
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
const HeaderSection = styled.section`
|
const TorrentMainSection = styled.section`
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
gap: 30px;
|
gap: 30px;
|
||||||
|
background: lightgray;
|
||||||
`
|
`
|
||||||
|
|
||||||
const TorrentData = styled.div`
|
const TorrentData = styled.div`
|
||||||
@@ -59,7 +55,8 @@ const TorrentData = styled.div`
|
|||||||
|
|
||||||
const CacheSection = styled.section`
|
const CacheSection = styled.section`
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
background: lightgray;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ButtonSection = styled.section`
|
const ButtonSection = styled.section`
|
||||||
@@ -86,6 +83,16 @@ const ButtonSectionButton = styled.div`
|
|||||||
:hover {
|
:hover {
|
||||||
background: red;
|
background: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hash-group {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hash-text {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #7c7b7c;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const TorrentFilesSection = styled.div``
|
const TorrentFilesSection = styled.div``
|
||||||
@@ -99,11 +106,22 @@ const TorrentSubName = styled.div`
|
|||||||
color: #7c7b7c;
|
color: #7c7b7c;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const SectionTitle = styled.div`
|
||||||
|
font-size: 35px;
|
||||||
|
font-weight: 200;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const DetailedTorrentCacheViewWrapper = styled.div`
|
||||||
|
padding-top: 50px;
|
||||||
|
`
|
||||||
|
|
||||||
const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '')
|
const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '')
|
||||||
|
|
||||||
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
||||||
const classes = useStyles()
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const [isDetailedCacheView, setIsDetailedCacheView] = useState(false)
|
||||||
const {
|
const {
|
||||||
poster,
|
poster,
|
||||||
hash,
|
hash,
|
||||||
@@ -118,31 +136,31 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
const cache = useUpdateCache(hash)
|
const cache = useUpdateCache(hash)
|
||||||
const cacheMap = useCreateCacheMap(cache)
|
const cacheMap = useCreateCacheMap(cache)
|
||||||
|
|
||||||
useEffect(() => setIsLoading(false), [cacheMap])
|
useEffect(() => {
|
||||||
|
const torrentLoaded = torrent.stat_string !== 'Torrent in db' && torrent.stat_string !== 'Torrent getting info'
|
||||||
|
torrentLoaded && isLoading && setIsLoading(false)
|
||||||
|
}, [torrent, isLoading])
|
||||||
|
|
||||||
const { Capacity, PiecesCount, PiecesLength } = cache
|
const { Capacity, PiecesCount, PiecesLength } = cache
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppBar className={classes.appBar}>
|
<DialogHeader
|
||||||
<Toolbar>
|
onClose={closeDialog}
|
||||||
<IconButton edge='start' color='inherit' onClick={closeDialog} aria-label='close'>
|
title={isDetailedCacheView ? 'Detailed Cache View' : 'Torrent Details'}
|
||||||
<CloseIcon />
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
</IconButton>
|
{...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })}
|
||||||
<Typography variant='h6' className={classes.title}>
|
/>
|
||||||
Torrent Details
|
|
||||||
</Typography>
|
|
||||||
<Button autoFocus color='inherit' onClick={closeDialog}>
|
|
||||||
close
|
|
||||||
</Button>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
'loading'
|
'loading'
|
||||||
|
) : isDetailedCacheView ? (
|
||||||
|
<DetailedTorrentCacheViewWrapper>
|
||||||
|
<TorrentCache cache={cache} cacheMap={cacheMap} />
|
||||||
|
</DetailedTorrentCacheViewWrapper>
|
||||||
) : (
|
) : (
|
||||||
<DialogContent>
|
<DialogContentGrid>
|
||||||
<HeaderSection>
|
<TorrentMainSection>
|
||||||
<Poster poster={poster}>{poster ? <img alt='poster' src={poster} /> : <NoImageIcon />}</Poster>
|
<Poster poster={poster}>{poster ? <img alt='poster' src={poster} /> : <NoImageIcon />}</Poster>
|
||||||
|
|
||||||
<TorrentData>
|
<TorrentData>
|
||||||
@@ -168,13 +186,32 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
<div>PiecesCount: {PiecesCount}</div>
|
<div>PiecesCount: {PiecesCount}</div>
|
||||||
<div>PiecesLength: {humanizeSize(PiecesLength)}</div>
|
<div>PiecesLength: {humanizeSize(PiecesLength)}</div>
|
||||||
</TorrentData>
|
</TorrentData>
|
||||||
</HeaderSection>
|
</TorrentMainSection>
|
||||||
|
|
||||||
<CacheSection />
|
<CacheSection>
|
||||||
|
<SectionTitle>Cache</SectionTitle>
|
||||||
|
|
||||||
|
<TorrentCache isMini cache={cache} cacheMap={cacheMap} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style={{ alignSelf: 'flex-end', marginTop: '30px' }}
|
||||||
|
variant='contained'
|
||||||
|
color='primary'
|
||||||
|
size='large'
|
||||||
|
onClick={() => setIsDetailedCacheView(true)}
|
||||||
|
>
|
||||||
|
Detailed cache view
|
||||||
|
</Button>
|
||||||
|
</CacheSection>
|
||||||
|
|
||||||
<ButtonSection>
|
<ButtonSection>
|
||||||
<CopyToClipboard text={hash}>
|
<CopyToClipboard text={hash}>
|
||||||
<ButtonSectionButton>copy hash</ButtonSectionButton>
|
<ButtonSectionButton>
|
||||||
|
<div className='hash-group'>
|
||||||
|
<div>copy hash</div>
|
||||||
|
<div className='hash-text'>{hash}</div>
|
||||||
|
</div>
|
||||||
|
</ButtonSectionButton>
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
|
|
||||||
<ButtonSectionButton>remove views</ButtonSectionButton>
|
<ButtonSectionButton>remove views</ButtonSectionButton>
|
||||||
@@ -187,7 +224,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
|||||||
</ButtonSection>
|
</ButtonSection>
|
||||||
|
|
||||||
<TorrentFilesSection />
|
<TorrentFilesSection />
|
||||||
</DialogContent>
|
</DialogContentGrid>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user