From b6fe9e72e1aaf3622471e094839271a51a72710b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 25 Oct 2020 19:38:00 +0100 Subject: [PATCH 01/35] Fix style and refactor --- front/dist/resources/style/style.css | 7 ++-- front/src/Phaser/Login/EnableCameraScene.ts | 24 ++++-------- front/src/WebRtc/MediaManager.ts | 43 ++++++++------------- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 5f5d37f2..d0f0e68b 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -72,8 +72,9 @@ body .message-info.warning{ #div-myCamVideo { position: absolute; - right: 0; - bottom: 0; + right: 15px; + bottom: 15px; + border-radius: 15px; } video#myCamVideo{ @@ -419,7 +420,7 @@ body { max-height: 80%; top: -80%; left: 10%; - background: #000000a6; + background: #333333; z-index: 200; transition: all 0.1s ease-out; } diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 32037858..56502704 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -7,6 +7,7 @@ import {mediaManager} from "../../WebRtc/MediaManager"; import {RESOLUTION} from "../../Enum/EnvironmentVariable"; import {SoundMeter} from "../Components/SoundMeter"; import {SoundMeterSprite} from "../Components/SoundMeterSprite"; +import {HtmlUtils} from "../../WebRtc/HtmlUtils"; export const EnableCameraSceneName = "EnableCameraScene"; enum LoginTextures { @@ -93,7 +94,7 @@ export class EnableCameraScene extends Phaser.Scene { this.login(); }); - this.getElementByIdOrFail('webRtcSetup').classList.add('active'); + HtmlUtils.getElementByIdOrFail('webRtcSetup').classList.add('active'); const mediaPromise = mediaManager.getCamera(); mediaPromise.then(this.getDevices.bind(this)); @@ -150,10 +151,10 @@ export class EnableCameraScene extends Phaser.Scene { * Function called each time a camera is changed */ private setupStream(stream: MediaStream): void { - const img = this.getElementByIdOrFail('webRtcSetupNoVideo'); + const img = HtmlUtils.getElementByIdOrFail('webRtcSetupNoVideo'); img.style.display = 'none'; - const div = this.getElementByIdOrFail('myCamVideoSetup'); + const div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); div.srcObject = stream; this.soundMeter.connectToSource(stream, new window.AudioContext()); @@ -164,7 +165,7 @@ export class EnableCameraScene extends Phaser.Scene { private updateWebCamName(): void { if (this.camerasList.length > 1) { - const div = this.getElementByIdOrFail('myCamVideoSetup'); + const div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); let label = this.camerasList[this.cameraSelected].label; // remove text in parenthesis @@ -211,10 +212,10 @@ export class EnableCameraScene extends Phaser.Scene { } private reposition(): void { - let div = this.getElementByIdOrFail('myCamVideoSetup'); + let div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); let bounds = div.getBoundingClientRect(); if (!div.srcObject) { - div = this.getElementByIdOrFail('webRtcSetup'); + div = HtmlUtils.getElementByIdOrFail('webRtcSetup'); bounds = div.getBoundingClientRect(); } @@ -255,7 +256,7 @@ export class EnableCameraScene extends Phaser.Scene { } private login(): void { - this.getElementByIdOrFail('webRtcSetup').style.display = 'none'; + HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none'; this.soundMeter.stop(); window.removeEventListener('resize', this.repositionCallback); @@ -276,13 +277,4 @@ export class EnableCameraScene extends Phaser.Scene { } this.updateWebCamName(); } - - private getElementByIdOrFail(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; - } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 557f12fb..9c3563ad 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -40,49 +40,49 @@ export class MediaManager { constructor() { - this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); - this.webrtcInAudio = this.getElementByIdOrFail('audio-webrtc-in'); + this.myCamVideo = HtmlUtils.getElementByIdOrFail('myCamVideo'); + this.webrtcInAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-in'); this.webrtcInAudio.volume = 0.2; - this.microphoneBtn = this.getElementByIdOrFail('btn-micro'); - this.microphoneClose = this.getElementByIdOrFail('microphone-close'); + this.microphoneBtn = HtmlUtils.getElementByIdOrFail('btn-micro'); + this.microphoneClose = HtmlUtils.getElementByIdOrFail('microphone-close'); this.microphoneClose.style.display = "none"; this.microphoneClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enableMicrophone(); //update tracking }); - this.microphone = this.getElementByIdOrFail('microphone'); + this.microphone = HtmlUtils.getElementByIdOrFail('microphone'); this.microphone.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.disableMicrophone(); //update tracking }); - this.cinemaBtn = this.getElementByIdOrFail('btn-video'); - this.cinemaClose = this.getElementByIdOrFail('cinema-close'); + this.cinemaBtn = HtmlUtils.getElementByIdOrFail('btn-video'); + this.cinemaClose = HtmlUtils.getElementByIdOrFail('cinema-close'); this.cinemaClose.style.display = "none"; this.cinemaClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enableCamera(); //update tracking }); - this.cinema = this.getElementByIdOrFail('cinema'); + this.cinema = HtmlUtils.getElementByIdOrFail('cinema'); this.cinema.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.disableCamera(); //update tracking }); - this.monitorBtn = this.getElementByIdOrFail('btn-monitor'); - this.monitorClose = this.getElementByIdOrFail('monitor-close'); + this.monitorBtn = HtmlUtils.getElementByIdOrFail('btn-monitor'); + this.monitorClose = HtmlUtils.getElementByIdOrFail('monitor-close'); this.monitorClose.style.display = "block"; this.monitorClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enableScreenSharing(); //update tracking }); - this.monitor = this.getElementByIdOrFail('monitor'); + this.monitor = HtmlUtils.getElementByIdOrFail('monitor'); this.monitor.style.display = "none"; this.monitor.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); @@ -126,12 +126,12 @@ export class MediaManager { } public showGameOverlay(){ - const gameOverlay = this.getElementByIdOrFail('game-overlay'); + const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); } public hideGameOverlay(){ - const gameOverlay = this.getElementByIdOrFail('game-overlay'); + const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.remove('active'); } @@ -355,14 +355,14 @@ export class MediaManager { layoutManager.add(DivImportance.Normal, userId, html); if (reportCallBack) { - const reportBtn = this.getElementByIdOrFail(`report-${userId}`); + const reportBtn = HtmlUtils.getElementByIdOrFail(`report-${userId}`); reportBtn.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.showReportModal(userId, userName, reportCallBack); }); } - this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); + this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); } addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ @@ -376,7 +376,7 @@ export class MediaManager { layoutManager.add(divImportance, userId, html); - this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); + this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); } disabledMicrophoneByUserId(userId: number){ @@ -499,18 +499,9 @@ export class MediaManager { return color; } - private getElementByIdOrFail(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; - } - private showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ //create report text area - const mainContainer = this.getElementByIdOrFail('main-container'); + const mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); const divReport = document.createElement('div'); divReport.classList.add('modal-report-user'); From f344adc48bba3e612d7636ae711d2a75efbf5d7e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 25 Oct 2020 19:39:15 +0100 Subject: [PATCH 02/35] Create discussion - Add new discussion class - Feature to discuss and report user --- front/dist/resources/logos/boy.svg | 77 +++++++++++++ front/dist/resources/logos/discussion.svg | 1 + front/dist/resources/style/style.css | 125 ++++++++++++++++++++++ front/src/Phaser/Login/LoginScene.ts | 8 ++ front/src/WebRtc/Discussion.ts | 124 +++++++++++++++++++++ 5 files changed, 335 insertions(+) create mode 100644 front/dist/resources/logos/boy.svg create mode 100644 front/dist/resources/logos/discussion.svg create mode 100644 front/src/WebRtc/Discussion.ts diff --git a/front/dist/resources/logos/boy.svg b/front/dist/resources/logos/boy.svg new file mode 100644 index 00000000..d6d9582e --- /dev/null +++ b/front/dist/resources/logos/boy.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/front/dist/resources/logos/discussion.svg b/front/dist/resources/logos/discussion.svg new file mode 100644 index 00000000..1b4572b3 --- /dev/null +++ b/front/dist/resources/logos/discussion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index d0f0e68b..d1f5e7f7 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -686,3 +686,128 @@ div.modal-report-user{ max-width: calc(800px - 60px); /* size of modal - padding*/ } +/*MESSAGE*/ +.discussion{ + position: fixed; + right: -300px; + width: 220px; + height: 100%; + background-color: #000000; + padding: 20px; +} +.discussion.active{ + right: 0; +} +.discussion .toggle-btn{ + display: none; + cursor: pointer; + height: 50px; + width: 50px; + background-color: #2d2d2dba; + position: absolute; + top: calc(50% - 25px); + margin-left: -125px; + border-radius: 50%; + border: none; + transition: all 0.5s ease; +} +.discussion .toggle-btn.active{ + display: block; +} +.discussion .toggle-btn:hover { + transform: scale(1.1) rotateY(3.142rad); +} +.discussion .toggle-btn img{ + width: 26px; + height: 26px; + margin: 13px 5px; +} +.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: 8px; +} + +.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% - 300px); +} + +.discussion .messages .message{ + margin: 5px; + float: right; + text-align: right; +} + +.discussion .messages .message.me{ + float: left; + text-align: left; +} + +.discussion .messages .message p{ + font-size: 12px; +} + diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 2c5c1882..55321124 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -9,6 +9,7 @@ import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; +import {Discussion} from "../../WebRtc/Discussion"; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; @@ -74,6 +75,13 @@ export class LoginScene extends ResizableScene { }); cypressAsserter.initFinished(); + + const discussion = new Discussion('test'); + discussion.addParticipant('GRP'); + discussion.addParticipant('LOL'); + + discussion.addMessage('GRP', 'ceci est un test d\'envoi de message', true); + discussion.addMessage('LOL', 'ceci est un test d\'envoi de message'); } update(time: number, delta: number): void { diff --git a/front/src/WebRtc/Discussion.ts b/front/src/WebRtc/Discussion.ts new file mode 100644 index 00000000..b7307745 --- /dev/null +++ b/front/src/WebRtc/Discussion.ts @@ -0,0 +1,124 @@ +import {HtmlUtils} from "./HtmlUtils"; + +export class Discussion { + + private mainContainer: HTMLDivElement; + + private divDiscuss?: HTMLDivElement; + private divParticipants?: HTMLDivElement; + private nbpParticipants?: HTMLParagraphElement; + private divMessages?: HTMLParagraphElement; + + private participants: Map = new Map(); + + private activeDiscussion: boolean = false; + + constructor(name: string) { + this.mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); + this.createDiscussPart(name); + } + + private createDiscussPart(name: string) { + this.divDiscuss = document.createElement('div'); + this.divDiscuss.classList.add('discussion'); + + const buttonToggleDiscussion = document.createElement('button'); + buttonToggleDiscussion.classList.add('toggle-btn'); + buttonToggleDiscussion.classList.add('active'); + buttonToggleDiscussion.innerHTML = ``; + buttonToggleDiscussion.addEventListener('click', () => { + if(this.activeDiscussion){ + this.activeDiscussion = false; + this.divDiscuss?.classList.remove('active'); + buttonToggleDiscussion.classList.add('active'); + }else{ + this.activeDiscussion = true; + this.divDiscuss?.classList.add('active'); + buttonToggleDiscussion.classList.remove('active'); + } + }); + this.divDiscuss.appendChild(buttonToggleDiscussion); + + const myName = 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.divDiscuss.appendChild(myName); + this.divDiscuss.appendChild(this.nbpParticipants); + this.divDiscuss.appendChild(this.divParticipants); + this.divDiscuss.appendChild(this.divMessages); + + //append in main container + this.mainContainer.appendChild(this.divDiscuss); + } + + public addParticipant(name: string, img?: string) { + const divParticipant = document.createElement('div'); + divParticipant.classList.add('participant'); + + const divImgParticipant = document.createElement('img'); + divImgParticipant.src = 'resources/logos/boy.svg'; + if (img) { + divImgParticipant.src = img; + } + const divPParticipant = document.createElement('p'); + divPParticipant.innerText = name; + + const reportBanUserAction = document.createElement('button'); + reportBanUserAction.classList.add('report-btn') + reportBanUserAction.innerText = 'Report'; + reportBanUserAction.addEventListener('click', () => { + //TODO report user + console.log('report'); + }); + + divParticipant.appendChild(divImgParticipant); + divParticipant.appendChild(divPParticipant); + divParticipant.appendChild(reportBanUserAction); + + this.divParticipants?.appendChild(divParticipant); + this.participants.set(name, divParticipant); + + 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 = document.createElement('div'); + divMessage.classList.add('message'); + if(isMe){ + divMessage.classList.add('me'); + } + + const pMessage = document.createElement('p'); + const date = new Date(); + if(isMe){ + name = 'Moi'; + } + pMessage.innerHTML = `${name} + + ${date.getHours()}:${date.getMinutes()} + `; + divMessage.appendChild(pMessage); + + const userMessage = document.createElement('p'); + userMessage.innerText = message; + divMessage.appendChild(userMessage); + + this.divMessages?.appendChild(divMessage); + } + +} \ No newline at end of file From 1945c24704e96baab1be4673581ef65e2ecc13a6 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 25 Oct 2020 21:59:14 +0100 Subject: [PATCH 03/35] Create send data discussion --- front/dist/resources/style/style.css | 77 ++++++++++-- front/src/Phaser/Login/LoginScene.ts | 8 -- front/src/WebRtc/Discussion.ts | 124 ------------------- front/src/WebRtc/DiscussionManager.ts | 169 ++++++++++++++++++++++++++ front/src/WebRtc/MediaManager.ts | 25 ++++ front/src/WebRtc/ScreenSharingPeer.ts | 3 +- front/src/WebRtc/SimplePeer.ts | 10 +- front/src/WebRtc/VideoPeer.ts | 34 ++++-- 8 files changed, 294 insertions(+), 156 deletions(-) delete mode 100644 front/src/WebRtc/Discussion.ts create mode 100644 front/src/WebRtc/DiscussionManager.ts diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index d1f5e7f7..dec0630f 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -689,16 +689,17 @@ div.modal-report-user{ /*MESSAGE*/ .discussion{ position: fixed; - right: -300px; + left: -300px; width: 220px; height: 100%; - background-color: #000000; + background-color: #333333; padding: 20px; + transition: all 0.5s ease; } .discussion.active{ - right: 0; + left: 0; } -.discussion .toggle-btn{ +.discussion .active-btn{ display: none; cursor: pointer; height: 50px; @@ -706,22 +707,36 @@ div.modal-report-user{ background-color: #2d2d2dba; position: absolute; top: calc(50% - 25px); - margin-left: -125px; + margin-left: 315px; border-radius: 50%; border: none; transition: all 0.5s ease; } -.discussion .toggle-btn.active{ +.discussion .active-btn.active{ display: block; } -.discussion .toggle-btn:hover { +.discussion .active-btn:hover { transform: scale(1.1) rotateY(3.142rad); } -.discussion .toggle-btn img{ +.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; @@ -793,13 +808,22 @@ div.modal-report-user{ .discussion .messages{ position: absolute; - height: calc(100% - 300px); + height: calc(100% - 360px); + overflow-x: hidden; + overflow-y: scroll; + 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{ @@ -811,3 +835,38 @@ div.modal-report-user{ 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; +} + +.discussion .send-message img{ + position: absolute; + margin-right: 10px; + width: 20px; + height: 20px; + background-color: #ffffff69; +} +.discussion .send-message img:hover{ + background-color: #ffffff; +} + diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 55321124..2c5c1882 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -9,7 +9,6 @@ import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {SelectCharacterSceneName} from "./SelectCharacterScene"; import {ResizableScene} from "./ResizableScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; -import {Discussion} from "../../WebRtc/Discussion"; //todo: put this constants in a dedicated file export const LoginSceneName = "LoginScene"; @@ -75,13 +74,6 @@ export class LoginScene extends ResizableScene { }); cypressAsserter.initFinished(); - - const discussion = new Discussion('test'); - discussion.addParticipant('GRP'); - discussion.addParticipant('LOL'); - - discussion.addMessage('GRP', 'ceci est un test d\'envoi de message', true); - discussion.addMessage('LOL', 'ceci est un test d\'envoi de message'); } update(time: number, delta: number): void { diff --git a/front/src/WebRtc/Discussion.ts b/front/src/WebRtc/Discussion.ts deleted file mode 100644 index b7307745..00000000 --- a/front/src/WebRtc/Discussion.ts +++ /dev/null @@ -1,124 +0,0 @@ -import {HtmlUtils} from "./HtmlUtils"; - -export class Discussion { - - private mainContainer: HTMLDivElement; - - private divDiscuss?: HTMLDivElement; - private divParticipants?: HTMLDivElement; - private nbpParticipants?: HTMLParagraphElement; - private divMessages?: HTMLParagraphElement; - - private participants: Map = new Map(); - - private activeDiscussion: boolean = false; - - constructor(name: string) { - this.mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); - this.createDiscussPart(name); - } - - private createDiscussPart(name: string) { - this.divDiscuss = document.createElement('div'); - this.divDiscuss.classList.add('discussion'); - - const buttonToggleDiscussion = document.createElement('button'); - buttonToggleDiscussion.classList.add('toggle-btn'); - buttonToggleDiscussion.classList.add('active'); - buttonToggleDiscussion.innerHTML = ``; - buttonToggleDiscussion.addEventListener('click', () => { - if(this.activeDiscussion){ - this.activeDiscussion = false; - this.divDiscuss?.classList.remove('active'); - buttonToggleDiscussion.classList.add('active'); - }else{ - this.activeDiscussion = true; - this.divDiscuss?.classList.add('active'); - buttonToggleDiscussion.classList.remove('active'); - } - }); - this.divDiscuss.appendChild(buttonToggleDiscussion); - - const myName = 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.divDiscuss.appendChild(myName); - this.divDiscuss.appendChild(this.nbpParticipants); - this.divDiscuss.appendChild(this.divParticipants); - this.divDiscuss.appendChild(this.divMessages); - - //append in main container - this.mainContainer.appendChild(this.divDiscuss); - } - - public addParticipant(name: string, img?: string) { - const divParticipant = document.createElement('div'); - divParticipant.classList.add('participant'); - - const divImgParticipant = document.createElement('img'); - divImgParticipant.src = 'resources/logos/boy.svg'; - if (img) { - divImgParticipant.src = img; - } - const divPParticipant = document.createElement('p'); - divPParticipant.innerText = name; - - const reportBanUserAction = document.createElement('button'); - reportBanUserAction.classList.add('report-btn') - reportBanUserAction.innerText = 'Report'; - reportBanUserAction.addEventListener('click', () => { - //TODO report user - console.log('report'); - }); - - divParticipant.appendChild(divImgParticipant); - divParticipant.appendChild(divPParticipant); - divParticipant.appendChild(reportBanUserAction); - - this.divParticipants?.appendChild(divParticipant); - this.participants.set(name, divParticipant); - - 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 = document.createElement('div'); - divMessage.classList.add('message'); - if(isMe){ - divMessage.classList.add('me'); - } - - const pMessage = document.createElement('p'); - const date = new Date(); - if(isMe){ - name = 'Moi'; - } - pMessage.innerHTML = `${name} - - ${date.getHours()}:${date.getMinutes()} - `; - divMessage.appendChild(pMessage); - - const userMessage = document.createElement('p'); - userMessage.innerText = message; - divMessage.appendChild(userMessage); - - this.divMessages?.appendChild(divMessage); - } - -} \ No newline at end of file diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts new file mode 100644 index 00000000..4663adcf --- /dev/null +++ b/front/src/WebRtc/DiscussionManager.ts @@ -0,0 +1,169 @@ +import {HtmlUtils} from "./HtmlUtils"; +import {UpdatedLocalStreamCallback} from "./MediaManager"; +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 participants: Map = new Map(); + + private activeDiscussion: boolean = false; + + private sendMessageCallBack : Set = new Set(); + + constructor(name: string) { + this.mainContainer = HtmlUtils.getElementByIdOrFail('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'); + const buttonActiveDiscussion: HTMLButtonElement = document.createElement('button'); + buttonCloseDiscussion.classList.add('close-btn'); + buttonCloseDiscussion.innerHTML = ``; + buttonCloseDiscussion.addEventListener('click', () => { + this.activeDiscussion = false; + this.divDiscuss?.classList.remove('active'); + buttonActiveDiscussion.classList.add('active'); + }); + buttonActiveDiscussion.classList.add('active-btn'); + buttonActiveDiscussion.classList.add('active'); + buttonActiveDiscussion.innerHTML = ``; + buttonActiveDiscussion.addEventListener('click', () => { + this.activeDiscussion = true; + this.divDiscuss?.classList.add('active'); + buttonActiveDiscussion.classList.remove('active'); + }); + this.divDiscuss.appendChild(buttonCloseDiscussion); + this.divDiscuss.appendChild(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 = "

Local messages

" + + 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(); + this.addMessage(name, inputMessage.value, true); + for(const callback of this.sendMessageCallBack) { + 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) { + 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) { + const reportBanUserAction: HTMLButtonElement = document.createElement('button'); + reportBanUserAction.classList.add('report-btn') + reportBanUserAction.innerText = 'Report'; + reportBanUserAction.addEventListener('click', () => { + //TODO report user + console.log('report'); + }); + divParticipant.appendChild(reportBanUserAction); + } + + this.divParticipants?.appendChild(divParticipant); + this.participants.set(userId, divParticipant); + + 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 = `${name} + + ${date.getHours()}:${date.getMinutes()} + `; + 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(); + } + } + + public onSendMessageCallback(callback: SendMessageCallback): void { + this.sendMessageCallBack.add(callback); + } + +} \ No newline at end of file diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 9c3563ad..660aa599 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,5 +1,6 @@ import {DivImportance, layoutManager} from "./LayoutManager"; import {HtmlUtils} from "./HtmlUtils"; +import {DiscussionManager, SendMessageCallback} from "./DiscussionManager"; declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any const videoConstraint: boolean|MediaTrackConstraints = { @@ -38,6 +39,8 @@ export class MediaManager { private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; + private discussionManager: DiscussionManager; + constructor() { this.myCamVideo = HtmlUtils.getElementByIdOrFail('myCamVideo'); @@ -89,6 +92,8 @@ export class MediaManager { this.disableScreenSharing(); //update tracking }); + + this.discussionManager = new DiscussionManager('test'); } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -363,6 +368,9 @@ export class MediaManager { } this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); + + //permit to create participant in discussion part + this.addNewParticipant(userId, userName); } addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ @@ -437,6 +445,9 @@ export class MediaManager { removeActiveVideo(userId: string){ layoutManager.remove(userId); this.remoteVideo.delete(userId); + + //permit to remove user in discussion part + this.removeParticipant(userId); } removeActiveScreenSharingVideo(userId: string) { this.removeActiveVideo(`screen-sharing-${userId}`) @@ -556,7 +567,21 @@ export class MediaManager { mainContainer.appendChild(divReport); } + public addNewParticipant(userId: number|string, name: string|undefined, img?: string){ + this.discussionManager.addParticipant(userId, name, img); + } + public removeParticipant(userId: number|string){ + this.discussionManager.removeParticipant(userId) + } + + public addNewMessage(name: string, message: string, isMe: boolean = false){ + this.discussionManager.addMessage(name, message, isMe) + } + + public addSendMessageCallback(callback: SendMessageCallback){ + this.discussionManager.onSendMessageCallback(callback) + } } export const mediaManager = new MediaManager(); diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 3efee1c3..a6cf679b 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -2,6 +2,7 @@ import * as SimplePeerNamespace from "simple-peer"; import {mediaManager} from "./MediaManager"; import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable"; import {RoomConnection} from "../Connexion/RoomConnection"; +import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -148,6 +149,6 @@ export class ScreenSharingPeer extends Peer { public stopPushingScreenSharingToRemoteUser(stream: MediaStream) { this.removeStream(stream); - this.write(new Buffer(JSON.stringify({streamEnded: true}))); + this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true}))); } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index eb2ee42b..a9c24f17 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -10,7 +10,7 @@ import { UpdatedLocalStreamCallback } from "./MediaManager"; import {ScreenSharingPeer} from "./ScreenSharingPeer"; -import {VideoPeer} from "./VideoPeer"; +import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer"; import {RoomConnection} from "../Connexion/RoomConnection"; export interface UserSimplePeerInterface{ @@ -145,6 +145,12 @@ export class SimplePeer { mediaManager.addActiveVideo("" + user.userId, reportCallback, name); const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); + + //permit to send message + mediaManager.addSendMessageCallback((message: string) => { + peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: name?.toUpperCase(), message: message}))); + }); + peer.toClose = false; // 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! @@ -318,7 +324,7 @@ export class SimplePeer { throw new Error('While adding media, cannot find user with ID ' + userId); } 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){ return; diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index fb34f29e..f8bfa3f9 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -5,6 +5,8 @@ import {RoomConnection} from "../Connexion/RoomConnection"; 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. */ @@ -78,19 +80,27 @@ export class VideoPeer extends Peer { }); this.on('data', (chunk: Buffer) => { - const constraint = JSON.parse(chunk.toString('utf8')); - console.log("data", constraint); - if (constraint.audio) { - mediaManager.enabledMicrophoneByUserId(this.userId); - } else { - mediaManager.disabledMicrophoneByUserId(this.userId); + const message = JSON.parse(chunk.toString('utf8')); + console.log("data", message); + + if(message.type === MESSAGE_TYPE_CONSTRAINT) { + const constraint = message; + if (constraint.audio) { + mediaManager.enabledMicrophoneByUserId(this.userId); + } else { + mediaManager.disabledMicrophoneByUserId(this.userId); + } + + if (constraint.video || constraint.screen) { + mediaManager.enabledVideoByUserId(this.userId); + } else { + this.stream(undefined); + mediaManager.disabledVideoByUserId(this.userId); + } } - if (constraint.video || constraint.screen) { - mediaManager.enabledVideoByUserId(this.userId); - } else { - this.stream(undefined); - mediaManager.disabledVideoByUserId(this.userId); + if(message.type === 'message') { + mediaManager.addNewMessage(message.name, message.message); } }); @@ -163,7 +173,7 @@ export class VideoPeer extends Peer { private pushVideoToRemoteUser() { try { 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){ return; From bdb504722ff75c4add12a3e25cc4f4c74c14b516 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 25 Oct 2020 22:41:31 +0100 Subject: [PATCH 04/35] Fix style -webkit-focus --- front/dist/resources/style/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index dec0630f..234425aa 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -1,6 +1,11 @@ body{ overflow: hidden; } +body button:focus, +body img:focus, +body input:focus { + outline: -webkit-focus-ring-color auto 0; +} body .message-info{ width: 20%; height: auto; From 1e096af43b3026a5e8ced762eb35da853d750d27 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 26 Oct 2020 00:03:30 +0100 Subject: [PATCH 05/35] Fix font family --- front/dist/resources/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 234425aa..762b44cd 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -1,3 +1,6 @@ +*{ + font-family: 'Open Sans', sans-serif; +} body{ overflow: hidden; } From 850be63284419a83a287ff146a320a90c1838cba Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 26 Oct 2020 00:26:10 +0100 Subject: [PATCH 06/35] Fix style --- front/dist/resources/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 762b44cd..4b06d926 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -792,7 +792,7 @@ div.modal-report-user{ .discussion .participants .participant p{ font-size: 16px; margin-left: 10px; - margin-top: 8px; + margin-top: 2px; } .discussion .participants .participant button.report-btn{ From f95f38b09251a6a796e3a7b16bf27d365056ea9f Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 26 Oct 2020 14:13:51 +0100 Subject: [PATCH 07/35] Permit to remove callback --- front/src/WebRtc/DiscussionManager.ts | 9 +++++---- front/src/WebRtc/MediaManager.ts | 4 ++-- front/src/WebRtc/SimplePeer.ts | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 4663adcf..d2a52632 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -15,7 +15,7 @@ export class DiscussionManager { private activeDiscussion: boolean = false; - private sendMessageCallBack : Set = new Set(); + private sendMessageCallBack : Map = new Map(); constructor(name: string) { this.mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); @@ -71,7 +71,7 @@ export class DiscussionManager { if (event.key === 'Enter') { event.preventDefault(); this.addMessage(name, inputMessage.value, true); - for(const callback of this.sendMessageCallBack) { + for(const callback of this.sendMessageCallBack.values()) { callback(inputMessage.value); } inputMessage.value = ""; @@ -160,10 +160,11 @@ export class DiscussionManager { if(element){ element.remove(); } + this.sendMessageCallBack.delete(userId); } - public onSendMessageCallback(callback: SendMessageCallback): void { - this.sendMessageCallBack.add(callback); + public onSendMessageCallback(userId: string|number, callback: SendMessageCallback): void { + this.sendMessageCallBack.set(userId, callback); } } \ No newline at end of file diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 660aa599..6c33bc32 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -579,8 +579,8 @@ export class MediaManager { this.discussionManager.addMessage(name, message, isMe) } - public addSendMessageCallback(callback: SendMessageCallback){ - this.discussionManager.onSendMessageCallback(callback) + public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){ + this.discussionManager.onSendMessageCallback(userId, callback) } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index a9c24f17..72c9d890 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -147,7 +147,7 @@ export class SimplePeer { const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); //permit to send message - mediaManager.addSendMessageCallback((message: string) => { + mediaManager.addSendMessageCallback(user.userId,(message: string) => { peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: name?.toUpperCase(), message: message}))); }); From 55c1bcee66ab8db8c86af4649d34a74a44c6b339 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 27 Oct 2020 20:18:25 +0100 Subject: [PATCH 08/35] Add callback report in discussion part --- front/dist/resources/style/style.css | 8 +++++--- front/src/WebRtc/DiscussionManager.ts | 18 ++++++++++++------ front/src/WebRtc/MediaManager.ts | 10 +++++----- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 4b06d926..6eb6f2b5 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -82,13 +82,14 @@ body .message-info.warning{ position: absolute; right: 15px; bottom: 15px; - border-radius: 15px; + border-radius: 15px 15px 15px 15px; } video#myCamVideo{ width: 15vw; -webkit-transform: scaleX(-1); transform: scaleX(-1); + border-radius: 15px 15px 15px 15px; /*width: 200px;*/ /*height: 113px;*/ } @@ -365,6 +366,7 @@ body { 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; cursor: pointer; + border-radius: 15px 15px 15px 15px; } .sidebar > div:hover { @@ -525,7 +527,7 @@ body { border: 1px solid black; background-color: #00000000; color: #ffda01; - border-radius: 10px; + border-radius: 15px; padding: 10px 30px; transition: all .2s ease; } @@ -662,7 +664,7 @@ div.modal-report-user{ border: 1px solid black; background-color: #00000000; color: #ffda01; - border-radius: 10px; + border-radius: 15px; padding: 10px 30px; transition: all .2s ease; } diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index d2a52632..a65ede25 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,9 +1,8 @@ import {HtmlUtils} from "./HtmlUtils"; -import {UpdatedLocalStreamCallback} from "./MediaManager"; +import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager"; export type SendMessageCallback = (message:string) => void; export class DiscussionManager { - private mainContainer: HTMLDivElement; private divDiscuss?: HTMLDivElement; @@ -17,7 +16,7 @@ export class DiscussionManager { private sendMessageCallBack : Map = new Map(); - constructor(name: string) { + constructor(private mediaManager: MediaManager, name: string) { this.mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); this.createDiscussPart(name); } @@ -86,7 +85,13 @@ export class DiscussionManager { this.addParticipant('me', 'Moi', undefined, true); } - public addParticipant(userId: number|string, name: string|undefined, img?: string|undefined, isMe: boolean = false) { + 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}`; @@ -110,8 +115,9 @@ export class DiscussionManager { reportBanUserAction.classList.add('report-btn') reportBanUserAction.innerText = 'Report'; reportBanUserAction.addEventListener('click', () => { - //TODO report user - console.log('report'); + if(reportCallback) { + this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback); + } }); divParticipant.appendChild(reportBanUserAction); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 6c33bc32..9ad2438d 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -93,7 +93,7 @@ export class MediaManager { //update tracking }); - this.discussionManager = new DiscussionManager('test'); + this.discussionManager = new DiscussionManager(this,'test'); } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -370,7 +370,7 @@ export class MediaManager { this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); //permit to create participant in discussion part - this.addNewParticipant(userId, userName); + this.addNewParticipant(userId, userName, undefined, reportCallBack); } addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ @@ -510,7 +510,7 @@ export class MediaManager { return color; } - private showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ + public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ //create report text area const mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); @@ -567,8 +567,8 @@ export class MediaManager { mainContainer.appendChild(divReport); } - public addNewParticipant(userId: number|string, name: string|undefined, img?: string){ - this.discussionManager.addParticipant(userId, name, img); + 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){ From 80355e6b85c331c1197546c94b33d101d58d1384 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 27 Oct 2020 20:46:53 +0100 Subject: [PATCH 09/35] fix layout mode click when discussion is activated --- front/dist/resources/style/style.css | 3 +++ front/src/Phaser/Game/GameScene.ts | 4 ++++ front/src/WebRtc/DiscussionManager.ts | 3 +++ front/src/WebRtc/MediaManager.ts | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 6eb6f2b5..a01b1723 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -379,6 +379,7 @@ body { justify-content: center; flex-direction: column; overflow: hidden; + border-radius: 15px; } .chat-mode { @@ -622,6 +623,7 @@ div.modal-report-user{ left: calc(50% - 400px); top: 100px; background-color: #000000ad; + border-radius: 15px; } .modal-report-user textarea{ @@ -633,6 +635,7 @@ div.modal-report-user{ color: white; width: calc(100% - 60px); margin: 30px; + border-radius: 15px; } .modal-report-user img{ diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index bfc6402e..39b40c74 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -643,6 +643,10 @@ export class GameScene extends ResizableScene implements CenterListener { } private switchLayoutMode(): void { + //if discussion is activated, this layout cannot be activated + if(mediaManager.activatedDiscussion){ + return; + } const mode = layoutManager.getLayoutMode(); if (mode === LayoutMode.Presentation) { layoutManager.switchLayoutMode(LayoutMode.VideoChat); diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index a65ede25..cd063996 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -173,4 +173,7 @@ export class DiscussionManager { this.sendMessageCallBack.set(userId, callback); } + get activatedDiscussion(){ + return this.activeDiscussion; + } } \ No newline at end of file diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 9ad2438d..9ce449c0 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -582,6 +582,10 @@ export class MediaManager { public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){ this.discussionManager.onSendMessageCallback(userId, callback) } + + get activatedDiscussion(){ + return this.discussionManager.activatedDiscussion; + } } export const mediaManager = new MediaManager(); From a2dd3bd6ab1b530542741a6a6ebabbc9e6198752 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 30 Oct 2020 20:35:45 +0100 Subject: [PATCH 10/35] Fix style --- front/dist/resources/style/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 9e14340e..e26256b8 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -719,6 +719,7 @@ div.modal-report-user{ .discussion{ position: fixed; left: -300px; + top: 0px; width: 220px; height: 100%; background-color: #333333; From 6ef2148a34aeacc8de7caedea3810d3df63d391b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Wed, 4 Nov 2020 13:07:38 +0100 Subject: [PATCH 11/35] Fix feedback @moufmouf --- front/src/WebRtc/DiscussionManager.ts | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index cd063996..37767ffa 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -9,6 +9,7 @@ export class DiscussionManager { private divParticipants?: HTMLDivElement; private nbpParticipants?: HTMLParagraphElement; private divMessages?: HTMLParagraphElement; + private buttonActiveDiscussion?: HTMLButtonElement; private participants: Map = new Map(); @@ -26,24 +27,23 @@ export class DiscussionManager { this.divDiscuss.classList.add('discussion'); const buttonCloseDiscussion: HTMLButtonElement = document.createElement('button'); - const buttonActiveDiscussion: HTMLButtonElement = document.createElement('button'); + this.buttonActiveDiscussion = document.createElement('button'); buttonCloseDiscussion.classList.add('close-btn'); buttonCloseDiscussion.innerHTML = ``; buttonCloseDiscussion.addEventListener('click', () => { this.activeDiscussion = false; this.divDiscuss?.classList.remove('active'); - buttonActiveDiscussion.classList.add('active'); + this.showButtonDiscussionBtn(); }); - buttonActiveDiscussion.classList.add('active-btn'); - buttonActiveDiscussion.classList.add('active'); - buttonActiveDiscussion.innerHTML = ``; - buttonActiveDiscussion.addEventListener('click', () => { + this.buttonActiveDiscussion.classList.add('active-btn'); + this.buttonActiveDiscussion.innerHTML = ``; + this.buttonActiveDiscussion.addEventListener('click', () => { this.activeDiscussion = true; this.divDiscuss?.classList.add('active'); - buttonActiveDiscussion.classList.remove('active'); + this.hideButtonDiscussionBtn(); }); this.divDiscuss.appendChild(buttonCloseDiscussion); - this.divDiscuss.appendChild(buttonActiveDiscussion); + this.divDiscuss.appendChild(this.buttonActiveDiscussion); const myName: HTMLParagraphElement = document.createElement('p'); myName.innerText = name.toUpperCase(); @@ -123,7 +123,9 @@ export class DiscussionManager { } this.divParticipants?.appendChild(divParticipant); + this.participants.set(userId, divParticipant); + this.showButtonDiscussionBtn(); this.updateParticipant(this.participants.size); } @@ -165,7 +167,13 @@ export class DiscussionManager { 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); } @@ -176,4 +184,16 @@ export class DiscussionManager { 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 hideButtonDiscussionBtn(){ + this.buttonActiveDiscussion?.classList.remove('active'); + } } \ No newline at end of file From c4f28a0a8d213227b2033a5127052306775c9d28 Mon Sep 17 00:00:00 2001 From: Jacem Chaieb Date: Mon, 9 Nov 2020 12:24:45 +0100 Subject: [PATCH 12/35] Add support for QWERTY keyboards --- front/src/Phaser/UserInput/UserInputManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index fa7080b5..636783bc 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -40,7 +40,9 @@ export class UserInputManager { initKeyBoardEvent(){ this.KeysCode = [ {event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z, false) }, + {event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W, false) }, {event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q, false) }, + {event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A, false) }, {event: UserInputEvent.MoveDown, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S, false) }, {event: UserInputEvent.MoveRight, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D, false) }, From 3333b3cee3beae37606c7f0cae7f7c5afd0dfe5e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 12:38:32 +0100 Subject: [PATCH 13/35] Fix feedback moufmouf --- front/dist/resources/style/style.css | 3 ++- front/src/Phaser/Game/GameScene.ts | 1 + front/src/WebRtc/DiscussionManager.ts | 36 ++++++++++++++++++++++++--- front/src/WebRtc/MediaManager.ts | 15 ++++++++--- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index e26256b8..fc055941 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -840,7 +840,7 @@ div.modal-report-user{ position: absolute; height: calc(100% - 360px); overflow-x: hidden; - overflow-y: scroll; + overflow-y: auto; max-width: calc(100% - 40px); width: calc(100% - 40px); } @@ -887,6 +887,7 @@ div.modal-report-user{ color: white; border-radius: 15px; border: none; + padding: 6px; } .discussion .send-message img{ diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index dd2694f8..6aa678a4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -387,6 +387,7 @@ export class GameScene extends ResizableScene implements CenterListener { //create input to move this.userInputManager = new UserInputManager(this); + mediaManager.setUserInputManager(this.userInputManager); //notify game manager can to create currentUser in map this.createCurrentPlayer(); diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 37767ffa..1671f661 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,5 +1,6 @@ import {HtmlUtils} from "./HtmlUtils"; import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager"; +import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; export type SendMessageCallback = (message:string) => void; export class DiscussionManager { @@ -17,6 +18,8 @@ export class DiscussionManager { private sendMessageCallBack : Map = new Map(); + private userInputManager?: UserInputManager; + constructor(private mediaManager: MediaManager, name: string) { this.mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); this.createDiscussPart(name); @@ -31,15 +34,13 @@ export class DiscussionManager { buttonCloseDiscussion.classList.add('close-btn'); buttonCloseDiscussion.innerHTML = ``; buttonCloseDiscussion.addEventListener('click', () => { - this.activeDiscussion = false; - this.divDiscuss?.classList.remove('active'); + this.hideDiscussion(); this.showButtonDiscussionBtn(); }); this.buttonActiveDiscussion.classList.add('active-btn'); this.buttonActiveDiscussion.innerHTML = ``; this.buttonActiveDiscussion.addEventListener('click', () => { - this.activeDiscussion = true; - this.divDiscuss?.classList.add('active'); + this.showDiscussion(); this.hideButtonDiscussionBtn(); }); this.divDiscuss.appendChild(buttonCloseDiscussion); @@ -69,6 +70,11 @@ export class DiscussionManager { 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); @@ -117,6 +123,8 @@ export class DiscussionManager { reportBanUserAction.addEventListener('click', () => { if(reportCallback) { this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback); + }else{ + console.info('report feature is not activated!'); } }); divParticipant.appendChild(reportBanUserAction); @@ -193,7 +201,27 @@ export class DiscussionManager { 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; + } } \ No newline at end of file diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 6dc35008..907b2c0b 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,6 +1,7 @@ import {DivImportance, layoutManager} from "./LayoutManager"; 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 const videoConstraint: boolean|MediaTrackConstraints = { @@ -41,6 +42,8 @@ export class MediaManager { private discussionManager: DiscussionManager; + private userInputManager?: UserInputManager; + constructor() { this.myCamVideo = HtmlUtils.getElementByIdOrFail('myCamVideo'); @@ -93,7 +96,7 @@ export class MediaManager { //update tracking }); - this.discussionManager = new DiscussionManager(this,'test'); + this.discussionManager = new DiscussionManager(this,''); } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -572,20 +575,24 @@ export class MediaManager { } public removeParticipant(userId: number|string){ - this.discussionManager.removeParticipant(userId) + this.discussionManager.removeParticipant(userId); } public addNewMessage(name: string, message: string, isMe: boolean = false){ - this.discussionManager.addMessage(name, message, isMe) + this.discussionManager.addMessage(name, message, isMe); } public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){ - this.discussionManager.onSendMessageCallback(userId, callback) + this.discussionManager.onSendMessageCallback(userId, callback); } get activatedDiscussion(){ return this.discussionManager.activatedDiscussion; } + + public setUserInputManager(userInputManager : UserInputManager){ + this.discussionManager.setUserInputManager(userInputManager); + } } export const mediaManager = new MediaManager(); From 0acbe20bbf9d2db310b25b1410c9c3a1a17911f1 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 13:00:14 +0100 Subject: [PATCH 14/35] Fix name send in message --- front/src/Phaser/Game/GameScene.ts | 2 +- front/src/WebRtc/SimplePeer.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6aa678a4..d34541dd 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -603,7 +603,7 @@ export class GameScene extends ResizableScene implements CenterListener { }); // 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.UserMessageManager = new UserMessageManager(this.connection); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 72c9d890..195b57b3 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -38,7 +38,7 @@ export class SimplePeer { private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; private readonly peerConnectionListeners: Array = new Array(); - 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. this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this); this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); @@ -148,7 +148,7 @@ export class SimplePeer { //permit to send message mediaManager.addSendMessageCallback(user.userId,(message: string) => { - peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: name?.toUpperCase(), message: message}))); + peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message}))); }); peer.toClose = false; From 142566dfde2fdaa92f940c152b9427ce8da86a7c Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 14:03:29 +0100 Subject: [PATCH 15/35] Fix error get media stream camera Fix error camera when user have not cam on computer --- front/src/WebRtc/MediaManager.ts | 57 +++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index ab6d9a3b..fa703fbe 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -38,6 +38,8 @@ export class MediaManager { private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; + private hasCamera = true; + constructor() { this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); @@ -136,6 +138,9 @@ export class MediaManager { } public enableCamera() { + if(!this.hasCamera){ + return; + } this.cinemaClose.style.display = "none"; this.cinemaBtn.classList.remove("disabled"); this.cinema.style.display = "block"; @@ -146,13 +151,7 @@ export class MediaManager { } public async disableCamera() { - 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(); - + this.disabledCameraView(); if (this.constraintsMedia.audio !== false) { const stream = await this.getCamera(); this.triggerUpdatedLocalStreamCallbacks(stream); @@ -161,6 +160,15 @@ 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() { this.microphoneClose.style.display = "none"; this.microphone.style.display = "block"; @@ -267,24 +275,33 @@ export class MediaManager { } } - try { - const stream = await navigator.mediaDevices.getUserMedia(this.constraintsMedia); + return this.getLocalStream().catch(() => { + console.info('Error get camera, trying with video option at null'); + this.disabledCameraView(); + return this.getLocalStream().then((stream : MediaStream) => { + this.hasCamera = false; + return stream; + }).catch((err) => { + console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err); + throw err; + }); + }); + //TODO resize remote cam + /*console.log(this.localStream.getTracks()); + let videoMediaStreamTrack = this.localStream.getTracks().find((media : MediaStreamTrack) => media.kind === "video"); + let {width, height} = videoMediaStreamTrack.getSettings(); + console.info(`${width}x${height}`); // 6*/ + } + + private getLocalStream() : Promise { + return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream : MediaStream) => { this.localStream = stream; this.myCamVideo.srcObject = this.localStream; - return stream; - - //TODO resize remote cam - /*console.log(this.localStream.getTracks()); - let videoMediaStreamTrack = this.localStream.getTracks().find((media : MediaStreamTrack) => media.kind === "video"); - let {width, height} = videoMediaStreamTrack.getSettings(); - console.info(`${width}x${height}`); // 6*/ - } catch (err) { - console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err); - this.localStream = null; + }).catch((err: Error) => { throw err; - } + }); } /** From 1080328afab761362f06c5fd1ebb55e1c0979c04 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 10 Nov 2020 15:22:30 +0100 Subject: [PATCH 16/35] FIX: cowebsite transitions are now queued to prevent conflicts --- front/src/WebRtc/CoWebsiteManager.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 46d03702..70d171a2 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -16,6 +16,11 @@ class CoWebsiteManager { private opened: iframeStates = iframeStates.closed; private observers = new Array(); + /** + * Quickly going in and out of an iframe trigger can create conflicts between the iframe states. + * So we use this promise to queue up every cowebsite state transition + */ + private currentOperationPromise: Promise = Promise.resolve(); private close(): HTMLDivElement { const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); @@ -52,7 +57,7 @@ class CoWebsiteManager { const onTimeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(), 2000); }); - Promise.race([onloadPromise, onTimeoutPromise]).then(() => { + this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => { this.open(); setTimeout(() => { this.fire(); @@ -65,7 +70,7 @@ class CoWebsiteManager { */ public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise): void { const cowebsiteDiv = this.load(); - callback(cowebsiteDiv).then(() => { + this.currentOperationPromise = this.currentOperationPromise.then(() => callback(cowebsiteDiv)).then(() => { this.open(); setTimeout(() => { this.fire(); @@ -74,14 +79,16 @@ class CoWebsiteManager { } public closeCoWebsite(): Promise { - return new Promise((resolve, reject) => { + this.currentOperationPromise = this.currentOperationPromise.then(() => new Promise((resolve, reject) => { + if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example const cowebsiteDiv = this.close(); this.fire(); setTimeout(() => { resolve(); setTimeout(() => cowebsiteDiv.innerHTML = '', 500) }, animationTime) - }); + })); + return this.currentOperationPromise; } public getGameSize(): {width: number, height: number} { From 5a1147866cffbaba1c92e389cd6fe87ed30222c9 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 10 Nov 2020 15:24:02 +0100 Subject: [PATCH 17/35] FIX: in dev mode, sockets won't be closed to prevent conflicts with live-reloading --- back/src/Enum/EnvironmentVariable.ts | 2 ++ back/src/Services/IoSocketHelpers.ts | 2 ++ docker-compose.yaml | 1 + 3 files changed, 5 insertions(+) diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 55fd1bb7..8be8d54d 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -10,6 +10,7 @@ const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ''; const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; +const DEV_MODE = process.env.DEV_MODE || false; export { SECRET_KEY, @@ -21,6 +22,7 @@ export { GROUP_RADIUS, ALLOW_ARTILLERY, CPU_OVERHEAT_THRESHOLD, + DEV_MODE, JITSI_URL, JITSI_ISS, SECRET_JITSI_KEY diff --git a/back/src/Services/IoSocketHelpers.ts b/back/src/Services/IoSocketHelpers.ts index acaa0bb9..1dbfa0bd 100644 --- a/back/src/Services/IoSocketHelpers.ts +++ b/back/src/Services/IoSocketHelpers.ts @@ -1,5 +1,6 @@ import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; +import {DEV_MODE} from "../Enum/EnvironmentVariable"; export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { socket.batchedMessages.addPayload(payload); @@ -52,6 +53,7 @@ export function emitError(Client: ExSocketInterface, message: string): void { export const pongMaxInterval = 30000; // the maximum duration (in ms) between pongs before we shutdown the connexion. export function refresLogoutTimerOnPong(ws: ExSocketInterface): void { + if (DEV_MODE) return; //this feature is disabled in dev mode as it clashes with live reload. if(ws.pongTimeout) clearTimeout(ws.pongTimeout); ws.pongTimeout = setTimeout(() => { ws.close(); diff --git a/docker-compose.yaml b/docker-compose.yaml index 482dfbcb..5deccb4d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,6 +78,7 @@ services: ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS + DEV_MODE: "1" volumes: - ./back:/usr/src/app labels: From 73fa0ecbfc9eabb5fb6a51386fe64632dad99d25 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 17:00:23 +0100 Subject: [PATCH 18/35] Fix init position and trigger layers properties --- front/src/Phaser/Game/GameScene.ts | 68 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 96648255..d632045f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -466,35 +466,7 @@ export class GameScene extends ResizableScene implements CenterListener { // From now, this game scene will be notified of reposition events layoutManager.setListener(this); - - 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); - } - }); + this.triggerOnMapLayerPropertyChange(); const camera = this.cameras.main; @@ -627,14 +599,49 @@ export class GameScene extends ResizableScene implements CenterListener { this.gameMap.setPosition(event.x, event.y); }) - this.scene.wake(); this.scene.sleep(ReconnectingSceneName); + //init connection in silent mode + this.connection.setSilent(true); + + //init user position and play trigger to check layers properties + this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); + return connection; }); } + private 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); + } + }); + } + private switchLayoutMode(): void { const mode = layoutManager.getLayoutMode(); if (mode === LayoutMode.Presentation) { @@ -713,6 +720,7 @@ export class GameScene extends ResizableScene implements CenterListener { */ //todo: push that into the gameManager private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){ + const room = new Room(roomId); gameManager.loadMap(room, this.scene); const exitSceneKey = roomId; From 9b64a970b57d094988073199451b47c857201ee1 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 10 Nov 2020 18:26:46 +0100 Subject: [PATCH 19/35] FIX: remove the ping and pong overrides server side in favor of and idleTimeout and added a manual ping client side --- back/src/Controller/IoSocketController.ts | 10 +++---- back/src/Enum/EnvironmentVariable.ts | 3 +-- back/src/Model/Websocket/ExSocketInterface.ts | 2 -- back/src/Services/IoSocketHelpers.ts | 26 ------------------- docker-compose.yaml | 1 - front/src/Connexion/RoomConnection.ts | 7 ++++- messages/messages.proto | 4 +++ 7 files changed, 14 insertions(+), 39 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index c71edd86..85549c03 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -20,9 +20,9 @@ import {parse} from "query-string"; import {jwtTokenManager} from "../Services/JWTTokenManager"; import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi"; import {SocketManager, socketManager} from "../Services/SocketManager"; -import {emitInBatch, pongMaxInterval, refresLogoutTimerOnPong, resetPing} from "../Services/IoSocketHelpers"; +import {emitInBatch} from "../Services/IoSocketHelpers"; import {clientEventsEmitter} from "../Services/ClientEventsEmitter"; -import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; +import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable"; export class IoSocketController { private nextUserId: number = 1; @@ -110,6 +110,7 @@ export class IoSocketController { this.app.ws('/room', { /* Options */ //compression: uWS.SHARED_COMPRESSOR, + idleTimeout: SOCKET_IDLE_TIMER, maxPayloadLength: 16 * 1024 * 1024, maxBackpressure: 65536, // Maximum 64kB of data in the buffer. //idleTimeout: 10, @@ -239,8 +240,6 @@ export class IoSocketController { // Let's join the room const client = this.initClient(ws); //todo: into the upgrade instead? socketManager.handleJoinRoom(client); - resetPing(client); - refresLogoutTimerOnPong(ws as ExSocketInterface); //get data information and show messages if (ADMIN_API_URL) { @@ -293,9 +292,6 @@ export class IoSocketController { drain: (ws) => { console.log('WebSocket backpressure: ' + ws.getBufferedAmount()); }, - pong(ws) { - refresLogoutTimerOnPong(ws as ExSocketInterface); - }, close: (ws, code, message) => { const Client = (ws as ExSocketInterface); try { diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 8be8d54d..3a2ac99e 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -10,7 +10,7 @@ const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ''; const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; -const DEV_MODE = process.env.DEV_MODE || false; +export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed export { SECRET_KEY, @@ -22,7 +22,6 @@ export { GROUP_RADIUS, ALLOW_ARTILLERY, CPU_OVERHEAT_THRESHOLD, - DEV_MODE, JITSI_URL, JITSI_ISS, SECRET_JITSI_KEY diff --git a/back/src/Model/Websocket/ExSocketInterface.ts b/back/src/Model/Websocket/ExSocketInterface.ts index e3d19138..c64a4952 100644 --- a/back/src/Model/Websocket/ExSocketInterface.ts +++ b/back/src/Model/Websocket/ExSocketInterface.ts @@ -25,8 +25,6 @@ export interface ExSocketInterface extends WebSocket, Identificable { emitInBatch: (payload: SubMessage) => void; batchedMessages: BatchMessage; batchTimeout: NodeJS.Timeout|null; - pingTimeout: NodeJS.Timeout|null; - pongTimeout: NodeJS.Timeout|null; disconnecting: boolean, tags: string[], textures: CharacterTexture[], diff --git a/back/src/Services/IoSocketHelpers.ts b/back/src/Services/IoSocketHelpers.ts index 1dbfa0bd..9c27c59a 100644 --- a/back/src/Services/IoSocketHelpers.ts +++ b/back/src/Services/IoSocketHelpers.ts @@ -1,6 +1,5 @@ import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; -import {DEV_MODE} from "../Enum/EnvironmentVariable"; export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { socket.batchedMessages.addPayload(payload); @@ -19,22 +18,6 @@ export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): voi socket.batchTimeout = null; }, 100); } - - // If we send a message, we don't need to keep the connection alive - resetPing(socket); -} - -export function resetPing(ws: ExSocketInterface): void { - if (ws.pingTimeout) { - clearTimeout(ws.pingTimeout); - } - ws.pingTimeout = setTimeout(() => { - if (ws.disconnecting) { - return; - } - ws.ping(); - resetPing(ws); - }, 29000); } export function emitError(Client: ExSocketInterface, message: string): void { @@ -50,12 +33,3 @@ export function emitError(Client: ExSocketInterface, message: string): void { console.warn(message); } -export const pongMaxInterval = 30000; // the maximum duration (in ms) between pongs before we shutdown the connexion. - -export function refresLogoutTimerOnPong(ws: ExSocketInterface): void { - if (DEV_MODE) return; //this feature is disabled in dev mode as it clashes with live reload. - if(ws.pongTimeout) clearTimeout(ws.pongTimeout); - ws.pongTimeout = setTimeout(() => { - ws.close(); - }, pongMaxInterval); -} diff --git a/docker-compose.yaml b/docker-compose.yaml index 5deccb4d..482dfbcb 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,7 +78,6 @@ services: ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS - DEV_MODE: "1" volumes: - ./back:/usr/src/app labels: diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 19d011ff..b25e2d76 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -26,6 +26,7 @@ import { QueryJitsiJwtMessage, SendJitsiJwtMessage, CharacterLayerMessage, + PingMessage, SendUserMessage } from "../Messages/generated/messages_pb" @@ -42,6 +43,8 @@ import { } from "./ConnexionModels"; import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character"; +const manualPingDelay = 20000; + export class RoomConnection implements RoomConnection { private readonly socket: WebSocket; private userId: number|null = null; @@ -84,7 +87,9 @@ export class RoomConnection implements RoomConnection { this.socket.binaryType = 'arraybuffer'; this.socket.onopen = (ev) => { - //console.log('WS connected'); + //we manually ping every 20s to not be logged out by the server, even when the game is in background. + const pingMessage = new PingMessage(); + setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay); }; this.socket.onmessage = (messageEvent) => { diff --git a/messages/messages.proto b/messages/messages.proto index 89353825..6e0b47df 100644 --- a/messages/messages.proto +++ b/messages/messages.proto @@ -38,6 +38,10 @@ message CharacterLayerMessage { /*********** CLIENT TO SERVER MESSAGES *************/ +message PingMessage { + +} + message SetPlayerDetailsMessage { string name = 1; repeated string characterLayers = 2; From b064f01f97cd91aefb76d539d47cfa14058bb3f7 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Thu, 12 Nov 2020 14:51:19 +0100 Subject: [PATCH 20/35] FEAT: added a prometheus gauge for the number of active rooms --- back/src/Services/GaugeManager.ts | 12 ++++++++++++ back/src/Services/SocketManager.ts | 2 ++ 2 files changed, 14 insertions(+) diff --git a/back/src/Services/GaugeManager.ts b/back/src/Services/GaugeManager.ts index f8af822b..80712856 100644 --- a/back/src/Services/GaugeManager.ts +++ b/back/src/Services/GaugeManager.ts @@ -6,8 +6,13 @@ class GaugeManager { private nbClientsPerRoomGauge: Gauge; private nbGroupsPerRoomGauge: Gauge; private nbGroupsPerRoomCounter: Counter; + private nbRoomsGauge: Gauge; constructor() { + this.nbRoomsGauge = new Gauge({ + name: 'workadventure_nb_rooms', + help: 'Number of active rooms' + }); this.nbClientsGauge = new Gauge({ name: 'workadventure_nb_sockets', help: 'Number of connected sockets', @@ -31,6 +36,13 @@ class GaugeManager { }); } + incNbRoomGauge(): void { + this.nbRoomsGauge.inc(); + } + decNbRoomGauge(): void { + this.nbRoomsGauge.dec(); + } + incNbClientPerRoomGauge(roomId: string): void { this.nbClientsGauge.inc(); this.nbClientsPerRoomGauge.inc({ room: roomId }); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 4bd26778..97f008c4 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -351,6 +351,7 @@ export class SocketManager { world.leave(Client); if (world.isEmpty()) { this.Worlds.delete(Client.roomId); + gaugeManager.decNbRoomGauge(); } } //user leave previous room @@ -383,6 +384,7 @@ export class SocketManager { world.tags = data.tags world.policyType = Number(data.policy_type) } + gaugeManager.incNbRoomGauge(); this.Worlds.set(roomId, world); } return Promise.resolve(world) From 3c0f61d3b3af59775d2784d65a4c94c3e6d1bcd2 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Fri, 13 Nov 2020 12:11:59 +0100 Subject: [PATCH 21/35] HOTFIX: fixed server crash on /map --- back/src/Controller/MapController.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/back/src/Controller/MapController.ts b/back/src/Controller/MapController.ts index abe34886..c111ba64 100644 --- a/back/src/Controller/MapController.ts +++ b/back/src/Controller/MapController.ts @@ -1,11 +1,9 @@ -import {OK} from "http-status-codes"; -import {URL_ROOM_STARTED} from "../Enum/EnvironmentVariable"; import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js"; import {BaseController} from "./BaseController"; import {parse} from "query-string"; import {adminApi} from "../Services/AdminApi"; -//todo: delete this + export class MapController extends BaseController{ constructor(private App : TemplatedApp) { @@ -36,18 +34,21 @@ export class MapController extends BaseController{ res.writeStatus("400 Bad request"); this.addCorsHeaders(res); res.end("Expected organizationSlug parameter"); + return; } if (typeof query.worldSlug !== 'string') { console.error('Expected worldSlug parameter'); res.writeStatus("400 Bad request"); this.addCorsHeaders(res); res.end("Expected worldSlug parameter"); + return; } if (typeof query.roomSlug !== 'string' && query.roomSlug !== undefined) { console.error('Expected only one roomSlug parameter'); res.writeStatus("400 Bad request"); this.addCorsHeaders(res); res.end("Expected only one roomSlug parameter"); + return; } (async () => { From ebfe08fb92b09a7ce2b9356998fd42c199606abf Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 13 Nov 2020 12:15:33 +0100 Subject: [PATCH 22/35] Create new env admin url --- deeployer.libsonnet | 1 + docker-compose.yaml | 1 + front/src/Enum/EnvironmentVariable.ts | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index db86fbb4..bad951f1 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -33,6 +33,7 @@ "ports": [80], "env": { "API_URL": "api."+url, + "ADMIN_URL": "admin."+url, "JITSI_URL": env.JITSI_URL, "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", diff --git a/docker-compose.yaml b/docker-compose.yaml index 482dfbcb..7f21b16d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -27,6 +27,7 @@ services: HOST: "0.0.0.0" NODE_ENV: development API_URL: api.workadventure.localhost + ADMIN_URL: admin.workadventure.localhost STARTUP_COMMAND_1: yarn install TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443" TURN_USER: workadventure diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 60f9cd3b..cc314032 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,6 +1,6 @@ const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true"; const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "api.workadventure.localhost"); -const ADMIN_URL = API_URL.replace('api', 'admin'); +const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "admin.workadventure.localhost"); const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca"; const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com'; const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$'; From 0a2e967eca1ccda7f2ad55840cb6a7737b42c15b Mon Sep 17 00:00:00 2001 From: kharhamel Date: Fri, 13 Nov 2020 12:15:58 +0100 Subject: [PATCH 23/35] HOTFIX: fixed a server when trying to connect to the adminRoomSocket --- back/src/Controller/IoSocketController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 85549c03..f037a99d 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -43,6 +43,7 @@ export class IoSocketController { if (token !== ADMIN_API_TOKEN) { console.log('Admin access refused for token: '+token) res.writeStatus("401 Unauthorized").end('Incorrect token'); + return; } const roomId = query.roomId as string; From 733efd4a03ce5658375ea5c69e714f10e73caac9 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 13 Nov 2020 12:41:49 +0100 Subject: [PATCH 24/35] Add env admin url in webpack --- front/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/webpack.config.js b/front/webpack.config.js index 218b7374..f03c4fbc 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -45,7 +45,7 @@ module.exports = { new webpack.ProvidePlugin({ Phaser: 'phaser' }), - new webpack.EnvironmentPlugin(['API_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE']) + new webpack.EnvironmentPlugin(['API_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE']) ], }; From 2a6b2e0cbffc1e96196c39046c1a38bb36af50f3 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Fri, 13 Nov 2020 18:08:43 +0100 Subject: [PATCH 25/35] FIX: the jitsi iframe promise now throw an error if it cannot load in 10 secondes --- front/src/WebRtc/JitsiFactory.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 45b9b3cf..d95ba4e5 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -50,8 +50,9 @@ class JitsiFactory { delete options.jwt; } - return new Promise((resolve) => { + return new Promise((resolve, reject) => { options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations. + setTimeout(() => reject('Failed to load the iframe'), 10000); //failsafe in case the iframe is deleted before loading this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); this.jitsiApi.executeCommand('displayName', playerName); From 0fc57f3548e823afb293a9ae73243a2d814bb014 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 14 Nov 2020 14:04:11 +0100 Subject: [PATCH 26/35] Change resolution --- front/src/Enum/EnvironmentVariable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index cc314032..d3b6c809 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -6,7 +6,7 @@ const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.co const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$'; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true"; -const RESOLUTION = 3; +const RESOLUTION = 2; const ZOOM_LEVEL = 1/*3/4*/; const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player From 2d0fc1072ff984fa96a91c2cece2222eea35d54f Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 16 Nov 2020 15:05:51 +0100 Subject: [PATCH 27/35] Fix feedback --- front/src/Phaser/Game/GameScene.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d632045f..7c467862 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -602,9 +602,6 @@ export class GameScene extends ResizableScene implements CenterListener { this.scene.wake(); this.scene.sleep(ReconnectingSceneName); - //init connection in silent mode - this.connection.setSilent(true); - //init user position and play trigger to check layers properties this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); From 3d8d8cc3a96efd16e93752b38381f75e1c3ee718 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Mon, 16 Nov 2020 16:15:21 +0100 Subject: [PATCH 28/35] FIX: more cowebsite fixes --- front/src/WebRtc/CoWebsiteManager.ts | 46 ++++++++++++++-------------- front/src/WebRtc/JitsiFactory.ts | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 70d171a2..b625bf6e 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -20,32 +20,31 @@ class CoWebsiteManager { * Quickly going in and out of an iframe trigger can create conflicts between the iframe states. * So we use this promise to queue up every cowebsite state transition */ - private currentOperationPromise: Promise = Promise.resolve(); + private currentOperationPromise: Promise = Promise.resolve(); + private cowebsiteDiv: HTMLDivElement; - private close(): HTMLDivElement { - const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); - cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition - cowebsiteDiv.classList.add('hidden'); + constructor() { + this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); + } + + private close(): void { + this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition + this.cowebsiteDiv.classList.add('hidden'); this.opened = iframeStates.closed; - return cowebsiteDiv; } - private load(): HTMLDivElement { - const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); - cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition - cowebsiteDiv.classList.add('loading'); + private load(): void { + this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition + this.cowebsiteDiv.classList.add('loading'); this.opened = iframeStates.loading; - return cowebsiteDiv; } - private open(): HTMLDivElement { - const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); - cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition + private open(): void { + this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition this.opened = iframeStates.opened; - return cowebsiteDiv; } public loadCoWebsite(url: string): void { - const cowebsiteDiv = this.load(); - cowebsiteDiv.innerHTML = ''; + this.load(); + this.cowebsiteDiv.innerHTML = ''; const iframe = document.createElement('iframe'); iframe.id = 'cowebsite-iframe'; @@ -53,7 +52,7 @@ class CoWebsiteManager { const onloadPromise = new Promise((resolve) => { iframe.onload = () => resolve(); }); - cowebsiteDiv.appendChild(iframe); + this.cowebsiteDiv.appendChild(iframe); const onTimeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(), 2000); }); @@ -69,23 +68,23 @@ class CoWebsiteManager { * Just like loadCoWebsite but the div can be filled by the user. */ public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise): void { - const cowebsiteDiv = this.load(); - this.currentOperationPromise = this.currentOperationPromise.then(() => callback(cowebsiteDiv)).then(() => { + this.load(); + this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => { this.open(); setTimeout(() => { this.fire(); - }, animationTime) + }, animationTime); }).catch(() => this.closeCoWebsite()); } public closeCoWebsite(): Promise { this.currentOperationPromise = this.currentOperationPromise.then(() => new Promise((resolve, reject) => { if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example - const cowebsiteDiv = this.close(); + this.close(); this.fire(); setTimeout(() => { + this.cowebsiteDiv.innerHTML = ''; resolve(); - setTimeout(() => cowebsiteDiv.innerHTML = '', 500) }, animationTime) })); return this.currentOperationPromise; @@ -111,6 +110,7 @@ class CoWebsiteManager { } } + //todo: is it still useful to allow any kind of observers? public onStateChange(observer: CoWebsiteStateChangedCallback) { this.observers.push(observer); } diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index d95ba4e5..88e247d7 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -52,7 +52,7 @@ class JitsiFactory { return new Promise((resolve, reject) => { options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations. - setTimeout(() => reject('Failed to load the iframe'), 10000); //failsafe in case the iframe is deleted before loading + setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); this.jitsiApi.executeCommand('displayName', playerName); From b6b76cbfaf20698bceeedab0e09a6560ca5e406c Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 17 Nov 2020 15:02:21 +0100 Subject: [PATCH 29/35] FIX: creating an exit to the current map should not cause a crash anymore --- front/src/Phaser/Game/GameScene.ts | 112 +++++++++++------------------ 1 file changed, 42 insertions(+), 70 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 96648255..888f62e1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -342,46 +342,11 @@ export class GameScene extends ResizableScene implements CenterListener { throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.'); } - // If there is an init position passed - if (this.initPosition !== null) { - this.startX = this.initPosition.x; - this.startY = this.initPosition.y; - } else { - // Now, let's find the start layer - if (this.room.hash) { - for (const layer of this.mapFile.layers) { - if (this.room.hash === layer.name && layer.type === 'tilelayer' && this.isStartLayer(layer)) { - const startPosition = this.startUser(layer); - this.startX = startPosition.x; - this.startY = startPosition.y; - } - } - } - 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. - for (const layer of this.mapFile.layers) { - if (layer.type === 'tilelayer' && layer.name === "start") { - const startPosition = this.startUser(layer); - this.startX = startPosition.x; - this.startY = startPosition.y; - } - } - } - } - // Still no start position? Something is wrong with the map, we need a "start" layer. - if (this.startX === undefined) { - console.warn('This map is missing a layer named "start" that contains the available default start positions.'); - // Let's start in the middle of the map - this.startX = this.mapFile.width * 16; - this.startY = this.mapFile.height * 16; - } + this.initStartXAndStartY(); //add entities this.Objects = new Array(); - //init event click - this.EventToClickOnTile(); - //initialise list of other player this.MapPlayers = this.physics.add.group({immovable: true}); @@ -647,6 +612,40 @@ export class GameScene extends ResizableScene implements CenterListener { this.chatModeSprite.setFrame(3); } } + + private initStartXAndStartY() { + // If there is an init position passed + if (this.initPosition !== null) { + this.startX = this.initPosition.x; + this.startY = this.initPosition.y; + } else { + // Now, let's find the start layer + if (this.room.hash) { + this.initPositionFromLayerName(this.room.hash); + } + 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. + this.initPositionFromLayerName("start"); + } + } + // Still no start position? Something is wrong with the map, we need a "start" layer. + if (this.startX === undefined) { + console.warn('This map is missing a layer named "start" that contains the available default start positions.'); + // Let's start in the middle of the map + this.startX = this.mapFile.width * 16; + this.startY = this.mapFile.height * 16; + } + } + + private initPositionFromLayerName(layerName: string) { + for (const layer of this.mapFile.layers) { + if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === "start" || this.isStartLayer(layer))) { + const startPosition = this.startUser(layer); + this.startX = startPosition.x; + this.startY = startPosition.y; + } + } + } private getExitUrl(layer: ITiledMapLayer): string|undefined { return this.getProperty(layer, "exitUrl") as string|undefined; @@ -686,9 +685,6 @@ export class GameScene extends ResizableScene implements CenterListener { instance = this.instance; } - //console.log('existSceneUrl', exitSceneUrl); - //console.log('existSceneInstance', instance); - const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href; const absoluteExitSceneUrlWithoutProtocol = absoluteExitSceneUrl.toString().substr(absoluteExitSceneUrl.toString().indexOf('://')+3); const roomId = '_/'+instance+'/'+absoluteExitSceneUrlWithoutProtocol; @@ -706,11 +702,6 @@ export class GameScene extends ResizableScene implements CenterListener { this.loadNextGame(layer, mapWidth, fullPath); } - /** - * - * @param layer - * @param mapWidth - */ //todo: push that into the gameManager private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){ const room = new Room(roomId); @@ -745,9 +736,6 @@ export class GameScene extends ResizableScene implements CenterListener { } } - /** - * @param layer - */ private startUser(layer: ITiledMapLayer): PositionInterface { const tiles = layer.data; if (typeof(tiles) === 'string') { @@ -903,17 +891,6 @@ export class GameScene extends ResizableScene implements CenterListener { }); } - EventToClickOnTile(){ - // debug code to get a tile properties by clicking on it - /*this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ - //pixel position toz tile position - const tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); - if(tile){ - this.CurrentPlayer.say("Your touch " + tile.layer.name); - } - });*/ - } - /** * @param time * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. @@ -958,13 +935,19 @@ export class GameScene extends ResizableScene implements CenterListener { }); const nextSceneKey = this.checkToExit(); - if (nextSceneKey) { + 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; } } @@ -1066,11 +1049,6 @@ export class GameScene extends ResizableScene implements CenterListener { await Promise.all(loadPromises); player.addTextures(characterLayerList, 1); - //init collision - /*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { - CurrentPlayer.say("Hello, how are you ? "); - });*/ - } /** @@ -1113,7 +1091,6 @@ export class GameScene extends ResizableScene implements CenterListener { // We do not update the player position directly (because it is sent only every 200ms). // Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms. const playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY); - //console.log('Target position: ', player.x, player.y); this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); } @@ -1162,11 +1139,6 @@ export class GameScene extends ResizableScene implements CenterListener { /** * Sends to the server an event emitted by one of the ActionableItems. - * - * @param itemId - * @param eventName - * @param state - * @param parameters */ emitActionableEvent(itemId: number, eventName: string, state: unknown, parameters: unknown) { this.connection.emitActionableEvent(itemId, eventName, state, parameters); From 64d00bda562bf58dac42d3ffc606a9375bdc842e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 17 Nov 2020 16:46:46 +0100 Subject: [PATCH 30/35] Add function to show when message received --- front/src/WebRtc/DiscussionManager.ts | 8 ++++++-- front/src/WebRtc/MediaManager.ts | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 1671f661..097bf3a3 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -40,8 +40,7 @@ export class DiscussionManager { this.buttonActiveDiscussion.classList.add('active-btn'); this.buttonActiveDiscussion.innerHTML = ``; this.buttonActiveDiscussion.addEventListener('click', () => { - this.showDiscussion(); - this.hideButtonDiscussionBtn(); + this.showDiscussionPart(); }); this.divDiscuss.appendChild(buttonCloseDiscussion); this.divDiscuss.appendChild(this.buttonActiveDiscussion); @@ -224,4 +223,9 @@ export class DiscussionManager { public setUserInputManager(userInputManager : UserInputManager){ this.userInputManager = userInputManager; } + + public showDiscussionPart(){ + this.showDiscussion(); + this.hideButtonDiscussionBtn(); + } } \ No newline at end of file diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 1c60dda8..ce1878fb 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -597,6 +597,11 @@ export class MediaManager { 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){ From 25f6fa7d2acae06e3833b92011185240c89788d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 17 Nov 2020 18:27:33 +0100 Subject: [PATCH 31/35] Migrating to rlespinasse/github-slug-action@3.1.0 --- .github/workflows/build-and-deploy.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 390f2556..4cf2a8fb 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -20,7 +20,7 @@ jobs: # 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" uses: docker/build-push-action@v1 @@ -43,7 +43,7 @@ jobs: uses: actions/checkout@v2 # 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" uses: docker/build-push-action@v1 @@ -66,7 +66,7 @@ jobs: uses: actions/checkout@v2 # 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" uses: docker/build-push-action@v1 @@ -90,7 +90,7 @@ jobs: # 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" uses: docker/build-push-action@v1 @@ -114,7 +114,7 @@ jobs: uses: actions/checkout@v2 # 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 uses: thecodingmachine/deeployer@master From 62a1732e53c5f9a15ba7ae673a8068db7f3295e6 Mon Sep 17 00:00:00 2001 From: kevin Date: Wed, 18 Nov 2020 08:40:42 +0100 Subject: [PATCH 32/35] wording changes --- website/dist/index.html | 96 +++++++++++++++++---- website/dist/static/images/facebook_bw.png | Bin 0 -> 1409 bytes website/dist/static/images/linkedin_bw.png | Bin 0 -> 1712 bytes website/dist/static/images/twitter_bw.png | Bin 0 -> 1455 bytes website/src/sass/styles.scss | 45 +++++----- 5 files changed, 100 insertions(+), 41 deletions(-) create mode 100644 website/dist/static/images/facebook_bw.png create mode 100644 website/dist/static/images/linkedin_bw.png create mode 100644 website/dist/static/images/twitter_bw.png diff --git a/website/dist/index.html b/website/dist/index.html index 1e06204a..167cea49 100644 --- a/website/dist/index.html +++ b/website/dist/index.html @@ -10,6 +10,9 @@ gtag('js', new Date()); gtag('config', 'UA-10196481-11'); + if (window.location.host.endsWith("localhost")){ + window['ga-disable-UA-10196481-11'] = true; + } @@ -82,22 +85,32 @@
-

Your workplace
but better

-

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"

+

Meet your teammates

+

+ WorkAdventure preserves your social interaction while COVID is still out there. +

+

+ Stay connected with your teamworkers, by creating your own online workspace to work remotely. +

+

+ Stay connected with your clients by providing a dedicated digital place to organize meetings, workshops. +

+

+ Stay connected with your future collaborators by organizing online event. +

@@ -107,7 +120,7 @@
@@ -158,7 +171,7 @@

Click the button below to come and say hi!

- START IN PUBLIC MODE + TRY IT NOW !

@@ -166,10 +179,11 @@

You can also create a private room with your friends or your team !

To try, press button work in private

-

- - START WORKING IN PRIVATE -

+

+ + + CHOOSE YOU OWN MAP +

Don’t forget to activate your mic and camera, let’s play

@@ -179,7 +193,6 @@ @@ -82,22 +85,32 @@
-

Your workplace
but better

-

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"

+

Meet your teammates

+

+ WorkAdventure preserves your social interaction while COVID is still out there. +

+

+ Stay connected with your teamworkers, by creating your own online workspace to work remotely. +

+

+ Stay connected with your clients by providing a dedicated digital place to organize meetings, workshops. +

+

+ Stay connected with your future collaborators by organizing online event. +

@@ -107,7 +120,7 @@
@@ -158,7 +171,7 @@

Click the button below to come and say hi!

- START IN PUBLIC MODE + TRY IT NOW !

@@ -166,10 +179,11 @@

You can also create a private room with your friends or your team !

To try, press button work in private

-

- - START WORKING IN PRIVATE -

+

+ + + CHOOSE YOU OWN MAP +

Don’t forget to activate your mic and camera, let’s play

@@ -179,7 +193,6 @@