From d51ac45079ef40f5304a63b8186596692c3d6feb Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 14:26:53 +0200 Subject: [PATCH 01/23] Show/Hide Layer now unset collision and can show/hide all the layer in a group layer --- front/src/Api/Events/LayerEvent.ts | 1 + front/src/Api/iframe/room.ts | 10 ++++---- front/src/Phaser/Game/GameMap.ts | 4 +++ front/src/Phaser/Game/GameScene.ts | 39 +++++++++++++++++++++--------- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/front/src/Api/Events/LayerEvent.ts b/front/src/Api/Events/LayerEvent.ts index b56c3163..d3fdda22 100644 --- a/front/src/Api/Events/LayerEvent.ts +++ b/front/src/Api/Events/LayerEvent.ts @@ -3,6 +3,7 @@ import * as tg from "generic-type-guard"; export const isLayerEvent = new tg.IsInterface() .withProperties({ name: tg.isString, + group: tg.isBoolean, }) .get(); /** diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index c70d0aad..8ff31375 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { isGameStateEvent } from "../Events/GameStateEvent"; -import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; +import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; @@ -93,11 +93,11 @@ export class WorkadventureRoomCommands extends IframeApiContribution layer.layer.name === layerName); } + public findPhaserLayers(groupName: string): TilemapLayer[] { + return this.phaserLayers.filter((l) => l.layer.name.includes(groupName)); + } + public addTerrain(terrain: Phaser.Tilemaps.Tileset): void { for (const phaserLayer of this.phaserLayers) { phaserLayer.tileset.push(terrain); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d767f0f4..5c3edfac 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1022,13 +1022,13 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.showLayerStream.subscribe((layerEvent) => { - this.setLayerVisibility(layerEvent.name, true); + this.setLayerVisibility(layerEvent.name, true, layerEvent.group); }) ); this.iframeSubscriptionList.push( iframeListener.hideLayerStream.subscribe((layerEvent) => { - this.setLayerVisibility(layerEvent.name, false); + this.setLayerVisibility(layerEvent.name, false, layerEvent.group); }) ); @@ -1044,7 +1044,7 @@ ${escapedMessage} }) ); - iframeListener.registerAnswerer('getState', () => { + iframeListener.registerAnswerer("getState", () => { return { mapUrl: this.MapUrlFile, startLayerName: this.startPositionCalculator.startLayerName, @@ -1084,14 +1084,31 @@ ${escapedMessage} property.value = propertyValue; } - private setLayerVisibility(layerName: string, visible: boolean): void { - const phaserLayer = this.gameMap.findPhaserLayer(layerName); - if (phaserLayer === undefined) { - console.warn('Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer'); - return; + private setLayerVisibility(layerName: string, visible: boolean, group: boolean): void { + if (group) { + const phaserLayers = this.gameMap.findPhaserLayers(layerName); + if (phaserLayers === []) { + console.warn( + 'Could not find layer with name that contains "' + + layerName + + '" when calling WA.hideLayer / WA.showLayer' + ); + return; + } + for (let i = 0; i < phaserLayers.length; i++) { + phaserLayers[i].setVisible(visible); + phaserLayers[i].setCollisionByProperty({ collides: true }, visible); + } + } else { + const phaserLayer = this.gameMap.findPhaserLayer(layerName); + if (phaserLayer === undefined) { + console.warn('Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer'); + return; + } + phaserLayer.setVisible(visible); + phaserLayer.setCollisionByProperty({ collides: true }, visible); } - phaserLayer.setVisible(visible); - this.dirty = true; + this.markDirty(); } private getMapDirUrl(): string { @@ -1147,7 +1164,7 @@ ${escapedMessage} this.emoteManager.destroy(); this.peerStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); - iframeListener.unregisterAnswerer('getState'); + iframeListener.unregisterAnswerer("getState"); mediaManager.hideGameOverlay(); From bef5e139c0d5fbcada2925bc5a3a54836986fdf7 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 14:42:17 +0200 Subject: [PATCH 02/23] SetTiles can now set a tile to null so that there is no more tile. --- front/src/Api/Events/SetTilesEvent.ts | 2 +- front/src/Api/iframe/room.ts | 4 ++-- front/src/Phaser/Game/GameMap.ts | 33 +++++++++++++++++++-------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/front/src/Api/Events/SetTilesEvent.ts b/front/src/Api/Events/SetTilesEvent.ts index c7f8f16d..371f0884 100644 --- a/front/src/Api/Events/SetTilesEvent.ts +++ b/front/src/Api/Events/SetTilesEvent.ts @@ -5,7 +5,7 @@ export const isSetTilesEvent = tg.isArray( .withProperties({ x: tg.isNumber, y: tg.isNumber, - tile: tg.isUnion(tg.isNumber, tg.isString), + tile: tg.isUnion(tg.isUnion(tg.isNumber, tg.isString), tg.isNull), layer: tg.isString, }) .get() diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index c70d0aad..a9ee52ce 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,7 +4,7 @@ import { isDataLayerEvent } from "../Events/DataLayerEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { isGameStateEvent } from "../Events/GameStateEvent"; -import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; +import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { apiCallback } from "./registeredCallbacks"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; @@ -34,7 +34,7 @@ interface User { interface TileDescriptor { x: number; y: number; - tile: number | string; + tile: number | string | null; layer: string; } diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index a616cf4a..1f232265 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -152,7 +152,10 @@ export class GameMap { } private getTileProperty(index: number): Array { - return this.tileSetPropertyMap[index]; + if (this.tileSetPropertyMap[index]) { + return this.tileSetPropertyMap[index]; + } + return []; } private trigger( @@ -198,37 +201,49 @@ export class GameMap { private putTileInFlatLayer(index: number, x: number, y: number, layer: string): void { const fLayer = this.findLayer(layer); if (fLayer == undefined) { - console.error("The layer that you want to change doesn't exist."); + console.error("The layer '" + layer + "' that you want to change doesn't exist."); return; } if (fLayer.type !== "tilelayer") { - console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + console.error( + "The layer '" + + layer + + "' that you want to change is not a tilelayer. Tile can only be put in tilelayer." + ); return; } if (typeof fLayer.data === "string") { - console.error("Data of the layer that you want to change is only readable."); + console.error("Data of the layer '" + layer + "' that you want to change is only readable."); return; } - fLayer.data[x + y * fLayer.height] = index; + fLayer.data[x + y * fLayer.width] = index; } - public putTile(tile: string | number, x: number, y: number, layer: string): void { + public putTile(tile: string | number | null, x: number, y: number, layer: string): void { const phaserLayer = this.findPhaserLayer(layer); if (phaserLayer) { + if (tile === null) { + phaserLayer.putTileAt(-1, x, y); + return; + } const tileIndex = this.getIndexForTileType(tile); if (tileIndex !== undefined) { this.putTileInFlatLayer(tileIndex, x, y, layer); const phaserTile = phaserLayer.putTileAt(tileIndex, x, y); for (const property of this.getTileProperty(tileIndex)) { - if (property.name === "collides" && property.value === "true") { + if (property.name === "collides" && property.value) { phaserTile.setCollision(true); } } } else { - console.error("The tile that you want to place doesn't exist."); + console.error("The tile '" + tile + "' that you want to place doesn't exist."); } } else { - console.error("The layer that you want to change is not a tilelayer. Tile can only be put in tilelayer."); + console.error( + "The layer '" + + layer + + "' that you want to change is not a tilelayer. Tile can only be put in tilelayer." + ); } } From 24811e0a31a70a4d32fe3fda16d0f71274d6f872 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 14:59:40 +0200 Subject: [PATCH 03/23] SetProperty delete a property where tha value is undefined and load the map of exitUrl property --- front/src/Phaser/Game/GameScene.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d6df242f..6427e0c0 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1077,14 +1077,24 @@ ${escapedMessage} console.warn('Could not find layer "' + layerName + '" when calling setProperty'); return; } + if (propertyName === "exitUrl" && typeof propertyValue === "string") { + this.loadNextGame(propertyValue); + } if (layer.properties === undefined) { layer.properties = []; } const property = layer.properties.find((property) => property.name === propertyName); if (property === undefined) { + if (propertyValue === undefined) { + return; + } layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue }); return; } + if (propertyValue === undefined) { + const index = layer.properties.indexOf(property); + layer.properties.splice(index, 1); + } property.value = propertyValue; } From 17525e1e158256023efda437e88a1f04f4d2bef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Wed, 7 Jul 2021 16:42:26 +0200 Subject: [PATCH 04/23] Return at the new line into the Pop-up (#1267) Add regex to replace "\r\n" or "\r" or "\n" by
--- front/src/WebRtc/HtmlUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts index 942e553f..569abd07 100644 --- a/front/src/WebRtc/HtmlUtils.ts +++ b/front/src/WebRtc/HtmlUtils.ts @@ -25,7 +25,7 @@ export class HtmlUtils { } public static escapeHtml(html: string): string { - const text = document.createTextNode(html); + const text = document.createTextNode(html.replace(/(\r\n|\r|\n)/g,'
')); const p = document.createElement('p'); p.appendChild(text); return p.innerHTML; From e50292a2ba42814fd6759df68abef10b26a3723b Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 16:58:54 +0200 Subject: [PATCH 05/23] Add documentation No second parameter --- docs/maps/api-room.md | 4 ++++ front/src/Api/Events/LayerEvent.ts | 1 - front/src/Api/iframe/room.ts | 8 ++++---- front/src/Phaser/Game/GameScene.ts | 22 +++++++++------------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 9d08ce1b..86886567 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -54,6 +54,7 @@ WA.room.showLayer(layerName : string): void WA.room.hideLayer(layerName : string) : void ``` These 2 methods can be used to show and hide a layer. +if `layerName` is the name of a group layer, show/hide all the layer in that group layer. Example : ```javascript @@ -70,6 +71,9 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. +Note : +To unset a property form a layer, use `setProperty` with `propertyValue` set to `undefined`. + Example : ```javascript WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); diff --git a/front/src/Api/Events/LayerEvent.ts b/front/src/Api/Events/LayerEvent.ts index d3fdda22..b56c3163 100644 --- a/front/src/Api/Events/LayerEvent.ts +++ b/front/src/Api/Events/LayerEvent.ts @@ -3,7 +3,6 @@ import * as tg from "generic-type-guard"; export const isLayerEvent = new tg.IsInterface() .withProperties({ name: tg.isString, - group: tg.isBoolean, }) .get(); /** diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index 8ff31375..deee0e2a 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -93,11 +93,11 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - this.setLayerVisibility(layerEvent.name, true, layerEvent.group); + this.setLayerVisibility(layerEvent.name, true); }) ); this.iframeSubscriptionList.push( iframeListener.hideLayerStream.subscribe((layerEvent) => { - this.setLayerVisibility(layerEvent.name, false, layerEvent.group); + this.setLayerVisibility(layerEvent.name, false); }) ); @@ -1088,9 +1088,13 @@ ${escapedMessage} property.value = propertyValue; } - private setLayerVisibility(layerName: string, visible: boolean, group: boolean): void { - if (group) { - const phaserLayers = this.gameMap.findPhaserLayers(layerName); + private setLayerVisibility(layerName: string, visible: boolean): void { + const phaserLayer = this.gameMap.findPhaserLayer(layerName); + if (phaserLayer != undefined) { + phaserLayer.setVisible(visible); + phaserLayer.setCollisionByProperty({ collides: true }, visible); + } else { + const phaserLayers = this.gameMap.findPhaserLayers(layerName + "/"); if (phaserLayers === []) { console.warn( 'Could not find layer with name that contains "' + @@ -1103,14 +1107,6 @@ ${escapedMessage} phaserLayers[i].setVisible(visible); phaserLayers[i].setCollisionByProperty({ collides: true }, visible); } - } else { - const phaserLayer = this.gameMap.findPhaserLayer(layerName); - if (phaserLayer === undefined) { - console.warn('Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer'); - return; - } - phaserLayer.setVisible(visible); - phaserLayer.setCollisionByProperty({ collides: true }, visible); } this.markDirty(); } From 64c569c42f6bf0b1edd1ee039551c7aff0132bb8 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 7 Jul 2021 17:06:23 +0200 Subject: [PATCH 06/23] Add documentation and CHANGELOG Modify error message --- CHANGELOG.md | 4 ++-- docs/maps/api-room.md | 1 + front/src/Phaser/Game/GameMap.ts | 6 +----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83e8213..ff7496ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,12 @@ - New scripting API features : - Use `WA.room.showLayer(): void` to show a layer - Use `WA.room.hideLayer(): void` to hide a layer - - Use `WA.room.setProperty() : void` to add or change existing property of a layer + - Use `WA.room.setProperty() : void` to add, delete or change existing property of a layer - Use `WA.player.onPlayerMove(): void` to track the movement of the current player - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - - Use `WA.room.setTiles(): void` to change an array of tiles + - Use `WA.room.setTiles(): void` to add, delete or change an array of tiles - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 9d08ce1b..22735f6c 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -134,6 +134,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property **Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. +Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`. Example : ```javascript diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 1f232265..99a1edad 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -239,11 +239,7 @@ export class GameMap { console.error("The tile '" + tile + "' that you want to place doesn't exist."); } } else { - console.error( - "The layer '" + - layer + - "' that you want to change is not a tilelayer. Tile can only be put in tilelayer." - ); + console.error("The layer '" + layer + "' does not exist (or is not a tilelaye)."); } } From cb5bdb5fea0d800bfecc235e78995d9650facf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 7 Jul 2021 17:15:22 +0200 Subject: [PATCH 07/23] Fixing typo --- docs/maps/api-room.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 86886567..93cb732a 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -72,7 +72,7 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. Note : -To unset a property form a layer, use `setProperty` with `propertyValue` set to `undefined`. +To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. Example : ```javascript From 3cfbcc6b020b615e2b768295ff0d58c856ea7b0e Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 7 Jul 2021 18:07:58 +0200 Subject: [PATCH 08/23] FEATURE: migrated the chat window to svelte --- CHANGELOG.md | 5 + back/src/Model/GameRoom.ts | 6 - back/src/Services/SocketManager.ts | 6 - front/dist/index.tmpl.html | 3 +- front/dist/static/images/send.png | Bin 0 -> 8523 bytes front/src/Components/App.svelte | 13 +- front/src/Components/Chat/Chat.svelte | 97 ++++++++ front/src/Components/Chat/ChatElement.svelte | 74 ++++++ .../Components/Chat/ChatMessageForm.svelte | 55 +++++ .../src/Components/Chat/ChatPlayerName.svelte | 37 +++ front/src/Phaser/Components/OpenChatIcon.ts | 12 +- .../Entity/PlayerTexturesLoadingManager.ts | 1 - front/src/Phaser/Game/GameScene.ts | 4 +- front/src/Phaser/Game/PlayerInterface.ts | 1 + front/src/Stores/ChatStore.ts | 102 ++++++++ front/src/Stores/PlayersStore.ts | 2 + front/src/Stores/UserInputStore.ts | 13 +- front/src/WebRtc/ColorGenerator.ts | 48 ++++ front/src/WebRtc/DiscussionManager.ts | 226 +----------------- front/src/WebRtc/MediaManager.ts | 21 +- front/src/WebRtc/SimplePeer.ts | 17 +- front/src/WebRtc/VideoPeer.ts | 27 ++- front/style/fonts.scss | 4 - front/webpack.config.ts | 1 - 24 files changed, 470 insertions(+), 305 deletions(-) create mode 100644 front/dist/static/images/send.png create mode 100644 front/src/Components/Chat/Chat.svelte create mode 100644 front/src/Components/Chat/ChatElement.svelte create mode 100644 front/src/Components/Chat/ChatMessageForm.svelte create mode 100644 front/src/Components/Chat/ChatPlayerName.svelte create mode 100644 front/src/Stores/ChatStore.ts create mode 100644 front/src/WebRtc/ColorGenerator.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a83e8213..018848b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ - Use `WA.ui.registerMenuCommand(): void` to add a custom menu - Use `WA.room.setTiles(): void` to change an array of tiles - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. +- The text chat was redesigned to be prettier and to use more features : + - The chat is now persistent bewteen discussions and always accesible + - The chat now tracks incoming and outcoming users in your conversation + - The chat allows your to see the visit card of users + - You can close the chat window with the escape key ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 020f4c29..71d2124e 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -15,12 +15,6 @@ import { Admin } from "../Model/Admin"; export type ConnectCallback = (user: User, group: Group) => void; export type DisconnectCallback = (user: User, group: Group) => void; -export enum GameRoomPolicyTypes { - ANONYMOUS_POLICY = 1, - MEMBERS_ONLY_POLICY, - USE_TAGS_POLICY, -} - export class GameRoom { private readonly minDistance: number; private readonly groupRadius: number; diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 8d04e713..8d1659df 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -436,10 +436,7 @@ export class SocketManager { const serverToClientMessage1 = new ServerToClientMessage(); serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1); - //if (!user.socket.disconnecting) { user.socket.write(serverToClientMessage1); - //console.log('Sending webrtcstart initiator to '+user.socket.userId) - //} const webrtcStartMessage2 = new WebRtcStartMessage(); webrtcStartMessage2.setUserid(user.id); @@ -453,10 +450,7 @@ export class SocketManager { const serverToClientMessage2 = new ServerToClientMessage(); serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2); - //if (!otherUser.socket.disconnecting) { otherUser.socket.write(serverToClientMessage2); - //console.log('Sending webrtcstart to '+otherUser.socket.userId) - //} } } diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index aa63229f..30ea8353 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -37,8 +37,7 @@
-
-
+
diff --git a/front/dist/static/images/send.png b/front/dist/static/images/send.png new file mode 100644 index 0000000000000000000000000000000000000000..1f75634a8ec2156ec4dfdf51353aa7f6f760143e GIT binary patch literal 8523 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 TvtN}2_6tB3)~5
{/if} - - {#if $gameOverlayVisibilityStore}
@@ -94,4 +88,7 @@
{/if} + {#if $chatVisibilityStore} + + {/if}
diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte new file mode 100644 index 00000000..c4f9075d --- /dev/null +++ b/front/src/Components/Chat/Chat.svelte @@ -0,0 +1,97 @@ + + + + + + + + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatElement.svelte b/front/src/Components/Chat/ChatElement.svelte new file mode 100644 index 00000000..95a050eb --- /dev/null +++ b/front/src/Components/Chat/ChatElement.svelte @@ -0,0 +1,74 @@ + + +
+
+ {#if message.type === ChatMessageTypes.userIncoming} + ➡️: {#each targets as target}{/each} ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.userOutcoming} + ⬅️: {#each targets as target}{/each} ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.me} +

Me: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {:else} +

: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {/if} +
+
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatMessageForm.svelte b/front/src/Components/Chat/ChatMessageForm.svelte new file mode 100644 index 00000000..acfd68ae --- /dev/null +++ b/front/src/Components/Chat/ChatMessageForm.svelte @@ -0,0 +1,55 @@ + + +
+ + +
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatPlayerName.svelte b/front/src/Components/Chat/ChatPlayerName.svelte new file mode 100644 index 00000000..f0fbe8cd --- /dev/null +++ b/front/src/Components/Chat/ChatPlayerName.svelte @@ -0,0 +1,37 @@ + + + showMenu = !showMenu}> + {player.name} + + +{#if showMenu} +
    +
  • +
+{/if} + + + \ No newline at end of file diff --git a/front/src/Phaser/Components/OpenChatIcon.ts b/front/src/Phaser/Components/OpenChatIcon.ts index ab07a80c..8c648bc1 100644 --- a/front/src/Phaser/Components/OpenChatIcon.ts +++ b/front/src/Phaser/Components/OpenChatIcon.ts @@ -1,7 +1,7 @@ -import {discussionManager} from "../../WebRtc/DiscussionManager"; -import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; +import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; +import { chatVisibilityStore } from "../../Stores/ChatStore"; -export const openChatIconName = 'openChatIcon'; +export const openChatIconName = "openChatIcon"; export class OpenChatIcon extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, openChatIconName, 3); @@ -9,9 +9,9 @@ export class OpenChatIcon extends Phaser.GameObjects.Image { this.setScrollFactor(0, 0); this.setOrigin(0, 1); this.setInteractive(); - this.setVisible(false); + //this.setVisible(false); this.setDepth(DEPTH_INGAME_TEXT_INDEX); - this.on("pointerup", () => discussionManager.showDiscussionPart()); + this.on("pointerup", () => chatVisibilityStore.set(true)); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index d2a659ec..3c47c9d9 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -101,7 +101,6 @@ export const createLoadingPromise = ( frameConfig: FrameConfig ) => { return new Promise((res, rej) => { - console.log("count", loadPlugin.listenerCount("loaderror")); if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d6df242f..3ccc9fd9 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -692,12 +692,12 @@ export class GameScene extends DirtyScene { const self = this; this.simplePeer.registerPeerConnectionListener({ onConnect(peer) { - self.openChatIcon.setVisible(true); + //self.openChatIcon.setVisible(true); audioManager.decreaseVolume(); }, onDisconnect(userId: number) { if (self.simplePeer.getNbConnections() === 0) { - self.openChatIcon.setVisible(false); + //self.openChatIcon.setVisible(false); audioManager.restoreVolume(); } }, diff --git a/front/src/Phaser/Game/PlayerInterface.ts b/front/src/Phaser/Game/PlayerInterface.ts index 5a81c89a..6ab439df 100644 --- a/front/src/Phaser/Game/PlayerInterface.ts +++ b/front/src/Phaser/Game/PlayerInterface.ts @@ -7,4 +7,5 @@ export interface PlayerInterface { visitCardUrl: string | null; companion: string | null; userUuid: string; + color?: string; } diff --git a/front/src/Stores/ChatStore.ts b/front/src/Stores/ChatStore.ts new file mode 100644 index 00000000..344a424e --- /dev/null +++ b/front/src/Stores/ChatStore.ts @@ -0,0 +1,102 @@ +import { writable } from "svelte/store"; +import { playersStore } from "./PlayersStore"; +import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; + +export const chatVisibilityStore = writable(false); +export const chatInputFocusStore = writable(false); + +export const newChatMessageStore = writable(null); + +export enum ChatMessageTypes { + text = 1, + me, + userIncoming, + userOutcoming, +} + +export interface ChatMessage { + type: ChatMessageTypes; + date: Date; + author?: PlayerInterface; + targets?: PlayerInterface[]; + text?: string[]; +} + +function getAuthor(authorId: number): PlayerInterface { + const author = playersStore.getPlayerById(authorId); + if (!author) { + throw "Could not find data for author " + authorId; + } + return author; +} + +function createChatMessagesStore() { + const { subscribe, update } = writable([]); + + return { + subscribe, + addIncomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userIncoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userIncoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addOutcomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userOutcoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userOutcoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addPersonnalMessage(text: string) { + newChatMessageStore.set(text); + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.me && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.me, + text: [text], + date: new Date(), + }); + } + return list; + }); + }, + addExternalMessage(authorId: number, text: string) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.text && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.text, + text: [text], + author: getAuthor(authorId), + date: new Date(), + }); + } + return list; + }); + }, + }; +} +export const chatMessagesStore = createChatMessagesStore(); diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 6c21de7a..2ea988bb 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; import type { RoomConnection } from "../Connexion/RoomConnection"; +import { getRandomColor } from "../WebRtc/ColorGenerator"; /** * A store that contains the list of players currently known. @@ -24,6 +25,7 @@ function createPlayersStore() { visitCardUrl: message.visitCardUrl, companion: message.companion, userUuid: message.userUuid, + color: getRandomColor(), }); return users; }); diff --git a/front/src/Stores/UserInputStore.ts b/front/src/Stores/UserInputStore.ts index cbb7f0c3..993d8795 100644 --- a/front/src/Stores/UserInputStore.ts +++ b/front/src/Stores/UserInputStore.ts @@ -1,10 +1,11 @@ -import {derived} from "svelte/store"; -import {consoleGlobalMessageManagerFocusStore} from "./ConsoleGlobalMessageManagerStore"; +import { derived } from "svelte/store"; +import { consoleGlobalMessageManagerFocusStore } from "./ConsoleGlobalMessageManagerStore"; +import { chatInputFocusStore } from "./ChatStore"; //derived from the focus on Menu, ConsoleGlobal, Chat and ... export const enableUserInputsStore = derived( - consoleGlobalMessageManagerFocusStore, - ($consoleGlobalMessageManagerFocusStore) => { - return !$consoleGlobalMessageManagerFocusStore; + [consoleGlobalMessageManagerFocusStore, chatInputFocusStore], + ([$consoleGlobalMessageManagerFocusStore, $chatInputFocusStore]) => { + return !$consoleGlobalMessageManagerFocusStore && !$chatInputFocusStore; } -); \ No newline at end of file +); diff --git a/front/src/WebRtc/ColorGenerator.ts b/front/src/WebRtc/ColorGenerator.ts new file mode 100644 index 00000000..a42aee85 --- /dev/null +++ b/front/src/WebRtc/ColorGenerator.ts @@ -0,0 +1,48 @@ +export function getRandomColor(): string { + return hsv_to_rgb(Math.random(), 0.5, 0.95); +} + +//todo: test this. +function hsv_to_rgb(hue: number, saturation: number, brightness: number): string { + const h_i = Math.floor(hue * 6); + const f = hue * 6 - h_i; + const p = brightness * (1 - saturation); + const q = brightness * (1 - f * saturation); + const t = brightness * (1 - (1 - f) * saturation); + let r: number, g: number, b: number; + switch (h_i) { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + case 5: + r = brightness; + g = p; + b = q; + break; + default: + throw "h_i cannot be " + h_i; + } + return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16); +} diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index ae351f76..a3c928f4 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,232 +1,12 @@ -import { HtmlUtils } from "./HtmlUtils"; -import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { connectionManager } from "../Connexion/ConnectionManager"; -import { GameConnexionTypes } from "../Url/UrlManager"; import { iframeListener } from "../Api/IframeListener"; -import { showReportScreenStore } from "../Stores/ShowReportScreenStore"; - -export type SendMessageCallback = (message: string) => void; +import { chatMessagesStore, chatVisibilityStore } from "../Stores/ChatStore"; 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: Map = new Map< - number | string, - SendMessageCallback - >(); - - private userInputManager?: UserInputManager; - constructor() { - this.mainContainer = HtmlUtils.getElementByIdOrFail("main-container"); - this.createDiscussPart(""); //todo: why do we always use empty string? - iframeListener.chatStream.subscribe((chatEvent) => { - this.addMessage(chatEvent.author, chatEvent.message, false); - this.showDiscussion(); + chatMessagesStore.addExternalMessage(parseInt(chatEvent.author), chatEvent.message); + chatVisibilityStore.set(true); }); - this.onSendMessageCallback("iframe_listener", (message) => { - iframeListener.sendUserInputChat(message); - }); - } - - private createDiscussPart(name: string) { - this.divDiscuss = document.createElement("div"); - this.divDiscuss.classList.add("discussion"); - - const buttonCloseDiscussion: HTMLButtonElement = document.createElement("button"); - buttonCloseDiscussion.classList.add("close-btn"); - buttonCloseDiscussion.innerHTML = ``; - buttonCloseDiscussion.addEventListener("click", () => { - this.hideDiscussion(); - }); - this.divDiscuss.appendChild(buttonCloseDiscussion); - - 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.onfocus = () => { - if (this.userInputManager) { - this.userInputManager.disableControls(); - } - }; - inputMessage.onblur = () => { - if (this.userInputManager) { - this.userInputManager.restoreControls(); - } - }; - 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 | "me", - 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 && - connectionManager.getConnexionType && - connectionManager.getConnexionType !== GameConnexionTypes.anonymous && - userId !== "me" - ) { - const reportBanUserAction: HTMLButtonElement = document.createElement("button"); - reportBanUserAction.classList.add("report-btn"); - reportBanUserAction.innerText = "Report"; - reportBanUserAction.addEventListener("click", () => { - showReportScreenStore.set({ userId: userId, userName: name ? name : "" }); - }); - 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 = "Me"; - } else { - name = HtmlUtils.escapeHtml(name); - } - pMessage.innerHTML = `${name} - - ${date.getHours()}:${date.getMinutes()} - `; - divMessage.appendChild(pMessage); - - const userMessage: HTMLParagraphElement = document.createElement("p"); - userMessage.innerHTML = HtmlUtils.urlify(message); - userMessage.classList.add("body"); - divMessage.appendChild(userMessage); - this.divMessages?.appendChild(divMessage); - - //automatic scroll when there are new message - setTimeout(() => { - this.divMessages?.scroll({ - top: this.divMessages?.scrollTop + divMessage.getBoundingClientRect().y, - behavior: "smooth", - }); - }, 200); - } - - 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 - - this.sendMessageCallBack.delete(userId); - } - - public onSendMessageCallback(userId: string | number, callback: SendMessageCallback): void { - this.sendMessageCallBack.set(userId, callback); - } - - get activatedDiscussion() { - return this.activeDiscussion; - } - - private showDiscussion() { - this.activeDiscussion = true; - this.divDiscuss?.classList.add("active"); - } - - private hideDiscussion() { - this.activeDiscussion = false; - this.divDiscuss?.classList.remove("active"); - } - - public setUserInputManager(userInputManager: UserInputManager) { - this.userInputManager = userInputManager; - } - - public showDiscussionPart() { - this.showDiscussion(); } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d9847f44..126bf1a8 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,16 +1,11 @@ -import { DivImportance, layoutManager } from "./LayoutManager"; +import { layoutManager } from "./LayoutManager"; import { HtmlUtils } from "./HtmlUtils"; -import { discussionManager, SendMessageCallback } from "./DiscussionManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { localUserStore } from "../Connexion/LocalUserStore"; -import type { UserSimplePeerInterface } from "./SimplePeer"; -import { SoundMeter } from "../Phaser/Components/SoundMeter"; import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable"; import { localStreamStore } from "../Stores/MediaStore"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore"; -export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void; @@ -182,22 +177,8 @@ export class MediaManager { } } - public addNewMessage(name: string, message: string, isMe: boolean = false) { - discussionManager.addMessage(name, message, isMe); - - //when there are new message, show discussion - if (!discussionManager.activatedDiscussion) { - discussionManager.showDiscussionPart(); - } - } - - public addSendMessageCallback(userId: string | number, callback: SendMessageCallback) { - discussionManager.onSendMessageCallback(userId, callback); - } - public setUserInputManager(userInputManager: UserInputManager) { this.userInputManager = userInputManager; - discussionManager.setUserInputManager(userInputManager); } public getNotification() { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 5045a5a3..e30f1b1f 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -12,6 +12,7 @@ import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { newChatMessageStore } from "../Stores/ChatStore"; export interface UserSimplePeerInterface { userId: number; @@ -155,27 +156,11 @@ export class SimplePeer { const name = this.getName(user.userId); - discussionManager.removeParticipant(user.userId); - this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcPassword = user.webRtcPassword; const peer = new VideoPeer(user, user.initiator ? user.initiator : false, name, this.Connection, localStream); - //permit to send message - mediaManager.addSendMessageCallback(user.userId, (message: string) => { - peer.write( - new Buffer( - JSON.stringify({ - type: MESSAGE_TYPE_MESSAGE, - name: this.myName.toUpperCase(), - userId: this.userId, - 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! diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index bde0bcde..45118b5f 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -5,10 +5,11 @@ import type { RoomConnection } from "../Connexion/RoomConnection"; import { blackListManager } from "./BlackListManager"; import type { Subscription } from "rxjs"; import type { UserSimplePeerInterface } from "./SimplePeer"; -import { get, readable, Readable } from "svelte/store"; +import { get, readable, Readable, Unsubscriber } from "svelte/store"; import { obtainedMediaConstraintStore } from "../Stores/MediaStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore"; const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer"); @@ -34,6 +35,7 @@ export class VideoPeer extends Peer { public readonly streamStore: Readable; public readonly statusStore: Readable; public readonly constraintsStore: Readable; + private newMessageunsubscriber: Unsubscriber | null = null; constructor( public user: UserSimplePeerInterface, @@ -147,6 +149,20 @@ export class VideoPeer extends Peer { this.on("connect", () => { this._connected = true; + chatMessagesStore.addIncomingUser(this.userId); + + this.newMessageunsubscriber = newChatMessageStore.subscribe((newMessage) => { + if (!newMessage) return; + this.write( + new Buffer( + JSON.stringify({ + type: MESSAGE_TYPE_MESSAGE, + message: newMessage, + }) + ) + ); //send more data + newChatMessageStore.set(null); //This is to prevent a newly created SimplePeer to send an old message a 2nd time. Is there a better way? + }); }); this.on("data", (chunk: Buffer) => { @@ -164,8 +180,9 @@ export class VideoPeer extends Peer { mediaManager.disabledVideoByUserId(this.userId); } } else if (message.type === MESSAGE_TYPE_MESSAGE) { - if (!blackListManager.isBlackListed(message.userId)) { - mediaManager.addNewMessage(message.name, message.message); + if (!blackListManager.isBlackListed(this.userUuid)) { + chatMessagesStore.addExternalMessage(this.userId, message.message); + chatVisibilityStore.set(true); } } else if (message.type === MESSAGE_TYPE_BLOCKED) { //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream. @@ -253,7 +270,9 @@ export class VideoPeer extends Peer { } this.onBlockSubscribe.unsubscribe(); this.onUnBlockSubscribe.unsubscribe(); - discussionManager.removeParticipant(this.userId); + if (this.newMessageunsubscriber) this.newMessageunsubscriber(); + chatMessagesStore.addOutcomingUser(this.userId); + //discussionManager.removeParticipant(this.userId); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. super.destroy(error); diff --git a/front/style/fonts.scss b/front/style/fonts.scss index a49d3967..526f6615 100644 --- a/front/style/fonts.scss +++ b/front/style/fonts.scss @@ -1,9 +1,5 @@ @import "~@fontsource/press-start-2p/index.css"; -*{ - font-family: PixelFont-7,monospace; -} - .nes-btn { font-family: "Press Start 2P"; } diff --git a/front/webpack.config.ts b/front/webpack.config.ts index b6efb389..37362baf 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin"; import sveltePreprocess from "svelte-preprocess"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import NodePolyfillPlugin from "node-polyfill-webpack-plugin"; -import { DISPLAY_TERMS_OF_USE } from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? "development"; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS; From b9a2433283766e2ff61a1f753d956a41abf37b86 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 12 Jul 2021 11:59:05 +0200 Subject: [PATCH 09/23] Upgrade graphic of the chat --- front/src/Components/Chat/Chat.svelte | 12 ++++++++++-- front/src/Components/Chat/ChatElement.svelte | 4 ++-- front/src/Components/Chat/ChatMessageForm.svelte | 2 ++ front/src/WebRtc/ColorGenerator.ts | 6 +++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte index c4f9075d..093d01a5 100644 --- a/front/src/Components/Chat/Chat.svelte +++ b/front/src/Components/Chat/Chat.svelte @@ -32,7 +32,7 @@