commit
48b856f836
10
.github/workflows/build-and-deploy.yml
vendored
10
.github/workflows/build-and-deploy.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
# Create a slugified value of the branch
|
# Create a slugified value of the branch
|
||||||
- uses: rlespinasse/github-slug-action@1.1.1
|
- uses: rlespinasse/github-slug-action@3.1.0
|
||||||
|
|
||||||
- name: "Build and push front image"
|
- name: "Build and push front image"
|
||||||
uses: docker/build-push-action@v1
|
uses: docker/build-push-action@v1
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# Create a slugified value of the branch
|
# Create a slugified value of the branch
|
||||||
- uses: rlespinasse/github-slug-action@1.1.1
|
- uses: rlespinasse/github-slug-action@3.1.0
|
||||||
|
|
||||||
- name: "Build and push back image"
|
- name: "Build and push back image"
|
||||||
uses: docker/build-push-action@v1
|
uses: docker/build-push-action@v1
|
||||||
@ -66,7 +66,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# Create a slugified value of the branch
|
# Create a slugified value of the branch
|
||||||
- uses: rlespinasse/github-slug-action@1.1.1
|
- uses: rlespinasse/github-slug-action@3.1.0
|
||||||
|
|
||||||
- name: "Build and push back image"
|
- name: "Build and push back image"
|
||||||
uses: docker/build-push-action@v1
|
uses: docker/build-push-action@v1
|
||||||
@ -90,7 +90,7 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
# Create a slugified value of the branch
|
# Create a slugified value of the branch
|
||||||
- uses: rlespinasse/github-slug-action@1.1.1
|
- uses: rlespinasse/github-slug-action@3.1.0
|
||||||
|
|
||||||
- name: "Build and push front image"
|
- name: "Build and push front image"
|
||||||
uses: docker/build-push-action@v1
|
uses: docker/build-push-action@v1
|
||||||
@ -114,7 +114,7 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# Create a slugified value of the branch
|
# Create a slugified value of the branch
|
||||||
- uses: rlespinasse/github-slug-action@1.1.0
|
- uses: rlespinasse/github-slug-action@3.1.0
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
uses: thecodingmachine/deeployer@master
|
uses: thecodingmachine/deeployer@master
|
||||||
|
1
front/.gitignore
vendored
1
front/.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/dist/bundle.js
|
/dist/bundle.js
|
||||||
|
/dist/tests/
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/dist/webpack.config.js
|
/dist/webpack.config.js
|
||||||
/dist/webpack.config.js.map
|
/dist/webpack.config.js.map
|
||||||
|
6
front/dist/index.html
vendored
6
front/dist/index.html
vendored
@ -72,7 +72,11 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="cowebsite" class="cowebsite hidden"></div>
|
<div id="cowebsite" class="cowebsite hidden">
|
||||||
|
<button class="close-btn" id="cowebsite-close">
|
||||||
|
<img src="resources/logos/close.svg"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="audio-playing">
|
<div class="audio-playing">
|
||||||
<img src="/resources/logos/megaphone.svg"/>
|
<img src="/resources/logos/megaphone.svg"/>
|
||||||
</div>
|
</div>
|
||||||
|
77
front/dist/resources/logos/boy.svg
vendored
Normal file
77
front/dist/resources/logos/boy.svg
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 188.149 188.149" style="enable-background:new 0 0 188.149 188.149;" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<defs>
|
||||||
|
<circle id="SVGID_1_" cx="94.075" cy="94.075" r="94.074"/>
|
||||||
|
</defs>
|
||||||
|
<use xlink:href="#SVGID_1_" style="overflow:visible;fill:#4AC8EB;"/>
|
||||||
|
<clipPath id="SVGID_2_">
|
||||||
|
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||||
|
</clipPath>
|
||||||
|
<g style="clip-path:url(#SVGID_2_);">
|
||||||
|
<path style="fill:#A57561;" d="M148.572,197.629v0.01H39.507v-0.01c0-15.124,6.147-28.809,16.09-38.679
|
||||||
|
c0.463-0.463,0.926-0.905,1.408-1.347c1.43-1.326,2.931-2.57,4.493-3.732c1.028-0.771,2.098-1.491,3.177-2.189
|
||||||
|
c1.08-0.699,2.19-1.357,3.331-1.975c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761
|
||||||
|
c1.295-0.761,2.519-1.624,3.69-2.56c4.966-3.948,8.812-9.254,11.001-15.34v-0.011c1.306-3.629,2.016-7.546,2.016-11.639
|
||||||
|
l16.121,0.072c0,4.04,0.688,7.927,1.964,11.525c2.169,6.117,5.994,11.433,10.96,15.401c0.339,0.277,0.688,0.545,1.038,0.802
|
||||||
|
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.257,0.853,0.514,1.285,0.75
|
||||||
|
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.02,0.062,0.041,0.093,0.061c1.265,0.699,2.498,1.439,3.69,2.221
|
||||||
|
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
|
||||||
|
z"/>
|
||||||
|
<path style="fill:#EB6D4A;" d="M148.572,197.629H39.507c0-15.124,6.147-28.809,16.09-38.679c0.463-0.463,0.926-0.905,1.408-1.347
|
||||||
|
c1.43-1.326,2.931-2.581,4.493-3.742c1.028-0.762,2.098-1.491,3.177-2.18c1.08-0.699,2.19-1.357,3.331-1.975
|
||||||
|
c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761c1.295-0.761,2.519-1.624,3.69-2.56
|
||||||
|
c5.347,5.469,12.79,8.852,21.046,8.852c8.226,0,15.669-3.393,21.016-8.842c0.339,0.277,0.688,0.545,1.038,0.802
|
||||||
|
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.267,0.853,0.514,1.285,0.75
|
||||||
|
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.011,0.062,0.031,0.093,0.052c1.265,0.699,2.498,1.439,3.69,2.23
|
||||||
|
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
|
||||||
|
z"/>
|
||||||
|
<path style="fill:#A57561;" d="M52.183,46.81v34.117c0,28.977,25.437,52.466,41.857,52.466c16.421,0,41.858-23.489,41.858-52.466
|
||||||
|
V46.81H52.183z"/>
|
||||||
|
<path style="fill:#141720;" d="M52.183,76.823L52.183,76.823c2.063,0,3.734-1.671,3.734-3.733V49.356h-3.734V76.823z"/>
|
||||||
|
<path style="fill:#141720;" d="M135.899,76.823L135.899,76.823V49.356h-3.733V73.09
|
||||||
|
C132.165,75.152,133.836,76.823,135.899,76.823z"/>
|
||||||
|
<path style="fill:#141720;" d="M135.893,48.33c0,4.884-0.328,6.061-3.734,5.801c-0.137-2.367-17.141-4.296-38.111-4.296
|
||||||
|
c-20.985,0-37.989,1.929-38.126,4.296c-3.406,0.26-3.734-0.917-3.734-5.801c0-0.479,0.014-0.984,0.027-1.519
|
||||||
|
c0.52-12.052,7.318-34.582,41.833-34.582c34.5,0,41.299,22.53,41.818,34.582C135.879,47.346,135.893,47.852,135.893,48.33z"/>
|
||||||
|
</g>
|
||||||
|
<path style="clip-path:url(#SVGID_2_);fill:#FFFFFF;" d="M115.106,146.119c-3.517,10.826-10.601,21.539-21.036,30.299
|
||||||
|
c-10.436-8.76-17.509-19.473-21.025-30.299c0.052,0.02,0.113,0.041,0.165,0.061c5.531,4.955,12.852,7.979,20.86,7.979
|
||||||
|
c8.009,0,15.319-3.013,20.851-7.968C114.982,146.17,115.044,146.14,115.106,146.119z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
1
front/dist/resources/logos/discussion.svg
vendored
Normal file
1
front/dist/resources/logos/discussion.svg
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg height="682pt" viewBox="-21 -47 682.66669 682" width="682pt" xmlns="http://www.w3.org/2000/svg"><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-279.152344l-185.183594 128.863281v-128.863281c-48.375-.164063-87.675781-39.574219-87.675781-87.988282v-283.972656c0-48.515625 39.472656-87.988281 87.988281-87.988281h464.023438c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffdb2d"/><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-232.109375v-459.949219h232.109375c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffaa20"/><g fill="#fff"><path d="m171.296875 131.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 211.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 291.167969h297.40625v37.5h-297.40625zm0 0"/></g><path d="m319.902344 131.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 211.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 291.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
255
front/dist/resources/style/style.css
vendored
255
front/dist/resources/style/style.css
vendored
@ -1,6 +1,14 @@
|
|||||||
|
*{
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
}
|
||||||
body{
|
body{
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
body button:focus,
|
||||||
|
body img:focus,
|
||||||
|
body input:focus {
|
||||||
|
outline: -webkit-focus-ring-color auto 0;
|
||||||
|
}
|
||||||
body .message-info{
|
body .message-info{
|
||||||
width: 20%;
|
width: 20%;
|
||||||
height: auto;
|
height: auto;
|
||||||
@ -72,14 +80,16 @@ body .message-info.warning{
|
|||||||
|
|
||||||
#div-myCamVideo {
|
#div-myCamVideo {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 15px;
|
||||||
bottom: 0;
|
bottom: 15px;
|
||||||
|
border-radius: 15px 15px 15px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
video#myCamVideo{
|
video#myCamVideo{
|
||||||
width: 15vw;
|
width: 15vw;
|
||||||
-webkit-transform: scaleX(-1);
|
-webkit-transform: scaleX(-1);
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
|
border-radius: 15px 15px 15px 15px;
|
||||||
/*width: 200px;*/
|
/*width: 200px;*/
|
||||||
/*height: 113px;*/
|
/*height: 113px;*/
|
||||||
}
|
}
|
||||||
@ -274,6 +284,23 @@ body {
|
|||||||
#cowebsite.hidden {
|
#cowebsite.hidden {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cowebsite .close-btn{
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: -100px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
animation: right .2s ease;
|
||||||
|
}
|
||||||
|
#cowebsite .close-btn img{
|
||||||
|
height: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
#cowebsite:hover .close-btn{
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-aspect-ratio: 1/1) {
|
@media (max-aspect-ratio: 1/1) {
|
||||||
.game-overlay {
|
.game-overlay {
|
||||||
@ -372,6 +399,7 @@ body {
|
|||||||
margin: 2%;
|
margin: 2%;
|
||||||
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
|
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: 15px 15px 15px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar > div:hover {
|
.sidebar > div:hover {
|
||||||
@ -384,6 +412,7 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-mode {
|
.chat-mode {
|
||||||
@ -435,7 +464,7 @@ body {
|
|||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
top: -80%;
|
top: -80%;
|
||||||
left: 10%;
|
left: 10%;
|
||||||
background: #000000a6;
|
background: #333333;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
transition: all 0.1s ease-out;
|
transition: all 0.1s ease-out;
|
||||||
}
|
}
|
||||||
@ -532,7 +561,7 @@ body {
|
|||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
background-color: #00000000;
|
background-color: #00000000;
|
||||||
color: #ffda01;
|
color: #ffda01;
|
||||||
border-radius: 10px;
|
border-radius: 15px;
|
||||||
padding: 10px 30px;
|
padding: 10px 30px;
|
||||||
transition: all .2s ease;
|
transition: all .2s ease;
|
||||||
}
|
}
|
||||||
@ -627,6 +656,7 @@ div.modal-report-user{
|
|||||||
left: calc(50% - 400px);
|
left: calc(50% - 400px);
|
||||||
top: 100px;
|
top: 100px;
|
||||||
background-color: #000000ad;
|
background-color: #000000ad;
|
||||||
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-report-user textarea{
|
.modal-report-user textarea{
|
||||||
@ -638,6 +668,7 @@ div.modal-report-user{
|
|||||||
color: white;
|
color: white;
|
||||||
width: calc(100% - 60px);
|
width: calc(100% - 60px);
|
||||||
margin: 30px;
|
margin: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-report-user img{
|
.modal-report-user img{
|
||||||
@ -669,7 +700,7 @@ div.modal-report-user{
|
|||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
background-color: #00000000;
|
background-color: #00000000;
|
||||||
color: #ffda01;
|
color: #ffda01;
|
||||||
border-radius: 10px;
|
border-radius: 15px;
|
||||||
padding: 10px 30px;
|
padding: 10px 30px;
|
||||||
transition: all .2s ease;
|
transition: all .2s ease;
|
||||||
}
|
}
|
||||||
@ -701,3 +732,217 @@ div.modal-report-user{
|
|||||||
max-width: calc(800px - 60px); /* size of modal - padding*/
|
max-width: calc(800px - 60px); /* size of modal - padding*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*MESSAGE*/
|
||||||
|
.discussion{
|
||||||
|
position: fixed;
|
||||||
|
left: -300px;
|
||||||
|
top: 0px;
|
||||||
|
width: 220px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #333333;
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
.discussion.active{
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.discussion .active-btn{
|
||||||
|
display: none;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
background-color: #2d2d2dba;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 25px);
|
||||||
|
margin-left: 315px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
.discussion .active-btn.active{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.discussion .active-btn:hover {
|
||||||
|
transform: scale(1.1) rotateY(3.142rad);
|
||||||
|
}
|
||||||
|
.discussion .active-btn img{
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
margin: 13px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .close-btn{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 10px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.discussion .close-btn img{
|
||||||
|
height: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion p{
|
||||||
|
color: white;
|
||||||
|
font-size: 22px;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants{
|
||||||
|
height: 200px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant{
|
||||||
|
display: flex;
|
||||||
|
margin: 5px 10px;
|
||||||
|
background-color: #ffffff69;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant:hover{
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.discussion .participants .participant:hover p{
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant:before {
|
||||||
|
content: '';
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
background-color: #1e7e34;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant img{
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant p{
|
||||||
|
font-size: 16px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant button.report-btn{
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #2d2d2dba;
|
||||||
|
right: 34px;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 6px 0px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all .5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .participants .participant:hover button.report-btn{
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .messages{
|
||||||
|
position: absolute;
|
||||||
|
height: calc(100% - 360px);
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-width: calc(100% - 40px);
|
||||||
|
width: calc(100% - 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .messages h2{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .messages .message{
|
||||||
|
margin: 5px;
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .messages .message.me{
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .messages .message p{
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .messages .message p.body{
|
||||||
|
font-size: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .send-message{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 45px;
|
||||||
|
width: 220px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .send-message input{
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
height: 20px;
|
||||||
|
background-color: #171717;
|
||||||
|
color: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: none;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discussion .send-message img{
|
||||||
|
position: absolute;
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #ffffff69;
|
||||||
|
}
|
||||||
|
.discussion .send-message img:hover{
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Action button **/
|
||||||
|
div.action{
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
text-align: center;
|
||||||
|
bottom: 40px;
|
||||||
|
transition: all .5s ease;
|
||||||
|
animation: mymove .5s;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
div.action p.action-body{
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #2d2d2dba;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 150px;
|
||||||
|
margin-left: calc(50% - 75px);
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
@keyframes mymove {
|
||||||
|
0% {bottom: 40px;}
|
||||||
|
50% {bottom: 30px;}
|
||||||
|
100% {bottom: 40px;}
|
||||||
|
}
|
@ -12,12 +12,14 @@ const URL_ROOM_STARTED = '/Floor0/floor0.json';
|
|||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!:LocalUser;
|
private localUser!:LocalUser;
|
||||||
|
|
||||||
|
private connexionType?: GameConnexionTypes
|
||||||
/**
|
/**
|
||||||
* Tries to login to the node server and return the starting map url to be loaded
|
* Tries to login to the node server and return the starting map url to be loaded
|
||||||
*/
|
*/
|
||||||
public async initGameConnexion(): Promise<Room> {
|
public async initGameConnexion(): Promise<Room> {
|
||||||
|
|
||||||
const connexionType = urlManager.getGameConnexionType();
|
const connexionType = urlManager.getGameConnexionType();
|
||||||
|
this.connexionType = connexionType;
|
||||||
if(connexionType === GameConnexionTypes.register) {
|
if(connexionType === GameConnexionTypes.register) {
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
||||||
@ -108,6 +110,10 @@ class ConnectionManager {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get getConnexionType(){
|
||||||
|
return this.connexionType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const connectionManager = new ConnectionManager();
|
export const connectionManager = new ConnectionManager();
|
||||||
|
@ -6,10 +6,8 @@ export class Room {
|
|||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private mapUrl: string|undefined;
|
private mapUrl: string|undefined;
|
||||||
private instance: string|undefined;
|
private instance: string|undefined;
|
||||||
public readonly hash: string;
|
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
this.hash = '';
|
|
||||||
if (id.startsWith('/')) {
|
if (id.startsWith('/')) {
|
||||||
id = id.substr(1);
|
id = id.substr(1);
|
||||||
}
|
}
|
||||||
@ -24,12 +22,29 @@ export class Room {
|
|||||||
|
|
||||||
const indexOfHash = this.id.indexOf('#');
|
const indexOfHash = this.id.indexOf('#');
|
||||||
if (indexOfHash !== -1) {
|
if (indexOfHash !== -1) {
|
||||||
const idWithHash = this.id;
|
|
||||||
this.id = this.id.substr(0, indexOfHash);
|
this.id = this.id.substr(0, indexOfHash);
|
||||||
this.hash = idWithHash.substr(indexOfHash + 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
|
||||||
|
let roomId = '';
|
||||||
|
let hash = '';
|
||||||
|
if (!identifier.startsWith('/_/') && !identifier.startsWith('/@/')) { //relative file link
|
||||||
|
const absoluteExitSceneUrl = new URL(identifier, baseUrl);
|
||||||
|
roomId = '_/'+currentInstance+'/'+absoluteExitSceneUrl.hostname + absoluteExitSceneUrl.pathname; //in case of a relative url, we need to create a public roomId
|
||||||
|
hash = absoluteExitSceneUrl.hash;
|
||||||
|
hash = hash.substring(1); //remove the leading diese
|
||||||
|
} else { //absolute room Id
|
||||||
|
const parts = identifier.split('#');
|
||||||
|
roomId = parts[0];
|
||||||
|
roomId = roomId.substring(1); //remove the leading slash
|
||||||
|
if (parts.length > 1) {
|
||||||
|
hash = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {roomId, hash}
|
||||||
|
}
|
||||||
|
|
||||||
public async getMapUrl(): Promise<string> {
|
public async getMapUrl(): Promise<string> {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
if (this.mapUrl !== undefined) {
|
if (this.mapUrl !== undefined) {
|
||||||
|
@ -52,6 +52,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
private closed: boolean = false;
|
private closed: boolean = false;
|
||||||
private tags: string[] = [];
|
private tags: string[] = [];
|
||||||
|
private intervalId!: NodeJS.Timeout;
|
||||||
|
|
||||||
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
|
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
RoomConnection.websocketFactory = websocketFactory;
|
RoomConnection.websocketFactory = websocketFactory;
|
||||||
@ -89,9 +90,13 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.onopen = (ev) => {
|
this.socket.onopen = (ev) => {
|
||||||
//we manually ping every 20s to not be logged out by the server, even when the game is in background.
|
//we manually ping every 20s to not be logged out by the server, even when the game is in background.
|
||||||
const pingMessage = new PingMessage();
|
const pingMessage = new PingMessage();
|
||||||
setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
|
this.intervalId = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
clearTimeout(this.intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
this.socket.onmessage = (messageEvent) => {
|
this.socket.onmessage = (messageEvent) => {
|
||||||
const arrayBuffer: ArrayBuffer = messageEvent.data;
|
const arrayBuffer: ArrayBuffer = messageEvent.data;
|
||||||
const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
||||||
|
@ -39,20 +39,16 @@ export class GameManager {
|
|||||||
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
||||||
const roomID = room.id;
|
const roomID = room.id;
|
||||||
const mapUrl = await room.getMapUrl();
|
const mapUrl = await room.getMapUrl();
|
||||||
console.log('Loading map '+roomID+' at url '+mapUrl);
|
|
||||||
|
|
||||||
const gameIndex = scenePlugin.getIndex(mapUrl);
|
const gameIndex = scenePlugin.getIndex(roomID);
|
||||||
if(gameIndex === -1){
|
if(gameIndex === -1){
|
||||||
const game : Phaser.Scene = GameScene.createFromUrl(room, mapUrl);
|
const game : Phaser.Scene = new GameScene(room, mapUrl);
|
||||||
console.log('Adding scene '+mapUrl);
|
scenePlugin.add(roomID, game, false);
|
||||||
scenePlugin.add(mapUrl, game, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) {
|
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
|
||||||
const url = await this.startRoom.getMapUrl();
|
scenePlugin.start(this.startRoom.id);
|
||||||
console.log('Starting scene '+url);
|
|
||||||
scenePlugin.start(url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,12 @@ import {Queue} from 'queue-typescript';
|
|||||||
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
||||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||||
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character";
|
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character";
|
||||||
import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager";
|
import {
|
||||||
|
CenterListener,
|
||||||
|
layoutManager,
|
||||||
|
LayoutMode,
|
||||||
|
ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES
|
||||||
|
} from "../../WebRtc/LayoutManager";
|
||||||
import Texture = Phaser.Textures.Texture;
|
import Texture = Phaser.Textures.Texture;
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||||
@ -54,6 +59,7 @@ import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMes
|
|||||||
import {ResizableScene} from "../Login/ResizableScene";
|
import {ResizableScene} from "../Login/ResizableScene";
|
||||||
import {Room} from "../../Connexion/Room";
|
import {Room} from "../../Connexion/Room";
|
||||||
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
|
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
|
||||||
|
import {urlManager} from "../../Url/UrlManager";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface|null
|
initPosition: PointInterface|null
|
||||||
@ -89,6 +95,8 @@ interface DeleteGroupEventInterface {
|
|||||||
groupId: number
|
groupId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultStartLayerName = 'start';
|
||||||
|
|
||||||
export class GameScene extends ResizableScene implements CenterListener {
|
export class GameScene extends ResizableScene implements CenterListener {
|
||||||
GameManager : GameManager;
|
GameManager : GameManager;
|
||||||
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
||||||
@ -118,7 +126,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
private createPromise: Promise<void>;
|
private createPromise: Promise<void>;
|
||||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||||
|
|
||||||
MapKey: string;
|
|
||||||
MapUrlFile: string;
|
MapUrlFile: string;
|
||||||
RoomId: string;
|
RoomId: string;
|
||||||
instance: string;
|
instance: string;
|
||||||
@ -132,7 +139,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
y: -1000
|
y: -1000
|
||||||
}
|
}
|
||||||
|
|
||||||
private PositionNextScene: Array<Array<{ key: string, hash: string }>> = new Array<Array<{ key: string, hash: string }>>();
|
|
||||||
private presentationModeSprite!: Sprite;
|
private presentationModeSprite!: Sprite;
|
||||||
private chatModeSprite!: Sprite;
|
private chatModeSprite!: Sprite;
|
||||||
private gameMap!: GameMap;
|
private gameMap!: GameMap;
|
||||||
@ -140,18 +146,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
// The item that can be selected by pressing the space key.
|
// The item that can be selected by pressing the space key.
|
||||||
private outlinedItem: ActionableItem|null = null;
|
private outlinedItem: ActionableItem|null = null;
|
||||||
private userInputManager!: UserInputManager;
|
private userInputManager!: UserInputManager;
|
||||||
|
private startLayerName!: string | null;
|
||||||
|
|
||||||
static createFromUrl(room: Room, mapUrlFile: string, gameSceneKey: string|null = null): GameScene {
|
constructor(private room: Room, MapUrlFile: string) {
|
||||||
// We use the map URL as a key
|
|
||||||
if (gameSceneKey === null) {
|
|
||||||
gameSceneKey = mapUrlFile;
|
|
||||||
}
|
|
||||||
return new GameScene(room, mapUrlFile, gameSceneKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private room: Room, MapUrlFile: string, gameSceneKey: string) {
|
|
||||||
super({
|
super({
|
||||||
key: gameSceneKey
|
key: room.id
|
||||||
});
|
});
|
||||||
|
|
||||||
this.GameManager = gameManager;
|
this.GameManager = gameManager;
|
||||||
@ -159,7 +158,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.groups = new Map<number, Sprite>();
|
this.groups = new Map<number, Sprite>();
|
||||||
this.instance = room.getInstance();
|
this.instance = room.getInstance();
|
||||||
|
|
||||||
this.MapKey = MapUrlFile;
|
|
||||||
this.MapUrlFile = MapUrlFile;
|
this.MapUrlFile = MapUrlFile;
|
||||||
this.RoomId = room.id;
|
this.RoomId = room.id;
|
||||||
|
|
||||||
@ -178,15 +176,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
file: file.src
|
file: file.src
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.load.on('filecomplete-tilemapJSON-'+this.MapKey, (key: string, type: string, data: unknown) => {
|
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
|
||||||
this.onMapLoad(data);
|
this.onMapLoad(data);
|
||||||
});
|
});
|
||||||
//TODO strategy to add access token
|
//TODO strategy to add access token
|
||||||
this.load.tilemapTiledJSON(this.MapKey, this.MapUrlFile);
|
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
||||||
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
|
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
|
||||||
// In this case, we check in the cache to see if the map is here and trigger the event manually.
|
// In this case, we check in the cache to see if the map is here and trigger the event manually.
|
||||||
if (this.cache.tilemap.exists(this.MapKey)) {
|
if (this.cache.tilemap.exists(this.MapUrlFile)) {
|
||||||
const data = this.cache.tilemap.get(this.MapKey);
|
const data = this.cache.tilemap.get(this.MapUrlFile);
|
||||||
this.onMapLoad(data);
|
this.onMapLoad(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,14 +295,16 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
//hook initialisation
|
//hook initialisation
|
||||||
init(initData : GameSceneInitInterface) {
|
init(initData : GameSceneInitInterface) {
|
||||||
if (initData.initPosition !== undefined) {
|
if (initData.initPosition !== undefined) {
|
||||||
this.initPosition = initData.initPosition;
|
this.initPosition = initData.initPosition; //todo: still used?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//hook create scene
|
//hook create scene
|
||||||
create(): void {
|
create(): void {
|
||||||
|
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||||
|
|
||||||
//initalise map
|
//initalise map
|
||||||
this.Map = this.add.tilemap(this.MapKey);
|
this.Map = this.add.tilemap(this.MapUrlFile);
|
||||||
this.gameMap = new GameMap(this.mapFile);
|
this.gameMap = new GameMap(this.mapFile);
|
||||||
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
|
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
|
||||||
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
||||||
@ -314,25 +314,21 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
//permit to set bound collision
|
//permit to set bound collision
|
||||||
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||||
|
|
||||||
// Let's alter browser history
|
|
||||||
let path = this.room.id;
|
|
||||||
if (this.room.hash) {
|
|
||||||
path += '#'+this.room.hash;
|
|
||||||
}
|
|
||||||
window.history.pushState({}, 'WorkAdventure', path);
|
|
||||||
|
|
||||||
//add layer on map
|
//add layer on map
|
||||||
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
|
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
|
||||||
let depth = -2;
|
let depth = -2;
|
||||||
for (const layer of this.mapFile.layers) {
|
for (const layer of this.mapFile.layers) {
|
||||||
if (layer.type === 'tilelayer') {
|
if (layer.type === 'tilelayer') {
|
||||||
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
||||||
|
|
||||||
|
const exitSceneUrl = this.getExitSceneUrl(layer);
|
||||||
|
if (exitSceneUrl !== undefined) {
|
||||||
|
this.loadNextGame(exitSceneUrl);
|
||||||
|
}
|
||||||
|
const exitUrl = this.getExitUrl(layer);
|
||||||
|
if (exitUrl !== undefined) {
|
||||||
|
this.loadNextGame(exitUrl);
|
||||||
}
|
}
|
||||||
if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) {
|
|
||||||
this.loadNextGameFromExitSceneUrl(layer, this.mapFile.width);
|
|
||||||
} else if (layer.type === 'tilelayer' && this.getExitUrl(layer) !== undefined) {
|
|
||||||
console.log('Loading exitUrl ', this.getExitUrl(layer))
|
|
||||||
this.loadNextGameFromExitUrl(layer, this.mapFile.width);
|
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||||
depth = 10000;
|
depth = 10000;
|
||||||
@ -352,6 +348,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
//create input to move
|
//create input to move
|
||||||
this.userInputManager = new UserInputManager(this);
|
this.userInputManager = new UserInputManager(this);
|
||||||
|
mediaManager.setUserInputManager(this.userInputManager);
|
||||||
|
|
||||||
//notify game manager can to create currentUser in map
|
//notify game manager can to create currentUser in map
|
||||||
this.createCurrentPlayer();
|
this.createCurrentPlayer();
|
||||||
@ -431,35 +428,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
// From now, this game scene will be notified of reposition events
|
// From now, this game scene will be notified of reposition events
|
||||||
layoutManager.setListener(this);
|
layoutManager.setListener(this);
|
||||||
|
this.triggerOnMapLayerPropertyChange();
|
||||||
this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => {
|
|
||||||
if (newValue === undefined) {
|
|
||||||
coWebsiteManager.closeCoWebsite();
|
|
||||||
} else {
|
|
||||||
coWebsiteManager.loadCoWebsite(newValue as string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => {
|
|
||||||
if (newValue === undefined) {
|
|
||||||
this.stopJitsi();
|
|
||||||
} else {
|
|
||||||
if (JITSI_PRIVATE_MODE) {
|
|
||||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
|
||||||
|
|
||||||
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
|
|
||||||
} else {
|
|
||||||
this.startJitsi(newValue as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
|
|
||||||
if (newValue === undefined || newValue === false || newValue === '') {
|
|
||||||
this.connection.setSilent(false);
|
|
||||||
} else {
|
|
||||||
this.connection.setSilent(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const camera = this.cameras.main;
|
const camera = this.cameras.main;
|
||||||
|
|
||||||
@ -504,7 +473,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Position missing from UserMovedMessage');
|
throw new Error('Position missing from UserMovedMessage');
|
||||||
}
|
}
|
||||||
//console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid());
|
|
||||||
|
|
||||||
const messageUserMoved: MessageUserMovedInterface = {
|
const messageUserMoved: MessageUserMovedInterface = {
|
||||||
userId: message.getUserid(),
|
userId: message.getUserid(),
|
||||||
@ -537,7 +505,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.simplePeer.unregister();
|
this.simplePeer.unregister();
|
||||||
|
|
||||||
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
||||||
const game: Phaser.Scene = GameScene.createFromUrl(this.room, this.MapUrlFile, gameSceneKey);
|
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile);
|
||||||
this.scene.add(gameSceneKey, game, true,
|
this.scene.add(gameSceneKey, game, true,
|
||||||
{
|
{
|
||||||
initPosition: {
|
initPosition: {
|
||||||
@ -567,7 +535,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// When connection is performed, let's connect SimplePeer
|
// When connection is performed, let's connect SimplePeer
|
||||||
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic);
|
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.GameManager.getPlayerName());
|
||||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
||||||
this.UserMessageManager = new UserMessageManager(this.connection);
|
this.UserMessageManager = new UserMessageManager(this.connection);
|
||||||
|
|
||||||
@ -592,15 +560,102 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.gameMap.setPosition(event.x, event.y);
|
this.gameMap.setPosition(event.x, event.y);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
this.scene.wake();
|
this.scene.wake();
|
||||||
this.scene.sleep(ReconnectingSceneName);
|
this.scene.sleep(ReconnectingSceneName);
|
||||||
|
|
||||||
|
//init user position and play trigger to check layers properties
|
||||||
|
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private triggerOnMapLayerPropertyChange(){
|
||||||
|
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
|
||||||
|
if (newValue) this.onMapExit(newValue as string);
|
||||||
|
});
|
||||||
|
this.gameMap.onPropertyChange('exitUrl', (newValue, oldValue) => {
|
||||||
|
if (newValue) this.onMapExit(newValue as string);
|
||||||
|
});
|
||||||
|
this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue, allProps) => {
|
||||||
|
if (newValue === undefined) {
|
||||||
|
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||||
|
coWebsiteManager.closeCoWebsite();
|
||||||
|
}else{
|
||||||
|
const openWebsiteFunction = () => {
|
||||||
|
coWebsiteManager.loadCoWebsite(newValue as string);
|
||||||
|
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
|
||||||
|
if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||||
|
layoutManager.addActionButton('openWebsite', 'Click on SPACE to open the web site', () => {
|
||||||
|
openWebsiteFunction();
|
||||||
|
}, this.userInputManager);
|
||||||
|
}else{
|
||||||
|
openWebsiteFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => {
|
||||||
|
if (newValue === undefined) {
|
||||||
|
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
|
||||||
|
this.stopJitsi();
|
||||||
|
}else{
|
||||||
|
const openJitsiRoomFunction = () => {
|
||||||
|
if (JITSI_PRIVATE_MODE) {
|
||||||
|
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||||
|
|
||||||
|
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
|
||||||
|
} else {
|
||||||
|
this.startJitsi(newValue as string);
|
||||||
|
}
|
||||||
|
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
|
||||||
|
if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||||
|
layoutManager.addActionButton('jitsiRoom', 'Click on SPACE to enter in jitsi meet room', () => {
|
||||||
|
openJitsiRoomFunction();
|
||||||
|
}, this.userInputManager);
|
||||||
|
}else{
|
||||||
|
openJitsiRoomFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
|
||||||
|
if (newValue === undefined || newValue === false || newValue === '') {
|
||||||
|
this.connection.setSilent(false);
|
||||||
|
} else {
|
||||||
|
this.connection.setSilent(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMapExit(exitKey: string) {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
|
||||||
|
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
|
||||||
|
urlManager.pushStartLayerNameToUrl(hash);
|
||||||
|
if (roomId !== this.scene.key) {
|
||||||
|
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||||
|
this.connection.closeConnection();
|
||||||
|
this.simplePeer.unregister();
|
||||||
|
this.scene.stop();
|
||||||
|
this.scene.remove(this.scene.key);
|
||||||
|
this.scene.start(roomId);
|
||||||
|
} else {
|
||||||
|
//if the exit points to the current map, we simply teleport the user back to the startLayer
|
||||||
|
this.initPositionFromLayerName(hash || defaultStartLayerName);
|
||||||
|
this.CurrentPlayer.x = this.startX;
|
||||||
|
this.CurrentPlayer.y = this.startY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private switchLayoutMode(): void {
|
private switchLayoutMode(): void {
|
||||||
|
//if discussion is activated, this layout cannot be activated
|
||||||
|
if(mediaManager.activatedDiscussion){
|
||||||
|
return;
|
||||||
|
}
|
||||||
const mode = layoutManager.getLayoutMode();
|
const mode = layoutManager.getLayoutMode();
|
||||||
if (mode === LayoutMode.Presentation) {
|
if (mode === LayoutMode.Presentation) {
|
||||||
layoutManager.switchLayoutMode(LayoutMode.VideoChat);
|
layoutManager.switchLayoutMode(LayoutMode.VideoChat);
|
||||||
@ -620,12 +675,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
this.startY = this.initPosition.y;
|
this.startY = this.initPosition.y;
|
||||||
} else {
|
} else {
|
||||||
// Now, let's find the start layer
|
// Now, let's find the start layer
|
||||||
if (this.room.hash) {
|
if (this.startLayerName) {
|
||||||
this.initPositionFromLayerName(this.room.hash);
|
this.initPositionFromLayerName(this.startLayerName);
|
||||||
}
|
}
|
||||||
if (this.startX === undefined) {
|
if (this.startX === undefined) {
|
||||||
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
||||||
this.initPositionFromLayerName("start");
|
this.initPositionFromLayerName(defaultStartLayerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||||
@ -639,7 +694,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
|
|
||||||
private initPositionFromLayerName(layerName: string) {
|
private initPositionFromLayerName(layerName: string) {
|
||||||
for (const layer of this.mapFile.layers) {
|
for (const layer of this.mapFile.layers) {
|
||||||
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === "start" || this.isStartLayer(layer))) {
|
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
|
||||||
const startPosition = this.startUser(layer);
|
const startPosition = this.startUser(layer);
|
||||||
this.startX = startPosition.x;
|
this.startX = startPosition.x;
|
||||||
this.startY = startPosition.y;
|
this.startY = startPosition.y;
|
||||||
@ -651,14 +706,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
return this.getProperty(layer, "exitUrl") as string|undefined;
|
return this.getProperty(layer, "exitUrl") as string|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated the map property exitSceneUrl is deprecated
|
||||||
|
*/
|
||||||
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
|
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
|
||||||
return this.getProperty(layer, "exitSceneUrl") as string|undefined;
|
return this.getProperty(layer, "exitSceneUrl") as string|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getExitSceneInstance(layer: ITiledMapLayer): string|undefined {
|
|
||||||
return this.getProperty(layer, "exitInstance") as string|undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isStartLayer(layer: ITiledMapLayer): boolean {
|
private isStartLayer(layer: ITiledMapLayer): boolean {
|
||||||
return this.getProperty(layer, "startLayer") == true;
|
return this.getProperty(layer, "startLayer") == true;
|
||||||
}
|
}
|
||||||
@ -668,72 +722,18 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
if (!properties) {
|
if (!properties) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const obj = properties.find((property: ITiledMapLayerProperty) => property.name === name);
|
const obj = properties.find((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase());
|
||||||
if (obj === undefined) {
|
if (obj === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return obj.value;
|
return obj.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadNextGameFromExitSceneUrl(layer: ITiledMapLayer, mapWidth: number) {
|
|
||||||
const exitSceneUrl = this.getExitSceneUrl(layer);
|
|
||||||
if (exitSceneUrl === undefined) {
|
|
||||||
throw new Error('Layer is not an exit scene layer.');
|
|
||||||
}
|
|
||||||
let instance = this.getExitSceneInstance(layer);
|
|
||||||
if (instance === undefined) {
|
|
||||||
instance = this.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href;
|
|
||||||
const absoluteExitSceneUrlWithoutProtocol = absoluteExitSceneUrl.toString().substr(absoluteExitSceneUrl.toString().indexOf('://')+3);
|
|
||||||
const roomId = '_/'+instance+'/'+absoluteExitSceneUrlWithoutProtocol;
|
|
||||||
|
|
||||||
this.loadNextGame(layer, mapWidth, roomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadNextGameFromExitUrl(layer: ITiledMapLayer, mapWidth: number) {
|
|
||||||
const exitUrl = this.getExitUrl(layer);
|
|
||||||
if (exitUrl === undefined) {
|
|
||||||
throw new Error('Layer is not an exit layer.');
|
|
||||||
}
|
|
||||||
const fullPath = new URL(exitUrl, window.location.toString()).pathname;
|
|
||||||
|
|
||||||
this.loadNextGame(layer, mapWidth, fullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: push that into the gameManager
|
//todo: push that into the gameManager
|
||||||
private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){
|
private async loadNextGame(exitSceneIdentifier: string){
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
|
||||||
const room = new Room(roomId);
|
const room = new Room(roomId);
|
||||||
gameManager.loadMap(room, this.scene);
|
await gameManager.loadMap(room, this.scene);
|
||||||
const exitSceneKey = roomId;
|
|
||||||
|
|
||||||
const tiles : number[] = layer.data as number[];
|
|
||||||
for (let key=0; key < tiles.length; key++) {
|
|
||||||
const objectKey = tiles[key];
|
|
||||||
if(objectKey === 0){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//key + 1 because the start x = 0;
|
|
||||||
const y : number = parseInt(((key + 1) / mapWidth).toString());
|
|
||||||
const x : number = key - (y * mapWidth);
|
|
||||||
|
|
||||||
let hash = new URL(roomId, this.MapUrlFile).hash;
|
|
||||||
if (hash) {
|
|
||||||
hash = hash.substr(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//push and save switching case
|
|
||||||
if (this.PositionNextScene[y] === undefined) {
|
|
||||||
this.PositionNextScene[y] = new Array<{key: string, hash: string}>();
|
|
||||||
}
|
|
||||||
room.getMapUrl().then((url: string) => {
|
|
||||||
this.PositionNextScene[y][x] = {
|
|
||||||
key: url,
|
|
||||||
hash
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private startUser(layer: ITiledMapLayer): PositionInterface {
|
private startUser(layer: ITiledMapLayer): PositionInterface {
|
||||||
@ -896,6 +896,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
||||||
*/
|
*/
|
||||||
update(time: number, delta: number) : void {
|
update(time: number, delta: number) : void {
|
||||||
|
mediaManager.setLastUpdateScene();
|
||||||
this.currentTick = time;
|
this.currentTick = time;
|
||||||
this.CurrentPlayer.moveUser(delta);
|
this.CurrentPlayer.moveUser(delta);
|
||||||
|
|
||||||
@ -933,33 +934,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
}
|
}
|
||||||
player.updatePosition(moveEvent);
|
player.updatePosition(moveEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextSceneKey = this.checkToExit();
|
|
||||||
if (!nextSceneKey) return;
|
|
||||||
if (nextSceneKey.key !== this.scene.key) {
|
|
||||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
|
||||||
this.connection.closeConnection();
|
|
||||||
this.simplePeer.unregister();
|
|
||||||
this.scene.stop();
|
|
||||||
this.scene.remove(this.scene.key);
|
|
||||||
this.scene.start(nextSceneKey.key);
|
|
||||||
} else {
|
|
||||||
//if the exit points to the current map, we simply teleport the user back to the startLayer
|
|
||||||
this.initPositionFromLayerName(this.room.hash || 'start');
|
|
||||||
this.CurrentPlayer.x = this.startX;
|
|
||||||
this.CurrentPlayer.y = this.startY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkToExit(): {key: string, hash: string} | null {
|
|
||||||
const x = Math.floor(this.CurrentPlayer.x / 32);
|
|
||||||
const y = Math.floor(this.CurrentPlayer.y / 32);
|
|
||||||
|
|
||||||
if (this.PositionNextScene[y] !== undefined && this.PositionNextScene[y][x] !== undefined) {
|
|
||||||
return this.PositionNextScene[y][x];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1191,12 +1165,19 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||||||
jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt);
|
jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt);
|
||||||
this.connection.setSilent(true);
|
this.connection.setSilent(true);
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
|
|
||||||
|
//permit to stop jitsi when user close iframe
|
||||||
|
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
|
||||||
|
this.stopJitsi();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopJitsi(): void {
|
public stopJitsi(): void {
|
||||||
this.connection.setSilent(false);
|
this.connection.setSilent(false);
|
||||||
jitsiFactory.stop();
|
jitsiFactory.stop();
|
||||||
mediaManager.showGameOverlay();
|
mediaManager.showGameOverlay();
|
||||||
|
|
||||||
|
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadSpritesheet(name: string, url: string): Promise<void> {
|
private loadSpritesheet(name: string, url: string): Promise<void> {
|
||||||
|
@ -7,6 +7,7 @@ import {mediaManager} from "../../WebRtc/MediaManager";
|
|||||||
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
||||||
import {SoundMeter} from "../Components/SoundMeter";
|
import {SoundMeter} from "../Components/SoundMeter";
|
||||||
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
|
import {SoundMeterSprite} from "../Components/SoundMeterSprite";
|
||||||
|
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||||
|
|
||||||
export const EnableCameraSceneName = "EnableCameraScene";
|
export const EnableCameraSceneName = "EnableCameraScene";
|
||||||
enum LoginTextures {
|
enum LoginTextures {
|
||||||
@ -93,7 +94,7 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
this.login();
|
this.login();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').classList.add('active');
|
||||||
|
|
||||||
const mediaPromise = mediaManager.getCamera();
|
const mediaPromise = mediaManager.getCamera();
|
||||||
mediaPromise.then(this.getDevices.bind(this));
|
mediaPromise.then(this.getDevices.bind(this));
|
||||||
@ -150,10 +151,10 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
* Function called each time a camera is changed
|
* Function called each time a camera is changed
|
||||||
*/
|
*/
|
||||||
private setupStream(stream: MediaStream): void {
|
private setupStream(stream: MediaStream): void {
|
||||||
const img = this.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
|
const img = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetupNoVideo');
|
||||||
img.style.display = 'none';
|
img.style.display = 'none';
|
||||||
|
|
||||||
const div = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||||
div.srcObject = stream;
|
div.srcObject = stream;
|
||||||
|
|
||||||
this.soundMeter.connectToSource(stream, new window.AudioContext());
|
this.soundMeter.connectToSource(stream, new window.AudioContext());
|
||||||
@ -164,7 +165,7 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
|
|
||||||
private updateWebCamName(): void {
|
private updateWebCamName(): void {
|
||||||
if (this.camerasList.length > 1) {
|
if (this.camerasList.length > 1) {
|
||||||
const div = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||||
|
|
||||||
let label = this.camerasList[this.cameraSelected].label;
|
let label = this.camerasList[this.cameraSelected].label;
|
||||||
// remove text in parenthesis
|
// remove text in parenthesis
|
||||||
@ -211,10 +212,10 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private reposition(): void {
|
private reposition(): void {
|
||||||
let div = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
let div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||||
let bounds = div.getBoundingClientRect();
|
let bounds = div.getBoundingClientRect();
|
||||||
if (!div.srcObject) {
|
if (!div.srcObject) {
|
||||||
div = this.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
|
div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('webRtcSetup');
|
||||||
bounds = div.getBoundingClientRect();
|
bounds = div.getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +256,7 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private login(): void {
|
private login(): void {
|
||||||
this.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||||
this.soundMeter.stop();
|
this.soundMeter.stop();
|
||||||
window.removeEventListener('resize', this.repositionCallback);
|
window.removeEventListener('resize', this.repositionCallback);
|
||||||
|
|
||||||
@ -276,13 +277,4 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
this.updateWebCamName();
|
this.updateWebCamName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
|
||||||
const elem = document.getElementById(id);
|
|
||||||
if (elem === null) {
|
|
||||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
|
||||||
}
|
|
||||||
// FIXME: does not check the type of the returned type
|
|
||||||
return elem as T;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -79,4 +79,11 @@ export class UserInputManager {
|
|||||||
return event;
|
return event;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSpaceEventListner(callback : Function){
|
||||||
|
this.Scene.input.keyboard.addListener('keyup-SPACE', callback);
|
||||||
|
}
|
||||||
|
removeSpaceEventListner(callback : Function){
|
||||||
|
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,14 @@ class UrlManager {
|
|||||||
return newUrl;
|
return newUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getStartLayerNameFromUrl(): string|null {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
return hash.length > 1 ? hash.substring(1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushStartLayerNameToUrl(startLayerName: string): void {
|
||||||
|
window.location.hash = startLayerName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const urlManager = new UrlManager();
|
export const urlManager = new UrlManager();
|
||||||
|
@ -44,7 +44,9 @@ class CoWebsiteManager {
|
|||||||
|
|
||||||
public loadCoWebsite(url: string): void {
|
public loadCoWebsite(url: string): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteDiv.innerHTML = '';
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
|
<img src="resources/logos/close.svg">
|
||||||
|
</button>`;
|
||||||
|
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.id = 'cowebsite-iframe';
|
iframe.id = 'cowebsite-iframe';
|
||||||
@ -83,7 +85,9 @@ class CoWebsiteManager {
|
|||||||
this.close();
|
this.close();
|
||||||
this.fire();
|
this.fire();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cowebsiteDiv.innerHTML = '';
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
|
<img src="resources/logos/close.svg">
|
||||||
|
</button>`;
|
||||||
resolve();
|
resolve();
|
||||||
}, animationTime)
|
}, animationTime)
|
||||||
}));
|
}));
|
||||||
|
238
front/src/WebRtc/DiscussionManager.ts
Normal file
238
front/src/WebRtc/DiscussionManager.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
|
import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager";
|
||||||
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
|
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||||
|
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||||
|
|
||||||
|
export type SendMessageCallback = (message:string) => void;
|
||||||
|
|
||||||
|
export class DiscussionManager {
|
||||||
|
private mainContainer: HTMLDivElement;
|
||||||
|
|
||||||
|
private divDiscuss?: HTMLDivElement;
|
||||||
|
private divParticipants?: HTMLDivElement;
|
||||||
|
private nbpParticipants?: HTMLParagraphElement;
|
||||||
|
private divMessages?: HTMLParagraphElement;
|
||||||
|
private buttonActiveDiscussion?: HTMLButtonElement;
|
||||||
|
|
||||||
|
private participants: Map<number|string, HTMLDivElement> = new Map<number|string, HTMLDivElement>();
|
||||||
|
|
||||||
|
private activeDiscussion: boolean = false;
|
||||||
|
|
||||||
|
private sendMessageCallBack : Map<number|string, SendMessageCallback> = new Map<number|string, SendMessageCallback>();
|
||||||
|
|
||||||
|
private userInputManager?: UserInputManager;
|
||||||
|
|
||||||
|
constructor(private mediaManager: MediaManager, name: string) {
|
||||||
|
this.mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||||
|
this.createDiscussPart(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDiscussPart(name: string) {
|
||||||
|
this.divDiscuss = document.createElement('div');
|
||||||
|
this.divDiscuss.classList.add('discussion');
|
||||||
|
|
||||||
|
const buttonCloseDiscussion: HTMLButtonElement = document.createElement('button');
|
||||||
|
this.buttonActiveDiscussion = document.createElement('button');
|
||||||
|
buttonCloseDiscussion.classList.add('close-btn');
|
||||||
|
buttonCloseDiscussion.innerHTML = `<img src="resources/logos/close.svg"/>`;
|
||||||
|
buttonCloseDiscussion.addEventListener('click', () => {
|
||||||
|
this.hideDiscussion();
|
||||||
|
this.showButtonDiscussionBtn();
|
||||||
|
});
|
||||||
|
this.buttonActiveDiscussion.classList.add('active-btn');
|
||||||
|
this.buttonActiveDiscussion.innerHTML = `<img src="resources/logos/discussion.svg"/>`;
|
||||||
|
this.buttonActiveDiscussion.addEventListener('click', () => {
|
||||||
|
this.showDiscussionPart();
|
||||||
|
});
|
||||||
|
this.divDiscuss.appendChild(buttonCloseDiscussion);
|
||||||
|
this.divDiscuss.appendChild(this.buttonActiveDiscussion);
|
||||||
|
|
||||||
|
const myName: HTMLParagraphElement = document.createElement('p');
|
||||||
|
myName.innerText = name.toUpperCase();
|
||||||
|
this.nbpParticipants = document.createElement('p');
|
||||||
|
this.nbpParticipants.innerText = 'PARTICIPANTS (1)';
|
||||||
|
|
||||||
|
this.divParticipants = document.createElement('div');
|
||||||
|
this.divParticipants.classList.add('participants');
|
||||||
|
|
||||||
|
this.divMessages = document.createElement('div');
|
||||||
|
this.divMessages.classList.add('messages');
|
||||||
|
this.divMessages.innerHTML = "<h2>Local messages</h2>"
|
||||||
|
|
||||||
|
this.divDiscuss.appendChild(myName);
|
||||||
|
this.divDiscuss.appendChild(this.nbpParticipants);
|
||||||
|
this.divDiscuss.appendChild(this.divParticipants);
|
||||||
|
this.divDiscuss.appendChild(this.divMessages);
|
||||||
|
|
||||||
|
const sendDivMessage: HTMLDivElement = document.createElement('div');
|
||||||
|
sendDivMessage.classList.add('send-message');
|
||||||
|
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||||
|
inputMessage.type = "text";
|
||||||
|
inputMessage.addEventListener('keyup', (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
if(inputMessage.value === null
|
||||||
|
|| inputMessage.value === ''
|
||||||
|
|| inputMessage.value === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addMessage(name, inputMessage.value, true);
|
||||||
|
for(const callback of this.sendMessageCallBack.values()) {
|
||||||
|
callback(inputMessage.value);
|
||||||
|
}
|
||||||
|
inputMessage.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sendDivMessage.appendChild(inputMessage);
|
||||||
|
this.divDiscuss.appendChild(sendDivMessage);
|
||||||
|
|
||||||
|
//append in main container
|
||||||
|
this.mainContainer.appendChild(this.divDiscuss);
|
||||||
|
|
||||||
|
this.addParticipant('me', 'Moi', undefined, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addParticipant(
|
||||||
|
userId: number|string,
|
||||||
|
name: string|undefined,
|
||||||
|
img?: string|undefined,
|
||||||
|
isMe: boolean = false,
|
||||||
|
reportCallback?: ReportCallback
|
||||||
|
) {
|
||||||
|
const divParticipant: HTMLDivElement = document.createElement('div');
|
||||||
|
divParticipant.classList.add('participant');
|
||||||
|
divParticipant.id = `participant-${userId}`;
|
||||||
|
|
||||||
|
const divImgParticipant: HTMLImageElement = document.createElement('img');
|
||||||
|
divImgParticipant.src = 'resources/logos/boy.svg';
|
||||||
|
if (img !== undefined) {
|
||||||
|
divImgParticipant.src = img;
|
||||||
|
}
|
||||||
|
const divPParticipant: HTMLParagraphElement = document.createElement('p');
|
||||||
|
if(!name){
|
||||||
|
name = 'Anonymous';
|
||||||
|
}
|
||||||
|
divPParticipant.innerText = name;
|
||||||
|
|
||||||
|
divParticipant.appendChild(divImgParticipant);
|
||||||
|
divParticipant.appendChild(divPParticipant);
|
||||||
|
|
||||||
|
if(
|
||||||
|
!isMe
|
||||||
|
&& connectionManager.getConnexionType
|
||||||
|
&& connectionManager.getConnexionType !== GameConnexionTypes.anonymous
|
||||||
|
) {
|
||||||
|
const reportBanUserAction: HTMLButtonElement = document.createElement('button');
|
||||||
|
reportBanUserAction.classList.add('report-btn')
|
||||||
|
reportBanUserAction.innerText = 'Report';
|
||||||
|
reportBanUserAction.addEventListener('click', () => {
|
||||||
|
if(reportCallback) {
|
||||||
|
this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
|
||||||
|
}else{
|
||||||
|
console.info('report feature is not activated!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
divParticipant.appendChild(reportBanUserAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.divParticipants?.appendChild(divParticipant);
|
||||||
|
|
||||||
|
this.participants.set(userId, divParticipant);
|
||||||
|
this.showButtonDiscussionBtn();
|
||||||
|
|
||||||
|
this.updateParticipant(this.participants.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateParticipant(nb: number) {
|
||||||
|
if (!this.nbpParticipants) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMessage(name: string, message: string, isMe: boolean = false) {
|
||||||
|
const divMessage: HTMLDivElement = document.createElement('div');
|
||||||
|
divMessage.classList.add('message');
|
||||||
|
if(isMe){
|
||||||
|
divMessage.classList.add('me');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pMessage: HTMLParagraphElement = document.createElement('p');
|
||||||
|
const date = new Date();
|
||||||
|
if(isMe){
|
||||||
|
name = 'Moi';
|
||||||
|
}
|
||||||
|
pMessage.innerHTML = `<span style="font-weight: bold">${name}</span>
|
||||||
|
<span style="color:#bac2cc;display:inline-block;font-size:12px;">
|
||||||
|
${date.getHours()}:${date.getMinutes()}
|
||||||
|
</span>`;
|
||||||
|
divMessage.appendChild(pMessage);
|
||||||
|
|
||||||
|
const userMessage: HTMLParagraphElement = document.createElement('p');
|
||||||
|
userMessage.innerText = message;
|
||||||
|
userMessage.classList.add('body');
|
||||||
|
divMessage.appendChild(userMessage);
|
||||||
|
|
||||||
|
this.divMessages?.appendChild(divMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeParticipant(userId: number|string){
|
||||||
|
const element = this.participants.get(userId);
|
||||||
|
if(element){
|
||||||
|
element.remove();
|
||||||
|
this.participants.delete(userId);
|
||||||
|
}
|
||||||
|
//if all participant leave, hide discussion button
|
||||||
|
if(this.participants.size === 1){
|
||||||
|
this.hideButtonDiscussionBtn();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessageCallBack.delete(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onSendMessageCallback(userId: string|number, callback: SendMessageCallback): void {
|
||||||
|
this.sendMessageCallBack.set(userId, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
get activatedDiscussion(){
|
||||||
|
return this.activeDiscussion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private showButtonDiscussionBtn(){
|
||||||
|
//if it's first participant, show discussion button
|
||||||
|
if(this.activatedDiscussion || this.participants.size === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.buttonActiveDiscussion?.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
private showDiscussion(){
|
||||||
|
this.activeDiscussion = true;
|
||||||
|
if(this.userInputManager) {
|
||||||
|
this.userInputManager.clearAllInputKeyboard();
|
||||||
|
}
|
||||||
|
this.divDiscuss?.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideDiscussion(){
|
||||||
|
this.activeDiscussion = false;
|
||||||
|
if(this.userInputManager) {
|
||||||
|
this.userInputManager.initKeyBoardEvent();
|
||||||
|
}
|
||||||
|
this.divDiscuss?.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideButtonDiscussionBtn(){
|
||||||
|
this.buttonActiveDiscussion?.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
public setUserInputManager(userInputManager : UserInputManager){
|
||||||
|
this.userInputManager = userInputManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public showDiscussionPart(){
|
||||||
|
this.showDiscussion();
|
||||||
|
this.hideButtonDiscussionBtn();
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,9 @@ class JitsiFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
|
if(!this.jitsiApi){
|
||||||
|
return;
|
||||||
|
}
|
||||||
await coWebsiteManager.closeCoWebsite();
|
await coWebsiteManager.closeCoWebsite();
|
||||||
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
||||||
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { UserInputManager } from "../Phaser/UserInput/UserInputManager";
|
||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
|
|
||||||
export enum LayoutMode {
|
export enum LayoutMode {
|
||||||
@ -22,6 +23,10 @@ export interface CenterListener {
|
|||||||
onCenterChange(): void;
|
onCenterChange(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ON_ACTION_TRIGGER_BUTTON = 'onaction';
|
||||||
|
export const TRIGGER_WEBSITE_PROPERTIES = 'openWebsiteTrigger';
|
||||||
|
export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is in charge of the video-conference layout.
|
* This class is in charge of the video-conference layout.
|
||||||
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
||||||
@ -33,6 +38,9 @@ class LayoutManager {
|
|||||||
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
private listener: CenterListener|null = null;
|
private listener: CenterListener|null = null;
|
||||||
|
|
||||||
|
private actionButtonTrigger: Map<string, Function> = new Map<string, Function>();
|
||||||
|
private actionButtonInformation: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
|
|
||||||
public setListener(centerListener: CenterListener|null) {
|
public setListener(centerListener: CenterListener|null) {
|
||||||
this.listener = centerListener;
|
this.listener = centerListener;
|
||||||
}
|
}
|
||||||
@ -305,6 +313,48 @@ class LayoutManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addActionButton(id: string, text: string, callBack: Function, userInputManager: UserInputManager){
|
||||||
|
//delete previous element
|
||||||
|
this.removeActionButton(id, userInputManager);
|
||||||
|
|
||||||
|
//create div and text html component
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.classList.add('action-body');
|
||||||
|
p.innerText = text;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('action');
|
||||||
|
div.id = id;
|
||||||
|
div.appendChild(p);
|
||||||
|
|
||||||
|
this.actionButtonInformation.set(id, div);
|
||||||
|
|
||||||
|
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||||
|
mainContainer.appendChild(div);
|
||||||
|
|
||||||
|
const callBackFunctionTrigger = (() => {
|
||||||
|
console.log('user click on space => ', id);
|
||||||
|
callBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
//add trigger action
|
||||||
|
this.actionButtonTrigger.set(id, callBackFunctionTrigger);
|
||||||
|
userInputManager.addSpaceEventListner(callBackFunctionTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeActionButton(id: string, userInputManager: UserInputManager){
|
||||||
|
//delete previous element
|
||||||
|
const previousDiv = this.actionButtonInformation.get(id);
|
||||||
|
if(previousDiv){
|
||||||
|
previousDiv.remove();
|
||||||
|
this.actionButtonInformation.delete(id);
|
||||||
|
}
|
||||||
|
const previousEventCallback = this.actionButtonTrigger.get(id);
|
||||||
|
if(previousEventCallback){
|
||||||
|
userInputManager.removeSpaceEventListner(previousEventCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutManager = new LayoutManager();
|
const layoutManager = new LayoutManager();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {DivImportance, layoutManager} from "./LayoutManager";
|
import {DivImportance, layoutManager} from "./LayoutManager";
|
||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
|
import {DiscussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||||
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
const videoConstraint: boolean|MediaTrackConstraints = {
|
const videoConstraint: boolean|MediaTrackConstraints = {
|
||||||
@ -38,59 +40,99 @@ export class MediaManager {
|
|||||||
private cinemaBtn: HTMLDivElement;
|
private cinemaBtn: HTMLDivElement;
|
||||||
private monitorBtn: HTMLDivElement;
|
private monitorBtn: HTMLDivElement;
|
||||||
|
|
||||||
|
private previousConstraint : MediaStreamConstraints;
|
||||||
|
private focused : boolean = true;
|
||||||
|
|
||||||
|
private lastUpdateScene : Date = new Date();
|
||||||
|
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
|
||||||
|
|
||||||
|
private discussionManager: DiscussionManager;
|
||||||
|
|
||||||
|
private userInputManager?: UserInputManager;
|
||||||
|
|
||||||
private hasCamera = true;
|
private hasCamera = true;
|
||||||
|
|
||||||
|
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
this.myCamVideo = this.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
|
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
|
||||||
this.webrtcInAudio = this.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
|
this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
|
||||||
this.webrtcInAudio.volume = 0.2;
|
this.webrtcInAudio.volume = 0.2;
|
||||||
|
|
||||||
this.microphoneBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-micro');
|
this.microphoneBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-micro');
|
||||||
this.microphoneClose = this.getElementByIdOrFail<HTMLImageElement>('microphone-close');
|
this.microphoneClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone-close');
|
||||||
this.microphoneClose.style.display = "none";
|
this.microphoneClose.style.display = "none";
|
||||||
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
|
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableMicrophone();
|
this.enableMicrophone();
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
this.microphone = this.getElementByIdOrFail<HTMLImageElement>('microphone');
|
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
|
||||||
this.microphone.addEventListener('click', (e: MouseEvent) => {
|
this.microphone.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableMicrophone();
|
this.disableMicrophone();
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cinemaBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-video');
|
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
|
||||||
this.cinemaClose = this.getElementByIdOrFail<HTMLImageElement>('cinema-close');
|
this.cinemaClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema-close');
|
||||||
this.cinemaClose.style.display = "none";
|
this.cinemaClose.style.display = "none";
|
||||||
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
|
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableCamera();
|
this.enableCamera();
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
this.cinema = this.getElementByIdOrFail<HTMLImageElement>('cinema');
|
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
|
||||||
this.cinema.addEventListener('click', (e: MouseEvent) => {
|
this.cinema.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableCamera();
|
this.disableCamera();
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
|
|
||||||
this.monitorBtn = this.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
|
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
|
||||||
this.monitorClose = this.getElementByIdOrFail<HTMLImageElement>('monitor-close');
|
this.monitorClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor-close');
|
||||||
this.monitorClose.style.display = "block";
|
this.monitorClose.style.display = "block";
|
||||||
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableScreenSharing();
|
this.enableScreenSharing();
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
this.monitor = this.getElementByIdOrFail<HTMLImageElement>('monitor');
|
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
|
||||||
this.monitor.style.display = "none";
|
this.monitor.style.display = "none";
|
||||||
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableScreenSharing();
|
this.disableScreenSharing();
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
||||||
|
this.pingCameraStatus();
|
||||||
|
|
||||||
|
this.checkActiveUser();
|
||||||
|
|
||||||
|
this.discussionManager = new DiscussionManager(this,'');
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLastUpdateScene(){
|
||||||
|
this.lastUpdateScene = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public blurCamera() {
|
||||||
|
if(!this.focused){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.focused = false;
|
||||||
|
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
||||||
|
this.disableCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
public focusCamera() {
|
||||||
|
if(this.focused){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.focused = true;
|
||||||
|
this.applyPreviousConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
||||||
@ -128,22 +170,29 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public showGameOverlay(){
|
public showGameOverlay(){
|
||||||
const gameOverlay = this.getElementByIdOrFail('game-overlay');
|
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||||
gameOverlay.classList.add('active');
|
gameOverlay.classList.add('active');
|
||||||
|
|
||||||
|
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
|
||||||
|
const functionTrigger = () => {
|
||||||
|
this.triggerCloseJitsiFrameButton();
|
||||||
|
}
|
||||||
|
buttonCloseFrame.removeEventListener('click', functionTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public hideGameOverlay(){
|
public hideGameOverlay(){
|
||||||
const gameOverlay = this.getElementByIdOrFail('game-overlay');
|
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||||
gameOverlay.classList.remove('active');
|
gameOverlay.classList.remove('active');
|
||||||
|
|
||||||
|
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
|
||||||
|
const functionTrigger = () => {
|
||||||
|
this.triggerCloseJitsiFrameButton();
|
||||||
|
}
|
||||||
|
buttonCloseFrame.addEventListener('click', functionTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enableCamera() {
|
public enableCamera() {
|
||||||
if(!this.hasCamera){
|
this.enableCameraStyle();
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.cinemaClose.style.display = "none";
|
|
||||||
this.cinemaBtn.classList.remove("disabled");
|
|
||||||
this.cinema.style.display = "block";
|
|
||||||
this.constraintsMedia.video = videoConstraint;
|
this.constraintsMedia.video = videoConstraint;
|
||||||
this.getCamera().then((stream: MediaStream) => {
|
this.getCamera().then((stream: MediaStream) => {
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||||
@ -151,7 +200,8 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async disableCamera() {
|
public async disableCamera() {
|
||||||
this.disabledCameraView();
|
this.disableCameraStyle();
|
||||||
|
|
||||||
if (this.constraintsMedia.audio !== false) {
|
if (this.constraintsMedia.audio !== false) {
|
||||||
const stream = await this.getCamera();
|
const stream = await this.getCamera();
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||||
@ -160,19 +210,8 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private disabledCameraView(){
|
|
||||||
this.cinemaClose.style.display = "block";
|
|
||||||
this.cinema.style.display = "none";
|
|
||||||
this.cinemaBtn.classList.add("disabled");
|
|
||||||
this.constraintsMedia.video = false;
|
|
||||||
this.myCamVideo.srcObject = null;
|
|
||||||
this.stopCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enableMicrophone() {
|
public enableMicrophone() {
|
||||||
this.microphoneClose.style.display = "none";
|
this.enableMicrophoneStyle();
|
||||||
this.microphone.style.display = "block";
|
|
||||||
this.microphoneBtn.classList.remove("disabled");
|
|
||||||
this.constraintsMedia.audio = true;
|
this.constraintsMedia.audio = true;
|
||||||
|
|
||||||
this.getCamera().then((stream) => {
|
this.getCamera().then((stream) => {
|
||||||
@ -181,10 +220,7 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async disableMicrophone() {
|
public async disableMicrophone() {
|
||||||
this.microphoneClose.style.display = "block";
|
this.disableMicrophoneStyle();
|
||||||
this.microphone.style.display = "none";
|
|
||||||
this.microphoneBtn.classList.add("disabled");
|
|
||||||
this.constraintsMedia.audio = false;
|
|
||||||
this.stopMicrophone();
|
this.stopMicrophone();
|
||||||
|
|
||||||
if (this.constraintsMedia.video !== false) {
|
if (this.constraintsMedia.video !== false) {
|
||||||
@ -195,6 +231,52 @@ export class MediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyPreviousConfig() {
|
||||||
|
this.constraintsMedia = this.previousConstraint;
|
||||||
|
if(!this.constraintsMedia.video){
|
||||||
|
this.disableCameraStyle();
|
||||||
|
}else{
|
||||||
|
this.enableCameraStyle();
|
||||||
|
}
|
||||||
|
if(!this.constraintsMedia.audio){
|
||||||
|
this.disableMicrophoneStyle()
|
||||||
|
}else{
|
||||||
|
this.enableMicrophoneStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getCamera().then((stream: MediaStream) => {
|
||||||
|
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableCameraStyle(){
|
||||||
|
this.cinemaClose.style.display = "none";
|
||||||
|
this.cinemaBtn.classList.remove("disabled");
|
||||||
|
this.cinema.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableCameraStyle(){
|
||||||
|
this.cinemaClose.style.display = "block";
|
||||||
|
this.cinema.style.display = "none";
|
||||||
|
this.cinemaBtn.classList.add("disabled");
|
||||||
|
this.constraintsMedia.video = false;
|
||||||
|
this.myCamVideo.srcObject = null;
|
||||||
|
this.stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableMicrophoneStyle(){
|
||||||
|
this.microphoneClose.style.display = "none";
|
||||||
|
this.microphone.style.display = "block";
|
||||||
|
this.microphoneBtn.classList.remove("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableMicrophoneStyle(){
|
||||||
|
this.microphoneClose.style.display = "block";
|
||||||
|
this.microphone.style.display = "none";
|
||||||
|
this.microphoneBtn.classList.add("disabled");
|
||||||
|
this.constraintsMedia.audio = false;
|
||||||
|
}
|
||||||
|
|
||||||
private enableScreenSharing() {
|
private enableScreenSharing() {
|
||||||
this.monitorClose.style.display = "none";
|
this.monitorClose.style.display = "none";
|
||||||
this.monitor.style.display = "block";
|
this.monitor.style.display = "block";
|
||||||
@ -277,7 +359,7 @@ export class MediaManager {
|
|||||||
|
|
||||||
return this.getLocalStream().catch(() => {
|
return this.getLocalStream().catch(() => {
|
||||||
console.info('Error get camera, trying with video option at null');
|
console.info('Error get camera, trying with video option at null');
|
||||||
this.disabledCameraView();
|
this.disableCameraStyle();
|
||||||
return this.getLocalStream().then((stream : MediaStream) => {
|
return this.getLocalStream().then((stream : MediaStream) => {
|
||||||
this.hasCamera = false;
|
this.hasCamera = false;
|
||||||
return stream;
|
return stream;
|
||||||
@ -372,14 +454,17 @@ export class MediaManager {
|
|||||||
layoutManager.add(DivImportance.Normal, userId, html);
|
layoutManager.add(DivImportance.Normal, userId, html);
|
||||||
|
|
||||||
if (reportCallBack) {
|
if (reportCallBack) {
|
||||||
const reportBtn = this.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
||||||
reportBtn.addEventListener('click', (e: MouseEvent) => {
|
reportBtn.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.showReportModal(userId, userName, reportCallBack);
|
this.showReportModal(userId, userName, reportCallBack);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
|
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||||
|
|
||||||
|
//permit to create participant in discussion part
|
||||||
|
this.addNewParticipant(userId, userName, undefined, reportCallBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
||||||
@ -393,7 +478,7 @@ export class MediaManager {
|
|||||||
|
|
||||||
layoutManager.add(divImportance, userId, html);
|
layoutManager.add(divImportance, userId, html);
|
||||||
|
|
||||||
this.remoteVideo.set(userId, this.getElementByIdOrFail<HTMLVideoElement>(userId));
|
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
disabledMicrophoneByUserId(userId: number){
|
disabledMicrophoneByUserId(userId: number){
|
||||||
@ -454,6 +539,9 @@ export class MediaManager {
|
|||||||
removeActiveVideo(userId: string){
|
removeActiveVideo(userId: string){
|
||||||
layoutManager.remove(userId);
|
layoutManager.remove(userId);
|
||||||
this.remoteVideo.delete(userId);
|
this.remoteVideo.delete(userId);
|
||||||
|
|
||||||
|
//permit to remove user in discussion part
|
||||||
|
this.removeParticipant(userId);
|
||||||
}
|
}
|
||||||
removeActiveScreenSharingVideo(userId: string) {
|
removeActiveScreenSharingVideo(userId: string) {
|
||||||
this.removeActiveVideo(`screen-sharing-${userId}`)
|
this.removeActiveVideo(`screen-sharing-${userId}`)
|
||||||
@ -516,18 +604,9 @@ export class MediaManager {
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
||||||
const elem = document.getElementById(id);
|
|
||||||
if (elem === null) {
|
|
||||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
|
||||||
}
|
|
||||||
// FIXME: does not check the type of the returned type
|
|
||||||
return elem as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
private showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
|
||||||
//create report text area
|
//create report text area
|
||||||
const mainContainer = this.getElementByIdOrFail<HTMLDivElement>('main-container');
|
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||||
|
|
||||||
const divReport = document.createElement('div');
|
const divReport = document.createElement('div');
|
||||||
divReport.classList.add('modal-report-user');
|
divReport.classList.add('modal-report-user');
|
||||||
@ -582,7 +661,74 @@ export class MediaManager {
|
|||||||
mainContainer.appendChild(divReport);
|
mainContainer.appendChild(divReport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
|
||||||
|
this.discussionManager.addParticipant(userId, name, img, false, reportCallBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeParticipant(userId: number|string){
|
||||||
|
this.discussionManager.removeParticipant(userId);
|
||||||
|
}
|
||||||
|
public addTriggerCloseJitsiFrameButton(id: String, Function: Function){
|
||||||
|
this.triggerCloseJistiFrame.set(id, Function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeTriggerCloseJitsiFrameButton(id: String){
|
||||||
|
this.triggerCloseJistiFrame.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private triggerCloseJitsiFrameButton(): void {
|
||||||
|
for (const callback of this.triggerCloseJistiFrame.values()) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* For some reasons, the microphone muted icon or the stream is not always up to date.
|
||||||
|
* Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser)
|
||||||
|
**/
|
||||||
|
private pingCameraStatus(){
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('ping camera status');
|
||||||
|
this.triggerUpdatedLocalStreamCallbacks(this.localStream);
|
||||||
|
this.pingCameraStatus();
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addNewMessage(name: string, message: string, isMe: boolean = false){
|
||||||
|
this.discussionManager.addMessage(name, message, isMe);
|
||||||
|
|
||||||
|
//when there are new message, show discussion
|
||||||
|
if(!this.discussionManager.activatedDiscussion) {
|
||||||
|
this.discussionManager.showDiscussionPart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){
|
||||||
|
this.discussionManager.onSendMessageCallback(userId, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
get activatedDiscussion(){
|
||||||
|
return this.discussionManager.activatedDiscussion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setUserInputManager(userInputManager : UserInputManager){
|
||||||
|
this.discussionManager.setUserInputManager(userInputManager);
|
||||||
|
}
|
||||||
|
//check if user is active
|
||||||
|
private checkActiveUser(){
|
||||||
|
if(this.setTimeOutlastUpdateScene){
|
||||||
|
clearTimeout(this.setTimeOutlastUpdateScene);
|
||||||
|
}
|
||||||
|
this.setTimeOutlastUpdateScene = setTimeout(() => {
|
||||||
|
const now = new Date();
|
||||||
|
//if last update is more of 10 sec
|
||||||
|
if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) {
|
||||||
|
this.blurCamera();
|
||||||
|
}else{
|
||||||
|
this.focusCamera();
|
||||||
|
}
|
||||||
|
this.checkActiveUser();
|
||||||
|
}, this.focused ? 10000 : 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mediaManager = new MediaManager();
|
export const mediaManager = new MediaManager();
|
||||||
|
@ -2,6 +2,7 @@ import * as SimplePeerNamespace from "simple-peer";
|
|||||||
import {mediaManager} from "./MediaManager";
|
import {mediaManager} from "./MediaManager";
|
||||||
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
@ -148,6 +149,6 @@ export class ScreenSharingPeer extends Peer {
|
|||||||
|
|
||||||
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
|
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
|
||||||
this.removeStream(stream);
|
this.removeStream(stream);
|
||||||
this.write(new Buffer(JSON.stringify({streamEnded: true})));
|
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true})));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
UpdatedLocalStreamCallback
|
UpdatedLocalStreamCallback
|
||||||
} from "./MediaManager";
|
} from "./MediaManager";
|
||||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
||||||
import {VideoPeer} from "./VideoPeer";
|
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||||
|
|
||||||
export interface UserSimplePeerInterface{
|
export interface UserSimplePeerInterface{
|
||||||
@ -38,7 +38,7 @@ export class SimplePeer {
|
|||||||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||||
|
|
||||||
constructor(private Connection: RoomConnection, private enableReporting: boolean) {
|
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
|
||||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||||
this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this);
|
this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this);
|
||||||
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
|
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
|
||||||
@ -145,6 +145,12 @@ export class SimplePeer {
|
|||||||
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
|
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
|
||||||
|
|
||||||
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
||||||
|
|
||||||
|
//permit to send message
|
||||||
|
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
|
||||||
|
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
|
||||||
|
});
|
||||||
|
|
||||||
peer.toClose = false;
|
peer.toClose = false;
|
||||||
// When a connection is established to a video stream, and if a screen sharing is taking place,
|
// When a connection is established to a video stream, and if a screen sharing is taking place,
|
||||||
// the user sharing screen should also initiate a connection to the remote user!
|
// the user sharing screen should also initiate a connection to the remote user!
|
||||||
@ -318,7 +324,7 @@ export class SimplePeer {
|
|||||||
throw new Error('While adding media, cannot find user with ID ' + userId);
|
throw new Error('While adding media, cannot find user with ID ' + userId);
|
||||||
}
|
}
|
||||||
const localStream: MediaStream | null = mediaManager.localStream;
|
const localStream: MediaStream | null = mediaManager.localStream;
|
||||||
PeerConnection.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia)));
|
PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
|
||||||
|
|
||||||
if(!localStream){
|
if(!localStream){
|
||||||
return;
|
return;
|
||||||
|
@ -5,6 +5,8 @@ import {RoomConnection} from "../Connexion/RoomConnection";
|
|||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||||
|
|
||||||
|
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
|
||||||
|
export const MESSAGE_TYPE_MESSAGE = 'message';
|
||||||
/**
|
/**
|
||||||
* A peer connection used to transmit video / audio signals between 2 peers.
|
* A peer connection used to transmit video / audio signals between 2 peers.
|
||||||
*/
|
*/
|
||||||
@ -78,8 +80,11 @@ export class VideoPeer extends Peer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.on('data', (chunk: Buffer) => {
|
this.on('data', (chunk: Buffer) => {
|
||||||
const constraint = JSON.parse(chunk.toString('utf8'));
|
const message = JSON.parse(chunk.toString('utf8'));
|
||||||
console.log("data", constraint);
|
console.log("data", message);
|
||||||
|
|
||||||
|
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||||
|
const constraint = message;
|
||||||
if (constraint.audio) {
|
if (constraint.audio) {
|
||||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||||
} else {
|
} else {
|
||||||
@ -92,6 +97,11 @@ export class VideoPeer extends Peer {
|
|||||||
this.stream(undefined);
|
this.stream(undefined);
|
||||||
mediaManager.disabledVideoByUserId(this.userId);
|
mediaManager.disabledVideoByUserId(this.userId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message.type === 'message') {
|
||||||
|
mediaManager.addNewMessage(message.name, message.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.once('finish', () => {
|
this.once('finish', () => {
|
||||||
@ -163,7 +173,7 @@ export class VideoPeer extends Peer {
|
|||||||
private pushVideoToRemoteUser() {
|
private pushVideoToRemoteUser() {
|
||||||
try {
|
try {
|
||||||
const localStream: MediaStream | null = mediaManager.localStream;
|
const localStream: MediaStream | null = mediaManager.localStream;
|
||||||
this.write(new Buffer(JSON.stringify(mediaManager.constraintsMedia)));
|
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...mediaManager.constraintsMedia})));
|
||||||
|
|
||||||
if(!localStream){
|
if(!localStream){
|
||||||
return;
|
return;
|
||||||
|
42
front/tests/Phaser/Game/RoomTest.ts
Normal file
42
front/tests/Phaser/Game/RoomTest.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import "jasmine";
|
||||||
|
import {Room} from "../../../src/Connexion/Room";
|
||||||
|
|
||||||
|
describe("Room getIdFromIdentifier()", () => {
|
||||||
|
it("should work with an absolute room id and no hash as parameter", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', '', '');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with an absolute room id and a hash as parameters", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json#start', '', '');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual("start");
|
||||||
|
});
|
||||||
|
it("should work with an absolute room id, regardless of baseUrl or instance", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', 'https://another.domain/_/global/test.json', 'lol');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should work with a relative file link and no hash as parameters", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('./test2.json', 'https://maps.workadventu.re/test.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with a relative file link with no dot", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('test2.json', 'https://maps.workadventu.re/test.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with a relative file link two levels deep", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('../floor1/Floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/floor1/Floor1.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with a relative file link and a hash as parameters", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('./test2.json#start', 'https://maps.workadventu.re/test.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual("start");
|
||||||
|
});
|
||||||
|
});
|
1
maps/.gitignore
vendored
1
maps/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
|
dist/
|
@ -44,6 +44,11 @@
|
|||||||
"name":"jitsiRoom",
|
"name":"jitsiRoom",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"tcm-chillzone-2"
|
"value":"tcm-chillzone-2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jitsiTrigger",
|
||||||
|
"type":"string",
|
||||||
|
"value":"onaction"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
@ -61,7 +66,12 @@
|
|||||||
{
|
{
|
||||||
"name":"jitsiRoom",
|
"name":"jitsiRoom",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"tcm-chillzone-1"
|
"value":"tcm-chillzone-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jitsiTrigger",
|
||||||
|
"type":"string",
|
||||||
|
"value":"onaction"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
@ -128,6 +138,11 @@
|
|||||||
"name":"openWebsite",
|
"name":"openWebsite",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"https:\/\/app.swile.co\/"
|
"value":"https:\/\/app.swile.co\/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteTrigger",
|
||||||
|
"type":"string",
|
||||||
|
"value":"onaction"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
|
1577
maps/yarn.lock
Normal file
1577
maps/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
94
website/dist/index.html
vendored
94
website/dist/index.html
vendored
@ -10,6 +10,9 @@
|
|||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', 'UA-10196481-11');
|
gtag('config', 'UA-10196481-11');
|
||||||
|
if (window.location.host.endsWith("localhost")){
|
||||||
|
window['ga-disable-UA-10196481-11'] = true;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
|
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
|
||||||
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
|
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
|
||||||
@ -82,22 +85,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-2 col-md-6">
|
<div class="col-2 col-md-6">
|
||||||
<div class="social-links">
|
<div class="social-links">
|
||||||
<span class="share-title">Share your experience</span>
|
<a href="https://www.facebook.com/workadventurebytcm">
|
||||||
<a onclick="shareFB()">
|
<img class="social-image" src="static/images/facebook_bw.png" />
|
||||||
<img class="social-image" src="static/images/facebook.png" />
|
|
||||||
</a>
|
</a>
|
||||||
<a onclick="shareLI()">
|
<a href="https://www.linkedin.com/company/workadventure-by-tcm">
|
||||||
<img class="social-image" src="static/images/linkedin.png" />
|
<img class="social-image" src="static/images/linkedin_bw.png" />
|
||||||
</a>
|
</a>
|
||||||
<a onclick="shareTW()">
|
<a href="https://twitter.com/Workadventure_">
|
||||||
<img class="social-image" src="static/images/twitter.png" />
|
<img class="social-image" src="static/images/twitter_bw.png" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title title-main text-center">
|
<div class="title title-main text-center">
|
||||||
<h1>Your workplace<br/>but better</h1>
|
<h1>Meet your teammates</h1>
|
||||||
<h3>You are impatient to discover this new world? Click on "Work online" and meet new people or share this adventure with your colleagues and friends by clicking on "Work in private"</h3>
|
<h3>
|
||||||
|
WorkAdventure preserves your social interaction while COVID is still out there.
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
Stay connected with your teamworkers, by creating your own online workspace to work remotely.
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
Stay connected with your clients by providing a dedicated digital place to organize meetings, workshops.
|
||||||
|
</h3>
|
||||||
|
<h3>
|
||||||
|
Stay connected with your future collaborators by organizing online event.
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="row buttons-row justify-content-md-center pt-5">
|
<div class="row buttons-row justify-content-md-center pt-5">
|
||||||
<div class="col col-lg-3">
|
<div class="col col-lg-3">
|
||||||
@ -107,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col col-lg-3">
|
<div class="col col-lg-3">
|
||||||
<a class="custom-link play" target="_BLANK" onclick="startGame()" title="WORK ONLINE">
|
<a class="custom-link play" target="_BLANK" onclick="startGame()" title="WORK ONLINE">
|
||||||
WORK ONLINE
|
TRY IT NOW!
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -158,7 +171,7 @@
|
|||||||
<p>Click the button below to come and say hi!</p>
|
<p>Click the button below to come and say hi!</p>
|
||||||
<p class="bubble-action"><span onclick="startGame()">
|
<p class="bubble-action"><span onclick="startGame()">
|
||||||
<img src="static/images/playicon.png" />
|
<img src="static/images/playicon.png" />
|
||||||
START IN PUBLIC MODE
|
TRY IT NOW !
|
||||||
</span></p>
|
</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -166,10 +179,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<p>You can also create a private room with your friends or your team ! </p>
|
<p>You can also create a private room with your friends or your team ! </p>
|
||||||
<p class="bubble-legend">To try, press button work in private</p>
|
<p class="bubble-legend">To try, press button work in private</p>
|
||||||
<p class="bubble-action"><a href="/choose-map.html">
|
<p class="bubble-action">
|
||||||
|
<a href="/choose-map.html">
|
||||||
<img src="static/images/playicon.png" />
|
<img src="static/images/playicon.png" />
|
||||||
START WORKING IN PRIVATE
|
CHOOSE YOU OWN MAP
|
||||||
</a></p>
|
</a>
|
||||||
<p>
|
<p>
|
||||||
Don’t forget to activate your mic and camera, let’s play
|
Don’t forget to activate your mic and camera, let’s play
|
||||||
</p>
|
</p>
|
||||||
@ -179,7 +193,6 @@
|
|||||||
<img src="static/images/story/character-walk-right.gif" style="display:none;" />
|
<img src="static/images/story/character-walk-right.gif" style="display:none;" />
|
||||||
</section>
|
</section>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
gsap.to(".title-main", {
|
gsap.to(".title-main", {
|
||||||
//y:-1000,
|
//y:-1000,
|
||||||
scale: 0,
|
scale: 0,
|
||||||
@ -308,6 +321,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-lg-9 text-right">
|
||||||
|
<p class="py-3 font-weight-bold" style="font-size: 1.25rem">
|
||||||
|
Want to try Woradventure with your team mates or friends?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<a class="custom-link relative" href="/choose-map.html" title="WORK IN PRIVATE">
|
||||||
|
GET STARTED!
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="social-links text-center pt-2 pb-4">
|
||||||
|
<span class="share-title">... and if you liked woradventure, share your experience!</span>
|
||||||
|
<a onclick="shareFB()">
|
||||||
|
<img class="social-image" src="static/images/facebook.png" />
|
||||||
|
</a>
|
||||||
|
<a onclick="shareLI()">
|
||||||
|
<img class="social-image" src="static/images/linkedin.png" />
|
||||||
|
</a>
|
||||||
|
<a onclick="shareTW()">
|
||||||
|
<img class="social-image" src="static/images/twitter.png" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section bg-white text-center pt-5">
|
||||||
|
<div class="container-fluid container-lg">
|
||||||
|
<div class="row justify-content-md-center">
|
||||||
|
<div class="col-12 col-md-12 text-center">
|
||||||
|
<h3>WORKADVENTURE'S USE CASES</h3>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Workadventure is an intuitive and fun solution to professional issues:
|
||||||
|
</p>
|
||||||
|
<ul class="text-left">
|
||||||
|
<li>
|
||||||
|
Work in a remote way within a team,
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Create an event (even a big one),
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Stay connected and increase social interactions...
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Feel free to contact us if you need a specific map, need a dedicated admin console or any
|
||||||
|
support (for instance a large number of connexions) : <a href="mailto:workadventure@thecodingmachine.com" target="_blank">workadventure@thecodingmachine.com</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section bg-white">
|
<div class="section bg-white">
|
||||||
|
BIN
website/dist/static/images/facebook_bw.png
vendored
Normal file
BIN
website/dist/static/images/facebook_bw.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
website/dist/static/images/linkedin_bw.png
vendored
Normal file
BIN
website/dist/static/images/linkedin_bw.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
website/dist/static/images/twitter_bw.png
vendored
Normal file
BIN
website/dist/static/images/twitter_bw.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
6
website/package-lock.json
generated
6
website/package-lock.json
generated
@ -2251,9 +2251,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dot-prop": {
|
"dot-prop": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
|
||||||
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
|
"integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-obj": "^1.0.0"
|
"is-obj": "^1.0.0"
|
||||||
|
@ -80,11 +80,17 @@ header {
|
|||||||
h1 {
|
h1 {
|
||||||
font-family: 'Karmatic Arcade';
|
font-family: 'Karmatic Arcade';
|
||||||
font-size: 2.75rem;
|
font-size: 2.75rem;
|
||||||
margin-bottom: 1.25rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
min-height: 200px;
|
text-align: left;
|
||||||
padding: 1rem;
|
padding: 1rem 0 0 1rem;
|
||||||
|
|
||||||
|
&:before{
|
||||||
|
content: ">";
|
||||||
|
position: absolute;
|
||||||
|
left: -10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@include media-breakpoint-down(xs) {
|
@include media-breakpoint-down(xs) {
|
||||||
h3 {
|
h3 {
|
||||||
@ -152,7 +158,6 @@ header {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
transition: all .1s cubic-bezier(0.000, -0.600, 1.000, 1.650); /* custom */
|
transition: all .1s cubic-bezier(0.000, -0.600, 1.000, 1.650); /* custom */
|
||||||
//transition-timing-function: cubic-bezier(0.000, -0.600, 1.000, 1.650); /* custom */
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -171,33 +176,23 @@ header {
|
|||||||
background-image: url('../images/btn-bg-3.png');
|
background-image: url('../images/btn-bg-3.png');
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
&.start {
|
|
||||||
/*padding-left: 55px;*/
|
|
||||||
&:before {
|
|
||||||
/*content: "";
|
|
||||||
position: absolute;
|
|
||||||
background: url('../images/playicon.png') no-repeat;
|
|
||||||
height: 20px;
|
|
||||||
width: 21px;
|
|
||||||
left: 36px;
|
|
||||||
top: 23px;*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.light{
|
&.light{
|
||||||
background-image: url('../images/btn-bg-light.png');
|
background-image: url('../images/btn-bg-light.png');
|
||||||
}
|
}
|
||||||
|
&.relative{
|
||||||
/*&::after{
|
position: relative;
|
||||||
content: "";
|
left: auto;
|
||||||
position: absolute;
|
right: auto;
|
||||||
background-size: 60%;
|
bottom: auto;
|
||||||
width: 100%;
|
}
|
||||||
height: 100%;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-links a {
|
.social-links a {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img{
|
img{
|
||||||
@ -388,7 +383,7 @@ img{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.how-to{
|
&.how-to{
|
||||||
padding: 6.25rem 0;
|
padding: 6.25rem 0 0;
|
||||||
background: url('../images/bg-briques.jpg') repeat-x bottom;
|
background: url('../images/bg-briques.jpg') repeat-x bottom;
|
||||||
.desktop-only {
|
.desktop-only {
|
||||||
padding: 0 1.25rem 4rem;
|
padding: 0 1.25rem 4rem;
|
||||||
|
Loading…
Reference in New Issue
Block a user