mirror of
https://github.com/Ernous/TorrServerJellyfin.git
synced 2025-12-19 13:36:09 +05:00
cache blocks rewritten on canvas
This commit is contained in:
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
<Typography>
|
||||
<b>Hash </b> {cache.Hash}
|
||||
<br />
|
||||
<b>Capacity </b> {humanizeSize(cache.Capacity)}
|
||||
<br />
|
||||
<b>Filled </b> {humanizeSize(cache.Filled)}
|
||||
<br />
|
||||
<b>Torrent size </b> {cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)}
|
||||
<br />
|
||||
<b>Pieces length </b> {humanizeSize(cache.PiecesLength)}
|
||||
<br />
|
||||
<b>Pieces count </b> {cache.PiecesCount}
|
||||
<br />
|
||||
<b>Peers: </b> {getPeerString(cache.Torrent)}
|
||||
<br />
|
||||
<b>Download speed </b>{' '}
|
||||
{cache.Torrent && cache.Torrent.download_speed ? `${humanizeSize(cache.Torrent.download_speed)}/sec` : ''}
|
||||
<br />
|
||||
<b>Upload speed </b>{' '}
|
||||
{cache.Torrent && cache.Torrent.upload_speed ? `${humanizeSize(cache.Torrent.upload_speed)}/sec` : ''}
|
||||
<br />
|
||||
<b>Status </b> {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<CacheWrapper>
|
||||
{pMap.map(({ prc, className: currentPieceCalss, id }) => (
|
||||
<span key={id} className={currentPieceCalss}>
|
||||
{prc > 0 && prc < 1 && <PieceInProgress prc={prc} />}
|
||||
</span>
|
||||
))}
|
||||
</CacheWrapper>
|
||||
</DialogContent>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
56
web/src/components/DialogCacheInfo/SingleBlock.jsx
Normal file
56
web/src/components/DialogCacheInfo/SingleBlock.jsx
Normal file
@@ -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 (
|
||||
<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,
|
||||
],
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
183
web/src/components/DialogCacheInfo/index.jsx
Normal file
183
web/src/components/DialogCacheInfo/index.jsx
Normal file
@@ -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 (
|
||||
<Measure bounds onResize={contentRect => setDimensions(contentRect.bounds)}>
|
||||
{({ measureRef }) => (
|
||||
<div ref={measureRef}>
|
||||
<DialogTitle id='form-dialog-title'>
|
||||
<Typography>
|
||||
<b>Hash </b> <span style={{ wordBreak: 'break-word' }}>{cache.Hash}</span>
|
||||
<br />
|
||||
<b>Capacity </b> {humanizeSize(cache.Capacity)}
|
||||
<br />
|
||||
<b>Filled </b> {humanizeSize(cache.Filled)}
|
||||
<br />
|
||||
<b>Torrent size </b>{' '}
|
||||
{cache.Torrent && cache.Torrent.torrent_size && humanizeSize(cache.Torrent.torrent_size)}
|
||||
<br />
|
||||
<b>Pieces length </b> {humanizeSize(cache.PiecesLength)}
|
||||
<br />
|
||||
<b>Pieces count </b> {cache.PiecesCount}
|
||||
<br />
|
||||
<b>Peers: </b> {getPeerString(cache.Torrent)}
|
||||
<br />
|
||||
<b>Download speed </b>{' '}
|
||||
{cache.Torrent && cache.Torrent.download_speed ? `${humanizeSize(cache.Torrent.download_speed)}/sec` : ''}
|
||||
<br />
|
||||
<b>Upload speed </b>{' '}
|
||||
{cache.Torrent && cache.Torrent.upload_speed ? `${humanizeSize(cache.Torrent.upload_speed)}/sec` : ''}
|
||||
<br />
|
||||
<b>Status </b> {cache.Torrent && cache.Torrent.stat_string && cache.Torrent.stat_string}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
{!pMap.length ? (
|
||||
'loading'
|
||||
) : (
|
||||
<Stage
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
offsetX={-3}
|
||||
width={dimensions.width * 0.9}
|
||||
height={amountOfRows * blockSizeWithMargin}
|
||||
>
|
||||
<Layer>
|
||||
{pMap.map(({ id, percentage, isComplete, inProgress, isActive, isReaderRange }) => {
|
||||
const currentRow = Math.floor(id / piecesInOneRow) + 1
|
||||
|
||||
return (
|
||||
<SingleBlock
|
||||
key={id}
|
||||
x={(id % piecesInOneRow) * blockSizeWithMargin}
|
||||
y={currentRow * blockSizeWithMargin}
|
||||
percentage={percentage}
|
||||
inProgress={inProgress}
|
||||
isComplete={isComplete}
|
||||
isReaderRange={isReaderRange}
|
||||
isActive={isActive}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Layer>
|
||||
</Stage>
|
||||
)}
|
||||
</DialogContent>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user