From e85116bf717b51c0d4aa4f07c91944414f0a4b4e Mon Sep 17 00:00:00 2001 From: Haishan Date: Mon, 10 Feb 2020 22:53:14 +0800 Subject: [PATCH] refactor(Proxies): UI revamp --- package.json | 1 + prettier.config.js | 3 + src/components/Collapsible.js | 79 ++++++ src/components/CollapsibleSectionHeader.js | 34 +++ .../CollapsibleSectionHeader.module.css | 30 +++ src/components/Proxies.js | 49 ++-- src/components/Proxy.js | 15 +- src/components/Proxy.module.css | 18 +- src/components/ProxyGroup.js | 151 +++++------ src/components/ProxyLatency.module.css | 1 - src/components/ProxyProvider.js | 161 ++++-------- src/components/ProxyProvider.module.css | 19 -- src/components/ProxyProviderList.js | 9 +- src/components/StateProvider.js | 6 +- src/components/rtf.css | 235 ++++++++++++++++++ src/components/shared/Basic.js | 3 +- src/hooks/basic.js | 9 + src/store/index.js | 9 +- src/store/proxies.js | 15 +- yarn.lock | 5 + 20 files changed, 571 insertions(+), 281 deletions(-) create mode 100644 prettier.config.js create mode 100644 src/components/Collapsible.js create mode 100644 src/components/CollapsibleSectionHeader.js create mode 100644 src/components/CollapsibleSectionHeader.module.css create mode 100644 src/components/rtf.css create mode 100644 src/hooks/basic.js diff --git a/package.json b/package.json index 80a0aa2..a911cf2 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "react-router-dom": "^6.0.0-alpha.1", "react-table": "7.0.0-rc.15", "react-tabs": "^3.1.0", + "react-tiny-fab": "^3.4.0", "react-window": "^1.8.5", "redux": "^4.0.5", "redux-logger": "^3.0.6", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..7d93e18 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,3 @@ +module.exports = { + singleQuote: true +}; diff --git a/src/components/Collapsible.js b/src/components/Collapsible.js new file mode 100644 index 0000000..22016fe --- /dev/null +++ b/src/components/Collapsible.js @@ -0,0 +1,79 @@ +import React from 'react'; +import ResizeObserver from 'resize-observer-polyfill'; +import { motion } from 'framer-motion'; + +const { memo, useState, useRef, useEffect } = React; + +function usePrevious(value) { + const ref = useRef(); + useEffect(() => void (ref.current = value), [value]); + return ref.current; +} + +function useMeasure() { + const ref = useRef(); + const [bounds, set] = useState({ height: 0 }); + useEffect(() => { + const ro = new ResizeObserver(([entry]) => set(entry.contentRect)); + if (ref.current) ro.observe(ref.current); + return () => ro.disconnect(); + }, []); + return [ref, bounds]; +} + +const variantsCollpapsibleWrap = { + initialOpen: { + height: 'auto', + transition: { duration: 0 } + }, + open: height => ({ + height, + opacity: 1, + visibility: 'visible', + transition: { duration: 0.3 } + }), + closed: { + height: 0, + opacity: 0, + visibility: 'hidden', + transition: { duration: 0.3 } + } +}; + +const variantsCollpapsibleChildContainer = { + open: { + x: 0 + }, + closed: { + x: 20 + } +}; + +const Collapsible = memo(({ children, isOpen }) => { + const previous = usePrevious(isOpen); + const [refToMeature, { height }] = useMeasure(); + return ( +
+ + + {children} + + +
+ ); +}); + +export default Collapsible; diff --git a/src/components/CollapsibleSectionHeader.js b/src/components/CollapsibleSectionHeader.js new file mode 100644 index 0000000..aebb643 --- /dev/null +++ b/src/components/CollapsibleSectionHeader.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { ChevronDown } from 'react-feather'; +import cx from 'classnames'; + +import { SectionNameType } from './shared/Basic'; +import Button from './Button'; + +import s from './CollapsibleSectionHeader.module.css'; + +type Props = { + name: string, + type: string, + qty?: number, + toggle?: () => void, + isOpen?: boolean +}; + +export default function Header({ name, type, toggle, isOpen, qty }: Props) { + return ( +
+
+ +
+ + {typeof qty === 'number' ? {qty} : null} + + +
+ ); +} diff --git a/src/components/CollapsibleSectionHeader.module.css b/src/components/CollapsibleSectionHeader.module.css new file mode 100644 index 0000000..de24eec --- /dev/null +++ b/src/components/CollapsibleSectionHeader.module.css @@ -0,0 +1,30 @@ +.header { + display: flex; + align-items: center; + + .arrow { + display: inline-flex; + transform: rotate(0deg); + transition: transform 0.3s; + &.isOpen { + transform: rotate(180deg); + } + + &:focus { + outline: var(--color-focus-blue) solid 1px; + } + } +} + +/* TODO duplicate with connQty in Connections.module.css */ +.qty { + font-family: var(--font-normal); + font-size: 0.75em; + margin-left: 3px; + padding: 2px 7px; + display: inline-flex; + justify-content: center; + align-items: center; + background-color: var(--bg-near-transparent); + border-radius: 30px; +} diff --git a/src/components/Proxies.js b/src/components/Proxies.js index 83d5205..acb26dd 100644 --- a/src/components/Proxies.js +++ b/src/components/Proxies.js @@ -1,19 +1,18 @@ import React from 'react'; -// import { useStoreState } from '../misc/store'; -import { connect } from './StateProvider'; +import { connect, useStoreActions } from './StateProvider'; import ContentHeader from './ContentHeader'; import ProxyGroup from './ProxyGroup'; -import Button from './Button'; -import { Zap, Filter } from 'react-feather'; +import { Zap, Filter, Circle } from 'react-feather'; import ProxyProviderList from './ProxyProviderList'; +import { Fab, Action } from 'react-tiny-fab'; +import './rtf.css'; import s0 from './Proxies.module.css'; import { - getProxies, getDelay, getRtFilterSwitch, getProxyGroupNames, @@ -23,29 +22,18 @@ import { } from '../store/proxies'; import { getClashAPIConfig } from '../store/app'; -const { useEffect, useMemo, useCallback, useRef } = React; +const { useEffect, useCallback, useRef } = React; function Proxies({ dispatch, groupNames, - proxies, delay, proxyProviders, apiConfig, filterZeroRT }) { const refFetchedTimestamp = useRef({}); - - const switchRequetState = (dispath, getState) => { - const preState = getRtFilterSwitch(getState()); - - dispatch('store/proxies#filterZeroRTProxies', s => { - s.filterZeroRT = !preState; - }); - }; - const filterZeroRTFn = useCallback(() => dispatch(switchRequetState), [ - dispatch - ]); + const { toggleUnavailableProxiesFilter } = useStoreActions(); const requestDelayAllFn = useCallback( () => dispatch(requestDelayAll(apiConfig)), [apiConfig, dispatch] @@ -73,29 +61,16 @@ function Proxies({ window.addEventListener('focus', fn, false); return () => window.removeEventListener('focus', fn, false); }, [fetchProxiesHooked]); - const icon = useMemo(() => , []); - const filterIcon = useMemo(() => , []); return ( <>
-
- -
{groupNames.map(groupName => { return (
+ }> + + + + + + + ); } @@ -113,7 +99,6 @@ function Proxies({ const mapState = s => ({ apiConfig: getClashAPIConfig(s), groupNames: getProxyGroupNames(s), - proxies: getProxies(s), proxyProviders: getProxyProviders(s), delay: getDelay(s), filterZeroRT: getRtFilterSwitch(s) diff --git a/src/components/Proxy.js b/src/components/Proxy.js index c485cc0..65bed4e 100644 --- a/src/components/Proxy.js +++ b/src/components/Proxy.js @@ -1,4 +1,3 @@ -// vim: set ft=javascript.flow : import React from 'react'; import cx from 'classnames'; @@ -84,14 +83,14 @@ function Proxy({ now, name, proxy, latency }: ProxyProps) { })} >
{name}
- - {proxy.type} - - {latency && latency.number ? ( - - +
+ + {proxy.type} - ) : null} + {latency && latency.number ? ( + + ) : null} +
); } diff --git a/src/components/Proxy.module.css b/src/components/Proxy.module.css index 2aa4504..7e6e2cc 100644 --- a/src/components/Proxy.module.css +++ b/src/components/Proxy.module.css @@ -6,7 +6,7 @@ max-width: 280px; @media (--breakpoint-not-small) { - min-width: 150px; + min-width: 200px; border-radius: 10px; padding: 10px; } @@ -23,33 +23,29 @@ .proxyType { font-family: var(--font-mono); - display: inline; - padding: 0 0.3em; font-size: 0.6em; @media (--breakpoint-not-small) { font-size: 1em; } } +.row { + display: flex; + align-items: center; + justify-content: space-between; +} + .proxyName { width: 100%; overflow: hidden; text-overflow: ellipsis; margin-bottom: 5px; font-size: 0.85em; - display: inline; @media (--breakpoint-not-small) { font-size: 1.1em; } } -.proxyLatencyWrap { - padding: 0 0.3em; - height: 30px; - /* display: flex; */ - align-items: flex-end; -} - .proxySmall { .now { outline: pink solid 1px; diff --git a/src/components/ProxyGroup.js b/src/components/ProxyGroup.js index ae1fe58..fffb020 100644 --- a/src/components/ProxyGroup.js +++ b/src/components/ProxyGroup.js @@ -1,73 +1,49 @@ import React from 'react'; - -import Button from './Button'; -import { ChevronsDown } from 'react-feather'; - import cx from 'classnames'; -import { connect } from './StateProvider'; -import { getDelay, getRtFilterSwitch } from '../store/proxies'; +import memoizeOne from 'memoize-one'; +import { connect } from './StateProvider'; +import { getProxies, getRtFilterSwitch } from '../store/proxies'; +import CollapsibleSectionHeader from './CollapsibleSectionHeader'; import Proxy, { ProxySmall } from './Proxy'; -import { SectionNameType } from './shared/Basic'; +import { useToggle } from '../hooks/basic'; import s0 from './ProxyGroup.module.css'; import { switchProxy } from '../store/proxies'; -const { memo, useCallback, useMemo, useState } = React; - -function ProxyGroup({ name, proxies, apiConfig, dispatch }) { - const group = proxies[name]; - const { all, type, now } = group; +const { useCallback, useMemo } = React; +function ProxyGroup({ name, all, type, now, apiConfig, dispatch }) { const isSelectable = useMemo(() => type === 'Selector', [type]); - - const [isShow, setIsShow] = useState({ - show: false - }); - - const updateShow = useCallback( - type => { - setIsShow({ - show: !isShow.show - }); - }, - [isShow] - ); - + const [isOpen, toggle] = useToggle(true); const itemOnTapCallback = useCallback( proxyName => { if (!isSelectable) return; - dispatch(switchProxy(apiConfig, name, proxyName)); - // switchProxyFn(name, proxyName); }, [apiConfig, dispatch, name, isSelectable] ); - const button = useMemo( - () => ( - -
+
Updated {timeAgo} ago
- - + +
-
- - - + + + +
); } @@ -106,78 +112,17 @@ function Refresh() { ); } -function usePrevious(value) { - const ref = useRef(); - useEffect(() => void (ref.current = value), [value]); - return ref.current; -} - -function useMeasure() { - const ref = useRef(); - const [bounds, set] = useState({ height: 0 }); - useEffect(() => { - const ro = new ResizeObserver(([entry]) => set(entry.contentRect)); - if (ref.current) ro.observe(ref.current); - return () => ro.disconnect(); - }, []); - return [ref, bounds]; -} - -const variantsCollpapsibleWrap = { - initialOpen: { - height: 'auto', - transition: { duration: 0 } - }, - open: height => ({ - height, - opacity: 1, - visibility: 'visible', - transition: { duration: 0.3 } - }), - closed: { - height: 0, - opacity: 0, - visibility: 'hidden', - transition: { duration: 0.3 } - } -}; -const variantsCollpapsibleChildContainer = { - open: { - x: 0 - }, - closed: { - x: 20 - } +const mapState = (s, { proxies }) => { + const filterByRt = getRtFilterSwitch(s); + const delay = getDelay(s); + const apiConfig = getClashAPIConfig(s); + return { + apiConfig, + proxies: filterAvailableProxiesAndSort(proxies, delay, filterByRt) + }; }; -const Collapsible2 = memo(({ children, isOpen }) => { - const previous = usePrevious(isOpen); - const [refToMeature, { height }] = useMeasure(); - return ( -
- - - {children} - - -
- ); -}); - -const mapState = s => ({ - apiConfig: getClashAPIConfig(s) -}); +// const mapState = s => ({ +// apiConfig: getClashAPIConfig(s) +// }); export default connect(mapState)(ProxyProvider); diff --git a/src/components/ProxyProvider.module.css b/src/components/ProxyProvider.module.css index 270cd03..534305b 100644 --- a/src/components/ProxyProvider.module.css +++ b/src/components/ProxyProvider.module.css @@ -1,22 +1,3 @@ -.header { - display: flex; - align-items: center; - cursor: pointer; - - .arrow { - display: inline-flex; - transform: rotate(0deg); - transition: transform 0.3s; - &.isOpen { - transform: rotate(180deg); - } - - &:focus { - outline: var(--color-focus-blue) solid 1px; - } - } -} - .updatedAt { margin-bottom: 12px; small { diff --git a/src/components/ProxyProviderList.js b/src/components/ProxyProviderList.js index 9cdd7a8..4242b51 100644 --- a/src/components/ProxyProviderList.js +++ b/src/components/ProxyProviderList.js @@ -11,7 +11,14 @@ function ProxyProviderList({ items }) {
{items.map(item => ( - + ))}
diff --git a/src/components/StateProvider.js b/src/components/StateProvider.js index 23041fe..aff6147 100644 --- a/src/components/StateProvider.js +++ b/src/components/StateProvider.js @@ -79,11 +79,7 @@ export function connect(mapStateToProps) { const state = useContext(StateContext); const dispatch = useContext(DispatchContext); const mapped = mapStateToProps(state, props); - const nextProps = { - ...props, - ...mapped, - dispatch - }; + const nextProps = { dispatch, ...props, ...mapped }; return ; } return Connected; diff --git a/src/components/rtf.css b/src/components/rtf.css new file mode 100644 index 0000000..1a68f6b --- /dev/null +++ b/src/components/rtf.css @@ -0,0 +1,235 @@ +/* + * for react-tiny-fab + * based on react-tiny-fab/dist/styles.css + */ +.rtf { + box-sizing: border-box; + margin: 25px; + position: fixed; + white-space: nowrap; + z-index: 9998; + padding-left: 0; + list-style: none; +} +.rtf.open .rtf--mb > * { + transform-origin: center center; + transform: rotate(315deg); + transition: ease-in-out transform 0.2s; +} +.rtf.open .rtf--mb > ul { + list-style: none; + margin: 0; + padding: 0; +} +.rtf.open .rtf--ab__c:hover > span { + transition: ease-in-out opacity 0.2s; + opacity: 0.9; +} +.rtf.open .rtf--ab__c > span.always-show { + transition: ease-in-out opacity 0.2s; + opacity: 0.9; +} +.rtf.open .rtf--ab__c:nth-child(1) { + transform: translateY(-60px) scale(1); + transition-delay: 0.03s; +} +.rtf.open .rtf--ab__c:nth-child(1).top { + transform: translateY(60px) scale(1); +} +.rtf.open .rtf--ab__c:nth-child(2) { + transform: translateY(-120px) scale(1); + transition-delay: 0.09s; +} +.rtf.open .rtf--ab__c:nth-child(2).top { + transform: translateY(120px) scale(1); +} +.rtf.open .rtf--ab__c:nth-child(3) { + transform: translateY(-180px) scale(1); + transition-delay: 0.12s; +} +.rtf.open .rtf--ab__c:nth-child(3).top { + transform: translateY(180px) scale(1); +} +.rtf.open .rtf--ab__c:nth-child(4) { + transform: translateY(-240px) scale(1); + transition-delay: 0.15s; +} +.rtf.open .rtf--ab__c:nth-child(4).top { + transform: translateY(240px) scale(1); +} +.rtf.open .rtf--ab__c:nth-child(5) { + transform: translateY(-300px) scale(1); + transition-delay: 0.18s; +} +.rtf.open .rtf--ab__c:nth-child(5).top { + transform: translateY(300px) scale(1); +} +.rtf.open .rtf--ab__c:nth-child(6) { + transform: translateY(-360px) scale(1); + transition-delay: 0.21s; +} +.rtf.open .rtf--ab__c:nth-child(6).top { + transform: translateY(360px) scale(1); +} + +.rtf--mb__c { + padding: 25px; + margin: -25px; +} +.rtf--mb__c *:last-child { + margin-bottom: 0; +} +.rtf--mb__c:hover > span { + transition: ease-in-out opacity 0.2s; + opacity: 0.9; +} +.rtf--mb__c > span.always-show { + transition: ease-in-out opacity 0.2s; + opacity: 0.9; +} +.rtf--mb__c > span { + opacity: 0; + transition: ease-in-out opacity 0.2s; + position: absolute; + top: 50%; + transform: translateY(-50%); + margin-right: 6px; + margin-left: 4px; + background: rgba(0, 0, 0, 0.75); + padding: 2px 4px; + border-radius: 2px; + color: white; + font-size: 13px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.14), 0 4px 8px rgba(0, 0, 0, 0.28); +} +.rtf--mb__c > span.right { + right: 100%; +} + +.rtf--mb { + height: 56px; + width: 56px; + z-index: 9999; + /* background-color: #666666; */ + background: #387cec; + /* background: var(--color-btn-bg); */ + display: inline-flex; + justify-content: center; + align-items: center; + position: relative; + border: none; + /* border: 1px solid #555; */ + border-radius: 50%; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.14), 0 4px 8px rgba(0, 0, 0, 0.28); + cursor: pointer; + outline: none; + padding: 0; + -webkit-user-drag: none; + font-weight: bold; + color: #f1f1f1; + font-size: 18px; +} +.rtf--mb > * { + transition: ease-in-out transform 0.2s; +} + +.rtf--ab__c { + display: block; + position: absolute; + top: 0; + right: 1px; + padding: 10px 0; + margin: -10px 0; + transition: ease-in-out transform 0.2s; +} +.rtf--ab__c > span { + opacity: 0; + transition: ease-in-out opacity 0.2s; + position: absolute; + top: 50%; + transform: translateY(-50%); + margin-right: 6px; + background: rgba(0, 0, 0, 0.75); + padding: 2px 4px; + border-radius: 2px; + color: white; + font-size: 13px; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.14), 0 4px 8px rgba(0, 0, 0, 0.28); +} +.rtf--ab__c > span.right { + right: 100%; +} +.rtf--ab__c:nth-child(1) { + transform: translateY(-60px) scale(0); + transition-delay: 0.21s; +} +.rtf--ab__c:nth-child(1).top { + transform: translateY(60px) scale(0); +} +.rtf--ab__c:nth-child(2) { + transform: translateY(-120px) scale(0); + transition-delay: 0.18s; +} +.rtf--ab__c:nth-child(2).top { + transform: translateY(120px) scale(0); +} +.rtf--ab__c:nth-child(3) { + transform: translateY(-180px) scale(0); + transition-delay: 0.15s; +} +.rtf--ab__c:nth-child(3).top { + transform: translateY(180px) scale(0); +} +.rtf--ab__c:nth-child(4) { + transform: translateY(-240px) scale(0); + transition-delay: 0.12s; +} +.rtf--ab__c:nth-child(4).top { + transform: translateY(240px) scale(0); +} +.rtf--ab__c:nth-child(5) { + transform: translateY(-300px) scale(0); + transition-delay: 0.09s; +} +.rtf--ab__c:nth-child(5).top { + transform: translateY(300px) scale(0); +} +.rtf--ab__c:nth-child(6) { + transform: translateY(-360px) scale(0); + transition-delay: 0.03s; +} +.rtf--ab__c:nth-child(6).top { + transform: translateY(360px) scale(0); +} + +.rtf--ab { + height: 48px; + width: 48px; + background-color: #aaaaaa; + display: inline-flex; + justify-content: center; + align-items: center; + position: relative; + border: none; + border-radius: 50%; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.14), 0 4px 8px rgba(0, 0, 0, 0.28); + cursor: pointer; + outline: none; + padding: 0; + -webkit-user-drag: none; + font-weight: bold; + color: #f1f1f1; + margin-right: 4px; + font-size: 16px; + z-index: 10000; +} + +.rtf--ab:hover { + background: #387cec; + border: 1px solid #387cec; + color: #fff; +} + +.rtf--ab:focus { + border-color: var(--color-focus-blue); +} diff --git a/src/components/shared/Basic.js b/src/components/shared/Basic.js index 9729411..dbd1bc7 100644 --- a/src/components/shared/Basic.js +++ b/src/components/shared/Basic.js @@ -2,12 +2,11 @@ import React from 'react'; import s from './Basic.module.css'; -export function SectionNameType({ name, type, dropDown }) { +export function SectionNameType({ name, type }) { return (

{name} {type} - {dropDown}

); } diff --git a/src/hooks/basic.js b/src/hooks/basic.js new file mode 100644 index 0000000..c8eddbc --- /dev/null +++ b/src/hooks/basic.js @@ -0,0 +1,9 @@ +import React from 'react'; + +const { useState, useCallback } = React; + +export function useToggle(initialValue = false) { + const [isOn, setState] = useState(initialValue); + const toggle = useCallback(() => setState(x => !x), []); + return [isOn, toggle]; +} diff --git a/src/store/index.js b/src/store/index.js index cc1e606..1fe8459 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -3,7 +3,10 @@ import { selectChartStyleIndex, updateAppConfig } from './app'; -import { initialState as proxies } from './proxies'; +import { + initialState as proxies, + toggleUnavailableProxiesFilter +} from './proxies'; import { initialState as modals } from './modals'; import { initialState as configs } from './configs'; import { initialState as rules } from './rules'; @@ -20,5 +23,7 @@ export const initialState = { export const actions = { selectChartStyleIndex, - updateAppConfig + updateAppConfig, + // proxies + toggleUnavailableProxiesFilter }; diff --git a/src/store/proxies.js b/src/store/proxies.js index 22b8f2b..95f724b 100644 --- a/src/store/proxies.js +++ b/src/store/proxies.js @@ -1,5 +1,3 @@ -// @flow -// vim: set ft=javascript.flow : import * as proxiesAPI from '../api/proxies'; import { getLatencyTestUrl } from './app'; @@ -27,7 +25,7 @@ const ProxyTypes = ['Shadowsocks', 'Snell', 'Socks5', 'Http', 'Vmess']; export const getProxies = s => s.proxies.proxies; export const getDelay = s => s.proxies.delay; -export const getRtFilterSwitch = s => s.filterZeroRT; +export const getRtFilterSwitch = s => s.proxies.filterZeroRT; export const getProxyGroupNames = s => s.proxies.groupNames; export const getProxyProviders = s => s.proxies.proxyProviders || []; export const getDangleProxyNames = s => s.proxies.dangleProxyNames; @@ -188,6 +186,15 @@ export function requestDelayAll(apiConfig) { }; } +export function toggleUnavailableProxiesFilter() { + return (dispatch, getState) => { + const preState = getRtFilterSwitch(getState()); + dispatch('store/proxies#toggleUnavailableProxiesFilter', s => { + s.proxies.filterZeroRT = !preState; + }); + }; +} + function retrieveGroupNamesFrom(proxies) { let groupNames = []; let globalAll; @@ -243,5 +250,5 @@ export const initialState = { proxies: {}, delay: {}, groupNames: [], - filterZeroRT: true + filterZeroRT: false }; diff --git a/yarn.lock b/yarn.lock index 198ce40..df3be98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6692,6 +6692,11 @@ react-tabs@^3.1.0: classnames "^2.2.0" prop-types "^15.5.0" +react-tiny-fab@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/react-tiny-fab/-/react-tiny-fab-3.4.0.tgz#65edafa65826aaab8da8cca5f6e099ee81c52eaa" + integrity sha512-8R1z0k5I/tcQFDDJHGRycyiAm991htdvgpaSgT1dB83SiG89L1VQJE7TVEaoDng8R50+fxLZaoOCUkNFmZBKgA== + react-window@^1.8.5: version "1.8.5" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"