diff --git a/web/package.json b/web/package.json
index 0aa4c5e..7984c45 100644
--- a/web/package.json
+++ b/web/package.json
@@ -14,6 +14,7 @@
"parse-torrent-title": "^1.3.0",
"react": "^17.0.2",
"react-copy-to-clipboard": "^5.0.3",
+ "react-div-100vh": "^0.6.0",
"react-dom": "^17.0.2",
"react-konva": "^17.0.2-4",
"react-measure": "^2.5.2",
diff --git a/web/src/App.jsx b/web/src/App.jsx
deleted file mode 100644
index 7286c36..0000000
--- a/web/src/App.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import CssBaseline from '@material-ui/core/CssBaseline'
-import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'
-
-import Appbar from './components/Appbar/index'
-
-const baseTheme = createMuiTheme({
- overrides: {
- MuiCssBaseline: {
- '@global': {
- html: {
- WebkitFontSmoothing: 'auto',
- },
- },
- },
- },
- palette: {
- primary: {
- main: '#3fb57a',
- },
- secondary: {
- main: '#FFA724',
- },
- tonalOffset: 0.2,
- },
-})
-
-export default function App() {
- return (
-
-
-
-
- )
-}
diff --git a/web/src/App/Sidebar.jsx b/web/src/App/Sidebar.jsx
new file mode 100644
index 0000000..5d6edac
--- /dev/null
+++ b/web/src/App/Sidebar.jsx
@@ -0,0 +1,60 @@
+import { playlistAllHost, shutdownHost } from 'utils/Hosts'
+import Divider from '@material-ui/core/Divider'
+import ListItem from '@material-ui/core/ListItem'
+import ListItemIcon from '@material-ui/core/ListItemIcon'
+import ListItemText from '@material-ui/core/ListItemText'
+import AddDialogButton from 'components/Add'
+import RemoveAll from 'components/RemoveAll'
+import SettingsDialog from 'components/Settings'
+import AboutDialog from 'components/About'
+import UploadDialog from 'components/Upload'
+import {
+ CreditCard as CreditCardIcon,
+ List as ListIcon,
+ PowerSettingsNew as PowerSettingsNewIcon,
+} from '@material-ui/icons'
+import List from '@material-ui/core/List'
+
+import { AppSidebarStyle } from './style'
+
+export default function Sidebar({ isDrawerOpen, setIsDonationDialogOpen }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ fetch(shutdownHost())}>
+
+
+
+
+
+
+
+
+
+
+ setIsDonationDialogOpen(true)}>
+
+
+
+
+
+
+
+ )
+}
diff --git a/web/src/App/index.jsx b/web/src/App/index.jsx
new file mode 100644
index 0000000..35bf94b
--- /dev/null
+++ b/web/src/App/index.jsx
@@ -0,0 +1,66 @@
+import CssBaseline from '@material-ui/core/CssBaseline'
+import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'
+import { useEffect, useState } from 'react'
+import Typography from '@material-ui/core/Typography'
+import IconButton from '@material-ui/core/IconButton'
+import { Menu as MenuIcon, Close as CloseIcon } from '@material-ui/icons'
+import { getTorrServerHost } from 'utils/Hosts'
+import TorrentList from 'components/TorrentList'
+import DonateSnackbar from 'components/Donate'
+import DonateDialog from 'components/Donate/DonateDialog'
+import Div100vh from 'react-div-100vh'
+
+import { AppWrapper, AppHeader } from './style'
+import Sidebar from './Sidebar'
+
+const baseTheme = createMuiTheme({
+ overrides: { MuiCssBaseline: { '@global': { html: { WebkitFontSmoothing: 'auto' } } } },
+ palette: { primary: { main: '#3fb57a' }, secondary: { main: '#FFA724' }, tonalOffset: 0.2 },
+})
+
+export default function App() {
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false)
+ const [isDonationDialogOpen, setIsDonationDialogOpen] = useState(false)
+ const [tsVersion, setTSVersion] = useState('')
+
+ useEffect(() => {
+ fetch(`${getTorrServerHost()}/echo`)
+ .then(resp => resp.text())
+ .then(txt => {
+ if (!txt.startsWith('')) setTSVersion(txt)
+ })
+ }, [isDrawerOpen])
+
+ return (
+
+
+
+ {/* Div100vh - iOS WebKit fix */}
+
+
+
+ setIsDrawerOpen(!isDrawerOpen)}
+ edge='start'
+ >
+ {isDrawerOpen ? : }
+
+
+
+ TorrServer {tsVersion}
+
+
+
+
+
+
+
+ {isDonationDialogOpen && setIsDonationDialogOpen(false)} />}
+ {!JSON.parse(localStorage.getItem('snackbarIsClosed')) && }
+
+
+
+ )
+}
diff --git a/web/src/App/style.js b/web/src/App/style.js
new file mode 100644
index 0000000..bf02027
--- /dev/null
+++ b/web/src/App/style.js
@@ -0,0 +1,57 @@
+import styled, { css } from 'styled-components'
+
+export const AppWrapper = styled.div`
+ height: 100%;
+ display: grid;
+ grid-template-columns: 60px 1fr;
+ grid-template-rows: 60px 1fr;
+ grid-template-areas:
+ 'head head'
+ 'side content';
+`
+export const AppHeader = styled.div`
+ background: #3fb57a;
+ color: rgba(0, 0, 0, 0.87);
+ grid-area: head;
+ display: flex;
+ align-items: center;
+ box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
+ padding: 0 24px;
+ z-index: 3;
+`
+export const AppSidebarStyle = styled.div`
+ ${({ isDrawerOpen }) => css`
+ grid-area: side;
+ width: ${isDrawerOpen ? '400%' : '100%'};
+ z-index: 2;
+ overflow-x: hidden;
+ transition: width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms;
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
+ background: #fff;
+ white-space: nowrap;
+ `}
+`
+export const TorrentListWrapper = styled.div`
+ grid-area: content;
+ padding: 20px;
+ overflow: auto;
+
+ display: grid;
+ place-content: start;
+ grid-template-columns: repeat(auto-fit, minmax(max-content, 570px));
+ gap: 20px;
+
+ @media (max-width: 1260px), (max-height: 500px) {
+ padding: 10px;
+ gap: 15px;
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ @media (max-width: 1100px) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ @media (max-width: 700px) {
+ grid-template-columns: 1fr;
+ }
+`
diff --git a/web/src/components/Appbar/index.jsx b/web/src/components/Appbar/index.jsx
deleted file mode 100644
index a4f1eec..0000000
--- a/web/src/components/Appbar/index.jsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import { useEffect, useState } from 'react'
-import clsx from 'clsx'
-import { useTheme } from '@material-ui/core/styles'
-import Drawer from '@material-ui/core/Drawer'
-import AppBar from '@material-ui/core/AppBar'
-import Toolbar from '@material-ui/core/Toolbar'
-import List from '@material-ui/core/List'
-import Typography from '@material-ui/core/Typography'
-import Divider from '@material-ui/core/Divider'
-import IconButton from '@material-ui/core/IconButton'
-import MenuIcon from '@material-ui/icons/Menu'
-import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
-import ChevronRightIcon from '@material-ui/icons/ChevronRight'
-import ListItem from '@material-ui/core/ListItem'
-import ListItemIcon from '@material-ui/core/ListItemIcon'
-import ListItemText from '@material-ui/core/ListItemText'
-import CreditCardIcon from '@material-ui/icons/CreditCard'
-import ListIcon from '@material-ui/icons/List'
-import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew'
-import { playlistAllHost, shutdownHost, getTorrServerHost } from 'utils/Hosts'
-import TorrentList from 'components/TorrentList'
-import AddDialogButton from 'components/Add'
-import RemoveAll from 'components/RemoveAll'
-import SettingsDialog from 'components/Settings'
-import AboutDialog from 'components/About'
-import DonateSnackbar from 'components/Donate'
-import DonateDialog from 'components/Donate/DonateDialog'
-import UploadDialog from 'components/Upload'
-
-import useStyles from './useStyles'
-
-export default function MiniDrawer() {
- const classes = useStyles()
- const theme = useTheme()
- const [isDrawerOpen, setIsDrawerOpen] = useState(false)
- const [isDonationDialogOpen, setIsDonationDialogOpen] = useState(false)
- const [tsVersion, setTSVersion] = useState('')
-
- const handleDrawerOpen = () => setIsDrawerOpen(true)
- const handleDrawerClose = () => setIsDrawerOpen(false)
-
- useEffect(() => {
- fetch(`${getTorrServerHost()}/echo`)
- .then(resp => resp.text())
- .then(txt => {
- if (!txt.startsWith('')) setTSVersion(txt)
- })
- }, [isDrawerOpen])
-
- return (
-
-
-
-
-
-
-
- TorrServer {tsVersion}
-
-
-
-
-
-
-
- {theme.direction === 'rtl' ? : }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- fetch(shutdownHost())}>
-
-
-
-
-
-
-
-
-
-
- setIsDonationDialogOpen(true)}>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {isDonationDialogOpen &&
setIsDonationDialogOpen(false)} />}
- {!JSON.parse(localStorage.getItem('snackbarIsClosed')) && }
-
- )
-}
diff --git a/web/src/components/Appbar/useStyles.js b/web/src/components/Appbar/useStyles.js
deleted file mode 100644
index d46b1d0..0000000
--- a/web/src/components/Appbar/useStyles.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import { makeStyles } from '@material-ui/core/styles'
-
-const drawerWidth = 240
-
-export default makeStyles(theme => ({
- root: {
- display: 'flex',
- },
- appBar: {
- zIndex: theme.zIndex.drawer + 1,
- transition: theme.transitions.create(['width', 'margin'], {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.leavingScreen,
- }),
- },
- appBarShift: {
- marginLeft: drawerWidth,
- width: `calc(100% - ${drawerWidth}px)`,
- transition: theme.transitions.create(['width', 'margin'], {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.enteringScreen,
- }),
- },
- menuButton: {
- marginRight: 36,
- },
- hide: {
- display: 'none',
- },
- drawer: {
- width: drawerWidth,
- flexShrink: 1,
- whiteSpace: 'nowrap',
- },
- drawerOpen: {
- width: drawerWidth,
- transition: theme.transitions.create('width', {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.enteringScreen,
- }),
- },
- drawerClose: {
- transition: theme.transitions.create('width', {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.leavingScreen,
- }),
- overflowX: 'hidden',
- width: theme.spacing(7) + 1,
- [theme.breakpoints.up('sm')]: {
- width: theme.spacing(9) + 1,
- },
- },
- toolbar: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'flex-end',
- padding: theme.spacing(0, 1),
- // necessary for content to be below app bar
- ...theme.mixins.toolbar,
- },
- content: {
- flexGrow: 1,
- padding: theme.spacing(3),
- },
-}))
diff --git a/web/src/components/DialogTorrentDetailsContent/index.jsx b/web/src/components/DialogTorrentDetailsContent/index.jsx
index 2ba1871..4f6eb26 100644
--- a/web/src/components/DialogTorrentDetailsContent/index.jsx
+++ b/web/src/components/DialogTorrentDetailsContent/index.jsx
@@ -1,5 +1,5 @@
import { NoImageIcon } from 'icons'
-import { humanizeSize } from 'utils/Utils'
+import { humanizeSize, shortenText } from 'utils/Utils'
import { useEffect, useState } from 'react'
import { Button, ButtonGroup } from '@material-ui/core'
import ptt from 'parse-torrent-title'
@@ -30,8 +30,6 @@ import { DownlodSpeedWidget, UploadSpeedWidget, PeersWidget, SizeWidget } from '
import TorrentFunctions from './TorrentFunctions'
import { isFilePlayable } from './helpers'
-const shortenText = (text, count) => text.slice(0, count) + (text.length > count ? '...' : '')
-
const Loader = () => (
diff --git a/web/src/components/TorrentCard/index.jsx b/web/src/components/TorrentCard/index.jsx
index 331a87f..022216c 100644
--- a/web/src/components/TorrentCard/index.jsx
+++ b/web/src/components/TorrentCard/index.jsx
@@ -1,9 +1,7 @@
import 'fontsource-roboto'
import { forwardRef, useState } from 'react'
-import HeightIcon from '@material-ui/icons/Height'
-import CloseIcon from '@material-ui/icons/Close'
-import DeleteIcon from '@material-ui/icons/Delete'
-import { getPeerString, humanizeSize } from 'utils/Utils'
+import { UnfoldMore as UnfoldMoreIcon, Close as CloseIcon, Delete as DeleteIcon } from '@material-ui/icons'
+import { getPeerString, humanizeSize, shortenText } from 'utils/Utils'
import { torrentsHost } from 'utils/Hosts'
import { NoImageIcon } from 'icons'
import DialogTorrentDetailsContent from 'components/DialogTorrentDetailsContent'
@@ -12,16 +10,7 @@ import Slide from '@material-ui/core/Slide'
import { Button, DialogActions, DialogTitle, useMediaQuery, useTheme } from '@material-ui/core'
import axios from 'axios'
-import {
- StyledButton,
- TorrentCard,
- TorrentCardButtons,
- TorrentCardDescription,
- TorrentCardDescriptionContent,
- TorrentCardDescriptionLabel,
- TorrentCardPoster,
- TorrentCardDetails,
-} from './style'
+import { StyledButton, TorrentCard, TorrentCardButtons, TorrentCardDescription, TorrentCardPoster } from './style'
const Transition = forwardRef((props, ref) =>
)
@@ -51,7 +40,7 @@ export default function Torrent({ torrent }) {
-
+
Details
@@ -67,31 +56,29 @@ export default function Torrent({ torrent }) {
-
- Name
- {title || name}
-
+
+
Name
+
{shortenText(title || name, 100)}
+
-
-
- Size
-
- {torrentSize > 0 && humanizeSize(torrentSize)}
-
-
+
+
+
Size
+
{torrentSize > 0 && humanizeSize(torrentSize)}
+
-
- Speed
-
+
+
Speed
+
{downloadSpeed > 0 ? humanizeSize(downloadSpeed) : '---'}
-
-
+
+
-
- Peers
- {getPeerString(torrent) || '---'}
-
-
+
+
Peers
+
{getPeerString(torrent) || '---'}
+
+
diff --git a/web/src/components/TorrentCard/style.js b/web/src/components/TorrentCard/style.js
index a8c242c..bd76160 100644
--- a/web/src/components/TorrentCard/style.js
+++ b/web/src/components/TorrentCard/style.js
@@ -4,22 +4,26 @@ export const TorrentCard = styled.div`
border: 1px solid;
border-radius: 5px;
display: grid;
- grid-template-columns: repeat(2, 1fr);
- grid-template-rows: 175px minmax(min-content, 1fr);
- grid-template-areas:
- 'poster buttons'
- 'description description';
+ grid-template-columns: 120px 260px 1fr;
+ grid-template-rows: 180px;
+ grid-template-areas: 'poster description buttons';
gap: 10px;
padding: 10px;
background: #3fb57a;
box-shadow: 0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
- @media (max-width: 600px), (max-height: 500px) {
+ @media (max-width: 1260px), (max-height: 500px) {
grid-template-areas:
'poster description'
'buttons buttons';
- grid-template-columns: 25% 1fr;
- grid-template-rows: 100px min-content;
+
+ grid-template-columns: 70px 1fr;
+ grid-template-rows: 110px max-content;
+ }
+
+ @media (max-width: 770px) {
+ grid-template-columns: 60px 1fr;
+ grid-template-rows: 90px max-content;
}
`
@@ -33,7 +37,9 @@ export const TorrentCardPoster = styled.div`
isPoster
? css`
img {
+ width: 100%;
height: 100%;
+ object-fit: cover;
border-radius: 5px;
}
`
@@ -41,26 +47,27 @@ export const TorrentCardPoster = styled.div`
display: grid;
place-items: center;
background: #74c39c;
- border: 1px solid;
+ border: 1px solid #337a57;
svg {
transform: translateY(-3px);
}
`};
- @media (max-width: 600px), (max-height: 500px) {
+ @media (max-width: 1260px), (max-height: 500px) {
svg {
width: 50%;
}
}
`
+
export const TorrentCardButtons = styled.div`
grid-area: buttons;
display: grid;
gap: 5px;
- @media (max-width: 600px), (max-height: 500px) {
- grid-template-columns: repeat(4, 1fr);
+ @media (max-width: 1260px), (max-height: 500px) {
+ grid-template-columns: repeat(3, 1fr);
}
`
export const TorrentCardDescription = styled.div`
@@ -68,43 +75,101 @@ export const TorrentCardDescription = styled.div`
background: #74c39c;
border-radius: 5px;
padding: 5px;
- word-break: break-word;
+ display: grid;
+ grid-template-rows: 55% 1fr;
+ gap: 10px;
- @media (max-width: 600px), (max-height: 500px) {
+ @media (max-width: 770px) {
+ grid-template-rows: 60% 1fr;
+ gap: 3px;
+ }
+
+ @media (max-width: 770px) {
+ grid-template-rows: 56% 1fr;
+ }
+
+ .description-title-wrapper {
display: flex;
flex-direction: column;
- justify-content: space-between;
- }
-`
-
-export const TorrentCardDescriptionLabel = styled.div`
- text-transform: uppercase;
- font-size: 10px;
- font-weight: 500;
- letter-spacing: 0.4px;
- color: #216e47;
-`
-
-export const TorrentCardDescriptionContent = styled.div`
- margin-left: 5px;
- margin-bottom: 10px;
- word-break: break-all;
-
- @media (max-width: 600px), (max-height: 500px) {
- font-size: 11px;
- margin-bottom: 3px;
- margin-left: 0;
-
- ${({ isTitle }) =>
- isTitle &&
- css`
- overflow: auto;
- height: 45px;
- `}
}
- @media (max-width: 410px) {
+ .description-section-name {
+ text-transform: uppercase;
font-size: 10px;
+ font-weight: 500;
+ letter-spacing: 0.4px;
+ color: #216e47;
+
+ @media (max-width: 770px) {
+ font-size: 0.4rem;
+ }
+ }
+
+ .description-torrent-title {
+ overflow: auto;
+ word-break: break-all;
+
+ @media (max-width: 770px) {
+ font-size: 0.6rem;
+ }
+
+ @media (max-width: 600px), (max-height: 500px) {
+ font-size: 11px;
+ margin-bottom: 3px;
+ margin-left: 0;
+ }
+
+ @media (max-width: 410px) {
+ font-size: 10px;
+ }
+ }
+
+ .description-statistics-wrapper {
+ display: grid;
+ grid-template-columns: 80px 80px 1fr;
+ align-self: end;
+
+ @media (max-width: 1260px), (max-height: 500px) {
+ grid-template-columns: 70px 70px 1fr;
+ }
+
+ @media (max-width: 770px) {
+ grid-template-columns: 65px 65px 1fr;
+ }
+
+ @media (max-width: 600px), (max-height: 500px) {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ }
+ }
+
+ .description-statistics-element-wrapper {
+ }
+
+ .description-statistics-element-value {
+ margin-left: 5px;
+ margin-bottom: 10px;
+ word-break: break-all;
+
+ @media (max-width: 1260px), (max-height: 500px) {
+ font-size: 0.7rem;
+ margin-bottom: 0;
+ margin-left: 0;
+ }
+
+ @media (max-width: 770px) {
+ font-size: 0.6rem;
+ }
+
+ @media (max-width: 600px), (max-height: 500px) {
+ font-size: 11px;
+ margin-bottom: 3px;
+ margin-left: 0;
+ }
+
+ @media (max-width: 410px) {
+ font-size: 10px;
+ }
}
`
@@ -121,41 +186,39 @@ export const StyledButton = styled.button`
font-size: 1rem;
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
letter-spacing: 0.009em;
+ padding: 10px 20px;
+
+ :hover {
+ background: #2a7e54;
+ }
> :first-child {
margin-right: 10px;
}
- @media (max-width: 600px), (max-height: 500px) {
- padding: 5px 0;
+ @media (max-width: 1260px), (max-height: 500px) {
+ padding: 5px 10px;
font-size: 0.8rem;
- justify-content: center;
-
- span {
- display: none;
- }
svg {
width: 20px;
}
-
- > :first-child {
- margin-right: 0;
- }
}
@media (max-width: 500px) {
font-size: 0.7rem;
+
+ svg {
+ width: 15px;
+ }
}
- :hover {
- background: #2a7e54;
- }
-`
+ @media (max-width: 420px) {
+ padding: 7px 10px;
+ justify-content: center;
-export const TorrentCardDetails = styled.div`
- @media (max-width: 600px), (max-height: 500px) {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
+ svg {
+ display: none;
+ }
}
`
diff --git a/web/src/components/TorrentList.jsx b/web/src/components/TorrentList.jsx
index 35fd993..0209f4c 100644
--- a/web/src/components/TorrentList.jsx
+++ b/web/src/components/TorrentList.jsx
@@ -5,24 +5,10 @@ import { torrentsHost } from 'utils/Hosts'
import TorrentCard from 'components/TorrentCard'
import axios from 'axios'
import CircularProgress from '@material-ui/core/CircularProgress'
-
-const TorrentListWrapper = styled.div`
- display: grid;
- grid-template-columns: repeat(auto-fit, 350px);
- gap: 30px;
-
- @media (max-width: 600px), (max-height: 500px) {
- gap: 10px;
- grid-template-columns: repeat(auto-fit, 310px);
- }
-
- @media (max-width: 410px) {
- grid-template-columns: minmax(min-content, 290px);
- }
-`
+import { TorrentListWrapper } from 'App/style'
const CenteredGrid = styled.div`
- height: 75vh;
+ height: 100%;
display: grid;
place-items: center;
`
@@ -63,7 +49,9 @@ export default function TorrentList() {
Offline
) : !torrents.length ? (
-
No torrents added
+
+ No torrents added
+
) : (
{torrents.map(torrent => (
diff --git a/web/src/utils/Utils.js b/web/src/utils/Utils.js
index 766489f..a26095b 100644
--- a/web/src/utils/Utils.js
+++ b/web/src/utils/Utils.js
@@ -8,3 +8,6 @@ export function getPeerString(torrent) {
if (!torrent || !torrent.connected_seeders) return ''
return `[${torrent.connected_seeders}] ${torrent.active_peers} / ${torrent.total_peers}`
}
+
+export const shortenText = (text, sympolAmount) =>
+ text.slice(0, sympolAmount) + (text.length > sympolAmount ? '...' : '')
diff --git a/web/yarn.lock b/web/yarn.lock
index 4ab2be3..96ed686 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -10282,6 +10282,11 @@ react-dev-utils@^11.0.3:
strip-ansi "6.0.0"
text-table "0.2.0"
+react-div-100vh@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/react-div-100vh/-/react-div-100vh-0.6.0.tgz#577972d8ac17693edcd44061c1a4b5a7578e49ec"
+ integrity sha512-ErV0VTNXUd8jZqofC0ExZr5u+XDD2kN2te4SbwtqsyTm0UOjVYu53kP+FalGQrTe+DoMG8VYR2dITcAFu7c/5w==
+
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"