Compare commits
14 Commits
443532b484
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ec701a7925 | |||
| 1a3e6f688d | |||
| 62be599d33 | |||
| 0adbf6f4e2 | |||
| 65bc4a011a | |||
| 4d9bd5c98b | |||
| d494abf7ae | |||
| 61efa5611d | |||
| 240f28935d | |||
| 4107a85a4d | |||
| 48e3061c09 | |||
| 13d725261f | |||
| a99883d093 | |||
| 031bd771f9 |
@@ -19,6 +19,7 @@
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/socket.io": "^3.0.2",
|
||||
"axios": "^1.3.4",
|
||||
"framer-motion": "^7.4.0",
|
||||
"i18next": "^22.4.6",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
@@ -4938,6 +4939,29 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
@@ -14146,6 +14170,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
@@ -20942,6 +20971,28 @@
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz",
|
||||
"integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w=="
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
@@ -27447,6 +27498,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/socket.io": "^3.0.2",
|
||||
"axios": "^1.3.4",
|
||||
"framer-motion": "^7.4.0",
|
||||
"i18next": "^22.4.6",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
|
||||
@@ -20,9 +20,20 @@
|
||||
"popup-control-link": "Link for connection",
|
||||
"popup-control-btn": "Copy",
|
||||
"popup-control-btn-active": "Link Copied",
|
||||
"language-control-btn": "Language",
|
||||
"language-control-btn": "English",
|
||||
"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"
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -20,10 +20,16 @@
|
||||
"popup-control-link": "Ссылка для подключения",
|
||||
"popup-control-btn": "Скопировать",
|
||||
"popup-control-btn-active": "Ссылка скопирована",
|
||||
"language-control-btn": "Язык",
|
||||
"language-control-btn": "Русский язык",
|
||||
"exit-control-btn": "Выйти",
|
||||
"popup-control-exit-title": "Вы уверены, что хотите закончить демонстрацию??",
|
||||
"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" />
|
||||
|
||||
@@ -4,44 +4,31 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content__container {
|
||||
height: 100vh;
|
||||
width: 400px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-width: 0px 2px;
|
||||
border-style: solid;
|
||||
border-color: #23242a;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.popup {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 56px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
margin-bottom: 128px;
|
||||
}
|
||||
|
||||
.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,42 +36,60 @@
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 1440px) {
|
||||
.card-title {
|
||||
margin: 22px 0 40px 0;
|
||||
}
|
||||
|
||||
|
||||
.card-container {
|
||||
gap: 24px;
|
||||
margin-bottom: 76px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1152px) {
|
||||
.card-container {
|
||||
gap: 20px;
|
||||
.card-title-container {
|
||||
margin-top: 96px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
margin: 42px 0 40px 0;
|
||||
.card-container {
|
||||
gap: 20px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.card-container {
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 115px;
|
||||
}
|
||||
|
||||
.content__container {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,38 +5,97 @@ 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 { handleChangeLanguage } = languageSlice.actions;
|
||||
const { cards, currentCard } = useAppSelector((state) => state.cardReducer);
|
||||
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(() => {
|
||||
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());
|
||||
dispatch(handleChangeLanguage(cookies.get("i18next")));
|
||||
}, []);
|
||||
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("/");
|
||||
};
|
||||
|
||||
@@ -47,7 +106,9 @@ const App: React.FC = () => {
|
||||
<Route exact path="/">
|
||||
<Header></Header>
|
||||
<div className="main">
|
||||
<h3 className="card-title">{t("demo-title")}</h3>
|
||||
<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>
|
||||
@@ -58,9 +119,15 @@ const App: React.FC = () => {
|
||||
<Route path="/connect-page">
|
||||
{currentCard ? (
|
||||
<div className="background">
|
||||
<Header></Header>
|
||||
<div className="content__container">
|
||||
<PopupComponent></PopupComponent>
|
||||
<div className="popup-container">
|
||||
<div className="content__container">
|
||||
<SessionStartComponent
|
||||
cleanErrors={cleanErrors}
|
||||
handleConnect={handleConnect}
|
||||
isLoading={isLoading}
|
||||
currentCard={currentCard}
|
||||
></SessionStartComponent>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -68,11 +135,17 @@ 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>
|
||||
<PlanComponent></PlanComponent>
|
||||
<div className="popup-container">
|
||||
<PlanComponent></PlanComponent>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import "./ConnectComponent.css";
|
||||
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const ConnectComponent: React.FC<any> = ({ logo, isLoading, title, handleConnect }) => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="popup">
|
||||
<div className="popup-img-container">
|
||||
<img className="popup-logo" src={logo} alt="лого" />
|
||||
</div>
|
||||
<div className="popup-button-container">
|
||||
<button disabled={isLoading} onClick={() => handleConnect(title)} className="button button-primary">
|
||||
{t("popup-main-btn-start")}
|
||||
</button>
|
||||
<div className="line"></div>
|
||||
<button onClick={() => history.goBack()} disabled={isLoading} className="button button-type-small">
|
||||
{t("popup-main-select")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import "./LoadingPopup.css";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import "./LoaderComponent.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const LoadingPopup: React.FC<any> = ({ logo }) => {
|
||||
const history = useHistory();
|
||||
export const LoaderComponent: React.FC<any> = ({ logo }) => {
|
||||
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -13,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.image.logo}
|
||||
onConnect={() => dispatch(createSession(currentCard.title))}
|
||||
></PopupConnect>
|
||||
</motion.div>
|
||||
)}
|
||||
{loadingPopup && (
|
||||
<motion.div
|
||||
key={2}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<LoadingPopup logo={currentCard.image.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;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import "./PopupConnect.css";
|
||||
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PopupConnect: React.FC<any> = ({ onConnect, logo, isLoading }) => {
|
||||
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-primary">
|
||||
{t("popup-main-btn-start")}
|
||||
</button>
|
||||
<div className="popup-line"></div>
|
||||
<button onClick={() => history.goBack()} className="button-secondary">
|
||||
{t("popup-main-select")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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,31 +1,24 @@
|
||||
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.image.preview} alt='building' />
|
||||
<img className="card-image" src={item.preview} alt='building' />
|
||||
<div className="card-body">
|
||||
<div className="card-header">
|
||||
<img src={iconButton} className="card-icon" alt="лого" />
|
||||
<img src={item.icon} className="card-icon" alt="лого" />
|
||||
<div className="card-name">
|
||||
<p className="caption-name">{currentLang === "ru" ? item.title_full.ru : item.title_full.en}</p>
|
||||
<p className="card-location">{currentLang === "ru" ? item.location.ru : item.location.en}</p>
|
||||
<h2 className="caption-name">{item.title}</h2>
|
||||
<h2 className="card-location">{item.location}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-description-block">
|
||||
<p className="card-description"> {currentLang === "ru"
|
||||
? item.description.ru
|
||||
: item.description.en}
|
||||
<p className="card-description">
|
||||
{item.description}
|
||||
</p>
|
||||
{item.description_second ? <p className="card-description-second">{currentLang === "ru"
|
||||
? item.description_second.ru
|
||||
: item.description_second.en}
|
||||
{item.feature_description ? <p className="card-description-second">
|
||||
{item.feature_description}
|
||||
</p> : <p className="card-description-empty"></p>}
|
||||
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 602 KiB |
|
Before Width: | Height: | Size: 792 KiB |
|
Before Width: | Height: | Size: 666 KiB |
@@ -2,6 +2,11 @@
|
||||
width: 312px;
|
||||
}
|
||||
|
||||
.calendar-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.weekday-table {
|
||||
user-select: none;
|
||||
background: transparent;
|
||||
@@ -23,7 +28,7 @@
|
||||
}
|
||||
|
||||
.calendar {
|
||||
height: 320px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
@@ -37,6 +42,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-cell_active {
|
||||
cursor: pointer;
|
||||
background: rgba(86, 126, 206, 0.2);
|
||||
|
||||
}
|
||||
|
||||
.calendar-nav:hover {
|
||||
background: rgba(86, 126, 206, 0.2);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.form-input-container {
|
||||
@@ -11,6 +11,7 @@
|
||||
}
|
||||
|
||||
.form-input {
|
||||
width: 100%;
|
||||
background: #23242a;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
@@ -26,6 +27,9 @@
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.form-input-caption {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
@@ -34,9 +38,78 @@
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-image-source: linear-gradient(180deg, #bc75ff 0%, #798fff 100%);
|
||||
border: linear-gradient(180deg, #bc75ff 0%, #798fff 100%);
|
||||
}
|
||||
|
||||
.form-input:invalid {
|
||||
border: 1px solid #E94444;
|
||||
.form-border {
|
||||
cursor: text;
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
|
||||
}
|
||||
|
||||
.form-border::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 4px;
|
||||
padding: 1px;
|
||||
background: transparent;
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
.form-border-error {
|
||||
cursor: text;
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
|
||||
}
|
||||
|
||||
.form-border-error::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 4px;
|
||||
padding: 1px;
|
||||
background: transparent;
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
.form-border-error:focus-within::after {
|
||||
background: linear-gradient(180deg, #E94444 100%, #E94444 100%);
|
||||
}
|
||||
|
||||
.form-border:focus-within::after {
|
||||
background: linear-gradient(180deg, #798fff -41.07%, #d375ff 100%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:focus {
|
||||
transition: background-color 600000s 0s, color 600000s 0s;
|
||||
}
|
||||
|
||||
.error-caption {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 10px;
|
||||
line-height: 130%;
|
||||
/* identical to box height, or 13px */
|
||||
height: 26px;
|
||||
display: none;
|
||||
/* Error */
|
||||
color: #E94444;
|
||||
}
|
||||
|
||||
.error-caption-active {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -2,24 +2,33 @@ import chevron from "images/icons/ChevronLeft.svg";
|
||||
import "./Form.css";
|
||||
|
||||
import { planSlice } from "store/reducers/planSlice";
|
||||
import useFormWithValidation from "hooks/useFormWithValidation";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export const Form: React.FC<any> = ({ time }) => {
|
||||
const inputRef = useRef()
|
||||
const [error, showError] = useState(false)
|
||||
const { values, handleChangeState, resetForm, errors, isValid } =
|
||||
useFormWithValidation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const { handleNavigation, handleTimeValue } = planSlice.actions;
|
||||
|
||||
const handleValue = (day: moment.Moment) => {
|
||||
dispatch(
|
||||
handleNavigation({
|
||||
isDone: false,
|
||||
isCalendar: false,
|
||||
isTimepicker: true,
|
||||
isForm: false,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(handleTimeValue(day));
|
||||
};
|
||||
useEffect(() => {
|
||||
resetForm();
|
||||
}, [resetForm]);
|
||||
|
||||
|
||||
|
||||
function handleSubmit(e: React.SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleReturn = () => {
|
||||
dispatch(
|
||||
@@ -32,13 +41,14 @@ export const Form: React.FC<any> = ({ time }) => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="calendar-container">
|
||||
<h3 className="plan-title plan-title-plan">
|
||||
Расскажите <br></br> о себе.
|
||||
<h3 className="plan-title">
|
||||
Расскажите о себе.
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => handleReturn()}
|
||||
onClick={(e) => handleReturn()}
|
||||
style={{ width: "141px", padding: "4px 16px 4px 4px", height: "32px" }}
|
||||
className="button button-type-small button-plan"
|
||||
>
|
||||
@@ -49,17 +59,27 @@ export const Form: React.FC<any> = ({ time }) => {
|
||||
<div className="calendar-header">
|
||||
<span className="calendar-date">{time.format("DD MMM, LT")}</span>
|
||||
</div>
|
||||
<form className="form">
|
||||
<form noValidate onSubmit={handleSubmit} className="form">
|
||||
<div className="form-input-container">
|
||||
<span className="form-input-caption">Имя</span>
|
||||
<input min={2} max={30} className="form-input" type="text"></input>
|
||||
<label className="form-input-caption">Имя</label>
|
||||
<div onClick={(e: any) => console.log(e.target.closest('input')) } className={errors.name ? "form-border-error" : 'form-border'}>
|
||||
<input ref={inputRef} required onChange={handleChangeState} name='name' value={values.name} minLength={2} maxLength={30} className="form-input" type="text"></input>
|
||||
</div>
|
||||
<span className={errors.name ? "error-caption error-caption-active" : "error-caption"}>{errors.name}</span>
|
||||
</div>
|
||||
<div className="form-input-container">
|
||||
<span className="form-input-caption">E-mail</span>
|
||||
<input className="form-input" type="e-mail"></input>
|
||||
<label className="form-input-caption">E-mail</label>
|
||||
<div className={errors.email ? "form-border-error" : 'form-border'}>
|
||||
<input required onChange={handleChangeState} name='email' value={values.email} className="form-input" type="email"></input>
|
||||
</div>
|
||||
<span className={errors.email ? "error-caption error-caption-active" : "error-caption"}>{errors.email}</span>
|
||||
|
||||
|
||||
</div>
|
||||
<button
|
||||
style={{ marginTop: "32px" }}
|
||||
type="submit"
|
||||
disabled={!isValid}
|
||||
style={{ marginTop: "24px" }}
|
||||
className="button button-primary"
|
||||
>
|
||||
Запланировать
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
width: 428px;
|
||||
justify-content: center;
|
||||
padding: 24px 56px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
box-sizing: border-box;
|
||||
border-width: 0px 2px;
|
||||
border-style: solid;
|
||||
border-color: #23242a;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.button-plan {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.calendar-position {
|
||||
@@ -33,6 +34,7 @@
|
||||
font-size: 38px;
|
||||
line-height: 100%;
|
||||
color: #f2f2f2;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.calendar-date {
|
||||
@@ -103,23 +105,44 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
.calendar-position {
|
||||
top: 50%;
|
||||
}
|
||||
.plan-logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 430px) {
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
|
||||
.plan-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.plan-logo-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content-container-plan {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
padding: 10px;
|
||||
gap: 45px;
|
||||
padding: 0 10px;
|
||||
border: none;
|
||||
height: auto;
|
||||
|
||||
}
|
||||
|
||||
.calendar-position {
|
||||
position: static;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.calendar {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -129,7 +152,8 @@
|
||||
}
|
||||
|
||||
.plan-title {
|
||||
margin-top: 32px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
@@ -137,9 +161,3 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 380px) {
|
||||
.calendar-position {
|
||||
top: 60%;
|
||||
}
|
||||
}
|
||||
@@ -16,18 +16,19 @@ import { useAppSelector } from "hooks/redux";
|
||||
import { popupAnimation } from "utils/animationProps";
|
||||
|
||||
export const PlanComponent: React.FC<any> = () => {
|
||||
const { isDone, isCalendar, isTimepicker, isForm, time } = useAppSelector(
|
||||
const { isDone, isCalendar, isTimepicker, isForm, time, currTime } = useAppSelector(
|
||||
(state) => state.planReducer
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="content-container-plan">
|
||||
<img alt="logo" src={logo} className="plan-logo"></img>
|
||||
<div className="plan-logo-container">
|
||||
<img style={{ width: "158px" }} src={logo}></img>
|
||||
</div>
|
||||
<div className="calendar-position">
|
||||
<AnimatePresence mode="wait">
|
||||
{isCalendar && (
|
||||
<motion.div
|
||||
style={{ height: "560px" }}
|
||||
key={1}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
@@ -39,31 +40,28 @@ export const PlanComponent: React.FC<any> = () => {
|
||||
)}
|
||||
{isTimepicker && (
|
||||
<motion.div
|
||||
style={{ height: "560px" }}
|
||||
key={2}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<TimepickerComponent time={time}></TimepickerComponent>
|
||||
<TimepickerComponent time={currTime}></TimepickerComponent>
|
||||
</motion.div>
|
||||
)}
|
||||
{isForm && (
|
||||
<motion.div
|
||||
style={{ height: "560px" }}
|
||||
key={3}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
<Form time={time}></Form>
|
||||
<Form time={currTime}></Form>
|
||||
</motion.div>
|
||||
)}
|
||||
{isDone && (
|
||||
<motion.div
|
||||
style={{ height: "560px" }}
|
||||
key={4}
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
@@ -77,7 +75,7 @@ export const PlanComponent: React.FC<any> = () => {
|
||||
{!isDone && (
|
||||
<>
|
||||
<div className="line line-calendar"></div>
|
||||
<button className="button button-type-small">
|
||||
<button style={{ height: '32px' }} className="button button-type-small">
|
||||
На сайт жилого комплекса
|
||||
</button>
|
||||
</>
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
.button-plan {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.plan-title-plan {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.timepicker-cell {
|
||||
padding: 9px 18px;
|
||||
|
||||
@@ -51,7 +51,7 @@ export const TimepickerComponent: React.FC<any> = ({ time }) => {
|
||||
};
|
||||
return (
|
||||
<div className="calendar-container">
|
||||
<h3 className="plan-title plan-title-plan">Выберите время.</h3>
|
||||
<h3 className="plan-title">Выберите время.</h3>
|
||||
<button
|
||||
onClick={() => handleReturn()}
|
||||
style={{ width: "120px", padding: "4px 16px 4px 4px", height: "32px" }}
|
||||
|
||||
@@ -1,27 +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,
|
||||
isMuted,
|
||||
isSidebarWide
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<div className="toolbar-field-part">
|
||||
<div className="toolbar-button-container-border-line"></div>
|
||||
<ControlButton isSidebarWide={isSidebarWide} onClick={() => console.log("click!")}></ControlButton>
|
||||
<MicroButton isSidebarWide={isSidebarWide} isMuted={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,55 +0,0 @@
|
||||
import microOn from "images/icons/MicroOn.svg";
|
||||
|
||||
import { useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { Button } from "components/shared/Button/Button";
|
||||
|
||||
const container = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
transition: { duration: 0.2, ease: "easeOut" },
|
||||
},
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: { delay: 0.15, duration: 0.2, ease: "easeIn" },
|
||||
},
|
||||
exit: {
|
||||
opacity: 0,
|
||||
transition: { duration: 0.2, ease: "easeOut" },
|
||||
},
|
||||
};
|
||||
|
||||
export const LanguageButton: React.FC<any> = ({ hover, setHover, isSidebarWide }) => {
|
||||
const [active, setActive] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const button = {
|
||||
icon: microOn,
|
||||
active: "language-control-btn",
|
||||
inactive: "language-control-btn",
|
||||
type: "language",
|
||||
};
|
||||
|
||||
function handleClick() {
|
||||
setOpen(true);
|
||||
setActive((prev) => !prev);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="toolbar-button-area">
|
||||
<Button isSidebarWide={isSidebarWide} button={button} onClick={handleClick}></Button>
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import "./PlayerStyles.css";
|
||||
import React, { useEffect } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
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";
|
||||
|
||||
type link = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const PlayerComponent: React.FC<any> = ({ closeStream }) => {
|
||||
const { id } = useParams<link>();
|
||||
const dispatch = useAppDispatch();
|
||||
const { cleanErrors } = sessionSlice.actions;
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(connectSession(id)).then((res: any) => {
|
||||
if (res.error) {
|
||||
alert(res.payload);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
dispatch(cleanErrors());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { url } = useAppSelector((state) => state.sessionReducer);
|
||||
|
||||
return (
|
||||
<>
|
||||
<iframe
|
||||
id="player"
|
||||
onFocus={() => console.log('focus')}
|
||||
onBlur={(e) => e.target.focus()} /// element loosing focus and keyboard input doesn't work
|
||||
src={url}
|
||||
className={"player playerOn"}
|
||||
security={""}
|
||||
allowFullScreen={true}
|
||||
></iframe>
|
||||
<Sidebar closeStream={closeStream}></Sidebar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
.playerOn {
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.playerOff {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import "./sharePopup.css";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PopupShare: React.FC<any> = ({ setClose }) => {
|
||||
const [copy, setCopy] = useState(false);
|
||||
|
||||
function copyLink() {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
setCopy((prev) => !prev);
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
setClose();
|
||||
setCopy(false);
|
||||
}
|
||||
|
||||
useEffect(() => () => setCopy(false), []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="share-popup-container popup">
|
||||
<div className="mobile-users-part-header share-header-popup">
|
||||
<span className="mobile-users-part-header-title">
|
||||
{t("popup-control-invite-title")}
|
||||
</span>
|
||||
<button
|
||||
onClick={closePopup}
|
||||
className="mobile-users-part-header-close-button"
|
||||
></button>
|
||||
</div>
|
||||
<div className="share-popup-data-container">
|
||||
<span className="share-popup-data-title">
|
||||
{t("popup-control-link")}
|
||||
</span>
|
||||
<input
|
||||
className="share-popup-data-input href"
|
||||
value={window.location.href}
|
||||
readOnly
|
||||
></input>
|
||||
</div>
|
||||
<div className="share-popup-data-container">
|
||||
{!copy ? (
|
||||
<button 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 onClick={copyLink} className="button button-teritary">
|
||||
<span className="share-popup-copied-button-icon"></span>
|
||||
<span className="">{t("popup-control-btn-active")}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
import { User } from "components/pages/Stream/User/User";
|
||||
|
||||
export const UserList: React.FC<any> = ({ isSidebarWide, isAdmin, closeSidebar }) => {
|
||||
return (
|
||||
<div className="toolbar-field-part">
|
||||
<User closeSidebar={closeSidebar} isAdmin={isAdmin} isSidebarWide={isSidebarWide}></User>
|
||||
<div className="toolbar-button-container-border-line"></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;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import "./PlayerStyles.css";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
import { connectSession } from "store/reducers/ActionCreator";
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
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> = ({ cleanErrors, handleDisconnect, loadStream }) => {
|
||||
const { isMobile } = useMobile();
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const width = windowDimensions.width;
|
||||
const height = windowDimensions.height;
|
||||
const [popup, setPopup] = useState(false);
|
||||
const { playerCount } = useAppSelector((state) => state.sessionReducer)
|
||||
|
||||
|
||||
|
||||
|
||||
const { id } = useParams<link>();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(connectSession(id)).unwrap().then(() => {
|
||||
loadStream()
|
||||
}).catch((res) => {
|
||||
alert(res);
|
||||
history.push('/')
|
||||
})
|
||||
return () => {
|
||||
dispatch(cleanErrors());
|
||||
handleDisconnect()
|
||||
window.removeEventListener("change ", (event: any) => {
|
||||
setPopup(false);
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
if (width < height) {
|
||||
setPopup(true);
|
||||
} else {
|
||||
setPopup(false);
|
||||
}
|
||||
}
|
||||
}, [width, height, isMobile]);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup && (
|
||||
<div className="popup-screen" style={{ height: height }}>
|
||||
<h2>Переверните устройство</h2>
|
||||
</div>
|
||||
)}
|
||||
<Player></Player>
|
||||
<Sidebar
|
||||
handleDisconnect={handleDisconnect}
|
||||
players={playerCount}
|
||||
heightDevice={height}
|
||||
isMobile={isMobile}
|
||||
></Sidebar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.playerOn {
|
||||
user-select: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.playerOff {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.popup-screen {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
z-index: 99;
|
||||
background: #1C1D21;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
line-height: 125%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -105,13 +104,15 @@
|
||||
}
|
||||
|
||||
.toolbar-button-area {
|
||||
border-radius: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toolbar-button-area.hidden {
|
||||
@@ -152,8 +153,8 @@
|
||||
.toolbar-button {
|
||||
background: transparent;
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #ffffff;
|
||||
@@ -161,7 +162,8 @@
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
line-height: 17px;
|
||||
padding: 0px;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.toolbar-button-active {
|
||||
@@ -265,7 +267,7 @@
|
||||
width: 18px;
|
||||
height: 26px;
|
||||
background-color: #eb5757;
|
||||
background: url("icons/newCaptTriangleIcon.svg");
|
||||
background: url("../../../../../images/icons/newCaptTriangleIcon.svg");
|
||||
}
|
||||
|
||||
.user-control-popup {
|
||||
@@ -286,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;
|
||||
}
|
||||
|
||||
@@ -310,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;
|
||||
}
|
||||
|
||||
@@ -369,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;
|
||||
}
|
||||
|
||||
@@ -429,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%;
|
||||
}
|
||||
|
||||
@@ -469,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%;
|
||||
}
|
||||
|
||||
@@ -485,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%;
|
||||
}
|
||||
|
||||
@@ -575,8 +577,7 @@
|
||||
}
|
||||
|
||||
@media screen and (max-height: 700px) {
|
||||
.toolbar-field {
|
||||
}
|
||||
.toolbar-field {}
|
||||
|
||||
|
||||
.toolbar-button:hover {
|
||||
@@ -643,4 +644,4 @@
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,13 +1,13 @@
|
||||
import "./Sidebar.css";
|
||||
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,
|
||||
@@ -16,24 +16,31 @@ import {
|
||||
wideSidebarAdminVariants,
|
||||
} from "utils/animationProps";
|
||||
|
||||
export const Sidebar: React.FC<any> = ({ closeStream }) => {
|
||||
export const SidebarDesktop: React.FC<any> = ({
|
||||
closeStream,
|
||||
exitPopup,
|
||||
isMuted,
|
||||
isControl,
|
||||
height,
|
||||
handleMuteClick,
|
||||
handleControlClick,
|
||||
isMobile,
|
||||
userArr
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [popup, setPopup] = useState({
|
||||
popup1: false,
|
||||
popup2: false,
|
||||
});
|
||||
|
||||
const isAdmin = true;
|
||||
useEffect(() => {
|
||||
console.log("test");
|
||||
}, [exitPopup]);
|
||||
|
||||
const isAdmin = false;
|
||||
|
||||
const [selected, setSelected] = useState(false);
|
||||
const [wideSidebar, setWideSidebar] = useState(false);
|
||||
|
||||
const [isMuted, setMuted] = useState(true);
|
||||
|
||||
const handleMuteClick = () => {
|
||||
setMuted((prev) => !prev);
|
||||
};
|
||||
|
||||
function handleClosePopups() {
|
||||
setPopup({
|
||||
popup1: false,
|
||||
@@ -56,7 +63,6 @@ export const Sidebar: React.FC<any> = ({ closeStream }) => {
|
||||
}
|
||||
|
||||
function closeSideBar() {
|
||||
setSelected(false);
|
||||
setOpen(false);
|
||||
setWideSidebar(false);
|
||||
}
|
||||
@@ -81,10 +87,14 @@ export const Sidebar: React.FC<any> = ({ closeStream }) => {
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
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" }}
|
||||
@@ -92,15 +102,15 @@ export const Sidebar: React.FC<any> = ({ closeStream }) => {
|
||||
>
|
||||
<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
|
||||
isAdmin={isAdmin}
|
||||
></UserList>
|
||||
</div>
|
||||
<motion.div
|
||||
onHoverStart={() => setWideSidebar(true)}
|
||||
className="toolbar-field-part"
|
||||
>
|
||||
<WideSidebarButton
|
||||
@@ -109,6 +119,8 @@ export const Sidebar: React.FC<any> = ({ closeStream }) => {
|
||||
></WideSidebarButton>
|
||||
</motion.div>
|
||||
<ControlPanel
|
||||
isControl={isControl}
|
||||
handleControlClick={handleControlClick}
|
||||
isSidebarWide={wideSidebar}
|
||||
isMuted={isMuted}
|
||||
handleMuteClick={handleMuteClick}
|
||||
@@ -153,7 +165,7 @@ export const Sidebar: React.FC<any> = ({ closeStream }) => {
|
||||
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>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
@@ -7,10 +7,17 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.user-container-mobile {
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
background: #1C1D21;
|
||||
border-radius: 4px;
|
||||
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
@@ -29,6 +36,12 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.control-elements {
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
|
||||
}
|
||||
|
||||
.you-caption {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
@@ -54,6 +67,13 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.user-button-container-mobile {
|
||||
background: #1C1D21;
|
||||
padding: 0px 8px 8px 8px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.icon-container {
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
@@ -17,16 +17,19 @@ import {
|
||||
transition,
|
||||
iconAnimation,
|
||||
} from "utils/animationProps";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const User: React.FC<any> = ({
|
||||
isAdmin,
|
||||
isSidebarWide,
|
||||
closeSidebar,
|
||||
isMobile
|
||||
}) => {
|
||||
const [hover, setHover] = useState(false);
|
||||
const [expand, setExpand] = useState(false);
|
||||
const [mute, setMute] = useState(true);
|
||||
const [isControl, setControl] = useState(false);
|
||||
const [isControl, setControl] = useState(true);
|
||||
const {t} = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(false);
|
||||
@@ -45,7 +48,7 @@ export const User: React.FC<any> = ({
|
||||
animate={expand ? "open" : "closed"}
|
||||
className="user-wrapper"
|
||||
>
|
||||
<div onClick={expandMenu} className="user-container">
|
||||
<div onClick={expandMenu} className={isMobile ? "user-container-mobile user-container" : "user-container"}>
|
||||
<div className="user">
|
||||
<motion.div
|
||||
onHoverStart={() => setHover(true)}
|
||||
@@ -64,42 +67,45 @@ export const User: React.FC<any> = ({
|
||||
animate={isSidebarWide ? "show" : "hidden"}
|
||||
className="user-name"
|
||||
>
|
||||
Пользователь
|
||||
{t('user')}
|
||||
</motion.span>
|
||||
</div>
|
||||
{isSidebarWide && (
|
||||
<AnimatePresence>
|
||||
{!isControl && (
|
||||
<div className="control-elements">
|
||||
<AnimatePresence>
|
||||
{!isControl && (
|
||||
<motion.div
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
className="user-control-caption"
|
||||
>
|
||||
<div className="control-circle"></div>
|
||||
<span className="user-control-caption">Управление</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{isAdmin && (
|
||||
<motion.div
|
||||
variants={popupAnimation}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
className="user-control-caption"
|
||||
initial={false}
|
||||
variants={animationButton}
|
||||
animate={isSidebarWide ? "show" : "hidden"}
|
||||
className="icon-container"
|
||||
>
|
||||
<div className="control-circle"></div>
|
||||
<span className="user-control-caption">Управление</span>
|
||||
<motion.img
|
||||
initial={false}
|
||||
transition={transition}
|
||||
variants={iconAnimation}
|
||||
animate={expand ? "open" : "closed"}
|
||||
src={chevronDown}
|
||||
className="arrow-caption"
|
||||
></motion.img>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<motion.div
|
||||
initial={false}
|
||||
variants={animationButton}
|
||||
animate={isSidebarWide ? "show" : "hidden"}
|
||||
className="icon-container"
|
||||
>
|
||||
<motion.img
|
||||
initial={false}
|
||||
transition={transition}
|
||||
variants={iconAnimation}
|
||||
animate={expand ? "open" : "closed"}
|
||||
src={chevronDown}
|
||||
className="arrow-caption"
|
||||
></motion.img>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
{expand && (
|
||||
<AnimatePresence>
|
||||
@@ -108,10 +114,11 @@ export const User: React.FC<any> = ({
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
className="user-button-container"
|
||||
className={isMobile ? "user-button-container user-button-container-mobile" : "user-button-container"}
|
||||
>
|
||||
<div
|
||||
onClick={() => setControl((prev) => !prev)}
|
||||
style={{ height: "40px" }}
|
||||
className="button button-secondary"
|
||||
>
|
||||
<img src={isControl ? control : controlOff}></img>
|
||||
@@ -120,12 +127,13 @@ export const User: React.FC<any> = ({
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{width: "40px", padding: "16px"}}
|
||||
style={{ width: "40px", height: "40px", padding: "16px" }}
|
||||
onClick={() => setMute((prev) => !prev)}
|
||||
className="button button-teritary"
|
||||
>
|
||||
<img
|
||||
alt="иконка звук выкл"
|
||||
|
||||
src={mute ? microOn : microOff}
|
||||
className="mic"
|
||||
></img>
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Button } from "components/shared/Button/Button";
|
||||
import more from "images/icons/More.svg";
|
||||
|
||||
|
||||
export const AdditionalButton: React.FC<any> = ({ active, onClick }) => {
|
||||
const button = {
|
||||
icon: more,
|
||||
inactive: "",
|
||||
active: "",
|
||||
type: "",
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={active ? { background: "#2E3038" } : { background: "transparent" }}
|
||||
className="toolbar-button-area"
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
<Button button={button}></Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,11 +6,13 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Button } from "components/shared/Button/Button";
|
||||
|
||||
|
||||
export const ControlButton: React.FC<any> = ({ onClick, isSidebarWide }) => {
|
||||
export const ControlButton: React.FC<any> = ({
|
||||
onClick,
|
||||
isSidebarWide,
|
||||
isActive,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [active, setActive] = useState(false);
|
||||
const [button, setButton] = useState({
|
||||
icon: control,
|
||||
active: "request-control-btn",
|
||||
@@ -19,23 +21,19 @@ export const ControlButton: React.FC<any> = ({ onClick, isSidebarWide }) => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setButton({ ...button, icon: active ? control : controlOff });
|
||||
}, [active]);
|
||||
setButton({ ...button, icon: isActive ? control : controlOff });
|
||||
}, [isActive]);
|
||||
|
||||
function handleClick() {
|
||||
onClick();
|
||||
setActive((prev) => !prev);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="toolbar-button-area"
|
||||
>
|
||||
<div style={{pointerEvents: "none"}} onClick={handleClick} className="toolbar-button-area">
|
||||
<Button
|
||||
isSidebarWide={isSidebarWide}
|
||||
button={button}
|
||||
active={active}
|
||||
active={isActive}
|
||||
></Button>
|
||||
</div>
|
||||
);
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Button } from "components/shared/Button/Button";
|
||||
|
||||
export const FullscreenButton: React.FC<any> = ({ isSidebarWide }) => {
|
||||
|
||||
|
||||
const [active, setActive] = useState(Boolean(document.fullscreenElement));
|
||||
const [button, setButton] = useState({
|
||||
icon: fullscreen,
|
||||
@@ -46,7 +46,8 @@ export const FullscreenButton: React.FC<any> = ({ isSidebarWide }) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} onClick={handleClick} className="toolbar-button-area">
|
||||
<div onFocus={(e) => e.target.blur()}
|
||||
tabIndex={-1} onClick={handleClick} className="toolbar-button-area">
|
||||
<Button
|
||||
isSidebarWide={isSidebarWide}
|
||||
button={button}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 19C2.44772 19 2 18.5523 2 18V7C2 6.44772 2.44772 6 3 6H8.82201C9.28827 6 9.66624 6.37798 9.66624 6.84424C9.66624 7.3105 9.28827 7.68848 8.82201 7.68848H4.34491C4.06877 7.68848 3.84491 7.91233 3.84491 8.18848V11.1494C3.84491 11.4256 4.06877 11.6494 4.34491 11.6494H8.42515C8.88966 11.6494 9.26621 12.026 9.26621 12.4905C9.26621 12.955 8.88966 13.3315 8.42515 13.3315H4.34491C4.06877 13.3315 3.84491 13.5554 3.84491 13.8315V16.8115C3.84491 17.0877 4.06877 17.3115 4.34491 17.3115H8.89365C9.35991 17.3115 9.73789 17.6895 9.73789 18.1558C9.73789 18.622 9.35991 19 8.89365 19H3Z" fill="#C5C7CE"/>
|
||||
<path d="M21.0865 6C21.591 6 22 6.40899 22 6.9135V18C22 18.5523 21.5523 19 21 19H20.8462C20.5082 19 20.193 18.8293 20.0085 18.5461L14.1107 9.49922C14.0972 9.47838 14.074 9.46582 14.0491 9.46582C14.0085 9.46582 13.9755 9.49876 13.9755 9.5394V18.0775C13.9755 18.587 13.5625 19 13.0531 19C12.5436 19 12.1306 18.587 12.1306 18.0775V7C12.1306 6.44772 12.5783 6 13.1306 6H13.2963C13.6343 6 13.9495 6.1708 14.1341 6.45403L20.0378 15.5135C20.0514 15.5343 20.0746 15.5469 20.0994 15.5469C20.1401 15.5469 20.173 15.5139 20.173 15.4733V6.9135C20.173 6.40899 20.582 6 21.0865 6Z" fill="#C5C7CE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,77 @@
|
||||
import en from "./EN.svg";
|
||||
import ru from "./RU.svg";
|
||||
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { useAppDispatch, useAppSelector } from "hooks/redux";
|
||||
import { languageSlice } from "store/reducers/languageSlice";
|
||||
|
||||
import { Button } from "components/shared/Button/Button";
|
||||
|
||||
const container = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
transition: { duration: 0.2, ease: "easeOut" },
|
||||
},
|
||||
show: {
|
||||
opacity: 1,
|
||||
transition: { delay: 0.15, duration: 0.2, ease: "easeIn" },
|
||||
},
|
||||
exit: {
|
||||
opacity: 0,
|
||||
transition: { duration: 0.2, ease: "easeOut" },
|
||||
},
|
||||
};
|
||||
|
||||
export const LanguageButton: React.FC<any> = ({ hover, setHover, isSidebarWide }) => {
|
||||
const { handleChangeLanguage } = languageSlice.actions;
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [button, setButton] = useState({
|
||||
icon: currentLang === 'en' ? en : ru,
|
||||
active: "language-control-btn",
|
||||
inactive: "language-control-btn",
|
||||
type: "language",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setButton({
|
||||
icon: currentLang === 'en' ? en : ru,
|
||||
active: "language-control-btn",
|
||||
inactive: "language-control-btn",
|
||||
type: "language",
|
||||
})
|
||||
}, [currentLang])
|
||||
|
||||
|
||||
const handleClick = () => {
|
||||
if (currentLang === 'en') {
|
||||
dispatch(handleChangeLanguage('ru'))
|
||||
} else {
|
||||
dispatch(handleChangeLanguage('en'))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div onClick={handleClick} className="toolbar-button-area">
|
||||
<Button isSidebarWide={isSidebarWide} button={button}></Button>
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial={"hidden"}
|
||||
animate={"show"}
|
||||
exit={"hidden"}
|
||||
>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.89773 18.7877C2.40193 18.7877 2 18.3858 2 17.89V7C2 6.44771 2.44772 6 3 6H6.24172C7.16366 6 7.92872 6.17067 8.5369 6.51201C9.14894 6.85335 9.60604 7.32581 9.90819 7.9294C10.2103 8.52882 10.3614 9.2219 10.3614 10.0086C10.3614 10.7912 10.2084 11.4801 9.90238 12.0754C9.60023 12.6665 9.14313 13.1265 8.53109 13.4553C7.92291 13.7842 7.15785 13.9486 6.23591 13.9486H3.85311C3.39447 13.9486 3.02266 13.5768 3.02266 13.1182V13.1182C3.02266 12.6595 3.39447 12.2877 3.85311 12.2877H6.07321C6.65427 12.2877 7.12686 12.1982 7.49099 12.0192C7.859 11.8402 8.12822 11.5801 8.29866 11.2387C8.46911 10.8974 8.55433 10.4874 8.55433 10.0086C8.55433 9.52578 8.46717 9.10743 8.29285 8.7536C8.12241 8.39978 7.85319 8.1292 7.48518 7.94188C7.12105 7.7504 6.64265 7.65466 6.04997 7.65466H4.29547C4.01933 7.65466 3.79547 7.87852 3.79547 8.15466V17.89C3.79547 18.3858 3.39354 18.7877 2.89773 18.7877V18.7877ZM7.32848 13.0183C7.66354 13.0183 7.97028 13.2062 8.12241 13.5047L10.1411 17.4661C10.4493 18.0708 10.01 18.7877 9.3313 18.7877V18.7877C8.9869 18.7877 8.67209 18.5931 8.51818 18.285L6.53132 14.3075C6.23536 13.7151 6.66621 13.0183 7.32848 13.0183V13.0183Z" fill="#C5C7CE"/>
|
||||
<path d="M20.1987 6.90064C20.1987 6.40323 20.602 6 21.0994 6V6C21.5968 6 22 6.40323 22 6.90064V14.4107C22 15.3056 21.8044 16.0986 21.4131 16.7896C21.0219 17.4765 20.4718 18.0176 19.7629 18.4131C19.054 18.8044 18.2231 19 17.2702 19C16.3211 19 15.4922 18.8044 14.7833 18.4131C14.0744 18.0176 13.5243 17.4765 13.1331 16.7896C12.7418 16.0986 12.5462 15.3056 12.5462 14.4107V6.89773C12.5462 6.40193 12.9481 6 13.4439 6V6C13.9397 6 14.3417 6.40193 14.3417 6.89773V14.2546C14.3417 14.8332 14.4598 15.3473 14.6961 15.7968C14.9363 16.2464 15.2752 16.6002 15.713 16.8583C16.1507 17.1122 16.6698 17.2392 17.2702 17.2392C17.8745 17.2392 18.3955 17.1122 18.8332 16.8583C19.2748 16.6002 19.6119 16.2464 19.8443 15.7968C20.0806 15.3473 20.1987 14.8332 20.1987 14.2546V6.90064Z" fill="#C5C7CE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -8,7 +8,7 @@ import { Button } from "components/shared/Button/Button";
|
||||
|
||||
export const MicroButton: React.FC<any> = ({
|
||||
onClick,
|
||||
isMuted,
|
||||
isActive,
|
||||
isSidebarWide,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -19,27 +19,20 @@ export const MicroButton: React.FC<any> = ({
|
||||
type: "microphone",
|
||||
});
|
||||
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setButton({ ...button, icon: active ? microOn : microOff });
|
||||
}, [active]);
|
||||
setButton({ ...button, icon: isActive ? microOn : microOff });
|
||||
}, [isActive]);
|
||||
|
||||
function handleClick() {
|
||||
onClick();
|
||||
setActive((prev) => !prev);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onClick={handleClick}
|
||||
className="toolbar-button-area"
|
||||
>
|
||||
<div style={{pointerEvents: "none"}} tabIndex={-1} onClick={handleClick} className="toolbar-button-area">
|
||||
<Button
|
||||
isSidebarWide={isSidebarWide}
|
||||
button={button}
|
||||
active={isMuted}
|
||||
active={isActive}
|
||||
></Button>
|
||||
</div>
|
||||
);
|
||||
@@ -20,7 +20,6 @@ export const ShareButton: React.FC<any> = ({ onClick, isSidebarWide }) => {
|
||||
|
||||
return (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
onClick={handleClick}
|
||||
className="toolbar-button-area"
|
||||
>
|
||||
@@ -0,0 +1,20 @@
|
||||
import persons from "images/icons/Persons.svg";
|
||||
import { Button } from "components/shared/Button/Button";
|
||||
|
||||
export const UserButtonMobile: React.FC<any> = ({ active, onClick }) => {
|
||||
const button = {
|
||||
icon: persons,
|
||||
inactive: "",
|
||||
active: "",
|
||||
type: "",
|
||||
};
|
||||
return (
|
||||
<div
|
||||
style={active ? { background: "#2E3038" } : { background: "transparent" }}
|
||||
className="toolbar-button-area"
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
<Button button={button}></Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
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>)
|
||||
}
|
||||
@@ -1,32 +1,25 @@
|
||||
import "../PopupShare/sharePopup.css";
|
||||
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 (
|
||||
<div className="popup exit-popup-container popup">
|
||||
<div className="popup-position-container">
|
||||
<div className="mobile-users-part-header">
|
||||
<span className="mobile-users-part-header-title exit-popup-button-title">
|
||||
{t("popup-control-exit-title")}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setClose()}
|
||||
className="mobile-users-part-header-close-button"
|
||||
></button>
|
||||
</div>
|
||||
<div className="popup-container-position">
|
||||
<h2 className="mobile-users-part-header-title">
|
||||
{t("popup-control-exit-title")}
|
||||
</h2>
|
||||
<div className="exit-popup-button-container">
|
||||
<button onClick={() => setClose()} className="button button-primary">
|
||||
{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>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import "./sharePopup.css";
|
||||
import close from 'images/icons/close.svg'
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const PopupShare: React.FC<any> = ({ setClose }) => {
|
||||
const [copy, setCopy] = useState(false);
|
||||
|
||||
function copyLink() {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
setCopy((prev) => !prev);
|
||||
}
|
||||
|
||||
function closePopup() {
|
||||
setClose();
|
||||
setCopy(false);
|
||||
}
|
||||
|
||||
useEffect(() => () => setCopy(false), []);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="share-popup-container popup">
|
||||
|
||||
<div className="popup-container-position">
|
||||
<div onClick={closePopup} className="icon-close-container">
|
||||
<img src={close}></img>
|
||||
</div>
|
||||
<div className="mobile-users-part-header share-header-popup">
|
||||
<h2 className="mobile-users-part-header-title">
|
||||
{t("popup-control-invite-title")}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="share-popup-data-container">
|
||||
<h3 className="share-popup-data-title">
|
||||
{t("popup-control-link")}
|
||||
</h3>
|
||||
<input
|
||||
className="share-popup-data-input href"
|
||||
value={window.location.href}
|
||||
readOnly
|
||||
></input>
|
||||
</div>
|
||||
<div className="share-popup-data-container">
|
||||
{!copy ? (
|
||||
<button 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 onClick={copyLink} className="button button-teritary">
|
||||
<span className="share-popup-copied-button-icon"></span>
|
||||
<span className="">{t("popup-control-btn-active")}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</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 |
@@ -1,7 +1,5 @@
|
||||
.share-popup-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background: #151619;
|
||||
@@ -10,7 +8,26 @@
|
||||
width: 400px;
|
||||
color: #ffffff;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.icon-close-container {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
box-sizing: border-box;
|
||||
top: -40px;
|
||||
right: -40px;
|
||||
|
||||
}
|
||||
|
||||
.popup-container-position {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
|
||||
}
|
||||
|
||||
.share-header-popup {
|
||||
@@ -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 [hover, setHover] = useState(false);
|
||||
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -27,11 +24,7 @@ export const Button: React.FC<any> = ({ button, active, isSidebarWide }) => {
|
||||
: "toolbar-button"
|
||||
}
|
||||
>
|
||||
{button.type === "language" ? (
|
||||
<span className="language-caption">{currentLang.toUpperCase()}</span>
|
||||
) : (
|
||||
<img alt="icon" className="toolbar-icon" src={button.icon} />
|
||||
)}
|
||||
<img alt="icon" className="toolbar-icon" src={button.icon} />
|
||||
</motion.div>
|
||||
<motion.span
|
||||
initial={false}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,11 +1,48 @@
|
||||
|
||||
.header-container {
|
||||
.header {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 30px 40px;
|
||||
color: #ffffff;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-popup {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.header-button-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.button-lang {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
|
||||
.button-lang:hover {
|
||||
background: #23242A;
|
||||
}
|
||||
|
||||
.button-lang-active {
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
background: #2E3038;
|
||||
|
||||
}
|
||||
|
||||
.header__buttons_wrapper {
|
||||
@@ -19,29 +56,15 @@
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 31px;
|
||||
height: 50px;
|
||||
|
||||
width: 104px;
|
||||
height: 40px;
|
||||
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
margin-right: 96px;
|
||||
}
|
||||
|
||||
.header-button {
|
||||
font-family: "GilroyWebRegular";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 125%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
padding: 0px;
|
||||
appearance: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.header-lang-button-text:hover {
|
||||
background: rgba(235, 235, 235, 0.2);
|
||||
@@ -159,15 +182,10 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1600px) {
|
||||
.header-container {
|
||||
.header {
|
||||
padding: 35px 48px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
width: 46px;
|
||||
height: 74px;
|
||||
}
|
||||
|
||||
.header-button {
|
||||
font-size: 20px;
|
||||
|
||||
@@ -177,7 +195,7 @@
|
||||
height: auto;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.header-buttons {
|
||||
gap: 48px;
|
||||
}
|
||||
@@ -186,25 +204,25 @@
|
||||
width: 70px;
|
||||
padding: 7px 15px;
|
||||
top: 57px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 1279px) {
|
||||
.header-container {
|
||||
.header {
|
||||
padding: 30px 12px;
|
||||
}
|
||||
|
||||
.header-lang-button {
|
||||
top: 40px;
|
||||
right: 12px;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.header-container {
|
||||
.header {
|
||||
padding: 15px 12px;
|
||||
}
|
||||
|
||||
@@ -216,11 +234,18 @@
|
||||
|
||||
|
||||
@media screen and (max-width: 639px) {
|
||||
.header {
|
||||
background: #262626;
|
||||
|
||||
.header-logo {
|
||||
width: 94px;
|
||||
height: 36px;
|
||||
|
||||
}
|
||||
|
||||
.header-popup {
|
||||
position: static;
|
||||
}
|
||||
.header-container {
|
||||
|
||||
.header {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import "./Header.css";
|
||||
import "styles/styles.css";
|
||||
|
||||
import logo from "./logoIcon.svg";
|
||||
import chevron from "./chevronIcon.svg";
|
||||
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";
|
||||
@@ -16,110 +17,24 @@ export type THeader = {
|
||||
};
|
||||
|
||||
export const Header: React.FC = ({ }) => {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [opacity, setOpacity] = useState(false);
|
||||
const buttons = [{
|
||||
icon: ru, value: "ru"
|
||||
}, { icon: en, value: "en" }]
|
||||
const location = useLocation();
|
||||
|
||||
const { handleChangeLanguage } = languageSlice.actions;
|
||||
const { currentLang } = useAppSelector((state) => state.languageReducer);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
function handleUpdateLanguage(value: string) {
|
||||
dispatch(handleChangeLanguage(value));
|
||||
setOpen((prev) => !prev);
|
||||
}
|
||||
|
||||
function handleOpen() {
|
||||
setOpen((prev) => !prev);
|
||||
}
|
||||
|
||||
function handleOpacity() {
|
||||
setOpacity(true);
|
||||
}
|
||||
|
||||
const style = {
|
||||
background: "rgba(235, 235, 235, 0.2",
|
||||
};
|
||||
|
||||
const iconTransform = {
|
||||
transform: "rotate(-90deg)",
|
||||
} as CSSProperties;
|
||||
|
||||
const setBackground = opacity && (!open as boolean);
|
||||
|
||||
function setOpacityAndMenu() {
|
||||
setMenuOpen(true);
|
||||
const targetElement = document.querySelector("body") as HTMLElement;
|
||||
targetElement.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
function openMenu() {
|
||||
setMenuOpen(false);
|
||||
const targetElement = document.querySelector("body") as HTMLElement;
|
||||
targetElement.style.overflow = "visible";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
location.pathname === "connect-page" ? "header" : "header-main"
|
||||
}
|
||||
>
|
||||
<div className="header-container">
|
||||
<a target='_blank' href="https://estate.graff.tech/" style={{cursor: "pointer"}}><img className="header-logo" alt="company-logo" src={logo} />
|
||||
</a>
|
||||
<div className="header-buttons">
|
||||
<div
|
||||
className={
|
||||
open
|
||||
? "header-lang-button header-lang-button_active"
|
||||
: "header-lang-button header-lang-button_disabled"
|
||||
}
|
||||
style={setBackground ? style : { background: "transparent" }}
|
||||
>
|
||||
<img
|
||||
alt="img"
|
||||
src={chevron}
|
||||
className="header-select__icon"
|
||||
style={
|
||||
open
|
||||
? iconTransform
|
||||
: {
|
||||
transform: "rotate(0deg)",
|
||||
}
|
||||
}
|
||||
></img>
|
||||
<div className="wrapper__button">
|
||||
<div
|
||||
onMouseEnter={handleOpacity}
|
||||
onMouseLeave={() => setOpacity(false)}
|
||||
onClick={handleOpen}
|
||||
className=" header-lang-button-picked"
|
||||
>
|
||||
{currentLang.toLocaleUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
{currentLang === "ru" ? (
|
||||
<button
|
||||
value={"en"}
|
||||
onClick={(e: any) => handleUpdateLanguage(e.target.value)}
|
||||
className="header-lang-button-text"
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={(e: any) => handleUpdateLanguage(e.target.value)}
|
||||
value={"ru"}
|
||||
className="header-lang-button-text"
|
||||
>
|
||||
RU
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<header className={location.pathname === '/' ? 'header' : 'header header-popup'}>
|
||||
<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>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,4 +1,3 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src:
|
||||
@@ -44,3 +43,12 @@
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'GilroyWebRegular';
|
||||
src: url('./Gilroy_Regular.woff'),
|
||||
url('./Gilroy_Regular.woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
interface IForm {
|
||||
name: string;
|
||||
email: string
|
||||
}
|
||||
|
||||
export default function useFormWithValidation() {
|
||||
const [values, setValues] = useState<IForm>({
|
||||
name: "",
|
||||
email: ""
|
||||
});
|
||||
const [errors, setErrors] = useState<IForm>({
|
||||
name: "",
|
||||
email: ""
|
||||
});
|
||||
const [isValid, setIsValid] = useState<boolean>(false);
|
||||
|
||||
const handleChangeState = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const target = event.target;
|
||||
const name = target.name;
|
||||
const value = target.value;
|
||||
setValues({ ...values, [name]: value });
|
||||
setErrors({ ...errors, [name]: target.validationMessage });
|
||||
setIsValid(target.closest("form").checkValidity());
|
||||
};
|
||||
|
||||
const resetForm = useCallback(
|
||||
(newValues = { name: "", email: "" }, newIsValid = false) => {
|
||||
setValues(newValues);
|
||||
setErrors(newValues);
|
||||
setIsValid(newIsValid);
|
||||
},
|
||||
[setValues, setErrors, setIsValid]
|
||||
);
|
||||
|
||||
return { values, handleChangeState, errors, isValid, resetForm };
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function useDeviceDetect() {
|
||||
const [isMobile, setMobile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const userAgent =
|
||||
typeof window.navigator === "undefined" ? "" : navigator.userAgent;
|
||||
const mobile = Boolean(
|
||||
userAgent.match(
|
||||
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
|
||||
)
|
||||
);
|
||||
setMobile(mobile);
|
||||
}, []);
|
||||
|
||||
return { isMobile };
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
function getWindowDimensions() {
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
export default function useWindowDimensions() {
|
||||
const [windowDimensions, setWindowDimensions] = useState(
|
||||
getWindowDimensions()
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowDimensions(getWindowDimensions());
|
||||
}
|
||||
window.addEventListener("orientationchange", handleResize)
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
window.removeEventListener('orientationchange', handleResize)
|
||||
}
|
||||
}, []);
|
||||
|
||||
return windowDimensions;
|
||||
}
|
||||
|
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,4 @@
|
||||
<svg width="20" height="13" viewBox="0 0 20 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 13C0.447716 13 0 12.5523 0 12V1C0 0.447715 0.447715 0 1 0H6.82201C7.28827 0 7.66624 0.377978 7.66624 0.844238V0.844238C7.66624 1.3105 7.28827 1.68848 6.82201 1.68848H2.34491C2.06877 1.68848 1.84491 1.91233 1.84491 2.18848V5.14941C1.84491 5.42556 2.06877 5.64941 2.34491 5.64941H6.42515C6.88966 5.64941 7.26621 6.02597 7.26621 6.49048V6.49048C7.26621 6.95499 6.88966 7.33154 6.42515 7.33154H2.34491C2.06877 7.33154 1.84491 7.5554 1.84491 7.83154V10.8115C1.84491 11.0877 2.06877 11.3115 2.34491 11.3115H6.89365C7.35991 11.3115 7.73789 11.6895 7.73789 12.1558V12.1558C7.73789 12.622 7.35991 13 6.89365 13H1Z" fill="#F2F2F2"/>
|
||||
<path d="M19.0865 0C19.591 0 20 0.408988 20 0.913501V12C20 12.5523 19.5523 13 19 13H18.8462C18.5082 13 18.193 12.8293 18.0085 12.5461L12.1107 3.49922C12.0972 3.47838 12.074 3.46582 12.0491 3.46582V3.46582C12.0085 3.46582 11.9755 3.49876 11.9755 3.5394V12.0775C11.9755 12.587 11.5625 13 11.0531 13V13C10.5436 13 10.1306 12.587 10.1306 12.0775V1C10.1306 0.447715 10.5783 0 11.1306 0H11.2963C11.6343 0 11.9495 0.170802 12.1341 0.454032L18.0378 9.51347C18.0514 9.53431 18.0746 9.54687 18.0994 9.54687V9.54687C18.1401 9.54687 18.173 9.51394 18.173 9.4733V0.913501C18.173 0.408988 18.582 0 19.0865 0V0Z" fill="#F2F2F2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 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="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 |
|
Before Width: | Height: | Size: 529 B After Width: | Height: | Size: 529 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 |