Improve the web viewer UI [WIP] (#333)

* functional but style is bad

* banner. sidebar. tabs for controls/scene/camera_path/logs. websocket text field

* moved window. toggle scene button. next step: lint/clean code

* websocket and webrtc reconnection on url change

* reconnection on websocket change seems robust

* cleaned up errors and organized code

* fixing lint
This commit is contained in:
Ethan Weber authored and GitHub committed 2022-09-06 00:36:48 -07:00
1 parent 32517f4587
commit 4c77c4e7c0
28 files changed
+1136 -294

No files matched your search

+8 -3
View File
@@ -17,7 +17,11 @@
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"plugins": [
"react",
"@typescript-eslint",
"unused-imports"
],
"rules": {
"arrow-body-style": "off",
"camelcase": "off",
@@ -25,6 +29,7 @@
"no-alert": "off",
"no-console": "off",
"prefer-destructuring": "off",
"react/destructuring-assignment": "off"
"react/destructuring-assignment": "off",
"unused-imports/no-unused-imports-ts": 2
}
}
}
+9
View File
@@ -4,6 +4,9 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@mui/material": "^5.10.3",
"@reduxjs/toolkit": "^1.8.3",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
@@ -12,14 +15,20 @@
"classnames": "^2.3.1",
"dat.gui": "^0.7.9",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-unused-imports": "^2.0.0",
"leva": "^0.9.29",
"msgpack-lite": "^0.1.26",
"prop-types": "^15.8.1",
"re-resizable": "^6.9.9",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-icons": "^4.4.0",
"react-pro-sidebar": "^0.7.1",
"react-redux": "^8.0.2",
"redux": "^4.2.0",
"sass": "^1.54.8",
"socket.io-client": "^4.5.1",
"stats.js": "^0.17.0",
"three": "^0.142.0",
"three-wtm": "^1.0",
"websocket": "^1.0.34",
-13
View File
@@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+23 -10
View File
@@ -1,21 +1,34 @@
import './App.css';
import React from 'react';
import { Leva } from 'leva';
import { RenderControls } from './modules/ConfigPanel/ConfigPanel';
import LevaTheme from './modules/ConfigPanel/leva_theme.json';
import SetupScene from './modules/Scene/Scene';
import { BasicTabs } from './modules/SidePanel/SidePanel';
import {
SceneTreeWebSocketListener,
get_scene_tree,
} from './modules/Scene/Scene';
import { RenderControls } from './modules/ConfigPanel/ConfigPanel';
import ViewerWindow from './modules/ViewerWindow/ViewerWindow';
import Banner from './modules/Banner';
export default function App() {
const scene = SetupScene();
// The scene tree won't rerender but it will listener to changes
// from the redux store and draw three.js objects.
const sceneTree = get_scene_tree();
return (
<div className="App">
<Leva theme={LevaTheme} titleBar={false} />
<RenderControls />
<ViewerWindow scene={scene} />
{/*
Code that listens for websocket 'write' messages and updates the redux store.
*/}
<SceneTreeWebSocketListener />
{/* ----- */}
<Banner />
<div className="App-body">
<div className="SidePanel">
<BasicTabs sceneTree={sceneTree} />
</div>
<RenderControls />
<ViewerWindow scene={sceneTree.object} />
</div>
</div>
);
}
+2 -32
View File
@@ -2,17 +2,6 @@
/* eslint-disable no-restricted-syntax */
import * as THREE from 'three';
const dat = require('dat.gui');
function remove_folders(gui) {
for (const name of Object.keys(gui.__folders)) {
const folder = gui.__folders[name];
remove_folders(folder);
dat.dom.dom.unbind(window, 'resize', folder.__resizeHandler);
gui.removeFolder(folder);
}
}
function dispose(object) {
if (!object) {
return;
@@ -38,19 +27,16 @@ function dispose(object) {
}
export default class SceneNode {
constructor(object, folder) {
constructor(object) {
this.object = object;
this.folder = folder;
this.children = {};
this.create_controls();
for (const c of this.object.children) {
this.add_child(c);
}
}
add_child(object) {
const f = this.folder.addFolder(object.name);
const node = new SceneNode(object, f);
const node = new SceneNode(object);
this.children[object.name] = node;
return node;
}
@@ -74,19 +60,6 @@ export default class SceneNode {
return child.find(path.slice(1));
}
create_controls() {
if (this.vis_controller !== undefined) {
this.folder.domElement.removeChild(this.vis_controller.domElement);
}
this.vis_controller = new dat.controllers.BooleanController(
this.object,
'visible',
);
this.folder.domElement.prepend(this.vis_controller.domElement);
this.vis_controller.domElement.style.height = '0';
this.vis_controller.domElement.style.float = 'right';
}
set_property(property, value) {
if (property === 'position') {
this.object.position.set(value[0], value[1], value[2]);
@@ -116,7 +89,6 @@ export default class SceneNode {
this.object.parent.remove(this.object);
this.object = object;
parent.add(object);
this.create_controls();
}
dispose_recursive() {
@@ -136,8 +108,6 @@ export default class SceneNode {
if (child !== undefined) {
child.dispose_recursive();
parent.object.remove(child.object);
remove_folders(child.folder);
parent.folder.removeFolder(child.folder);
delete parent.children[name];
}
}
+1
View File
@@ -1,3 +1,4 @@
import './index.scss';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
+160
View File
@@ -0,0 +1,160 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100%;
overflow: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.App {
position: relative;
height: 100vh;
}
$banner-color: #84c3ff4d;
.banner {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 50px;
width: 100%;
z-index: 1000;
background-color: $banner-color;
.banner-button {
margin: 10px;
float: left;
}
}
.App-body {
position: absolute;
top: 50px;
left: 0;
right: 0;
bottom: 0;
}
$SidePanel-width: 500px;
$custom-border: solid 1px #ddd;
.SidePanel {
position: absolute;
background-color: #f0f0f080;
top: 0;
width: $SidePanel-width;
right: 0;
bottom: 0;
border: $custom-border;
z-index: 999;
text-align: center;
}
.Leva-container > div {
position: relative;
margin: auto;
width: 100%;
padding: 10px;
}
.Scene-container {
height: 500px;
}
// Your variable overrides
$sidebar-color: #b71f0b !default;
$sidebar-width: 100% !default;
@import '~react-pro-sidebar/dist/scss/styles.scss';
// ------------------------------------
// Status Panel styling
.StatusPanel {
display: block;
// height: 170px;
}
.StatusPanel-title {
margin: 5px;
text-align: center;
font-size: 1em;
font-weight: bold;
}
.StatusPanel .StatusPanel-button {
margin: 20px;
}
.StatusPanel .WebSocketUrlField {
// display: block;
// float: left;
margin: 10px;
}
.StatusPanel-metrics {
background-color: $banner-color;
}
.StatusPanel-metrics div {
position: relative;
font-size: 16px;
display: block;
text-align: left;
}
// ------------------------------------
// Landing Modal styling
// .LandingModal {
// display: block;
// }
$background-paper-color: #fff;
.LandingModal-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400px; // 400
background-color: $background-paper-color;
border: 2px solid #000;
// box-shadow: 24px 24px;
padding: 32px;
}
.LandingModal-box .WebSocketUrlField {
width: 100%;
}
.LandingModal-box .LandingModal-button {
margin: 3px;
}
// ------------------------------------
// Camera Vie
// }
.canvas-container-main,
.WebRTCVideo {
position: absolute;
top: 0px;
right: $SidePanel-width;
bottom: 0px;
left: 0px;
border: $custom-border;
}
.WebRTCVideo-video {
width: 100%;
height: 100%;
object-fit: fill;
}
@@ -0,0 +1,82 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import Button from '@mui/material/Button';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import LandingModal from '../LandingModal';
function getParam(param_name) {
// https://stackoverflow.com/questions/831030/how-to-get-get-request-parameters-in-javascript
const params = new RegExp(
`[?&]${encodeURIComponent(param_name)}=([^&]*)`,
).exec(window.location.href);
if (params === null) {
return undefined;
}
return decodeURIComponent(params[1]);
}
function getWebsocketEndpoint() {
const endpoint = getParam('websocket_url');
return endpoint;
}
export default function Banner() {
// const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [anchorEl, setAnchorEl] = React.useState(0);
const dispatch = useDispatch();
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
let open_modal = true;
const websocket_url_from_argument = getWebsocketEndpoint();
if (websocket_url_from_argument !== undefined) {
open_modal = false;
dispatch({
type: 'write',
path: 'websocketState/websocket_url',
data: websocket_url_from_argument,
});
}
return (
<div className="banner">
<LandingModal initial_state={open_modal} />
<Button
id="basic-button"
className="banner-button"
variant="outlined"
size="small"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
Options
</Button>
<Menu
id="basic-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleClose}>Load Training Config</MenuItem>
<MenuItem onClick={handleClose}>Export Training Config</MenuItem>
<MenuItem onClick={handleClose}>Dark Mode</MenuItem>
</Menu>
<Button className="banner-button" variant="outlined" size="small">
Download Desktop App
</Button>
</div>
);
}
@@ -0,0 +1,3 @@
import Banner from './Banner';
export default Banner;
Whitespace-only changes.
@@ -9,12 +9,6 @@ const msgpack = require('msgpack-lite');
export function RenderControls() {
// connection status indicators
const websocket = useContext(WebSocketContext).socket;
const isWebsocketConnected = useSelector(
(state) => state.websocketState.isConnected,
);
const isWebrtcConnected = useSelector(
(state) => state.webrtcState.isConnected,
);
const isTraining = useSelector((state) => state.renderingState.isTraining);
const outputOptions = useSelector(
(state) => state.renderingState.output_options,
@@ -37,11 +31,6 @@ export function RenderControls() {
const field_of_view = useSelector(
(state) => state.renderingState.field_of_view,
);
let eval_fps = useSelector((state) => state.renderingState.eval_fps);
let train_eta = useSelector((state) => state.renderingState.train_eta);
let vis_train_ratio = useSelector(
(state) => state.renderingState.vis_train_ratio,
);
const dispatch = useDispatch();
@@ -265,44 +254,7 @@ export function RenderControls() {
min_resolution,
max_resolution,
field_of_view,
],
);
const [, setState] = useControls(
'Rendering Monitor',
() => ({
// WebSocket isConnected
// button does nothing except be an indicator
'WebSocket Connected': button(() => {}, {
disabled: !isWebsocketConnected,
}),
// webRTC isConnected
// button does nothing except be an indicator
'WebRTC Connected': button(() => {}, {
disabled: !isWebrtcConnected,
}),
eval_fps: {
label: 'Eval FPS',
value: eval_fps,
disabled: true,
},
train_eta: {
label: 'Train ETA',
value: train_eta,
disabled: true,
},
vis_train_ratio: {
label: 'Time Allocation',
value: vis_train_ratio,
disabled: true,
},
}),
[
isWebsocketConnected,
isWebrtcConnected,
eval_fps,
train_eta,
vis_train_ratio,
websocket // need to re-render when websocket changes to use the new websocket
],
);
@@ -324,26 +276,5 @@ export function RenderControls() {
field_of_view,
]);
useEffect(() => {}, [setState, isWebsocketConnected, isWebrtcConnected]);
useEffect(() => {
websocket.addEventListener('message', (originalCmd) => {
// set the remote description when the offer is received
const cmd = msgpack.decode(new Uint8Array(originalCmd.data));
if (cmd.path === '/renderingState/eval_fps') {
eval_fps = cmd.data;
setState({ eval_fps });
}
if (cmd.path === '/renderingState/train_eta') {
train_eta = cmd.data;
setState({ train_eta });
}
if (cmd.path === '/renderingState/vis_train_ratio') {
vis_train_ratio = cmd.data;
setState({ vis_train_ratio });
}
});
}, []);
return null;
}
@@ -0,0 +1,80 @@
import * as React from 'react';
import { Box, Button, Modal, Typography } from '@mui/material';
import WebSocketUrlField from '../WebSocketUrlField';
interface LandingModalProps {
initial_state: object
}
export default function LandingModel(props: LandingModalProps) {
const [open, setOpen] = React.useState(props.initial_state);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const text_1 = `At the top right is the Status Panel where you can connect to a WebSocket and see the train/eval status of your NeRF model. The WebSocket URL takes the form <domain-or-ip_address>:<port>.`;
const text_2 =
'If the WebSocket is not connecting, make sure your port is being forwarded properly!';
return (
<div className="LandingModal">
<Button
className="banner-button"
variant="outlined"
size="small"
onClick={handleOpen}
>
Getting Started
</Button>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box className="LandingModal-box">
<Typography id="modal-modal-title" component="div">
Welcome to the Nerfactory Viewer!{' '}
<img style={{ height: 37, margin: 'auto' }} src="/favicon.png" alt="The favicon."/>
</Typography>
<Typography
id="modal-modal-description"
component="div"
sx={{ mt: 2 }}
>
<div className="LandingModel-content">
<b>Getting Started</b>
<p>{text_1}</p>
<p>
You can enter your WebSocket URL in the Status Panel or here:
</p>
<WebSocketUrlField />
<p>{text_2}</p>
<b>Other Resources:</b>
<Button
className="LandingModal-button"
variant="outlined"
href="https://github.com/plenoptix/nerfactory"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</Button>
<Button
className="LandingModal-button"
variant="outlined"
href="https://plenoptix-nerfactory.readthedocs-hosted.com/en/latest/index.html"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</Button>
</div>
</Typography>
</Box>
</Modal>
</div>
);
}
@@ -0,0 +1,3 @@
import LandingModel from './LandingModal';
export default LandingModel;
@@ -1,5 +0,0 @@
#datgui {
position: absolute;
top: 2px;
left: 2px;
}
@@ -1,38 +1,24 @@
import './Scene.css';
/* eslint-disable no-restricted-syntax */
import * as THREE from 'three';
import { useContext, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { GUI } from 'dat.gui';
import SceneNode from '../../SceneNode';
import { subscribe_to_changes } from '../../subscriber';
import { WebSocketContext } from '../WebSocket/WebSocket';
import { drawCamera, drawSceneBounds } from './drawing';
import SceneNode from '../../SceneNode';
import { WebSocketContext } from '../WebSocket/WebSocket';
import { subscribe_to_changes } from '../../subscriber';
const msgpack = require('msgpack-lite');
// manages setting up the scene and other logic for keeping state in sync with the server
export default function SetupScene() {
const SCENE_BOUNDS_NAME = 'Scene Bounds';
const CAMERAS_NAME = 'Training Cameras';
export function get_scene_tree() {
let scene = null;
let gui = null;
let sceneTree = null;
// the websocket context
const socket = useContext(WebSocketContext).socket;
const dispatch = useDispatch();
// Scene
scene = new THREE.Scene();
// GUI
gui = new GUI();
gui.domElement.id = 'datgui';
const sceneFolder = gui.addFolder('Scene');
sceneFolder.open();
sceneTree = new SceneNode(scene, sceneFolder);
sceneTree = new SceneNode(scene);
// add objects to the the scene tree
const setObject = (path, object) => {
@@ -64,9 +50,9 @@ export default function SetupScene() {
const fn_value_scene_bounds = (previous, current) => {
if (current !== null) {
const line = drawSceneBounds(current);
setObject(['Scene Bounds'], line);
setObject([SCENE_BOUNDS_NAME], line);
} else {
deleteObject(['Scene Bounds']);
deleteObject([SCENE_BOUNDS_NAME]);
}
};
subscribe_to_changes(selector_fn_scene_bounds, fn_value_scene_bounds);
@@ -92,22 +78,30 @@ export default function SetupScene() {
// keys_valid.push(key);
const json = current[key];
const camera = drawCamera(json);
setObject(['Cameras', key], camera);
setObject([CAMERAS_NAME, key], camera);
}
}
for (const key of prev) {
// invalid so delete
if (!curr.has(key) || current[key] === null) {
// keys_invalid.push(key);
deleteObject(['Cameras', key]);
deleteObject([CAMERAS_NAME, key]);
}
}
} else {
deleteObject(['Cameras']);
deleteObject([CAMERAS_NAME]);
}
};
subscribe_to_changes(selector_fn_cameras, fn_value_cameras);
return sceneTree;
}
// manages setting up the scene and other logic for keeping state in sync with the server
export function SceneTreeWebSocketListener() {
const socket = useContext(WebSocketContext).socket;
const dispatch = useDispatch();
useEffect(() => {
socket.addEventListener('message', (originalCmd) => {
// set the remote description when the offer is received
@@ -121,7 +115,5 @@ export default function SetupScene() {
});
}
});
}, []); // empty dependency array means only run once
return scene;
}, [socket]); // dependency to call this whenever the websocket changes
}
@@ -122,7 +122,6 @@ function transpose(matrix) {
export function drawCamera(camera): THREE.Object3D {
const group = new THREE.Group();
// console.log(camera);
console.assert(
camera.type === 'PinholeCamera',
'The camera should be a PinholeCamera',
@@ -0,0 +1,14 @@
import * as React from 'react';
import { Button } from '@mui/material';
export default function CameraPanel() {
return (
<div className="CameraPanel">
TODO: create a camera path panel list of cameras
<Button id="CameraPanel-add-camera-button" variant="outlined">
Add Camera
</Button>
</div>
);
}
@@ -0,0 +1,3 @@
import CameraPanel from './CameraPanel';
export default CameraPanel;
@@ -0,0 +1,163 @@
/* eslint-disable react/jsx-props-no-spreading */
import * as React from 'react';
import { FaLightbulb, FaTractor } from 'react-icons/fa';
import { Menu, MenuItem, ProSidebar, SubMenu } from 'react-pro-sidebar';
import Box from '@mui/material/Box';
import { Leva } from 'leva';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import Typography from '@mui/material/Typography';
import { useEffect } from 'react';
import StatusPanel from './StatusPanel';
import SceneNode from '../../SceneNode';
import LevaTheme from '../ConfigPanel/leva_theme.json';
import CameraPanel from './CameraPanel';
interface TabPanelProps {
children: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
// eslint-disable-next-line react/jsx-props-no-spreading
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography component="div">{children}</Typography>
</Box>
)}
</div>
);
}
function a11yProps(index: number) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
interface ListItemProps {
name: object;
object: object;
}
function ListItem(props: ListItemProps) {
const name = props.name;
const object = props.object;
const [value, setValue] = React.useState(1);
object.visible = value;
function handleClick() {
setValue(!value);
}
return <button type="button" onClick={handleClick}>{name}</button>;
}
interface ClickableListProps {
sceneTree: object
}
function ClickableList(props: ClickableListProps) {
const sceneTree = props.sceneTree;
const get_menu_items = (name: String, scene_node: SceneNode) => {
// TODO: sort the keys by string
const num_children = Object.keys(scene_node.children).length;
if (num_children === 0) {
return (
<MenuItem icon={<FaLightbulb />}>
<ListItem name={name} object={scene_node.object} />
</MenuItem>
);
}
return (
<SubMenu title={name} icon={<FaTractor />} defaultOpen>
{Object.keys(scene_node.children).map((key) =>
get_menu_items(key, scene_node.children[key]),
)}
</SubMenu>
);
};
const menu_items = get_menu_items('Scene', sceneTree);
return (
<ProSidebar>
<Menu iconShape="square">{menu_items}</Menu>
</ProSidebar>
);
}
interface BasicTabsProps {
sceneTree: object
}
export function BasicTabs(props: BasicTabsProps) {
const sceneTree = props.sceneTree;
const [value, setValue] = React.useState(0);
const handleChange = (event: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
// set the panel pane index
useEffect(() => {
const panel_index = 0;
setValue(panel_index);
}, []);
return (
<div>
<StatusPanel sceneTree={sceneTree} />
<hr />
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab label="Controls" {...a11yProps(0)} />
<Tab label="Scene" {...a11yProps(1)} />
<Tab label="Camera Path" {...a11yProps(2)} />
<Tab label="Logs" {...a11yProps(3)} />
</Tabs>
</Box>
<TabPanel value={value} index={0}>
<div className="Leva-container">
<Leva className="Leva-panel" theme={LevaTheme} titleBar={false} />
</div>
</TabPanel>
<TabPanel value={value} index={1}>
<div className="Scene-container">
<ClickableList sceneTree={sceneTree} />
</div>
</TabPanel>
<TabPanel value={value} index={2}>
<CameraPanel />
</TabPanel>
<TabPanel value={value} index={3}>
TODO: add a logging panel
</TabPanel>
</Box>
</div>
);
}
@@ -0,0 +1,77 @@
import * as React from 'react';
import Button from '@mui/material/Button';
import { useSelector } from 'react-redux';
import WebSocketUrlField from '../../WebSocketUrlField';
interface StatusPanelProps {
sceneTree: object
}
export default function StatusPanel(props: StatusPanelProps) {
const sceneTree = props.sceneTree;
const isWebsocketConnected = useSelector(
(state) => state.websocketState.isConnected,
);
const isWebrtcConnected = useSelector(
(state) => state.webrtcState.isConnected,
);
const eval_fps = useSelector((state) => state.renderingState.eval_fps);
const train_eta = useSelector((state) => state.renderingState.train_eta);
const vis_train_ratio = useSelector(
(state) => state.renderingState.vis_train_ratio,
);
// simple toggle button
const [value, setValue] = React.useState(1);
const handleChange = () => {
setValue(!value);
};
const scene_button = value ? "Hide Scene" : "Show Scene";
sceneTree.object.visible = value;
return (
<div className="StatusPanel">
<div className="StatusPanel-title">Status Panel</div>
<Button
className="StatusPanel-button"
variant="contained"
disabled={!isWebsocketConnected}
style={{ textTransform: 'none' }}
>
Websocket Connected
</Button>
<Button
className="StatusPanel-button"
variant="contained"
disabled={!isWebrtcConnected}
style={{ textTransform: 'none' }}
>
WebRTC Connected
</Button>
<Button
className="StatusPanel-button"
variant="outlined"
onClick={handleChange}
style={{ textTransform: 'none' }}
>
{scene_button}
</Button>
<WebSocketUrlField />
<div className="StatusPanel-metrics">
<div>
<b>Eval FPS:</b> {eval_fps}
</div>
<div>
<b>Train ETA:</b> {train_eta}
</div>
<div>
<b>Time Allocation:</b> {vis_train_ratio}
</div>
</div>
</div>
);
}
@@ -0,0 +1,3 @@
import StatusPanel from './StatusPanel';
export default StatusPanel;
@@ -1,27 +0,0 @@
.WebRTCVideo {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.WebRTCVideo-video {
width: 100%;
height: 100%;
object-fit: fill;
}
.canvas-container-main {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.canvas-container-main canvas {
position: relative;
height: 100%;
width: 100%;
}
@@ -1,62 +1,57 @@
import './ViewerWindow.css';
import * as THREE from 'three';
import React, { useContext, useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import WebRtcWindow from '../WebRtcWindow/WebRtcWindow';
import Stats from 'stats.js';
import { useSelector } from 'react-redux';
import React, { useContext, useEffect, useRef } from 'react';
import { WebSocketContext } from '../WebSocket/WebSocket';
import WebRtcWindow from '../WebRtcWindow/WebRtcWindow';
const msgpack = require('msgpack-lite');
function createStats() {
const stats = new Stats();
stats.setMode(0);
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0';
stats.domElement.style.top = '0';
return stats;
}
// manages a camera and the web rtc stream...
export default function ViewerWindow(props) {
// eslint-disable-next-line react/prop-types
const scene = props.scene;
let cameraControls = null;
const myRef = useRef(null);
const websocket = useContext(WebSocketContext).socket;
const field_of_view = useSelector(
(state) => state.renderingState.field_of_view,
);
const field_of_view_ref = useRef(field_of_view);
const camera = useRef(null);
let cameraControls = null;
let renderer = null;
let viewportWidth = null;
let viewportHeight = null;
let stats = null;
const getViewportWidth = () => {
return window.innerWidth - (window.innerWidth % 2);
const width = myRef.current.clientWidth;
return width - (width % 2);
};
const getViewportHeight = () => {
return window.innerHeight;
return myRef.current.clientHeight;
};
let viewportWidth = getViewportWidth();
let viewportHeight = getViewportHeight();
const camera = new THREE.PerspectiveCamera(
field_of_view,
viewportWidth / viewportHeight,
0.01,
100,
);
camera.position.x = 5;
camera.position.y = -5;
camera.position.z = 5;
camera.up = new THREE.Vector3(0, 0, 1);
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(viewportWidth, viewportHeight);
renderer.domElement.style.border = '1px solid black';
const handleResize = () => {
viewportWidth = getViewportWidth();
viewportHeight = getViewportHeight();
camera.aspect = viewportWidth / viewportHeight;
camera.updateProjectionMatrix();
camera.current.aspect = viewportWidth / viewportHeight;
camera.current.updateProjectionMatrix();
renderer.setSize(viewportWidth, viewportHeight);
};
@@ -68,27 +63,62 @@ export default function ViewerWindow(props) {
const data = {
type: cmd,
path,
data: camera.toJSON(),
data: camera.current.toJSON(),
};
const message = msgpack.encode(data);
websocket.send(message);
}
};
// keep sending the camera often
useEffect(() => {
const fps = 60;
const interval = 1000 / fps;
const refreshIntervalId = setInterval(sendCamera, interval);
return () => {
clearInterval(refreshIntervalId);
};
}, [websocket]);
const update = () => {
handleResize();
camera.updateProjectionMatrix();
cameraControls.update();
requestAnimationFrame(update);
renderer.render(scene, camera);
sendCamera();
handleResize();
camera.current.fov = field_of_view_ref.current;
camera.current.updateProjectionMatrix();
cameraControls.update();
renderer.render(scene, camera.current);
stats.update();
};
// similar to componentDidMount
// this is run once
useEffect(() => {
viewportWidth = getViewportWidth();
viewportHeight = getViewportHeight();
stats = createStats();
myRef.current.append(stats.domElement);
camera.current = new THREE.PerspectiveCamera(
field_of_view_ref.current,
viewportWidth / viewportHeight,
0.01,
100,
);
camera.current.position.x = 5;
camera.current.position.y = -5;
camera.current.position.z = 5;
camera.current.up = new THREE.Vector3(0, 0, 1);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(viewportWidth, viewportHeight);
myRef.current.append(renderer.domElement);
// add camera controls
cameraControls = new OrbitControls(camera, renderer.domElement);
cameraControls = new OrbitControls(camera.current, renderer.domElement);
cameraControls.rotateSpeed = 2.0;
cameraControls.zoomSpeed = 0.3;
cameraControls.panSpeed = 0.2;
@@ -98,19 +128,19 @@ export default function ViewerWindow(props) {
cameraControls.dampingFactor = 1.0;
cameraControls.update();
update();
}, []);
// useEffect(() => {
// camera.fov = field_of_view;
// console.log(field_of_view);
// camera.updateProjectionMatrix();
// }, [field_of_view]);
// updates the field of view inside the ref to avoid rerendering so often
useEffect(() => {
field_of_view_ref.current = field_of_view;
}, [field_of_view]);
return (
<div>
<>
{/* the webrtc viewer needs to know the camera pose */}
<WebRtcWindow />
<div className="canvas-container-main" ref={myRef} />
</div>
</>
);
}
@@ -36,7 +36,7 @@ export default function WebRtcWindow() {
username: 'openrelayproject',
credential: 'openrelayproject',
},
// extra STUN server(s):
// Extra STUN and TURN server(s):
// {
// urls: 'stun:stun.l.google.com:19302',
// },
@@ -83,6 +83,15 @@ export default function WebRtcWindow() {
});
}
};
pc.onclose = () => {
dispatch({
type: 'write',
path: 'webrtcState/isConnected',
data: false,
});
};
return pc;
};
@@ -161,7 +170,19 @@ export default function WebRtcWindow() {
console.log('[webrtc] starting process');
sendOffer();
});
}, []); // empty dependency array means only run once
// kill the webrtc connection on dismount
return () => {
if (pcRef.current !== null) {
dispatch({
type: 'write',
path: 'webrtcState/isConnected',
data: false,
});
pcRef.current.close();
}
};
}, [websocket]); // dependency to call this whenever the websocket changes
return (
<div className="WebRTCVideo">
@@ -1,49 +1,29 @@
// Much of this code comes from or is inspired by:
// https://www.pluralsight.com/guides/using-web-sockets-in-your-reactredux-app
import React, { createContext } from 'react';
import React, { createContext, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
const WebSocketContext = createContext(null);
export { WebSocketContext };
function getParam(param_name) {
// https://stackoverflow.com/questions/831030/how-to-get-get-request-parameters-in-javascript
const params = new RegExp(
`[?&]${encodeURIComponent(param_name)}=([^&]*)`,
).exec(window.location.href);
if (params === null) {
return undefined;
}
return decodeURIComponent(params[1]);
}
function getWebsocketEndpoint() {
const endpoint = getParam('websocket_url');
console.log('websocket url: ', endpoint);
if (endpoint === undefined) {
const message =
'Please set the websocket endpoint. The format should look like "<viewer_url>?websocket_url=localhost:<port>". E.g., "https://viewer.nerfactory.com/branch/master/?websocket_url=localhost:7007".';
window.alert(message);
return '';
}
return endpoint;
}
export default function WebSocketContextFunction({ children }) {
const dispatch = useDispatch();
let ws = null;
let socket = null;
let socket;
let ws;
// should look like e.g., "ws://<localhost:port>"
const websocketEndpoint = getWebsocketEndpoint();
// this code will rerender anytime the webosocket changes now
const websocket_url = useSelector(
(state) => state.websocketState.websocket_url,
);
console.log(websocket_url);
const connect = () => {
socket = new WebSocket(`ws://${websocketEndpoint}/`);
const url = `ws://${websocket_url}/`;
socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
socket.onopen = () => {
dispatch({
@@ -60,9 +40,6 @@ export default function WebSocketContextFunction({ children }) {
path: 'websocketState/isConnected',
data: false,
});
setTimeout(() => {
connect();
}, 1000);
};
socket.onerror = (err) => {
@@ -73,14 +50,20 @@ export default function WebSocketContextFunction({ children }) {
);
socket.close();
};
return socket;
};
if (!socket) {
connect();
ws = {
socket,
useEffect(() => {
// cleanup function to close the websocket on rerender
return () => {
socket.close();
};
}
}, [websocket_url]);
connect();
ws = {
socket
};
return (
<WebSocketContext.Provider value={ws}>{children}</WebSocketContext.Provider>
@@ -0,0 +1,30 @@
import * as React from 'react';
import { TextField } from '@mui/material';
import { useDispatch, useSelector } from 'react-redux';
export default function WebSocketUrlField() {
const websocket_url = useSelector(
(state) => state.websocketState.websocket_url,
);
const dispatch = useDispatch();
const websocket_url_onchange = (event) => {
const value = event.target.value;
dispatch({
type: 'write',
path: 'websocketState/websocket_url',
data: value,
});
};
return (
<TextField
className="WebSocketUrlField"
label="WebSocket URL"
variant="outlined"
value={websocket_url}
onChange={websocket_url_onchange}
/>
);
}
+1
View File
@@ -3,6 +3,7 @@ import { split_path } from './utils';
const initialState = {
// the websocket connection state
websocketState: {
websocket_url: 'localhost:7007',
isConnected: false,
},
// the webrtc connection state
+327 -13
View File
@@ -162,7 +162,7 @@
dependencies:
"@babel/types" "^7.18.9"
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6":
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==
@@ -513,7 +513,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-jsx@^7.18.6":
"@babel/plugin-syntax-jsx@^7.17.12", "@babel/plugin-syntax-jsx@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0"
integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==
@@ -1025,7 +1025,7 @@
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@@ -1167,6 +1167,114 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
"@emotion/babel-plugin@^11.10.0":
version "11.10.2"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz#879db80ba622b3f6076917a1e6f648b1c7d008c7"
integrity sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/plugin-syntax-jsx" "^7.17.12"
"@babel/runtime" "^7.18.3"
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/serialize" "^1.1.0"
babel-plugin-macros "^3.1.0"
convert-source-map "^1.5.0"
escape-string-regexp "^4.0.0"
find-root "^1.1.0"
source-map "^0.5.7"
stylis "4.0.13"
"@emotion/cache@^11.10.0", "@emotion/cache@^11.10.3":
version "11.10.3"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.3.tgz#c4f67904fad10c945fea5165c3a5a0583c164b87"
integrity sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/sheet" "^1.2.0"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
stylis "4.0.13"
"@emotion/hash@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7"
integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==
"@emotion/is-prop-valid@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83"
integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/memoize@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
"@emotion/react@^11.10.4":
version "11.10.4"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.4.tgz#9dc6bccbda5d70ff68fdb204746c0e8b13a79199"
integrity sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.0"
"@emotion/cache" "^11.10.0"
"@emotion/serialize" "^1.1.0"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.0.tgz#b1f97b1011b09346a40e9796c37a3397b4ea8ea8"
integrity sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==
dependencies:
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/unitless" "^0.8.0"
"@emotion/utils" "^1.2.0"
csstype "^3.0.2"
"@emotion/sheet@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.0.tgz#771b1987855839e214fc1741bde43089397f7be5"
integrity sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==
"@emotion/styled@^11.10.4":
version "11.10.4"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.4.tgz#e93f84a4d54003c2acbde178c3f97b421fce1cd4"
integrity sha512-pRl4R8Ez3UXvOPfc2bzIoV8u9P97UedgHS4FPX594ntwEuAMA114wlaHvOK24HB48uqfXiGlYIZYCxVJ1R1ttQ==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.0"
"@emotion/is-prop-valid" "^1.2.0"
"@emotion/serialize" "^1.1.0"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
"@emotion/unitless@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db"
integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==
"@emotion/use-insertion-effect-with-fallbacks@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df"
integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==
"@emotion/utils@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561"
integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==
"@emotion/weak-memoize@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@eslint/eslintrc@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
@@ -1492,6 +1600,92 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
"@mui/base@5.0.0-alpha.95":
version "5.0.0-alpha.95"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.95.tgz#eac88ddb6ded633a73cd3c19638241f0f9fa274f"
integrity sha512-fcxnDeO7rBwzP0buVdI5fn0aA7NQ/AeUV5RzIIH0kOXVVT21HB4JFf41Qhwd0PIq63PXxmc6Fs2mdlzMYuPo9g==
dependencies:
"@babel/runtime" "^7.18.9"
"@emotion/is-prop-valid" "^1.2.0"
"@mui/types" "^7.2.0"
"@mui/utils" "^5.10.3"
"@popperjs/core" "^2.11.6"
clsx "^1.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"
"@mui/core-downloads-tracker@^5.10.3":
version "5.10.3"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.10.3.tgz#e17a3cd87c7814ff35592284b19ae39ad52b85ac"
integrity sha512-mX2S0d1oboKBbWQqWIgRmyALAEzh37yiknpD3mKx8bcoMKbp1VtqzIt0aeHP16Uhsd0eValDFILxLNHWi0oddQ==
"@mui/material@^5.10.3":
version "5.10.3"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.10.3.tgz#5497128ea7c8de1d40ba9bd95437d46d441fa1d1"
integrity sha512-g0lzHcqWHYeOEAxTzcwpM1I7b+wyiRTeXkEdRsspnOpZtb0H/1xg386tMFRGbxBJ4zfVGT+TWublofw7pyQkqw==
dependencies:
"@babel/runtime" "^7.18.9"
"@mui/base" "5.0.0-alpha.95"
"@mui/core-downloads-tracker" "^5.10.3"
"@mui/system" "^5.10.3"
"@mui/types" "^7.2.0"
"@mui/utils" "^5.10.3"
"@types/react-transition-group" "^4.4.5"
clsx "^1.2.1"
csstype "^3.1.0"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"
"@mui/private-theming@^5.10.3":
version "5.10.3"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.10.3.tgz#7325eef3e480caaaa2d866b9057943ec4fbcb8ce"
integrity sha512-LCYIKlkGz2BTSng2BFzzwSJBRZbChIUri2x2Nh8ryk2B1Ho7zpvE7ex6y39LlStG2Frf92NFC/V4YQbmMAjD5A==
dependencies:
"@babel/runtime" "^7.18.9"
"@mui/utils" "^5.10.3"
prop-types "^15.8.1"
"@mui/styled-engine@^5.10.3":
version "5.10.3"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.10.3.tgz#c3e061548951568936b749a58531fb269af48948"
integrity sha512-9Uz7eB8xXoiDvpJ9qBxZ/2xGO8xKfA2T23dw4AsQ69SQtGatrOLAapzP2lNr0tfB9xvKucclPFhRO5aLhDFOVQ==
dependencies:
"@babel/runtime" "^7.18.9"
"@emotion/cache" "^11.10.3"
csstype "^3.1.0"
prop-types "^15.8.1"
"@mui/system@^5.10.3":
version "5.10.3"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.10.3.tgz#26f82e506af506f4fddde265c20e1100baaca03e"
integrity sha512-uLW/CIz3zk1jr5zH0ahOUqJIrpWP02Mv4emfrplh7Mh5JCb/oumhYaC/ALJJEjzUHKg9wwiyuM0pCwK/kSf1jQ==
dependencies:
"@babel/runtime" "^7.18.9"
"@mui/private-theming" "^5.10.3"
"@mui/styled-engine" "^5.10.3"
"@mui/types" "^7.2.0"
"@mui/utils" "^5.10.3"
clsx "^1.2.1"
csstype "^3.1.0"
prop-types "^15.8.1"
"@mui/types@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.0.tgz#91380c2d42420f51f404120f7a9270eadd6f5c23"
integrity sha512-lGXtFKe5lp3UxTBGqKI1l7G8sE2xBik8qCfrLHD5olwP/YU0/ReWoWT7Lp1//ri32dK39oPMrJN8TgbkCSbsNA==
"@mui/utils@^5.10.3":
version "5.10.3"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.10.3.tgz#ce2a96f31de2a5e717f507b5383dbabbddbc4dfc"
integrity sha512-4jXMDPfx6bpMVuheLaOpKTjpzw39ogAZLeaLj5+RJec3E37/hAZMYjURfblLfTWMMoGoqkY03mNsZaEwNobBow==
dependencies:
"@babel/runtime" "^7.18.9"
"@types/prop-types" "^15.7.5"
"@types/react-is" "^16.7.1 || ^17.0.0"
prop-types "^15.8.1"
react-is "^18.2.0"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -1528,6 +1722,11 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@popperjs/core@^2.11.6", "@popperjs/core@^2.4.0":
version "2.11.6"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
"@radix-ui/popper@0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063"
@@ -2193,7 +2392,7 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.4.tgz#ad899dad022bab6b5a9f0a0fe67c2f7a4a8950ed"
integrity sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw==
"@types/prop-types@*":
"@types/prop-types@*", "@types/prop-types@^15.7.5":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
@@ -2220,6 +2419,20 @@
dependencies:
"@types/react" "*"
"@types/react-is@^16.7.1 || ^17.0.0":
version "17.0.3"
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a"
integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.5":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
dependencies:
"@types/react" "*"
"@types/react@*":
version "18.0.15"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
@@ -3254,7 +3467,7 @@ check-types@^11.1.1:
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
chokidar@^3.4.2, chokidar@^3.5.3:
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.2, chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@@ -3284,7 +3497,7 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
classnames@^2.3.1:
classnames@^2.2.6, classnames@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
@@ -3305,6 +3518,11 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
clsx@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -3457,7 +3675,7 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
@@ -3732,7 +3950,7 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
csstype@^3.0.2, csstype@^3.0.4:
csstype@^3.0.2, csstype@^3.0.4, csstype@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
@@ -3957,6 +4175,14 @@ dom-converter@^0.2.0:
dependencies:
utila "~0.4"
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@@ -4391,6 +4617,18 @@ eslint-plugin-testing-library@^5.0.1:
dependencies:
"@typescript-eslint/utils" "^5.13.0"
eslint-plugin-unused-imports@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz#d8db8c4d0cfa0637a8b51ce3fd7d1b6bc3f08520"
integrity sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==
dependencies:
eslint-rule-composer "^0.3.0"
eslint-rule-composer@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==
eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
@@ -4747,6 +4985,11 @@ find-cache-dir@^3.3.1:
make-dir "^3.0.2"
pkg-dir "^4.1.0"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
@@ -5093,7 +5336,7 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -5285,6 +5528,11 @@ immer@^9.0.7:
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.15.tgz#0b9169e5b1d22137aba7d43f8a81a495dd1b62dc"
integrity sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==
immutable@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -7664,7 +7912,7 @@ prompts@^2.0.1, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.8.1:
prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7742,6 +7990,11 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
re-resizable@^6.9.9:
version "6.9.9"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.9.tgz#99e8b31c67a62115dc9c5394b7e55892265be216"
integrity sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==
react-app-polyfill@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7"
@@ -7811,6 +8064,11 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
react-icons@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703"
integrity sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -7821,11 +8079,21 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.0.0:
react-is@^18.0.0, react-is@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
react-pro-sidebar@^0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/react-pro-sidebar/-/react-pro-sidebar-0.7.1.tgz#0b5edca0809ff6a23bda188c5db370f880690ee7"
integrity sha512-Iy1X8ce4t5Vqz4CsyzjwokGUE3/IObgmYzS0ins7/2eWKle0SMUPaWdgMKFIVjtVrMr5vmjPbRicq8FxnVaf8A==
dependencies:
"@popperjs/core" "^2.4.0"
classnames "^2.2.6"
react-slidedown "^2.4.5"
resize-observer-polyfill "^1.5.1"
react-redux@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.2.tgz#bc2a304bb21e79c6808e3e47c50fe1caf62f7aad"
@@ -7898,6 +8166,23 @@ react-scripts@^5.0.1:
optionalDependencies:
fsevents "^2.3.2"
react-slidedown@^2.4.5:
version "2.4.7"
resolved "https://registry.yarnpkg.com/react-slidedown/-/react-slidedown-2.4.7.tgz#c09e72bba8aac25018fd644ece041da771854589"
integrity sha512-HGDfrqo70r1WVE0DwrySPdCT27/2wcZaJYh5kOnmuPSCtjDDJrNkDdn4Ep/cma2VVfwupeAGhbc2pbrGThU6VQ==
dependencies:
tslib "^2.0.0"
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@^18.1.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -8071,6 +8356,11 @@ reselect@^4.1.5:
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656"
integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==
resize-observer-polyfill@^1.5.1:
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@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -8198,6 +8488,15 @@ sass-loader@^12.3.0:
klona "^2.0.4"
neo-async "^2.6.2"
sass@^1.54.8:
version "1.54.8"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.8.tgz#4adef0dd86ea2b1e4074f551eeda4fc5f812a996"
integrity sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -8437,7 +8736,7 @@ source-list-map@^2.0.0, source-list-map@^2.0.1:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
source-map-js@^1.0.1, source-map-js@^1.0.2:
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -8472,6 +8771,11 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
source-map@^0.7.3:
version "0.7.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
@@ -8546,6 +8850,11 @@ stackframe@^1.3.4:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
stats.js@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/stats.js/-/stats.js-0.17.0.tgz#b1c3dc46d94498b578b7fd3985b81ace7131cc7d"
integrity sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
@@ -8700,6 +9009,11 @@ stylehacks@^5.1.0:
browserslist "^4.16.6"
postcss-selector-parser "^6.0.4"
stylis@4.0.13:
version "4.0.13"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -8969,7 +9283,7 @@ tslib@^1.8.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3, tslib@^2.1.0:
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==