snake replaced with canvas

This commit is contained in:
Daniel Shleifman
2021-06-19 02:35:09 +03:00
parent 8d555db847
commit a6f966718c
7 changed files with 121 additions and 101 deletions

View File

@@ -1,7 +1,5 @@
export default ({ cacheMap, preloadPiecesAmount, piecesInOneRow }) => {
const cacheMapWithoutEmptyBlocks = cacheMap.filter(
({ className }) => className.includes('piece-complete') || className.includes('piece-loading'),
)
const cacheMapWithoutEmptyBlocks = cacheMap.filter(({ percentage }) => percentage > 0)
const getFullAmountOfBlocks = amountOfBlocks =>
// this function counts existed amount of blocks with extra "empty blocks" to fill the row till the end
@@ -23,9 +21,7 @@ export default ({ cacheMap, preloadPiecesAmount, piecesInOneRow }) => {
const extraBlocksAmount = finalAmountOfBlocksToRenderInShortView - cacheMapWithoutEmptyBlocks.length + 1
// amount of blocks needed to fill the line till the end
const extraEmptyBlocksForFillingLine = extraBlocksAmount
? new Array(extraBlocksAmount).fill({ className: 'piece' })
: []
const extraEmptyBlocksForFillingLine = extraBlocksAmount ? new Array(extraBlocksAmount).fill({}) : []
return [...cacheMapWithoutEmptyBlocks, ...extraEmptyBlocksForFillingLine]
}

View File

@@ -1,55 +1,113 @@
import Measure from 'react-measure'
import { useState, memo } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useState, memo, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import isEqual from 'lodash/isEqual'
import { useCreateCacheMap } from '../customHooks'
import { gapBetweenPieces, miniCacheMaxHeight, pieceSizeForMiniMap, defaultPieceSize } from './snakeSettings'
import getShortCacheMap from './getShortCacheMap'
import { SnakeWrapper, PercentagePiece, ScrollNotification } from './style'
import { SnakeWrapper, ScrollNotification } from './style'
import {
defaultBorderWidth,
miniCacheMaxHeight,
pieceSizeForMiniMap,
defaultPieceSize,
defaultBackgroundColor,
defaultBorderColor,
completeColor,
activeColor,
rangeColor,
defaultGapBetweenPieces,
miniBackgroundColor,
miniBorderWidth,
miniGapBetweenPieces,
createGradient,
} from './snakeSettings'
const TorrentCache = ({ cache, isMini }) => {
const { t } = useTranslation()
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
const { width } = dimensions
const canvasRef = useRef(null)
const ctxRef = useRef(null)
const cacheMap = useCreateCacheMap(cache)
const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1)
const canvasWidth = isMini ? width * 0.93 : width
const pieceSize = isMini ? pieceSizeForMiniMap : defaultPieceSize
const gapBetweenPieces = isMini ? miniGapBetweenPieces : defaultGapBetweenPieces
const pieceSizeWithGap = pieceSize + gapBetweenPieces
const piecesInOneRow = Math.floor(canvasWidth / pieceSizeWithGap)
let piecesInOneRow
let shotCacheMap
if (isMini) {
const pieceSizeWithGap = pieceSize + gapBetweenPieces
piecesInOneRow = Math.floor((dimensions.width * 0.95) / pieceSizeWithGap)
shotCacheMap = isMini && getShortCacheMap({ cacheMap, preloadPiecesAmount, piecesInOneRow })
const preloadPiecesAmount = Math.round(cache.Capacity / cache.PiecesLength - 1)
shotCacheMap = getShortCacheMap({ cacheMap, preloadPiecesAmount, piecesInOneRow })
}
const source = isMini ? shotCacheMap : cacheMap
const startingXPoint = Math.ceil((canvasWidth - pieceSizeWithGap * piecesInOneRow) / 2) // needed to center grid
const height = Math.ceil(source.length / piecesInOneRow) * pieceSizeWithGap
return isMini ? (
useEffect(() => {
if (!canvasWidth || !height) return
const canvas = canvasRef.current
canvas.width = canvasWidth
canvas.height = height
ctxRef.current = canvas.getContext('2d')
}, [canvasRef, height, canvasWidth])
useEffect(() => {
const ctx = ctxRef.current
if (!ctx) return
ctx.clearRect(0, 0, canvasWidth, height)
source.forEach(({ percentage, isReader, isReaderRange }, i) => {
const inProgress = percentage > 0 && percentage < 100
const isCompleted = percentage === 100
const currentRow = i % piecesInOneRow
const currentColumn = Math.floor(i / piecesInOneRow)
const borderWidth = isMini ? miniBorderWidth : defaultBorderWidth
const fixBlurStroke = borderWidth % 2 === 0 ? 0 : 0.5
const requiredFix = Math.ceil(borderWidth / 2) + 1 + fixBlurStroke
const x = currentRow * pieceSize + currentRow * gapBetweenPieces + startingXPoint + requiredFix
const y = currentColumn * pieceSize + currentColumn * gapBetweenPieces + requiredFix
ctx.lineWidth = borderWidth
ctx.fillStyle = inProgress
? createGradient(ctx, percentage)
: isCompleted
? completeColor
: isMini
? miniBackgroundColor
: defaultBackgroundColor
ctx.strokeStyle = isReader
? activeColor
: inProgress || isCompleted
? completeColor
: isReaderRange
? rangeColor
: defaultBorderColor
ctx.translate(x, y)
ctx.fillRect(0, 0, pieceSize, pieceSize)
ctx.strokeRect(0, 0, pieceSize, pieceSize)
ctx.setTransform(1, 0, 0, 1, 0, 0)
})
}, [cacheMap, height, canvasWidth, piecesInOneRow, isMini, startingXPoint, pieceSize, gapBetweenPieces, source])
return (
<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>
))}
<div style={{ display: 'flex', flexDirection: 'column' }} ref={measureRef}>
<SnakeWrapper isMini={isMini}>
<canvas ref={canvasRef} />
</SnakeWrapper>
{dimensions.height >= miniCacheMaxHeight && <ScrollNotification>{t('ScrollDown')}</ScrollNotification>}
{isMini && 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,12 +1,27 @@
export const borderWidth = 1
import { cacheBackground } from '../style'
export const defaultBorderWidth = 1
export const miniBorderWidth = 2
export const defaultPieceSize = 14
export const pieceSizeForMiniMap = 23
export const gapBetweenPieces = 3
export const defaultGapBetweenPieces = 3
export const miniGapBetweenPieces = 6
export const miniCacheMaxHeight = 340
export const defaultBorderColor = '#dbf2e8'
export const defaultBackgroundColor = '#fff'
export const miniBackgroundColor = cacheBackground
export const completeColor = '#00a572'
export const progressColor = '#ffa724'
export const progressColor = '#86beee'
export const activeColor = '#000'
export const rangeColor = '#ffa724'
export const rangeColor = '#afa6e3'
export const createGradient = (ctx, percentage) => {
const gradient = ctx.createLinearGradient(0, 12, 0, 0)
gradient.addColorStop(0, completeColor)
gradient.addColorStop(percentage / 100, completeColor)
gradient.addColorStop(percentage / 100, progressColor)
gradient.addColorStop(1, progressColor)
return gradient
}

View File

@@ -1,16 +1,6 @@
import styled, { css } from 'styled-components'
import {
defaultBackgroundColor,
defaultBorderColor,
progressColor,
completeColor,
activeColor,
rangeColor,
gapBetweenPieces,
miniCacheMaxHeight,
borderWidth,
} from './snakeSettings'
import { miniCacheMaxHeight } from './snakeSettings'
export const ScrollNotification = styled.div`
margin-top: 10px;
@@ -20,47 +10,17 @@ export const ScrollNotification = styled.div`
`
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 &&
${({ isMini }) => css`
${isMini &&
css`
display: grid;
justify-content: center;
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};
canvas {
display: block;
}
`}
`
export const PercentagePiece = styled.div`
background: ${completeColor};
height: ${({ percentage }) => percentage}%;
`
`

View File

@@ -43,25 +43,15 @@ export const useCreateCacheMap = cache => {
const map = []
for (let i = 0; i < PiecesCount; i++) {
const newPiece = { id: i }
const { Size, Length } = Pieces[i] || {}
const activeBlock = Pieces[i]
const className = ['piece']
if (activeBlock) {
const { Completed, Size, Length } = activeBlock
className.push(Completed && Size >= Length ? 'piece-complete' : 'piece-loading')
newPiece.percentage = ((Size / Length) * 100).toFixed(2)
}
const newPiece = { id: i, percentage: (Size / Length) * 100 || 0 }
Readers.forEach(r => {
if (i === r.Reader) {
className.push('piece-reader')
} else if (i >= r.Start && i <= r.End) className.push('reader-range')
if (i === r.Reader) newPiece.isReader = true
if (i >= r.Start && i <= r.End) newPiece.isReaderRange = true
})
newPiece.className = className.join(' ')
map.push(newPiece)
}
setCacheMap(map)

View File

@@ -162,7 +162,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) {
) : (
<>
<SectionTitle>{getParsedTitle()}</SectionTitle>
<SectionSubName mb={20}>{ptt.parse(name).title}</SectionSubName>
<SectionSubName mb={20}>{ptt.parse(name || '')?.title}</SectionSubName>
</>
)
) : (

View File

@@ -74,13 +74,14 @@ export const MainSection = styled.section`
}
`
export const cacheBackground = '#88cdaa'
export const CacheSection = styled.section`
grid-area: cache;
padding: 40px;
display: grid;
align-content: start;
grid-template-rows: min-content 1fr min-content;
background: #88cdaa;
background: ${cacheBackground};
@media (max-width: 800px) {
padding: 20px;