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
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 edd3ddf0..fc055941 100644
--- a/front/dist/resources/style/style.css
+++ b/front/dist/resources/style/style.css
@@ -1,6 +1,14 @@
+*{
+ font-family: 'Open Sans', sans-serif;
+}
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;
@@ -72,14 +80,16 @@ body .message-info.warning{
#div-myCamVideo {
position: absolute;
- right: 0;
- bottom: 0;
+ right: 15px;
+ bottom: 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;*/
}
@@ -372,6 +382,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 {
@@ -384,6 +395,7 @@ body {
justify-content: center;
flex-direction: column;
overflow: hidden;
+ border-radius: 15px;
}
.chat-mode {
@@ -435,7 +447,7 @@ body {
max-height: 80%;
top: -80%;
left: 10%;
- background: #000000a6;
+ background: #333333;
z-index: 200;
transition: all 0.1s ease-out;
}
@@ -532,7 +544,7 @@ body {
border: 1px solid black;
background-color: #00000000;
color: #ffda01;
- border-radius: 10px;
+ border-radius: 15px;
padding: 10px 30px;
transition: all .2s ease;
}
@@ -627,6 +639,7 @@ div.modal-report-user{
left: calc(50% - 400px);
top: 100px;
background-color: #000000ad;
+ border-radius: 15px;
}
.modal-report-user textarea{
@@ -638,6 +651,7 @@ div.modal-report-user{
color: white;
width: calc(100% - 60px);
margin: 30px;
+ border-radius: 15px;
}
.modal-report-user img{
@@ -669,7 +683,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;
}
@@ -701,3 +715,189 @@ div.modal-report-user{
max-width: calc(800px - 60px); /* size of modal - padding*/
}
+/*MESSAGE*/
+.discussion{
+ position: fixed;
+ left: -300px;
+ top: 0px;
+ width: 220px;
+ height: 100%;
+ background-color: #333333;
+ padding: 20px;
+ transition: all 0.5s ease;
+}
+.discussion.active{
+ left: 0;
+}
+.discussion .active-btn{
+ display: none;
+ cursor: pointer;
+ height: 50px;
+ width: 50px;
+ background-color: #2d2d2dba;
+ position: absolute;
+ top: calc(50% - 25px);
+ margin-left: 315px;
+ border-radius: 50%;
+ border: none;
+ transition: all 0.5s ease;
+}
+.discussion .active-btn.active{
+ display: block;
+}
+.discussion .active-btn:hover {
+ transform: scale(1.1) rotateY(3.142rad);
+}
+.discussion .active-btn img{
+ width: 26px;
+ height: 26px;
+ margin: 13px 5px;
+}
+
+.discussion .close-btn{
+ position: absolute;
+ top: 0;
+ right: 10px;
+ background: none;
+ border: none;
+ cursor: pointer;
+}
+.discussion .close-btn img{
+ height: 15px;
+ right: 15px;
+}
+
+.discussion p{
+ color: white;
+ font-size: 22px;
+ padding-left: 10px;
+ margin: 0;
+}
+
+.discussion .participants{
+ height: 200px;
+ margin: 10px 0;
+}
+
+.discussion .participants .participant{
+ display: flex;
+ margin: 5px 10px;
+ background-color: #ffffff69;
+ padding: 5px;
+ border-radius: 15px;
+ cursor: pointer;
+}
+
+.discussion .participants .participant:hover{
+ background-color: #ffffff;
+}
+.discussion .participants .participant:hover p{
+ color: black;
+}
+
+.discussion .participants .participant:before {
+ content: '';
+ height: 10px;
+ width: 10px;
+ background-color: #1e7e34;
+ position: absolute;
+ margin-left: 18px;
+ border-radius: 50%;
+ margin-top: 18px;
+}
+
+.discussion .participants .participant img{
+ width: 26px;
+ height: 26px;
+}
+
+.discussion .participants .participant p{
+ font-size: 16px;
+ margin-left: 10px;
+ margin-top: 2px;
+}
+
+.discussion .participants .participant button.report-btn{
+ cursor: pointer;
+ position: absolute;
+ background-color: #2d2d2dba;
+ right: 34px;
+ margin: 0px;
+ padding: 6px 0px;
+ border-radius: 15px;
+ border: none;
+ color: white;
+ width: 0px;
+ overflow: hidden;
+ transition: all .5s ease;
+}
+
+.discussion .participants .participant:hover button.report-btn{
+ width: 70px;
+}
+
+.discussion .messages{
+ position: absolute;
+ height: calc(100% - 360px);
+ overflow-x: hidden;
+ overflow-y: auto;
+ max-width: calc(100% - 40px);
+ width: calc(100% - 40px);
+}
+
+.discussion .messages h2{
+ color: white;
+}
+
+.discussion .messages .message{
+ margin: 5px;
+ float: right;
+ text-align: right;
+ width: 100%;
+}
+
+.discussion .messages .message.me{
+ float: left;
+ text-align: left;
+}
+
+.discussion .messages .message p{
+ font-size: 12px;
+}
+
+.discussion .messages .message p.body{
+ font-size: 16px;
+ overflow: hidden;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+.discussion .send-message{
+ position: absolute;
+ bottom: 45px;
+ width: 220px;
+ height: 26px;
+}
+
+.discussion .send-message input{
+ position: absolute;
+ width: calc(100% - 10px);
+ height: 20px;
+ background-color: #171717;
+ color: white;
+ border-radius: 15px;
+ border: none;
+ padding: 6px;
+}
+
+.discussion .send-message img{
+ position: absolute;
+ margin-right: 10px;
+ width: 20px;
+ height: 20px;
+ background-color: #ffffff69;
+}
+.discussion .send-message img:hover{
+ background-color: #ffffff;
+}
+
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index 888f62e1..a704062e 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -352,6 +352,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();
@@ -431,35 +432,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;
@@ -567,7 +540,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);
@@ -592,15 +565,51 @@ export class GameScene extends ResizableScene implements CenterListener {
this.gameMap.setPosition(event.x, event.y);
})
-
this.scene.wake();
this.scene.sleep(ReconnectingSceneName);
+ //init user position and play trigger to check layers properties
+ this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
+
return connection;
});
}
+ 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 {
+ //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);
@@ -704,6 +713,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;
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/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts
new file mode 100644
index 00000000..097bf3a3
--- /dev/null
+++ b/front/src/WebRtc/DiscussionManager.ts
@@ -0,0 +1,231 @@
+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 {
+ private mainContainer: HTMLDivElement;
+
+ private divDiscuss?: HTMLDivElement;
+ private divParticipants?: HTMLDivElement;
+ private nbpParticipants?: HTMLParagraphElement;
+ private divMessages?: HTMLParagraphElement;
+ private buttonActiveDiscussion?: HTMLButtonElement;
+
+ private participants: Map = new Map();
+
+ private activeDiscussion: boolean = false;
+
+ private sendMessageCallBack : Map = new Map();
+
+ private userInputManager?: UserInputManager;
+
+ constructor(private mediaManager: MediaManager, 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');
+ this.buttonActiveDiscussion = document.createElement('button');
+ buttonCloseDiscussion.classList.add('close-btn');
+ buttonCloseDiscussion.innerHTML = ``;
+ buttonCloseDiscussion.addEventListener('click', () => {
+ this.hideDiscussion();
+ this.showButtonDiscussionBtn();
+ });
+ this.buttonActiveDiscussion.classList.add('active-btn');
+ this.buttonActiveDiscussion.innerHTML = ``;
+ this.buttonActiveDiscussion.addEventListener('click', () => {
+ this.showDiscussionPart();
+ });
+ this.divDiscuss.appendChild(buttonCloseDiscussion);
+ this.divDiscuss.appendChild(this.buttonActiveDiscussion);
+
+ const myName: HTMLParagraphElement = document.createElement('p');
+ myName.innerText = name.toUpperCase();
+ this.nbpParticipants = document.createElement('p');
+ this.nbpParticipants.innerText = 'PARTICIPANTS (1)';
+
+ this.divParticipants = document.createElement('div');
+ this.divParticipants.classList.add('participants');
+
+ this.divMessages = document.createElement('div');
+ this.divMessages.classList.add('messages');
+ this.divMessages.innerHTML = "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();
+ if(inputMessage.value === null
+ || inputMessage.value === ''
+ || inputMessage.value === undefined) {
+ return;
+ }
+ this.addMessage(name, inputMessage.value, true);
+ for(const callback of this.sendMessageCallBack.values()) {
+ callback(inputMessage.value);
+ }
+ inputMessage.value = "";
+ }
+ });
+ sendDivMessage.appendChild(inputMessage);
+ this.divDiscuss.appendChild(sendDivMessage);
+
+ //append in main container
+ this.mainContainer.appendChild(this.divDiscuss);
+
+ this.addParticipant('me', 'Moi', undefined, true);
+ }
+
+ public addParticipant(
+ userId: number|string,
+ name: string|undefined,
+ img?: string|undefined,
+ isMe: boolean = false,
+ reportCallback?: ReportCallback
+ ) {
+ const divParticipant: HTMLDivElement = document.createElement('div');
+ divParticipant.classList.add('participant');
+ divParticipant.id = `participant-${userId}`;
+
+ const divImgParticipant: HTMLImageElement = document.createElement('img');
+ divImgParticipant.src = 'resources/logos/boy.svg';
+ if (img !== undefined) {
+ divImgParticipant.src = img;
+ }
+ const divPParticipant: HTMLParagraphElement = document.createElement('p');
+ if(!name){
+ name = 'Anonymous';
+ }
+ divPParticipant.innerText = name;
+
+ divParticipant.appendChild(divImgParticipant);
+ divParticipant.appendChild(divPParticipant);
+
+ if(!isMe) {
+ const reportBanUserAction: HTMLButtonElement = document.createElement('button');
+ reportBanUserAction.classList.add('report-btn')
+ reportBanUserAction.innerText = 'Report';
+ reportBanUserAction.addEventListener('click', () => {
+ if(reportCallback) {
+ this.mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
+ }else{
+ console.info('report feature is not activated!');
+ }
+ });
+ divParticipant.appendChild(reportBanUserAction);
+ }
+
+ this.divParticipants?.appendChild(divParticipant);
+
+ this.participants.set(userId, divParticipant);
+ this.showButtonDiscussionBtn();
+
+ this.updateParticipant(this.participants.size);
+ }
+
+ public updateParticipant(nb: number) {
+ if (!this.nbpParticipants) {
+ return;
+ }
+ this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`;
+ }
+
+ public addMessage(name: string, message: string, isMe: boolean = false) {
+ const divMessage: HTMLDivElement = document.createElement('div');
+ divMessage.classList.add('message');
+ if(isMe){
+ divMessage.classList.add('me');
+ }
+
+ const pMessage: HTMLParagraphElement = document.createElement('p');
+ const date = new Date();
+ if(isMe){
+ name = 'Moi';
+ }
+ pMessage.innerHTML = `${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();
+ this.participants.delete(userId);
+ }
+ //if all participant leave, hide discussion button
+ if(this.participants.size === 1){
+ this.hideButtonDiscussionBtn();
+ }
+
+ this.sendMessageCallBack.delete(userId);
+ }
+
+ public onSendMessageCallback(userId: string|number, callback: SendMessageCallback): void {
+ this.sendMessageCallBack.set(userId, callback);
+ }
+
+ get activatedDiscussion(){
+ return this.activeDiscussion;
+ }
+
+ private showButtonDiscussionBtn(){
+ //if it's first participant, show discussion button
+ if(this.activatedDiscussion || this.participants.size === 1) {
+ return;
+ }
+ this.buttonActiveDiscussion?.classList.add('active');
+ }
+
+ private showDiscussion(){
+ this.activeDiscussion = true;
+ if(this.userInputManager) {
+ this.userInputManager.clearAllInputKeyboard();
+ }
+ this.divDiscuss?.classList.add('active');
+ }
+
+ private hideDiscussion(){
+ this.activeDiscussion = false;
+ if(this.userInputManager) {
+ this.userInputManager.initKeyBoardEvent();
+ }
+ this.divDiscuss?.classList.remove('active');
+ }
+
+ private hideButtonDiscussionBtn(){
+ this.buttonActiveDiscussion?.classList.remove('active');
+ }
+
+ public setUserInputManager(userInputManager : UserInputManager){
+ this.userInputManager = userInputManager;
+ }
+
+ public showDiscussionPart(){
+ this.showDiscussion();
+ this.hideButtonDiscussionBtn();
+ }
+}
\ No newline at end of file
diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts
index fa703fbe..ce1878fb 100644
--- a/front/src/WebRtc/MediaManager.ts
+++ b/front/src/WebRtc/MediaManager.ts
@@ -1,5 +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 = {
@@ -38,59 +40,65 @@ export class MediaManager {
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
+ private discussionManager: DiscussionManager;
+
+ private userInputManager?: UserInputManager;
+
private hasCamera = true;
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();
this.disableScreenSharing();
//update tracking
});
+
+ this.discussionManager = new DiscussionManager(this,'');
}
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
@@ -128,12 +136,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');
}
@@ -372,14 +380,17 @@ 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));
+
+ //permit to create participant in discussion part
+ this.addNewParticipant(userId, userName, undefined, reportCallBack);
}
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
@@ -393,7 +404,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){
@@ -454,6 +465,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}`)
@@ -516,18 +530,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){
+ public 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');
@@ -582,7 +587,34 @@ export class MediaManager {
mainContainer.appendChild(divReport);
}
+ public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
+ this.discussionManager.addParticipant(userId, name, img, false, reportCallBack);
+ }
+ public removeParticipant(userId: number|string){
+ this.discussionManager.removeParticipant(userId);
+ }
+
+ public addNewMessage(name: string, message: string, isMe: boolean = false){
+ this.discussionManager.addMessage(name, message, isMe);
+
+ //when there are new message, show discussion
+ if(!this.discussionManager.activatedDiscussion) {
+ this.discussionManager.showDiscussionPart();
+ }
+ }
+
+ public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){
+ this.discussionManager.onSendMessageCallback(userId, callback);
+ }
+
+ get activatedDiscussion(){
+ return this.discussionManager.activatedDiscussion;
+ }
+
+ public setUserInputManager(userInputManager : UserInputManager){
+ this.discussionManager.setUserInputManager(userInputManager);
+ }
}
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..195b57b3 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{
@@ -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);
@@ -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(user.userId,(message: string) => {
+ peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.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;