This commit is contained in:
Daniel Shleifman
2021-06-15 18:05:45 +03:00
parent 31d4311dd3
commit b0ffc3e6ed
9 changed files with 129 additions and 356 deletions

View File

@@ -10,7 +10,6 @@
"fontsource-roboto": "^4.0.0", "fontsource-roboto": "^4.0.0",
"i18next": "^20.3.1", "i18next": "^20.3.1",
"i18next-browser-languagedetector": "^6.1.1", "i18next-browser-languagedetector": "^6.1.1",
"konva": "^8.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"material-ui-image": "^3.3.2", "material-ui-image": "^3.3.2",
"parse-torrent": "^9.1.3", "parse-torrent": "^9.1.3",
@@ -21,7 +20,6 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-dropzone": "^11.3.2", "react-dropzone": "^11.3.2",
"react-i18next": "^11.10.0", "react-i18next": "^11.10.0",
"react-konva": "^17.0.2-4",
"react-measure": "^2.5.2", "react-measure": "^2.5.2",
"react-query": "^3.17.0", "react-query": "^3.17.0",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",

View File

@@ -1,127 +0,0 @@
import { useEffect, useState } from 'react'
import DialogContent from '@material-ui/core/DialogContent'
import { Stage, Layer } from 'react-konva'
import Measure from 'react-measure'
import { v4 as uuidv4 } from 'uuid'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import SingleBlock from './SingleBlock'
import getShortCacheMap from './getShortCacheMap'
const ScrollNotification = styled.div`
margin-top: 10px;
text-transform: uppercase;
color: rgba(0, 0, 0, 0.5);
align-self: center;
`
export default function DefaultSnake({ isMini, cacheMap, preloadPiecesAmount }) {
const { t } = useTranslation()
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
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
if (isMini) return dimensions.width < 500 ? updateStageSettings(20, 3) : updateStageSettings(24, 4)
updateStageSettings(12, 2)
}, [isMini, dimensions.width])
const miniCacheMaxHeight = 340
const { boxHeight, strokeWidth, marginBetweenBlocks, stageOffset } = stageSettings
const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks
const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin)
const shortCacheMap = isMini ? getShortCacheMap({ cacheMap, preloadPiecesAmount, piecesInOneRow }) : []
const amountOfRows = Math.ceil((isMini ? shortCacheMap.length : cacheMap.length) / piecesInOneRow)
const getItemCoordinates = blockOrder => {
const currentRow = Math.floor(blockOrder / piecesInOneRow)
const x = (blockOrder % piecesInOneRow) * blockSizeWithMargin || 0
const y = currentRow * blockSizeWithMargin || 0
return { x, y }
}
return (
<Measure bounds onResize={({ bounds }) => setDimensions(bounds)}>
{({ measureRef }) => (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<DialogContent
ref={measureRef}
{...(isMini
? { style: { padding: 0, maxHeight: `${miniCacheMaxHeight}px`, overflow: 'auto' } }
: { 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>
{isMini
? shortCacheMap.map(({ percentage, isComplete, inProgress, isActive, isReaderRange }, i) => {
const { x, y } = getItemCoordinates(i)
return (
<SingleBlock
key={uuidv4()}
x={x}
y={y}
percentage={percentage}
inProgress={inProgress}
isComplete={isComplete}
isReaderRange={isReaderRange}
isActive={isActive}
boxHeight={boxHeight}
strokeWidth={strokeWidth}
/>
)
})
: cacheMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => {
const { x, y } = getItemCoordinates(id)
return (
<SingleBlock
key={uuidv4()}
x={x}
y={y}
percentage={percentage}
inProgress={inProgress}
isComplete={isComplete}
isReaderRange={isReaderRange}
isActive={isActive}
boxHeight={boxHeight}
strokeWidth={strokeWidth}
/>
)
})}
</Layer>
</Stage>
</DialogContent>
{isMini &&
(stageOffset + blockSizeWithMargin * amountOfRows || 0) >= miniCacheMaxHeight &&
dimensions.height >= miniCacheMaxHeight && <ScrollNotification>{t('ScrollDown')}</ScrollNotification>}
</div>
)}
</Measure>
)
}

View File

@@ -1,114 +0,0 @@
import styled, { css } from 'styled-components'
import Measure from 'react-measure'
import { useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useTranslation } from 'react-i18next'
import {
defaultBackgroundColor,
defaultBorderColor,
progressColor,
completeColor,
activeColor,
rangeColor,
} from './colors'
import getShortCacheMap from './getShortCacheMap'
const borderWidth = 1
const defaultPieceSize = 14
const pieceSizeForMiniMap = 23
const gapBetweenPieces = 3
const miniCacheMaxHeight = 340
const ScrollNotification = styled.div`
margin-top: 10px;
text-transform: uppercase;
color: rgba(0, 0, 0, 0.5);
align-self: center;
`
const SnakeWrapper = styled.div`
${({ pieceSize, piecesInOneRow }) => css`
display: grid;
gap: ${gapBetweenPieces}px;
grid-template-columns: repeat(${piecesInOneRow || 'auto-fit'}, ${pieceSize}px);
grid-auto-rows: max-content;
justify-content: center;
${piecesInOneRow &&
css`
max-height: ${miniCacheMaxHeight}px;
overflow: auto;
`}
.piece {
width: ${pieceSize}px;
height: ${pieceSize}px;
background: ${defaultBackgroundColor};
border: ${borderWidth}px solid ${defaultBorderColor};
display: grid;
align-items: end;
&-loading {
background: ${progressColor};
border-color: ${progressColor};
}
&-complete {
background: ${completeColor};
border-color: ${completeColor};
}
&-reader {
border-color: ${activeColor};
}
}
.reader-range {
border-color: ${rangeColor};
}
`}
`
const PercentagePiece = styled.div`
background: ${completeColor};
height: ${({ percentage }) => (percentage / 100) * 12}px;
`
export default function LargeSnake({ cacheMap, isMini, preloadPiecesAmount }) {
const { t } = useTranslation()
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
const pieceSize = isMini ? pieceSizeForMiniMap : defaultPieceSize
let piecesInOneRow
let shotCacheMap
if (isMini) {
const pieceSizeWithGap = pieceSize + gapBetweenPieces
piecesInOneRow = Math.floor((dimensions.width * 0.95) / pieceSizeWithGap)
shotCacheMap = isMini && getShortCacheMap({ cacheMap, preloadPiecesAmount, piecesInOneRow })
}
return isMini ? (
<Measure bounds onResize={({ bounds }) => setDimensions(bounds)}>
{({ measureRef }) => (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<SnakeWrapper ref={measureRef} pieceSize={pieceSize} piecesInOneRow={piecesInOneRow}>
{shotCacheMap.map(({ className, id, percentage }) => (
<span key={id || uuidv4()} className={className}>
{percentage > 0 && percentage <= 100 && <PercentagePiece percentage={percentage} />}
</span>
))}
</SnakeWrapper>
{dimensions.height >= miniCacheMaxHeight && <ScrollNotification>{t('ScrollDown')}</ScrollNotification>}
</div>
)}
</Measure>
) : (
<SnakeWrapper pieceSize={pieceSize}>
{cacheMap.map(({ className, id, percentage }) => (
<span key={id || uuidv4()} className={className}>
{percentage > 0 && percentage <= 100 && <PercentagePiece percentage={percentage} />}
</span>
))}
</SnakeWrapper>
)
}

View File

@@ -1,55 +0,0 @@
import { Rect } from 'react-konva'
import { activeColor, completeColor, defaultBorderColor, progressColor, rangeColor } from './colors'
export default function SingleBlock({
x,
y,
percentage,
isActive = false,
inProgress = false,
isReaderRange = false,
isComplete = false,
boxHeight,
strokeWidth,
}) {
const strokeColor = isActive
? activeColor
: isComplete
? completeColor
: inProgress
? progressColor
: isReaderRange
? rangeColor
: defaultBorderColor
const backgroundColor = inProgress ? progressColor : defaultBorderColor
const percentageProgressColor = completeColor
const processCompletedColor = completeColor
return (
<Rect
x={x}
y={y}
stroke={strokeColor}
strokeWidth={strokeWidth}
height={boxHeight}
width={boxHeight}
fillAfterStrokeEnabled
preventDefault={false}
{...(isComplete
? { fill: processCompletedColor }
: inProgress && {
fillLinearGradientStartPointY: boxHeight,
fillLinearGradientEndPointY: 0,
fillLinearGradientColorStops: [
0,
percentageProgressColor,
percentage,
percentageProgressColor,
percentage,
backgroundColor,
],
})}
/>
)
}

View File

@@ -1,26 +0,0 @@
export const defaultBorderColor = '#eef2f4'
export const defaultBackgroundColor = '#fff'
export const completeColor = '#00a572'
export const progressColor = '#ffa724'
export const activeColor = '#000'
export const rangeColor = '#9a9aff'
export const getLargeSnakeColors = ({ isActive, isComplete, inProgress, isReaderRange, percentage }) => {
const gradientBackgroundColor = inProgress ? progressColor : defaultBackgroundColor
const gradient = `linear-gradient(to top, ${completeColor} 0%, ${completeColor} ${
percentage * 100
}%, ${gradientBackgroundColor} ${percentage * 100}%, ${gradientBackgroundColor} 100%)`
const borderColor = isActive
? activeColor
: isComplete
? completeColor
: inProgress
? progressColor
: isReaderRange
? rangeColor
: defaultBorderColor
const backgroundColor = isComplete ? completeColor : inProgress ? gradient : defaultBackgroundColor
return { borderColor, backgroundColor }
}

View File

@@ -1,18 +1,59 @@
import { memo } from 'react' import Measure from 'react-measure'
import { useState, memo } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useTranslation } from 'react-i18next'
import isEqual from 'lodash/isEqual' import isEqual from 'lodash/isEqual'
import { useCreateCacheMap } from '../customHooks' import { useCreateCacheMap } from '../customHooks'
import LargeSnake from './LargeSnake' import { gapBetweenPieces, miniCacheMaxHeight, pieceSizeForMiniMap, defaultPieceSize } from './snakeSettings'
import getShortCacheMap from './getShortCacheMap'
import { SnakeWrapper, PercentagePiece, ScrollNotification } from './style'
const TorrentCache = memo( const TorrentCache = ({ cache, isMini }) => {
({ cache, isMini }) => { const { t } = useTranslation()
const cacheMap = useCreateCacheMap(cache) const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
const cacheMap = useCreateCacheMap(cache)
const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1) const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1)
return <LargeSnake isMini={isMini} cacheMap={cacheMap} preloadPiecesAmount={preloadPiecesAmount} /> const pieceSize = isMini ? pieceSizeForMiniMap : defaultPieceSize
},
let piecesInOneRow
let shotCacheMap
if (isMini) {
const pieceSizeWithGap = pieceSize + gapBetweenPieces
piecesInOneRow = Math.floor((dimensions.width * 0.95) / pieceSizeWithGap)
shotCacheMap = isMini && getShortCacheMap({ cacheMap, preloadPiecesAmount, piecesInOneRow })
}
return isMini ? (
<Measure bounds onResize={({ bounds }) => setDimensions(bounds)}>
{({ measureRef }) => (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<SnakeWrapper ref={measureRef} pieceSize={pieceSize} piecesInOneRow={piecesInOneRow}>
{shotCacheMap.map(({ className, id, percentage }) => (
<span key={id || uuidv4()} className={className}>
{percentage > 0 && percentage <= 100 && <PercentagePiece percentage={percentage} />}
</span>
))}
</SnakeWrapper>
{dimensions.height >= miniCacheMaxHeight && <ScrollNotification>{t('ScrollDown')}</ScrollNotification>}
</div>
)}
</Measure>
) : (
<SnakeWrapper pieceSize={pieceSize}>
{cacheMap.map(({ className, id, percentage }) => (
<span key={id || uuidv4()} className={className}>
{percentage > 0 && percentage <= 100 && <PercentagePiece percentage={percentage} />}
</span>
))}
</SnakeWrapper>
)
}
export default memo(
TorrentCache,
(prev, next) => isEqual(prev.cache.Pieces, next.cache.Pieces) && isEqual(prev.cache.Readers, next.cache.Readers), (prev, next) => isEqual(prev.cache.Pieces, next.cache.Pieces) && isEqual(prev.cache.Readers, next.cache.Readers),
) )
export default TorrentCache

