Compare commits
8 Commits
240f28935d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ec701a7925 | |||
| 1a3e6f688d | |||
| 62be599d33 | |||
| 0adbf6f4e2 | |||
| 65bc4a011a | |||
| 4d9bd5c98b | |||
| d494abf7ae | |||
| 61efa5611d |
@@ -24,5 +24,16 @@
|
||||
"exit-control-btn": "Exit",
|
||||
"popup-control-exit-title": "Are you sure you want to end the demo?",
|
||||
"popup-control-yes": "Finish",
|
||||
"popup-control-no": "Stay"
|
||||
"popup-control-no": "Stay",
|
||||
"popup-loading": "Please, wait",
|
||||
"sidebar-hide": "Hide menu",
|
||||
"language-button": "Language",
|
||||
"popup-additional": "Additional",
|
||||
"popup-userlist": "Spectrators",
|
||||
"user": "User",
|
||||
"popup-change-language": "Change Language"
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
"exit-control-btn": "Выйти",
|
||||
"popup-control-exit-title": "Вы уверены, что хотите закончить демонстрацию?",
|
||||
"popup-control-yes": "Закончить",
|
||||
"popup-control-no": "Остаться"
|
||||
|
||||
"popup-control-no": "Остаться",
|
||||
"popup-loading": "Пожалуйста, подождите",
|
||||
"sidebar-hide": "Скрыть меню",
|
||||
"language-button": "Язык",
|
||||
"popup-additional": "Дополнительно",
|
||||
"popup-userlist": "Участники демонстрации",
|
||||
"user": "Пользователь",
|
||||
"popup-change-language": "Выбор языка"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, height=device-height" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
|
||||
@@ -5,24 +5,6 @@
|
||||
}
|
||||
|
||||
|
||||
.content__container {
|
||||
height: 100vh;
|
||||
width: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-width: 0px 2px;
|
||||
border-style: solid;
|
||||
border-color: #23242a;
|
||||
}
|
||||
|
||||
.popup {
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 56px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.card-container {
|
||||
@@ -34,14 +16,19 @@
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 28px 0 40px 0;
|
||||
margin: 0;
|
||||
width: 496px;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 38px;
|
||||
font-weight: 300;
|
||||
font-size: 56px;
|
||||
line-height: 100%;
|
||||
/* identical to box height, or 38px */
|
||||
background: linear-gradient(180deg, #BC75FF 0%, #798FFF 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.demos_container {
|
||||
@@ -49,17 +36,12 @@
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
.card-title {
|
||||
margin: 22px 0 40px 0;
|
||||
}
|
||||
|
||||
|
||||
.card-container {
|
||||
gap: 24px;
|
||||
@@ -68,14 +50,16 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
.card-title-container {
|
||||
margin-top: 96px;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
gap: 20px;
|
||||
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 42px 0 40px 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
@@ -87,12 +71,15 @@
|
||||
}
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
.card-title {
|
||||
margin: 36px 0 32px 0;
|
||||
|
||||
.card-title-container {
|
||||
margin-bottom: 40px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 28px;
|
||||
font-size: 40px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
@@ -105,4 +92,4 @@
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,54 +5,110 @@ import { useEffect } from "react";
|
||||
import { Redirect, Route, Switch, useHistory } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useQuery from "hooks/useQuery";
|
||||
import cookies from "js-cookie";
|
||||
|
||||
import { Header } from "components/shared/Header/Header";
|
||||
import { Card } from "components/pages/Main/Card/Card";
|
||||
import { PopupComponent } from "components/pages/ConnectPage/PopupComponent/PopupComponent";
|
||||
import { PlayerComponent } from "components/pages/Stream/PlayerComponent/PlayerComponent";
|
||||
import { SessionStartComponent } from "components/pages/ConnectPage/SessionStartComponent/SessionStartComponent";
|
||||
import { PlayerComponent } from "components/pages/Stream/components/PlayerComponent/PlayerComponent";
|
||||
import { PlanComponent } from "components/pages/Plan/PlanComponent";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { fetchCards } from "store/reducers/ActionCreator";
|
||||
import { fetchCards, fetchPrivateCards } from "store/reducers/ActionCreator";
|
||||
import { cardSlice } from "store/reducers/cardSlice";
|
||||
import { sessionSlice } from "store/reducers/sessionSlice";
|
||||
import { languageSlice } from "store/reducers/languageSlice";
|
||||
import { createSession } from "store/reducers/ActionCreator";
|
||||
|
||||
import { ICards } from "models/ICards";
|
||||
import { closeStream, load as loadStream } from "utils/app";
|
||||
|
||||
const App: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const { handleCurrentCard } = cardSlice.actions;
|
||||
const { cards, currentCard, error } = useAppSelector((state) => state.cardReducer);
|
||||
const { isLoading } = useAppSelector((state) => state.sessionReducer)
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
console.log(error)
|
||||
const { handleChangeLanguage } = languageSlice.actions;
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
|
||||
const { cards, currentCard, error } = useAppSelector(
|
||||
(state) => state.cardReducer
|
||||
);
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
const { isLoading } = useAppSelector((state) => state.sessionReducer);
|
||||
const query = useQuery();
|
||||
|
||||
const langQuery = query.get("lang");
|
||||
|
||||
const langArray = ["en", "ru"];
|
||||
|
||||
const browserLanguage = window.navigator.language;
|
||||
|
||||
const handleBrowserLanguage = () => {
|
||||
return langArray.includes(browserLanguage);
|
||||
};
|
||||
|
||||
const handleCookiesLanguage = () => {
|
||||
const language = cookies.get("i18next");
|
||||
return language;
|
||||
};
|
||||
useEffect(() => {
|
||||
dispatch(fetchCards(cookies.get("i18next")));
|
||||
if (langArray.includes(langQuery as string)) {
|
||||
dispatch(handleChangeLanguage(langQuery as string));
|
||||
return;
|
||||
} else if (handleCookiesLanguage()) {
|
||||
const languageCookies = handleCookiesLanguage();
|
||||
dispatch(handleChangeLanguage(languageCookies as string));
|
||||
return;
|
||||
}
|
||||
let isSupported = handleBrowserLanguage();
|
||||
dispatch(handleChangeLanguage(isSupported ? browserLanguage : "en"));
|
||||
}, []);
|
||||
|
||||
const handleConnect = (title: string) => {
|
||||
dispatch(createSession(title))
|
||||
.unwrap()
|
||||
.then((res) => {
|
||||
history.push(`/stream/${res.session_id}`);
|
||||
})
|
||||
.catch((res) => {
|
||||
alert(res);
|
||||
history.push("/");
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchCards(cookies.get("i18next")));
|
||||
}, [currentLang])
|
||||
if (currentLang) {
|
||||
const key = new URLSearchParams(history.location.search).get("key");
|
||||
|
||||
if (key) {
|
||||
dispatch(fetchPrivateCards(key));
|
||||
} else {
|
||||
dispatch(fetchCards(currentLang));
|
||||
}
|
||||
}
|
||||
}, [currentLang]);
|
||||
|
||||
const handleCards = (card: ICards) => {
|
||||
dispatch(handleCurrentCard(card));
|
||||
history.push("/connect-page");
|
||||
};
|
||||
|
||||
const closeStream = () => {
|
||||
const handleDisconnect = () => {
|
||||
closeStream();
|
||||
history.push("/");
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
console.log(isLoading, 'LOADING')
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<Header></Header>
|
||||
<div className="main">
|
||||
<h2 className="card-title">{error ? error : t("demo-title")}</h2>
|
||||
<div className="card-title-container">
|
||||
<h2 className="card-title">{error ? error : t("demo-title")}</h2>
|
||||
</div>
|
||||
<div className="card-container">
|
||||
{cards.map((i: ICards) => (
|
||||
<Card onClick={() => handleCards(i)} key={i._id} item={i}></Card>
|
||||
@@ -63,11 +119,14 @@ const App: React.FC = () => {
|
||||
<Route path="/connect-page">
|
||||
{currentCard ? (
|
||||
<div className="background">
|
||||
{isLoading && (<Header></Header>
|
||||
)}
|
||||
<div className="popup-container">
|
||||
<div className="content__container">
|
||||
<PopupComponent></PopupComponent>
|
||||
<SessionStartComponent
|
||||
cleanErrors={cleanErrors}
|
||||
handleConnect={handleConnect}
|
||||
isLoading={isLoading}
|
||||
currentCard={currentCard}
|
||||
></SessionStartComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,7 +135,11 @@ const App: React.FC = () => {
|
||||
)}
|
||||
</Route>
|
||||
<Route exact path="/stream/:id">
|
||||
<PlayerComponent closeStream={closeStream}></PlayerComponent>
|
||||
<PlayerComponent
|
||||
handleDisconnect={handleDisconnect}
|
||||
loadStream={loadStream}
|
||||
cleanErrors={cleanErrors}
|
||||
></PlayerComponent>
|
||||
</Route>
|
||||
<Route path="/plan">
|
||||
<Header></Header>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
.content__container {
|
||||
height: 100vh;
|
||||
width: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-width: 0px 2px;
|
||||
border-style: solid;
|
||||
border-color: #23242a;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popup {
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 56px;
|
||||
box-sizing: border-box;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.card-title-container {
|
||||
margin-top: 136px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.popup-img-container {
|
||||
height: 94px;
|
||||
width: 100%;
|
||||
padding: 0 21px 16px 0;
|
||||
margin-bottom: 32px;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
.popup-logo {
|
||||
height: 78px;
|
||||
width: 267px;
|
||||
object-fit: contain;
|
||||
|
||||
}
|
||||
|
||||
.popup-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 1px;
|
||||
background-color: #23242A;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.popup-img-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.popup-connect {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,25 @@
|
||||
import "./PopupConnect.css";
|
||||
import "./ConnectComponent.css";
|
||||
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PopupConnect: React.FC<any> = ({ onConnect, logo, isLoading }) => {
|
||||
export const ConnectComponent: React.FC<any> = ({ logo, isLoading, title, handleConnect }) => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleConnect = () => {
|
||||
onConnect().then((res: any) => {
|
||||
if (!res.error) {
|
||||
history.push(`/stream/${res.payload.session_id}`);
|
||||
} else {
|
||||
alert(res.payload);
|
||||
history.push("/");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="popup">
|
||||
<div className="popup-img-container">
|
||||
<img className="popup-logo" src={logo} alt="лого" />
|
||||
</div>
|
||||
<div className="popup-button-container">
|
||||
<button onClick={handleConnect} className="button button-primary">
|
||||
<button disabled={isLoading} onClick={() => handleConnect(title)} className="button button-primary">
|
||||
{t("popup-main-btn-start")}
|
||||
</button>
|
||||
<div className="line"></div>
|
||||
<button onClick={() => history.goBack()} className="button button-type-small">
|
||||
<button onClick={() => history.goBack()} disabled={isLoading} className="button button-type-small">
|
||||
{t("popup-main-select")}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1,14 +1,10 @@
|
||||
import "./LoadingPopup.css";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import "./LoaderComponent.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
|
||||
export const LoaderComponent: React.FC<any> = ({ logo }) => {
|
||||
|
||||
|
||||
export const LoadingPopup: React.FC<any> = ({ logo }) => {
|
||||
|
||||
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -17,7 +13,7 @@ export const LoadingPopup: React.FC<any> = ({ logo }) => {
|
||||
<div className="popup-img-container">
|
||||
<img className="popup-logo" src={logo} alt="лого" />
|
||||
</div>
|
||||
<span className="loading-caption">Пожалуйста подождите</span>
|
||||
<span className="loading-caption">{t('popup-loading')}</span>
|
||||
</div>
|
||||
<span className="loader"></span>
|
||||
</div>
|
||||
@@ -1,9 +0,0 @@
|
||||
<svg width="106" height="106" viewBox="0 0 106 106" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M103.35 53C104.814 53 106.007 51.8126 105.934 50.3509C105.354 38.7527 100.977 27.6378 93.4384 18.7402C85.328 9.16712 74.0844 2.78179 61.7081 0.720288C49.3319 -1.34121 36.6256 1.05484 25.85 7.48211C15.0745 13.9094 6.92854 23.951 2.86158 35.8204C-1.20539 47.6898 -0.929606 60.6171 3.63985 72.3022C8.2093 83.9873 16.7761 93.6725 27.8159 99.6343C38.8558 105.596 51.6527 107.448 63.9298 104.861C75.3406 102.456 85.6145 96.361 93.1857 87.5559C94.1399 86.4462 93.9256 84.7764 92.7696 83.8789V83.8789C91.6136 82.9813 89.9541 83.1964 88.9938 84.3008C82.1966 92.1174 73.0197 97.5286 62.8368 99.6747C51.7874 102.003 40.2702 100.337 30.3343 94.9709C20.3985 89.6052 12.6884 80.8886 8.57587 70.372C4.46336 59.8554 4.21516 48.2208 7.87542 37.5384C11.5357 26.8559 18.867 17.8184 28.565 12.0339C38.263 6.24936 49.6987 4.09292 60.8373 5.94826C71.976 7.80361 82.0952 13.5504 89.3946 22.1662C96.1215 30.1063 100.051 40.0084 100.626 50.3511C100.708 51.8124 101.886 53 103.35 53V53Z" fill="url(#paint0_angular_2745_13429)"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_angular_2745_13429" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(53 53) rotate(45) scale(52.4673)">
|
||||
<stop offset="0.874517" stop-color="white"/>
|
||||
<stop offset="0.982613" stop-color="white" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,73 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
import { popupAnimation } from "utils/animationProps";
|
||||
|
||||
import { PopupConnect } from "components/pages/ConnectPage/PopupConnect/PopupConnect";
|
||||
import { LoadingPopup } from "components/pages/ConnectPage/LoadingPopup/LoadingPopup";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { createSession } from "store/reducers/ActionCreator";
|
||||
import { sessionSlice } from "store/reducers/sessionSlice";
|
||||
|
||||
export const PopupComponent: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { currentCard } = useAppSelector((state) => state.cardReducer);
|
||||
const [visible, setVisible] = useState({
|
||||
connectPopup: true,
|
||||
loadingPopup: false,
|
||||
});
|
||||
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
|
||||
const { isLoading, error } = useAppSelector((state) => state.sessionReducer);
|
||||
|
||||
const { connectPopup, loadingPopup } = visible;
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
setVisible({ connectPopup: false, loadingPopup: true });
|
||||
return;
|
||||
} else if (error) {
|
||||
setVisible({ connectPopup: false, loadingPopup: false });
|
||||
return;
|
||||
}
|
||||
}, [isLoading, error]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(cleanErrors());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
{connectPopup && (
|
||||
<motion.div
|
||||
key={1}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<PopupConnect
|
||||
isLoading={true}
|
||||
logo={currentCard.logo}
|
||||
onConnect={() => dispatch(createSession(currentCard.app_title))}
|
||||
></PopupConnect>
|
||||
</motion.div>
|
||||
)}
|
||||
{loadingPopup && (
|
||||
<motion.div
|
||||
key={2}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<LoadingPopup logo={currentCard.logo}></LoadingPopup>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
.popup-img-container {
|
||||
height: 94px;
|
||||
width: 100%;
|
||||
padding: 0 21px 16px 0;
|
||||
margin-bottom: 32px;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
.popup-logo {
|
||||
height: 78px;
|
||||
width: 267px;
|
||||
object-fit: contain;
|
||||
|
||||
}
|
||||
|
||||
.popup-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 1px;
|
||||
background-color: #23242A;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.popup-img-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.popup-connect {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useEffect } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
|
||||
import { popupAnimation } from "utils/animationProps";
|
||||
|
||||
import { ConnectComponent } from "components/pages/ConnectPage/ConnectComponent/ConnectComponent";
|
||||
import { LoaderComponent } from "components/pages/ConnectPage/LoaderComponent/LoaderComponent";
|
||||
|
||||
import { useAppDispatch } from "hooks/redux";
|
||||
|
||||
export const SessionStartComponent: React.FC<any> = ({isLoading, handleConnect, currentCard, cleanErrors}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(cleanErrors());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
{!isLoading && (
|
||||
<motion.div
|
||||
key={1}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<ConnectComponent
|
||||
title={currentCard.app_title}
|
||||
isLoading={isLoading}
|
||||
logo={currentCard.logo}
|
||||
handleConnect={handleConnect}
|
||||
></ConnectComponent>
|
||||
</motion.div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<motion.div
|
||||
key={2}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<LoaderComponent logo={currentCard.logo}></LoaderComponent>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
.card-image {
|
||||
border-radius: 4px;
|
||||
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 364px;
|
||||
min-height: 260px;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import "./Card.css";
|
||||
import iconButton from "./iconButton.svg";
|
||||
|
||||
import { useAppSelector } from "hooks/redux";
|
||||
|
||||
export const Card: React.FC<any> = ({ item, onClick }) => {
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
|
||||
console.log(currentLang);
|
||||
return (
|
||||
<div onClick={() => onClick()} className="card">
|
||||
<img className="card-image" src={item.preview} alt='building' />
|
||||
|
||||
|
Before Width: | Height: | Size: 602 KiB |
|
Before Width: | Height: | Size: 792 KiB |
|
Before Width: | Height: | Size: 666 KiB |
@@ -1,30 +0,0 @@
|
||||
import { ControlButton } from "components/pages/Stream/ControlButton/ControlButton";
|
||||
import { MicroButton } from "components/pages/Stream/MicroButton/MicroButton";
|
||||
import { ShareButton } from "components/pages/Stream/ShareButton/ShareButton";
|
||||
import { LanguageButton } from "components/pages/Stream/LanguageButton/LanguageButton";
|
||||
import { ExitButton } from "components/pages/Stream/ExitButton/ExitButton";
|
||||
|
||||
export const ControlPanel: React.FC<any> = ({
|
||||
handleOpenSharePopup,
|
||||
handleOpenExitPopup,
|
||||
handleMuteClick,
|
||||
handleControlClick,
|
||||
|
||||
isMuted,
|
||||
isControl,
|
||||
isSidebarWide
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<div className="toolbar-field-part">
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<ControlButton isActive={isControl} isSidebarWide={isSidebarWide} onClick={handleControlClick}></ControlButton>
|
||||
<MicroButton isSidebarWide={isSidebarWide} isActive={isMuted} onClick={handleMuteClick}></MicroButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<ShareButton isSidebarWide={isSidebarWide} onClick={handleOpenSharePopup}></ShareButton>
|
||||
<LanguageButton isSidebarWide={isSidebarWide} onClick={() => console.log("click!")}></LanguageButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<ExitButton isSidebarWide={isSidebarWide} onClick={handleOpenExitPopup}></ExitButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
import "./Sidebar.css";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { SidebarDesktop } from "../SidebarDesktop/SidebarDesktop";
|
||||
import { SidebarMobile } from "../SidebarMobile/SidebarMobile";
|
||||
|
||||
|
||||
export const Sidebar: React.FC<any> = ({ closeStream, exitPopup, isMobile, heightDevice }) => {
|
||||
const [isMuted, setMuted] = useState(true);
|
||||
const [isControl, setControl] = useState(false);
|
||||
const [height, setHeight] = useState(heightDevice);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
setHeight(heightDevice)
|
||||
}
|
||||
}, [heightDevice])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const handleMuteClick = () => {
|
||||
setMuted((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleControlClick = () => {
|
||||
setControl((prev) => !prev);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<SidebarMobile
|
||||
isMobile={isMobile}
|
||||
height={height}
|
||||
isMuted={isMuted}
|
||||
isControl={isControl}
|
||||
handleMuteClick={handleMuteClick}
|
||||
handleControlClick={handleControlClick}
|
||||
closeStream={closeStream}
|
||||
></SidebarMobile>
|
||||
) : (
|
||||
<SidebarDesktop
|
||||
isMobile={isMobile}
|
||||
isMuted={isMuted}
|
||||
isControl={isControl}
|
||||
handleMuteClick={handleMuteClick}
|
||||
handleControlClick={handleControlClick}
|
||||
closeStream={closeStream}
|
||||
exitPopup={exitPopup}
|
||||
></SidebarDesktop>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
import { FullscreenButton } from "../FullscreenButton/FullscreenButton";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import {
|
||||
sidebarVariants,
|
||||
popupAnimation,
|
||||
wideSidebarVariants,
|
||||
wideSidebarAdminVariants,
|
||||
} from "utils/animationProps";
|
||||
import { useState } from "react";
|
||||
import { WideSidebarButton } from "../WideSidebarButton/WideSidebarButton";
|
||||
import { UserButtonMobile } from "../UserButtonMobile/UserButtonMobile";
|
||||
import { ControlButton } from "../ControlButton/ControlButton";
|
||||
import { MicroButton } from "../MicroButton/MicroButton";
|
||||
import { AdditionalButton } from "../AdditionalButton/AdditionalButton";
|
||||
import { UserListMobile } from "../UserListMoblie/UserListMobile";
|
||||
|
||||
export const SidebarMobile: React.FC<any> = ({ height, isMobile }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [userList, setUserList] = useState(false);
|
||||
const [popupAdditional, setPopupAdditipnal] = useState(false);
|
||||
console.log(userList, 'popup')
|
||||
return (
|
||||
<> <motion.div
|
||||
initial={false}
|
||||
animate={open ? "open" : "closed"}
|
||||
variants={sidebarVariants}
|
||||
style={{ height: height }}
|
||||
className="toolbar-container"
|
||||
>
|
||||
<div className="toolbar-field">
|
||||
<div className="toolbar-field-part">
|
||||
<FullscreenButton isSidebarWide={false}> </FullscreenButton>
|
||||
<WideSidebarButton close={() => setOpen(false)}></WideSidebarButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<UserButtonMobile
|
||||
active={userList}
|
||||
onClick={() => setUserList(true)}
|
||||
></UserButtonMobile>
|
||||
</div>
|
||||
<div className="toolbar-field-part">
|
||||
<ControlButton></ControlButton>
|
||||
<MicroButton></MicroButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<AdditionalButton
|
||||
active={popupAdditional}
|
||||
onClick={() => setPopupAdditipnal(true)}
|
||||
></AdditionalButton>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{!open && (
|
||||
<motion.div
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
onClick={() => setOpen(true)}
|
||||
className="toolbar-open-button"
|
||||
>
|
||||
<span className="toolbar-open-button-icon"></span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
<AnimatePresence>
|
||||
{userList && (<motion.div key={1}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<UserListMobile closePopup={() => setUserList(false)} isMobile={isMobile}></UserListMobile>
|
||||
</motion.div>)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { User } from "components/pages/Stream/User/User";
|
||||
|
||||
export const UserList: React.FC<any> = ({ isSidebarWide, isAdmin, closeSidebar, isMobile }) => {
|
||||
return (
|
||||
<div className="toolbar-field-part">
|
||||
<User closeSidebar={closeSidebar} isAdmin={isAdmin} isSidebarWide={isSidebarWide}></User>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +0,0 @@
|
||||
import { User } from "../User/User"
|
||||
import close from 'images/icons/close.svg'
|
||||
|
||||
export const UserListMobile: React.FC<any> = ({ isMobile, closePopup }) => {
|
||||
return (<div className="popup-mobile">
|
||||
<div className="popup-mobile-container">
|
||||
<div style={{top:"-20px", right: "-20px"}} onClick={closePopup} className="icon-close-container">
|
||||
<img src={close}></img>
|
||||
</div>
|
||||
<h2 style={{ marginBottom: "20px" }}>Участники демонстрации</h2>
|
||||
<User isMobile={isMobile} closeSidebar={closePopup}
|
||||
isSidebarWide={true} /// this is for disable showhing button's caption
|
||||
isAdmin={false}
|
||||
></User>
|
||||
</div>
|
||||
|
||||
</div>)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ControlButton } from "components/pages/Stream/components/buttons/ControlButton/ControlButton";
|
||||
import { MicroButton } from "components/pages/Stream/components/buttons/MicroButton/MicroButton";
|
||||
import { ShareButton } from "components/pages/Stream/components/buttons/ShareButton/ShareButton";
|
||||
import { LanguageButton } from "components/pages/Stream/components/buttons/LanguageButton/LanguageButton";
|
||||
import { ExitButton } from "components/pages/Stream/components/buttons/ExitButton/ExitButton";
|
||||
|
||||
export const ControlPanel: React.FC<any> = ({
|
||||
handleOpenSharePopup,
|
||||
handleOpenExitPopup,
|
||||
handleMuteClick,
|
||||
handleControlClick,
|
||||
|
||||
isMuted,
|
||||
isControl,
|
||||
isSidebarWide
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<div className="toolbar-field-part">
|
||||
<ShareButton isSidebarWide={isSidebarWide} onClick={handleOpenSharePopup}></ShareButton>
|
||||
<LanguageButton isSidebarWide={isSidebarWide} onClick={() => console.log("click!")}></LanguageButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<ExitButton isSidebarWide={isSidebarWide} onClick={handleOpenExitPopup}></ExitButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
.popup-mobile-button-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.menu-item-button {
|
||||
justify-content: space-between;
|
||||
width: 50%;
|
||||
background: #23242A;
|
||||
border-radius: 4px;
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 140%;
|
||||
/* or 22px */
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
/* White */
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
color: #F2F2F2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.menu-item-container {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.menu-item-language-caption {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 130%;
|
||||
/* identical to box height, or 16px */
|
||||
align-items: center;
|
||||
text-align: right;
|
||||
/* Button_3 */
|
||||
color: #73788C;
|
||||
}
|
||||
|
||||
.menu-item-language {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-item-button-exit {
|
||||
position: absolute;
|
||||
width: 118px;
|
||||
right: 0%;
|
||||
bottom: 0%;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import './ContolPanelMobile.css'
|
||||
import close from 'images/icons/close.svg'
|
||||
import share from 'images/icons/Share.svg'
|
||||
import right from 'images/icons/ChevronRight.svg'
|
||||
import exit from 'images/icons/exit.svg'
|
||||
import language from 'images/icons/Language.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
||||
export const ControlPanelMobile: React.FC<any> = ({ closePopup, currentLang, setPopupExit, setPopupShare, setLanguagePopup }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
<div style={{ top: "-20px", right: "-20px" }} onClick={closePopup} className="icon-close-container">
|
||||
<img src={close}></img>
|
||||
</div>
|
||||
<h2 style={{ marginBottom: "20px" }}>{t('popup-additional')}</h2>
|
||||
<div className='popup-mobile-button-container'>
|
||||
<button onClick={setPopupShare} className='menu-item-button'>
|
||||
<div className='menu-item-container'>
|
||||
<img src={share} className='menu-item-icon' />
|
||||
<span className='menu-item-caption'>{t('share-contro-btn')}</span>
|
||||
</div>
|
||||
<img src={right} className='menu-item-arrow' />
|
||||
</button>
|
||||
<button onClick={setLanguagePopup} className='menu-item-button'>
|
||||
<div className='menu-item-container'>
|
||||
<img src={language} className='menu-item-icon' />
|
||||
<span className='menu-item-caption'>{t('language-button')}</span>
|
||||
</div>
|
||||
<div className="menu-item-language">
|
||||
<span className='menu-item-language-caption'>{currentLang === 'ru' ? 'Русский' : 'English'}</span>
|
||||
<img src={right} className='menu-item-arrow' />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={setPopupExit} className='menu-item-button-exit button button-teritary'>
|
||||
<img src={exit} className='' />
|
||||
<span>{t('exit-control-btn')}</span>
|
||||
</button>
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import './player.css'
|
||||
export const Player: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div id="playerUI" className="noselect">
|
||||
<div id="player"></div>
|
||||
</div>
|
||||
<div id="overlay">
|
||||
<div id="controls">
|
||||
<button className="tooltip" id="fullscreen-btn">
|
||||
<span className="tooltiptext">Fullscreen</span>
|
||||
</button>
|
||||
<button className="tooltip" id="settingsBtn">
|
||||
<span className="tooltiptext">Settings</span>
|
||||
</button>
|
||||
<button className="tooltip" id="statsBtn">
|
||||
<span className="tooltiptext">Information</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="unrealengine">
|
||||
</div>
|
||||
<div id="connection" className="tooltip">
|
||||
<span className="tooltiptext" id="qualityText">Not connected</span>
|
||||
</div>
|
||||
<div className="panel-wrap" id="settings-panel">
|
||||
<div className="panel">
|
||||
<div id="heading">Settings</div>
|
||||
<div id="close"></div>
|
||||
<div id="content">
|
||||
<div id="fillWindow" className="setting">
|
||||
<div className="settings-text">Enlarge display to fill window</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="enlarge-display-to-fill-window-tgl" className="tgl tgl-flat" checked />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="qualityControlOwnership" className="setting">
|
||||
<div className="settings-text">Is quality controller?</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="quality-control-ownership-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="matchViewportResolution" className="setting">
|
||||
<div className="settings-text">Match viewport resolution</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="match-viewport-res-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="offerToReceive" className="setting">
|
||||
<div className="settings-text">Offer To Receive</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="offer-receive-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="preferSFU" className="setting">
|
||||
<div className="settings-text">Prefer SFU</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="prefer-sfu-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="useMic" className="setting">
|
||||
<div className="settings-text">Use microphone</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="use-mic-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="forceMonoAudio" className="setting">
|
||||
<div className="settings-text">Force mono audio</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="force-mono-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="forceTURN" className="setting">
|
||||
<div className="settings-text">Force TURN</div>
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="force-turn-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div id="toggleControl" className="setting">
|
||||
<div className="settings-text" id="control-scheme-text">Control Scheme</div>
|
||||
<label className="btn-overlay">
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="control-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="toggleCursor" className="setting">
|
||||
<div className="settings-text" id="cursor-text">Hide Browser Cursor</div>
|
||||
<label className="btn-overlay">
|
||||
<label className="tgl-switch">
|
||||
<input type="checkbox" id="cursor-tgl" className="tgl tgl-flat" />
|
||||
<div className="tgl-slider"></div>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
<div id="showFPS" className="setting">
|
||||
<div className="settings-text">Show FPS</div>
|
||||
<label className="btn-overlay">
|
||||
<input type="button" id="show-fps-button" className="overlay-button btn-flat" value="Toggle" />
|
||||
</label>
|
||||
</div>
|
||||
<div id="keyframeRequest" className="setting">
|
||||
<div className="settings-text">Request KeyFrame</div>
|
||||
<label className="btn-overlay">
|
||||
<input type="button" id="request-keyframe-button" className="overlay-button btn-flat" value="Request" />
|
||||
</label>
|
||||
</div>
|
||||
<section id="encoderSettings">
|
||||
<div id="encoderSettingsHeader" className="settings-text">
|
||||
<div>Encoder Settings</div>
|
||||
</div>
|
||||
<div id="encoderParamsContainer" className="collapse">
|
||||
<div className="form-group">
|
||||
<label htmlFor="encoder-min-qp-text">Min QP</label>
|
||||
<input type="number" className="form-control" id="encoder-min-qp-text" value="0" min="0"
|
||||
max="51" />
|
||||
<label htmlFor="encoder-max-qp-text">Max QP</label>
|
||||
<input type="number" className="form-control" id="encoder-max-qp-text" value="51" min="0"
|
||||
max="51" />
|
||||
<br></br>
|
||||
<input id="encoder-params-submit" className="overlay-button btn-flat" type="button"
|
||||
value="Apply" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="webRTCSettings">
|
||||
<div id="webRTCSettingsHeader" className="settings-text">
|
||||
<div>WebRTC Settings</div>
|
||||
</div>
|
||||
<div id="webrtcParamsContainer" className="collapse">
|
||||
<div className="form-group">
|
||||
<label htmlFor="webrtc-fps-text">FPS</label>
|
||||
<input type="number" className="form-control" id="webrtc-fps-text" value="60" min="1"
|
||||
max="999" />
|
||||
<label htmlFor="webrtc-min-bitrate-text">Min bitrate (kbps)</label>
|
||||
<input type="number" className="form-control" id="webrtc-min-bitrate-text" value="0" min="0"
|
||||
max="100000" />
|
||||
<label htmlFor="webrtc-max-bitrate-text">Max bitrate (kbps)</label>
|
||||
<input type="number" className="form-control" id="webrtc-max-bitrate-text" value="0" min="0"
|
||||
max="100000" />
|
||||
<br></br>
|
||||
<input id="webrtc-params-submit" className="overlay-button btn-flat" type="button"
|
||||
value="Apply" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="streamSettings">
|
||||
<div id="streamSettingsHeader" className="settings-text">
|
||||
<div>Stream Settings</div>
|
||||
</div>
|
||||
<div id="streamSettingsContainer" className="collapse">
|
||||
<div className="form-group">
|
||||
<div className="settings-text">Player stream</div>
|
||||
<select className="form-control" id="stream-select"></select>
|
||||
<div className="settings-text">Player track</div>
|
||||
<select className="form-control" id="track-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<br></br>
|
||||
<section id="connectionSettings">
|
||||
<div id="connectionHeader" className="settings-text">
|
||||
<div>Stream Settings</div>
|
||||
</div>
|
||||
<div id="connectionContainer" className="collapse">
|
||||
<div className="setting">
|
||||
<div className="settings-text"></div>
|
||||
<label className="btn-overlay">
|
||||
<input type="button" id="restart-stream-button" className="overlay-button btn-flat" value="Restart stream" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel-wrap" id="stats-panel">
|
||||
<div className="panel">
|
||||
<div id="heading">Information</div>
|
||||
<div id="close"></div>
|
||||
<div id="content">
|
||||
<section id="statsPanel">
|
||||
<div className="setting settings-text">
|
||||
<div>Session Stats</div>
|
||||
</div>
|
||||
<div id="statsContainer" className="statsContainer">
|
||||
<div id="stats" className="stats"></div>
|
||||
</div>
|
||||
</section>
|
||||
<br></br>
|
||||
<section id="latencyTest">
|
||||
<div className="setting">
|
||||
<div className="settings-text">
|
||||
<div>Latency Report</div>
|
||||
</div>
|
||||
<label className="btn-overlay">
|
||||
<input type="button" id="test-latency-button" className="overlay-button btn-flat"
|
||||
value="Get Report" />
|
||||
</label>
|
||||
</div>
|
||||
<div id="latencyStatsContainer" className="statsContainer">
|
||||
<div className="stats">No report yet</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
#loader {
|
||||
width: 106px;
|
||||
height: 106px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background: conic-gradient(from 135deg at 50% 50%,
|
||||
rgba(255, 255, 255, 0) -6.26deg,
|
||||
#ffffff 314.83deg,
|
||||
rgba(255, 255, 255, 0) 353.74deg,
|
||||
#ffffff 674.83deg);
|
||||
box-sizing: border-box;
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#loader::after {
|
||||
content: "";
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
background: #151619;
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
font-family: "GilroyWebRegular";
|
||||
}
|
||||
|
||||
#playerUI {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
image-rendering: crisp-edges;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
video {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#videoPlayOverlay {
|
||||
position: absolute;
|
||||
font-size: 1.8em;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: var(--colour2)
|
||||
}
|
||||
|
||||
/* State for element to be clickable */
|
||||
.clickableState {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* State for element to show text, this is for informational use*/
|
||||
.textDisplayState {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* State to hide overlay, WebRTC communication is in progress and or is playing */
|
||||
.hiddenState {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
#playButton {
|
||||
display: block;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
z-index: 30;
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 112px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#container {
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
/* Background */
|
||||
background: transparent;
|
||||
/* Button_1 */
|
||||
border-width: 0px 2px;
|
||||
border-style: solid;
|
||||
border-color: #23242A;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding: 40px 56px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
#container {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#playButton:hover {
|
||||
background: linear-gradient(180deg, #BC75FF 0%, #798FFF 100%);
|
||||
backdrop-filter: blur(10px)
|
||||
}
|
||||
|
||||
#title {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 38px;
|
||||
line-height: 100%;
|
||||
/* or 38px */
|
||||
/* White */
|
||||
color: #F2F2F2;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
#caption {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 140%;
|
||||
/* or 22px */
|
||||
text-align: center;
|
||||
/* Inactive */
|
||||
color: #C5C7CE;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
|
||||
}
|
||||
|
||||
#link {
|
||||
font-family: 'Inter';
|
||||
cursor: pointer;
|
||||
background: #1C1D21;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
box-sizing: border-box;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 130%;
|
||||
/* identical to box height, or 16px */
|
||||
/* Inactive */
|
||||
color: #C5C7CE;
|
||||
}
|
||||
|
||||
#link:hover {
|
||||
background: #23242A;
|
||||
}
|
||||
|
||||
|
||||
#freezeFrameOverlay {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.freezeframeBackground {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 20;
|
||||
position: absolute;
|
||||
color: var(--colour2);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#overlay button {
|
||||
background-color: var(--colour7);
|
||||
border: 1px solid var(--colour7);
|
||||
color: var(--colour2);
|
||||
position: relative;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#fullscreen-btn {
|
||||
padding: 0.6rem !important;
|
||||
}
|
||||
|
||||
#overlay button:hover {
|
||||
background-color: var(--colour3);
|
||||
border: 3px solid var(--colour3);
|
||||
transition: 0.25s ease;
|
||||
padding-left: 0.55rem;
|
||||
padding-top: 0.55rem;
|
||||
}
|
||||
|
||||
#overlay button:active {
|
||||
border: 3px solid var(--colour3);
|
||||
background-color: var(--colour7);
|
||||
padding-left: 0.55rem;
|
||||
padding-top: 0.55rem;
|
||||
}
|
||||
|
||||
#overlay img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: auto;
|
||||
color: var(--colour2);
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
padding: 0px 10px;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.75px;
|
||||
/* Position the tooltip */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateY(25%);
|
||||
left: 125%;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
background-color: var(--colour7);
|
||||
}
|
||||
|
||||
#connection .tooltiptext {
|
||||
top: 125%;
|
||||
transform: translateX(-25%);
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
#settings-panel .tooltiptext {
|
||||
display: block;
|
||||
top: 125%;
|
||||
transform: translateX(-50%);
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
padding: 5px 10px;
|
||||
border: 3px solid var(--colour5);
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: absolute;
|
||||
top: 2%;
|
||||
left: 1%;
|
||||
font-family: 'Michroma', sans-serif;
|
||||
pointer-events: all;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#controls>* {
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
height: 2rem;
|
||||
line-height: 1.75rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
#controls #additionalinfo {
|
||||
text-align: center;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
|
||||
#unrealengine {
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
right: 10%;
|
||||
font-family: 'Michroma', sans-serif;
|
||||
pointer-events: all;
|
||||
visibility: hidden;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
#unrealengine p {
|
||||
visibility: hidden;
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
#connection {
|
||||
position: absolute;
|
||||
bottom: 5%;
|
||||
left: 10%;
|
||||
font-family: 'Michroma', sans-serif;
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
/* iOS Safari */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-khtml-user-select: none;
|
||||
/* Konqueror HTML */
|
||||
-moz-user-select: none;
|
||||
/* Old versions of Firefox */
|
||||
-ms-user-select: none;
|
||||
/* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
/* Non-prefixed version, currently
|
||||
supported by Chrome, Edge, Opera and Firefox */
|
||||
}
|
||||
|
||||
.panel-wrap {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
min-width: 20vw;
|
||||
transform: translateX(100%);
|
||||
transition: .3s ease-out;
|
||||
pointer-events: all;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: rgba(30, 29, 34, 0.5)
|
||||
}
|
||||
|
||||
.panel-wrap-visible {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
.panel {
|
||||
color: #eee;
|
||||
overflow-y: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#heading {
|
||||
display: inline-block;
|
||||
font-size: 2em;
|
||||
margin-block-start: 0.67em;
|
||||
margin-block-end: 0.67em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
position: relative;
|
||||
padding: 0 0 0 2rem;
|
||||
}
|
||||
|
||||
#close {
|
||||
margin: 0.5rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
font-size: 2em;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#close:after {
|
||||
padding-left: 0.5rem;
|
||||
display: inline-block;
|
||||
content: "\00d7";
|
||||
/* This will render the 'X' */
|
||||
}
|
||||
|
||||
#close:hover {
|
||||
color: var(--colour3);
|
||||
transition: ease 0.3s;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.setting {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.settings-text {
|
||||
margin-right: 2rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/*** Toggle Switch styles ***/
|
||||
.tgl-switch {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tgl-switch .tgl {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tgl,
|
||||
.tgl:after,
|
||||
.tgl:before,
|
||||
.tgl *,
|
||||
.tgl *:after,
|
||||
.tgl *:before,
|
||||
.tgl+.tgl-slider {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tgl::-moz-selection,
|
||||
.tgl:after::-moz-selection,
|
||||
.tgl:before::-moz-selection,
|
||||
.tgl *::-moz-selection,
|
||||
.tgl *:after::-moz-selection,
|
||||
.tgl *:before::-moz-selection,
|
||||
.tgl+.tgl-slider::-moz-selection {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tgl::selection,
|
||||
.tgl:after::selection,
|
||||
.tgl:before::selection,
|
||||
.tgl *::selection,
|
||||
.tgl *:after::selection,
|
||||
.tgl *:before::selection,
|
||||
.tgl+.tgl-slider::selection {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.tgl+.tgl-slider {
|
||||
outline: 0;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 18px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tgl+.tgl-slider:after,
|
||||
.tgl+.tgl-slider:before {
|
||||
position: relative;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tgl+.tgl-slider:after {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tgl+.tgl-slider:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tgl-flat+.tgl-slider {
|
||||
padding: 2px;
|
||||
-webkit-transition: all .2s ease;
|
||||
transition: all .2s ease;
|
||||
background: var(--colour6);
|
||||
border: 3px solid var(--colour7);
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
.tgl-flat+.tgl-slider:after {
|
||||
-webkit-transition: all .2s ease;
|
||||
transition: all .2s ease;
|
||||
background: var(--colour7);
|
||||
content: "";
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
.tgl-flat:checked+.tgl-slider {
|
||||
border: 3px solid var(--colour3);
|
||||
}
|
||||
|
||||
.tgl-flat:checked+.tgl-slider:after {
|
||||
left: 50%;
|
||||
background: var(--colour3);
|
||||
}
|
||||
|
||||
.subtitle-text {
|
||||
margin: 0 0 0 1rem;
|
||||
color: var(--colour5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
padding-top: 4px;
|
||||
display: grid;
|
||||
grid-template-columns: 50% 50%;
|
||||
row-gap: 4px;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
color: var(--colour2);
|
||||
vertical-align: middle;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#stats {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
#LatencyStats {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
#hiddenInput {
|
||||
position: absolute;
|
||||
left: -10%;
|
||||
/* Although invisible, push off-screen to prevent user interaction. */
|
||||
width: 0px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#editTextButton {
|
||||
position: absolute;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
margin-right: 2rem;
|
||||
min-width: 75%;
|
||||
}
|
||||
|
||||
|
||||
.warning {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
transform: scale(var(--ggs, 1));
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid;
|
||||
border-radius: 40px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.warning::after,
|
||||
.warning::before {
|
||||
content: "";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
border-radius: 3px;
|
||||
width: 2px;
|
||||
background: currentColor;
|
||||
left: 7px
|
||||
}
|
||||
|
||||
.warning::after {
|
||||
top: 2px;
|
||||
height: 8px
|
||||
}
|
||||
|
||||
.warning::before {
|
||||
height: 2px;
|
||||
bottom: 2px
|
||||
}
|
||||
|
||||
/* Flat buttons */
|
||||
input[type="button"] {
|
||||
background-color: transparent;
|
||||
color: var(--colour2);
|
||||
font-family: 'Montserrat';
|
||||
border: 3px solid var(--colour3);
|
||||
border-radius: 1rem;
|
||||
font-size: 0.75rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
input[type="button"]:hover {
|
||||
background-color: var(--colour3);
|
||||
transition: ease 0.3s;
|
||||
}
|
||||
|
||||
input[type="button"]:active {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#encoder-params-submit,
|
||||
#webrtc-params-submit {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
select,
|
||||
input[type="number"] {
|
||||
background-color: var(--colour7);
|
||||
color: var(--colour2);
|
||||
border: 1px solid var(--colour6);
|
||||
padding: 0.25rem;
|
||||
font-family: 'Montserrat';
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-inner-spin-button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
input[type="number"]:disabled {
|
||||
padding-right: 0.5rem;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type=number]:disabled::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
#settingsBtn,
|
||||
#statsBtn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#streamingVideo {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
embed {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
g {
|
||||
fill: var(--colour2);
|
||||
}
|
||||
|
||||
object {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#connectionStrength {
|
||||
fill: var(--colour7);
|
||||
}
|
||||
|
||||
#minimize {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#afkOverlay {
|
||||
z-index: 999;
|
||||
background-color: rgba(30, 29, 34, 0.5);
|
||||
display: inline-block;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
line-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#afkOverlay center {
|
||||
display: inline-block;
|
||||
line-height: 1.5;
|
||||
height: 100vh;
|
||||
}
|
||||
@@ -1,53 +1,54 @@
|
||||
import "./PlayerStyles.css";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import useWindowDimensions from "hooks/useWindowDimensions";
|
||||
|
||||
import useMobile from "hooks/useMobile";
|
||||
import { Sidebar } from "components/pages/Stream/Sidebar/Sidebar";
|
||||
|
||||
import { connectSession } from "store/reducers/ActionCreator";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { sessionSlice } from "store/reducers/sessionSlice";
|
||||
import useWindowDimensions from "hooks/useWindowDimensions";
|
||||
import useMobile from "hooks/useMobile";
|
||||
|
||||
import { Sidebar } from "components/pages/Stream/components/Sidebar/Sidebar";
|
||||
import { Player } from "components/pages/Stream/components/Player/Player";
|
||||
|
||||
type link = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const PlayerComponent: React.FC<any> = ({ closeStream }) => {
|
||||
const frameRef = useRef<HTMLIFrameElement>()
|
||||
|
||||
|
||||
export const PlayerComponent: React.FC<any> = ({ cleanErrors, handleDisconnect, loadStream }) => {
|
||||
const { isMobile } = useMobile();
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const width = windowDimensions.width;
|
||||
const height = windowDimensions.height;
|
||||
const [popup, setPopup] = useState(false);
|
||||
const history = useHistory()
|
||||
console.log(popup);
|
||||
const { playerCount } = useAppSelector((state) => state.sessionReducer)
|
||||
|
||||
|
||||
|
||||
|
||||
const { id } = useParams<link>();
|
||||
const [click, setClick] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
const history = useHistory()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(connectSession(id)).then((res: any) => {
|
||||
if (res.error) {
|
||||
alert(res.payload);
|
||||
}
|
||||
});
|
||||
dispatch(connectSession(id)).unwrap().then(() => {
|
||||
loadStream()
|
||||
}).catch((res) => {
|
||||
alert(res);
|
||||
history.push('/')
|
||||
})
|
||||
return () => {
|
||||
dispatch(cleanErrors());
|
||||
handleDisconnect()
|
||||
window.removeEventListener("change ", (event: any) => {
|
||||
setPopup(false);
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { url } = useAppSelector((state) => state.sessionReducer);
|
||||
|
||||
const exitPopup = () => {
|
||||
setClick((prev) => !prev);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
@@ -60,7 +61,6 @@ export const PlayerComponent: React.FC<any> = ({ closeStream }) => {
|
||||
}, [width, height, isMobile]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup && (
|
||||
@@ -68,27 +68,14 @@ export const PlayerComponent: React.FC<any> = ({ closeStream }) => {
|
||||
<h2>Переверните устройство</h2>
|
||||
</div>
|
||||
)}
|
||||
<iframe
|
||||
ref={frameRef}
|
||||
onLoad={(e: any) => e.target.focus()}
|
||||
id="player"
|
||||
onBlur={(e) => e.target.focus()} /// element loosing focus and keyboard input doesn't work
|
||||
src={url}
|
||||
className={"player playerOn"}
|
||||
security={""}
|
||||
allowFullScreen={true}
|
||||
></iframe>
|
||||
|
||||
<Player></Player>
|
||||
<Sidebar
|
||||
handleDisconnect={handleDisconnect}
|
||||
players={playerCount}
|
||||
heightDevice={height}
|
||||
isMobile={isMobile}
|
||||
exitPopup={click}
|
||||
closeStream={closeStream}
|
||||
></Sidebar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
function getElementById(arg0: string) {
|
||||
throw new Error("Function not implemented.");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
user-select: none;
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
@@ -82,7 +81,7 @@
|
||||
width: 32px;
|
||||
height: 128px;
|
||||
border: none;
|
||||
background: url("openToolbarBackIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/openToolbarBackIcon.svg") 50% 50% no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
/* background-color: #2F80ED; */
|
||||
@@ -93,11 +92,11 @@
|
||||
.toolbar-open-button-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("pointerIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/pointerIcon.svg") 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
.toolbar-container.opened .toolbar-open-button-icon {
|
||||
background: url("closeToolbarIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/closeToolbarIcon.svg") 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
.toolbar-button-area-container {
|
||||
@@ -268,7 +267,7 @@
|
||||
width: 18px;
|
||||
height: 26px;
|
||||
background-color: #eb5757;
|
||||
background: url("icons/newCaptTriangleIcon.svg");
|
||||
background: url("../../../../../images/icons/newCaptTriangleIcon.svg");
|
||||
}
|
||||
|
||||
.user-control-popup {
|
||||
@@ -289,12 +288,12 @@
|
||||
}
|
||||
|
||||
.toolbar-button.fullscreen.on {
|
||||
background: url("./icons/openFullscreenIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/openFullscreenIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.fullscreen {
|
||||
background: url("./icons/closeFullscreenIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/fullscreen.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
@@ -313,57 +312,57 @@
|
||||
}
|
||||
|
||||
.toolbar-button.users {
|
||||
background: url("./icons/usersIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/usersIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.micro {
|
||||
background: url("./icons/microOffIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/microOffIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.micro.on {
|
||||
background: url("./icons/microOnIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/microOnIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.control {
|
||||
background: url("./icons/constrolOffIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/constrolOffIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.control.on {
|
||||
background: url("./icons/constrolOnIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/constrolOnIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.other {
|
||||
background: url("./icons/othertIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/othertIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.sound {
|
||||
background: url("./icons/soundOffIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/soundOffIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.sound.on {
|
||||
background: url("./icons/soundOnIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/soundOnIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.exit {
|
||||
background: url("./icons/exitIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/exitIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #eb5757;
|
||||
}
|
||||
|
||||
.toolbar-button.copy {
|
||||
background: url("./icons/copyIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/copyIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.share {
|
||||
background: url("./icons/shareIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/shareIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
@@ -372,12 +371,12 @@
|
||||
}
|
||||
|
||||
.toolbar-button.language.ru {
|
||||
background: url("./icons/ruIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/ruIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
.toolbar-button.language.en {
|
||||
background: url("./icons/enIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/enIcon.svg") 50% 50% no-repeat;
|
||||
background-color: #4f4f4f;
|
||||
}
|
||||
|
||||
@@ -432,14 +431,14 @@
|
||||
.toolbar-button.large.control .toolbar-button-large-view-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("icons/constrolOffIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/constrolOffIcon.svg") 50% 50% no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.toolbar-button.large.control.on .toolbar-button-large-view-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("icons/constrolOnIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/constrolOnIcon.svg") 50% 50% no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
@@ -472,7 +471,7 @@
|
||||
.toolbar-button.yes .toolbar-button-medium-view-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("icons/constrolOnIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/constrolOnIcon.svg") 50% 50% no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
@@ -488,7 +487,7 @@
|
||||
.toolbar-button.no .toolbar-button-medium-view-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("icons/constrolOffIcon.svg") 50% 50% no-repeat;
|
||||
background: url("../../../../../images/icons/constrolOffIcon.svg") 50% 50% no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import "./Sidebar.css";
|
||||
import React, { useState } from "react";
|
||||
|
||||
|
||||
import { SidebarDesktop } from "../SidebarDesktop/SidebarDesktop";
|
||||
import { SidebarMobile } from "../SidebarMobile/SidebarMobile";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { languageSlice } from "store/reducers/languageSlice";
|
||||
|
||||
export const Sidebar: React.FC<any> = ({ exitPopup, isMobile, heightDevice, players, handleDisconnect }) => {
|
||||
const [isMuted, setMuted] = useState(true);
|
||||
const [isControl, setControl] = useState(false);
|
||||
|
||||
const { playerCount } = useAppSelector((state) => state.sessionReducer);
|
||||
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer)
|
||||
const { handleChangeLanguage } = languageSlice.actions;
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
|
||||
const handleCloseStream = () => {
|
||||
handleDisconnect()
|
||||
}
|
||||
|
||||
const handleLanguage = (value: string) => {
|
||||
dispatch(handleChangeLanguage(value))
|
||||
}
|
||||
|
||||
const handleMuteClick = () => {
|
||||
setMuted((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleControlClick = () => {
|
||||
setControl((prev) => !prev);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<SidebarMobile
|
||||
handleLanguage={handleLanguage}
|
||||
currentLang={currentLang}
|
||||
userArr={playerCount}
|
||||
isMobile={isMobile}
|
||||
height={heightDevice}
|
||||
isMuted={isMuted}
|
||||
isControl={isControl}
|
||||
handleMuteClick={handleMuteClick}
|
||||
handleControlClick={handleControlClick}
|
||||
closeStream={handleCloseStream}
|
||||
handleOpenExitPopup={handleCloseStream}
|
||||
></SidebarMobile>
|
||||
) : (
|
||||
<SidebarDesktop
|
||||
userArr={playerCount}
|
||||
height={heightDevice}
|
||||
isMobile={isMobile}
|
||||
isMuted={isMuted}
|
||||
isControl={isControl}
|
||||
handleMuteClick={handleMuteClick}
|
||||
handleControlClick={handleControlClick}
|
||||
closeStream={handleCloseStream}
|
||||
exitPopup={exitPopup}
|
||||
></SidebarDesktop>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { UserList } from "components/pages/Stream/UserList/UserList";
|
||||
import { FullscreenButton } from "components/pages/Stream/FullscreenButton/FullscreenButton";
|
||||
import { PopupShare } from "components/pages/Stream/PopupShare/PopupShare";
|
||||
import { PopupExit } from "components/pages/Stream/PopupExit/PopupExit";
|
||||
import { UserList } from "components/pages/Stream/components/UserList/UserList";
|
||||
import { FullscreenButton } from "components/pages/Stream/components/buttons/FullscreenButton/FullscreenButton";
|
||||
import { PopupShare } from "components/pages/Stream/components/popups/PopupShare/PopupShare";
|
||||
import { PopupExit } from "components/pages/Stream/components/popups/PopupExit/PopupExit";
|
||||
import { ControlPanel } from "../ControlPanel/ControlPanel";
|
||||
import { WideSidebarButton } from "components/pages/Stream/WideSidebarButton/WideSidebarButton";
|
||||
import { WideSidebarButton } from "components/pages/Stream/components/buttons/WideSidebarButton/WideSidebarButton";
|
||||
|
||||
import {
|
||||
sidebarVariants,
|
||||
@@ -20,9 +21,11 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
exitPopup,
|
||||
isMuted,
|
||||
isControl,
|
||||
height,
|
||||
handleMuteClick,
|
||||
handleControlClick,
|
||||
isMobile
|
||||
isMobile,
|
||||
userArr
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [popup, setPopup] = useState({
|
||||
@@ -84,11 +87,14 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
onHoverEnd={() => closeSideBar()}
|
||||
onHoverStart={() => setWideSidebar(true)}
|
||||
onHoverEnd={() => setWideSidebar(false)}
|
||||
initial={false}
|
||||
animate={open ? "open" : "closed"}
|
||||
variants={wideSidebar ? setAnimation() : sidebarVariants}
|
||||
className="toolbar-container"
|
||||
style={{ height: height }}
|
||||
|
||||
>
|
||||
<div
|
||||
style={wideSidebar ? { overflow: "hidden" } : { overflow: "visible" }}
|
||||
@@ -96,8 +102,8 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
>
|
||||
<div className="toolbar-field-part">
|
||||
<FullscreenButton isSidebarWide={wideSidebar}> </FullscreenButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<UserList
|
||||
userArr={userArr}
|
||||
isMobile={isMobile}
|
||||
closeSidebar={closeSideBar}
|
||||
isSidebarWide={wideSidebar} /// this is for disable showhing button's caption
|
||||
@@ -105,7 +111,6 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
></UserList>
|
||||
</div>
|
||||
<motion.div
|
||||
onHoverStart={() => setWideSidebar(true)}
|
||||
className="toolbar-field-part"
|
||||
>
|
||||
<WideSidebarButton
|
||||
@@ -160,7 +165,7 @@ export const SidebarDesktop: React.FC<any> = ({
|
||||
exit={"hidden"}
|
||||
>
|
||||
<PopupExit
|
||||
onExit={closeStream}
|
||||
closeStream={closeStream}
|
||||
setClose={handleClosePopups}
|
||||
></PopupExit>
|
||||
</motion.div>
|
||||
@@ -0,0 +1,105 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import {
|
||||
sidebarVariants,
|
||||
popupAnimation,
|
||||
} from "utils/animationProps";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { FullscreenButton } from "components/pages/Stream/components/buttons/FullscreenButton/FullscreenButton";
|
||||
import { WideSidebarButton } from "components/pages/Stream/components/buttons/WideSidebarButton/WideSidebarButton";
|
||||
import { UserButtonMobile } from "components/pages/Stream/components/buttons/UserButtonMobile/UserButtonMobile";
|
||||
import { ControlButton } from "components/pages/Stream/components/buttons/ControlButton/ControlButton";
|
||||
import { MicroButton } from "components/pages/Stream/components/buttons/MicroButton/MicroButton";
|
||||
import { AdditionalButton } from "components/pages/Stream/components/buttons/AdditionalButton/AdditionalButton";
|
||||
import { UserListMobilePopup } from "components/pages/Stream/components/popups/UserListMobliePopup/UserListMobilePopup";
|
||||
import { ExitButton } from "components/pages/Stream/components/buttons/ExitButton/ExitButton";
|
||||
import { PopupAdditional } from 'components/pages/Stream/components/popups/PopupAdditional/PopupAdditional'
|
||||
|
||||
export const SidebarMobile: React.FC<any> = ({ height, isMobile, handleLanguage, userArr, currentLang, closeStream }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [userListPopup, setUserListPopup] = useState(false);
|
||||
const [popupAdditional, setPopupAdditional] = useState(false); //should be insted of exit button, but popup doesn't ready yet
|
||||
|
||||
const handleOpenPopupAdditional = () => {
|
||||
if (userListPopup) {
|
||||
setUserListPopup(false)
|
||||
}
|
||||
setPopupAdditional(true)
|
||||
}
|
||||
|
||||
const handleOpenPopupUserList = () => {
|
||||
if (popupAdditional) {
|
||||
setPopupAdditional(false)
|
||||
}
|
||||
setUserListPopup(true)
|
||||
}
|
||||
|
||||
const handleClosePopups = () => {
|
||||
setPopupAdditional(false)
|
||||
setUserListPopup(false)
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => () => handleClosePopups(), []);
|
||||
|
||||
|
||||
return (
|
||||
<> <motion.div
|
||||
initial={false}
|
||||
animate={open ? "open" : "closed"}
|
||||
variants={sidebarVariants}
|
||||
style={{ height: height }}
|
||||
className="toolbar-container"
|
||||
>
|
||||
<div className="toolbar-field">
|
||||
<div className="toolbar-field-part">
|
||||
<FullscreenButton isSidebarWide={false}> </FullscreenButton>
|
||||
<WideSidebarButton close={() => setOpen(false)}></WideSidebarButton>
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<UserButtonMobile
|
||||
active={userListPopup}
|
||||
onClick={handleOpenPopupUserList}
|
||||
></UserButtonMobile>
|
||||
</div>
|
||||
<div className="toolbar-field-part">
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<AdditionalButton active={popupAdditional} isSidebarWide={false} onClick={handleOpenPopupAdditional}></AdditionalButton>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{!open && (
|
||||
<motion.div
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
onClick={() => setOpen(true)}
|
||||
className="toolbar-open-button"
|
||||
>
|
||||
<span className="toolbar-open-button-icon"></span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
<AnimatePresence mode="wait">
|
||||
{userListPopup && (<motion.div key={1}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<UserListMobilePopup height={height} userArr={userArr} closePopup={() => setUserListPopup(false)} isMobile={isMobile}></UserListMobilePopup>
|
||||
</motion.div>)}
|
||||
{popupAdditional && (<motion.div key={2} variants={popupAnimation} initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<PopupAdditional height={height} handleLanguage={handleLanguage} closeStream={closeStream} active={popupAdditional} currentLang={currentLang} closePopup={() => setPopupAdditional(false)}></PopupAdditional>
|
||||
</motion.div>)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
transition,
|
||||
iconAnimation,
|
||||
} from "utils/animationProps";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const User: React.FC<any> = ({
|
||||
isAdmin,
|
||||
@@ -28,6 +29,7 @@ export const User: React.FC<any> = ({
|
||||
const [expand, setExpand] = useState(false);
|
||||
const [mute, setMute] = useState(true);
|
||||
const [isControl, setControl] = useState(true);
|
||||
const {t} = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(false);
|
||||
@@ -65,7 +67,7 @@ export const User: React.FC<any> = ({
|
||||
animate={isSidebarWide ? "show" : "hidden"}
|
||||
className="user-name"
|
||||
>
|
||||
Пользователь
|
||||
{t('user')}
|
||||
</motion.span>
|
||||
</div>
|
||||
{isSidebarWide && (
|
||||
@@ -0,0 +1,12 @@
|
||||
import { User } from "components/pages/Stream/components/User/User";
|
||||
|
||||
export const UserList: React.FC<any> = ({ isSidebarWide, isAdmin, closeSidebar, isMobile, userArr }) => {
|
||||
console.log(userArr)
|
||||
return (
|
||||
<div className="toolbar-field-part">
|
||||
{userArr.map((i: any) => (<User closeSidebar={closeSidebar} isAdmin={isAdmin} isSidebarWide={isSidebarWide}></User>
|
||||
))}
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -20,7 +20,6 @@ export const ShareButton: React.FC<any> = ({ onClick, isSidebarWide }) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
onClick={handleClick}
|
||||
className="toolbar-button-area"
|
||||
>
|
||||
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
@@ -10,8 +10,8 @@ export const WideSidebarButton: React.FC<any> = ({ close, isSidebarWide }) => {
|
||||
const [active, setActive] = useState(false);
|
||||
const [button, setButton] = useState({
|
||||
icon: wideButton,
|
||||
inactive: "Скрыть меню",
|
||||
active: "Скрыть меню",
|
||||
inactive: "sidebar-hide",
|
||||
active: "sidebar-hide",
|
||||
type: "fullscreen",
|
||||
noHover: true,
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import '../popup.css'
|
||||
import { ControlPanelMobile } from "components/pages/Stream/components/ControlPanelMobile/ControlPanelMobile"
|
||||
import { PopupInviteMobile } from "../PopupInviteMobile/PopupInviteMobile"
|
||||
import { PopupExitMobile } from '../PopupExitMobile/PopupExitMobile'
|
||||
import { PopupLanguageMobile } from '../PopupLanguageMobile/PopupLanguageMobile'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export const PopupAdditional: React.FC<any> = ({ closePopup, currentLang, active, closeStream, handleLanguage, height }) => {
|
||||
const [popup, setPopup] = useState(true)
|
||||
const [popupShare, setPopupShare] = useState(false)
|
||||
const [popupLanguage, setPopupLanguage] = useState(false)
|
||||
const [popupExit, setPopupExit] = useState(false)
|
||||
|
||||
const handleCloseComponent = () => {
|
||||
setPopup(true)
|
||||
setPopupExit(false)
|
||||
setPopupShare(false)
|
||||
setPopupLanguage(false)
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
handleCloseComponent()
|
||||
}
|
||||
}, [active])
|
||||
|
||||
useEffect(() => () => handleCloseComponent(), []);
|
||||
|
||||
|
||||
const handleMainPopupOff = () => {
|
||||
setPopup(false)
|
||||
}
|
||||
|
||||
const handlePopupShare = () => {
|
||||
handleMainPopupOff()
|
||||
setPopupShare(true)
|
||||
}
|
||||
|
||||
const handlePopupExit = () => {
|
||||
handleMainPopupOff()
|
||||
setPopupExit(true)
|
||||
}
|
||||
|
||||
|
||||
const handleLanguagePopup = () => {
|
||||
handleMainPopupOff()
|
||||
setPopupLanguage(true)
|
||||
}
|
||||
|
||||
console.log(height)
|
||||
|
||||
return (<div style={{height: height}} className="popup-mobile">
|
||||
<div className="popup-mobile-container">
|
||||
{popup && (<ControlPanelMobile closePopup={closePopup} currentLang={currentLang} handleReturn={() => setPopup(true)} setPopupExit={handlePopupExit} setPopupShare={handlePopupShare} setPopupInvite={handlePopupShare} setLanguagePopup={handleLanguagePopup}></ControlPanelMobile>
|
||||
)}
|
||||
{popupShare && (<PopupInviteMobile handleReturn={() => setPopup(true)} setPopup={() => setPopupShare(false)}></PopupInviteMobile>
|
||||
)}
|
||||
{popupLanguage && (<PopupLanguageMobile handleLanguage={handleLanguage} currentLang={currentLang} handleReturn={() => setPopup(true)} setPopup={() => setPopupLanguage(false)}></PopupLanguageMobile>
|
||||
)}
|
||||
{popupExit && (<PopupExitMobile closeStream={closeStream} handleReturn={() => setPopup(true)} setPopup={() => setPopupExit(false)}></PopupExitMobile>
|
||||
)}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import "./PopupExit.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { TSidebarPopup } from "utils/types";
|
||||
|
||||
export const PopupExit: React.FC<TSidebarPopup> = ({ setClose, onExit }) => {
|
||||
export const PopupExit: React.FC<any> = ({ setClose, closeStream }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -20,8 +18,8 @@ export const PopupExit: React.FC<TSidebarPopup> = ({ setClose, onExit }) => {
|
||||
{t("popup-control-no")}
|
||||
</button>
|
||||
<button
|
||||
onClick={closeStream}
|
||||
style={{ height: "48px" }}
|
||||
onClick={() => onExit?.()}
|
||||
className=" button button-secondary"
|
||||
>
|
||||
{t("popup-control-yes")}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export const PopupExitMobile: React.FC<any> = ({ handleReturn, setPopup, closeStream }) => {
|
||||
const onReturnClick = () => {
|
||||
setPopup()
|
||||
handleReturn()
|
||||
}
|
||||
|
||||
const { t } = useTranslation()
|
||||
return (<div className="popup-exit-container">
|
||||
<h2 style={{ marginBottom: "24px" }} className="mobile-users-part-header-title">
|
||||
{t("popup-control-exit-title")}
|
||||
</h2>
|
||||
<div className="popup-exit-button-container-mobile">
|
||||
<button onClick={onReturnClick} className="button button-primary">
|
||||
{t("popup-control-no")}
|
||||
</button>
|
||||
<button
|
||||
onClick={closeStream}
|
||||
style={{ height: "48px" }}
|
||||
className=" button button-secondary"
|
||||
>
|
||||
{t("popup-control-yes")}
|
||||
</button>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import back from 'images/icons/Back.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useState } from 'react'
|
||||
|
||||
|
||||
export const PopupInviteMobile: React.FC<any> = ({ setPopup, handleReturn }) => {
|
||||
const { t } = useTranslation()
|
||||
const [copy, setCopy] = useState(false);
|
||||
|
||||
function copyLink() {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
setCopy((prev) => !prev);
|
||||
}
|
||||
|
||||
const onReturnClick = () => {
|
||||
setPopup()
|
||||
handleReturn()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: "28px" }} className='popup-header-mobile'>
|
||||
<img onClick={onReturnClick} src={back}></img>
|
||||
<h2>{t('popup-control-invite-title')}</h2>
|
||||
</div>
|
||||
<div className='popup-invite-container'>
|
||||
<p className='popup-invite-title'>{t('popup-control-link')}</p>
|
||||
<input
|
||||
className="share-popup-data-input href"
|
||||
value={window.location.href}
|
||||
readOnly
|
||||
></input>
|
||||
{!copy ? (
|
||||
<button style={{ width: '171px' }} onClick={copyLink} className="button button-primary ">
|
||||
<span className="share-popup-copy-button-icon"></span>
|
||||
<span className="">
|
||||
{copy ? t("popup-control-btn-active") : t("popup-control-btn")}
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<button style={{ width: '171px' }} onClick={copyLink} className="button button-teritary">
|
||||
<span className="share-popup-copied-button-icon"></span>
|
||||
<span className="">{t("popup-control-btn-active")}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import back from 'images/icons/Back.svg'
|
||||
import ok from 'images/icons/OK.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const PopupLanguageMobile: React.FC<any> = ({ currentLang, setPopup, handleReturn, handleLanguage }) => {
|
||||
console.log(currentLang)
|
||||
const buttonArray = [{
|
||||
value: 'ru',
|
||||
captionInt: "Русский",
|
||||
caption: 'Русский',
|
||||
}, {
|
||||
value: 'en',
|
||||
captionInt: "English",
|
||||
caption: 'Английский',
|
||||
}]
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onReturnClick = () => {
|
||||
setPopup()
|
||||
handleReturn()
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div style={{ marginBottom: "28px" }} className='popup-header-mobile'>
|
||||
<img onClick={onReturnClick} src={back}></img>
|
||||
<h2>{t('popup-change-language')}</h2>
|
||||
</div>
|
||||
<div className="popup-exit-button-container-mobile">
|
||||
{buttonArray.map((i, index) => (
|
||||
<button onClick={() => handleLanguage(i.value)} disabled={i.value === currentLang} key={index} className='popup-button-language'>
|
||||
<div className='popup-button-container-language'>
|
||||
<span>{i.caption}</span>
|
||||
<span style={{ fontSize: "12px" }}>{i.captionInt}</span>
|
||||
</div>
|
||||
{currentLang === i.value && (<img className='popup-button-icon-active' src={ok} />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 211 B After Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 444 B After Width: | Height: | Size: 444 B |
@@ -0,0 +1,21 @@
|
||||
import { User } from "components/pages/Stream/components/User/User"
|
||||
import close from 'images/icons/close.svg'
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export const UserListMobilePopup: React.FC<any> = ({ isMobile, closePopup, userArr, height }) => {
|
||||
const {t} = useTranslation()
|
||||
return (<div style={{height: height}} className="popup-mobile">
|
||||
<div className="popup-mobile-container">
|
||||
<div style={{ top: "-20px", right: "-20px" }} onClick={closePopup} className="icon-close-container">
|
||||
<img src={close}></img>
|
||||
</div>
|
||||
<h2 style={{ marginBottom: "20px" }}>{t('popup-userlist')}</h2>
|
||||
{userArr.map((i: any) => (<User isMobile={isMobile} closeSidebar={closePopup}
|
||||
isSidebarWide={true} /// this is for disable showhing button's caption
|
||||
isAdmin={false}
|
||||
></User>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
.popup-header-mobile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-invite-title {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
line-height: 120%;
|
||||
/* identical to box height, or 24px */
|
||||
|
||||
|
||||
/* Graff/White/Inactive */
|
||||
|
||||
color: #C5C7CE;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.popup-invite-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.popup-exit-container {
|
||||
padding-top: 48px;
|
||||
}
|
||||
|
||||
.popup-exit-button-container-mobile {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.popup-button-language {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
font-family: 'Inter';
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
color: #FFFFFF;
|
||||
padding: 16px 12px;
|
||||
position: relative;
|
||||
/* Button_1 */
|
||||
background: #23242A;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.popup-button-container-language {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.popup-button-icon-active {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 20px;
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
import "components/pages/Stream/Sidebar/Sidebar.css";
|
||||
import "components/pages/Stream/components/Sidebar/Sidebar.css";
|
||||
import { useState } from "react";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { animationButton } from "utils/animationProps";
|
||||
import { useAppSelector } from "hooks/redux";
|
||||
|
||||
export const Button: React.FC<any> = ({ button, active, isSidebarWide }) => {
|
||||
|
||||
const [hover, setHover] = useState(false);
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
|
||||
const typeButton = button.type !== "fullscreen" && button.type !== "language";
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.footer__container {
|
||||
box-sizing: border-box;
|
||||
background-color: transparent;
|
||||
background: #141414;
|
||||
background: #1C1D21;
|
||||
border-radius: 32px 32px 0px 0px;
|
||||
color: #EBEBEB;
|
||||
display: flex;
|
||||
@@ -388,7 +387,7 @@
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.footer__block_copyright {
|
||||
|
||||
@@ -6,7 +6,6 @@ import ru from 'images/icons/RU.svg'
|
||||
import en from 'images/icons/EN.svg'
|
||||
|
||||
|
||||
import React, { CSSProperties, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import { useAppSelector, useAppDispatch } from "hooks/redux";
|
||||
@@ -30,7 +29,7 @@ export const Header: React.FC = ({ }) => {
|
||||
|
||||
return (
|
||||
<header className={location.pathname === '/' ? 'header' : 'header header-popup'}>
|
||||
<a className="header-logo" target='_blank' href="https://estate.graff.tech/" style={{ cursor: "pointer" }}><img alt="company-logo" src={logo} />
|
||||
<a className="header-logo" href={`https://estate.graff.tech/?lang=${currentLang}`} style={{ cursor: "pointer" }}><img alt="company-logo" src={logo} />
|
||||
</a>
|
||||
<div className="header-button-container">
|
||||
{buttons.map((item, index) => (<button key={index} onClick={() => dispatch(handleChangeLanguage(item.value))} value={item.value} className={currentLang === item.value ? 'button-lang button-lang-active' : "button-lang"}><img src={item.icon}></img></button>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
const useQuery = () => new URLSearchParams(useLocation().search);
|
||||
|
||||
export default useQuery
|
||||
@@ -1,401 +0,0 @@
|
||||
import { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { ACTIONS } from "../socket/actions";
|
||||
import socketInit from "../socket";
|
||||
import freeice from "freeice";
|
||||
import { useStateWithCallback } from "./useStateWithCallback";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
export const useWebRTC = (roomId, user) => {
|
||||
const history = useHistory();
|
||||
const [clients, setClients] = useStateWithCallback([]);
|
||||
const audioElements = useRef({});
|
||||
const [message, setMessages] = useState("");
|
||||
const [control, setControl] = useState('')
|
||||
const [warning, setWarning] = useState("");
|
||||
const connections = useRef({});
|
||||
const socket = useRef(null);
|
||||
const localMediaStream = useRef(null);
|
||||
const clientsRef = useRef(null);
|
||||
|
||||
const addNewClient = useCallback(
|
||||
(newClient, cb) => {
|
||||
const lookingFor = clients.find((client) => client.id === newClient.id);
|
||||
|
||||
if (lookingFor === undefined) {
|
||||
setClients((existingClients) => [...existingClients, newClient], cb);
|
||||
}
|
||||
},
|
||||
[clients, setClients]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
clientsRef.current = clients;
|
||||
}, [clients]);
|
||||
|
||||
useEffect(() => {
|
||||
const initChat = async () => {
|
||||
socket.current = socketInit();
|
||||
console.log(localMediaStream.current)
|
||||
await captureMedia();
|
||||
addNewClient(
|
||||
{
|
||||
...user,
|
||||
muted: true,
|
||||
admin: false,
|
||||
control: false,
|
||||
},
|
||||
() => {
|
||||
const localElement = audioElements.current[user.id];
|
||||
if (localElement) {
|
||||
localElement.volume = 0;
|
||||
localElement.srcObject = localMediaStream.current;
|
||||
}
|
||||
}
|
||||
);
|
||||
socket.current.on(ACTIONS.MUTE_INFO, ({ userId, isMute, isAdmin }) => {
|
||||
handleSetMute(isMute, userId);
|
||||
});
|
||||
|
||||
socket.current.on(ACTIONS.ADD_PEER, handleNewPeer);
|
||||
socket.current.on(ACTIONS.REMOVE_PEER, handleRemovePeer);
|
||||
socket.current.on(ACTIONS.ICE_CANDIDATE, handleIceCandidate);
|
||||
socket.current.on(ACTIONS.SESSION_DESCRIPTION, setRemoteMedia);
|
||||
socket.current.on(ACTIONS.MUTE, ({ peerId, userId }) => {
|
||||
handleSetMute(true, userId);
|
||||
});
|
||||
|
||||
socket.current.on("ADMIN-LEFT", ({ isLeft }) => {
|
||||
setWarning(isLeft);
|
||||
});
|
||||
|
||||
socket.current.on("ADMIN-SET", ({ admin }) => {
|
||||
handleSetAdmin(true, admin);
|
||||
setControl(admin)
|
||||
});
|
||||
|
||||
socket.current.on(ACTIONS.UNMUTE, ({ peerId, userId }) => {
|
||||
handleSetMute(false, userId);
|
||||
});
|
||||
socket.current.emit(ACTIONS.JOIN, {
|
||||
roomId,
|
||||
user,
|
||||
});
|
||||
|
||||
socket.current.on("CONTROL-SET", ({ userId }) => {
|
||||
handleSetControl(userId);
|
||||
setControl(userId)
|
||||
});
|
||||
|
||||
async function captureMedia() {
|
||||
// Start capturing local audio stream.
|
||||
localMediaStream.current = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
}).catch(() => {
|
||||
alert('Подключите микрофон!')
|
||||
});
|
||||
}
|
||||
|
||||
socket.current.on("REQUEST-CONTROL", ({ userID }) => {
|
||||
setMessages(userID);
|
||||
});
|
||||
|
||||
async function handleNewPeer({ peerId, createOffer, user: remoteUser }) {
|
||||
if (peerId in connections.current) {
|
||||
return console.warn(
|
||||
`You are already connected with ${peerId} (${user.name})`
|
||||
);
|
||||
}
|
||||
|
||||
// Store it to connections
|
||||
connections.current[peerId] = new RTCPeerConnection({
|
||||
iceServers: freeice(),
|
||||
});
|
||||
|
||||
// Handle new ice candidate on this peer connection
|
||||
connections.current[peerId].onicecandidate = (event) => {
|
||||
socket.current.emit(ACTIONS.RELAY_ICE, {
|
||||
peerId,
|
||||
icecandidate: event.candidate,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle on track event on this connection
|
||||
connections.current[peerId].ontrack = ({ streams: [remoteStream] }) => {
|
||||
addNewClient(
|
||||
{
|
||||
...remoteUser,
|
||||
muted: true,
|
||||
admin: false,
|
||||
control: false,
|
||||
},
|
||||
() => {
|
||||
// get current users mute info
|
||||
const currentUser = clientsRef.current.find(
|
||||
(client) => client.id === user.id
|
||||
);
|
||||
if (currentUser) {
|
||||
socket.current.emit(ACTIONS.MUTE_INFO, {
|
||||
userId: user.id,
|
||||
roomId,
|
||||
isMute: currentUser.muted,
|
||||
isAdmin: currentUser.admin,
|
||||
isControl: currentUser.control,
|
||||
});
|
||||
}
|
||||
if (audioElements.current[remoteUser.id]) {
|
||||
audioElements.current[remoteUser.id].srcObject = remoteStream;
|
||||
} else {
|
||||
let settled = false;
|
||||
const interval = setInterval(() => {
|
||||
if (audioElements.current[remoteUser.id]) {
|
||||
audioElements.current[remoteUser.id].srcObject =
|
||||
remoteStream;
|
||||
settled = true;
|
||||
}
|
||||
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Add connection to peer connections track
|
||||
localMediaStream.current.getTracks().forEach((track) => {
|
||||
connections.current[peerId].addTrack(track, localMediaStream.current);
|
||||
});
|
||||
|
||||
// Create an offer if required
|
||||
if (createOffer) {
|
||||
const offer = await connections.current[peerId].createOffer();
|
||||
|
||||
// Set as local description
|
||||
await connections.current[peerId].setLocalDescription(offer);
|
||||
|
||||
// send offer to the server
|
||||
socket.current.emit(ACTIONS.RELAY_SDP, {
|
||||
peerId,
|
||||
sessionDescription: offer,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function handleRemovePeer({ peerId, userId }) {
|
||||
console.log(userId);
|
||||
// Correction: peerID to peerId
|
||||
if (connections.current[peerId]) {
|
||||
connections.current[peerId].close();
|
||||
}
|
||||
|
||||
delete connections.current[peerId];
|
||||
delete audioElements.current[peerId];
|
||||
setClients((list) => handleLogout(list, userId));
|
||||
}
|
||||
async function handleIceCandidate({ peerId, icecandidate }) {
|
||||
if (icecandidate) {
|
||||
connections.current[peerId].addIceCandidate(icecandidate);
|
||||
}
|
||||
}
|
||||
async function setRemoteMedia({
|
||||
peerId,
|
||||
sessionDescription: remoteSessionDescription,
|
||||
}) {
|
||||
connections.current[peerId].setRemoteDescription(
|
||||
new RTCSessionDescription(remoteSessionDescription)
|
||||
);
|
||||
|
||||
// If session descrition is offer then create an answer
|
||||
if (remoteSessionDescription.type === "offer") {
|
||||
const connection = connections.current[peerId];
|
||||
|
||||
const answer = await connection.createAnswer();
|
||||
connection.setLocalDescription(answer);
|
||||
|
||||
socket.current.emit(ACTIONS.RELAY_SDP, {
|
||||
peerId,
|
||||
sessionDescription: answer,
|
||||
});
|
||||
}
|
||||
}
|
||||
async function handleSetMute(mute, userId) {
|
||||
console.log(clientsRef.current);
|
||||
const clientIdx = clientsRef.current
|
||||
.map((client) => client.id)
|
||||
.indexOf(userId);
|
||||
const allConnectedClients = JSON.parse(
|
||||
JSON.stringify(clientsRef.current)
|
||||
);
|
||||
|
||||
if (clientIdx > -1) {
|
||||
allConnectedClients[clientIdx].muted = mute;
|
||||
setClients(allConnectedClients);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetControl(userId) {
|
||||
const clientIdx = clientsRef.current
|
||||
.map((client) => client.id)
|
||||
.indexOf(userId);
|
||||
const allConnectedClients = JSON.parse(
|
||||
JSON.stringify(clientsRef.current)
|
||||
);
|
||||
for (let i = 0; i < allConnectedClients.length; i++) {
|
||||
allConnectedClients[i].control = false;
|
||||
}
|
||||
|
||||
if (clientIdx > -1) {
|
||||
allConnectedClients[clientIdx].control = true;
|
||||
setClients(allConnectedClients);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetAdmin(admin, userId) {
|
||||
const clientIdx = clientsRef.current
|
||||
.map((client) => client.id)
|
||||
.indexOf(userId);
|
||||
const allConnectedClients = JSON.parse(
|
||||
JSON.stringify(clientsRef.current)
|
||||
);
|
||||
if (clientIdx > -1) {
|
||||
allConnectedClients[clientIdx].admin = admin;
|
||||
allConnectedClients[clientIdx].control = true;
|
||||
setClients(allConnectedClients);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initChat();
|
||||
return () => {
|
||||
if (localMediaStream.current) {
|
||||
localMediaStream.current.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
socket.current.emit(ACTIONS.LEAVE, { roomId });
|
||||
for (let peerId in connections.current) {
|
||||
connections.current[peerId].close();
|
||||
delete connections.current[peerId];
|
||||
delete audioElements.current[peerId];
|
||||
}
|
||||
socket.current.off(ACTIONS.ADD_PEER);
|
||||
socket.current.off(ACTIONS.REMOVE_PEER);
|
||||
socket.current.off(ACTIONS.ICE_CANDIDATE);
|
||||
socket.current.off(ACTIONS.SESSION_DESCRIPTION);
|
||||
socket.current.off(ACTIONS.MUTE);
|
||||
socket.current.off(ACTIONS.UNMUTE);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const provideRef = (instance, userId) => {
|
||||
audioElements.current[userId] = instance;
|
||||
};
|
||||
|
||||
const handleAdmin = () => {
|
||||
let settled = false;
|
||||
let interval = setInterval(() => {
|
||||
socket.current.emit("ADMIN-SET", {
|
||||
roomId,
|
||||
});
|
||||
|
||||
settled = true;
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const handleMute = (isMute, userId) => {
|
||||
let settled = false;
|
||||
|
||||
if (userId === user.id) {
|
||||
let interval = setInterval(() => {
|
||||
if (localMediaStream.current) {
|
||||
localMediaStream.current.getTracks()[0].enabled = !isMute;
|
||||
if (isMute) {
|
||||
socket.current.emit(ACTIONS.MUTE, {
|
||||
roomId,
|
||||
userId: user.id,
|
||||
});
|
||||
} else {
|
||||
socket.current.emit(ACTIONS.UNMUTE, {
|
||||
roomId,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
settled = true;
|
||||
}
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
const handleControl = (userId) => {
|
||||
let settled = false;
|
||||
let interval = setInterval(() => {
|
||||
socket.current.emit("CONTROL-SET", {
|
||||
roomId,
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
settled = true;
|
||||
if (settled) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const handleLogout = (list, userId) => {
|
||||
const isAdmin = list.some((i) => i.admin === true && i.id === userId);
|
||||
const isControl = list.some((i) => i.control === true && i.id === userId);
|
||||
console.log(isControl);
|
||||
if (isAdmin) {
|
||||
socket.current.emit("ADMIN-LEFT", {
|
||||
isLeft: true,
|
||||
});
|
||||
} else if (isControl) {
|
||||
handleChangeControl(list);
|
||||
}
|
||||
|
||||
return list.filter((c) => c.id !== userId);
|
||||
};
|
||||
|
||||
const handleChangeControl = (list) => {
|
||||
const admin = list.find((i) => i.admin === true);
|
||||
if (admin) {
|
||||
handleControl(admin.id);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const handleReturnControl = () => {
|
||||
const admin = clients.find((i) => i.admin === true);
|
||||
if (admin) {
|
||||
handleControl(admin.id);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const sendRequset = (user) => {
|
||||
socket.current.emit("REQUEST-CONTROL", {
|
||||
userID: user.id,
|
||||
roomId,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
clients,
|
||||
provideRef,
|
||||
warning,
|
||||
handleMute,
|
||||
handleControl,
|
||||
sendRequset,
|
||||
message,
|
||||
control,
|
||||
handleAdmin,
|
||||
handleReturnControl,
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
function getWindowDimensions() {
|
||||
const { width: width, height: height } = window.visualViewport;
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12L18 12M6 12L12.3636 18M6 12L12.3636 6" stroke="#C5C7CE" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 239 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.4242 11.6364L12.9393 7.57131C12.6193 6.69547 11.3806 6.69547 11.0607 7.57131L9.57576 11.6364M14.4242 11.6364H9.57576M14.4242 11.6364L15 13.0398M9.57576 11.6364L9 13.0398M11.6571 16.8182H3C2.44772 16.8182 2 16.3705 2 15.8182V4C2 3.44772 2.44772 3 3 3H21C21.5523 3 22 3.44772 22 4V15.8182C22 16.3705 21.5523 16.8182 21 16.8182H19.6667C19.1144 16.8182 18.6667 17.2659 18.6667 17.8182V19.9562C18.6667 20.7882 17.7099 21.2563 17.053 20.7457L12.2708 17.0286C12.0953 16.8922 11.8793 16.8182 11.6571 16.8182Z" stroke="#C5C7CE" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 677 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12L10 17L19 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 211 B |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 921 B After Width: | Height: | Size: 921 B |
|
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1018 B |
|
Before Width: | Height: | Size: 430 B After Width: | Height: | Size: 430 B |
|
Before Width: | Height: | Size: 495 B After Width: | Height: | Size: 495 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 581 B After Width: | Height: | Size: 581 B |
|
Before Width: | Height: | Size: 554 B After Width: | Height: | Size: 554 B |
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 957 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 772 B |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 610 B |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 610 B |
|
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 529 B |
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |