diff --git a/nerfactory/viewer/app/.eslintrc.json b/nerfactory/viewer/app/.eslintrc.json index e6eb3ed02..8884cfb12 100644 --- a/nerfactory/viewer/app/.eslintrc.json +++ b/nerfactory/viewer/app/.eslintrc.json @@ -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 } -} +} \ No newline at end of file diff --git a/nerfactory/viewer/app/package.json b/nerfactory/viewer/app/package.json index 365c29d94..4ac52a07f 100644 --- a/nerfactory/viewer/app/package.json +++ b/nerfactory/viewer/app/package.json @@ -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", diff --git a/nerfactory/viewer/app/src/App.css b/nerfactory/viewer/app/src/App.css deleted file mode 100644 index ec2585e8c..000000000 --- a/nerfactory/viewer/app/src/App.css +++ /dev/null @@ -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; -} diff --git a/nerfactory/viewer/app/src/App.jsx b/nerfactory/viewer/app/src/App.jsx index 887aa4054..7f08e1550 100644 --- a/nerfactory/viewer/app/src/App.jsx +++ b/nerfactory/viewer/app/src/App.jsx @@ -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 (
- - - + {/* + Code that listens for websocket 'write' messages and updates the redux store. + */} + + {/* ----- */} + +
+
+ +
+ + +
); } diff --git a/nerfactory/viewer/app/src/SceneNode.js b/nerfactory/viewer/app/src/SceneNode.js index edfa23d58..4fd179074 100644 --- a/nerfactory/viewer/app/src/SceneNode.js +++ b/nerfactory/viewer/app/src/SceneNode.js @@ -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]; } } diff --git a/nerfactory/viewer/app/src/index.jsx b/nerfactory/viewer/app/src/index.jsx index 7ab345c5c..855be0964 100644 --- a/nerfactory/viewer/app/src/index.jsx +++ b/nerfactory/viewer/app/src/index.jsx @@ -1,3 +1,4 @@ +import './index.scss'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; diff --git a/nerfactory/viewer/app/src/index.scss b/nerfactory/viewer/app/src/index.scss new file mode 100644 index 000000000..778d7d65d --- /dev/null +++ b/nerfactory/viewer/app/src/index.scss @@ -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; +} diff --git a/nerfactory/viewer/app/src/modules/Banner/Banner.jsx b/nerfactory/viewer/app/src/modules/Banner/Banner.jsx new file mode 100644 index 000000000..17062fafb --- /dev/null +++ b/nerfactory/viewer/app/src/modules/Banner/Banner.jsx @@ -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); + const [anchorEl, setAnchorEl] = React.useState(0); + const dispatch = useDispatch(); + + const open = Boolean(anchorEl); + const handleClick = (event: React.MouseEvent) => { + 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 ( +
+ + + + Load Training Config + Export Training Config + Dark Mode + + +
+ ); +} diff --git a/nerfactory/viewer/app/src/modules/Banner/index.jsx b/nerfactory/viewer/app/src/modules/Banner/index.jsx new file mode 100644 index 000000000..6c682527f --- /dev/null +++ b/nerfactory/viewer/app/src/modules/Banner/index.jsx @@ -0,0 +1,3 @@ +import Banner from './Banner'; + +export default Banner; \ No newline at end of file diff --git a/nerfactory/viewer/app/src/modules/ConfigPanel/ConfigPanel.css b/nerfactory/viewer/app/src/modules/ConfigPanel/ConfigPanel.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/nerfactory/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx b/nerfactory/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx index c26e42b38..4fe9bbdb7 100644 --- a/nerfactory/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx +++ b/nerfactory/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx @@ -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; } diff --git a/nerfactory/viewer/app/src/modules/LandingModal/LandingModal.jsx b/nerfactory/viewer/app/src/modules/LandingModal/LandingModal.jsx new file mode 100644 index 000000000..22d1de6ca --- /dev/null +++ b/nerfactory/viewer/app/src/modules/LandingModal/LandingModal.jsx @@ -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 :.`; + const text_2 = + 'If the WebSocket is not connecting, make sure your port is being forwarded properly!'; + + return ( +
+ + + + + Welcome to the Nerfactory Viewer!{' '} + The favicon. + + +
+ Getting Started +

{text_1}

+

+ You can enter your WebSocket URL in the Status Panel or here: +

+ +

{text_2}

+ Other Resources: + + +
+
+
+
+
+ ); +} diff --git a/nerfactory/viewer/app/src/modules/LandingModal/index.jsx b/nerfactory/viewer/app/src/modules/LandingModal/index.jsx new file mode 100644 index 000000000..f44274051 --- /dev/null +++ b/nerfactory/viewer/app/src/modules/LandingModal/index.jsx @@ -0,0 +1,3 @@ +import LandingModel from './LandingModal'; + +export default LandingModel; \ No newline at end of file diff --git a/nerfactory/viewer/app/src/modules/Scene/Scene.css b/nerfactory/viewer/app/src/modules/Scene/Scene.css deleted file mode 100644 index 43ff9bf1a..000000000 --- a/nerfactory/viewer/app/src/modules/Scene/Scene.css +++ /dev/null @@ -1,5 +0,0 @@ -#datgui { - position: absolute; - top: 2px; - left: 2px; -} diff --git a/nerfactory/viewer/app/src/modules/Scene/Scene.jsx b/nerfactory/viewer/app/src/modules/Scene/Scene.jsx index 4e5a1b891..f2aff9538 100644 --- a/nerfactory/viewer/app/src/modules/Scene/Scene.jsx +++ b/nerfactory/viewer/app/src/modules/Scene/Scene.jsx @@ -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 } diff --git a/nerfactory/viewer/app/src/modules/Scene/drawing.js b/nerfactory/viewer/app/src/modules/Scene/drawing.js index bf411657d..2e8419cdb 100644 --- a/nerfactory/viewer/app/src/modules/Scene/drawing.js +++ b/nerfactory/viewer/app/src/modules/Scene/drawing.js @@ -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', diff --git a/nerfactory/viewer/app/src/modules/SidePanel/CameraPanel/CameraPanel.jsx b/nerfactory/viewer/app/src/modules/SidePanel/CameraPanel/CameraPanel.jsx new file mode 100644 index 000000000..b20f97495 --- /dev/null +++ b/nerfactory/viewer/app/src/modules/SidePanel/CameraPanel/CameraPanel.jsx @@ -0,0 +1,14 @@ +import * as React from 'react'; + +import { Button } from '@mui/material'; + +export default function CameraPanel() { + return ( +
+ TODO: create a camera path panel list of cameras + +
+ ); +} diff --git a/nerfactory/viewer/app/src/modules/SidePanel/CameraPanel/index.jsx b/nerfactory/viewer/app/src/modules/SidePanel/CameraPanel/index.jsx new file mode 100644 index 000000000..200115208 --- /dev/null +++ b/nerfactory/viewer/app/src/modules/SidePanel/CameraPanel/index.jsx @@ -0,0 +1,3 @@ +import CameraPanel from './CameraPanel'; + +export default CameraPanel; \ No newline at end of file diff --git a/nerfactory/viewer/app/src/modules/SidePanel/SidePanel.jsx b/nerfactory/viewer/app/src/modules/SidePanel/SidePanel.jsx new file mode 100644 index 000000000..028b1d158 --- /dev/null +++ b/nerfactory/viewer/app/src/modules/SidePanel/SidePanel.jsx @@ -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 ( + + ); +} + +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 ; +} + +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 ( + }> + + + ); + } + return ( + } defaultOpen> + {Object.keys(scene_node.children).map((key) => + get_menu_items(key, scene_node.children[key]), + )} + + ); + }; + + const menu_items = get_menu_items('Scene', sceneTree); + + return ( + + {menu_items} + + ); +} + +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 ( +
+ +
+ + + + + + + + + + +
+ +
+
+ +
+ +
+
+ + + + + + TODO: add a logging panel + +
+
+ ); +} diff --git a/nerfactory/viewer/app/src/modules/SidePanel/StatusPanel/StatusPanel.jsx b/nerfactory/viewer/app/src/modules/SidePanel/StatusPanel/StatusPanel.jsx new file mode 100644 index 000000000..3c59dded1 --- /dev/null +++ b/nerfactory/viewer/app/src/modules/SidePanel/StatusPanel/StatusPanel.jsx @@ -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 ( +
+
Status Panel
+ + + + +
+
+ Eval FPS: {eval_fps} +
+
+ Train ETA: {train_eta} +
+
+ Time Allocation: {vis_train_ratio} +
+
+
+ ); +} diff --git a/nerfactory/viewer/app/src/modules/SidePanel/StatusPanel/index.jsx b/nerfactory/viewer/app/src/modules/SidePanel/StatusPanel/index.jsx new file mode 100644 index 000000000..2c3fd81ac --- /dev/null +++ b/nerfactory/viewer/app/src/modules/SidePanel/StatusPanel/index.jsx @@ -0,0 +1,3 @@ +import StatusPanel from './StatusPanel'; + +export default StatusPanel; diff --git a/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.css b/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.css deleted file mode 100644 index 2390151aa..000000000 --- a/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.css +++ /dev/null @@ -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%; -} diff --git a/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.jsx b/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.jsx index f5611b586..b82683977 100644 --- a/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.jsx +++ b/nerfactory/viewer/app/src/modules/ViewerWindow/ViewerWindow.jsx @@ -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 ( -
+ <> {/* the webrtc viewer needs to know the camera pose */}
-
+ ); } diff --git a/nerfactory/viewer/app/src/modules/WebRtcWindow/WebRtcWindow.jsx b/nerfactory/viewer/app/src/modules/WebRtcWindow/WebRtcWindow.jsx index 3b93464c6..55398e4cd 100644 --- a/nerfactory/viewer/app/src/modules/WebRtcWindow/WebRtcWindow.jsx +++ b/nerfactory/viewer/app/src/modules/WebRtcWindow/WebRtcWindow.jsx @@ -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 (
diff --git a/nerfactory/viewer/app/src/modules/WebSocket/WebSocket.jsx b/nerfactory/viewer/app/src/modules/WebSocket/WebSocket.jsx index 89c1f7416..934b8b803 100644 --- a/nerfactory/viewer/app/src/modules/WebSocket/WebSocket.jsx +++ b/nerfactory/viewer/app/src/modules/WebSocket/WebSocket.jsx @@ -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 "?websocket_url=localhost:". 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://" - 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 ( {children} diff --git a/nerfactory/viewer/app/src/modules/WebSocketUrlField.jsx b/nerfactory/viewer/app/src/modules/WebSocketUrlField.jsx new file mode 100644 index 000000000..6310f9ba4 --- /dev/null +++ b/nerfactory/viewer/app/src/modules/WebSocketUrlField.jsx @@ -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 ( + + ); +} diff --git a/nerfactory/viewer/app/src/reducer.js b/nerfactory/viewer/app/src/reducer.js index 6b6d3b56b..bdfc509a1 100644 --- a/nerfactory/viewer/app/src/reducer.js +++ b/nerfactory/viewer/app/src/reducer.js @@ -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 diff --git a/nerfactory/viewer/app/yarn.lock b/nerfactory/viewer/app/yarn.lock index c8a7ea1ec..238148356 100644 --- a/nerfactory/viewer/app/yarn.lock +++ b/nerfactory/viewer/app/yarn.lock @@ -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==