From a6f966718c178744c7ba9180d0037338f8bfa058 Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Sat, 19 Jun 2021 02:35:09 +0300 Subject: [PATCH] snake replaced with canvas --- .../TorrentCache/getShortCacheMap.js | 8 +- .../TorrentCache/index.jsx | 112 +++++++++++++----- .../TorrentCache/snakeSettings.js | 23 +++- .../TorrentCache/style.js | 56 ++------- .../customHooks.jsx | 18 +-- .../DialogTorrentDetailsContent/index.jsx | 2 +- .../DialogTorrentDetailsContent/style.js | 3 +- 7 files changed, 121 insertions(+), 101 deletions(-) diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache/getShortCacheMap.js b/web/src/components/DialogTorrentDetailsContent/TorrentCache/getShortCacheMap.js index 9aed090..efcac57 100644 --- a/web/src/components/DialogTorrentDetailsContent/TorrentCache/getShortCacheMap.js +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache/getShortCacheMap.js @@ -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] } diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache/index.jsx b/web/src/components/DialogTorrentDetailsContent/TorrentCache/index.jsx index 6a4903f..62f464e 100644 --- a/web/src/components/DialogTorrentDetailsContent/TorrentCache/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache/index.jsx @@ -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 ( setDimensions(bounds)}> {({ measureRef }) => ( -
- - {shotCacheMap.map(({ className, id, percentage }) => ( - - {percentage > 0 && percentage <= 100 && } - - ))} +
+ + - {dimensions.height >= miniCacheMaxHeight && {t('ScrollDown')}} + {isMini && height >= miniCacheMaxHeight && {t('ScrollDown')}}
)} - ) : ( - - {cacheMap.map(({ className, id, percentage }) => ( - - {percentage > 0 && percentage <= 100 && } - - ))} - ) } diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache/snakeSettings.js b/web/src/components/DialogTorrentDetailsContent/TorrentCache/snakeSettings.js index 4621717..968140c 100644 --- a/web/src/components/DialogTorrentDetailsContent/TorrentCache/snakeSettings.js +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache/snakeSettings.js @@ -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 +} diff --git a/web/src/components/DialogTorrentDetailsContent/TorrentCache/style.js b/web/src/components/DialogTorrentDetailsContent/TorrentCache/style.js index 3887f5d..a83bff0 100644 --- a/web/src/components/DialogTorrentDetailsContent/TorrentCache/style.js +++ b/web/src/components/DialogTorrentDetailsContent/TorrentCache/style.js @@ -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}%; -` +` \ No newline at end of file diff --git a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx index 6c50096..1f0cf53 100644 --- a/web/src/components/DialogTorrentDetailsContent/customHooks.jsx +++ b/web/src/components/DialogTorrentDetailsContent/customHooks.jsx @@ -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) diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx index 0138ddd..8a4140f 100644 --- a/web/src/components/DialogTorrentDetailsContent/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/index.jsx @@ -162,7 +162,7 @@ export default function DialogTorrentDetailsContent({ closeDialog, torrent }) { ) : ( <> {getParsedTitle()} - {ptt.parse(name).title} + {ptt.parse(name || '')?.title} ) ) : ( diff --git a/web/src/components/DialogTorrentDetailsContent/style.js b/web/src/components/DialogTorrentDetailsContent/style.js index 4884967..f4384de 100644 --- a/web/src/components/DialogTorrentDetailsContent/style.js +++ b/web/src/components/DialogTorrentDetailsContent/style.js @@ -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;