View File

@@ -0,0 +1,12 @@
export const borderWidth = 1
export const defaultPieceSize = 14
export const pieceSizeForMiniMap = 23
export const gapBetweenPieces = 3
export const miniCacheMaxHeight = 340
export const defaultBorderColor = '#eef2f4'
export const defaultBackgroundColor = '#fff'
export const completeColor = '#00a572'
export const progressColor = '#ffa724'
export const activeColor = '#000'
export const rangeColor = '#9a9aff'

View File

@@ -0,0 +1,66 @@
import styled, { css } from 'styled-components'
import {
defaultBackgroundColor,
defaultBorderColor,
progressColor,
completeColor,
activeColor,
rangeColor,
gapBetweenPieces,
miniCacheMaxHeight,
borderWidth,
} from './snakeSettings'
export const ScrollNotification = styled.div`
margin-top: 10px;
text-transform: uppercase;
color: rgba(0, 0, 0, 0.5);
align-self: center;
`
export const SnakeWrapper = styled.div`
${({ pieceSize, piecesInOneRow }) => css`
display: grid;
gap: ${gapBetweenPieces}px;
grid-template-columns: repeat(${piecesInOneRow || 'auto-fit'}, ${pieceSize}px);
grid-auto-rows: max-content;
justify-content: center;
${piecesInOneRow &&
css`
max-height: ${miniCacheMaxHeight}px;
overflow: auto;
`}
.piece {
width: ${pieceSize}px;
height: ${pieceSize}px;
background: ${defaultBackgroundColor};
border: ${borderWidth}px solid ${defaultBorderColor};
display: grid;
align-items: end;
&-loading {
background: ${progressColor};
border-color: ${progressColor};
}
&-complete {
background: ${completeColor};
border-color: ${completeColor};
}
&-reader {
border-color: ${activeColor};
}
}
.reader-range {
border-color: ${rangeColor};
}
`}
`
export const PercentagePiece = styled.div`
background: ${completeColor};
height: ${({ percentage }) => (percentage / 100) * 12}px;
`

View File

@@ -7938,11 +7938,6 @@ klona@^2.0.4:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
konva@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/konva/-/konva-8.0.1.tgz#f34f483cdf62c36f966addc1a7484ed694313c2b"
integrity sha512-QDppGS1L5Dhod1zjwy9GVVjeyfPBHnPncL5oRh1NyjR1mEvhrLjzflrkdW+p73uFIW9hwCDZVLGxzzjQre9izw==
language-subtag-registry@~0.3.2: language-subtag-registry@~0.3.2:
version "0.3.21" version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
@@ -10472,14 +10467,6 @@ react-is@^16.7.0, react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-konva@^17.0.2-4:
version "17.0.2-4"
resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-17.0.2-4.tgz#afd0968e1295b624bf2a7a154ba294e0d5be55cd"
integrity sha512-YvRVPT81y8sMQV1SY1/tIDetGxBK+7Rk86u4LmiyDBLLE17vD78F01b8EC3AuP3nI3hUaTblfBugUF35cm6Etg==
dependencies:
react-reconciler "~0.26.2"
scheduler "^0.20.2"
react-measure@^2.5.2: react-measure@^2.5.2:
version "2.5.2" version "2.5.2"
resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.5.2.tgz#4ffc410e8b9cb836d9455a9ff18fc1f0fca67f89" resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.5.2.tgz#4ffc410e8b9cb836d9455a9ff18fc1f0fca67f89"
@@ -10499,15 +10486,6 @@ react-query@^3.17.0:
broadcast-channel "^3.4.1" broadcast-channel "^3.4.1"
match-sorter "^6.0.2" match-sorter "^6.0.2"
react-reconciler@~0.26.2:
version "0.26.2"
resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91"
integrity sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react-refresh@^0.8.3: react-refresh@^0.8.3:
version "0.8.3" version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"