From 9373a644909e430941f3770dd090f3eb8d0bc6ee Mon Sep 17 00:00:00 2001 From: Daniel Shleifman Date: Wed, 26 May 2021 21:10:26 +0300 Subject: [PATCH] cache blocks rewritten on canvas --- web/package.json | 3 + web/src/components/DialogCacheInfo.jsx | 190 ------------------ .../DialogCacheInfo/SingleBlock.jsx | 56 ++++++ web/src/components/DialogCacheInfo/index.jsx | 183 +++++++++++++++++ web/yarn.lock | 44 +++- 5 files changed, 285 insertions(+), 191 deletions(-) delete mode 100644 web/src/components/DialogCacheInfo.jsx create mode 100644 web/src/components/DialogCacheInfo/SingleBlock.jsx create mode 100644 web/src/components/DialogCacheInfo/index.jsx diff --git a/web/package.json b/web/package.json index 261134a..5c00cee 100644 --- a/web/package.json +++ b/web/package.json @@ -7,9 +7,12 @@ "@material-ui/icons": "^4.11.2", "clsx": "^1.1.1", "fontsource-roboto": "^4.0.0", + "konva": "^8.0.1", "material-ui-image": "^3.3.2", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-konva": "^17.0.2-4", + "react-measure": "^2.5.2", "react-scripts": "4.0.3", "styled-components": "^5.3.0" }, diff --git a/web/src/components/DialogCacheInfo.jsx b/web/src/components/DialogCacheInfo.jsx deleted file mode 100644 index d64a62d..0000000 --- a/web/src/components/DialogCacheInfo.jsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import Typography from '@material-ui/core/Typography' -import DialogTitle from '@material-ui/core/DialogTitle' -import DialogContent from '@material-ui/core/DialogContent' -import { getPeerString, humanizeSize } from 'utils/Utils' -import { cacheHost } from 'utils/Hosts' -import styled, { css } from 'styled-components' - -const boxHeight = 12 - -const CacheWrapper = styled.div` - padding-left: 6px; - padding-right: 2px; - line-height: 11px; - - .piece { - width: ${boxHeight}px; - height: ${boxHeight}px; - background-color: #eef2f4; - border: 1px solid #eef2f4; - display: inline-block; - margin-right: 1px; - } - .piece-complete { - background-color: #3fb57a; - border-color: #3fb57a; - } - .piece-loading { - background-color: #00d0d0; - border-color: #00d0d0; - } - .reader-range { - border-color: #9a9aff; - } - .piece-reader { - border-color: #000000; - } -` - -const PieceInProgress = styled.div` - ${({ prc }) => css` - position: relative; - z-index: 1; - background-color: #3fb57a; - - top: -1px; - left: -1px; - width: 12px; - height: ${prc * boxHeight}px; - `} -` - -export default function DialogCacheInfo({ hash }) { - const [cache, setCache] = useState({}) - const [pMap, setPMap] = useState([]) - const timerID = useRef(null) - const componentIsMounted = useRef(true) - - useEffect( - // this function is required to notify "getCache" when NOT to make state update - () => () => { - componentIsMounted.current = false - }, - [], - ) - - useEffect(() => { - if (hash) { - timerID.current = setInterval(() => { - getCache(hash, value => { - // this is required to avoid memory leak - if (componentIsMounted.current) setCache(value) - }) - }, 100) - } else clearInterval(timerID.current) - - return () => { - clearInterval(timerID.current) - } - }, [hash]) - - useEffect(() => { - if (!cache?.PiecesCount || !cache?.Pieces) return - - const { Pieces, PiecesCount, Readers } = cache - - const map = [] - - for (let i = 0; i < PiecesCount; i++) { - const cls = ['piece'] - let prc = 0 - - const currentPiece = Pieces[i] - if (currentPiece) { - if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) cls.push('piece-complete') - else cls.push('piece-loading') - - prc = (currentPiece.Size / currentPiece.Length).toFixed(2) - } - - Readers.forEach(r => { - if (i === r.Reader) return cls.push('piece-reader') - if (i >= r.Start && i <= r.End) cls.push('reader-range') - }) - - map.push({ prc, className: cls.join(' '), id: i }) - } - - setPMap(map) - }, [cache]) - - return ( -
- - - Hash {cache.Hash} -
- Capacity {humanizeSize(cache.Capacity)} -
- Filled {humanizeSize(cache.Filled)} -
- Torrent size {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)} -
- Pieces length {humanizeSize(cache.PiecesLength)} -
- Pieces count {cache.PiecesCount} -
- Peers: {getPeerString(cache.Torrent)} -
- Download speed {' '} - {cache.Torrent && cache.Torrent.download_speed ? `${humanizeSize(cache.Torrent.download_speed)}/sec` : ''} -
- Upload speed {' '} - {cache.Torrent && cache.Torrent.upload_speed ? `${humanizeSize(cache.Torrent.upload_speed)}/sec` : ''} -
- Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string} -
-
- - - - {pMap.map(({ prc, className: currentPieceCalss, id }) => ( - - {prc > 0 && prc < 1 && } - - ))} - - -
- ) -} - -const getCache = (hash, callback) => { - try { - fetch(cacheHost(), { - method: 'post', - body: JSON.stringify({ action: 'get', hash }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - .then(res => res.json()) - .then(callback, error => { - callback({}) - console.error(error) - }) - } catch (e) { - console.error(e) - callback({}) - } -} -/* -{ - "Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e", - "Capacity": 209715200, - "Filled": 2914808, - "PiecesLength": 4194304, - "PiecesCount": 2065, - "DownloadSpeed": 32770.860273455524, - "Pieces": { - "2064": { - "Id": 2064, - "Length": 2914808, - "Size": 162296, - "Completed": false - } - } -} - */ diff --git a/web/src/components/DialogCacheInfo/SingleBlock.jsx b/web/src/components/DialogCacheInfo/SingleBlock.jsx new file mode 100644 index 0000000..ba8cb01 --- /dev/null +++ b/web/src/components/DialogCacheInfo/SingleBlock.jsx @@ -0,0 +1,56 @@ +import { Rect } from 'react-konva' + +export const boxHeight = 12 +export const strokeWidth = 2 +export const marginBetweenBlocks = 2 + +export default function SingleBlock({ + x, + y, + percentage, + isActive = false, + inProgress = false, + isReaderRange = false, + isComplete = false, +}) { + const strokeColor = isActive + ? '#000' + : isComplete + ? '#3fb57a' + : inProgress + ? '#00d0d0' + : isReaderRange + ? '#9a9aff' + : '#eef2f4' + const backgroundColor = inProgress ? '#00d0d0' : '#eef2f4' + const percentageProgressColor = '#3fb57a' + const processCompletedColor = '#3fb57a' + + return ( + + ) +} diff --git a/web/src/components/DialogCacheInfo/index.jsx b/web/src/components/DialogCacheInfo/index.jsx new file mode 100644 index 0000000..28946fd --- /dev/null +++ b/web/src/components/DialogCacheInfo/index.jsx @@ -0,0 +1,183 @@ +import { useEffect, useRef, useState } from 'react' +import Typography from '@material-ui/core/Typography' +import DialogTitle from '@material-ui/core/DialogTitle' +import DialogContent from '@material-ui/core/DialogContent' +import { getPeerString, humanizeSize } from 'utils/Utils' +import { cacheHost } from 'utils/Hosts' +import { Stage, Layer } from 'react-konva' +import Measure from 'react-measure' + +import SingleBlock, { boxHeight, strokeWidth, marginBetweenBlocks } from './SingleBlock' + +export default function DialogCacheInfo({ hash }) { + const [cache, setCache] = useState({}) + const [pMap, setPMap] = useState([]) + const timerID = useRef(null) + const componentIsMounted = useRef(true) + const [dimensions, setDimensions] = useState({ width: -1, height: -1 }) + + useEffect( + // this function is required to notify "getCache" when NOT to make state update + () => () => { + componentIsMounted.current = false + }, + [], + ) + + useEffect(() => { + if (hash) { + timerID.current = setInterval(() => { + getCache(hash, value => { + // this is required to avoid memory leak + if (componentIsMounted.current) setCache(value) + }) + }, 100) + } else clearInterval(timerID.current) + + return () => { + clearInterval(timerID.current) + } + }, [hash]) + + useEffect(() => { + if (!cache?.PiecesCount || !cache?.Pieces) return + + const { Pieces, PiecesCount, Readers } = cache + + const map = [] + + for (let i = 0; i < PiecesCount; i++) { + const newPiece = { id: i } + + const currentPiece = Pieces[i] + if (currentPiece) { + if (currentPiece.Completed && currentPiece.Size === currentPiece.Length) newPiece.isComplete = true + else newPiece.inProgress = true + + newPiece.percentage = (currentPiece.Size / currentPiece.Length).toFixed(2) + } + + Readers.forEach(r => { + if (i === r.Reader) { + newPiece.isActive = true + return + } + if (i >= r.Start && i <= r.End) newPiece.isReaderRange = true + }) + + map.push(newPiece) + } + + setPMap(map) + }, [cache]) + + const blockSizeWithMargin = boxHeight + strokeWidth + marginBetweenBlocks + const piecesInOneRow = Math.floor((dimensions.width * 0.9) / blockSizeWithMargin) + const amountOfRows = Math.ceil(pMap.length / piecesInOneRow) + 1 + + return ( + setDimensions(contentRect.bounds)}> + {({ measureRef }) => ( +
+ + + Hash {cache.Hash} +
+ Capacity {humanizeSize(cache.Capacity)} +
+ Filled {humanizeSize(cache.Filled)} +
+ Torrent size {' '} + {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)} +
+ Pieces length {humanizeSize(cache.PiecesLength)} +
+ Pieces count {cache.PiecesCount} +
+ Peers: {getPeerString(cache.Torrent)} +
+ Download speed {' '} + {cache.Torrent && cache.Torrent.download_speed ? `${humanizeSize(cache.Torrent.download_speed)}/sec` : ''} +
+ Upload speed {' '} + {cache.Torrent && cache.Torrent.upload_speed ? `${humanizeSize(cache.Torrent.upload_speed)}/sec` : ''} +
+ Status {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string} +
+
+ + + {!pMap.length ? ( + 'loading' + ) : ( + + + {pMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => { + const currentRow = Math.floor(id / piecesInOneRow) + 1 + + return ( + + ) + })} + + + )} + +
+ )} +
+ ) +} + +const getCache = (hash, callback) => { + try { + fetch(cacheHost(), { + method: 'post', + body: JSON.stringify({ action: 'get', hash }), + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + }, + }) + .then(res => res.json()) + .then(callback, error => { + callback({}) + console.error(error) + }) + } catch (e) { + console.error(e) + callback({}) + } +} +/* +{ + "Hash": "41e36c8de915d80db83fc134bee4e7e2d292657e", + "Capacity": 209715200, + "Filled": 2914808, + "PiecesLength": 4194304, + "PiecesCount": 2065, + "DownloadSpeed": 32770.860273455524, + "Pieces": { + "2064": { + "Id": 2064, + "Length": 2914808, + "Size": 162296, + "Completed": false + } + } +} + */ diff --git a/web/yarn.lock b/web/yarn.lock index d9fe032..162396c 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1174,7 +1174,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== @@ -5927,6 +5927,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-node-dimensions@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz#fb7b4bb57060fb4247dd51c9d690dfbec56b0823" + integrity sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ== + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -7826,6 +7831,11 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" 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: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -10269,6 +10279,33 @@ react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" 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: + version "2.5.2" + resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.5.2.tgz#4ffc410e8b9cb836d9455a9ff18fc1f0fca67f89" + integrity sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA== + dependencies: + "@babel/runtime" "^7.2.0" + get-node-dimensions "^1.2.1" + prop-types "^15.6.2" + resize-observer-polyfill "^1.5.0" + +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: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -10638,6 +10675,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +resize-observer-polyfill@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"