From 199ed1266a1ce419d398e8c527acb5b65a4483fa Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 24 Oct 2020 14:13:23 +0200 Subject: [PATCH 01/66] Switch off camera when user is not focused on WorkAdventure windows Feature to switch off camera when user is not focused on WorkAdventure windows after 10 seconds --- front/src/WebRtc/MediaManager.ts | 84 ++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 557f12fb..070f27f9 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -38,6 +38,9 @@ export class MediaManager { private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; + private previousConstraint : MediaStreamConstraints; + private timeoutBlurWindows?: NodeJS.Timeout; + constructor() { this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); @@ -89,6 +92,23 @@ export class MediaManager { this.disableScreenSharing(); //update tracking }); + + this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); + window.addEventListener('blur', () => { + if(this.timeoutBlurWindows){ + clearTimeout(this.timeoutBlurWindows); + } + this.timeoutBlurWindows = setTimeout(() => { + this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); + this.disableCamera(); + }, 10000); + }); + window.addEventListener('focus', () => { + if(this.timeoutBlurWindows){ + clearTimeout(this.timeoutBlurWindows); + } + this.applyPreviousConfig(); + }); } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -136,9 +156,7 @@ export class MediaManager { } private enableCamera() { - this.cinemaClose.style.display = "none"; - this.cinemaBtn.classList.remove("disabled"); - this.cinema.style.display = "block"; + this.enableCameraStyle(); this.constraintsMedia.video = videoConstraint; this.getCamera().then((stream: MediaStream) => { this.triggerUpdatedLocalStreamCallbacks(stream); @@ -146,11 +164,7 @@ export class MediaManager { } private 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.disableCameraStyle(); this.stopCamera(); if (this.constraintsMedia.audio !== false) { @@ -162,9 +176,7 @@ export class MediaManager { } private enableMicrophone() { - this.microphoneClose.style.display = "none"; - this.microphone.style.display = "block"; - this.microphoneBtn.classList.remove("disabled"); + this.enableMicrophoneStyle(); this.constraintsMedia.audio = true; this.getCamera().then((stream) => { @@ -173,10 +185,7 @@ export class MediaManager { } private async disableMicrophone() { - this.microphoneClose.style.display = "block"; - this.microphone.style.display = "none"; - this.microphoneBtn.classList.add("disabled"); - this.constraintsMedia.audio = false; + this.disableMicrophoneStyle(); this.stopMicrophone(); if (this.constraintsMedia.video !== false) { @@ -187,6 +196,51 @@ export class MediaManager { } } + private applyPreviousConfig() { + this.constraintsMedia = this.previousConstraint; + if(!this.constraintsMedia.video){ + this.disableCameraStyle(); + }else{ + this.enableCameraStyle(); + } + if(!this.constraintsMedia.audio){ + this.disableMicrophoneStyle() + }else{ + this.enableMicrophoneStyle() + } + + this.getCamera().then((stream: MediaStream) => { + this.triggerUpdatedLocalStreamCallbacks(stream); + }); + } + + private enableCameraStyle(){ + this.cinemaClose.style.display = "none"; + this.cinemaBtn.classList.remove("disabled"); + this.cinema.style.display = "block"; + } + + private disableCameraStyle(){ + this.cinemaClose.style.display = "block"; + this.cinema.style.display = "none"; + this.cinemaBtn.classList.add("disabled"); + this.constraintsMedia.video = false; + this.myCamVideo.srcObject = null; + } + + private enableMicrophoneStyle(){ + this.microphoneClose.style.display = "none"; + this.microphone.style.display = "block"; + this.microphoneBtn.classList.remove("disabled"); + } + + private disableMicrophoneStyle(){ + this.microphoneClose.style.display = "block"; + this.microphone.style.display = "none"; + this.microphoneBtn.classList.add("disabled"); + this.constraintsMedia.audio = false; + } + private enableScreenSharing() { this.monitorClose.style.display = "none"; this.monitor.style.display = "block"; From 69f3e511aba0a6c3d89c71ef93f20193bde9dc4c Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 24 Oct 2020 14:40:51 +0200 Subject: [PATCH 02/66] Ping status camera and microphone --- front/src/WebRtc/MediaManager.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 070f27f9..28771f63 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -109,6 +109,7 @@ export class MediaManager { } this.applyPreviousConfig(); }); + this.pingCameraStatus(); } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -619,6 +620,17 @@ export class MediaManager { mainContainer.appendChild(divReport); } + //ping always 30 seconds data on stream + private pingCameraStatus(){ + setTimeout(() => { + console.log('ping camera status'); + this.getCamera().then((stream: MediaStream) => { + this.triggerUpdatedLocalStreamCallbacks(stream); + this.pingCameraStatus(); + }); + }, 30000); + } + } From b6fe9e72e1aaf3622471e094839271a51a72710b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 25 Oct 2020 19:38:00 +0100 Subject: [PATCH 03/66] 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 04/66] 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 05/66] 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 06/66] 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 07/66] 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 08/66] 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 09/66] 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 997acd17ad8216ba2d4e0f641fa36823a2fdb0dc Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 26 Oct 2020 22:39:52 +0100 Subject: [PATCH 10/66] Update to use update function scene --- front/src/Phaser/Game/GameScene.ts | 7 ++-- front/src/WebRtc/MediaManager.ts | 60 +++++++++++++++++++++--------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index bfc6402e..46efc5e5 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -324,7 +324,7 @@ export class GameScene extends ResizableScene implements CenterListener { // Let's alter browser history let path = this.room.id; if (this.room.hash) { - path += '#'+this.room.hash; + path += '#' + this.room.hash; } window.history.pushState({}, 'WorkAdventure', path); @@ -486,7 +486,7 @@ export class GameScene extends ResizableScene implements CenterListener { this.stopJitsi(); } else { if (JITSI_PRIVATE_MODE) { - const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; + const adminTag = allProps.get("jitsiRoomAdminTag") as string | undefined; this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag); } else { @@ -634,7 +634,6 @@ export class GameScene extends ResizableScene implements CenterListener { this.gameMap.setPosition(event.x, event.y); }) - this.scene.wake(); this.scene.sleep(ReconnectingSceneName); @@ -973,6 +972,8 @@ export class GameScene extends ResizableScene implements CenterListener { this.scene.remove(this.scene.key); this.scene.start(nextSceneKey.key); } + + mediaManager.setLastUpdateScene(); } private checkToExit(): {key: string, hash: string} | null { diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 28771f63..1cb1bdbb 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -39,7 +39,10 @@ export class MediaManager { private monitorBtn: HTMLDivElement; private previousConstraint : MediaStreamConstraints; - private timeoutBlurWindows?: NodeJS.Timeout; + private focused : boolean = true; + + private lastUpdateScene : Date = new Date(); + private setTimeOutlastUpdateScene? : NodeJS.Timeout; constructor() { @@ -94,22 +97,30 @@ export class MediaManager { }); this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); - window.addEventListener('blur', () => { - if(this.timeoutBlurWindows){ - clearTimeout(this.timeoutBlurWindows); - } - this.timeoutBlurWindows = setTimeout(() => { - this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); - this.disableCamera(); - }, 10000); - }); - window.addEventListener('focus', () => { - if(this.timeoutBlurWindows){ - clearTimeout(this.timeoutBlurWindows); - } - this.applyPreviousConfig(); - }); this.pingCameraStatus(); + + this.checkActiveUser(); + } + + public setLastUpdateScene(){ + this.lastUpdateScene = new Date(); + } + + public blurCamera() { + if(!this.focused){ + return; + } + this.focused = false; + this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia)); + this.disableCamera(); + } + + public focusCamera() { + if(this.focused){ + return; + } + this.focused = true; + this.applyPreviousConfig(); } public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { @@ -631,7 +642,22 @@ export class MediaManager { }, 30000); } - + //check if user is active + private checkActiveUser(){ + if(this.setTimeOutlastUpdateScene){ + clearTimeout(this.setTimeOutlastUpdateScene); + } + this.setTimeOutlastUpdateScene = setTimeout(() => { + const now = new Date(); + //if last update is more of 10 sec + if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) { + this.blurCamera(); + }else{ + this.focusCamera(); + } + this.checkActiveUser(); + }, this.focused ? 10000 : 1000); + } } export const mediaManager = new MediaManager(); From 55c1bcee66ab8db8c86af4649d34a74a44c6b339 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 27 Oct 2020 20:18:25 +0100 Subject: [PATCH 11/66] 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 12/66] 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 df49ea856a9d446c9f87742443eb763bac2e7045 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 27 Oct 2020 20:51:11 +0100 Subject: [PATCH 13/66] Fix conflict error --- front/src/WebRtc/MediaManager.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 1cb1bdbb..672b09ae 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -167,7 +167,7 @@ export class MediaManager { gameOverlay.classList.remove('active'); } - private enableCamera() { + public enableCamera() { this.enableCameraStyle(); this.constraintsMedia.video = videoConstraint; this.getCamera().then((stream: MediaStream) => { @@ -175,7 +175,7 @@ export class MediaManager { }); } - private async disableCamera() { + public async disableCamera() { this.disableCameraStyle(); this.stopCamera(); @@ -187,7 +187,7 @@ export class MediaManager { } } - private enableMicrophone() { + public enableMicrophone() { this.enableMicrophoneStyle(); this.constraintsMedia.audio = true; @@ -196,7 +196,7 @@ export class MediaManager { }); } - private async disableMicrophone() { + public async disableMicrophone() { this.disableMicrophoneStyle(); this.stopMicrophone(); @@ -574,7 +574,7 @@ export class MediaManager { return elem as T; } - private showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ + public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ //create report text area const mainContainer = this.getElementByIdOrFail('main-container'); From a2dd3bd6ab1b530542741a6a6ebabbc9e6198752 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 30 Oct 2020 20:35:45 +0100 Subject: [PATCH 14/66] 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 74de2746c2088f19c9397662c2535166da35c92e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 31 Oct 2020 14:04:55 +0100 Subject: [PATCH 15/66] Create action button --- front/dist/resources/style/style.css | 27 +++++++++++ front/src/Phaser/Game/GameScene.ts | 27 +++++++---- .../src/Phaser/UserInput/UserInputManager.ts | 7 +++ front/src/WebRtc/JitsiFactory.ts | 3 ++ front/src/WebRtc/LayoutManager.ts | 46 +++++++++++++++++++ 5 files changed, 100 insertions(+), 10 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index edd3ddf0..9aa9bc32 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -701,3 +701,30 @@ div.modal-report-user{ max-width: calc(800px - 60px); /* size of modal - padding*/ } +/** Action button **/ +div.action{ + position: absolute; + width: 100%; + height: auto; + text-align: center; + bottom: 40px; + transition: all .5s ease; + animation: mymove .5s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; +} +div.action p.action-body{ + padding: 10px; + background-color: #2d2d2dba; + color: #fff; + font-size: 12px; + text-align: center; + max-width: 150px; + margin-left: calc(50% - 75px); + border-radius: 15px; +} +@keyframes mymove { + 0% {bottom: 40px;} + 50% {bottom: 30px;} + 100% {bottom: 40px;} +} \ No newline at end of file diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 96648255..9aaf8ee8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -469,24 +469,31 @@ export class GameScene extends ResizableScene implements CenterListener { this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => { if (newValue === undefined) { + layoutManager.removeActionButton('openWebsite', this.userInputManager); coWebsiteManager.closeCoWebsite(); - } else { - coWebsiteManager.loadCoWebsite(newValue as string); + }else{ + layoutManager.addActionButton('openWebsite', 'Clik on SPACE to open web site', () => { + coWebsiteManager.loadCoWebsite(newValue as string); + }, this.userInputManager); } }); + this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => { if (newValue === undefined) { + layoutManager.removeActionButton('jitsiRoom', this.userInputManager); this.stopJitsi(); - } else { - if (JITSI_PRIVATE_MODE) { - const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; + }else{ + layoutManager.addActionButton('jitsiRoom', 'Clik on SPACE to enter in jitsi meet room', () => { + 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.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag); + } else { + this.startJitsi(newValue as string); + } + }, this.userInputManager); } - }) + }); this.gameMap.onPropertyChange('silent', (newValue, oldValue) => { if (newValue === undefined || newValue === false || newValue === '') { diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index fa7080b5..9f4a6071 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -77,4 +77,11 @@ export class UserInputManager { return event; }); } + + addSpaceEventListner(callback : Function){ + this.Scene.input.keyboard.addListener('keyup-SPACE', callback); + } + removeSpaceEventListner(callback : Function){ + this.Scene.input.keyboard.removeListener('keyup-SPACE', callback); + } } diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 45b9b3cf..6ad69e2a 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -62,6 +62,9 @@ class JitsiFactory { } public async stop(): Promise { + if(!this.jitsiApi){ + return; + } await coWebsiteManager.closeCoWebsite(); this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback); this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback); diff --git a/front/src/WebRtc/LayoutManager.ts b/front/src/WebRtc/LayoutManager.ts index dc013563..4013f1d8 100644 --- a/front/src/WebRtc/LayoutManager.ts +++ b/front/src/WebRtc/LayoutManager.ts @@ -1,3 +1,4 @@ +import { UserInputManager } from "../Phaser/UserInput/UserInputManager"; import {HtmlUtils} from "./HtmlUtils"; export enum LayoutMode { @@ -33,6 +34,9 @@ class LayoutManager { private normalDivs: Map = new Map(); private listener: CenterListener|null = null; + private actionButtonTrigger: Map = new Map(); + private actionButtonInformation: Map = new Map(); + public setListener(centerListener: CenterListener|null) { this.listener = centerListener; } @@ -305,6 +309,48 @@ class LayoutManager { } } } + + public addActionButton(id: string, text: string, callBack: Function, userInputManager: UserInputManager){ + //delete previous element + this.removeActionButton(id, userInputManager); + + //create div and text html component + const p = document.createElement('p'); + p.classList.add('action-body'); + p.innerText = text; + + const div = document.createElement('div'); + div.classList.add('action'); + div.id = id; + div.appendChild(p); + + this.actionButtonInformation.set(id, div); + + const mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); + mainContainer.appendChild(div); + + const callBackFunctionTrigger = (() => { + console.log('user click on space => ', id); + callBack(); + }); + + //add trigger action + this.actionButtonTrigger.set(id, callBackFunctionTrigger); + userInputManager.addSpaceEventListner(callBackFunctionTrigger); + } + + public removeActionButton(id: string, userInputManager: UserInputManager){ + //delete previous element + const previousDiv = this.actionButtonInformation.get(id); + if(previousDiv){ + previousDiv.remove(); + this.actionButtonInformation.delete(id); + } + const previousEventCallback = this.actionButtonTrigger.get(id); + if(previousEventCallback){ + userInputManager.removeSpaceEventListner(previousEventCallback); + } + } } const layoutManager = new LayoutManager(); From fcb7f364b6c6f6247c9128cde47fe8a02880626b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 1 Nov 2020 11:49:09 +0100 Subject: [PATCH 16/66] Fix button action when user have activated --- front/src/Phaser/Game/GameScene.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9aaf8ee8..f470c994 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -474,6 +474,7 @@ export class GameScene extends ResizableScene implements CenterListener { }else{ layoutManager.addActionButton('openWebsite', 'Clik on SPACE to open web site', () => { coWebsiteManager.loadCoWebsite(newValue as string); + layoutManager.removeActionButton('openWebsite', this.userInputManager); }, this.userInputManager); } }); From 6051e5f46a01fddc0ae21ddbdcdce1f97f817e36 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sun, 1 Nov 2020 11:53:24 +0100 Subject: [PATCH 17/66] Fix when user have activated action button --- front/src/Phaser/Game/GameScene.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index f470c994..27d7d3d2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -492,6 +492,7 @@ export class GameScene extends ResizableScene implements CenterListener { } else { this.startJitsi(newValue as string); } + layoutManager.removeActionButton('jitsiRoom', this.userInputManager); }, this.userInputManager); } }); From 47a049ecafd689ece1cb2e7fc1b4edc2a1ec6414 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Wed, 4 Nov 2020 12:42:33 +0100 Subject: [PATCH 18/66] Feedback comment @moufmouf --- front/src/WebRtc/MediaManager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 672b09ae..37a23578 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -631,7 +631,10 @@ export class MediaManager { mainContainer.appendChild(divReport); } - //ping always 30 seconds data on stream + /** + * For some reasons, the microphone muted icon or the stream is not always up to date. + * Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser) + **/ private pingCameraStatus(){ setTimeout(() => { console.log('ping camera status'); From 6ef2148a34aeacc8de7caedea3810d3df63d391b Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Wed, 4 Nov 2020 13:07:38 +0100 Subject: [PATCH 19/66] 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 968787555fe2c91ef978f404b6380ccadaa8b5c2 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Wed, 4 Nov 2020 13:33:58 +0100 Subject: [PATCH 20/66] Update feedback @moufmouf --- front/src/Phaser/Game/GameScene.ts | 32 +++++++++++++++++++++++------- maps/Floor0/floor0.json | 17 +++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 27d7d3d2..882681ed 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -467,24 +467,33 @@ 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) => { + this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue, allProps) => { if (newValue === undefined) { layoutManager.removeActionButton('openWebsite', this.userInputManager); coWebsiteManager.closeCoWebsite(); }else{ - layoutManager.addActionButton('openWebsite', 'Clik on SPACE to open web site', () => { + const openWebsiteFunction = () => { coWebsiteManager.loadCoWebsite(newValue as string); layoutManager.removeActionButton('openWebsite', this.userInputManager); - }, this.userInputManager); + }; + + const openWebsiteTriggerValue = allProps.get('openWebsiteTrigger'); + if(openWebsiteTriggerValue && openWebsiteTriggerValue === 'onaction') { + layoutManager.addActionButton('openWebsite', 'Clik on SPACE to open web site', () => { + openWebsiteFunction(); + }, this.userInputManager); + }else{ + openWebsiteFunction(); + } } }); - + this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => { if (newValue === undefined) { layoutManager.removeActionButton('jitsiRoom', this.userInputManager); this.stopJitsi(); }else{ - layoutManager.addActionButton('jitsiRoom', 'Clik on SPACE to enter in jitsi meet room', () => { + const openJitsiRoomFunction = () => { if (JITSI_PRIVATE_MODE) { const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; @@ -493,7 +502,16 @@ export class GameScene extends ResizableScene implements CenterListener { this.startJitsi(newValue as string); } layoutManager.removeActionButton('jitsiRoom', this.userInputManager); - }, this.userInputManager); + } + + const jitsiTriggerValue = allProps.get('jitsiTrigger'); + if(jitsiTriggerValue && jitsiTriggerValue === 'onaction') { + layoutManager.addActionButton('jitsiRoom', 'Clik on SPACE to enter in jitsi meet room', () => { + openJitsiRoomFunction(); + }, this.userInputManager); + }else{ + openJitsiRoomFunction(); + } } }); @@ -678,7 +696,7 @@ export class GameScene extends ResizableScene implements CenterListener { if (!properties) { return undefined; } - const obj = properties.find((property: ITiledMapLayerProperty) => property.name === name); + const obj = properties.find((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase()); if (obj === undefined) { return undefined; } diff --git a/maps/Floor0/floor0.json b/maps/Floor0/floor0.json index 7e6f179b..f03778e6 100644 --- a/maps/Floor0/floor0.json +++ b/maps/Floor0/floor0.json @@ -44,6 +44,11 @@ "name":"jitsiRoom", "type":"string", "value":"tcm-chillzone-2" + }, + { + "name":"jitsiTrigger", + "type":"string", + "value":"auto" }], "type":"tilelayer", "visible":true, @@ -61,7 +66,12 @@ { "name":"jitsiRoom", "type":"string", - "value":"tcm-chillzone-1" + "value":"tcm-chillzone-11" + }, + { + "name":"jitsiTrigger", + "type":"string", + "value":"onaction" }], "type":"tilelayer", "visible":true, @@ -128,6 +138,11 @@ "name":"openWebsite", "type":"string", "value":"https:\/\/app.swile.co\/" + }, + { + "name":"openWebsiteTrigger", + "type":"string", + "value":"auto" }], "type":"tilelayer", "visible":true, From a1d3b016198e5c8b3862e97591d3b0882839da94 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Wed, 4 Nov 2020 16:30:01 +0100 Subject: [PATCH 21/66] Fix typo --- front/src/Phaser/Game/GameScene.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 882681ed..3e9361ff 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -479,7 +479,7 @@ export class GameScene extends ResizableScene implements CenterListener { const openWebsiteTriggerValue = allProps.get('openWebsiteTrigger'); if(openWebsiteTriggerValue && openWebsiteTriggerValue === 'onaction') { - layoutManager.addActionButton('openWebsite', 'Clik on SPACE to open web site', () => { + layoutManager.addActionButton('openWebsite', 'Click on SPACE to open web site', () => { openWebsiteFunction(); }, this.userInputManager); }else{ @@ -506,7 +506,7 @@ export class GameScene extends ResizableScene implements CenterListener { const jitsiTriggerValue = allProps.get('jitsiTrigger'); if(jitsiTriggerValue && jitsiTriggerValue === 'onaction') { - layoutManager.addActionButton('jitsiRoom', 'Clik on SPACE to enter in jitsi meet room', () => { + layoutManager.addActionButton('jitsiRoom', 'Click on SPACE to enter in jitsi meet room', () => { openJitsiRoomFunction(); }, this.userInputManager); }else{ From 3333b3cee3beae37606c7f0cae7f7c5afd0dfe5e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 12:38:32 +0100 Subject: [PATCH 22/66] 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 23/66] 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 73fa0ecbfc9eabb5fb6a51386fe64632dad99d25 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 17:00:23 +0100 Subject: [PATCH 24/66] 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 b064f01f97cd91aefb76d539d47cfa14058bb3f7 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Thu, 12 Nov 2020 14:51:19 +0100 Subject: [PATCH 25/66] 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 26/66] 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 27/66] 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 28/66] 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 29/66] 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 30/66] 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 31/66] 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 32/66] 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 33/66] 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 34/66] 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 b8bafe9c7effb600a077b8f1025cc1e1e0e3b2e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Nov 2020 14:26:58 +0000 Subject: [PATCH 35/66] Bump dot-prop from 4.2.0 to 4.2.1 in /website Bumps [dot-prop](https://github.com/sindresorhus/dot-prop) from 4.2.0 to 4.2.1. - [Release notes](https://github.com/sindresorhus/dot-prop/releases) - [Commits](https://github.com/sindresorhus/dot-prop/compare/v4.2.0...v4.2.1) Signed-off-by: dependabot[bot] --- website/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/package-lock.json b/website/package-lock.json index 286ca3f0..947eb4ff 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -2251,9 +2251,9 @@ } }, "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "dev": true, "requires": { "is-obj": "^1.0.0" From 64d00bda562bf58dac42d3ffc606a9375bdc842e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 17 Nov 2020 16:46:46 +0100 Subject: [PATCH 36/66] 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 0a7063a47822255c7152bdc6e00db375d1b571a6 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 17 Nov 2020 18:03:44 +0100 Subject: [PATCH 37/66] Add close button --- front/dist/index.html | 6 +++++- front/dist/resources/style/style.css | 17 +++++++++++++++++ front/src/Phaser/Game/GameScene.ts | 7 +++++++ front/src/WebRtc/CoWebsiteManager.ts | 8 ++++++-- front/src/WebRtc/MediaManager.ts | 27 +++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/front/dist/index.html b/front/dist/index.html index 0e696622..5806bef7 100644 --- a/front/dist/index.html +++ b/front/dist/index.html @@ -72,7 +72,11 @@ - +
diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index edd3ddf0..4a74fdae 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -274,6 +274,23 @@ body { #cowebsite.hidden { transform: translateX(100%); } + + #cowebsite .close-btn{ + position: absolute; + top: 10px; + right: -100px; + background: none; + border: none; + cursor: pointer; + animation: right .2s ease; + } + #cowebsite .close-btn img{ + height: 15px; + right: 15px; + } + #cowebsite:hover .close-btn{ + right: 10px; + } } @media (max-aspect-ratio: 1/1) { .game-overlay { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ae689775..2d6f1eec 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1196,12 +1196,19 @@ export class GameScene extends ResizableScene implements CenterListener { jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt); this.connection.setSilent(true); mediaManager.hideGameOverlay(); + + //permit to stop jitsi when user close iframe + mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { + this.stopJitsi(); + }); } public stopJitsi(): void { this.connection.setSilent(false); jitsiFactory.stop(); mediaManager.showGameOverlay(); + + mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi'); } private loadSpritesheet(name: string, url: string): Promise { diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index b625bf6e..e6d5b748 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -44,7 +44,9 @@ class CoWebsiteManager { public loadCoWebsite(url: string): void { this.load(); - this.cowebsiteDiv.innerHTML = ''; + this.cowebsiteDiv.innerHTML = ``; const iframe = document.createElement('iframe'); iframe.id = 'cowebsite-iframe'; @@ -83,7 +85,9 @@ class CoWebsiteManager { this.close(); this.fire(); setTimeout(() => { - this.cowebsiteDiv.innerHTML = ''; + this.cowebsiteDiv.innerHTML = ``; resolve(); }, animationTime) })); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index fa703fbe..1f0988de 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -40,6 +40,8 @@ export class MediaManager { private hasCamera = true; + private triggerCloseJistiFrame : Map = new Map(); + constructor() { this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); @@ -130,11 +132,23 @@ export class MediaManager { public showGameOverlay(){ const gameOverlay = this.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); + + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const functionTrigger = () => { + this.triggerCloseJitsiFrameButton(); + } + buttonCloseFrame.removeEventListener('click', functionTrigger); } public hideGameOverlay(){ const gameOverlay = this.getElementByIdOrFail('game-overlay'); gameOverlay.classList.remove('active'); + + const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close'); + const functionTrigger = () => { + this.triggerCloseJitsiFrameButton(); + } + buttonCloseFrame.addEventListener('click', functionTrigger); } public enableCamera() { @@ -582,6 +596,19 @@ export class MediaManager { mainContainer.appendChild(divReport); } + public addTriggerCloseJitsiFrameButton(id: String, Function: Function){ + this.triggerCloseJistiFrame.set(id, Function); + } + + public removeTriggerCloseJitsiFrameButton(id: String){ + this.triggerCloseJistiFrame.delete(id); + } + + private triggerCloseJitsiFrameButton(): void { + for (const callback of this.triggerCloseJistiFrame.values()) { + callback(); + } + } } 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 38/66] 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 39/66] 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 @@