diff --git a/package.json b/package.json index 735384c..d7af1e6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@livekit/components-react": "^1.1.6", "@livekit/components-styles": "^1.0.6", "@uidotdev/usehooks": "^2.0.1", + "ahooks": "^3.7.10", "date-fns": "^2.30.0", "i18next": "^23.8.2", "i18next-browser-languagedetector": "^7.2.0", @@ -24,6 +25,7 @@ "react-countdown": "^2.3.5", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", + "react-draggable": "^4.4.6", "react-full-screen": "^1.1.1", "react-i18next": "^14.0.3", "react-input-mask": "^2.0.4", diff --git a/public/icons/Chat.svg b/public/icons/Chat.svg new file mode 100644 index 0000000..433def1 --- /dev/null +++ b/public/icons/Chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/Close.svg b/public/icons/Close.svg new file mode 100644 index 0000000..36ddcf7 --- /dev/null +++ b/public/icons/Close.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/public/icons/Detach.svg b/public/icons/Detach.svg new file mode 100644 index 0000000..082f4ec --- /dev/null +++ b/public/icons/Detach.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/Fullscreen.svg b/public/icons/Fullscreen.svg new file mode 100644 index 0000000..a3573a9 --- /dev/null +++ b/public/icons/Fullscreen.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/Gear.svg b/public/icons/Gear.svg new file mode 100644 index 0000000..c339544 --- /dev/null +++ b/public/icons/Gear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/HandOff.svg b/public/icons/HandOff.svg new file mode 100644 index 0000000..a7cb35a --- /dev/null +++ b/public/icons/HandOff.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/icons/HandOn.svg b/public/icons/HandOn.svg new file mode 100644 index 0000000..794c60a --- /dev/null +++ b/public/icons/HandOn.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/icons/LoaderPrimary.png b/public/icons/LoaderPrimary.png new file mode 100644 index 0000000..1c16c1f Binary files /dev/null and b/public/icons/LoaderPrimary.png differ diff --git a/public/icons/MicroOn.svg b/public/icons/MicroOn.svg new file mode 100644 index 0000000..cdda753 --- /dev/null +++ b/public/icons/MicroOn.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/icons/More.svg b/public/icons/More.svg new file mode 100644 index 0000000..caee5dc --- /dev/null +++ b/public/icons/More.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/icons/Persons.svg b/public/icons/Persons.svg new file mode 100644 index 0000000..3b60851 --- /dev/null +++ b/public/icons/Persons.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/public/icons/Share.svg b/public/icons/Share.svg new file mode 100644 index 0000000..f81e2e3 --- /dev/null +++ b/public/icons/Share.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ErrorBoundary.tsx b/src/ErrorBoundary.tsx new file mode 100644 index 0000000..16f81a0 --- /dev/null +++ b/src/ErrorBoundary.tsx @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useRouteError } from "react-router-dom"; + +function ErrorBoundary() { + const error: any = useRouteError(); + console.error(error); + + return ( +
+
+

{error.status}

+

{error.statusText}

+
+
+ ); +} + +export default ErrorBoundary; diff --git a/src/components/PixelStreamingWrapper.tsx b/src/components/PixelStreamingWrapper.tsx index 25068cb..e36c95e 100644 --- a/src/components/PixelStreamingWrapper.tsx +++ b/src/components/PixelStreamingWrapper.tsx @@ -1,4 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable no-empty */ // Copyright Epic Games, Inc. All Rights Reserved. import { useEffect, useRef, useState } from "react"; @@ -6,16 +8,17 @@ import { Config, AllSettings, PixelStreaming, + LatencyTestResults, } from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3"; -import { Trans } from "react-i18next"; -import LoaderIcon from "./icons/LoaderIcon"; export interface PixelStreamingWrapperProps { initialSettings?: Partial; + onVideoInitialized?: () => void; } export const PixelStreamingWrapper = ({ initialSettings, + onVideoInitialized, }: PixelStreamingWrapperProps) => { // A reference to parent div element that the Pixel Streaming library attaches into: const videoParent = useRef(null); @@ -24,10 +27,10 @@ export const PixelStreamingWrapper = ({ const [pixelStreaming, setPixelStreaming] = useState(); // A boolean state variable that determines if the Click to play overlay is shown: - const [clickToPlayVisible, setClickToPlayVisible] = useState(false); - const [videoInitialized, setVideoInitialized] = useState(false); - const videoInitializedRef = useRef(); - videoInitializedRef.current = videoInitialized; + const [clickToPlayVisible, setClickToPlayVisible] = useState(false); + + const [latencyTestResult, setLatencyTestResult] = + useState(); // Run on component mount: useEffect(() => { @@ -38,132 +41,93 @@ export const PixelStreamingWrapper = ({ videoElementParent: videoParent.current, }); - streaming.addEventListener("videoInitialized", () => { - setVideoInitialized(true); - }); - // register a playStreamRejected handler to show Click to play overlay if needed: streaming.addEventListener("playStreamRejected", () => { setClickToPlayVisible(true); }); + streaming.addEventListener("videoInitialized", () => { + onVideoInitialized && onVideoInitialized(); + }); + + streaming.addEventListener("latencyTestResult", (e) => { + setLatencyTestResult(e.data.latencyTimings); + // console.log("Data", e.data.latencyTimings); + }); + + setInterval(() => { + streaming.requestLatencyTest(); + }, 500); + // Save the library instance into component state so that it can be accessed later: setPixelStreaming(streaming); - document.getElementById("hiddenInput")?.remove(); - document.getElementById("editTextButton")?.remove(); - - setTimeout(() => { - if (!videoInitializedRef.current) { - window.location.reload(); - } - }, 10000); - // Clean up on component unmount: return () => { try { streaming.disconnect(); - } catch { - // - } + } catch {} }; } }, []); return ( -
-
- {!videoInitialized && ( -
- - Буферизация потока -
- )} +
+
{clickToPlayVisible && ( -
-
-
-

- Демонстрация начата -

-

- - Нажмите, чтобы продолжить - -

-
- - -
+
{ + pixelStreaming?.play(); + setClickToPlayVisible(false); + }} + > +
Click to play
)} + +
+ {latencyTestResult && + Object.entries(latencyTestResult).map(([key, value], i) => { + if ( + [ + "EncodeMs", + "CaptureToSendMs", + "latencyExcludingDecode", + "networkLatency", + "testStartTimeMs", + ].includes(key) + ) + return ( +
+ {key}: {value} +
+ ); + })} +
); }; diff --git a/src/components/icons/AttachIcon.tsx b/src/components/icons/AttachIcon.tsx new file mode 100644 index 0000000..f45135a --- /dev/null +++ b/src/components/icons/AttachIcon.tsx @@ -0,0 +1,27 @@ +function AttachIcon() { + return ( + + + + + ); +} + +export default AttachIcon; diff --git a/src/components/icons/ChatIcon.tsx b/src/components/icons/ChatIcon.tsx index 5201671..e000dac 100644 --- a/src/components/icons/ChatIcon.tsx +++ b/src/components/icons/ChatIcon.tsx @@ -1,17 +1,17 @@ -interface IconProps { - className?: string; -} - -function ChatIcon({ className }: IconProps) { +function ChatIcon() { return ( - - + ); } diff --git a/src/components/icons/CloseIcon.tsx b/src/components/icons/CloseIcon.tsx index 1a35e7c..9a6fcdc 100644 --- a/src/components/icons/CloseIcon.tsx +++ b/src/components/icons/CloseIcon.tsx @@ -7,16 +7,13 @@ function CloseIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - + ); } diff --git a/src/components/icons/DetachIcon.tsx b/src/components/icons/DetachIcon.tsx new file mode 100644 index 0000000..ed9303b --- /dev/null +++ b/src/components/icons/DetachIcon.tsx @@ -0,0 +1,28 @@ +function DetachIcon() { + return ( + + + + + ); +} + +export default DetachIcon; diff --git a/src/components/icons/FullscreenIcon.tsx b/src/components/icons/FullscreenIcon.tsx index 34e7163..27ca23c 100644 --- a/src/components/icons/FullscreenIcon.tsx +++ b/src/components/icons/FullscreenIcon.tsx @@ -7,16 +7,20 @@ function FullscreenIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - + + ); } diff --git a/src/components/icons/GearIcon.tsx b/src/components/icons/GearIcon.tsx new file mode 100644 index 0000000..814f22a --- /dev/null +++ b/src/components/icons/GearIcon.tsx @@ -0,0 +1,28 @@ +function GearIcon() { + return ( + + + + + ); +} + +export default GearIcon; diff --git a/src/components/icons/HandOffIcon.tsx b/src/components/icons/HandOffIcon.tsx index a03106f..5c40efb 100644 --- a/src/components/icons/HandOffIcon.tsx +++ b/src/components/icons/HandOffIcon.tsx @@ -7,30 +7,21 @@ function HandOffIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - - - - - + + ); } diff --git a/src/components/icons/MicroOffIcon.tsx b/src/components/icons/MicroOffIcon.tsx index 5ddcd09..149d029 100644 --- a/src/components/icons/MicroOffIcon.tsx +++ b/src/components/icons/MicroOffIcon.tsx @@ -7,25 +7,21 @@ function MicroOffIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - - + + ); } diff --git a/src/components/icons/MicroOnIcon.tsx b/src/components/icons/MicroOnIcon.tsx index 5570df7..c6ea9e5 100644 --- a/src/components/icons/MicroOnIcon.tsx +++ b/src/components/icons/MicroOnIcon.tsx @@ -7,18 +7,13 @@ function MicroOnIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - - - - + ); } diff --git a/src/components/icons/MoreIcon.tsx b/src/components/icons/MoreIcon.tsx new file mode 100644 index 0000000..ec25d90 --- /dev/null +++ b/src/components/icons/MoreIcon.tsx @@ -0,0 +1,17 @@ +function MoreIcon() { + return ( + + + + + + ); +} + +export default MoreIcon; diff --git a/src/components/icons/PersonsIcon.tsx b/src/components/icons/PersonsIcon.tsx index 91a15b6..8e1e372 100644 --- a/src/components/icons/PersonsIcon.tsx +++ b/src/components/icons/PersonsIcon.tsx @@ -7,25 +7,18 @@ function PersonsIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - - - - - + + ); } diff --git a/src/components/icons/ShareIcon.tsx b/src/components/icons/ShareIcon.tsx index 20caeb8..f8706c4 100644 --- a/src/components/icons/ShareIcon.tsx +++ b/src/components/icons/ShareIcon.tsx @@ -7,17 +7,13 @@ function ShareIcon() { fill="none" xmlns="http://www.w3.org/2000/svg" > - - - - - - + ); } diff --git a/src/components/icons/WindowIcon.tsx b/src/components/icons/WindowIcon.tsx new file mode 100644 index 0000000..82f6b99 --- /dev/null +++ b/src/components/icons/WindowIcon.tsx @@ -0,0 +1,28 @@ +function WindowIcon() { + return ( + + + + + ); +} + +export default WindowIcon; diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 0000000..0d4d513 --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,67 @@ +interface ButtonProps { + type?: "button" | "reset" | "submit"; + variant?: "primary" | "secondary" | "tertiary"; + fullWidth?: boolean; + large?: boolean; + icon?: JSX.Element; + onlyIcon?: boolean; + disabled?: boolean; + children?: React.ReactNode; + onClick?: () => void; +} + +function Button({ + type = "button", + variant = "primary", + fullWidth, + large, + icon, + onlyIcon, + disabled, + children, + onClick, +}: ButtonProps) { + const variantClasses = [ + { + name: "primary", + classes: + "enabled:bg-[#49A1F5] enabled:hover:bg-[#4190DB] enabled:text-white disabled:bg-[#F2F2F2] disabled:text-[#CCCCCC]", + }, + { + name: "secondary", + classes: + "enabled:bg-[#F0F1F2] enabled:hover:bg-[#E6ECF2] enabled:text-[#77828C] disabled:bg-[#F2F2F2] disabled:text-[##CCCCCC]", + }, + { + name: "tertiary", + classes: + "enabled:hover:bg-[#E6ECF2] enabled:text-[#77828C] disabled:text-[#CCCCCC]", + }, + ]; + + return ( + + ); +} + +export default Button; diff --git a/src/main.tsx b/src/main.tsx index 0961f0b..1c8e426 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,22 +8,24 @@ import { import "./index.css"; import "./i18n"; import App from "./App"; -import StreamPage from "./StreamPage"; import HistoryPage from "./HistoryPage"; import ScheduledPage from "./ScheduledPage"; import CalendarPage from "./CalendarPage"; import useAuthStore from "./stores/useAuthStore"; import PersonalAreaLoginPage from "./PersonalAreaLoginPage"; import PersonalAreaDashboardPage from "./PersonalAreaDashboardPage"; +import StreamPage2 from "./pages/StreamPage2"; +import ErrorBoundary from "./ErrorBoundary"; const router = createBrowserRouter([ { path: "/", element: , + errorElement: , }, { path: "/stream/:id", - element: , + element: , }, { path: "/history", diff --git a/src/pages/StreamPage2.css b/src/pages/StreamPage2.css new file mode 100644 index 0000000..bb3daf5 --- /dev/null +++ b/src/pages/StreamPage2.css @@ -0,0 +1,15 @@ +.entering { + opacity: 1; +} + +.entered { + opacity: 1; +} + +.exiting { + opacity: 0; +} + +.exited { + opacity: 0; +} diff --git a/src/pages/StreamPage2.tsx b/src/pages/StreamPage2.tsx new file mode 100644 index 0000000..680147b --- /dev/null +++ b/src/pages/StreamPage2.tsx @@ -0,0 +1,468 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable react-hooks/exhaustive-deps */ +import "./StreamPage2.css"; +import ky from "ky"; +import { PixelStreamingWrapper } from "../components/PixelStreamingWrapper"; +import { useParams } from "react-router-dom"; +import { FormEvent, useEffect, useRef, useState } from "react"; +import { Transition } from "react-transition-group"; +import Button from "../components/ui/Button"; +import CloseIcon from "../components/icons/CloseIcon"; +import HandOffIcon from "../components/icons/HandOffIcon"; +import MicroOffIcon from "../components/icons/MicroOffIcon"; +import PersonsIcon from "../components/icons/PersonsIcon"; +import MicroOnIcon from "../components/icons/MicroOnIcon"; +import MoreIcon from "../components/icons/MoreIcon"; +import FullscreenIcon from "../components/icons/FullscreenIcon"; +import WindowIcon from "../components/icons/WindowIcon"; +import ChatIcon from "../components/icons/ChatIcon"; +import ShareIcon from "../components/icons/ShareIcon"; +import GearIcon from "../components/icons/GearIcon"; +import { useFullscreen } from "ahooks"; +import AttachIcon from "../components/icons/AttachIcon"; +import DetachIcon from "../components/icons/DetachIcon"; +import Draggable from "react-draggable"; + +interface User { + id: string; + username: string; +} + +function StreamPage2() { + const params = useParams(); + const [wsUrl, setWsUrl] = useState(); + const [username, setUsername] = useState(""); + const [step, setStep] = useState(1); + const usernameRef = useRef(null!); + const [users, setUsers] = useState([ + { + id: "1", + username: "User 1", + }, + { + id: "2", + username: "User 2", + }, + { + id: "3", + username: "User 3", + }, + { + id: "4", + username: "User 3", + }, + { + id: "1", + username: "User 1", + }, + { + id: "2", + username: "User 2", + }, + ]); + const [isShowUsers, setIsShowUsers] = useState(false); + const [isShowChat, setIsShowChat] = useState(false); + const [isMicEnabled, setIsMicEnabled] = useState(false); + const [isVideoInitialized, setIsVideoInitialized] = useState(false); + const ref = useRef(null); + const [isFullscreen, { toggleFullscreen }] = useFullscreen(ref); + const [isUsersDetached, setIsUsersDetached] = useState(false); + const [isChatDetached, setIsChatDetached] = useState(false); + + async function getWsUrl() { + const activeSession: any = await ky + .get(`${import.meta.env.VITE_COORD_URL}/active_sessions/${params.id}`) + .json(); + + setWsUrl( + `wss://${activeSession.location}.sess.stream.graff.tech/${activeSession.server}/${activeSession.cirrusPort}/` + ); + } + + function handleSetUsername(e: FormEvent) { + e.preventDefault(); + + if (!username) { + usernameRef.current.focus(); + return; + } + + setStep(2); + } + + function setUsernameGuest() { + setUsername("Гость"); + setStep(2); + } + + function allowMic() { + setStep(3); + } + + function disallowMic() { + setStep(3); + } + + useEffect(() => { + if (!username) return; + + setUsername(() => username.trim()); + }, [username]); + + useEffect(() => { + if (isChatDetached || isUsersDetached) { + document.body.classList.add("overflow-hidden"); + } else { + document.body.classList.remove("overflow-hidden"); + } + }, [isUsersDetached, isChatDetached]); + + useEffect(() => { + getWsUrl(); + }, []); + + return ( +
+
+
+
+
+
+ {username && username[0].toUpperCase()} +
+

{username}

+
+
+
+
+
+ {users.map( + (user, index) => + index < 3 && ( +
+
+ {user.username && user.username[0].toUpperCase()} +
+

{user.username}

+
+ ) + )} + + {users.length > 3 && ( +
+
+ + +
+ +
+
+ {wsUrl && ( + setIsVideoInitialized(true)} + /> + )} +
+
+ + + + + + + +
+
+
+ + + {(state) => ( +
+ + {(state) => ( +
+
+

Здравствуйте!

+
+
+

Представьтесь, пожалуйста

+

+ Так мы будем знать, как к вам обратиться +

+
+
+
+ + setUsername(e.target.value)} + /> +
+
+
+ + +
+
+ )} +
+ + + {(state) => ( +
+
+

+ Хотите принять участие в обсуждении? +

+
+
+
+ +
+

Разрешите использование микрофона

+
+
+

+ Выключить микрофон можно в любой момент +

+
+
+ + +
+
+ )} +
+ + + {(state) => ( +
+
+

+ Пожалуйста, подождите +

+
+
+ +

Подключение

+
+
+ )} +
+
+ )} +
+
+ ); +} + +export default StreamPage2; diff --git a/yarn.lock b/yarn.lock index 563b31b..3179b06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -596,6 +596,20 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +ahooks@^3.7.10: + version "3.7.10" + resolved "https://registry.yarnpkg.com/ahooks/-/ahooks-3.7.10.tgz#6ac1dfbcedff8c673367c044ffef41ffae6da483" + integrity sha512-/HLYif7sFA/5qSuWKrwvjDbf3bq+sdaMrUWS7XGCDRWdC2FrG/i+u5LZdakMYc6UIgJTMQ7tGiJCV7sdU4kSIw== + dependencies: + "@babel/runtime" "^7.21.0" + dayjs "^1.9.1" + intersection-observer "^0.12.0" + js-cookie "^2.x.x" + lodash "^4.17.21" + resize-observer-polyfill "^1.5.1" + screenfull "^5.0.0" + tslib "^2.4.1" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -828,6 +842,11 @@ date-fns@^2.30.0: dependencies: "@babel/runtime" "^7.21.0" +dayjs@^1.9.1: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -1316,6 +1335,11 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +intersection-observer@^0.12.0: + version "0.12.2" + resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" + integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== + invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -1388,6 +1412,11 @@ jiti@^1.19.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +js-cookie@^2.x.x: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -1620,6 +1649,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loglevel@1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" @@ -1948,6 +1982,14 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-draggable@^4.4.6: + version "4.4.6" + resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.6.tgz#63343ee945770881ca1256a5b6fa5c9f5983fe1e" + integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw== + dependencies: + clsx "^1.1.1" + prop-types "^15.8.1" + react-full-screen@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/react-full-screen/-/react-full-screen-1.1.1.tgz#b707d56891015a71c503a65dbab3086d75be97d7" @@ -2071,6 +2113,11 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== +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-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2125,6 +2172,11 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +screenfull@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba" + integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== + sdp-transform@^2.14.1: version "2.14.2" resolved "https://registry.yarnpkg.com/sdp-transform/-/sdp-transform-2.14.2.tgz#d2cee6a1f7abe44e6332ac6cbb94e8600f32d813" @@ -2339,7 +2391,7 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -tslib@2.6.2, tslib@^2.1.0: +tslib@2.6.2, tslib@^2.1.0, tslib@^2.4.1: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==