mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 13:36:09 +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 { NoImageIcon } from 'icons'
|
||||
import { getPeerString, humanizeSize } from 'utils/Utils'
|
||||
import { viewedHost } from 'utils/Hosts'
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from '@material-ui/core'
|
||||
|
||||
import { useUpdateCache, useCreateCacheMap } from './customHooks'
|
||||
import DialogHeader from './DialogHeader'
|
||||
import TorrentCache from './TorrentCache'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
appBar: { position: 'relative' },
|
||||
title: { marginLeft: theme.spacing(2), flex: 1 },
|
||||
}))
|
||||
|
||||
const DialogContent = styled.div`
|
||||
const DialogContentGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-rows: min-content 200px 80px 70px;
|
||||
grid-template-rows: min-content min-content 80px min-content;
|
||||
`
|
||||
const Poster = styled.div`
|
||||
${({ poster }) => css`
|
||||
@@ -44,11 +39,12 @@ const Poster = styled.div`
|
||||
`}
|
||||
`}
|
||||
`
|
||||
const HeaderSection = styled.section`
|
||||
const TorrentMainSection = styled.section`
|
||||
padding: 40px;
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
gap: 30px;
|
||||
background: lightgray;
|
||||
`
|
||||
|
||||
const TorrentData = styled.div`
|
||||
@@ -59,7 +55,8 @@ const TorrentData = styled.div`
|
||||
|
||||
const CacheSection = styled.section`
|
||||
padding: 40px;
|
||||
background: lightgray;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const ButtonSection = styled.section`
|
||||
@@ -86,6 +83,16 @@ const ButtonSectionButton = styled.div`
|
||||
:hover {
|
||||
background: red;
|
||||
}
|
||||
|
||||
.hash-group {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.hash-text {
|
||||
font-size: 10px;
|
||||
color: #7c7b7c;
|
||||
}
|
||||
`
|
||||
|
||||
const TorrentFilesSection = styled.div``
|
||||
@@ -99,11 +106,22 @@ const TorrentSubName = styled.div`
|
||||
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 ? '...' : '')
|
||||
|
||||
export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
||||
const classes = useStyles()
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isDetailedCacheView, setIsDetailedCacheView] = useState(false)
|
||||
const {
|
||||
poster,
|
||||
hash,
|
||||
@@ -118,31 +136,31 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
||||
const cache = useUpdateCache(hash)
|
||||
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
|
||||
|
||||
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>
|
||||
<DialogHeader
|
||||
onClose={closeDialog}
|
||||
title={isDetailedCacheView ? 'Detailed Cache View' : 'Torrent Details'}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...(isDetailedCacheView && { onBack: () => setIsDetailedCacheView(false) })}
|
||||
/>
|
||||
|
||||
{isLoading ? (
|
||||
'loading'
|
||||
) : isDetailedCacheView ? (
|
||||
<DetailedTorrentCacheViewWrapper>
|
||||
<TorrentCache cache={cache} cacheMap={cacheMap} />
|
||||
</DetailedTorrentCacheViewWrapper>
|
||||
) : (
|
||||
<DialogContent>
|
||||
<HeaderSection>
|
||||
<DialogContentGrid>
|
||||
<TorrentMainSection>
|
||||
<Poster poster={poster}>{poster ? <img alt='poster' src={poster} /> : <NoImageIcon />}</Poster>
|
||||
|
||||
<TorrentData>
|
||||
@@ -168,13 +186,32 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
||||
<div>PiecesCount: {PiecesCount}</div>
|
||||
<div>PiecesLength: {humanizeSize(PiecesLength)}</div>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<ButtonSectionButton>remove views</ButtonSectionButton>
|
||||
@@ -187,7 +224,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
|
||||
</ButtonSection>
|
||||
|
||||
<TorrentFilesSection />
|
||||
</DialogContent>
|
||||
</DialogContentGrid>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user