From 7eb38fae831ad82494c1f14626770f260c9f39e0 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 29 Jan 2021 21:09:10 +0100 Subject: [PATCH 1/2] Permit puiblic report - Create report html - Add report flag --- back/src/Services/AdminApi.ts | 3 +- front/dist/resources/html/gameReport.html | 104 +++++++++++++++++++++ front/dist/resources/logos/report.back.svg | 1 + front/dist/resources/logos/report.svg | 2 +- front/dist/resources/style/style.css | 7 +- front/src/Phaser/Game/GameScene.ts | 2 +- front/src/Phaser/Menu/MenuScene.ts | 85 ++++++++++++++++- front/src/WebRtc/DiscussionManager.ts | 8 +- front/src/WebRtc/MediaManager.ts | 95 +++++-------------- front/src/WebRtc/SimplePeer.ts | 15 +-- pusher/src/Services/AdminApi.ts | 3 +- pusher/src/Services/SocketManager.ts | 2 +- 12 files changed, 231 insertions(+), 96 deletions(-) create mode 100644 front/dist/resources/html/gameReport.html create mode 100644 front/dist/resources/logos/report.back.svg diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index 9c46a41b..3e2dd3e0 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -100,11 +100,12 @@ class AdminApi { return res.data; } - reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) { + reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) { return Axios.post(`${ADMIN_API_URL}/api/report`, { reportedUserUuid, reportedUserComment, reporterUserUuid, + reportWorldSlug, }, { headers: {"Authorization": `${ADMIN_API_TOKEN}`} diff --git a/front/dist/resources/html/gameReport.html b/front/dist/resources/html/gameReport.html new file mode 100644 index 00000000..9a761c32 --- /dev/null +++ b/front/dist/resources/html/gameReport.html @@ -0,0 +1,104 @@ + + + diff --git a/front/dist/resources/logos/report.back.svg b/front/dist/resources/logos/report.back.svg new file mode 100644 index 00000000..1cb3b068 --- /dev/null +++ b/front/dist/resources/logos/report.back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/dist/resources/logos/report.svg b/front/dist/resources/logos/report.svg index 1cb3b068..14753256 100644 --- a/front/dist/resources/logos/report.svg +++ b/front/dist/resources/logos/report.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 2e2c6c10..427eff75 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -55,14 +55,13 @@ body .message-info.warning{ } .video-container img.active{ display: block; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; } .video-container img{ position: absolute; display: none; - width: 15px; - height: 15px; - background: #d93025; - border-radius: 48px; + width: 25px; + height: 25px; left: 5px; bottom: 5px; padding: 10px; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 405fcb85..2d14babf 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -154,7 +154,7 @@ export class GameScene extends ResizableScene implements CenterListener { private actionableItems: Map = new Map(); // The item that can be selected by pressing the space key. private outlinedItem: ActionableItem|null = null; - private userInputManager!: UserInputManager; + public userInputManager!: UserInputManager; private isReconnecting: boolean = false; private startLayerName!: string | null; private openChatIcon!: OpenChatIcon; diff --git a/front/src/Phaser/Menu/MenuScene.ts b/front/src/Phaser/Menu/MenuScene.ts index 8bd64cd1..6f035446 100644 --- a/front/src/Phaser/Menu/MenuScene.ts +++ b/front/src/Phaser/Menu/MenuScene.ts @@ -2,7 +2,7 @@ import {LoginScene, LoginSceneName} from "../Login/LoginScene"; import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import {gameManager} from "../Game/GameManager"; import {localUserStore} from "../../Connexion/LocalUserStore"; -import {mediaManager} from "../../WebRtc/MediaManager"; +import {mediaManager, ReportCallback, ShowReportCallBack} from "../../WebRtc/MediaManager"; import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; export const MenuSceneName = 'MenuScene'; @@ -10,6 +10,7 @@ const gameMenuKey = 'gameMenu'; const gameMenuIconKey = 'gameMenuIcon'; const gameSettingsMenuKey = 'gameSettingsMenu'; const gameShare = 'gameShare'; +const gameReport = 'gameReport'; const closedSideMenuX = -200; const openedSideMenuX = 0; @@ -21,9 +22,11 @@ export class MenuScene extends Phaser.Scene { private menuElement!: Phaser.GameObjects.DOMElement; private gameQualityMenuElement!: Phaser.GameObjects.DOMElement; private gameShareElement!: Phaser.GameObjects.DOMElement; + private gameReportElement!: Phaser.GameObjects.DOMElement; private sideMenuOpened = false; private settingsMenuOpened = false; private gameShareOpened = false; + private gameReportOpened = false; private gameQualityValue: number; private videoQualityValue: number; private menuButton!: Phaser.GameObjects.DOMElement; @@ -40,6 +43,7 @@ export class MenuScene extends Phaser.Scene { this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html'); this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html'); this.load.html(gameShare, 'resources/html/gameShare.html'); + this.load.html(gameReport, 'resources/html/gameReport.html'); } create() { @@ -64,6 +68,19 @@ export class MenuScene extends Phaser.Scene { } }); + this.gameReportElement = this.add.dom(middleX, -400).createFromCache(gameReport); + this.revealMenusAfterInit(this.gameReportElement, gameReport); + this.gameReportElement.addListener('click'); + this.gameReportElement.on('click', (event:MouseEvent) => { + event.preventDefault(); + if((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') { + this.submitReport(); + }else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') { + this.closeGameReport(); + } + }); + mediaManager.setShowReportModalCallBacks(this.openGameReport.bind(this)); + this.input.keyboard.on('keyup-TAB', () => { this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu(); }); @@ -221,6 +238,71 @@ export class MenuScene extends Phaser.Scene { }); } + private openGameReport(userId: string, userName: string|undefined){ + if (this.gameReportOpened) { + this.closeGameReport(); + return; + } + + //close all + this.closeAll(); + + const gameTitleReport = this.gameReportElement.getChildByID('nameReported') as HTMLElement; + gameTitleReport.innerText = userName ? `Report user: ${userName}` : 'Report user'; + const gameIdUserReported = this.gameReportElement.getChildByID('idUserReported') as HTMLInputElement; + gameIdUserReported.value = userId; + + this.gameReportOpened = true; + let middleY = (window.innerHeight / 3) - (257); + if(middleY < 0){ + middleY = 0; + } + let middleX = (window.innerWidth / 3) - 298; + if(middleX < 0){ + middleX = 0; + } + + gameManager.getCurrentGameScene(this).userInputManager.clearAllInputKeyboard(); + + this.tweens.add({ + targets: this.gameReportElement, + y: middleY, + x: middleX, + duration: 1000, + ease: 'Power3' + }); + return; + } + + private closeGameReport(): void{ + this.gameReportOpened = false; + gameManager.getCurrentGameScene(this).userInputManager.initKeyBoardEvent(); + this.tweens.add({ + targets: this.gameReportElement, + y: -400, + duration: 1000, + ease: 'Power3' + }); + } + + private submitReport(): void{ + const gamePError = this.gameReportElement.getChildByID('gameReportErr') as HTMLParagraphElement; + gamePError.innerText = ''; + gamePError.style.display = 'none'; + const gameTextArea = this.gameReportElement.getChildByID('gameReportInput') as HTMLInputElement; + const gameIdUserReported = this.gameReportElement.getChildByID('idUserReported') as HTMLInputElement; + if(!gameTextArea || !gameTextArea.value || !gameIdUserReported || !gameIdUserReported.value){ + gamePError.innerText = 'Report message cannot to be empty.'; + gamePError.style.display = 'block'; + return; + } + gameManager.getCurrentGameScene(this).connection.emitReportPlayerMessage( + parseInt(gameIdUserReported.value), + gameTextArea.value + ); + this.closeGameReport(); + } + private onMenuClick(event:MouseEvent) { event.preventDefault(); @@ -280,5 +362,6 @@ export class MenuScene extends Phaser.Scene { private closeAll(){ this.closeGameQualityMenu(); this.closeGameShare(); + this.closeGameReport(); } } diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index 7653bc7a..1bd488e9 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,5 +1,5 @@ import {HtmlUtils} from "./HtmlUtils"; -import {mediaManager, ReportCallback} from "./MediaManager"; +import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager"; import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import {connectionManager} from "../Connexion/ConnectionManager"; import {GameConnexionTypes} from "../Url/UrlManager"; @@ -99,7 +99,7 @@ export class DiscussionManager { name: string|undefined, img?: string|undefined, isMe: boolean = false, - reportCallback?: ReportCallback + showReportCallBack?: ShowReportCallBack ) { const divParticipant: HTMLDivElement = document.createElement('div'); divParticipant.classList.add('participant'); @@ -128,8 +128,8 @@ export class DiscussionManager { reportBanUserAction.classList.add('report-btn') reportBanUserAction.innerText = 'Report'; reportBanUserAction.addEventListener('click', () => { - if(reportCallback) { - mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback); + if(showReportCallBack) { + showReportCallBack(`${userId}`, name); }else{ console.info('report feature is not activated!'); } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 1a84d4a9..054f0fe2 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -3,6 +3,8 @@ import {HtmlUtils} from "./HtmlUtils"; import {discussionManager, SendMessageCallback} from "./DiscussionManager"; import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager"; +import {connectionManager} from "../Connexion/ConnectionManager"; +import {GameConnexionTypes} from "../Url/UrlManager"; declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT); @@ -23,6 +25,7 @@ export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void; export type ReportCallback = (message: string) => void; +export type ShowReportCallBack = (userId: string, userName: string|undefined) => void; // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!) @@ -46,6 +49,7 @@ export class MediaManager { updatedLocalStreamCallBacks : Set = new Set(); startScreenSharingCallBacks : Set = new Set(); stopScreenSharingCallBacks : Set = new Set(); + showReportModalCallBacks : Set = new Set(); private microphoneBtn: HTMLDivElement; private cinemaBtn: HTMLDivElement; private monitorBtn: HTMLDivElement; @@ -469,7 +473,7 @@ export class MediaManager { return this.getCamera(); } - addActiveVideo(userId: string, reportCallBack: ReportCallback|undefined, userName: string = ""){ + addActiveVideo(userId: string, userName: string = "", anonymous: boolean = true){ this.webrtcInAudio.play(); userName = userName.toUpperCase(); @@ -482,7 +486,7 @@ export class MediaManager { ${userName} ` + - ((reportCallBack!==undefined)?``:'') + ((anonymous === false)?``:'') + ` @@ -490,18 +494,22 @@ export class MediaManager { layoutManager.add(DivImportance.Normal, userId, html); - if (reportCallBack) { - const reportBtn = HtmlUtils.getElementByIdOrFail(`report-${userId}`); - reportBtn.addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - this.showReportModal(userId, userName, reportCallBack); - }); - } - this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail(userId)); //permit to create participant in discussion part - this.addNewParticipant(userId, userName, undefined, reportCallBack); + const showReportUser = () => { + for(const callBack of this.showReportModalCallBacks){ + callBack(userId, userName); + } + }; + this.addNewParticipant(userId, userName, undefined, showReportUser); + + if(!anonymous){ + const reportBanUserAction: HTMLImageElement = HtmlUtils.getElementByIdOrFail(`report-${userId}`); + reportBanUserAction.addEventListener('click', () => { + showReportUser(); + }); + } } addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ @@ -645,65 +653,8 @@ export class MediaManager { return color; } - public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){ - //create report text area - const mainContainer = HtmlUtils.getElementByIdOrFail('main-container'); - - const divReport = document.createElement('div'); - divReport.classList.add('modal-report-user'); - - const inputHidden = document.createElement('input'); - inputHidden.id = 'input-report-user'; - inputHidden.type = 'hidden'; - inputHidden.value = userId; - divReport.appendChild(inputHidden); - - const titleMessage = document.createElement('p'); - titleMessage.id = 'title-report-user'; - titleMessage.innerText = 'Open a report'; - divReport.appendChild(titleMessage); - - const bodyMessage = document.createElement('p'); - bodyMessage.id = 'body-report-user'; - bodyMessage.innerText = `You are about to open a report regarding an offensive conduct from user ${userName.toUpperCase()}. Please explain to us how you think ${userName.toUpperCase()} breached the code of conduct.`; - divReport.appendChild(bodyMessage); - - const imgReportUser = document.createElement('img'); - imgReportUser.id = 'img-report-user'; - imgReportUser.src = 'resources/logos/report.svg'; - divReport.appendChild(imgReportUser); - - const textareaUser = document.createElement('textarea'); - textareaUser.id = 'textarea-report-user'; - textareaUser.placeholder = 'Write ...'; - divReport.appendChild(textareaUser); - - const buttonReport = document.createElement('button'); - buttonReport.id = 'button-save-report-user'; - buttonReport.innerText = 'Report'; - buttonReport.addEventListener('click', () => { - if(!textareaUser.value){ - textareaUser.style.border = '1px solid red' - return; - } - reportCallBack(textareaUser.value); - divReport.remove(); - }); - divReport.appendChild(buttonReport); - - const buttonCancel = document.createElement('img'); - buttonCancel.id = 'cancel-report-user'; - buttonCancel.src = 'resources/logos/close.svg'; - buttonCancel.addEventListener('click', () => { - divReport.remove(); - }); - divReport.appendChild(buttonCancel); - - mainContainer.appendChild(divReport); - } - - public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){ - discussionManager.addParticipant(userId, name, img, false, reportCallBack); + public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){ + discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack); } public removeParticipant(userId: number|string){ @@ -769,6 +720,10 @@ export class MediaManager { this.checkActiveUser(); }, this.focused ? 10000 : 1000); } + + public setShowReportModalCallBacks(callback: ShowReportCallBack){ + this.showReportModalCallBacks.add(callback); + } } export const mediaManager = new MediaManager(); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 90d260ee..bc2590d7 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -11,6 +11,8 @@ import { import {ScreenSharingPeer} from "./ScreenSharingPeer"; import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer"; import {RoomConnection} from "../Connexion/RoomConnection"; +import {connectionManager} from "../Connexion/ConnectionManager"; +import {GameConnexionTypes} from "../Url/UrlManager"; export interface UserSimplePeerInterface{ userId: number; @@ -134,11 +136,7 @@ export class SimplePeer { mediaManager.removeActiveVideo("" + user.userId); - const reportCallback = this.enableReporting ? (comment: string) => { - this.reportUser(user.userId, comment); - } : undefined; - - mediaManager.addActiveVideo("" + user.userId, reportCallback, name); + mediaManager.addActiveVideo("" + user.userId, name, connectionManager.getConnexionType === GameConnexionTypes.anonymous); const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection); @@ -391,13 +389,6 @@ export class SimplePeer { } } - /** - * Triggered locally when clicking on the report button - */ - public reportUser(userId: number, message: string) { - this.Connection.emitReportPlayerMessage(userId, message) - } - private sendLocalScreenSharingStreamToUser(userId: number): void { // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) if (this.PeerScreenSharingConnectionArray.has(userId)) { diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index 48e8a1a4..e9bccef8 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -105,11 +105,12 @@ class AdminApi { return res.data; } - reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) { + reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string, reportWorldSlug: string) { return Axios.post(`${ADMIN_API_URL}/api/report`, { reportedUserUuid, reportedUserComment, reporterUserUuid, + reportWorldSlug }, { headers: {"Authorization": `${ADMIN_API_TOKEN}`} diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 2f86ae19..7bd50b32 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -304,7 +304,7 @@ export class SocketManager implements ZoneEventListener { throw 'reported socket user not found'; } //TODO report user on admin application - await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid) + await adminApi.reportPlayer(reportedSocket.userUuid, reportPlayerMessage.getReportcomment(), client.userUuid, client.roomId.split('/')[2]) } catch (e) { console.error('An error occurred on "handleReportMessage"'); console.error(e); From ac2bc76239a3b0fa4cc6b3ffc46dc9f666e8e375 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 30 Jan 2021 14:08:11 +0100 Subject: [PATCH 2/2] change style report button --- front/dist/resources/style/style.css | 51 ++++++++++++++++++++++++---- front/src/WebRtc/MediaManager.ts | 11 ++++-- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/front/dist/resources/style/style.css b/front/dist/resources/style/style.css index 427eff75..1eb8440d 100644 --- a/front/dist/resources/style/style.css +++ b/front/dist/resources/style/style.css @@ -39,6 +39,7 @@ body .message-info.warning{ position: relative; transition: all 0.2s ease; background-color: #00000099; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; } .video-container i{ position: absolute; @@ -53,10 +54,7 @@ body .message-info.warning{ font-size: 28px; color: white; } -.video-container img.active{ - display: block; - cursor: url('/resources/logos/cursor_pointer.png'), pointer; -} + .video-container img{ position: absolute; display: none; @@ -68,9 +66,50 @@ body .message-info.warning{ z-index: 2; } -.video-container img.report{ +.video-container button.report{ + display: block; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; + background: none; + background-color: rgba(0, 0, 0, 0); + border: none; + background-color: black; + border-radius: 15px; + position: absolute; + width: 0px; + height: 35px; right: 5px; - left: auto; + bottom: 5px; + padding: 0px; + overflow: hidden; + z-index: 2; + transition: all .5s ease; +} + +.video-container:hover button.report{ + width: 35px; + padding: 10px; +} + +.video-container button.report:hover { + width: 94px; +} + +.video-container button.report img{ + position: absolute; + display: block; + bottom: 5px; + left: 5px; + margin: 0; + padding: 0; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; +} +.video-container button.report span{ + position: absolute; + bottom: 8px; + left: 36px; + color: white; + font-size: 16px; + cursor: url('/resources/logos/cursor_pointer.png'), pointer; } .video-container video{ diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 054f0fe2..78749491 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -486,7 +486,13 @@ export class MediaManager { ${userName} ` + - ((anonymous === false)?``:'') + ((anonymous === false)?` + + `:'' + ) + ` @@ -506,7 +512,8 @@ export class MediaManager { if(!anonymous){ const reportBanUserAction: HTMLImageElement = HtmlUtils.getElementByIdOrFail(`report-${userId}`); - reportBanUserAction.addEventListener('click', () => { + reportBanUserAction.addEventListener('click', (e) => { + e.preventDefault(); showReportUser(); }); }