From 4a5ef4ab8631d314f26f252bd589fb9533876e87 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Wed, 12 Jan 2022 10:48:41 +0100 Subject: [PATCH 01/60] Change group radius management --- back/src/Model/GameRoom.ts | 112 ++++++++++++++++++++++++++++--------- back/src/Model/Group.ts | 57 +++++++++++++++---- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index aefade43..7d7b24a5 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -213,13 +213,13 @@ export class GameRoom { } private updateUserGroup(user: User): void { - user.group?.updatePosition(); - user.group?.searchForNearbyUsers(); - if (user.silent) { return; } + const group = user.group; + const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user); + if (group === undefined) { // If the user is not part of a group: // should he join a group? @@ -229,12 +229,11 @@ export class GameRoom { return; } - const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user); - if (closestItem !== null) { if (closestItem instanceof Group) { // Let's join the group! closestItem.join(user); + closestItem.setOutOfBounds(false); } else { const closestUser: User = closestItem; const group: Group = new Group( @@ -249,32 +248,95 @@ export class GameRoom { } } } else { - // If the user is part of a group: - // should he leave the group? - let noOneOutOfBounds = true; - group.getUsers().forEach((foreignUser: User) => { - if (foreignUser.group === undefined) { - return; + let hasKickOutSomeone = false; + let followingMembers: User[] = []; + + const previewNewGroupPosition = group.previewGroupPosition(); + + if (!previewNewGroupPosition) { + this.leaveGroup(user); + return; + } + + if (user.hasFollowers() || user.following) { + followingMembers = user.hasFollowers() + ? group.getUsers().filter((currentUser) => currentUser.following === user) + : group.getUsers().filter((currentUser) => currentUser.following === user.following); + + // If all group members are part of the same follow group + if (group.getUsers().length - 1 === followingMembers.length) { + let isOutOfBounds = false; + + // If a follower is far away from the leader, "outOfBounds" is set to true + for (const member of followingMembers) { + const distance = GameRoom.computeDistanceBetweenPositions( + member.getPosition(), + previewNewGroupPosition + ); + + if (distance > this.groupRadius) { + isOutOfBounds = true; + break; + } + } + group.setOutOfBounds(isOutOfBounds); } - const usrPos = foreignUser.getPosition(); - const grpPos = foreignUser.group.getPosition(); - const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos); + } + + // Check if the moving user has kicked out another user + for (const headMember of group.getGroupHeads()) { + if (!headMember.group) { + this.leaveGroup(headMember); + continue; + } + + const headPosition = headMember.getPosition(); + const distance = GameRoom.computeDistanceBetweenPositions(headPosition, previewNewGroupPosition); if (distance > this.groupRadius) { - if (foreignUser.hasFollowers() || foreignUser.following) { - // If one user is out of the group bounds BUT following, the group still exists... but should be hidden. - // We put it in 'outOfBounds' mode - group.setOutOfBounds(true); - noOneOutOfBounds = false; - } else { - this.leaveGroup(foreignUser); - } + hasKickOutSomeone = true; + break; + } + } + + /** + * If the current moving user has kicked another user from the radius, + * the moving user leaves the group because he is too far away. + */ + const userDistance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), previewNewGroupPosition); + + if (hasKickOutSomeone && userDistance > this.groupRadius) { + if (user.hasFollowers() && group.getUsers().length === 3 && followingMembers.length === 1) { + const other = group + .getUsers() + .find((currentUser) => !currentUser.hasFollowers() && !currentUser.following); + if (other) { + this.leaveGroup(other); + } + } else if (user.hasFollowers()) { + this.leaveGroup(user); + for (const member of followingMembers) { + this.leaveGroup(member); + } + + // Re-create a group with the followers + const newGroup: Group = new Group( + this.roomUrl, + [user, ...followingMembers], + this.groupRadius, + this.connectCallback, + this.disconnectCallback, + this.positionNotifier + ); + this.groups.add(newGroup); + } else { + this.leaveGroup(user); } - }); - if (noOneOutOfBounds && !user.group?.isEmpty()) { - group.setOutOfBounds(false); } } + + user.group?.updatePosition(); + user.group?.searchForNearbyUsers(); } public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void { diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index 0782bd1b..c14d509f 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -59,6 +59,39 @@ export class Group implements Movable { }; } + /** + * Returns the list of users of the group, ignoring any "followers". + * Useful to compute the position of the group if a follower is "trapped" far away from the the leader. + */ + getGroupHeads(): User[] { + return Array.from(this.users).filter((user) => user.group?.leader === user || !user.following); + } + + /** + * Preview the position of the group but don't update it + */ + previewGroupPosition(): { x: number; y: number } | undefined { + const users = this.getGroupHeads(); + + let x = 0; + let y = 0; + + if (users.length === 0) { + return undefined; + } + + users.forEach((user: User) => { + const position = user.getPosition(); + x += position.x; + y += position.y; + }); + + x /= users.length; + y /= users.length; + + return { x, y }; + } + /** * Computes the barycenter of all users (i.e. the center of the group) */ @@ -66,19 +99,15 @@ export class Group implements Movable { const oldX = this.x; const oldY = this.y; - let x = 0; - let y = 0; // Let's compute the barycenter of all users. - this.users.forEach((user: User) => { - const position = user.getPosition(); - x += position.x; - y += position.y; - }); - x /= this.users.size; - y /= this.users.size; - if (this.users.size === 0) { - throw new Error("EMPTY GROUP FOUND!!!"); + const newPosition = this.previewGroupPosition(); + + if (!newPosition) { + return; } + + const { x, y } = newPosition; + this.x = x; this.y = y; @@ -97,10 +126,12 @@ export class Group implements Movable { if (!this.currentZone) return; for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) { + // Todo: Merge two groups with a leader if (user.group || this.isFull()) return; //we ignore users that are already in a group. const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition()); if (distance < this.groupRadius) { this.join(user); + this.setOutOfBounds(false); this.updatePosition(); } } @@ -176,4 +207,8 @@ export class Group implements Movable { this.outOfBounds = true; } } + + get getOutOfBounds() { + return this.outOfBounds; + } } From cb0c5beecfde3c50c96acc7256f0d5385dfd7de1 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Thu, 20 Jan 2022 14:14:24 +0100 Subject: [PATCH 02/60] get additional hash parameters from url --- front/src/Url/UrlManager.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 011efa5a..c6e463e4 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -49,6 +49,14 @@ class UrlManager { return hash.length > 1 ? hash.substring(1) : null; } + public getHashParameters(): Record { + return window.location.hash.split("&").reduce((res: Record, item: string) => { + const parts = item.split("="); + res[parts[0]] = parts[1]; + return res; + }, {}); + } + pushStartLayerNameToUrl(startLayerName: string): void { window.location.hash = startLayerName; } From e87c0a07a41004fa86d50333629f2fd43bb7bd3b Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Thu, 20 Jan 2022 15:10:54 +0100 Subject: [PATCH 03/60] moveTo parameter working if layer is found --- front/src/Phaser/Game/GameMap.ts | 33 +++++++++++++++++++++++++++++- front/src/Phaser/Game/GameScene.ts | 23 ++++++++++++++++----- front/src/Url/UrlManager.ts | 5 +++-- front/src/Utils/MathUtils.ts | 4 ++++ 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index fc16110f..54f91866 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,4 +1,10 @@ -import type { ITiledMap, ITiledMapLayer, ITiledMapObject, ITiledMapProperty } from "../Map/ITiledMap"; +import type { + ITiledMap, + ITiledMapLayer, + ITiledMapObject, + ITiledMapProperty, + ITiledMapTileLayer, +} from "../Map/ITiledMap"; import { flattenGroupLayersMap } from "../Map/LayersFlattener"; import TilemapLayer = Phaser.Tilemaps.TilemapLayer; import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; @@ -291,6 +297,31 @@ export class GameMap { } } + public getRandomPositionFromLayer(layerName: string): { x: number; y: number } { + const layer = this.findLayer(layerName) as ITiledMapTileLayer; + if (!layer) { + throw new Error(`No layer "${layerName}" was found`); + } + const tiles = layer.data; + if (!tiles) { + throw new Error(`No tiles in "${layerName}" were found`); + } + if (typeof tiles === "string") { + throw new Error("The content of a JSON map must be filled as a JSON array, not as a string"); + } + const possiblePositions: { x: number; y: number }[] = []; + tiles.forEach((objectKey: number, key: number) => { + if (objectKey === 0) { + return; + } + possiblePositions.push({ x: key % layer.width, y: Math.floor(key / layer.width) }); + }); + if (possiblePositions.length > 0) { + return MathUtils.randomFromArray(possiblePositions); + } + throw new Error("No possible position found"); + } + private getLayersByKey(key: number): Array { return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 739e195d..8c5df9b7 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -559,6 +559,12 @@ export class GameScene extends DirtyScene { .catch((e) => console.error(e)); } + this.pathfindingManager = new PathfindingManager( + this, + this.gameMap.getCollisionsGrid(), + this.gameMap.getTileDimensions() + ); + //notify game manager can to create currentUser in map this.createCurrentPlayer(); this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted @@ -569,11 +575,6 @@ export class GameScene extends DirtyScene { waScaleManager ); - this.pathfindingManager = new PathfindingManager( - this, - this.gameMap.getCollisionsGrid(), - this.gameMap.getTileDimensions() - ); biggestAvailableAreaStore.recompute(); this.cameraManager.startFollowPlayer(this.CurrentPlayer); @@ -1725,6 +1726,18 @@ ${escapedMessage} this.connection?.emitEmoteEvent(emoteKey); analyticsClient.launchEmote(emoteKey); }); + const moveToParam = urlManager.getHashParameter("moveTo"); + if (moveToParam) { + const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam); + this.pathfindingManager + .findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos) + .then((path) => { + if (path && path.length > 0) { + this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason)); + } + }) + .catch((reason) => console.warn(reason)); + } } catch (err) { if (err instanceof TextureError) { gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index c6e463e4..3be17f08 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -49,12 +49,13 @@ class UrlManager { return hash.length > 1 ? hash.substring(1) : null; } - public getHashParameters(): Record { - return window.location.hash.split("&").reduce((res: Record, item: string) => { + public getHashParameter(name: string): string | undefined { + const parameters = window.location.hash.split("&").reduce((res: Record, item: string) => { const parts = item.split("="); res[parts[0]] = parts[1]; return res; }, {}); + return parameters[name]; } pushStartLayerNameToUrl(startLayerName: string): void { diff --git a/front/src/Utils/MathUtils.ts b/front/src/Utils/MathUtils.ts index c2fc88a2..fc055d11 100644 --- a/front/src/Utils/MathUtils.ts +++ b/front/src/Utils/MathUtils.ts @@ -31,4 +31,8 @@ export class MathUtils { const distance = Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2); return squared ? Math.sqrt(distance) : distance; } + + public static randomFromArray(array: T[]): T { + return array[Math.floor(Math.random() * array.length)]; + } } From cebb803b2b9f9696f82418c79a2f3d79260f00f5 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Thu, 20 Jan 2022 15:47:49 +0100 Subject: [PATCH 04/60] added test for moveTo command --- front/src/Url/UrlManager.ts | 6 +- maps/tests/index.html | 8 ++ maps/tests/move_to.json | 269 ++++++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 maps/tests/move_to.json diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 3be17f08..0833919a 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -46,7 +46,11 @@ class UrlManager { public getStartLayerNameFromUrl(): string | null { const hash = window.location.hash; - return hash.length > 1 ? hash.substring(1) : null; + let layerName = null; + if (hash.length > 1) { + layerName = hash.includes("&") ? hash.split("&")[0].substring(1) : hash.substring(1); + } + return layerName; } public getHashParameter(name: string): string | undefined { diff --git a/maps/tests/index.html b/maps/tests/index.html index 4af51be5..d27d7804 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -56,6 +56,14 @@ Test start tile (S2) + + + Success Failure Pending + + + Test moveTo parameter + + Success Failure Pending diff --git a/maps/tests/move_to.json b/maps/tests/move_to.json new file mode 100644 index 00000000..2fdabab0 --- /dev/null +++ b/maps/tests/move_to.json @@ -0,0 +1,269 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17], + "height":10, + "id":7, + "name":"walls", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"exit", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":10, + "name":"start2", + "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":33.4788210765457, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"Add \"moveTo\" parameter to the URL to make character move at game start.", + "wrap":true + }, + "type":"", + "visible":true, + "width":249.954975648686, + "x":35.2740564642832, + "y":34.4372323693377 + }, + { + "height":19.6921, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=exit", + "wrap":true + }, + "type":"", + "visible":true, + "width":158.292, + "x":32, + "y":114 + }, + { + "height":19.6921, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"start", + "wrap":true + }, + "type":"", + "visible":true, + "width":26.7596292501164, + "x":3.30880298090358, + "y":135.124359571495 + }, + { + "height":19.6921, + "id":5, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=start", + "wrap":true + }, + "type":"", + "visible":true, + "width":158.292, + "x":32, + "y":132 + }, + { + "height":19.6921, + "id":6, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start&moveTo=exit", + "wrap":true + }, + "type":"", + "visible":true, + "width":158.292, + "x":32, + "y":93.740349627387 + }, + { + "height":19.6921, + "id":7, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"start2", + "wrap":true + }, + "type":"", + "visible":true, + "width":33.0940201210992, + "x":74.556855798789, + "y":245.393819585468 + }, + { + "height":19.6921, + "id":8, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"exit", + "wrap":true + }, + "type":"", + "visible":true, + "width":33.094, + "x":257.323517000466, + "y":135.100386888682 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":11, + "nextobjectid":9, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file From 5dabd0b2f81f9c6137a7fb88175b6c8d802411bb Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Fri, 21 Jan 2022 21:01:34 +0100 Subject: [PATCH 05/60] Pre-compile frontend and add environment config script --- front/Dockerfile | 32 +++++++---- front/dist/.gitignore | 1 - front/dist/{index.tmpl.html => index.ejs} | 2 +- front/src/Enum/EnvironmentVariable.ts | 66 ++++++++++++++--------- front/templater.sh | 7 ++- front/webpack.config.ts | 59 ++++++++++---------- 6 files changed, 98 insertions(+), 69 deletions(-) rename front/dist/{index.tmpl.html => index.ejs} (99%) diff --git a/front/Dockerfile b/front/Dockerfile index 80d525a9..96a7d6d7 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,23 +1,35 @@ FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder -WORKDIR /usr/src + +WORKDIR /usr/src/messages COPY messages . RUN yarn install && yarn ts-proto -# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL -FROM thecodingmachine/nodejs:14-apache +WORKDIR /usr/src/front +COPY front . -COPY --chown=docker:docker front . -COPY --from=builder --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated -RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts -COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages +# move messages to front +RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generated +RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts +RUN cp -r ../messages/JsonMessages src/Messages/JsonMessages + +RUN yarn install && yarn build # Removing the iframe.html file from the final image as this adds a XSS attack. # iframe.html is only in dev mode to circumvent a limitation RUN rm dist/iframe.html -RUN yarn install +FROM thecodingmachine/nodejs:14-apache + +COPY --from=builder --chown=docker:docker /usr/src/front/dist dist +COPY front/templater.sh . + +USER root +RUN DEBIAN_FRONTEND=noninteractive apt-get update \ + && apt-get install -y \ + gettext-base \ + && rm -rf /var/lib/apt/lists/* +USER docker -ENV NODE_ENV=production ENV STARTUP_COMMAND_0="./templater.sh" -ENV STARTUP_COMMAND_1="yarn run build" +ENV STARTUP_COMMAND_1="envsubst < dist/env-config.template.js > dist/env-config.js" ENV APACHE_DOCUMENT_ROOT=dist/ diff --git a/front/dist/.gitignore b/front/dist/.gitignore index 785f2eb9..4ffce093 100644 --- a/front/dist/.gitignore +++ b/front/dist/.gitignore @@ -1,4 +1,3 @@ index.html -index.tmpl.html.tmp /js/ style.*.css diff --git a/front/dist/index.tmpl.html b/front/dist/index.ejs similarity index 99% rename from front/dist/index.tmpl.html rename to front/dist/index.ejs index 3b43a5ef..29b8e6cb 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.ejs @@ -27,7 +27,7 @@ - + diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 76b4c8af..91be4cd8 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,30 +1,46 @@ -const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true"; -const START_ROOM_URL: string = - process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json"; -const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost"; -export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re"; -const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost"; -const ICON_URL = process.env.ICON_URL || "//icon.workadventure.localhost"; -const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302"; -const TURN_SERVER: string = process.env.TURN_SERVER || ""; -const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true"; -const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true"; -const TURN_USER: string = process.env.TURN_USER || ""; -const TURN_PASSWORD: string = process.env.TURN_PASSWORD || ""; -const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; -const JITSI_PRIVATE_MODE: boolean = process.env.JITSI_PRIVATE_MODE == "true"; +declare global { + interface Window { + env?: Record; + } +} + +const getEnv = (key: string): string | undefined => { + if (!window.env) { + return; + } + const value = window.env[key]; + if (value === "") { + return; + } + return value; +}; + +const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true"; +const START_ROOM_URL: string = getEnv("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json"; +const PUSHER_URL = getEnv("PUSHER_URL") || "//pusher.workadventure.localhost"; +export const ADMIN_URL = getEnv("ADMIN_URL") || "//workadventu.re"; +const UPLOADER_URL = getEnv("UPLOADER_URL") || "//uploader.workadventure.localhost"; +const ICON_URL = getEnv("ICON_URL") || "//icon.workadventure.localhost"; +const STUN_SERVER: string = getEnv("STUN_SERVER") || "stun:stun.l.google.com:19302"; +const TURN_SERVER: string = getEnv("TURN_SERVER") || ""; +const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnv("SKIP_RENDER_OPTIMIZATIONS") == "true"; +const DISABLE_NOTIFICATIONS: boolean = getEnv("DISABLE_NOTIFICATIONS") == "true"; +const TURN_USER: string = getEnv("TURN_USER") || ""; +const TURN_PASSWORD: string = getEnv("TURN_PASSWORD") || ""; +const JITSI_URL: string | undefined = getEnv("JITSI_URL") === "" ? undefined : getEnv("JITSI_URL"); +const JITSI_PRIVATE_MODE: boolean = getEnv("JITSI_PRIVATE_MODE") == "true"; const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player -export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8; -export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4"); -export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true"; -export const NODE_ENV = process.env.NODE_ENV || "development"; -export const CONTACT_URL = process.env.CONTACT_URL || undefined; -export const PROFILE_URL = process.env.PROFILE_URL || undefined; -export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || ""; -export const POSTHOG_URL = process.env.POSTHOG_URL || undefined; -export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true"; -export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER; +export const MAX_USERNAME_LENGTH = parseInt(getEnv("MAX_USERNAME_LENGTH") || "") || 8; +export const MAX_PER_GROUP = parseInt(getEnv("MAX_PER_GROUP") || "4"); +export const DISPLAY_TERMS_OF_USE = getEnv("DISPLAY_TERMS_OF_USE") == "true"; +export const NODE_ENV = getEnv("NODE_ENV") || "development"; +export const CONTACT_URL = getEnv("CONTACT_URL") || undefined; +export const PROFILE_URL = getEnv("PROFILE_URL") || undefined; +export const POSTHOG_API_KEY: string = (getEnv("POSTHOG_API_KEY") as string) || ""; +export const POSTHOG_URL = getEnv("POSTHOG_URL") || undefined; +export const DISABLE_ANONYMOUS: boolean = getEnv("DISABLE_ANONYMOUS") === "true"; +export const OPID_LOGIN_SCREEN_PROVIDER = getEnv("OPID_LOGIN_SCREEN_PROVIDER"); export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600; diff --git a/front/templater.sh b/front/templater.sh index a5a28c9c..e6a78e08 100755 --- a/front/templater.sh +++ b/front/templater.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash set -x set -o nounset errexit -template_file_index=dist/index.tmpl.html -generated_file_index=dist/index.tmpl.html.tmp +index_file=dist/index.html tmp_trackcodefile=/tmp/trackcode # To inject tracking code, you have two choices: @@ -21,6 +20,6 @@ if [[ "${GA_TRACKING_ID:-}" != "" || "${INSERT_ANALYTICS:-NO}" != "NO" ]]; then sed "s##${GA_TRACKING_ID:-}#g" "${ANALYTICS_CODE_PATH}" > "$tmp_trackcodefile" fi -echo "Templating ${generated_file_index} from ${template_file_index}" -sed "//r ${tmp_trackcodefile}" "${template_file_index}" > "${generated_file_index}" +echo "Templating ${index_file}" +sed --in-place "//r ${tmp_trackcodefile}" "${index_file}" rm "${tmp_trackcodefile}" diff --git a/front/webpack.config.ts b/front/webpack.config.ts index 77ad92bd..87ce8e0d 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -1,5 +1,6 @@ import type { Configuration } from "webpack"; import type WebpackDevServer from "webpack-dev-server"; +import fs from 'fs/promises'; import path from "path"; import webpack from "webpack"; import HtmlWebpackPlugin from "html-webpack-plugin"; @@ -33,6 +34,35 @@ module.exports = { disableDotRule: true, }, liveReload: process.env.LIVE_RELOAD != "0" && process.env.LIVE_RELOAD != "false", + before: (app) => { + let appConfigContent = ''; + const TEMPLATE_PATH = path.join(__dirname, 'dist', 'env-config.template.js'); + + function renderTemplateWithEnvVars(content: string): string { + let result = content; + const regex = /\$\{([a-zA-Z_]+[a-zA-Z0-9_]*?)\}/g; + + let matched: RegExpExecArray | null; + while ((matched = regex.exec(content))) { + result = result.replace(`\${${matched[1]}}`, process.env[matched[1]] || ''); + } + + return result; + } + + void (async () => { + const content = (await fs.readFile(TEMPLATE_PATH)).toString(); + appConfigContent = renderTemplateWithEnvVars(content); + })(); + + app.get('/env-config.js', (_, response) => { + response.setHeader('Content-Type', 'application/javascript; charset=utf-8'); + response.setHeader('Cache-Control', 'no-cache'); + response.setHeader('Content-Length', Buffer.byteLength(appConfigContent, 'utf8')); + + response.send(appConfigContent); + }); + }, }, module: { rules: [ @@ -168,7 +198,7 @@ module.exports = { }), new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }), new HtmlWebpackPlugin({ - template: "./dist/index.tmpl.html.tmp", + template: "./dist/index.ejs", minify: { collapseWhitespace: true, keepClosingSlash: true, @@ -184,32 +214,5 @@ module.exports = { Phaser: "phaser", }), new NodePolyfillPlugin(), - new webpack.EnvironmentPlugin({ - API_URL: null, - SKIP_RENDER_OPTIMIZATIONS: false, - DISABLE_NOTIFICATIONS: false, - PUSHER_URL: undefined, - UPLOADER_URL: null, - ADMIN_URL: null, - CONTACT_URL: null, - PROFILE_URL: null, - ICON_URL: null, - DEBUG_MODE: null, - STUN_SERVER: null, - TURN_SERVER: null, - TURN_USER: null, - TURN_PASSWORD: null, - JITSI_URL: null, - JITSI_PRIVATE_MODE: null, - START_ROOM_URL: null, - MAX_USERNAME_LENGTH: 8, - MAX_PER_GROUP: 4, - DISPLAY_TERMS_OF_USE: false, - POSTHOG_API_KEY: null, - POSTHOG_URL: null, - NODE_ENV: mode, - DISABLE_ANONYMOUS: false, - OPID_LOGIN_SCREEN_PROVIDER: null, - }), ], } as Configuration & WebpackDevServer.Configuration; From 8c1f9e1ac73d426cd64145473e3bf60ac3f9766a Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Sun, 23 Jan 2022 15:17:53 +0100 Subject: [PATCH 06/60] Fix cp path --- front/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/Dockerfile b/front/Dockerfile index 96a7d6d7..4469bdaf 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -10,7 +10,7 @@ COPY front . # move messages to front RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generated RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts -RUN cp -r ../messages/JsonMessages src/Messages/JsonMessages +RUN cp -r ../messages/JsonMessages/* src/Messages/JsonMessages RUN yarn install && yarn build From 605765a86f69307e383f2463650ca3673d49cb2a Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Mon, 24 Jan 2022 11:26:49 +0100 Subject: [PATCH 07/60] Check in env config template --- front/dist/.gitignore | 1 + front/dist/env-config.template.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 front/dist/env-config.template.js diff --git a/front/dist/.gitignore b/front/dist/.gitignore index 4ffce093..0561f7a5 100644 --- a/front/dist/.gitignore +++ b/front/dist/.gitignore @@ -1,3 +1,4 @@ index.html /js/ style.*.css +!env-config.template.js diff --git a/front/dist/env-config.template.js b/front/dist/env-config.template.js new file mode 100644 index 00000000..4a549c18 --- /dev/null +++ b/front/dist/env-config.template.js @@ -0,0 +1,26 @@ +window.env = { + SKIP_RENDER_OPTIMIZATIONS: '${SKIP_RENDER_OPTIMIZATIONS}', + DISABLE_NOTIFICATIONS: '${DISABLE_NOTIFICATIONS}', + PUSHER_URL: '${PUSHER_URL}', + UPLOADER_URL: '${UPLOADER_URL}', + ADMIN_URL: '${ADMIN_URL}', + CONTACT_URL: '${CONTACT_URL}', + PROFILE_URL: '${PROFILE_URL}', + ICON_URL: '${ICON_URL}', + DEBUG_MODE: '${DEBUG_MODE}', + STUN_SERVER: '${STUN_SERVER}', + TURN_SERVER: '${TURN_SERVER}', + TURN_USER: '${TURN_USER}', + TURN_PASSWORD: '${TURN_PASSWORD}', + JITSI_URL: '${JITSI_URL}', + JITSI_PRIVATE_MODE: '${JITSI_PRIVATE_MODE}', + START_ROOM_URL: '${START_ROOM_URL}', + MAX_USERNAME_LENGTH: '${MAX_USERNAME_LENGTH}', + MAX_PER_GROUP: '${MAX_PER_GROUP}', + DISPLAY_TERMS_OF_USE: '${DISPLAY_TERMS_OF_USE}', + POSTHOG_API_KEY: '${POSTHOG_API_KEY}', + POSTHOG_URL: '${POSTHOG_URL}', + NODE_ENV: '${NODE_ENV}', + DISABLE_ANONYMOUS: '${DISABLE_ANONYMOUS}', + OPID_LOGIN_SCREEN_PROVIDER: '${OPID_LOGIN_SCREEN_PROVIDER}', +}; From 00464f71449fe7ed237c441bbfc930f38c7d3855 Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Mon, 24 Jan 2022 11:29:26 +0100 Subject: [PATCH 08/60] Test for window existence on global object This fixes running tests in Jasmine where window not defined --- front/src/Enum/EnvironmentVariable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 91be4cd8..df8ec7e6 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -5,10 +5,10 @@ declare global { } const getEnv = (key: string): string | undefined => { - if (!window.env) { + if (!global.window || !global.window.env) { return; } - const value = window.env[key]; + const value = global.window.env[key]; if (value === "") { return; } From 7863774dcacfa5f2546f22b44374348c29916486 Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Mon, 24 Jan 2022 11:53:01 +0100 Subject: [PATCH 09/60] Return value from process.env if it exists --- front/src/Enum/EnvironmentVariable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index df8ec7e6..83a820b3 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -5,8 +5,8 @@ declare global { } const getEnv = (key: string): string | undefined => { - if (!global.window || !global.window.env) { - return; + if (global.process?.env) { + return global.process.env[key]; } const value = global.window.env[key]; if (value === "") { From 6f247808745cb833591f701bf917696a57421811 Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Mon, 24 Jan 2022 11:54:00 +0100 Subject: [PATCH 10/60] Allow returning empty strings --- front/src/Enum/EnvironmentVariable.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 83a820b3..e9b3bfc4 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -5,14 +5,13 @@ declare global { } const getEnv = (key: string): string | undefined => { + if (global.window?.env) { + return global.window.env[key]; + } if (global.process?.env) { return global.process.env[key]; } - const value = global.window.env[key]; - if (value === "") { - return; - } - return value; + return; }; const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true"; From b5887bceb4452524781198ba07d49a2d3f7e9ece Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Mon, 24 Jan 2022 17:03:14 +0100 Subject: [PATCH 11/60] PR fixes --- docs/maps/entry-exit.md | 17 +++++++++++++++++ docs/maps/images/moveTo-layer-example.png | Bin 0 -> 34963 bytes front/src/Phaser/Game/GameScene.ts | 22 +++++++++++++--------- 3 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 docs/maps/images/moveTo-layer-example.png diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md index 6f98af93..7f74a46b 100644 --- a/docs/maps/entry-exit.md +++ b/docs/maps/entry-exit.md @@ -65,3 +65,20 @@ How to use entry point : * To enter via this entry point, simply add a hash with the entry point name to the URL ("#[_entryPointName_]"). For instance: "`https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point`". * You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL) + +## Defining destination point with moveTo parameter + +We are able to direct WOKA to the desired place immediately after spawn. To make users spawn on entry point and the go to the, for example, meeting room automatically, simply add `moveTo` as an additional parameter of URL: + +*Use default entry point* +``` +.../my_map.json#&moveTo=exit +``` +*Define entry point and moveTo parameter* +``` +.../my_map.json#start&moveTo=meeting-room +``` + +For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. + +![](images/moveTo-layer-example.png) \ No newline at end of file diff --git a/docs/maps/images/moveTo-layer-example.png b/docs/maps/images/moveTo-layer-example.png new file mode 100644 index 0000000000000000000000000000000000000000..66f5e3c6bfd1fa246bb04e73af351e100f34bad8 GIT binary patch literal 34963 zcmbq*2UJs8+b)QLg`#4kTSie)5D-BiL}e5Uhz=r(C4wMGXD9(e60jknV5bR)Xrdw@ zg7gp^1QdjTp_harC6t5`2qYxA`y>JU=KKG3?^<`QaVBH-IcM*;KF|BUC*h|~n#n9z zTrMUiCUflQpMQyoElL4@+?Or_fAc_%&H?`s_xa20kXU++G7bFCVyA-^2gSrPqgKq? zEdl?(%)1Y2K@uIG)P`*@Msv>k zZL&DH!}i!?o!5_#9ZwE_dSKJJkh|u`qxF)XZ|}a<`)Bw!%(L=a+kAj)Lcu&G) zW1AQOOM@q20x zcM|>OVH~#V0>$1amx|#aizgDQEwFg_o$=R}Ko<9Xk3`Ewo2W>QSqDM!;)yVxva7j^*Y1TWR?IbT}s|3oMdlud^9H>vt;C)qU z)Z$UscBgbG9io>Ulxi_SeXBS#avW##?#z30jT~CE3+*8n`@Q)@JmoxIH9OA{_1r%& z&^lUlyL%q&N1rdHA&eLMtv}16WNS4JyhJ9HrJRsEGjc+7lXRMXds=SVB>iEV?Nkgk zXTtBusty91PQ!$@_vGU%FU}mR)1Biuz&K z#&^f%99gS#S|;xYGimmhI-Ww{f04tSDrNdrF?V~iF0RlDYJ74t zYw9|Y$d8Oy%7&}$m53#k_{Z}TQS6qQ5c8xzt4o*#tKbp$?Abq>3`})jDtrr;bYTDS z9ziZ_V-EGs@J`^OZd^N{`*2438}nGbIh~Tq*6JWYe}`mrkV*c~>f<=lYX|mrXGw=3 z7BeeBJk})qF->=eaGB`Rr{+n=zB5nPN;^yHB#BnMTk^q7{7XWcTb;Cni3EGaMzZ>8 zfk~Mi)ZIuXC$>MNJ$XyVv9Mn{*o|;_nA)c%%eIPl+i~j;IYffZ`|af(ofz8GSC^4~ ziF%scVUGO~xZb2kkAWUXB>b@yeNt^nb;z}i6AI9MB60oLd*y*aOzKTp+n`hiW^81@ z^~S4R+O1i`(XGUeWf)Tzt`g;H+X>=cBu&37DpJeiX?;$HiBH%BqjjPO$p{QRcL!6c zxjZiPhtmPLqdM5FiUoX%hZ?hK<(R0p1kY&YBkmhq_cXW?)u@#iDwDdI=5DS_AOv>w zcbnz%Yj56oNK1JMb~7iqozH1aSBWF%;X!*IGyiIdbc|h1)>2S?m1Ii_7-=y=kB_a# z^x-)dw>?iTWc?<)d;Tm+?&m^zVsW$HCaY^f{nPAMME0~Ch8=5GMB)eA8VJ+@X&X}V z;h9KdYGcGJ7NOX;a)7V2II?8CBSMZKdqO=m?m3+T#Qn06MW;C#N$8+gH`FoDMrdlj zKb1{%y-poS4JlJZXHbHU65hUHd{cUMLKF6>_9yO{IW^~BIv@04FZf{JCtBgSQ=`fSt>PRGvf8X<>zp3}RhGbtB z^!VxneZZL2Pv6wx-mVCqdfEZ^YTFTI96-sqFIB_sedbw2)4RYejZ$TMKRWe!w_wY^ zU2`E3-95=*CzFj00;48CoYRlE_1?XVfP$|z^!BKgu74qJ=;|@bpxBtI*T?|j=xrwx zuXCh($Elg*6*gg3(pM{={!#mkZCK%{L-d$^s ztvV+6h(?;8SfPn7(?b$i-ldU=`J@UGs#^V?{98>UH{H|@%fi5HJvO3kFo3;MgWkb; zR~zml0jpj<@HIa)@tT5fqwzsfwlvrkit64s;W z2Hc9NbwmTZ^AC)%&5q5_Y0=Mk=%7u127r*_atcSBX>Ge)I8w(S9oD53HdTAaelLF0i5cm*$?6(^sS7hqc)8*xB|};#huD z@PtuChgqEdwpdF0#rjvTVAj;&R(jd0qMoEYAKyy5uojOt?JrCv#rr2WBoMn??t12Z zFd9Jkly4>f9*(+Vcok8`{?)gexIJdYQ@D{R&Qu-pQOB1AM&K~xY*Ctl)A^a8@~NY> zx38HgCVujv1@r$pb0Tb*e}a9vm043T{PE2x+U+1s?$b-ZS+Jx%+w)=&w+mRpG1Loe zU$GiLtpA1gKbrkr_(8P!1OrufxW8!dMt8PjZ}_^ls|;zQk+V@q=&6&I*PA#nb2#bn$I^YTUNSVH(!grDcbz^z#kgJ8;-^fqdAw)Y~vJ?*t9UY z;@PiU3gWfYUnzwwiNFdIg3whv2uMqKn;=_&izsajct1mPx^S&$sXle4sQbESAOnG( zu8GH-d+mgv??W3VM$@(={}#_(HsWl`H2<%8Pm~$|KNF~8B&~_==c{rr`@J2F4%vSJ zijj!PnTt01jOouQ_Jv1N6`u>_VLtz*4s0!^7k!+#4u53#71%v-!nBjDOoo)Rm~Pme zCW*Zvv8JU%;)*3dlDiHc0%5bSi8;ylQ_u>WxQ<^-tiT-L8(2JiOzSx$5wm}9k?KqF zAIgfCompy0z)Q0;x+Rh3q-4epRmU$1lQ}2!3<>or*Fd zO#c%HxZpC*h>h|&9)lFQ=-sTY-{<23^nsgK%ZxTGHSpJXrhaepbtMDiP5Z}wy*>eV zT>o31pV8)aUkI`!RXM_R3N>=c@Z+{>m%d?*)dYS#2XI_!Oz=qLXtYPO)=W@#6M>qW z=0e`G$5WWJ2Iz!&fK=DdJiI%#D)6vx%(MeZ_w#@f87f&m#rrER?vaFKj`z3?o)(T^#&cKJj8;RFJtZavJ!vrhw zvR)=gr?un{Atzrb4P1YI(5YIUX&DwEouYP}LjeCwDH*%Li;k7H~ z^4ia$Jly(3Nt7JDu}bMHH(QFJ8FS)y+y(NR`^njI=@#~MYHsk5(^&%d@6!+dg=^l$ zW2$m5<&0d{zx(bRboIhXpB2z-2qWX}mV|@mhlNR3lue%%)8JVn@0ucnm&^e@V5dnl zpRxuII6W}OR_2zS`7dV!IRf1@+k86H!s$Cx@6o?7k{{V~CWqwaQ<)n#7F(gpZSeG% z9%KdZF9PTN4HLylk&|ZAu3emJP9e@#%QRu++M+M+dmd}_A5iEGZ$I}HEyJ~XsF;-z z#T!Rx<=JF^HZBqaa$8Di@ESdQ~HL&lJRu3}k5K*I%+rtn1(aAT$^Gs&CVX+TB0R z)VO#);eNcQ+6bIaQMw0%!@09E-<8s5BHfAfzPjWSnwQ$)O#-#e>EJlehn;)5Z{HMw z-5!vwv)Cxtq$|g=uxO;huMcpq-?_=f7@n*B&K2jP>m8$vr;K z0goW3`31*gtKM8t<6CP#+m2HkPPlM?&prrCidj;jXePaDCltn%$+@PujAM2|7h*o{ z^P~F9jS$=1c_%mrP zxJwBHi>u9v{3MUlVf!!l(qf+{Lv*e&J@~OX;@h8koq&`9@`TSiur0Odpc6o*CLmX= zhX^D9&@Q3T_?5~ZtDei#+@Imcm~t?05G)w>(k!NECA@7LdgJKugbZlVKe<&F`E(vOl8Twea4~Lp`|Mf$?v_ z4fnA=vS~RTU!FNteYhqTov>feDWsgLvNvvbF^wiMY!2DPOg>7k0pZ_FKYL>8GVc!h z;^+w_1N2GPa-vN1a~i#~vhCR8O>ON!;WjDz;gt%hNi4K19-0RW#?v~7Lt!!8%#^n64U-cULkyE?V=Adw?q`` z4>#*wW0xr|(Jew*t~kHM*Pe@NQVFE%>R-s}eIniV()8d<{CV+&<~HfCcM`9G?-gQy zfOu5}Q2T5V9;wKpx2gFzzhb6cnRxX5*l3lx=k1IoeIFdyI|r_xJ)mC6gqR>irY0PM zEPqwyu25gGm82MUZ-M*1#YtToLnj#pKE4@WOjCos^e;&YZ8{Qu)By(g-)zalOr={WK7qu6R$2J((XZ`#{%Zda&Qs3>m;KUuyni_|r9A zM+3Qv|&F^hn`>`Zw`LKzvFI3UQ7It!6|8WzwjgaO}>I$xc3&rS?wtD zKYwi~d5@n(8t#pR2jt>$v=MGW)%_vmiN+29YhKi8b`H@w{R4AcQhc~x3ytq zGiJLDVygK1p4U3u z&PraitkswE@=~Vv!&3WOx-i4lj^$EnwamzOruV?z?g)1X!Z(>z9eo-EtkhH**EGP% zjnThyPo=NXv$0}FT*e*^a|x%OPivj*emr;@KK8W`6;-GxFV&bu-)WqK1n9f#{HO!ufQ!nHic@*yOPj7RP&kemTSCIB_ z$e<_gx=WCf#1UsDk2B3zNmV1UcdIsJ6Bk_!?0teIjUb>W8 zdTlRH!YEi+z%@h}VHN|bGg_%2p^#_~{9U=z=ikK?bNZR`+E^RZNu1hJ_6sN{^bNqc zKj9WeY^0O^tWq{zExBVfZ(9GlnLm7ZkC7dbdrxA5O=?*8>@%&39-1rJvy7t>cvSIS z5uU}D&E}A`OmUWrAPB5RjxUUU7u0R!H>~^)OB?)2vL5a1hJRk&-B_K=?DW9AhwqcKXR7jUpTzC4lKkR~T{)mQnjvkgl zvzAY7>_ACK>gdie4vr)<)LAQ?^2H+AVJoI%*u@^|yrn=qszHW1Qf6G!OI}zh z=YFyINL>E{eKKQbY5`6iKGS@fe>q!w43Xyk-dt+ZkCon$gU#;lcC5i^N7vam`}cIp`Pt!8h6K-_5*MHb_-uZyEItHMF>3epTUE%w=IYxm265)1^U=(A8E} zp)wiG@g_a`so2GmaSkfgcU2gngqvJ-9JBmpvj@}5#^tgak1FP!ktQoOV439s2tMOL zG@R=VOb_6h?I2v(He>tOPPRB!Uo$dB_dp4<6eOld`os(TN$F;cL+j~AB;H-$8|79? z;+*?rm{pOL*o|oa7`BdXGCZbH7}16~Zs-QL_jOi=yXDn&e{cLkY&}!STPnt-@#~K8 z%Nz?VufG4reE$Us+j_c$m9I2)2ri=#4m}OvV(*b`b$viybfL%+9pFG*aC?ev>1wNrJ4Ic1y}&T3~>0Q zc&!wuz9HZ#d(N?A#XATP*gP0Z;a>`HY&&e*@KM0apq7Im7*#UqtfnYT6896rHRIEA zx1e1qr;~A$G|X)mFK+WGD%+QMNH34q`pohlf9bcL=mJEt;za~b%i)!xSM?*BSt5dM z=Hd_pqVXSj7`M3v@O|~UCLI~4WYV>Sc)D-ECx}Ydf!VAf4{={y+bH_}LE-d(R)_#7 zRiW5*NM1RJsm|~N2{-uX5mDC^LUpZ=*ta-&{ zB{UWnmLW-0;Qqu@6djglFz2UEOZ&$=94k+ALH>h6o zneh1EnXymJiPDQ*Hk0*lw@#AkA8o8%8+%!A8lq9RxyZ|_UVPTje+8SUR3!yf`fKn? zx)=<1$x6wM*Qy4zN*>cvHyspI9q>RRTkV2$x;QS*-K$C$5!Tv-^abZIuh9GR80@avCe8m|40n{%7K!*I^P4Oo${wg4k)!2zlss zHT%r&G;bWtVO#fjPsvbC@i)%7^4nDfPp`K{w;TBDRW^N9lQfTl-O**%@7Km%tL&3e zxBuI1^`2Pz2SW+3et&Tg5V0oNiQ+m44-quotqTKyr8IgSg*5Z`p}&<`<$mRe8yssr8T?-YVjGkc5}y z?#bsg?78K$tkJ;80X0lyPPJlFu9tl1!6BOud)*FaeA^ur0$2Sz2b+$)chK}7o?&yo zL7mK6-79SyqTJ6lt5FR0pi=@R^76`tmmf8Bi%NP|LcC_QMGB z1W@!DyC5m)!Ha+SJh++dmNNX%?%J!AU+=xU4*u=yIIIRQ3oY)k&?pILC+n9xl^C*{ zzTQ`8KL6xiU`lM}*i#r;Q{rvuv~>L+^V|>$SBqj2YsTO0OXsV`Q6>$`8(d#@!U(QL zEV0n_mbI$xDe>wYxx_q~aM@LRLpse9#24uv;E>;RNP}{xNDNoW3|5hK-zMFMj?&KM z3BAwrPvMdV4!IbvePOV6y20ySf`Np|AGRzeS3#^{;z-y2J?MlCzsD~-K+#OZ3j6>h z(|=^~ng$YczpbmGgLx70ftAY*`Jn^-9=T;ljM8|eF#qP@?ywc*_3`~9eKJVK*#1Kb zHwmZ=5^(4_8qlCa$au26uY}1or{RaolWr&BO^*x(#!HY%GHIZwAPC7ob@jev2FPe% zw#t&DwvKeXN8wL6DFndRH!vL;Sw`K&73yRq2gm!hqpUXRW9%8y`=5H;uU0OHEbTUPV4e*iWO!Io&7&0d%#v!VOz^o1 zB|iGwOt>v^s+P%$0g+SDp+h}@tR+8+uJZt0a1n*1;llbWi&H(?nH%rW+}lQW6<$*r z*zq*fn)1PuR0Fe+2-N{l=;7L)r!=P(4~p(L{#K58A4(f}t0oDHS8Y;W?|=rEb!Cii zL$UVCwE2e`;*zH8@6YVxjzUii64ht~4E#V#Zk)K3fhx@VJba#2H+e|t(J2-TnN=v7BcR>7z**=NIWJC z?>X#$GCR^U4rA__mmqr-sceLPzXQE%_(IF5%}n^ogy|zxaAybhRWDN2C^Z79jcySO zxwKrIg|A(Tl~fERN-PHD?x$Z%&;KrpQ#5z;KG?)xT&9gpMKo4Z}wSD$j?gUo=C+9=AP4w zQeQ^y5Kmm%>@kcVmNtyYzLaz?(A9x`PrSMAd@RPp`S*JOx!gK)Pt;&2~e25T!$)Z;Hr2VEm9G}ZkHO}`eeIywb7RE?!lGu+)>3xiO>Q7s-LWDJ$(`ge!N1XcHivs3YiWMBC@&kMqS zyGvKeC7b>8Np{$+Vk&hX4knu`tC)@D)k+WNHm!<`R+*JMeQ?VSdsrtDkrkvrUk-%^ zOOD{Eto#ectl0R26FDPVExRA~@0GwA_8b4S^V(=5xF#CH0nplY8v`nm+z88dx0p<9nSm3Up+`S84W z87LQvEtDjHfiQZi$2car^_C%LCv*mOJ&fuH{~}30l)Y(cyJQV$mx+lm78JS-e1rm#Y-;m|wyJkqzjOMbXe&J>&x?)|oZ`@kGy-{MX*a7VrleyNOzvn7?l2SVc4&~Lw&UVAv|qBa#Mb=gWz5848+($N zrjgH4RNAJrMfX4%UhEXe5_7eT_d2liuNN6=Q}FdLQ_HMHhNpFFx$_b_bstP}%opOc zwy2T5Gj#~_6k$hf@p3^g%@JtKp?DpnF?-Vv@v`zq!b&+%0@N&%=BwbJGre_p+mU5DrC*yO-&9wJZiC@)SA?AK>-&WZ7W&*$WBlb*SMKI zX!^bR6!JhOdqJ{4J2+osl*mtnhZQfgU8wO3+NoCtJ5V|rFVDHXHF|l@N?5oT4bW^P zSAjGRu`39`tdpgnhXQH`sR_8m6wnZOfH!*gLo8Mf5+mdv+Zz=Z8V}&y9;p`XlIQV8 zKv8PT6~$hj1L1Qnn+r#J2I#=1reiUXNc{dpGDL36+>c%ZlIG?4oDP!m2Ix^y*&>t9 zq%WK=Zma{v_CoHg@yRB?tlG91_UPSf9f_5y%hqnJm8(}dwQJ)ZwB8@z@(&6lkl@T> zNXGXMVn>Fh24%OeTh6EdzQCm%DBs5F5eOpwkSI^hLzz%)Wlh?6w)_DJi!W;{jrCWS zOPO!1U2}2MGlh-Gi*)RPABmOSFg-$odK1FtN(G55UwgpHYY8}w=W<~3y9|Ua3J872 zjrB7vyc1IrB>vR>cx}=H?c%3QER-v|(NoY&4*R{(3lPt%|5Szp`|D9h$t!22Xdgic z3$g!laLa=5S8y0bn+;rTJpM%e>2V<7kAIq9Y8}knaLR09i4HU%kcYA~yf5?TiBr2I zF-8KaEM!MPoBpNvc0X&AJh^aFlx41vLOpHDcp#j*>j&nW(f2 ztO6T5nhiFV?7slcoaYM|`cEx{_@mxuod2>AA+DDh2oi|#E6YZbpI{@bnh z0Xp5czxu#vA?ugTfy~ZRMhCR*zabggAK!||l#p`wL2)N+;%K(6ltjcmVWXp#~~+y!F6} zERSg2T)ZC|WrI-tcjo5h4#n(;&f>sFN5skoP5PyzMM$>(+W7~q_%@I)>i29V+axOh7-+$uJej*^++Tb;w`flAWlSGah{8DrE%4&+ zOlxOJTN2Qi^mwY=)|=2yY$f{Zq?%(zW5qhEbx>Ld41n~fmjpWT`m%FM_+Yf~XkS+Gy+++V`he{JRE7LYV9rba z{4G`^ZUf~=KkXo>&sqEJLkqFp${(B#s=h5`(6zlj>@L$%%@msSspw6nTeMQZ(Ptow z(S8{T?C(D;rW9*-;$KUrz5y%!_fnu9a2hXkwHVk))grk3vcaX>*R7k6a?9P=E-yoM zwugshy#l-C8pGxYNs#1~pmB|mHCqk>-E4+Jp~!xOntNgPY6)c9;`ho-7fy|-OrMj> zxW+~mNyy13Mg3i1kq|WyMVa;p7rYg;TdJD z+-$&&cn(5XnIu}uHApy$@LNR{0E=7w8&%4oCz&>B5TT3l$Al{0OCiL50xxf=4f{*9zb!nAa}BOO8R708piu z$eRyqb0Ov+ucCwA?rc4WxOc2m23m$tZ`fRXq)G?j9BcpDploX`f+_1D*$a^I{C>cE zw2&|yJ$=yNO7w2^pYgQo#$3}Ab_bkcOV&il0K63;?zdR@2}DfbHvr;$nTZe!C@Oy~ zL|UW*Fb!bT0kGv54&D>MYNMT^AY8_D1=%qY`Hk6;3=RgtevrwwD6A9hr4w|M1!nfZ z+%j(XWD$vmj+eYu;477Cx7}G=cs^0E6Tu8%oC1t1+B&H9O2-!KWxp3;Ye80m=#3zN z3$Sd6y+|>-yz2c9?O=!@YioV=1zU5GV8DoVXb6(=4g^%eGnD7kB7#qc1r`|Pjk^I@ z6x2#qoRgsTyqwjR*iPD-o)FRr2nK=u2~gpJxaSt1c}6?-3QWE^-jBz03P#*sTW2iE zjsX@7XpJyq3qA>qM8Fj8iwc&YiYgp@;P&lKdoMaw4;TaYnPAEQjJZJ+oIz^wnP~H( z35OkYU?bO;i*&ZI1R?}f=HAZE+V?hU^Fk18cb(E8-Sj(@i8BRoT4!F!iP_5?*zaoJ z&jHc7HEPENE-%>jv$=@?G0we^y@bFM5j#W@#XX*p&IYJ!PBv#j`&>|OTtm*dl)q}^ zy9@}7c$ihPzlcht9s#H|n4AJ0&_5i%YQ|R7vOaA0XE+e4ADAJ(&E_m?-l>Kjlmjp-+%eqXK z;1eWnoh2-XtWIF~LVzHk2ZCfPunkZJS3kXndx0{(uFZru-816OrSiE{;!L%t340)g z%A3k{1|{27I6NDI>Mx<&dfy{>UeaNdj2>VH>cuC1f?sbQk`` z-}m0B;IQO)iti8Yie?PB$1cc~47ehooCOED1+bwgnXf8eoDA4ryibGap zazIeZaNq9vr?^`5>7MdtCC+aOgeJkO2_hfN)Gw2g&^mD1o_UI%J$>B<2iUlZ-61n$^x9G3KHOMBhA&gG6^!eGN_Tti`9(BR7k5h?l} zEct^8z|$u|9JCV^w;&E6PJX=HS#KBw@&Agd!l<=Q_>>*cT)y)$J!h5sP!jk}whO)h z_}VTx)>eHU$RFKj=e95)N*as%HQG<&Gisq`8RGJ z(SaEn&LyTiO_4x;<8ptei&@&lgPfbX zJXg|H)1h4g;enyTA55TonUb)_;3`>>1Jt;pouMjHX`HVEvib}*}K0$A_h5g;l zltY$PB5tz*NVktcQv#myW1e|HAuFU7y5YaDj38SYshlXeJw0q=cv*GdRWNL zM;6R?DQ4pNy7O~X5&(h?NXGR!3~_70omuzIscQfV*cWMTi7GG!)jol1Y!MxLQ9LDZ z4cyYw%qddIx+t`Zojv&Z&VNc6$W@dD1Y(#=njmwBMAp1$mMERP-ry>;dk)&q8M(>R z1xM5n+0SvD+_H4FK@g1RRy)2nk*Kc;dn~^!U_sJzkNEt9xzgIK5{wN}UALL5EL)^P zE3PGvFBeL4-?JZ18$0lxkSVy#>(Fsy?J59p4v5T7l+Oiv7N9VJ?%&T<&!I_+%?8D&;%JS|N`k>=zm0vH0V%_YM#YXr@- z5C~MvS*tL3{L9R6^9BP^RUC*Rj{sPJ0J7_GP>BTaChli99VV!ELELqR=$r*8#Ni=@ zx5Nc$H^#;39_T=CR}efQvJ1pWDC>eLebH;AI{{fv3tBsG?Ei*l0!Q^1?Ps@g5vS3y z#0SjP_3v+u)wFObfuV0g>D^sW=n%s8|BCpABE~M{jG(KFqx_}}BcP0g-;@!#pp1gF zeaYPLpT3gd8o=EWr|!>t0^rpGX!q0vp8*JF?RzQF9ze0>Usy_DMTbPr{U+&Cs%Sls zDNxVtTG0po zew}ih_@L~p`5%z@&)u0=Uji|%g_N1V*7QY=!+FP-Vv$P)k5QWWcVf>%a!t~{l7o6Ky z%dh?4DEedwRi!`=L`JXxxSl2e4jmR5$3fQKQ!Dnd)@ql*z)=BBpBpGw0pGrfi&dpY zT$b4_{v}zjEbK9eIbuj1K|KXh066m;jtW^o08XDPm41uu5WoH}M+H}R&ND%ygAi<* z$EfqEup;h_=a&d65RAY*<{sb@`iIJ5+og*Rr7jb3bV`$<2z_n@Z?pg4lJ%X%=0U<^ zTHApR@kKg>Ya3TX0Sw^B`S0%82c?Y*>Jfriy5IpoOTws1e=CykHzw0A7*a<}5^kYlR_^T-NKY61mYQ)jfq~c#6Qj3{W zZN$mP98U^(JCr|M%pfqI4_Wfcn@oJF^=#Jm*Qb-PW0m%5#ixi>WeaKwRb z%UPzARcmO9emp6-^I!bbXkvGL7HN9y zsvM8$6vZFzhjS_JxflJa^_&QNWg`c~;x%=5CPz!<_*1#W2jlTR1A0_N+vB1dgXE#z z5X={rI}YfsTs{|R1Td&fms?@N+mzK|smdQqfdh_d#^BYN+8BGc@h88g@hQsuHmb2* z^PL_1u{IuAg$jFPfIo7{w`KiVy3aqVVOX~a^hsmJ4bnPPeA5$_!IYm9mMhaAJYmrv z%&lhd-`gQzV0fE0QC;zZ;NKMe;3|hdlf=(2rj105@Znc@8?R>zvKVLJWQ(3!VdGKA z|ISektm|^RIPo||v&}w9-sJDD{(Dzts?4#|v4}Qqb#w-H5}!EJqz0RX*`zh6#?nTH zV)}=ak@Ms;d)KSY zMr*Z!AM5x`YQ^xQRegUd;4cPzuOK2hEECon()kYtc7L^~b??q?|I2uE5d0j;6WY+B zIW+n27@#c9pjgdGWj{!1I}>T}*}4gfJ#w(W`CK^)w}eZ@%!VR?Nn-bWj*8{MjlD^T zHT`EAqtj zKO-LnAM23l z%OR!1XRUyi%@FK#4c@HGdK2zp^-Hs{TI=##9dX#+>rFAVq=~qQAIz}C>0UT*q9-nn z7syea3bn+vc+)G0c`SPszgdnQyNoo1$6oPVPGp2isRsU{Be;W3M0T$hW-=8qJ8lDn zLc)%ZZeev``dKH9IQQjG?x*EFIqBlW20ttV>Q0LQt5!0@HA(y4nk$Gv#lE)zI5^+5 zKjsQ1fGUUaVvQiVqvc_A?kF7gkw{1bKSsl=rc`Q;za=E|`=waZ-#apP)$GWKE*dLx z;+C~w(LdqEooPv&tSu4x-LAa8t5SpsAFTg$MNS9*7wO_y?`GSKA51K&vLOPec4<&6 zietZm9AAeF{*vlHJF&|0yibTOcgko)hTJq;0)EEj@+{AXM3@>v6pjz3(u;;rEDEwB z0of5$iX{3l!z&U3>72;@^y&3ee~ZrEUhrTdHl9|*MJYNn+cMLS_FC9T5}?~IO78UpQe$# zrmm9{=~sf>*5IGjb7~aONN#A@ED7kdxBG-9dY~&zO?j*~%!FNMituYr`ar!NMVNYr zG4%xR7}%7utM_L=6%&^0<}=?yX&N*bk49_xL#1Z{I933JVJ(T-{{s0uf0WL_cs0S0 zF$_jAt5J&18Mjn57y%J&f*&;fkUu$k0vp7^@^~^>R0YzK$SYV8;j^uw&gnjL_E*0e z=P8Uru+Na^bx&&~_e(^whsi8h*%W+cW(y3?(xOh>*0QFx4C++`*B3iaCskl(9Mv(7 za7M(POP4wCZc=9Qqm|KRK8Ud|PO1TwPIz>k7+KBlXBdBK62H>I!qagxwzn%RgM#6t zCC>cVoqLJUn?q{0Pjx?Qj2?nt{@hTY!v6(hz^>lK;hTcd&KBZ=*1^kj3T#{TNd>5M z_CnZ#8#Ct@`_$}RPhoX-uqgael-hI`TCN>Ul|Y&V@b%TuqZ3gu-!GP`1)aJ4;1RsZ zOgf0sU*es|T<;NJH%;;sIzp2nWl zo19d9f2?PL5TF@{@qIVK>q|j__l^=S&m_BF4jzA##Qkg4ZY?MFJGi4$+a!2NwO^CS zyhkdY)eB3Q4E4F}J)PPJLxq|0rZN&Ur(&_*1JoDj8jfK+rho01ZJGHp7U@)Q5s~gEDH8c{^%DoI<_+zr4~G0e3*_Mc!W|5`jsB0mf!Qpj4?ez zew;VO@SZRbYGes>5J5<=5@Ou z4VDCo#2)fN_|~sAQo)R+n+3bzZ7>eKChEbnhGK`2XX zO<;;=i^i+1dSy((7!)%uBLu?A!Xc)5#K60`5tPQOxyUd=_tz&UjRP}l_V>T&{xPltW`w6SreK2; zsN$JV{e-E_S0`W>;wXc!s)|4MuCNR)KS50pMz08nVYYOMI(1>4)d*CN@FL&@Wm9qj8#9-lZz{k;gyNsywwwj4Wx5xJgp_>e`PmGUrN!xigJKu3h#8gH?v~0ezEBqv=DF%FF3g+(Sx-)q= zu$L?Q2cS-Y)g04HL8d+-LhKb)|b|wfwpMCQF$TP(wr5qM2Y?KkR5(a^{FLP6vN!|#Il#4UkdE*%Xu*SFgP$*Io0)+G|yc*YtV<{FQ582{7}x( zzinnEt9IYTlH!jcJ!r32U61XO`(;H9k&jS~rsY)%O{;jiTMmD5A8$?4ZKbLE7Fwj! zbBq9(yFyJ~F`^<62%NM2OKy__4H;uG|NjL1K&=+9jiqWDR;3IbWXDV@PVTLtw>t z(TYneNWhH!GuSyRv9u+^k}VkUqa=w38^d|U%AF%dT;(F|F;(6) z3fT(301QV&6)XG%HE1wx7-g(4D;yvNH4^1*mz=1d^qI2J3i@Nyu-S0dh>8vj>P0RQ zU14&ajODaZc`gLOdxBp=Y1fwxh&LgXg;Fq zlk%fG{n6IwecQ7FW@L`#;uD8lV==*J(%&?BmtI@tf7SMbJ!$(VH}f(KQU{m(<)f2Z zknPg$gApqPf#<->+zdZa<4sH)D>=G~Gp#hn?u;J{kQd1e`j3$s&rI z@@3cY))8eG(>1WU;SNj*hrb2|Q^=WDKBNJ&dYuq|p%c|g&CybE7L(9MCkaP%Mb9F{ zVs0!Qqn34k;(A1B3@6KQv@S-%P8cnU$IQBbUoY)ZK;57}Or@a|rztX=J4znYL5If_>Fu4jS~i8C?M?j=LKO?aQH9sVeGY0QRwvz{G{=?9@0BQ-d= zbbTSzbBBun07dxo8?KnNVB6q7yPUUIj z;YUB1A_&YgMOx}RQ`2b?sUGXfn9Mji+1=Wrwtsv>lcBKvlcF$6{YwdFhse!&z(`d$ zb^Vlhm|~w#*`+H<2)q+N_|4Ba4JFiFDXvu>UYzNVIL#&PJ4E$Vs6vCot2ND?NNMS zx{wyN1?BUbaTwa}AU6`Jof#RBm{PXnzZBI_Ys3kkK2o7S!tpHR()Vx+My7ntG|W9a zZ!9j`*3Fts7IIODtj9pGqOX#fw(~8GqX9bG^VLRyD*U|kf1vAHP33jb!E~y`sN75k z7d8DnH>ClmzpD?K>yGR`Gh1x3#^(q1grKh?gbz>laV_*anAJg`>B)>^$foTQG2))Z zh=KX8D>UAuZEiVN6ce^sqiJoX88h_S)e^$g1^TcV3QJ|@I&Ojz%0`?eVKsW)K1h~z z4hZ~D0ANl?|4&8rf9Jmg+RzWZ8OBjgW@@~?tezNPpZV-e0vJLnJNp3i&shXkvbm)O z@b!g4#=zIrs`5;S%I_4Uqz-{AN&R-z#0^0VXM9STC<1yg7rf=fg+81y$p zH2D8CcI9y~u3@{BqSaE-$~mEkO8cS(EjTT-h_oC@o26ATnHHp@qe$9!Y1$}Tv==Ev zn<+(m2~#RD6|LX>yz|Z!$M^fb^OxW6^uF`F&w4%geO=d$@e+(L0)~Zrt>ekb0E7oI zxI<@QfgK5u(K;;7SX%v_Xh!?p+iTIjl7Zbq)_n%Xc|WqS zIfB?9bbJkXiH5JS)U$^2DJ((zf#k~kpEt;t`5NPUIvuznhzcohJY_r2Eull=jj?`$ z^QRz+=g=YOyRFmF0o#Es@s3WAa|2ClG&7kz%aK5#C~D0wfR5Or1~TnfV5%yDC}`8x z`E0T{x~(=)!L?bic`~~E?2N-;*O#R}4JR@kOyy>lgq%X*M1vp(szdNNnhDt(`fmvULG4n=al&z5 zb*<5hV^tk9=ZzAnr+@IMyJzVIKYNl*XdX0v+lW>z#>WXsCEngSbXHO@#0;oHsgTwO zvt=K$ufDN{(}SbxQM-LN-#L4Qt1H)@V8pha1e+N@eu6PoVHsm$ykDj7*DARaGQ;kv z`$E~LIZY0j7ZWBEk_1*Q3{GHklmX%v5G{ic)^s>Lxpbr^u}hzQ1M4(rn9ELhhy7#{ zh7Zx?o_{dtsEQ2pd03pZ=iDd!3Liz+OqcpS@ZZ*LdnYsfPWH*gN~J7$-j)ZM7KNuk zMr^cV2W(!u^f1b__%}gh55+`dP-BT-2XOBG*Y%aYgHJwHI&qQF>`C8kx3VpO31&~I zWn#1e``bTbY!shj3Aq1)_J!FJdPaFu%|vJ3!BXKZ(ma-&zwmSBFNOlbQ`ivxoU;Wd zoAqoBmdmh#ia&@33T~6YH^N4Vrjw9-PK}#SR368(>;jllp&B-*#aMpwcguy&tAKoc zYXJ#nG?)*2hgkm+d9N`k;@Au7A4q%lE2%E@m|6lXA^eG>vx9a7^_gne-Zuu_bARVl z|1w|Tp=~}$Kyq^~x3+-*$i6jt5r6ED*6JOuoLz8tspb`D?RqHihI8nSb<~wdhDRzY z2ksZSrlob22AB1<^&R=-xXw=>OB=^mBSAukq3enn2acnC4;uYsTP)rD2G~m|88hG5 zqqaR-QeAmUFc`728@TXY5GD#Ry3IJr{t)acof&WI@b)0hx#a>?m3dI~_C<^#KEKrs zGMB5DHEe}bVm`gR#lSUg!qC4jRM}5F$irtuE9w|xhYObrwbyLG^xrR2bY)fkOLXRu z*MOS`Y%h40I8%T$A*D*Ty$Zcqn*z4RnkAQ65os9xejGDx+XjAF1=@|a7#L$31)!o) zHm@ZIpWBaX^=jvj7?gU3mu3`CD7U*+wwOu^JH8=F=q~O7U1!2BR}yl`+Y-c_I>v{S z8tHpRLE30p109uP$>Dt2Qri;-b6Mg(?mD>)j908LN8MqmbTjUHrQNC?EN8-HO zGuN^>s`=W81Y5OFluhu<>W$=y3&4o8G@*0p6|i0NR%_m>JZz+nD78zl9- z=5}Bj6U2YU&>l81$%f@rP2!A{O;2p6vu{>Z`$)E%GP|C+mCNTv(~6G|yr911>)$Dt z1Fo|1T^IjCLR;(y{xjVqffI-}r6*Lsw-glK$Tdtoo**}4zsmub`*Jhwo2hV_I4)AF zt&eCr*b#~{ST2VNMZU;baG#&Pm@_ZeGeod|FfPKNNlS^-k{=^HzB+CVTV%(pMJhwgogFEaqoU zelnkvXvT>@Ah~t|yg{D>ce9+>;ux|11sr`&Ft%wxtVh~g5vIMHe&6D&&NH{< zx7Zvb`FBlqI@T-K|9=?uh6DbojG*6sWHu-o!RA6lm^IbVXGj&|uF)c~^F&yT^r!l_6RCVK!oROiM=|=;aezW=Hs0BAN9rX$FQktE{~&+bD&7^!@!*Fwc5E=fm|Un2XS$Gz#+XPPGqPWbZ< zDTI(O?j!Oz_jGqW;;93Eox5<^^tpy6>S~jE#G(6*6z1K&!vw~dU`#;Owd1}7xe1l) z5($|bqMX0BUR0EP`;wY|({A!!P*Ri`jui6si)D2Fv7%id7!-52#ts_Zko z^`w(KN97DtWCP=lq~1s9D?NqH0ZC77eR6zvS}$rxz7n*9V4gs}6B3aTX<)l&6FA%MFz#TkD%uGQ&46SI z`_BLvBSE6UnR{W;X%bp0lc5pnb|daUkcIGmcaPFdbUXVAPA-4oClmNihn(y!J zpri)D3`RU$pp!ze;KYKWCG63dZlJ#io(WeMe8%>y$7|O@JZc z2<&sTrhl|V#9wkJw{0AAht||xH_TBfwBi4#0H5dFEZKf{kv}BMMWAck^bZ9{U61u! z`*#Isi7dH&e}Ra1VkmI#*kilO6Kp0jm1g8+_9j3#Z@VoNqz=C5E4WVA4s=vD4(`-evxFZoeLZue!yyRX8-JnOfS zD|UWPO$>u!?6+@!fQFH??P_#zG*6jdIwuy6gW43?tQW}9-QWa&G{Q#R%03gjD$=3ZV(`#$V@Dk zT3iwsi>cHdSjj0AL3zmmp4|Ileqb{RGz-Ax0}zKq>x^AYKm`9yQA6YM@m$Suo8)nW zr-H}qwjYrr9_+b=xR8IGW2tZabG2}bmk82(xv=44Hd^Gk#{Oz1rAIa`oD$l*f?RXW zsM;{cfpb+9WhEN|k(fMT{sO%9OWTh}3UELoy#r0#u;F|dN%^pG(EB0Dwvi)(j<-h7 zRf0P1mfIVQ^H_(Oj<7uLm{xZ3cXL|)nc33u7b@XLS+eP7B?Tr-*M+D?HTKKKH8eik zTugrIy}0f`U;cJt?MCUXw>J;M*nEE4yCSmxi5&4XL0)6#&4gIWDH@gAi47gxG%XoY z?!f!p%&y2U>&Cj&R>*~?Cz-CC%=iLcB=?A3Cn81VuO)@}b4bgD$-g=M_%Ik<>{s~< zY}x~wWtD*FI*-e+@EC|Jei`(>{)BhAA%7W2t+aRFQR(u&uQ#E8@y;uz3H$ThV7&x& z3J{P80*(HnxWG>OZ4C={8nB@m@H&@soF{2~HT2n7WSTd-g;;*@*0Nylb1<|abuK#Y zmxOdB)Hr&KvwI)Fe&zZ3ZlED=q+SqRn{mW9j4mA5TC2^VP9x|T%e6vrrckEiwWL^6 zuAbvq!xI;~q^BdM>jwDiFroi%H5!4*l%6Oyl(a&UB}g(zMr_w7lgoFL7lW?p#T)9% z97^PEw;B*F?F_{|^BxQ=zVI7$ifV5x(s95HUKD~&5|OLDzLNrVJDayX3Kqy62o|<} z!*2xs=Cb}+!V)UfV5c`jp@Hr91a{jw4I<~qRF|Z-otze9m7&su92q)WiNuu7J9}4w z#~%j(k7Y~gDxMFB0_&-rSv+ymZK6x>a(w3jJ^>5H^PxowqAR}2BU=Fng5_Az&fjQ1 z{#kANi4jOZxJ{0I9buxZ)y4dj(MW1gj$UY77{?F6uF?^~*s_g32Rkodf^l?GWUTS$ z4O_`?&MP!bf94FtMjx+Y%!#V%MlRN1dUOOZf?=H=s)ffJ8MU+?rQ@ctUZdow{3zg17;KmrfEHXl?Wfg7HHz^z&^> z>z|O7k=aMKd@ldSv0MZJ-ro$8%j6jtl+y910FnuJ#93oHw3UU8oL$h5;UpZKeQjQ%p zJrmIiinze6&7ymozB)Ob!c_p0ZP1M(sm#wgG`_)Ii85YzJ}baZ6s1`4!Jb(t0dZ;; z4rYj_uATwixCzB{J=!iw}Hutgdj*jO54^KGCwx+@jp^vv>(EZc>BRu22(r?FyoszL?RhL z0AiUa;(*P3%%UMa>7unBS+HX487y!c{J=a1XtE0zSH61KUnzb*`EawIV0UjQc(q{1P@xciH0$7=uiSsgi zXiAq!^aM!AVOkvc1d^o#%P>tsZ>k^@ zW1eEt5mhu#K?X}#c=R_j6dq1&jDEaJn+Ij$GWbh>ryLQ87i}3bWMRT%c}Vb0o1R+6 zNDGysD*)hY0q#Nx3J|0q48U`h;+grAa>Q>W?#*Ki_JgAjM5gSzAAQ5KR9M#KNM9VX z5x|-clv8+(#&(npfvaM*BGNy42B6kjs<^e7gPybXPH1ZzN!z#B)y)3G5#L^;N}}lB z@_WN$ubhxCjVRqR16mg0u8=in+bmO}%Td5^=t>XRM-b;km2IaUbUm6Y`*hH_6FI#d z7f(KcZ3{4Oo1Rl2KlU(IsTYJ2gIGBW$M(#W<=gLyeJPIrTsz7mQH~jqdTK*#keZP< zyvsfHV9VlFXBY7m--yI4f;zje@tkKVT!HRpxQm7y`^}>S5AS0kFKaVTs-Ci~WW6%_ zG>Rxs?(=QTNxfeoaykrE&=G**5zo&(rhAOx21V#*q#mBhW+O+G{}c<=bOO&h1b_<% zZht01i&%<_m(ED*r_$zDUIL1Z)RT^)AvsM_K707c z5uMtaNof*1(7Q`yRO~@y2%BdBy{A^{D3*?Vj%(OxWRYu7AC#kR5kxuM6~-4GZ&p`E zj89s({K|0BH|rJ423A}->%jDcb1^v4q0_IT9(m)@{Nid1meo_=e^$Gk-0Rh!%}(A~ zx?NIQZGT-EOaO3cD@$0k!@4LS zh_yS!KVl8Ym7!)w@b1ro6-OO;obwdzP4SC7v&t3CiaJ(o{;?_(WA964s#IO7 zG1nr=tVqnsqSUJoZk}=_h8&BGUK2&}U5{1n2!7BagbzPcl?VvTP5$11@X3$0ws5_Q!w0^Ej_lv+{dl|ty~UjlMzB> zbc;7tT3@VZRUqcymyjFd`?$qIBc^kl{i*eRr|!r$UT*a>>f|HFi;a5p+zWRHT~<%8 zq1uN-)x6WmCKFt!NQh3Yw$Hq88{Nx}bOAhA2R^$GB=nZ_H+_<{>PVf=DI7dh7<_Xe zY%k~aHeNyXf%s0h4Af5PfU|t$=RrL+te*j``vw2qa@o^+pH44irzrpxlTp! z<@?ol55t+H3t^d@YpaU}uf;|tc!r!=|!_&5Cy_-<3t(SDZ_6w@Ub`~D+G-P=0 zzP!HdDfQ+@4k71GJ7`QSkH-P=_QKib_OaXTJ0o(*UkWY{@+JPYW$%)d<7VN8sb-CuAc*8R* z$kxkUIMP7o^?C4%V$7>-x~*dDigRc6ZA2}dI^8-wYP_0SdUQT}&AALu?jKIw9eunb z^OKcJ&noM7y~re}rh{8tfoJagn~)*hx|-_^S15;1zSCN+^|_?~Iwdo&@=_~>{!!9z zi}p1ZHa(FkDhS<_OW_4Au$wo{H)BFjc1UDxTwokYA|4#25GVAOrkfZsAaN-=YW943 zOf4(S*m3wbU-;rpXh>bUfL~+P2{THprdZZ#X)$o)bI#*>osFY($aZ5pn8DJ!hQ=Rq z9k%@+joU-&0@Gb(l0+&lbWyA-_ci8tg)QOrV76OOC>68X`HERqE+waSU95dXs>dsAdT~uuDr}^}Pdh|omF5T8MoY&;L?+6rl z&cMPeK9>|2;XZT2bEv#3{aFpqk8%~XokPj zwvOFLVJQaC?K$th!Otku9b9CK7~h0}JKqGT&M~5i)$g`zdbjxWOAoKf4~8+wpA(1{ zlZxK@wm4L;yO6IX4sApBlLZZ@i^L&g1-Gm>JTyls(;aSPi}?5$A?U)R)gC*BY`n92 zZ`c$Lsw=8l75Ft4j(7FGj;IMMdNii^(t4$0GgbHGUBKmF5LUo_&&bDQG{GU|^RR|9 z?N|WvE0*)Dxenn`)7l|Z2^ilHvzGJ?YOUTYbc?(jDSGad-TjNIVijGsSy?%m@Lx)Q z{Sg(4yGnUMs;H;3K$e1TBL)kzfqLo{x68%EM^>=|LZwTkrF`m33NkH>N)K<8*2uC- zS#26B#Jg%$oSCj&7QnDeU;=V=oZ%}v7)G<}lv{s6j%etqJ`kMvXs?6%H!uB7tOhT_ z_*QL^k(l!4iH~Mvd=+WNR2OE4&*3DkkTBhO7h=a^GTQP@8K3dd- zwxy_wvhbRuDyDVY#`W41_4{( z0>`dk9N`LTpkiSVyha<|r3VcqL5Z1}maUEEwJQ(VcA2M<4^gxIIj+eOcj1ne+i^3> z&0`~-Sj~>3JfEBh5-XZ3AX+&}e$T3aY%Tf0T>s6j&d^r_YsJS=o@w)D+J~~J#g%>d zE30a-u0Ju;O!O};V`S?Xn`Bdoo1HW)sY~O%BabXMUok@v$bU}a+^WSxc}y89`?A!c z{E{@cn#3}f>=mWT4b#%+4ZCPwYOySK@nU&Pz_!jGyCJ`zUiyRFasi7Vv>b)lEbbwy zx;C3~sATvPpTs8fXEes@!D0__sCgL2++O;OvtZ2~gJnosZG>a9ZREaw?J23%T2+_! zG^5&zPzz7F^bgv;^@=&%;L55+LwSGqZZtB>4$DV1*}g)VX(jNrhY}OB$MUiFp@^54XiqcI9XtZLPL=U$bIY4-)qjd6rzezOR={y;m(mqd6qa$j#@l6BPH31 zPoH{aN$sAF9`5|8>ng7;({lRn^tBlE3RsYs+v10Sy`(k%OAXTNHY)A-Hu|#DTaU|r z^2bf(dhV!BhX9-0(F(imcnEOTwQ@bpNHCV=TujpJY)k3~?5Q>_4|8}bO6A{M)<4#* zUeDI_KohHSktg^MaYF1RE9NmuM}h2K$}>-+wH3#V?=ByAp8PoSz+W;%Dyqp=^vk$1-<8vVweYrUjzcKXPW-deq-*nG$G8wU8}yfo-aYnO zEE$4Q!K{w9Wk*&f#1_pDLQ)}+c*?dWrDGoNw zIzmuO7xy-N*C$S{U(L0VX1#LG<}UN&J+!Xa`wGfzqtlV_=_FzDrS0~yoQrO|g%1Z& z=F&^9{!uHeqR=)$6_}bev{js5dFV*XO%2U`2DWpKuhu5val2aWlH!R2o0rhytT-mr zVEqwRqGncZzvRM+3!+_Q|7rQTB?IxZbt7|3b0-1~wEiM|gCZdXl+hXq8i7u!?r*~@ zfx3vQW57EDz?1XYmrA84Z$>Ep-cE-!WLT2|QTub;gJ+IlUh7MujcSE|2OG1DA2w|Q z?D{Ista}%Gp8gUIcu7um%AHWR4J4oBhC$ECb#O5`pAJ^`gGzj-XI9dpqv99OPKeRm zMq|b7;$hWSZxwsEoM%X*l;Z!VABjXUE%hDQd@gxwTAKW`&`q5c;JAn)lc)Q!H+)Nd zuR~SEV#;DS;>sFx9PC>4R4ON^D+?hA7V}SXIK}Z2%^?OVbQt1Asies}w~>_zFRllj zxR7cvlKjbOA}NWLciO;^%>FcYx9{1gSh1I0>+m4nKdkwK$ylups>catn;#heoT-ZR zCyq&Y!wtGV1(_~?xcs5gjM*jw;}7GbL&JTJ_LB$YezGYg6h35P^j9;j3Icp}3^bdh zc3eE+5=Cn5V-~H5Aj(U>Rk!HgF^yc1y}8n&gsPneG9y{e5-cj*RV&Ti-HL&Gg>VYa`OOkgUfN$?d?tP)6XZ&iSe^n zH7ue7>#dyU%rI0LAqj|GzJWTd!RYijlP>BvWUUq}mO$zH8uh4keDv8GUh*0uwQ<8N z@~%6D27@fQ?VerepU>1ojwEurl3!3;ZQsXTPt=tg{>M6E{QGm7Nh)?$p2hrDN$if3 z5k_j1P}j44#a%PQE5Xn1XA`@6P2X(P142Qe;rz$=R6F;H3|h$&1a)(Fzdg9FFRWG3 z`HC^}w?S=&EmH2wy)`(I`ZlAK({;+@*x3-NT$La1BZjzVdj2{(@}XXB@K&2^dFeZ9 zsEtbKlQ7*aMjKa@7>9q_U)CgH@|$Kem0G>5kn$IyIRfTa&Ldlm3aW+8nSLM%_<}?R zH2-7D9bROg@~MtPCkxX_6w1h)j%CTy%_W&tknJ4lN?#wM^XO~3X84~iH$QK_=B|&H zxIQ431}?B3iGxBJz4ULu0UEWTk^1jD6Yf&XtXu9*4MY>#Hz6x-C;uDB1J+1hwwX$| z8D`jMLoib-Vqi)`<93s#p~aa>aJiPZ28Wn!`cM!A`OsD~oQr`kgO%Ed6|ZXqwT}|6QXEa>@c+MH2RIKciVH5Ds0_w}jw#YR zFY4Lo4GOLu3b(~0i0A0H4qmw#Fp3^w5%Lx^bPOos5)?Wkh>UynqwpX$=fAY;DP=n; zd3Wu3O0fS(93IP5Wbv9v+RRBezQI@TbE(R$ zTM?muvl0!N!JvrjA?59mZy@{xZ{Ehu3M+R3si<77J3m!2SLHH>nRc*d_&1s$&$E;f z#tHL^84AnJ#Fq(O(%l`TP&#MaVyvLiK^@-VUUm4ofy@o29@SmJwDhaHVau&f?mF?t zI=o0Oz!JMjTdm2&ZdL`2rDD&ii3jMYtO%Kn)2(j6TN~w^Ufrd<80@pRpJef|&W9>s z+-*zphm@)(;+YHr*@lwKhcb>&+e!>SvtWYi5Dz=5zt4)y@)U4Ol;j&v%?v5cX=EH| zdv4!`#j`8GK+el%yA5fCTSE2x_{a08Qf@`@p^OSzu(6^&CTX(`RO93LaqH_*tj;dY zg!?GZt`6$D5xpj`lx-U^V^@2L`p3nAF#XD0u^@T(tH+y+>mNYf*$?t}y!#^<0Mwcs z@$k@IGWCI@2vDA5Z+*S!fNI;~c { - if (path && path.length > 0) { - this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason)); - } - }) - .catch((reason) => console.warn(reason)); + try { + const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam); + this.pathfindingManager + .findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos) + .then((path) => { + if (path && path.length > 0) { + this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason)); + } + }) + .catch((reason) => console.warn(reason)); + } catch (err) { + console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`); + } } } catch (err) { if (err instanceof TextureError) { From baf1453e146ee56be18e611466463cd60a1dfb6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:54:52 +0000 Subject: [PATCH 12/60] Bump node-fetch from 2.6.6 to 2.6.7 in /back Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.6 to 2.6.7. - [Release notes](https://github.com/node-fetch/node-fetch/releases) - [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.6...v2.6.7) --- updated-dependencies: - dependency-name: node-fetch dependency-type: indirect ... Signed-off-by: dependabot[bot] --- back/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/back/yarn.lock b/back/yarn.lock index d22087d2..04c6929b 100644 --- a/back/yarn.lock +++ b/back/yarn.lock @@ -1504,9 +1504,9 @@ natural-compare@^1.4.0: integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= node-fetch@^2.6.5: - version "2.6.6" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89" - integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" From 6684c87efa48cfd4f2026f73004bb38cd1eab114 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:54:59 +0000 Subject: [PATCH 13/60] Bump node-fetch from 2.6.1 to 2.6.7 in /pusher Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7. - [Release notes](https://github.com/node-fetch/node-fetch/releases) - [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7) --- updated-dependencies: - dependency-name: node-fetch dependency-type: indirect ... Signed-off-by: dependabot[bot] --- pusher/yarn.lock | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/pusher/yarn.lock b/pusher/yarn.lock index bdafd193..1321a9c3 100644 --- a/pusher/yarn.lock +++ b/pusher/yarn.lock @@ -1622,9 +1622,11 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" nopt@^5.0.0: version "5.0.0" @@ -2265,6 +2267,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -2376,6 +2383,19 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" From 934a59e59f9ed06a2d6a9a1625a4292998026672 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Tue, 25 Jan 2022 10:05:04 +0100 Subject: [PATCH 14/60] improved way of getting hash parameters --- front/src/Url/UrlManager.ts | 29 ++++++++++++++++++----------- maps/tests/move_to.json | 6 ++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 0833919a..cb0e1ed0 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -45,21 +45,28 @@ class UrlManager { } public getStartLayerNameFromUrl(): string | null { - const hash = window.location.hash; - let layerName = null; - if (hash.length > 1) { - layerName = hash.includes("&") ? hash.split("&")[0].substring(1) : hash.substring(1); + const parameters = this.getHashParameters(); + for (const key in parameters) { + if (parameters[key] === undefined) { + return key; + } } - return layerName; + return null; } public getHashParameter(name: string): string | undefined { - const parameters = window.location.hash.split("&").reduce((res: Record, item: string) => { - const parts = item.split("="); - res[parts[0]] = parts[1]; - return res; - }, {}); - return parameters[name]; + return this.getHashParameters()[name]; + } + + private getHashParameters(): Record { + return window.location.hash + .substring(1) + .split("&") + .reduce((res: Record, item: string) => { + const parts = item.split("="); + res[parts[0]] = parts[1]; + return res; + }, {}); } pushStartLayerNameToUrl(startLayerName: string): void { diff --git a/maps/tests/move_to.json b/maps/tests/move_to.json index 2fdabab0..39be809b 100644 --- a/maps/tests/move_to.json +++ b/maps/tests/move_to.json @@ -44,6 +44,12 @@ "id":8, "name":"exit", "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], "type":"tilelayer", "visible":true, "width":10, From 40366eb070644b5cc7f31e3777aa890f636a8c25 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Tue, 25 Jan 2022 10:20:21 +0100 Subject: [PATCH 15/60] readme update --- docs/maps/entry-exit.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md index 7f74a46b..6163afb1 100644 --- a/docs/maps/entry-exit.md +++ b/docs/maps/entry-exit.md @@ -74,10 +74,14 @@ We are able to direct WOKA to the desired place immediately after spawn. To make ``` .../my_map.json#&moveTo=exit ``` -*Define entry point and moveTo parameter* +*Define entry point and moveTo parameter like this...* ``` .../my_map.json#start&moveTo=meeting-room ``` +*...or like this* +``` +.../my_map.json#moveTo=meeting-room&start +``` For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. From 1ca56efd426affe44ac4999af12a86a797db4b7a Mon Sep 17 00:00:00 2001 From: Piotr Hanusiak Date: Tue, 25 Jan 2022 10:58:59 +0100 Subject: [PATCH 16/60] Update docs/maps/entry-exit.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/entry-exit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md index 6163afb1..2040beb3 100644 --- a/docs/maps/entry-exit.md +++ b/docs/maps/entry-exit.md @@ -68,7 +68,7 @@ How to use entry point : ## Defining destination point with moveTo parameter -We are able to direct WOKA to the desired place immediately after spawn. To make users spawn on entry point and the go to the, for example, meeting room automatically, simply add `moveTo` as an additional parameter of URL: +We are able to direct a Woka to the desired place immediately after spawn. To make users spawn on an entry point and then, walk automatically to a meeting room, simply add `moveTo` as an additional parameter of URL: *Use default entry point* ``` From 90a76e9f04a54b4829cdc10f28c0309d17f3b029 Mon Sep 17 00:00:00 2001 From: Hanusiak Piotr Date: Tue, 25 Jan 2022 11:03:58 +0100 Subject: [PATCH 17/60] fixed layer name in docs --- docs/maps/images/moveTo-layer-example.png | Bin 34963 -> 28120 bytes maps/tests/move_to.json | 18 +++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/maps/images/moveTo-layer-example.png b/docs/maps/images/moveTo-layer-example.png index 66f5e3c6bfd1fa246bb04e73af351e100f34bad8..12e8a4ad9b8277684f06fa3b24fdebc27b339972 100644 GIT binary patch literal 28120 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 Date: Tue, 25 Jan 2022 11:37:29 +0100 Subject: [PATCH 18/60] Change Warning Container with Yellow color Signed-off-by: Gregoire Parant --- .../WarningContainer/WarningContainer.svelte | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/front/src/Components/WarningContainer/WarningContainer.svelte b/front/src/Components/WarningContainer/WarningContainer.svelte index fef56217..834b45b9 100644 --- a/front/src/Components/WarningContainer/WarningContainer.svelte +++ b/front/src/Components/WarningContainer/WarningContainer.svelte @@ -26,19 +26,25 @@ From 9c381d1a07962d1a0f7e2b389ad3758e048426fa Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 25 Jan 2022 11:52:31 +0100 Subject: [PATCH 19/60] Fix yarn pretty Signed-off-by: Gregoire Parant --- .../WarningContainer/WarningContainer.svelte | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/front/src/Components/WarningContainer/WarningContainer.svelte b/front/src/Components/WarningContainer/WarningContainer.svelte index 834b45b9..773f4023 100644 --- a/front/src/Components/WarningContainer/WarningContainer.svelte +++ b/front/src/Components/WarningContainer/WarningContainer.svelte @@ -26,25 +26,25 @@ From 30ebb90617875e256b2b099e18f2417873b3475f Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Tue, 25 Jan 2022 17:41:05 +0100 Subject: [PATCH 20/60] Enchance emoji menu --- .../src/Components/EmoteMenu/EmoteMenu.svelte | 22 +++++++++++++++++++ front/src/i18n/en-US/emoji.ts | 21 ++++++++++++++++++ front/src/i18n/en-US/index.ts | 2 ++ front/src/i18n/fr-FR/emoji.ts | 21 ++++++++++++++++++ front/src/i18n/fr-FR/index.ts | 2 ++ 5 files changed, 68 insertions(+) create mode 100644 front/src/i18n/en-US/emoji.ts create mode 100644 front/src/i18n/fr-FR/emoji.ts diff --git a/front/src/Components/EmoteMenu/EmoteMenu.svelte b/front/src/Components/EmoteMenu/EmoteMenu.svelte index d084718d..16525e5e 100644 --- a/front/src/Components/EmoteMenu/EmoteMenu.svelte +++ b/front/src/Components/EmoteMenu/EmoteMenu.svelte @@ -4,6 +4,7 @@ import { onDestroy, onMount } from "svelte"; import { EmojiButton } from "@joeattardi/emoji-button"; import { isMobile } from "../../Enum/EnvironmentVariable"; + import LL from "../../i18n/i18n-svelte"; let emojiContainer: HTMLElement; let picker: EmojiButton; @@ -15,10 +16,31 @@ rootElement: emojiContainer, styleProperties: { "--font": "Press Start 2P", + "--text-color": "whitesmoke", + "--secondary-text-color": "whitesmoke", + "--category-button-color": "whitesmoke", }, emojisPerRow: isMobile() ? 6 : 8, autoFocusSearch: false, style: "twemoji", + showPreview: false, + i18n: { + search: $LL.emoji.search(), + categories: { + recents: $LL.emoji.categories.recents(), + smileys: $LL.emoji.categories.smileys(), + people: $LL.emoji.categories.people(), + animals: $LL.emoji.categories.animals(), + food: $LL.emoji.categories.food(), + activities: $LL.emoji.categories.activities(), + travel: $LL.emoji.categories.travel(), + objects: $LL.emoji.categories.objects(), + symbols: $LL.emoji.categories.symbols(), + flags: $LL.emoji.categories.flags(), + custom: $LL.emoji.categories.custom(), + }, + notFound: $LL.emoji.notFound(), + }, }); //the timeout is here to prevent the menu from flashing setTimeout(() => picker.showPicker(emojiContainer), 100); diff --git a/front/src/i18n/en-US/emoji.ts b/front/src/i18n/en-US/emoji.ts new file mode 100644 index 00000000..fb23d102 --- /dev/null +++ b/front/src/i18n/en-US/emoji.ts @@ -0,0 +1,21 @@ +import type { BaseTranslation } from "../i18n-types"; + +const emoji: BaseTranslation = { + search: "Search emojis...", + categories: { + recents: "Recent Emojis", + smileys: "Smileys & Emotion", + people: "People & Body", + animals: "Animals & Nature", + food: "Food & Drink", + activities: "Activities", + travel: "Travel & Places", + objects: "Objects", + symbols: "Symbols", + flags: "Flags", + custom: "Custom", + }, + notFound: "No emojis found", +}; + +export default emoji; diff --git a/front/src/i18n/en-US/index.ts b/front/src/i18n/en-US/index.ts index 609e38ca..2d3ac74a 100644 --- a/front/src/i18n/en-US/index.ts +++ b/front/src/i18n/en-US/index.ts @@ -10,6 +10,7 @@ import login from "./login"; import menu from "./menu"; import report from "./report"; import warning from "./warning"; +import emoji from "./emoji"; const en_US: BaseTranslation = { language: "English", @@ -25,6 +26,7 @@ const en_US: BaseTranslation = { menu, report, warning, + emoji, }; export default en_US; diff --git a/front/src/i18n/fr-FR/emoji.ts b/front/src/i18n/fr-FR/emoji.ts new file mode 100644 index 00000000..0913e074 --- /dev/null +++ b/front/src/i18n/fr-FR/emoji.ts @@ -0,0 +1,21 @@ +import type { Translation } from "../i18n-types"; + +const emoji: NonNullable = { + search: "Chercher un emoji...", + categories: { + recents: "Emojis récents", + smileys: "Smileys & emotions", + people: "Personne & corps", + animals: "Animaux & nature", + food: "Nourriture & boissons", + activities: "Activités", + travel: "Voyage & endroits", + objects: "Objets", + symbols: "Symbols", + flags: "Drapeaux", + custom: "Personalisés", + }, + notFound: "Aucun emoji trouvé", +}; + +export default emoji; diff --git a/front/src/i18n/fr-FR/index.ts b/front/src/i18n/fr-FR/index.ts index 9cc5a0bc..b9f91b13 100644 --- a/front/src/i18n/fr-FR/index.ts +++ b/front/src/i18n/fr-FR/index.ts @@ -4,6 +4,7 @@ import audio from "./audio"; import camera from "./camera"; import chat from "./chat"; import companion from "./companion"; +import emoji from "./emoji"; import error from "./error"; import follow from "./follow"; import login from "./login"; @@ -27,6 +28,7 @@ const fr_FR: Translation = { menu, report, warning, + emoji, }; export default fr_FR; From 78a020576fdfbea60b53954d28caee9ccd9d6068 Mon Sep 17 00:00:00 2001 From: Lukas Hass Date: Tue, 25 Jan 2022 18:04:59 +0100 Subject: [PATCH 21/60] Add FALLBACK_LOCALE to config template --- front/dist/env-config.template.js | 1 + 1 file changed, 1 insertion(+) diff --git a/front/dist/env-config.template.js b/front/dist/env-config.template.js index 4a549c18..e672d7aa 100644 --- a/front/dist/env-config.template.js +++ b/front/dist/env-config.template.js @@ -23,4 +23,5 @@ window.env = { NODE_ENV: '${NODE_ENV}', DISABLE_ANONYMOUS: '${DISABLE_ANONYMOUS}', OPID_LOGIN_SCREEN_PROVIDER: '${OPID_LOGIN_SCREEN_PROVIDER}', + FALLBACK_LOCALE: '${FALLBACK_LOCALE}', }; From aefb28d895b63dd572057729ea201b7966213d52 Mon Sep 17 00:00:00 2001 From: Valdo Romao Date: Tue, 25 Jan 2022 19:43:27 +0000 Subject: [PATCH 22/60] Fixed some typos :) --- front/src/Api/IframeListener.ts | 1 - front/src/Api/ScriptUtils.ts | 1 - front/src/WebRtc/CoWebsiteManager.ts | 4 ++-- maps/tests/ChangeLayerApi/script.js | 4 ++-- maps/tests/CoWebsite/script.js | 26 +++++++++++++------------- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 27486d5d..65ab1303 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -8,7 +8,6 @@ import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent"; import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent"; import { scriptUtils } from "./ScriptUtils"; import { isGoToPageEvent } from "./Events/GoToPageEvent"; -import { isCloseCoWebsite, CloseCoWebsiteEvent } from "./Events/CloseCoWebsiteEvent"; import { IframeErrorAnswerEvent, IframeQueryMap, diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index 10a80c92..f0a0625a 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -1,4 +1,3 @@ -import { coWebsiteManager, CoWebsite } from "../WebRtc/CoWebsiteManager"; import { playersStore } from "../Stores/PlayersStore"; import { chatMessagesStore } from "../Stores/ChatStore"; import type { ChatEvent } from "./Events/ChatEvent"; diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 48cc9680..92629dd6 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -512,7 +512,7 @@ class CoWebsiteManager { if (this.coWebsites.length < 1) { this.loadMain(); } else if (this.coWebsites.length === 5) { - throw new Error("Too many we"); + throw new Error("Too many websites"); } Promise.resolve(callback(this.cowebsiteBufferDom)) @@ -580,7 +580,7 @@ class CoWebsiteManager { return reject(); }); }) - .catch((e) => console.error("Error loadCoWebsite >=> ", e)); + .catch((e) => console.error("Error loadCoWebsite => ", e)); }); } diff --git a/maps/tests/ChangeLayerApi/script.js b/maps/tests/ChangeLayerApi/script.js index 32f038ba..4fae2b3a 100644 --- a/maps/tests/ChangeLayerApi/script.js +++ b/maps/tests/ChangeLayerApi/script.js @@ -1,7 +1,7 @@ WA.room.onEnterLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Hello!", 'Wooka'); + WA.chat.sendChatMessage("Hello!", 'Woka'); }); WA.room.onLeaveLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Goodbye!", 'Wooka'); + WA.chat.sendChatMessage("Goodbye!", 'Woka'); }); \ No newline at end of file diff --git a/maps/tests/CoWebsite/script.js b/maps/tests/CoWebsite/script.js index b45c0ffc..5563768f 100644 --- a/maps/tests/CoWebsite/script.js +++ b/maps/tests/CoWebsite/script.js @@ -12,12 +12,12 @@ WA.onInit().then(() => { }); }); -function wookaSendMessage(message) { - WA.chat.sendChatMessage(message, 'Wooka'); +function wokaSendMessage(message) { + WA.chat.sendChatMessage(message, 'Woka'); } function unknownCommand() { - wookaSendMessage('Unknown command'); + wokaSendMessage('Unknown command'); } function executeCommand(command, args) { @@ -32,7 +32,7 @@ function executeCommand(command, args) { function coWebsiteCommand(args) { if (args.length < 1) { - wookaSendMessage('Too few arguments'); + wokaSendMessage('Too few arguments'); return; } @@ -52,27 +52,27 @@ function coWebsiteCommand(args) { async function openCoWebsite(args) { if (args.length < 1) { - wookaSendMessage('Too few arguments'); + wokaSendMessage('Too few arguments'); return; } try { const url = new URL(args[0]); } catch (exception) { - wookaSendMessage('Parameter is not a valid URL !'); + wokaSendMessage('Parameter is not a valid URL !'); return; } await WA.nav.openCoWebSite(args[0]).then(() => { - wookaSendMessage('Co-website has been opened !'); + wokaSendMessage('Co-website has been opened !'); }).catch((error) => { - wookaSendMessage(`Something wrong happen during co-website opening: ${error.message}`); + wokaSendMessage(`Something wrong happen during co-website opening: ${error.message}`); }); } async function closeCoWebsite(args) { if (args.length < 1) { - wookaSendMessage('Too few arguments'); + wokaSendMessage('Too few arguments'); return; } @@ -83,7 +83,7 @@ async function closeCoWebsite(args) { coWebsites.forEach(coWebsite => { coWebsite.close(); }); - wookaSendMessage('All co-websites has been closed !'); + wokaSendMessage('All co-websites has been closed !'); return; } @@ -96,13 +96,13 @@ async function closeCoWebsite(args) { coWebsites.find((coWebsite) => coWebsite.position === position); if (!coWebsite) { - wookaSendMessage('Unknown co-website'); + wokaSendMessage('Unknown co-website'); return; } await coWebsite.close().then(() => { - wookaSendMessage('This co-websites has been closed !'); + wokaSendMessage('This co-websites has been closed !'); }).catch((error) => { - wookaSendMessage(`Something wrong happen during co-website closing: ${error.message}`); + wokaSendMessage(`Something wrong happen during co-website closing: ${error.message}`); }); } From a55e5373fc7677cf247889db09f86679e5afc617 Mon Sep 17 00:00:00 2001 From: Lurkars Date: Wed, 26 Jan 2022 10:33:07 +0100 Subject: [PATCH 23/60] added de-DE translation, fix quality small vs. minimum --- .../Components/Menu/SettingsSubMenu.svelte | 16 +-- front/src/i18n/de-DE/audio.ts | 10 ++ front/src/i18n/de-DE/camera.ts | 22 ++++ front/src/i18n/de-DE/chat.ts | 12 ++ front/src/i18n/de-DE/companion.ts | 11 ++ front/src/i18n/de-DE/error.ts | 20 +++ front/src/i18n/de-DE/follow.ts | 27 ++++ front/src/i18n/de-DE/index.ts | 30 +++++ front/src/i18n/de-DE/login.ts | 14 ++ front/src/i18n/de-DE/menu.ts | 124 ++++++++++++++++++ front/src/i18n/de-DE/report.ts | 25 ++++ front/src/i18n/de-DE/warning.ts | 16 +++ front/src/i18n/de-DE/woka.ts | 20 +++ front/src/i18n/en-US/menu.ts | 16 +-- front/src/i18n/fr-FR/menu.ts | 16 +-- 15 files changed, 355 insertions(+), 24 deletions(-) create mode 100644 front/src/i18n/de-DE/audio.ts create mode 100644 front/src/i18n/de-DE/camera.ts create mode 100644 front/src/i18n/de-DE/chat.ts create mode 100644 front/src/i18n/de-DE/companion.ts create mode 100644 front/src/i18n/de-DE/error.ts create mode 100644 front/src/i18n/de-DE/follow.ts create mode 100644 front/src/i18n/de-DE/index.ts create mode 100644 front/src/i18n/de-DE/login.ts create mode 100644 front/src/i18n/de-DE/menu.ts create mode 100644 front/src/i18n/de-DE/report.ts create mode 100644 front/src/i18n/de-DE/warning.ts create mode 100644 front/src/i18n/de-DE/woka.ts diff --git a/front/src/Components/Menu/SettingsSubMenu.svelte b/front/src/Components/Menu/SettingsSubMenu.svelte index 9ab9ba98..85b16f12 100644 --- a/front/src/Components/Menu/SettingsSubMenu.svelte +++ b/front/src/Components/Menu/SettingsSubMenu.svelte @@ -104,13 +104,13 @@ > @@ -131,13 +131,13 @@ > diff --git a/front/src/i18n/de-DE/audio.ts b/front/src/i18n/de-DE/audio.ts new file mode 100644 index 00000000..33ab5047 --- /dev/null +++ b/front/src/i18n/de-DE/audio.ts @@ -0,0 +1,10 @@ +import type { BaseTranslation } from "../i18n-types"; + +const audio: BaseTranslation = { + manager: { + reduce: "Während Unterhaltungen verringern", + }, + message: "Sprachnachricht", +}; + +export default audio; diff --git a/front/src/i18n/de-DE/camera.ts b/front/src/i18n/de-DE/camera.ts new file mode 100644 index 00000000..58a53a8b --- /dev/null +++ b/front/src/i18n/de-DE/camera.ts @@ -0,0 +1,22 @@ +import type { BaseTranslation } from "../i18n-types"; + +const camera: BaseTranslation = { + enable: { + title: "Bitte schalte deine Kamera und dein Mikrofon ein.", + start: "Los gehts!", + }, + help: { + title: "Zugriff auf Kamera / Mikrofon erforderlich", + permissionDenied: "Zugriff verweigert", + content: "Der Zugriff auf Kamera und Mikrofon muss im Browser freigegeben werden.", + firefoxContent: + 'Bitte klicke auf "Diese Entscheidungen speichern" Schaltfläche um erneute Nachfragen nach der Freigabe in Firefox zu verhindern.', + refresh: "Aktualisieren", + continue: "Ohne Kamera fortfahren", + }, + my: { + silentZone: "Stiller Bereich", + }, +}; + +export default camera; diff --git a/front/src/i18n/de-DE/chat.ts b/front/src/i18n/de-DE/chat.ts new file mode 100644 index 00000000..2958c27c --- /dev/null +++ b/front/src/i18n/de-DE/chat.ts @@ -0,0 +1,12 @@ +import type { BaseTranslation } from "../i18n-types"; + +const chat: BaseTranslation = { + intro: "Hier ist dein Nachrichtenverlauf:", + enter: "Verfasse deine Nachricht...", + menu: { + visitCard: "Visitenkarte", + addFriend: "Freund*In hinzufügen", + }, +}; + +export default chat; diff --git a/front/src/i18n/de-DE/companion.ts b/front/src/i18n/de-DE/companion.ts new file mode 100644 index 00000000..acf3bd85 --- /dev/null +++ b/front/src/i18n/de-DE/companion.ts @@ -0,0 +1,11 @@ +import type { BaseTranslation } from "../i18n-types"; + +const companion: BaseTranslation = { + select: { + title: "Wähle einen Gefährten", + any: "Kein Gefährte", + continue: "Weiter", + }, +}; + +export default companion; diff --git a/front/src/i18n/de-DE/error.ts b/front/src/i18n/de-DE/error.ts new file mode 100644 index 00000000..0f82d482 --- /dev/null +++ b/front/src/i18n/de-DE/error.ts @@ -0,0 +1,20 @@ +import type { BaseTranslation } from "../i18n-types"; + +const error: BaseTranslation = { + accessLink: { + title: "Ungültiger Zugangslink", + subTitle: "Karte konnte nicht gefunden werden. Prüfe bitte deinen Zugangslink.", + details: "Für weitere Information kannst du die Administratoren kontaktieren oder melde dich bei uns unter: hello@workadventu.re", + }, + connectionRejected: { + title: "Verbindungen zurückgewiesen", + subTitle: "Du kannst diese Welt nicht betreten. Versuche es später noch einmal {error}.", + details: "Für weitere Information kannst du die Administratoren kontaktieren oder melde dich bei uns unter: hello@workadventu.re", + }, + connectionRetry: { + unableConnect: "Es konnte keine Verbindung zu WorkAdventure erstellt werden. Bist du mit dem Internet verbunden?", + }, + error: "Fehler", +}; + +export default error; diff --git a/front/src/i18n/de-DE/follow.ts b/front/src/i18n/de-DE/follow.ts new file mode 100644 index 00000000..997f4f72 --- /dev/null +++ b/front/src/i18n/de-DE/follow.ts @@ -0,0 +1,27 @@ +import type { BaseTranslation } from "../i18n-types"; + +const follow: BaseTranslation = { + interactStatus: { + following: "{leader} folgen", + waitingFollowers: "Warte auf Bestätigung der Gefolgschaft", + followed: { + one: "{follower} folgt dir", + two: "{firstFollower} und {secondFollower} folgen dir", + many: "{followers} und {lastFollower} folgen dir", + }, + }, + interactMenu: { + title: { + interact: "Interaktion", + follow: "Möchtest du {leader} folgen?", + }, + stop: { + leader: "Möchtest du nicht weiter den Weg weisen?", + follower: "Möchtest du nicht mehr {leader} folgen?", + }, + yes: "Ja", + no: "Nein", + }, +}; + +export default follow; diff --git a/front/src/i18n/de-DE/index.ts b/front/src/i18n/de-DE/index.ts new file mode 100644 index 00000000..d9e8bb48 --- /dev/null +++ b/front/src/i18n/de-DE/index.ts @@ -0,0 +1,30 @@ +import type { BaseTranslation } from "../i18n-types"; +import audio from "./audio"; +import camera from "./camera"; +import chat from "./chat"; +import companion from "./companion"; +import woka from "./woka"; +import error from "./error"; +import follow from "./follow"; +import login from "./login"; +import menu from "./menu"; +import report from "./report"; +import warning from "./warning"; + +const en_US: BaseTranslation = { + language: "Deutsch", + country: "Deutschland", + audio, + camera, + chat, + companion, + woka, + error, + follow, + login, + menu, + report, + warning, +}; + +export default en_US; diff --git a/front/src/i18n/de-DE/login.ts b/front/src/i18n/de-DE/login.ts new file mode 100644 index 00000000..921ffc0e --- /dev/null +++ b/front/src/i18n/de-DE/login.ts @@ -0,0 +1,14 @@ +import type { BaseTranslation } from "../i18n-types"; + +const login: BaseTranslation = { + input: { + name: { + placeholder: "Trage deinen Namen ein", + empty: "Kein Name angegeben", + }, + }, + terms: 'Wenn du fortfährst, akzeptierst du die Nutzungsbedingungen, Datenschutzerklärung und Cookierichtlinien.', + continue: "Fortfahren", +}; + +export default login; diff --git a/front/src/i18n/de-DE/menu.ts b/front/src/i18n/de-DE/menu.ts new file mode 100644 index 00000000..13a3287a --- /dev/null +++ b/front/src/i18n/de-DE/menu.ts @@ -0,0 +1,124 @@ +import type { BaseTranslation } from "../i18n-types"; + +const menu: BaseTranslation = { + title: "Menu", + icon: { + open: { + menu: "Menu öffnen", + invite: "Einladung anzeigen", + register: "Registrieren", + chat: "Chat öffnen", + }, + }, + visitCard: { + close: "Schliessen", + }, + profile: { + edit: { + name: "Deinen Namen ändern", + woka: "Dein WOKA ändern", + companion: "Deinen Begleiter ändern", + camera: "Kameraeinstellungen ändern", + }, + login: "Einloggen", + logout: "Ausloggen", + }, + settings: { + gameQuality: { + title: "Spiel Qualität", + short: { + high: "Hoch (120 BpS)", + medium: "Mittel (60 BpS)", + small: "Gering (40 BpS)", + minimum: "Minimal (20 BpS)", + }, + long: { + high: "Hohe Video Qualität (120 BpS)", + medium: "Mittlere Video Qualität (60 BpS, empfohlen)", + small: "Geringe Video Qualität (40 BpS)", + minimum: "Minimale Video Qualität (20 BpS)", + }, + }, + videoQuality: { + title: "Video Qualität", + short: { + high: "Hoch (30 BpS)", + medium: "Mittel (20 BpS)", + small: "Gering (10 BpS)", + minimum: "Minimale (5 BpS)", + }, + long: { + high: "Hohe Video Qualität (30 BpS)", + medium: "Mittlere Video Qualität (20 BpS, empfohlen)", + small: "Geringe Video Qualität (10 BpS)", + minimum: "Minimale Video Qualität (5 BpS)", + }, + }, + language: { + title: "Sprache", + }, + save: { + warning: "(Das Spiel wird nach dem Speichern neugestartet)", + button: "Speichern", + }, + fullscreen: "Vollbild", + notifications: "Benachrichtigungen", + cowebsiteTrigger: "Jedes mal nachfragen bevor Webseiten oder Jitsi Meet Räume geöffnet werden", + ignoreFollowRequest: "Ignoriere Folgen-Anfragen anderer Nutzer", + }, + invite: { + description: "Link zu diesem Raum teilen!", + copy: "Kopieren", + share: "Teilen", + }, + globalMessage: { + text: "Text", + audio: "Audio", + warning: "An alle Räume dieser Welt senden", + enter: "Trage hier deine Nachricht ein...", + send: "Senden", + }, + globalAudio: { + uploadInfo: "Datei hochladen", + error: "Keine Datei ausgewählt. Du musst vor dem Versenden eine Datei hochladen.", + }, + contact: { + gettingStarted: { + title: "Erste Schritte", + description: + "Mit WorkAdventure kannst du eine Onlinewelt schaffen in der du dich spontan mit Anderen treffen und unterhalten kannst. Erstelle als erstes deine eigene Karte. Es steht dir eine großen Auswahl an vorgefertigten Karten von unserem Team zur Auswahl.", + }, + createMap: { + title: "Eigene Karte erstellen ", + description: "Du kannst auch deine eigene Karte erstellen. Folge dazu unserer Schritt-für-Schritt Anleitung.", + }, + }, + about: { + mapInfo: "Informationen über diese Karte", + mapLink: "Link zur Karte", + copyrights: { + map: { + title: "Urheberrecht der Karte", + empty: "Die Ersteller*In der Karte hat keine Informationen zum Urheberrecht hinterlegt.", + }, + tileset: { + title: "Urheberrecht der Tilesets", + empty: "Die Ersteller*In der Karte hat keine Informationen zum Urheberrecht der Tilesets hinterlegt. Dies bedeutet nicht, dass die Tilesets keiner Lizenz unterliegen.", + }, + audio: { + title: "Urheberrecht der Audiodateien", + empty: "Die Ersteller*In der Karte hat keine Informationen zum Urheberrecht der Audiodateien hinterlegt. Dies bedeutet nicht, dass die Audiodateien keiner Lizenz unterliegen.", + }, + }, + }, + sub: { + profile: "Profil", + settings: "Einstellungen", + invite: "Einladung", + credit: "Über die Karte", + globalMessages: "Globale Nachrichten", + contact: "Kontakt", + }, +}; + +export default menu; diff --git a/front/src/i18n/de-DE/report.ts b/front/src/i18n/de-DE/report.ts new file mode 100644 index 00000000..6f8b2a94 --- /dev/null +++ b/front/src/i18n/de-DE/report.ts @@ -0,0 +1,25 @@ +import type { BaseTranslation } from "../i18n-types"; + +const report: BaseTranslation = { + block: { + title: "Blockieren", + content: "Blockiere jede Kommunikation von und zu {userName}. Kann jederzeit rückgängig gemacht werden.", + unblock: "Blockierung für diesen User aufheben", + block: "Blockiere diese User", + }, + title: "Melden", + content: "Verfasse eine Meldung an die Administratoren dieses Raums. Diese können den User anschließend bannen.", + message: { + title: "Deine Nachricht: ", + empty: "Bitte einen Text angeben.", + }, + submit: "Diesen User melden", + moderate: { + title: "{userName} moderieren", + block: "Blockieren", + report: "Melden", + noSelect: "FEHLER : Es ist keine Handlung ausgewählt.", + }, +}; + +export default report; diff --git a/front/src/i18n/de-DE/warning.ts b/front/src/i18n/de-DE/warning.ts new file mode 100644 index 00000000..958e9cab --- /dev/null +++ b/front/src/i18n/de-DE/warning.ts @@ -0,0 +1,16 @@ +import type { BaseTranslation } from "../i18n-types"; + +const warning: BaseTranslation = { + title: "Warnung!", + content: + 'Diese Welt erreicht bald die maximale Kapazität. Du kannst die Kapazität hier erhöhen', + limit: "Diese Welt erreicht bald die maximale Kapazität!", + accessDenied: { + camera: "Zugriff auf die Kamera verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.", + screenSharing: "Zugriff auf die Bildschirmfreigabe verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.", + }, + importantMessage: "Wichtige Nachricht", + connectionLost: "Verbindungen unterbrochen. Wiederverbinden...", +}; + +export default warning; diff --git a/front/src/i18n/de-DE/woka.ts b/front/src/i18n/de-DE/woka.ts new file mode 100644 index 00000000..a571feea --- /dev/null +++ b/front/src/i18n/de-DE/woka.ts @@ -0,0 +1,20 @@ +import type { BaseTranslation } from "../i18n-types"; + +const woka: BaseTranslation = { + customWoka: { + title: "Dein WOKA bearbeiten", + navigation: { + return: "Zurück", + back: "Hoch", + finish: "Auswählen", + next: "Runter", + }, + }, + selectWoka: { + title: "Dein WOKA auswählen", + continue: "Auswählen", + customize: "Bearbeite dein WOKA", + }, +}; + +export default woka; diff --git a/front/src/i18n/en-US/menu.ts b/front/src/i18n/en-US/menu.ts index 239b0ac1..b7c69935 100644 --- a/front/src/i18n/en-US/menu.ts +++ b/front/src/i18n/en-US/menu.ts @@ -29,14 +29,14 @@ const menu: BaseTranslation = { short: { high: "High (120 fps)", medium: "Medium (60 fps)", - minimum: "Minimum (40 fps)", - small: "Small (20 fps)", + small: "Small (40 fps)", + minimum: "Minimum (20 fps)", }, long: { high: "High video quality (120 fps)", medium: "Medium video quality (60 fps, recommended)", - minimum: "Minimum video quality (40 fps)", - small: "Small video quality (20 fps)", + small: "Small video quality (40 fps)", + minimum: "Minimum video quality (20 fps)", }, }, videoQuality: { @@ -44,14 +44,14 @@ const menu: BaseTranslation = { short: { high: "High (30 fps)", medium: "Medium (20 fps)", - minimum: "Minimum (10 fps)", - small: "Small (5 fps)", + small: "Small (10 fps)", + minimum: "Minimum (5 fps)", }, long: { high: "High video quality (30 fps)", medium: "Medium video quality (20 fps, recommended)", - minimum: "Minimum video quality (10 fps)", - small: "Small video quality (5 fps)", + small: "Small video quality (10 fps)", + minimum: "Minimum video quality (5 fps)", }, }, language: { diff --git a/front/src/i18n/fr-FR/menu.ts b/front/src/i18n/fr-FR/menu.ts index 2c0efeba..54481ec8 100644 --- a/front/src/i18n/fr-FR/menu.ts +++ b/front/src/i18n/fr-FR/menu.ts @@ -29,14 +29,14 @@ const menu: NonNullable = { short: { high: "Haute (120 fps)", medium: "Moyenne (60 fps)", - minimum: "Minimale (40 fps)", - small: "Reduite (20 fps)", + small: "Reduite (40 fps)", + minimum: "Minimale (20 fps)", }, long: { high: "Haute (120 fps)", medium: "Moyenne (60 fps, recommandée)", - minimum: "Minimale (40 fps)", - small: "Reduite (20 fps)", + small: "Reduite (40 fps)", + minimum: "Minimale (20 fps)", }, }, videoQuality: { @@ -44,14 +44,14 @@ const menu: NonNullable = { short: { high: "Haute (30 fps)", medium: "Moyenne (20 fps)", - minimum: "Minimale (10 fps)", - small: "Reduite (5 fps)", + small: "Reduite (10 fps)", + minimum: "Minimale (5 fps)", }, long: { high: "Haute (30 fps)", medium: "Moyenne (20 fps, recommandée)", - minimum: "Minimale (10 fps)", - small: "Reduite (5 fps)", + small: "Reduite (10 fps)", + minimum: "Minimale (5 fps)", }, }, language: { From a808819a1130741751f82ac90921a62a60fff7ba Mon Sep 17 00:00:00 2001 From: Lurkars Date: Wed, 26 Jan 2022 10:38:40 +0100 Subject: [PATCH 24/60] fix de-DE index.js --- front/src/i18n/de-DE/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/front/src/i18n/de-DE/index.ts b/front/src/i18n/de-DE/index.ts index d9e8bb48..f07a5b01 100644 --- a/front/src/i18n/de-DE/index.ts +++ b/front/src/i18n/de-DE/index.ts @@ -1,17 +1,19 @@ -import type { BaseTranslation } from "../i18n-types"; +import en_US from "../en-US"; +import type { Translation } from "../i18n-types"; import audio from "./audio"; import camera from "./camera"; import chat from "./chat"; import companion from "./companion"; -import woka from "./woka"; import error from "./error"; import follow from "./follow"; import login from "./login"; import menu from "./menu"; import report from "./report"; import warning from "./warning"; +import woka from "./woka"; -const en_US: BaseTranslation = { +const de_DE: Translation = { + ...en_US, language: "Deutsch", country: "Deutschland", audio, @@ -27,4 +29,4 @@ const en_US: BaseTranslation = { warning, }; -export default en_US; +export default de_DE; From 8858fccd18558753f389064a1f4d12a4187e51fd Mon Sep 17 00:00:00 2001 From: Lurkars Date: Wed, 26 Jan 2022 10:46:42 +0100 Subject: [PATCH 25/60] fix Translation --- front/src/i18n/de-DE/audio.ts | 4 ++-- front/src/i18n/de-DE/camera.ts | 4 ++-- front/src/i18n/de-DE/chat.ts | 4 ++-- front/src/i18n/de-DE/companion.ts | 4 ++-- front/src/i18n/de-DE/error.ts | 4 ++-- front/src/i18n/de-DE/follow.ts | 4 ++-- front/src/i18n/de-DE/login.ts | 4 ++-- front/src/i18n/de-DE/menu.ts | 4 ++-- front/src/i18n/de-DE/report.ts | 4 ++-- front/src/i18n/de-DE/warning.ts | 4 ++-- front/src/i18n/de-DE/woka.ts | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/front/src/i18n/de-DE/audio.ts b/front/src/i18n/de-DE/audio.ts index 33ab5047..c3608b8e 100644 --- a/front/src/i18n/de-DE/audio.ts +++ b/front/src/i18n/de-DE/audio.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const audio: BaseTranslation = { +const audio: NonNullable = { manager: { reduce: "Während Unterhaltungen verringern", }, diff --git a/front/src/i18n/de-DE/camera.ts b/front/src/i18n/de-DE/camera.ts index 58a53a8b..40ea9062 100644 --- a/front/src/i18n/de-DE/camera.ts +++ b/front/src/i18n/de-DE/camera.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const camera: BaseTranslation = { +const camera: NonNullable = { enable: { title: "Bitte schalte deine Kamera und dein Mikrofon ein.", start: "Los gehts!", diff --git a/front/src/i18n/de-DE/chat.ts b/front/src/i18n/de-DE/chat.ts index 2958c27c..a8d96de9 100644 --- a/front/src/i18n/de-DE/chat.ts +++ b/front/src/i18n/de-DE/chat.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const chat: BaseTranslation = { +const chat: NonNullable = { intro: "Hier ist dein Nachrichtenverlauf:", enter: "Verfasse deine Nachricht...", menu: { diff --git a/front/src/i18n/de-DE/companion.ts b/front/src/i18n/de-DE/companion.ts index acf3bd85..10ea32f1 100644 --- a/front/src/i18n/de-DE/companion.ts +++ b/front/src/i18n/de-DE/companion.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const companion: BaseTranslation = { +const companion: NonNullable = { select: { title: "Wähle einen Gefährten", any: "Kein Gefährte", diff --git a/front/src/i18n/de-DE/error.ts b/front/src/i18n/de-DE/error.ts index 0f82d482..6bfdfc35 100644 --- a/front/src/i18n/de-DE/error.ts +++ b/front/src/i18n/de-DE/error.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const error: BaseTranslation = { +const error: NonNullable = { accessLink: { title: "Ungültiger Zugangslink", subTitle: "Karte konnte nicht gefunden werden. Prüfe bitte deinen Zugangslink.", diff --git a/front/src/i18n/de-DE/follow.ts b/front/src/i18n/de-DE/follow.ts index 997f4f72..fd635759 100644 --- a/front/src/i18n/de-DE/follow.ts +++ b/front/src/i18n/de-DE/follow.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const follow: BaseTranslation = { +const follow: NonNullable = { interactStatus: { following: "{leader} folgen", waitingFollowers: "Warte auf Bestätigung der Gefolgschaft", diff --git a/front/src/i18n/de-DE/login.ts b/front/src/i18n/de-DE/login.ts index 921ffc0e..5a2b323c 100644 --- a/front/src/i18n/de-DE/login.ts +++ b/front/src/i18n/de-DE/login.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const login: BaseTranslation = { +const login: NonNullable = { input: { name: { placeholder: "Trage deinen Namen ein", diff --git a/front/src/i18n/de-DE/menu.ts b/front/src/i18n/de-DE/menu.ts index 13a3287a..8a0c0cf7 100644 --- a/front/src/i18n/de-DE/menu.ts +++ b/front/src/i18n/de-DE/menu.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const menu: BaseTranslation = { +const menu: NonNullable = { title: "Menu", icon: { open: { diff --git a/front/src/i18n/de-DE/report.ts b/front/src/i18n/de-DE/report.ts index 6f8b2a94..d0e17ffc 100644 --- a/front/src/i18n/de-DE/report.ts +++ b/front/src/i18n/de-DE/report.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const report: BaseTranslation = { +const report: NonNullable = { block: { title: "Blockieren", content: "Blockiere jede Kommunikation von und zu {userName}. Kann jederzeit rückgängig gemacht werden.", diff --git a/front/src/i18n/de-DE/warning.ts b/front/src/i18n/de-DE/warning.ts index 958e9cab..64efc70b 100644 --- a/front/src/i18n/de-DE/warning.ts +++ b/front/src/i18n/de-DE/warning.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const warning: BaseTranslation = { +const warning: NonNullable = { title: "Warnung!", content: 'Diese Welt erreicht bald die maximale Kapazität. Du kannst die Kapazität hier erhöhen', diff --git a/front/src/i18n/de-DE/woka.ts b/front/src/i18n/de-DE/woka.ts index a571feea..640cd756 100644 --- a/front/src/i18n/de-DE/woka.ts +++ b/front/src/i18n/de-DE/woka.ts @@ -1,6 +1,6 @@ -import type { BaseTranslation } from "../i18n-types"; +import type { Translation } from "../i18n-types"; -const woka: BaseTranslation = { +const woka: NonNullable = { customWoka: { title: "Dein WOKA bearbeiten", navigation: { From c4e5a413764b8a51de5cc3d8939800a927b30cd2 Mon Sep 17 00:00:00 2001 From: Lurkars Date: Wed, 26 Jan 2022 12:34:51 +0100 Subject: [PATCH 26/60] add emoji translation --- front/src/i18n/de-DE/emoji.ts | 21 +++++++++++++++++++++ front/src/i18n/de-DE/index.ts | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 front/src/i18n/de-DE/emoji.ts diff --git a/front/src/i18n/de-DE/emoji.ts b/front/src/i18n/de-DE/emoji.ts new file mode 100644 index 00000000..01ba9311 --- /dev/null +++ b/front/src/i18n/de-DE/emoji.ts @@ -0,0 +1,21 @@ +import type { Translation } from "../i18n-types"; + +const emoji: NonNullable = { + search: "Emojis suchen...", + categories: { + recents: "Letzte Emojis", + smileys: "Smileys & Emotionen", + people: "Menschen", + animals: "Tiere & Natur", + food: "Essen & Trinken", + activities: "Aktivitäten", + travel: "Reise & Orte", + objects: "Objekte", + symbols: "Symbole", + flags: "Flaggen", + custom: "Benutzerdefinier", + }, + notFound: "Keine Emojis gefunden", +}; + +export default emoji; diff --git a/front/src/i18n/de-DE/index.ts b/front/src/i18n/de-DE/index.ts index f07a5b01..ab628a4d 100644 --- a/front/src/i18n/de-DE/index.ts +++ b/front/src/i18n/de-DE/index.ts @@ -4,6 +4,7 @@ import audio from "./audio"; import camera from "./camera"; import chat from "./chat"; import companion from "./companion"; +import emoji from "./emoji"; import error from "./error"; import follow from "./follow"; import login from "./login"; @@ -27,6 +28,7 @@ const de_DE: Translation = { menu, report, warning, + emoji, }; export default de_DE; From 2252bc79fff708324aa124f15a30a7cf3971b324 Mon Sep 17 00:00:00 2001 From: Lurkars Date: Wed, 26 Jan 2022 12:39:20 +0100 Subject: [PATCH 27/60] prettier --- front/src/i18n/de-DE/error.ts | 9 ++++++--- front/src/i18n/de-DE/menu.ts | 3 ++- front/src/i18n/de-DE/warning.ts | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/front/src/i18n/de-DE/error.ts b/front/src/i18n/de-DE/error.ts index 6bfdfc35..372c3974 100644 --- a/front/src/i18n/de-DE/error.ts +++ b/front/src/i18n/de-DE/error.ts @@ -4,15 +4,18 @@ const error: NonNullable = { accessLink: { title: "Ungültiger Zugangslink", subTitle: "Karte konnte nicht gefunden werden. Prüfe bitte deinen Zugangslink.", - details: "Für weitere Information kannst du die Administratoren kontaktieren oder melde dich bei uns unter: hello@workadventu.re", + details: + "Für weitere Information kannst du die Administratoren kontaktieren oder melde dich bei uns unter: hello@workadventu.re", }, connectionRejected: { title: "Verbindungen zurückgewiesen", subTitle: "Du kannst diese Welt nicht betreten. Versuche es später noch einmal {error}.", - details: "Für weitere Information kannst du die Administratoren kontaktieren oder melde dich bei uns unter: hello@workadventu.re", + details: + "Für weitere Information kannst du die Administratoren kontaktieren oder melde dich bei uns unter: hello@workadventu.re", }, connectionRetry: { - unableConnect: "Es konnte keine Verbindung zu WorkAdventure erstellt werden. Bist du mit dem Internet verbunden?", + unableConnect: + "Es konnte keine Verbindung zu WorkAdventure erstellt werden. Bist du mit dem Internet verbunden?", }, error: "Fehler", }; diff --git a/front/src/i18n/de-DE/menu.ts b/front/src/i18n/de-DE/menu.ts index 8a0c0cf7..5fb7882d 100644 --- a/front/src/i18n/de-DE/menu.ts +++ b/front/src/i18n/de-DE/menu.ts @@ -90,7 +90,8 @@ const menu: NonNullable = { }, createMap: { title: "Eigene Karte erstellen ", - description: "Du kannst auch deine eigene Karte erstellen. Folge dazu unserer Schritt-für-Schritt Anleitung.", + description: + "Du kannst auch deine eigene Karte erstellen. Folge dazu unserer Schritt-für-Schritt Anleitung.", }, }, about: { diff --git a/front/src/i18n/de-DE/warning.ts b/front/src/i18n/de-DE/warning.ts index 64efc70b..fb9acd5f 100644 --- a/front/src/i18n/de-DE/warning.ts +++ b/front/src/i18n/de-DE/warning.ts @@ -7,7 +7,8 @@ const warning: NonNullable = { limit: "Diese Welt erreicht bald die maximale Kapazität!", accessDenied: { camera: "Zugriff auf die Kamera verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.", - screenSharing: "Zugriff auf die Bildschirmfreigabe verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.", + screenSharing: + "Zugriff auf die Bildschirmfreigabe verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.", }, importantMessage: "Wichtige Nachricht", connectionLost: "Verbindungen unterbrochen. Wiederverbinden...", From fe031579d910ad4c5e19b86d34af291e492802ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 27 Jan 2022 10:24:19 +0100 Subject: [PATCH 28/60] Fixing typo --- front/src/i18n/fr-FR/camera.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/i18n/fr-FR/camera.ts b/front/src/i18n/fr-FR/camera.ts index 4f2c10fa..18697f1a 100644 --- a/front/src/i18n/fr-FR/camera.ts +++ b/front/src/i18n/fr-FR/camera.ts @@ -3,7 +3,7 @@ import type { Translation } from "../i18n-types"; const camera: NonNullable = { enable: { title: "Allumez votre caméra et votre microphone", - start: "C'est partie!", + start: "C'est parti!", }, help: { title: "Accès à la caméra / au microphone nécessaire", From d18e9162b96003970a40a41eeefbbcd46ae7d91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 27 Jan 2022 15:51:23 +0100 Subject: [PATCH 29/60] Generating i18n files before build --- front/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/Dockerfile b/front/Dockerfile index 4469bdaf..14914ebf 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -12,7 +12,7 @@ RUN cp -r ../messages/ts-proto-generated/protos/* src/Messages/ts-proto-generate RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' src/Messages/ts-proto-generated/messages.ts RUN cp -r ../messages/JsonMessages/* src/Messages/JsonMessages -RUN yarn install && yarn build +RUN yarn install && yarn run typesafe-i18n && yarn build # Removing the iframe.html file from the final image as this adds a XSS attack. # iframe.html is only in dev mode to circumvent a limitation From d7121d4192586a27c49142dfd06fa3e862923229 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Tue, 28 Dec 2021 11:28:53 +0100 Subject: [PATCH 30/60] Add fonts & png files built to the gitignore file --- front/dist/.gitignore | 2 ++ front/dist/index.ejs | 76 +++++-------------------------------------- 2 files changed, 11 insertions(+), 67 deletions(-) diff --git a/front/dist/.gitignore b/front/dist/.gitignore index 0561f7a5..bc766e57 100644 --- a/front/dist/.gitignore +++ b/front/dist/.gitignore @@ -1,4 +1,6 @@ index.html /js/ +/fonts/ style.*.css !env-config.template.js +*.png diff --git a/front/dist/index.ejs b/front/dist/index.ejs index 29b8e6cb..07732877 100644 --- a/front/dist/index.ejs +++ b/front/dist/index.ejs @@ -37,65 +37,9 @@
-
-
-
-
- - -
-
-
- -
-
-
-
- -
-
- - -
-
-
- -
-
-
- -
-
- - -
-
-
- -
-
-
- -
-
- - -
-
-
-
-
-
-
-
-
- - -
+
- - -
-
- - From 56d8f62dd07e575c30fc39b01ef1c45fd65f8b44 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Wed, 5 Jan 2022 09:54:20 +0100 Subject: [PATCH 31/60] Add a new test map for co-website with trigger property --- .../CoWebsite/cowebsite_property_trigger.json | 194 ++++++++++++++++++ maps/tests/index.html | 8 + 2 files changed, 202 insertions(+) create mode 100644 maps/tests/CoWebsite/cowebsite_property_trigger.json diff --git a/maps/tests/CoWebsite/cowebsite_property_trigger.json b/maps/tests/CoWebsite/cowebsite_property_trigger.json new file mode 100644 index 00000000..183fad3a --- /dev/null +++ b/maps/tests/CoWebsite/cowebsite_property_trigger.json @@ -0,0 +1,194 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34], + "height":10, + "id":8, + "name":"firstCoWebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/workadventu.re" + }, + { + "name":"openWebsiteClosable", + "type":"bool", + "value":true + }, + { + "name":"openWebsiteTrigger", + "type":"string", + "value":"onaction" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23], + "height":10, + "id":11, + "name":"secondCoWebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/wikipedia.org" + }, + { + "name":"openWebsiteClosable", + "type":"bool", + "value":true + }, + { + "name":"openWebsiteTrigger", + "type":"string", + "value":"onaction" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12], + "height":10, + "id":10, + "name":"terCoWebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/www.typescriptlang.org\/" + }, + { + "name":"openWebsiteClosable", + "type":"bool", + "value":true + }, + { + "name":"openWebsiteTrigger", + "type":"string", + "value":"onaction" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 111, 111, 111, 111, 111, 0, 0, 0, 0, 0, 111, 111, 111, 111, 111, 0, 0, 0, 0, 0, 111, 111, 111, 111, 111, 0, 0, 0, 0, 0, 111, 111, 111, 111, 111], + "height":10, + "id":12, + "name":"quatCoWebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/svelte.dev\/" + }, + { + "name":"openWebsiteClosable", + "type":"bool", + "value":true + }, + { + "name":"openWebsiteTrigger", + "type":"string", + "value":"onaction" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":141, + "id":1, + "name":"Tests", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on the blue carpet, an iframe open, walk on the white carpet another one to open another one\nResult:\n2 co-websites must be opened\n\nTest:\nGo outside of carpets\nResult:\nAll co-websites must disapeared", + "wrap":true + }, + "type":"", + "visible":true, + "width":316.770833333333, + "x":0.28125, + "y":187.833333333333 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":13, + "nextobjectid":3, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index d27d7804..b4391fee 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -326,6 +326,14 @@ Open co-websites by map property + + + Success Failure Pending + + + Open co-websites by map property trigger + + Success Failure Pending From 9695064e8250ee564f601a135c2a0a9830307bd2 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Wed, 5 Jan 2022 09:56:10 +0100 Subject: [PATCH 32/60] Implement breakpoints utils on SCSS and JS --- front/src/Utils/BreakpointsUtils.ts | 112 ++++++++++++++++++++++++++++ front/style/breakpoints.scss | 102 +++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 front/src/Utils/BreakpointsUtils.ts create mode 100644 front/style/breakpoints.scss diff --git a/front/src/Utils/BreakpointsUtils.ts b/front/src/Utils/BreakpointsUtils.ts new file mode 100644 index 00000000..3cefb37d --- /dev/null +++ b/front/src/Utils/BreakpointsUtils.ts @@ -0,0 +1,112 @@ +type InternalBreakpoint = { + beforeBreakpoint: InternalBreakpoint | undefined; + nextBreakpoint: InternalBreakpoint | undefined; + pixels: number; +}; + +function generateBreakpointsMap(): Map { + // If is changed don't forget to also change it on SASS. + const breakpoints: { [key: string]: number } = { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + xxl: 1400, + }; + + let beforeBreakpoint: InternalBreakpoint | undefined; + let beforeBreakpointTag: string | undefined; + const mapRender = new Map(); + + for (const breakpoint in breakpoints) { + const newBreakpoint = { + beforeBreakpoint: beforeBreakpoint, + nextBreakpoint: undefined, + pixels: breakpoints[breakpoint], + }; + + if (beforeBreakpointTag && beforeBreakpoint) { + beforeBreakpoint.nextBreakpoint = newBreakpoint; + mapRender.set(beforeBreakpointTag, beforeBreakpoint); + } + + mapRender.set(breakpoint, { + beforeBreakpoint: beforeBreakpoint, + nextBreakpoint: undefined, + pixels: breakpoints[breakpoint], + }); + + beforeBreakpointTag = breakpoint; + beforeBreakpoint = newBreakpoint; + } + + return mapRender; +} + +const breakpoints = generateBreakpointsMap(); + +export type Breakpoint = "xs" | "sm" | "md" | "lg" | "xl" | "xxl"; + +export function isMediaBreakpointUp(breakpoint: Breakpoint): boolean { + if (breakpoint === "xxl") { + return true; + } + + const breakpointObject = breakpoints.get(breakpoint); + + if (!breakpointObject) { + throw new Error(`Unknown breakpoint: ${breakpoint}`); + } + + if (!breakpointObject.nextBreakpoint) { + return false; + } + + return breakpointObject.nextBreakpoint.pixels - 1 >= window.innerWidth; +} + +export function isMediaBreakpointDown(breakpoint: Breakpoint): boolean { + if (breakpoint === "xs") { + return true; + } + + const breakpointObject = breakpoints.get(breakpoint); + + if (!breakpointObject) { + throw new Error(`Unknown breakpoint: ${breakpoint}`); + } + + return breakpointObject.pixels <= window.innerWidth; +} + +export function isMediaBreakpointOnly(breakpoint: Breakpoint): boolean { + const breakpointObject = breakpoints.get(breakpoint); + + if (!breakpointObject) { + throw new Error(`Unknown breakpoint: ${breakpoint}`); + } + + return ( + breakpointObject.pixels <= window.innerWidth && + (!breakpointObject.nextBreakpoint || breakpointObject.nextBreakpoint.pixels - 1 >= window.innerWidth) + ); +} + +export function isMediaBreakpointBetween(startBreakpoint: Breakpoint, endBreakpoint: Breakpoint): boolean { + const startBreakpointObject = breakpoints.get(startBreakpoint); + const endBreakpointObject = breakpoints.get(endBreakpoint); + + if (!startBreakpointObject) { + throw new Error(`Unknown start breakpoint: ${startBreakpointObject}`); + } + + if (!endBreakpointObject) { + throw new Error(`Unknown end breakpoint: ${endBreakpointObject}`); + } + + return ( + startBreakpointObject.pixels <= innerWidth && + (!endBreakpointObject.nextBreakpoint || endBreakpointObject.nextBreakpoint.pixels - 1 >= window.innerWidth) + ); +} diff --git a/front/style/breakpoints.scss b/front/style/breakpoints.scss new file mode 100644 index 00000000..8b988d66 --- /dev/null +++ b/front/style/breakpoints.scss @@ -0,0 +1,102 @@ +@use "sass:map"; + +// If you modify this breakpoints don't forget to change it also in TS utils. +$grid-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px, + xxl: 1400px +); + +@function get-upper-breakpoint($breakpoint) { + @return map-get(( + xs: map.get($grid-breakpoints, "sm"), + sm: map.get($grid-breakpoints, "md"), + md: map.get($grid-breakpoints, "lg"), + lg: map.get($grid-breakpoints, "xl"), + xl: map.get($grid-breakpoints, "xxl"), + xxl: null, + ), $breakpoint); +} + +@function get-under-breakpoint($breakpoint) { + @return map-get(( + xs: null, + sm: map.get($grid-breakpoints, "xs"), + md: map.get($grid-breakpoints, "sm"), + lg: map.get($grid-breakpoints, "md"), + xl: map.get($grid-breakpoints, "lg"), + xxl: map.get($grid-breakpoints, "xl"), + ), $breakpoint); +} + +@mixin media-breakpoint-up($upTo) { + @if $upTo == 'xxl' { + @media only screen { + @content; + } + } @else { + $value: get-upper-breakpoint($upTo); + + @if $value != null { + $value: $value - 1px; + @media only screen and (max-width: $value) { + @content; + } + } + } +} + +@mixin media-breakpoint-down($downFrom) { + @if $downFrom == 'xs' { + @media only screen { + @content; + } + } @else { + $value: map.get($grid-breakpoints, $downFrom); + + @if $value != null { + @media only screen and (min-width: $value) { + @content; + } + } + } +} + +@mixin media-breakpoint-only($only) { + $maxValue: get-upper-breakpoint($only); + $minValue: map.get($grid-breakpoints, $only); + + @if $minValue == null { + + } @else if $maxValue != null { + $maxValue: $maxValue - 1px; + @media only screen and (min-width: $minValue) and (max-width: $maxValue) { + @content; + } + } @else { + @media only screen and (min-width: $minValue) { + @content; + } + } +} + +@mixin media-breakpoint-between($start, $end) { + $maxValue: get-upper-breakpoint($end); + $minValue: map.get($grid-breakpoints, $start); + + @if $minValue == null { + + } @else if $maxValue != null { + $maxValue: $maxValue - 1px; + @media only screen and (min-width: $minValue) and (max-width: $maxValue) { + @content; + } + } @else { + @media only screen and (min-width: $minValue) { + @content; + } + } +} From 5f1dd09cb96f835c261b7af56c4a252cdae8f2a4 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Wed, 5 Jan 2022 09:58:57 +0100 Subject: [PATCH 33/60] Implement minimizer for css/scss --- front/package.json | 1 + front/webpack.config.ts | 33 +-- front/yarn.lock | 549 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 555 insertions(+), 28 deletions(-) diff --git a/front/package.json b/front/package.json index 564e1485..9a17f4a0 100644 --- a/front/package.json +++ b/front/package.json @@ -15,6 +15,7 @@ "@typescript-eslint/eslint-plugin": "^5.6.0", "@typescript-eslint/parser": "^5.6.0", "css-loader": "^5.2.4", + "css-minimizer-webpack-plugin": "^3.3.1", "eslint": "^8.4.1", "eslint-plugin-svelte3": "^3.2.1", "fork-ts-checker-webpack-plugin": "^6.5.0", diff --git a/front/webpack.config.ts b/front/webpack.config.ts index e5b338fc..3185b0a6 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -2,6 +2,7 @@ import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import fs from 'fs/promises'; import HtmlWebpackPlugin from "html-webpack-plugin"; import MiniCssExtractPlugin from "mini-css-extract-plugin"; +import CssMinimizerPlugin from "css-minimizer-webpack-plugin"; import NodePolyfillPlugin from "node-polyfill-webpack-plugin"; import path from "path"; import sveltePreprocess from "svelte-preprocess"; @@ -79,33 +80,9 @@ module.exports = { }, }, { - test: /\.scss$/, + test: /\.(sc|c)ss$/, exclude: /node_modules/, - use: [ - MiniCssExtractPlugin.loader, - { - loader: "css-loader", - options: { - //url: false, - sourceMap: true, - }, - }, - "sass-loader", - ], - }, - { - test: /\.css$/, - exclude: /node_modules/, - use: [ - MiniCssExtractPlugin.loader, - { - loader: "css-loader", - options: { - //url: false, - sourceMap: true, - }, - }, - ], + use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], }, { test: /\.(html|svelte)$/, @@ -185,6 +162,10 @@ module.exports = { extensions: [".tsx", ".ts", ".js", ".svelte"], mainFields: ["svelte", "browser", "module", "main"], }, + optimization: { + minimize: true, + minimizer: [new CssMinimizerPlugin(), "..."], + }, output: { filename: (pathData) => { // Add a content hash only for the main bundle. diff --git a/front/yarn.lock b/front/yarn.lock index 1758ebd3..89a8a568 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -208,6 +208,11 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0.tgz#b7395688a79403c6df8d8bb8d81deb8222519853" integrity sha512-urtgLzE4EDMAYQHYdkgC0Ei9QvLajodK1ntg71bGn0Pm84QUpaqpPDfHRU+i6jLeteyC7kWwa5O5W1m/jrjGXA== +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -774,11 +779,25 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + ajv@^6.1.0, ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -789,6 +808,21 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.8.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" + integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + ansi-colors@^3.0.0: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" @@ -1212,6 +1246,17 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" +browserslist@^4.0.0, browserslist@^4.16.0, browserslist@^4.16.6: + version "4.19.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + browserslist@^4.14.5: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" @@ -1307,6 +1352,21 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286: + version "1.0.30001292" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz#4a55f61c06abc9595965cfd77897dc7bc1cdc456" + integrity sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw== + caniuse-lite@^1.0.30001219: version "1.0.30001228" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa" @@ -1483,6 +1543,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colord@^2.9.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" + integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== + colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" @@ -1692,6 +1757,13 @@ crypto-browserify@^3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-declaration-sorter@^6.0.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz#e9852e4cf940ba79f509d9425b137d1f94438dc2" + integrity sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA== + dependencies: + timsort "^0.3.0" + css-loader@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.4.tgz#e985dcbce339812cb6104ef3670f08f9893a1536" @@ -1709,6 +1781,18 @@ css-loader@^5.2.4: schema-utils "^3.0.0" semver "^7.3.5" +css-minimizer-webpack-plugin@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.1.tgz#5afc4507a4ec13dd223f043cda8953ee0bf6ecfa" + integrity sha512-SHA7Hu/EiF0dOwdmV2+agvqYpG+ljlUa7Dvn1AVOmSH3N8KOERoaM9lGpstz9nGsoTjANGyUXdrxl/EwdMScRg== + dependencies: + cssnano "^5.0.6" + jest-worker "^27.0.2" + postcss "^8.3.5" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + css-select@^2.0.2: version "2.1.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" @@ -1719,16 +1803,96 @@ css-select@^2.0.2: domutils "^1.7.0" nth-check "^1.0.2" +css-select@^4.1.3: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssnano-preset-default@^5.1.9: + version "5.1.9" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" + integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== + dependencies: + css-declaration-sorter "^6.0.3" + cssnano-utils "^2.0.1" + postcss-calc "^8.0.0" + postcss-colormin "^5.2.2" + postcss-convert-values "^5.0.2" + postcss-discard-comments "^5.0.1" + postcss-discard-duplicates "^5.0.1" + postcss-discard-empty "^5.0.1" + postcss-discard-overridden "^5.0.1" + postcss-merge-longhand "^5.0.4" + postcss-merge-rules "^5.0.3" + postcss-minify-font-values "^5.0.1" + postcss-minify-gradients "^5.0.3" + postcss-minify-params "^5.0.2" + postcss-minify-selectors "^5.1.0" + postcss-normalize-charset "^5.0.1" + postcss-normalize-display-values "^5.0.1" + postcss-normalize-positions "^5.0.1" + postcss-normalize-repeat-style "^5.0.1" + postcss-normalize-string "^5.0.1" + postcss-normalize-timing-functions "^5.0.1" + postcss-normalize-unicode "^5.0.1" + postcss-normalize-url "^5.0.4" + postcss-normalize-whitespace "^5.0.1" + postcss-ordered-values "^5.0.2" + postcss-reduce-initial "^5.0.2" + postcss-reduce-transforms "^5.0.1" + postcss-svgo "^5.0.3" + postcss-unique-selectors "^5.0.2" + +cssnano-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" + integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== + +cssnano@^5.0.6: + version "5.0.14" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" + integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== + dependencies: + cssnano-preset-default "^5.1.9" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + dataloader@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" @@ -1952,6 +2116,15 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + domain-browser@^4.19.0: version "4.19.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1" @@ -1962,7 +2135,7 @@ domelementtype@1, domelementtype@^1.3.1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@^2.0.1: +domelementtype@^2.0.1, domelementtype@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== @@ -1974,6 +2147,13 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domhandler@^4.2.0, domhandler@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + dependencies: + domelementtype "^2.2.0" + domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -1982,6 +2162,15 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -2007,6 +2196,11 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.728.tgz#dbedd6373f595ae10a13d146b66bece4c1afa5bd" integrity sha512-SHv4ziXruBpb1Nz4aTuqEHBYi/9GNCJMYIJgDEXrp/2V01nFXMNFUTli5Z85f5ivSkioLilQatqBYFB44wNJrA== +electron-to-chromium@^1.4.17: + version "1.4.28" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.28.tgz#fef0e92e281df6d568f482d8d53c34ca5374de48" + integrity sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg== + elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -3541,6 +3735,15 @@ jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" +jest-worker@^27.0.2: + version "27.4.5" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242" + integrity sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -3568,6 +3771,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -3659,6 +3867,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lilconfig@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -3747,11 +3960,21 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -3830,6 +4053,11 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -4036,6 +4264,11 @@ nanoid@^3.1.23: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== +nanoid@^3.1.30: + version "3.1.30" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" + integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -4121,6 +4354,11 @@ node-releases@^1.1.71: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -4143,6 +4381,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-run-all@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" @@ -4179,6 +4422,13 @@ nth-check@^1.0.2: dependencies: boolbase "~1.0.0" +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + object-assign@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4549,6 +4799,11 @@ phaser@^3.54.0: eventemitter3 "^4.0.7" path "^0.12.7" +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" @@ -4621,6 +4876,103 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss-calc@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" + integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== + dependencies: + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-colormin@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" + integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" + integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== + dependencies: + postcss-value-parser "^4.1.0" + +postcss-discard-comments@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" + integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== + +postcss-discard-duplicates@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" + integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== + +postcss-discard-empty@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" + integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== + +postcss-discard-overridden@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" + integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== + +postcss-merge-longhand@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz#41f4f3270282ea1a145ece078b7679f0cef21c32" + integrity sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw== + dependencies: + postcss-value-parser "^4.1.0" + stylehacks "^5.0.1" + +postcss-merge-rules@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" + integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + cssnano-utils "^2.0.1" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" + integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== + dependencies: + postcss-value-parser "^4.1.0" + +postcss-minify-gradients@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" + integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== + dependencies: + colord "^2.9.1" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + +postcss-minify-params@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" + integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== + dependencies: + alphanum-sort "^1.0.2" + browserslist "^4.16.6" + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + +postcss-minify-selectors@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" + integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== + dependencies: + alphanum-sort "^1.0.2" + postcss-selector-parser "^6.0.5" + postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" @@ -4649,6 +5001,96 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" +postcss-normalize-charset@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" + integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== + +postcss-normalize-display-values@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" + integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== + dependencies: + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + +postcss-normalize-positions@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" + integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== + dependencies: + postcss-value-parser "^4.1.0" + +postcss-normalize-repeat-style@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" + integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== + dependencies: + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + +postcss-normalize-string@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" + integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== + dependencies: + postcss-value-parser "^4.1.0" + +postcss-normalize-timing-functions@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" + integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== + dependencies: + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + +postcss-normalize-unicode@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" + integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== + dependencies: + browserslist "^4.16.0" + postcss-value-parser "^4.1.0" + +postcss-normalize-url@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz#3b0322c425e31dd275174d0d5db0e466f50810fb" + integrity sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" + integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== + dependencies: + postcss-value-parser "^4.1.0" + +postcss-ordered-values@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" + integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== + dependencies: + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + +postcss-reduce-initial@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz#fa424ce8aa88a89bc0b6d0f94871b24abe94c048" + integrity sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" + integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== + dependencies: + cssnano-utils "^2.0.1" + postcss-value-parser "^4.1.0" + postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: version "6.0.6" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" @@ -4657,6 +5099,35 @@ postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.0.5: + version "6.0.8" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.8.tgz#f023ed7a9ea736cd7ef70342996e8e78645a7914" + integrity sha512-D5PG53d209Z1Uhcc0qAZ5U3t5HagH3cxu+WLZ22jt3gLUpXM4eXXfiO14jiDWST3NNooX/E8wISfOhZ9eIjGTQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" + integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== + dependencies: + postcss-value-parser "^4.1.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz#5d6893daf534ae52626708e0d62250890108c0c1" + integrity sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA== + dependencies: + alphanum-sort "^1.0.2" + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" @@ -4671,6 +5142,15 @@ postcss@^8.2.10: nanoid "^3.1.23" source-map "^0.6.1" +postcss@^8.3.5: + version "8.4.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" + integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== + dependencies: + nanoid "^3.1.30" + picocolors "^1.0.0" + source-map-js "^1.0.1" + posthog-js@^1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.14.1.tgz#68553b9074c686784b994b3f433ad035b241deaa" @@ -4998,6 +5478,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -5190,6 +5675,16 @@ schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -5250,6 +5745,13 @@ serialize-javascript@^5.0.1: dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -5470,6 +5972,11 @@ source-list-map@^2.0.0, source-list-map@^2.0.1: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== +source-map-js@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" + integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -5565,6 +6072,11 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + standardized-audio-context@^25.2.4: version "25.2.4" resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.2.4.tgz#d64dbdd70615171ec90d1b7151a0d945af94cf3d" @@ -5731,6 +6243,14 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +stylehacks@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" + integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== + dependencies: + browserslist "^4.16.0" + postcss-selector-parser "^6.0.4" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -5752,6 +6272,13 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + svelte-check@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-2.1.0.tgz#3ee8ad86068256346ebca862bbee8417a757fc53" @@ -5801,6 +6328,19 @@ svelte@^3.38.2: resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.38.2.tgz#55e5c681f793ae349b5cc2fe58e5782af4275ef5" integrity sha512-q5Dq0/QHh4BLJyEVWGe7Cej5NWs040LWjMbicBGZ+3qpFWJ1YObRmUDZKbbovddLC9WW7THTj3kYbTOFmU9fbg== +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + tabbable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" @@ -5868,6 +6408,11 @@ timers-browserify@^2.0.12: dependencies: setimmediate "^1.0.4" +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + tiny-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" @@ -6496,7 +7041,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From 0bf1acfefbf20240c8fb79dbfac8ffdcbecf4c5d Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Wed, 5 Jan 2022 10:27:40 +0100 Subject: [PATCH 34/60] Improve game overlay UI --- front/src/Components/App.svelte | 196 +++---------- .../AudioManager/AudioManager.svelte | 7 +- front/src/Components/CameraControls.svelte | 220 +++++++++++--- front/src/Components/Chat/Chat.svelte | 4 +- .../CustomCharacterScene.svelte | 4 +- .../src/Components/EmoteMenu/EmoteMenu.svelte | 6 +- .../EnableCamera/EnableCameraScene.svelte | 4 +- .../Components/FollowMenu/FollowButton.svelte | 33 +++ .../Components/FollowMenu/FollowMenu.svelte | 76 ++--- .../HelpCameraSettingsPopup.svelte | 7 +- .../LayoutActionManager.svelte} | 8 +- front/src/Components/MainLayout.svelte | 163 +++++++++++ .../Components/Menu/AboutRoomSubMenu.svelte | 4 +- .../Menu/GlobalMessagesSubMenu.svelte | 4 +- front/src/Components/Menu/GuestSubMenu.svelte | 4 +- front/src/Components/Menu/Menu.svelte | 18 +- front/src/Components/Menu/MenuIcon.svelte | 63 ++-- .../src/Components/Menu/ProfileSubMenu.svelte | 4 +- .../Components/Menu/SettingsSubMenu.svelte | 24 +- front/src/Components/MyCamera.svelte | 80 ++++- .../Components/ReportMenu/ReportMenu.svelte | 21 +- .../SelectCompanionScene.svelte | 4 +- .../Components/TypeMessage/BanMessage.svelte | 16 +- .../Components/TypeMessage/TextMessage.svelte | 11 +- front/src/Components/UI/ErrorDialog.svelte | 8 +- .../Video/LocalStreamMediaBox.svelte | 22 +- front/src/Components/Video/MediaBox.svelte | 59 +++- .../Video/PresentationLayout.svelte | 26 -- .../Video/ScreenSharingMediaBox.svelte | 23 +- .../src/Components/Video/VideoMediaBox.svelte | 83 ++++-- .../src/Components/Video/VideoOverlay.svelte | 12 +- .../src/Components/VisitCard/VisitCard.svelte | 1 + .../WarningContainer/WarningContainer.svelte | 16 +- front/src/Components/Woka/Woka.svelte | 13 +- .../SelectCharacterScene.svelte | 4 +- front/src/Enum/EnvironmentVariable.ts | 2 - front/src/Phaser/Components/SoundMeter.ts | 2 - front/src/Phaser/Game/GameMapProperties.ts | 1 - front/src/Phaser/Game/GameScene.ts | 67 +++-- front/src/Phaser/Login/CustomizeScene.ts | 6 +- .../src/Phaser/Login/SelectCharacterScene.ts | 6 +- .../src/Phaser/Login/SelectCompanionScene.ts | 4 +- front/src/Phaser/Services/WaScaleManager.ts | 12 +- front/src/Stores/BiggestAvailableAreaStore.ts | 4 +- .../src/Stores/GameOverlayStoreVisibility.ts | 17 -- front/src/Stores/LayoutManagerStore.ts | 2 +- front/src/Stores/MediaStore.ts | 8 +- front/src/Stores/MyCameraStoreVisibility.ts | 8 + front/src/Stores/ScreenSharingStore.ts | 8 +- front/src/Stores/StreamableCollectionStore.ts | 5 +- front/src/WebRtc/HtmlUtils.ts | 5 + front/src/WebRtc/MediaManager.ts | 51 +--- front/src/WebRtc/ScreenSharingPeer.ts | 17 +- front/src/WebRtc/SimplePeer.ts | 2 +- front/src/WebRtc/VideoPeer.ts | 4 +- front/src/index.ts | 8 +- .../style/TextGlobalMessageSvelte-Style.scss | 2 +- front/style/cowebsite-mobile.scss | 136 --------- front/style/fonts.scss | 5 - front/style/fonts/TwemojiMozilla.ttf | Bin 1324332 -> 0 bytes front/style/index.scss | 6 +- front/style/mobile-style.scss | 48 --- front/style/style.scss | 276 ++---------------- 63 files changed, 976 insertions(+), 984 deletions(-) create mode 100644 front/src/Components/FollowMenu/FollowButton.svelte rename front/src/Components/{LayoutManager/LayoutManager.svelte => LayoutActionManager/LayoutActionManager.svelte} (91%) create mode 100644 front/src/Components/MainLayout.svelte delete mode 100644 front/src/Components/Video/PresentationLayout.svelte delete mode 100644 front/src/Stores/GameOverlayStoreVisibility.ts create mode 100644 front/src/Stores/MyCameraStoreVisibility.ts delete mode 100644 front/style/cowebsite-mobile.scss delete mode 100644 front/style/fonts/TwemojiMozilla.ttf delete mode 100644 front/style/mobile-style.scss diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index a1277ed2..620884cf 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -1,166 +1,52 @@ -
- {#if $loginSceneVisibleStore} -
- -
- {/if} - {#if $selectCharacterSceneVisibleStore} -
- -
- {/if} - {#if $customCharacterSceneVisibleStore} -
- -
- {/if} - {#if $selectCompanionSceneVisibleStore} -
- -
- {/if} - {#if $enableCameraSceneVisibilityStore} -
- -
- {/if} - {#if $banMessageStore.length > 0} -
- -
- {:else if $textMessageStore.length > 0} -
- -
- {/if} - {#if $soundPlayingStore} -
- -
- {/if} - {#if $audioManagerVisibilityStore} -
- -
- {/if} - {#if $layoutManagerVisibilityStore} -
- -
- {/if} - {#if $showReportScreenStore !== userReportEmpty} -
- -
- {/if} - {#if $followStateStore !== "off" || $peerStore.size > 0} -
- -
- {/if} - {#if $menuIconVisiblilityStore} -
- -
- {/if} - {#if $menuVisiblilityStore} -
- -
- {/if} - {#if $emoteMenuStore} -
- -
- {/if} - {#if $gameOverlayVisibilityStore} -
- - - -
- {/if} - {#if $helpCameraSettingsVisibleStore} -
- -
- {/if} - {#if $showLimitRoomModalStore} -
- -
- {/if} - {#if $showShareLinkMapModalStore} -
- -
- {/if} - {#if $requestVisitCardsStore} - - {/if} - {#if $errorStore.length > 0} -
- -
- {/if} +{#if $errorStore.length > 0} +
+ +
+{:else if $loginSceneVisibleStore} +
+ +
+{:else if $selectCharacterSceneVisibleStore} +
+ +
+{:else if $customCharacterSceneVisibleStore} +
+ +
+{:else if $selectCompanionSceneVisibleStore} +
+ +
+{:else if $enableCameraSceneVisibilityStore} +
+ +
+{:else} + + {#if $chatVisibilityStore} {/if} - {#if $warningContainerStore} - - {/if} -
+{/if} diff --git a/front/src/Components/AudioManager/AudioManager.svelte b/front/src/Components/AudioManager/AudioManager.svelte index 3385d6da..87b949c7 100644 --- a/front/src/Components/AudioManager/AudioManager.svelte +++ b/front/src/Components/AudioManager/AudioManager.svelte @@ -157,13 +157,16 @@ diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte index c4756a36..cd9b90b5 100644 --- a/front/src/Components/Chat/Chat.svelte +++ b/front/src/Components/Chat/Chat.svelte @@ -43,7 +43,7 @@
diff --git a/front/src/Components/ReportMenu/ReportMenu.svelte b/front/src/Components/ReportMenu/ReportMenu.svelte index 3934f21b..caf1ff76 100644 --- a/front/src/Components/ReportMenu/ReportMenu.svelte +++ b/front/src/Components/ReportMenu/ReportMenu.svelte @@ -108,12 +108,16 @@ pointer-events: auto; background-color: #333333; color: whitesmoke; - - position: relative; + z-index: 300; + position: absolute; height: 70vh; width: 50vw; - top: 10vh; - margin: auto; + top: 4%; + + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; section.report-menu-title { display: grid; @@ -137,13 +141,4 @@ display: none; } } - - @media only screen and (max-width: 800px) { - div.report-menu-main { - top: 21vh; - height: 60vh; - width: 100vw; - font-size: 0.5em; - } - } diff --git a/front/src/Components/SelectCompanion/SelectCompanionScene.svelte b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte index dd14914b..aadc9ed6 100644 --- a/front/src/Components/SelectCompanion/SelectCompanionScene.svelte +++ b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte @@ -47,6 +47,8 @@ diff --git a/front/src/Components/Video/PresentationLayout.svelte b/front/src/Components/Video/PresentationLayout.svelte deleted file mode 100644 index 82a7c989..00000000 --- a/front/src/Components/Video/PresentationLayout.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - -
- {#if $videoFocusStore} - {#key $videoFocusStore.uniqueId} - - {/key} - {/if} -
- diff --git a/front/src/Components/Video/ScreenSharingMediaBox.svelte b/front/src/Components/Video/ScreenSharingMediaBox.svelte index b6a227ef..022770bb 100644 --- a/front/src/Components/Video/ScreenSharingMediaBox.svelte +++ b/front/src/Components/Video/ScreenSharingMediaBox.svelte @@ -1,12 +1,26 @@
@@ -20,7 +34,12 @@ {name} {:else} -
diff --git a/front/src/Components/Video/VideoMediaBox.svelte b/front/src/Components/Video/VideoMediaBox.svelte index aec1b0ce..a724f704 100644 --- a/front/src/Components/Video/VideoMediaBox.svelte +++ b/front/src/Components/Video/VideoMediaBox.svelte @@ -4,11 +4,17 @@ import microphoneCloseImg from "../images/microphone-close.svg"; import reportImg from "./images/report.svg"; import blockSignImg from "./images/blockSign.svg"; - import { videoFocusStore } from "../../Stores/VideoFocusStore"; import { showReportScreenStore } from "../../Stores/ShowReportScreenStore"; import { getColorByString, srcObject } from "./utils"; + import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; + import type { EmbedScreen } from "../../Stores/EmbedScreensStore"; + import type { Streamable } from "../../Stores/StreamableCollectionStore"; import Woka from "../Woka/Woka.svelte"; + import { onMount } from "svelte"; + import { isMediaBreakpointOnly } from "../../Utils/BreakpointsUtils"; + + export let clickable = false; export let peer: VideoPeer; let streamStore = peer.streamStore; @@ -19,9 +25,32 @@ function openReport(peer: VideoPeer): void { showReportScreenStore.set({ userId: peer.userId, userName: peer.userName }); } + + let embedScreen: EmbedScreen; + let videoContainer: HTMLDivElement; + let minimized = isMediaBreakpointOnly("md"); + + if (peer) { + embedScreen = { + type: "streamable", + embed: peer as unknown as Streamable, + }; + } + + function noDrag() { + return false; + } + + const resizeObserver = new ResizeObserver(() => { + minimized = isMediaBreakpointOnly("md"); + }); + + onMount(() => { + resizeObserver.observe(videoContainer); + }); -
+
{#if $statusStore === "connecting"}
{/if} @@ -30,42 +59,60 @@ {/if} - {peer.userName} + {peer.userName}
{#if $constraintStore && $constraintStore.audio === false} - Muted + Muted {/if} -
- diff --git a/front/src/Components/Video/VideoOverlay.svelte b/front/src/Components/Video/VideoOverlay.svelte index ba316feb..7cf19aa4 100644 --- a/front/src/Components/Video/VideoOverlay.svelte +++ b/front/src/Components/Video/VideoOverlay.svelte @@ -1,16 +1,16 @@
- {#if $layoutModeStore === LayoutMode.Presentation} +
diff --git a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte new file mode 100644 index 00000000..a3895a52 --- /dev/null +++ b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte @@ -0,0 +1,100 @@ + + +
+ +
+ + diff --git a/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte b/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte new file mode 100644 index 00000000..95000daf --- /dev/null +++ b/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte @@ -0,0 +1,34 @@ + + +{#if $coWebsiteThumbails.length > 0} +
+ {#each [...$coWebsiteThumbails.values()] as coWebsite, index (coWebsite.iframe.id)} + + {/each} +
+{/if} + + diff --git a/front/src/Components/EmbedScreens/EmbedScreensContainer.svelte b/front/src/Components/EmbedScreens/EmbedScreensContainer.svelte new file mode 100644 index 00000000..79c59a58 --- /dev/null +++ b/front/src/Components/EmbedScreens/EmbedScreensContainer.svelte @@ -0,0 +1,22 @@ + + +
+ {#if $embedScreenLayout === LayoutMode.Presentation} + + {:else} + + {/if} +
+ + diff --git a/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte b/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte new file mode 100644 index 00000000..25ff16c8 --- /dev/null +++ b/front/src/Components/EmbedScreens/Layouts/MozaicLayout.svelte @@ -0,0 +1,61 @@ + + +
+
+ {#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)} + + {/each} +
+
+ + diff --git a/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte b/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte new file mode 100644 index 00000000..8cecfe12 --- /dev/null +++ b/front/src/Components/EmbedScreens/Layouts/PresentationLayout.svelte @@ -0,0 +1,169 @@ + + +
+ {#if displayFullMedias} +
+ +
+ {:else} +
+
+ {#if $highlightedEmbedScreen} + {#if $highlightedEmbedScreen.type === "streamable"} + {#key $highlightedEmbedScreen.embed.uniqueId} + + {/key} + {:else if $highlightedEmbedScreen.type === "cowebsite"} + {#key $highlightedEmbedScreen.embed.iframe.id} +
+
+ + + +
+
+ {/key} + {/if} + {/if} +
+ + {#if displayCoWebsiteContainer} + + {/if} +
+ + {#if $peerStore.size > 0} + + {/if} + {/if} +
+ + diff --git a/front/src/Components/EmoteMenu/EmoteMenu.svelte b/front/src/Components/EmoteMenu/EmoteMenu.svelte index 24f08812..ac068484 100644 --- a/front/src/Components/EmoteMenu/EmoteMenu.svelte +++ b/front/src/Components/EmoteMenu/EmoteMenu.svelte @@ -87,7 +87,7 @@ justify-content: center; align-items: center; position: absolute; - z-index: 101; + z-index: 300; } .emote-menu { diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index bc054443..0a0f5b68 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -121,7 +121,7 @@ right: 0; margin-left: auto; margin-right: auto; - z-index: 150; + z-index: 400; } div.interact-menu { diff --git a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte index 2401399a..d4bb4ae0 100644 --- a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte +++ b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte @@ -60,7 +60,7 @@ margin-top: 4%; max-height: 80vh; max-width: 80vw; - z-index: 250; + z-index: 600; overflow: auto; text-align: center; diff --git a/front/src/Components/MainLayout.svelte b/front/src/Components/MainLayout.svelte index b6bfecda..6175f540 100644 --- a/front/src/Components/MainLayout.svelte +++ b/front/src/Components/MainLayout.svelte @@ -1,7 +1,7 @@ -
+
{#if streamable instanceof VideoPeer} - + {:else if streamable instanceof ScreenSharingPeer} - + {:else} - + {/if}
@@ -57,6 +64,32 @@ } } + &.mozaic-full-width { + width: 95%; + max-width: 95%; + margin-left: 3%; + margin-right: 3%; + margin-top: auto; + margin-bottom: auto; + + &:hover { + margin-top: auto; + margin-bottom: auto; + } + } + + &.mozaic-quarter { + width: 95%; + max-width: 95%; + margin-top: auto; + margin-bottom: auto; + + &:hover { + margin-top: auto; + margin-bottom: auto; + } + } + &.clickable { cursor: url("../../../style/images/cursor_pointer.png"), pointer; } diff --git a/front/src/Components/VisitCard/VisitCard.svelte b/front/src/Components/VisitCard/VisitCard.svelte index 94f19f93..57911638 100644 --- a/front/src/Components/VisitCard/VisitCard.svelte +++ b/front/src/Components/VisitCard/VisitCard.svelte @@ -57,7 +57,7 @@ height: 120px; margin: auto; animation: spin 2s linear infinite; - z-index: 102; + z-index: 350; } @keyframes spin { diff --git a/front/src/Components/WarningContainer/WarningContainer.svelte b/front/src/Components/WarningContainer/WarningContainer.svelte index c85d3bd7..b5def355 100644 --- a/front/src/Components/WarningContainer/WarningContainer.svelte +++ b/front/src/Components/WarningContainer/WarningContainer.svelte @@ -42,7 +42,7 @@ font-family: Lato; min-width: 300px; opacity: 0.9; - z-index: 270; + z-index: 700; h2 { padding: 5px; } diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index f6c28862..fec982d1 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -9,21 +9,21 @@ import { get } from "svelte/store"; import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager"; import type { ITiledMapLayer } from "../Map/ITiledMap"; import { GameMapProperties } from "./GameMapProperties"; +import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; enum OpenCoWebsiteState { - LOADING, + ASLEEP, OPENED, MUST_BE_CLOSE, } interface OpenCoWebsite { - coWebsite: CoWebsite | undefined; + coWebsite: CoWebsite; state: OpenCoWebsiteState; } export class GameMapPropertiesListener { private coWebsitesOpenByLayer = new Map(); - private coWebsitesActionTriggerByLayer = new Map(); constructor(private scene: GameScene, private gameMap: GameMap) {} @@ -64,10 +64,8 @@ export class GameMapPropertiesListener { let openWebsiteProperty: string | undefined; let allowApiProperty: boolean | undefined; let websitePolicyProperty: string | undefined; - let websiteWidthProperty: number | undefined; let websitePositionProperty: number | undefined; let websiteTriggerProperty: string | undefined; - let websiteTriggerMessageProperty: string | undefined; layer.properties.forEach((property) => { switch (property.name) { @@ -80,18 +78,12 @@ export class GameMapPropertiesListener { case GameMapProperties.OPEN_WEBSITE_POLICY: websitePolicyProperty = property.value as string | undefined; break; - case GameMapProperties.OPEN_WEBSITE_WIDTH: - websiteWidthProperty = property.value as number | undefined; - break; case GameMapProperties.OPEN_WEBSITE_POSITION: websitePositionProperty = property.value as number | undefined; break; case GameMapProperties.OPEN_WEBSITE_TRIGGER: websiteTriggerProperty = property.value as string | undefined; break; - case GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE: - websiteTriggerMessageProperty = property.value as string | undefined; - break; } }); @@ -105,27 +97,30 @@ export class GameMapPropertiesListener { return; } + const coWebsite = coWebsiteManager.addCoWebsite( + openWebsiteProperty, + this.scene.MapUrlFile, + allowApiProperty, + websitePolicyProperty, + websitePositionProperty, + false + ); + this.coWebsitesOpenByLayer.set(layer, { - coWebsite: undefined, - state: OpenCoWebsiteState.LOADING, + coWebsite: coWebsite, + state: OpenCoWebsiteState.ASLEEP, }); const openWebsiteFunction = () => { coWebsiteManager - .loadCoWebsite( - openWebsiteProperty as string, - this.scene.MapUrlFile, - allowApiProperty, - websitePolicyProperty, - websiteWidthProperty, - websitePositionProperty - ) + .loadCoWebsite(coWebsite) .then((coWebsite) => { const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { - coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => console.error(e)); + coWebsiteManager.closeCoWebsite(coWebsite).catch(() => { + console.error("Error during a co-website closing"); + }); this.coWebsitesOpenByLayer.delete(layer); - this.coWebsitesActionTriggerByLayer.delete(layer); } else { this.coWebsitesOpenByLayer.set(layer, { coWebsite, @@ -133,27 +128,17 @@ export class GameMapPropertiesListener { }); } }) - .catch((e) => console.error(e)); + .catch(() => { + console.error("Error during loading a co-website: " + coWebsite.url); + }); layoutManagerActionStore.removeAction(actionUuid); }; - const forceTrigger = localUserStore.getForceCowebsiteTrigger(); - if (forceTrigger || websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON) { - if (!websiteTriggerMessageProperty) { - websiteTriggerMessageProperty = "Press SPACE or touch here to open web site"; - } - - this.coWebsitesActionTriggerByLayer.set(layer, actionUuid); - - layoutManagerActionStore.addAction({ - uuid: actionUuid, - type: "message", - message: websiteTriggerMessageProperty, - callback: () => openWebsiteFunction(), - userInputManager: this.scene.userInputManager, - }); - } else { + if ( + !localUserStore.getForceCowebsiteTrigger() && + websiteTriggerProperty !== ON_ACTION_TRIGGER_BUTTON + ) { openWebsiteFunction(); } }); @@ -194,7 +179,7 @@ export class GameMapPropertiesListener { return; } - if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) { + if (coWebsiteOpen.state === OpenCoWebsiteState.ASLEEP) { coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE; } @@ -203,26 +188,6 @@ export class GameMapPropertiesListener { } this.coWebsitesOpenByLayer.delete(layer); - - if (!websiteTriggerProperty) { - return; - } - - const actionStore = get(layoutManagerActionStore); - const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer); - - if (!actionTriggerUuid) { - return; - } - - const action = - actionStore && actionStore.length > 0 - ? actionStore.find((action) => action.uuid === actionTriggerUuid) - : undefined; - - if (action) { - layoutManagerActionStore.removeAction(actionTriggerUuid); - } }); }; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9b93ce5b..5b10f5c3 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -2145,8 +2145,8 @@ ${escapedMessage} public stopJitsi(): void { const coWebsite = coWebsiteManager.searchJitsi(); if (coWebsite) { - coWebsiteManager.closeCoWebsite(coWebsite).catch(() => { - console.error("Error during Jitsi co-website closing"); + coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => { + console.error("Error during Jitsi co-website closing", e); }); } } diff --git a/front/src/Stores/CoWebsiteStore.ts b/front/src/Stores/CoWebsiteStore.ts new file mode 100644 index 00000000..57779e58 --- /dev/null +++ b/front/src/Stores/CoWebsiteStore.ts @@ -0,0 +1,66 @@ +import { derived, get, writable } from "svelte/store"; +import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; +import { highlightedEmbedScreen } from "./EmbedScreensStore"; + +function createCoWebsiteStore() { + const { subscribe, set, update } = writable(Array()); + + set(Array()); + + return { + subscribe, + add: (coWebsite: CoWebsite, position?: number) => { + coWebsite.state.subscribe((value) => { + update((currentArray) => currentArray); + }); + + if (position || position === 0) { + update((currentArray) => { + if (position === 0) { + return [coWebsite, ...currentArray]; + } else if (currentArray.length > position) { + const test = [...currentArray.splice(position, 0, coWebsite)]; + return [...currentArray.splice(position, 0, coWebsite)]; + } + + return [...currentArray, coWebsite]; + }); + return; + } + + update((currentArray) => [...currentArray, coWebsite]); + }, + remove: (coWebsite: CoWebsite) => { + update((currentArray) => [ + ...currentArray.filter((currentCoWebsite) => currentCoWebsite.iframe.id !== coWebsite.iframe.id), + ]); + }, + empty: () => { + set(Array()); + }, + }; +} + +export const coWebsites = createCoWebsiteStore(); + +export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) => + $coWebsites.filter((coWebsite) => get(coWebsite.state) !== "asleep") +); + +export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) => + $coWebsites.find((coWebsite) => get(coWebsite.state) !== "asleep") +); + +export const coWebsiteThumbails = derived( + [coWebsites, highlightedEmbedScreen, mainCoWebsite], + ([$coWebsites, highlightedEmbedScreen, $mainCoWebsite]) => + $coWebsites.filter((coWebsite, index) => { + return ( + (!$mainCoWebsite || $mainCoWebsite.iframe.id !== coWebsite.iframe.id) && + (!highlightedEmbedScreen || + highlightedEmbedScreen.type !== "cowebsite" || + (highlightedEmbedScreen.type === "cowebsite" && + highlightedEmbedScreen.embed.iframe.id !== coWebsite.iframe.id)) + ); + }) +); diff --git a/front/src/Stores/EmbedScreensStore.ts b/front/src/Stores/EmbedScreensStore.ts new file mode 100644 index 00000000..0db7c675 --- /dev/null +++ b/front/src/Stores/EmbedScreensStore.ts @@ -0,0 +1,51 @@ +import { derived, get, writable } from "svelte/store"; +import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; +import { LayoutMode } from "../WebRtc/LayoutManager"; +import { coWebsites } from "./CoWebsiteStore"; +import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore"; + +export type EmbedScreen = + | { + type: "streamable"; + embed: Streamable; + } + | { + type: "cowebsite"; + embed: CoWebsite; + }; + +function createHighlightedEmbedScreenStore() { + const { subscribe, set, update } = writable(null); + + return { + subscribe, + highlight: (embedScreen: EmbedScreen) => { + set(embedScreen); + }, + removeHighlight: () => { + set(null); + }, + toggleHighlight: (embedScreen: EmbedScreen) => { + update((currentEmbedScreen) => + !currentEmbedScreen || + embedScreen.type !== currentEmbedScreen.type || + (embedScreen.type === "cowebsite" && + currentEmbedScreen.type === "cowebsite" && + embedScreen.embed.iframe.id !== currentEmbedScreen.embed.iframe.id) || + (embedScreen.type === "streamable" && + currentEmbedScreen.type === "streamable" && + embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId) + ? embedScreen + : null + ); + }, + }; +} + +export const highlightedEmbedScreen = createHighlightedEmbedScreenStore(); +export const embedScreenLayout = writable(LayoutMode.Presentation); + +export const hasEmbedScreen = derived( + [streamableCollectionStore], + ($values) => get(streamableCollectionStore).size + get(coWebsites).length > 0 +); diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 92629dd6..ae61f35d 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -2,7 +2,13 @@ import { HtmlUtils } from "./HtmlUtils"; import { Subject } from "rxjs"; import { iframeListener } from "../Api/IframeListener"; import { waScaleManager } from "../Phaser/Services/WaScaleManager"; -import { ICON_URL } from "../Enum/EnvironmentVariable"; +import { coWebsites, coWebsitesNotAsleep, mainCoWebsite } from "../Stores/CoWebsiteStore"; +import { get, Writable, writable } from "svelte/store"; +import { embedScreenLayout, highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; +import { isMediaBreakpointDown } from "../Utils/BreakpointsUtils"; +import { jitsiFactory } from "./JitsiFactory"; +import { gameManager } from "../Phaser/Game/GameManager"; +import { LayoutMode } from "./LayoutManager"; enum iframeStates { closed = 1, @@ -11,16 +17,15 @@ enum iframeStates { } const cowebsiteDomId = "cowebsite"; // the id of the whole container. -const cowebsiteContainerDomId = "cowebsite-container"; // the id of the whole container. -const cowebsiteMainDomId = "cowebsite-slot-0"; // the id of the parent div of the iframe. +const gameOverlayDomId = "game-overlay"; const cowebsiteBufferDomId = "cowebsite-buffer"; // the id of the container who contains cowebsite iframes. -const cowebsiteAsideDomId = "cowebsite-aside"; // the id of the parent div of the iframe. const cowebsiteAsideHolderDomId = "cowebsite-aside-holder"; -const cowebsiteSubIconsDomId = "cowebsite-sub-icons"; +const cowebsiteLoaderDomId = "cowebsite-loader"; export const cowebsiteCloseButtonId = "cowebsite-close"; const cowebsiteFullScreenButtonId = "cowebsite-fullscreen"; const cowebsiteOpenFullScreenImageId = "cowebsite-fullscreen-open"; const cowebsiteCloseFullScreenImageId = "cowebsite-fullscreen-close"; +const cowebsiteSlotBaseDomId = "cowebsite-slot-"; const animationTime = 500; //time used by the css transitions, in ms. interface TouchMoveCoordinates { @@ -28,15 +33,16 @@ interface TouchMoveCoordinates { y: number; } +export type CoWebsiteState = "asleep" | "loading" | "ready"; + export type CoWebsite = { iframe: HTMLIFrameElement; - icon: HTMLDivElement; - position: number; -}; - -type CoWebsiteSlot = { - container: HTMLElement; - position: number; + url: URL; + state: Writable; + closable: boolean; + allowPolicy: string | undefined; + allowApi: boolean | undefined; + jitsi?: boolean; }; class CoWebsiteManager { @@ -50,18 +56,17 @@ class CoWebsiteManager { */ private currentOperationPromise: Promise = Promise.resolve(); private cowebsiteDom: HTMLDivElement; - private cowebsiteContainerDom: HTMLDivElement; private resizing: boolean = false; - private cowebsiteMainDom: HTMLDivElement; + private gameOverlayDom: HTMLDivElement; private cowebsiteBufferDom: HTMLDivElement; - private cowebsiteAsideDom: HTMLDivElement; private cowebsiteAsideHolderDom: HTMLDivElement; - private cowebsiteSubIconsDom: HTMLDivElement; + private cowebsiteLoaderDom: HTMLDivElement; private previousTouchMoveCoordinates: TouchMoveCoordinates | null = null; //only use on touchscreens to track touch movement - private coWebsites: CoWebsite[] = []; - - private slots: CoWebsiteSlot[]; + private loaderAnimationInterval: { + interval: NodeJS.Timeout | undefined; + trails: number[] | undefined; + }; private resizeObserver = new ResizeObserver((entries) => { this.resizeAllIframes(); @@ -97,59 +102,39 @@ class CoWebsiteManager { constructor() { this.cowebsiteDom = HtmlUtils.getElementByIdOrFail(cowebsiteDomId); - this.cowebsiteContainerDom = HtmlUtils.getElementByIdOrFail(cowebsiteContainerDomId); - this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail(cowebsiteMainDomId); + this.gameOverlayDom = HtmlUtils.getElementByIdOrFail(gameOverlayDomId); this.cowebsiteBufferDom = HtmlUtils.getElementByIdOrFail(cowebsiteBufferDomId); - this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail(cowebsiteAsideDomId); this.cowebsiteAsideHolderDom = HtmlUtils.getElementByIdOrFail(cowebsiteAsideHolderDomId); - this.cowebsiteSubIconsDom = HtmlUtils.getElementByIdOrFail(cowebsiteSubIconsDomId); - this.initResizeListeners(); + this.cowebsiteLoaderDom = HtmlUtils.getElementByIdOrFail(cowebsiteLoaderDomId); + + this.loaderAnimationInterval = { + interval: undefined, + trails: undefined, + }; + + this.holderListeners(); + this.transitionListeners(); this.resizeObserver.observe(this.cowebsiteDom); - this.resizeObserver.observe(this.cowebsiteContainerDom); - - this.slots = [ - { - container: this.cowebsiteMainDom, - position: 0, - }, - { - container: HtmlUtils.getElementByIdOrFail("cowebsite-slot-1"), - position: 1, - }, - { - container: HtmlUtils.getElementByIdOrFail("cowebsite-slot-2"), - position: 2, - }, - { - container: HtmlUtils.getElementByIdOrFail("cowebsite-slot-3"), - position: 3, - }, - { - container: HtmlUtils.getElementByIdOrFail("cowebsite-slot-4"), - position: 4, - }, - ]; - - this.slots.forEach((slot) => { - this.resizeObserver.observe(slot.container); - }); - - this.initActionsListeners(); + this.resizeObserver.observe(this.gameOverlayDom); const buttonCloseCoWebsites = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); buttonCloseCoWebsites.addEventListener("click", () => { - if (this.isSmallScreen() && this.coWebsites.length > 1) { - const coWebsite = this.getCoWebsiteByPosition(0); + const coWebsite = this.getMainCoWebsite(); - if (coWebsite) { - this.removeCoWebsiteFromStack(coWebsite); - return; - } + if (!coWebsite) { + throw new Error("Undefined main co-website on closing"); } - buttonCloseCoWebsites.blur(); - this.closeCoWebsites().catch((e) => console.error(e)); + if (coWebsite.closable) { + this.closeCoWebsite(coWebsite).catch(() => { + console.error("Error during closing a co-website by a button"); + }); + } else { + this.unloadCoWebsite(coWebsite).catch(() => { + console.error("Error during unloading a co-website by a button"); + }); + } }); const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId); @@ -159,20 +144,17 @@ class CoWebsiteManager { }); } + public getCoWebsiteBuffer(): HTMLDivElement { + return this.cowebsiteBufferDom; + } + public getDevicePixelRatio(): number { //on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels //so on chrome-based browser we need to adjust using 'devicePixelRatio' return window.navigator.userAgent.includes("Firefox") ? 1 : window.devicePixelRatio; } - private isSmallScreen(): boolean { - return ( - window.matchMedia("(max-aspect-ratio: 1/1)").matches || - window.matchMedia("(max-width:960px) and (max-height:768px)").matches - ); - } - - private initResizeListeners() { + private holderListeners() { const movecallback = (event: MouseEvent | TouchEvent) => { let x, y; if (event.type === "mousemove") { @@ -187,13 +169,48 @@ class CoWebsiteManager { y = last.y - previous.y; } - this.verticalMode ? (this.height += y) : (this.width -= x); + if (this.verticalMode) { + const tempValue = this.height + y; + let maxHeight = 60 * window.innerHeight; + if (maxHeight !== 0) { + maxHeight = Math.round(maxHeight / 100); + } + + if (tempValue < this.cowebsiteAsideHolderDom.offsetHeight) { + this.height = this.cowebsiteAsideHolderDom.offsetHeight; + } else if (tempValue > maxHeight) { + this.height = maxHeight; + } else { + this.height = tempValue; + } + } else { + const tempValue = this.width - x; + let maxWidth = 75 * window.innerWidth; + if (maxWidth !== 0) { + maxWidth = Math.round(maxWidth / 100); + } + + if (tempValue < this.cowebsiteAsideHolderDom.offsetWidth) { + this.width = this.cowebsiteAsideHolderDom.offsetWidth; + } else if (tempValue > maxWidth) { + this.width = maxWidth; + } else { + this.width = tempValue; + } + } this.fire(); }; this.cowebsiteAsideHolderDom.addEventListener("mousedown", (event) => { if (this.isFullScreen) return; - this.cowebsiteMainDom.style.display = "none"; + const coWebsite = this.getMainCoWebsite(); + + if (!coWebsite) { + this.closeMain(); + return; + } + + coWebsite.iframe.style.display = "none"; this.resizing = true; document.addEventListener("mousemove", movecallback); }); @@ -201,14 +218,28 @@ class CoWebsiteManager { document.addEventListener("mouseup", (event) => { if (!this.resizing || this.isFullScreen) return; document.removeEventListener("mousemove", movecallback); - this.cowebsiteMainDom.style.display = "block"; + const coWebsite = this.getMainCoWebsite(); + + if (!coWebsite) { + this.resizing = false; + this.closeMain(); + return; + } + + coWebsite.iframe.style.display = "flex"; this.resizing = false; - this.cowebsiteMainDom.style.display = "flex"; }); this.cowebsiteAsideHolderDom.addEventListener("touchstart", (event) => { if (this.isFullScreen) return; - this.cowebsiteMainDom.style.display = "none"; + const coWebsite = this.getMainCoWebsite(); + + if (!coWebsite) { + this.closeMain(); + return; + } + + coWebsite.iframe.style.display = "none"; this.resizing = true; const touchEvent = event.touches[0]; this.previousTouchMoveCoordinates = { x: touchEvent.pageX, y: touchEvent.pageY }; @@ -219,30 +250,81 @@ class CoWebsiteManager { if (!this.resizing || this.isFullScreen) return; this.previousTouchMoveCoordinates = null; document.removeEventListener("touchmove", movecallback); - this.cowebsiteMainDom.style.display = "block"; + const coWebsite = this.getMainCoWebsite(); + + if (!coWebsite) { + this.closeMain(); + this.resizing = false; + return; + } + + coWebsite.iframe.style.display = "flex"; this.resizing = false; - this.cowebsiteMainDom.style.display = "flex"; + }); + } + + private transitionListeners() { + this.cowebsiteDom.addEventListener("transitionend", (event) => { + if (this.cowebsiteDom.classList.contains("loading")) { + this.fire(); + } + + if (this.cowebsiteDom.classList.contains("closing")) { + this.cowebsiteDom.classList.remove("closing"); + if (this.loaderAnimationInterval.interval) { + clearInterval(this.loaderAnimationInterval.interval); + } + this.loaderAnimationInterval.trails = undefined; + } }); } private closeMain(): void { - this.cowebsiteDom.classList.remove("loaded"); //edit the css class to trigger the transition - this.cowebsiteDom.classList.add("hidden"); + this.toggleFullScreenIcon(true); + this.cowebsiteDom.classList.add("closing"); + this.cowebsiteDom.classList.remove("opened"); this.openedMain = iframeStates.closed; this.resetStyleMain(); - this.cowebsiteDom.style.display = "none"; + this.fire(); } + private loadMain(): void { - this.cowebsiteDom.style.display = "flex"; - this.cowebsiteDom.classList.remove("hidden"); //edit the css class to trigger the transition - this.cowebsiteDom.classList.add("loading"); + this.loaderAnimationInterval.interval = setInterval(() => { + if (!this.loaderAnimationInterval.trails) { + this.loaderAnimationInterval.trails = [0, 1, 2]; + } + + for (let trail = 1; trail < this.loaderAnimationInterval.trails.length + 1; trail++) { + for (let state = 0; state < 4; state++) { + // const newState = this.loaderAnimationInterval.frames + trail -1; + const stateDom = this.cowebsiteLoaderDom.querySelector( + `#trail-${trail}-state-${state}` + ) as SVGPolygonElement; + + if (!stateDom) { + continue; + } + + stateDom.style.visibility = + this.loaderAnimationInterval.trails[trail - 1] !== 0 && + this.loaderAnimationInterval.trails[trail - 1] >= state + ? "visible" + : "hidden"; + } + } + + this.loaderAnimationInterval.trails = this.loaderAnimationInterval.trails.map((trail) => + trail === 3 ? 0 : trail + 1 + ); + }, 200); + this.cowebsiteDom.classList.add("opened"); this.openedMain = iframeStates.loading; } + private openMain(): void { this.cowebsiteDom.addEventListener("transitionend", () => { this.resizeAllIframes(); }); - this.cowebsiteDom.classList.remove("loading", "hidden"); //edit the css class to trigger the transition this.openedMain = iframeStates.opened; this.resetStyleMain(); } @@ -252,335 +334,321 @@ class CoWebsiteManager { this.cowebsiteDom.style.height = ""; } - private initActionsListeners() { - this.slots.forEach((slot: CoWebsiteSlot) => { - const expandButton = slot.container.querySelector(".expand"); - const highlightButton = slot.container.querySelector(".hightlight"); - const closeButton = slot.container.querySelector(".close"); - - if (expandButton) { - expandButton.addEventListener("click", (event) => { - event.preventDefault(); - const coWebsite = this.getCoWebsiteByPosition(slot.position); - - if (!coWebsite) { - return; - } - - this.moveRightPreviousCoWebsite(coWebsite, 0); - }); - } - - if (highlightButton) { - highlightButton.addEventListener("click", (event) => { - event.preventDefault(); - const coWebsite = this.getCoWebsiteByPosition(slot.position); - - if (!coWebsite) { - return; - } - - this.moveRightPreviousCoWebsite(coWebsite, 1); - }); - } - - if (closeButton) { - closeButton.addEventListener("click", (event) => { - event.preventDefault(); - const coWebsite = this.getCoWebsiteByPosition(slot.position); - - if (!coWebsite) { - return; - } - - this.removeCoWebsiteFromStack(coWebsite); - }); - } - }); - } - public getCoWebsites(): CoWebsite[] { - return this.coWebsites; + return get(coWebsites); } public getCoWebsiteById(coWebsiteId: string): CoWebsite | undefined { - return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId); - } - - private getSlotByPosition(position: number): CoWebsiteSlot | undefined { - return this.slots.find((slot: CoWebsiteSlot) => slot.position === position); + return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId); } private getCoWebsiteByPosition(position: number): CoWebsite | undefined { - return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.position === position); - } - - private setIframeOffset(coWebsite: CoWebsite, slot: CoWebsiteSlot) { - const bounding = slot.container.getBoundingClientRect(); - - if (coWebsite.iframe.classList.contains("thumbnail")) { - coWebsite.iframe.style.width = (bounding.right - bounding.left) * 2 + "px"; - coWebsite.iframe.style.height = (bounding.bottom - bounding.top) * 2 + "px"; - coWebsite.iframe.style.top = bounding.top - Math.floor(bounding.height * 0.5) + "px"; - coWebsite.iframe.style.left = bounding.left - Math.floor(bounding.width * 0.5) + "px"; - } else { - coWebsite.iframe.style.top = bounding.top + "px"; - coWebsite.iframe.style.left = bounding.left + "px"; - coWebsite.iframe.style.width = bounding.right - bounding.left + "px"; - coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px"; - } - } - - private resizeAllIframes() { - this.coWebsites.forEach((coWebsite: CoWebsite) => { - const slot = this.getSlotByPosition(coWebsite.position); - - if (slot) { - this.setIframeOffset(coWebsite, slot); + let i = 0; + return get(coWebsites).find((coWebsite: CoWebsite) => { + if (i === position) { + return coWebsite; } + + i++; + return false; }); } - private moveCoWebsite(coWebsite: CoWebsite, newPosition: number) { - const oldSlot = this.getSlotByPosition(coWebsite.position); - const newSlot = this.getSlotByPosition(newPosition); + private getMainCoWebsite(): CoWebsite | undefined { + return get(mainCoWebsite); + } - if (!newSlot) { + private getPositionByCoWebsite(coWebsite: CoWebsite): number { + return get(coWebsites).findIndex((currentCoWebsite) => currentCoWebsite.iframe.id === coWebsite.iframe.id); + } + + private getSlotByCowebsite(coWebsite: CoWebsite): HTMLDivElement | undefined { + const index = this.getPositionByCoWebsite(coWebsite); + if (index === -1) { + return undefined; + } + + let id = cowebsiteSlotBaseDomId; + + if (index === 0) { + id += "main"; + } else { + id += coWebsite.iframe.id; + } + + const slot = HtmlUtils.getElementById(id); + + return slot; + } + + private setIframeOffset(coWebsite: CoWebsite) { + const coWebsiteSlot = this.getSlotByCowebsite(coWebsite); + + if (!coWebsiteSlot) { return; } - coWebsite.iframe.scrolling = newPosition === 0 || newPosition === 1 ? "yes" : "no"; + const bounding = coWebsiteSlot.getBoundingClientRect(); - if (newPosition === 0) { - coWebsite.iframe.classList.add("main"); - coWebsite.icon.style.display = "none"; - } else { - coWebsite.iframe.classList.remove("main"); - coWebsite.icon.style.display = "flex"; + coWebsite.iframe.style.top = bounding.top + "px"; + coWebsite.iframe.style.left = bounding.left + "px"; + coWebsite.iframe.style.width = bounding.right - bounding.left + "px"; + coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px"; + } + + public resizeAllIframes() { + const mainCoWebsite = this.getCoWebsiteByPosition(0); + const highlightEmbed = get(highlightedEmbedScreen); + + get(coWebsites).forEach((coWebsite) => { + const notMain = !mainCoWebsite || (mainCoWebsite && mainCoWebsite.iframe.id !== coWebsite.iframe.id); + const notHighlighEmbed = + !highlightEmbed || + (highlightEmbed && + (highlightEmbed.type !== "cowebsite" || + (highlightEmbed.type === "cowebsite" && + highlightEmbed.embed.iframe.id !== coWebsite.iframe.id))); + + if (coWebsite.iframe.classList.contains("main") && notMain) { + coWebsite.iframe.classList.remove("main"); + } + + if (coWebsite.iframe.classList.contains("highlighted") && notHighlighEmbed) { + coWebsite.iframe.classList.remove("highlighted"); + coWebsite.iframe.classList.add("pixel"); + coWebsite.iframe.style.top = "-1px"; + coWebsite.iframe.style.left = "-1px"; + } + + if (notMain && notHighlighEmbed) { + coWebsite.iframe.classList.add("pixel"); + coWebsite.iframe.style.top = "-1px"; + coWebsite.iframe.style.left = "-1px"; + } + + this.setIframeOffset(coWebsite); + }); + + if (mainCoWebsite) { + mainCoWebsite.iframe.classList.add("main"); + mainCoWebsite.iframe.classList.remove("pixel"); } - if (newPosition === 1) { - coWebsite.iframe.classList.add("sub-main"); - } else { - coWebsite.iframe.classList.remove("sub-main"); + if (highlightEmbed && highlightEmbed.type === "cowebsite") { + highlightEmbed.embed.iframe.classList.add("highlighted"); + highlightEmbed.embed.iframe.classList.remove("pixel"); + } + } + + private removeHighlightCoWebsite(coWebsite: CoWebsite) { + const highlighted = get(highlightedEmbedScreen); + + if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.iframe.id === coWebsite.iframe.id) { + highlightedEmbedScreen.removeHighlight(); + } + } + + private removeCoWebsiteFromStack(coWebsite: CoWebsite) { + this.removeHighlightCoWebsite(coWebsite); + coWebsites.remove(coWebsite); + + if (get(coWebsites).length < 1) { + this.closeMain(); } - if (newPosition >= 2) { - coWebsite.iframe.classList.add("thumbnail"); - } else { - coWebsite.iframe.classList.remove("thumbnail"); + coWebsite.iframe.remove(); + } + + public goToMain(coWebsite: CoWebsite) { + const mainCoWebsite = this.getMainCoWebsite(); + coWebsites.remove(coWebsite); + coWebsites.add(coWebsite, 0); + + if ( + isMediaBreakpointDown("lg") && + get(embedScreenLayout) === LayoutMode.Presentation && + mainCoWebsite && + mainCoWebsite.iframe.id !== coWebsite.iframe.id && + get(mainCoWebsite.state) !== "asleep" + ) { + highlightedEmbedScreen.toggleHighlight({ + type: "cowebsite", + embed: mainCoWebsite, + }); } - coWebsite.position = newPosition; - - if (oldSlot && !this.getCoWebsiteByPosition(oldSlot.position)) { - oldSlot.container.style.display = "none"; - } - - this.displayCowebsiteContainer(); - - newSlot.container.style.display = "block"; - - coWebsite.iframe.classList.remove("pixel"); - this.resizeAllIframes(); } - private displayCowebsiteContainer() { - if (this.coWebsites.find((cowebsite) => cowebsite.position > 0)) { - this.cowebsiteContainerDom.style.display = "block"; - } else { - this.cowebsiteContainerDom.style.display = "none"; - } - } - - private moveLeftPreviousCoWebsite(coWebsite: CoWebsite, newPosition: number) { - const nextCoWebsite = this.getCoWebsiteByPosition(coWebsite.position + 1); - - this.moveCoWebsite(coWebsite, newPosition); - - if (nextCoWebsite) { - this.moveLeftPreviousCoWebsite(nextCoWebsite, nextCoWebsite.position - 1); - } - } - - private moveRightPreviousCoWebsite(coWebsite: CoWebsite, newPosition: number) { - if (newPosition >= 5) { - return; - } - - const currentCoWebsite = this.getCoWebsiteByPosition(newPosition); - - this.moveCoWebsite(coWebsite, newPosition); - - if (newPosition === 4 || !currentCoWebsite || currentCoWebsite.iframe.id === coWebsite.iframe.id) { - return; - } - - if (!currentCoWebsite) { - return; - } - - this.moveRightPreviousCoWebsite(currentCoWebsite, currentCoWebsite.position + 1); - } - - private removeCoWebsiteFromStack(coWebsite: CoWebsite) { - this.coWebsites = this.coWebsites.filter( - (coWebsiteToRemove: CoWebsite) => coWebsiteToRemove.iframe.id !== coWebsite.iframe.id - ); - - if (this.coWebsites.length < 1) { - this.closeMain(); - } - - if (coWebsite.position > 0) { - const slot = this.getSlotByPosition(coWebsite.position); - if (slot) { - slot.container.style.display = "none"; - } - } - - const previousCoWebsite = this.coWebsites.find( - (coWebsiteToCheck: CoWebsite) => coWebsite.position + 1 === coWebsiteToCheck.position - ); - - if (previousCoWebsite) { - this.moveLeftPreviousCoWebsite(previousCoWebsite, coWebsite.position); - } - - this.displayCowebsiteContainer(); - - coWebsite.icon.remove(); - coWebsite.iframe.remove(); - } - public searchJitsi(): CoWebsite | undefined { - return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.iframe.id.toLowerCase().includes("jitsi")); + return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.jitsi); } - private generateCoWebsiteIcon(iframe: HTMLIFrameElement): HTMLDivElement { - const icon = document.createElement("div"); - icon.id = "cowebsite-icon-" + iframe.id; - icon.style.display = "none"; + private initialiseCowebsite(coWebsite: CoWebsite, position: number | undefined) { + if (coWebsite.allowPolicy) { + coWebsite.iframe.allow = coWebsite.allowPolicy; + } - const iconImage = document.createElement("img"); - iconImage.src = `${ICON_URL}/icon?url=${iframe.src}&size=16..30..256`; - const url = new URL(iframe.src); - iconImage.alt = url.hostname; + if (coWebsite.allowApi) { + iframeListener.registerIframe(coWebsite.iframe); + } - icon.appendChild(iconImage); + coWebsite.iframe.classList.add("pixel"); - return icon; + const coWebsitePosition = position === undefined ? get(coWebsites).length : position; + coWebsites.add(coWebsite, coWebsitePosition); } - public loadCoWebsite( + private generateUniqueId() { + let id = undefined; + do { + id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); + } while (this.getCoWebsiteById(id)); + + return id; + } + + public addCoWebsite( url: string, base: string, allowApi?: boolean, allowPolicy?: string, - widthPercent?: number, - position?: number - ): Promise { - return this.addCoWebsite( - (iframeBuffer) => { - const iframe = document.createElement("iframe"); - iframe.src = new URL(url, base).toString(); + position?: number, + closable?: boolean + ): CoWebsite { + const iframe = document.createElement("iframe"); + const fullUrl = new URL(url, base); + iframe.src = fullUrl.toString(); + iframe.id = this.generateUniqueId(); - if (allowPolicy) { - iframe.allow = allowPolicy; - } + const newCoWebsite: CoWebsite = { + iframe, + url: fullUrl, + state: writable("asleep" as CoWebsiteState), + closable: closable ?? false, + allowPolicy, + allowApi, + }; - if (allowApi) { - iframeListener.registerIframe(iframe); - } + this.initialiseCowebsite(newCoWebsite, position); - iframeBuffer.appendChild(iframe); - - return iframe; - }, - widthPercent, - position - ); + return newCoWebsite; } - public async addCoWebsite( - callback: (iframeBuffer: HTMLDivElement) => PromiseLike | HTMLIFrameElement, - widthPercent?: number, - position?: number - ): Promise { + public addCoWebsiteFromIframe( + iframe: HTMLIFrameElement, + allowApi?: boolean, + allowPolicy?: string, + position?: number, + closable?: boolean, + jitsi?: boolean + ): CoWebsite { + if (get(coWebsitesNotAsleep).length < 1) { + this.loadMain(); + } + + iframe.id = this.generateUniqueId(); + + const newCoWebsite: CoWebsite = { + iframe, + url: new URL(iframe.src), + state: writable("ready" as CoWebsiteState), + closable: closable ?? false, + allowPolicy, + allowApi, + jitsi, + }; + + if (position === 0) { + this.openMain(); + setTimeout(() => { + this.fire(); + }, animationTime); + } + + this.initialiseCowebsite(newCoWebsite, position); + + return newCoWebsite; + } + + public loadCoWebsite(coWebsite: CoWebsite): Promise { + if (get(coWebsitesNotAsleep).length < 1) { + coWebsites.remove(coWebsite); + coWebsites.add(coWebsite, 0); + this.loadMain(); + } + + coWebsite.state.set("loading"); + + const mainCoWebsite = this.getMainCoWebsite(); + return new Promise((resolve, reject) => { - if (this.coWebsites.length < 1) { - this.loadMain(); - } else if (this.coWebsites.length === 5) { - throw new Error("Too many websites"); + const onloadPromise = new Promise((resolve) => { + coWebsite.iframe.onload = () => { + coWebsite.state.set("ready"); + resolve(); + }; + }); + + const onTimeoutPromise = new Promise((resolve) => { + setTimeout(() => resolve(), 2000); + }); + + this.cowebsiteBufferDom.appendChild(coWebsite.iframe); + + if (coWebsite.jitsi) { + const gameScene = gameManager.getCurrentGameScene(); + gameScene.disableMediaBehaviors(); } - Promise.resolve(callback(this.cowebsiteBufferDom)) - .then((iframe) => { - iframe?.classList.add("pixel"); + this.currentOperationPromise = this.currentOperationPromise + .then(() => Promise.race([onloadPromise, onTimeoutPromise])) + .then(() => { + if (mainCoWebsite && mainCoWebsite.iframe.id === coWebsite.iframe.id) { + this.openMain(); - if (!iframe.id) { - do { - iframe.id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); - } while (this.getCoWebsiteById(iframe.id)); + setTimeout(() => { + this.fire(); + }, animationTime); } - const onloadPromise = new Promise((resolve) => { - iframe.onload = () => resolve(); - }); - - const icon = this.generateCoWebsiteIcon(iframe); - - const coWebsite = { - iframe, - icon, - position: position ?? this.coWebsites.length, - }; - - // Iframe management on mobile - icon.addEventListener("click", () => { - if (this.isSmallScreen()) { - this.moveRightPreviousCoWebsite(coWebsite, 0); - } - }); - - this.coWebsites.push(coWebsite); - this.cowebsiteSubIconsDom.appendChild(icon); - - const onTimeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 2000); - }); - - this.currentOperationPromise = this.currentOperationPromise - .then(() => Promise.race([onloadPromise, onTimeoutPromise])) - .then(() => { - if (coWebsite.position === 0) { - this.openMain(); - if (widthPercent) { - this.widthPercent = widthPercent; - } - - setTimeout(() => { - this.fire(); - position !== undefined - ? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) - : this.moveCoWebsite(coWebsite, coWebsite.position); - }, animationTime); - } else { - position !== undefined - ? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) - : this.moveCoWebsite(coWebsite, coWebsite.position); - } - - return resolve(coWebsite); - }) - .catch((err) => { - console.error("Error loadCoWebsite => ", err); - this.removeCoWebsiteFromStack(coWebsite); - return reject(); - }); + return resolve(coWebsite); }) - .catch((e) => console.error("Error loadCoWebsite => ", e)); + .catch((err) => { + console.error("Error on co-website loading => ", err); + this.removeCoWebsiteFromStack(coWebsite); + return reject(); + }); + }); + } + + public unloadCoWebsite(coWebsite: CoWebsite): Promise { + return new Promise((resolve, reject) => { + this.removeHighlightCoWebsite(coWebsite); + + coWebsite.iframe.parentNode?.removeChild(coWebsite.iframe); + coWebsite.state.set("asleep"); + coWebsites.remove(coWebsite); + + if (coWebsite.jitsi) { + jitsiFactory.stop(); + const gameScene = gameManager.getCurrentGameScene(); + gameScene.enableMediaBehaviors(); + } + + const mainCoWebsite = this.getMainCoWebsite(); + + if (mainCoWebsite) { + this.removeHighlightCoWebsite(mainCoWebsite); + this.goToMain(mainCoWebsite); + this.resizeAllIframes(); + } else { + this.closeMain(); + } + + coWebsites.add(coWebsite, get(coWebsites).length); + + resolve(); }); } @@ -588,13 +656,17 @@ class CoWebsiteManager { this.currentOperationPromise = this.currentOperationPromise.then( () => new Promise((resolve) => { - if (this.coWebsites.length === 1) { - if (this.openedMain === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example - this.closeMain(); + if (coWebsite.jitsi) { + jitsiFactory.stop(); + const gameScene = gameManager.getCurrentGameScene(); + gameScene.enableMediaBehaviors(); + } + + if (get(coWebsites).length === 1) { this.fire(); } - if (coWebsite) { + if (coWebsite.allowApi) { iframeListener.unregisterIframe(coWebsite.iframe); } @@ -605,27 +677,19 @@ class CoWebsiteManager { return this.currentOperationPromise; } - public async closeJitsi() { - const jitsi = this.searchJitsi(); - if (jitsi) { - return this.closeCoWebsite(jitsi); - } - } - - public async closeCoWebsites(): Promise { - await this.currentOperationPromise; - - const promises: Promise[] = []; - this.coWebsites.forEach((coWebsite: CoWebsite) => { - promises.push(this.closeCoWebsite(coWebsite)); - }); - await Promise.all(promises); - // TODO: this.currentOperationPromise does not point any more on the last promise - return; + public closeCoWebsites(): Promise { + return (this.currentOperationPromise = this.currentOperationPromise.then(() => { + get(coWebsites).forEach((coWebsite: CoWebsite) => { + this.closeCoWebsite(coWebsite).catch(() => { + console.error("Error during closing a co-website"); + }); + }); + })); + return this.currentOperationPromise; } public getGameSize(): { width: number; height: number } { - if (this.openedMain !== iframeStates.opened) { + if (this.openedMain === iframeStates.closed) { return { width: window.innerWidth, height: window.innerHeight, @@ -651,19 +715,27 @@ class CoWebsiteManager { } private fullscreen(): void { - const openFullscreenImage = HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId); - const closeFullScreenImage = HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId); - if (this.isFullScreen) { + this.toggleFullScreenIcon(true); this.resetStyleMain(); this.fire(); //we don't trigger a resize of the phaser game since it won't be visible anyway. + } else { + this.toggleFullScreenIcon(false); + this.verticalMode ? (this.height = window.innerHeight) : (this.width = window.innerWidth); + //we don't trigger a resize of the phaser game since it won't be visible anyway. + } + } + + private toggleFullScreenIcon(visible: boolean) { + const openFullscreenImage = HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId); + const closeFullScreenImage = HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId); + + if (visible) { this.cowebsiteAsideHolderDom.style.visibility = "visible"; openFullscreenImage.style.display = "inline"; closeFullScreenImage.style.display = "none"; } else { - this.verticalMode ? (this.height = window.innerHeight) : (this.width = window.innerWidth); - //we don't trigger a resize of the phaser game since it won't be visible anyway. this.cowebsiteAsideHolderDom.style.visibility = "hidden"; openFullscreenImage.style.display = "none"; closeFullScreenImage.style.display = "inline"; diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index c067a255..8f9524a2 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -132,64 +132,64 @@ class JitsiFactory { return slugify(instance.replace("/", "-") + "-" + roomName); } - public start( + public async start( roomName: string, playerName: string, jwt?: string, config?: object, interfaceConfig?: object, - jitsiUrl?: string, - jitsiWidth?: number - ): Promise { - return coWebsiteManager.addCoWebsite( - async (cowebsiteDiv) => { - // Jitsi meet external API maintains some data in local storage - // which is sent via the appData URL parameter when joining a - // conference. Problem is that this data grows indefinitely. Thus - // after some time the URLs get so huge that loading the iframe - // becomes slow and eventually breaks completely. Thus lets just - // clear jitsi local storage before starting a new conference. - window.localStorage.removeItem("jitsiLocalStorage"); + jitsiUrl?: string + ) { + const coWebsite = coWebsiteManager.searchJitsi(); - const domain = jitsiUrl || JITSI_URL; - if (domain === undefined) { - throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); - } - await this.loadJitsiScript(domain); + if (coWebsite) { + await coWebsiteManager.closeCoWebsite(coWebsite); + } - const options: JitsiOptions = { - roomName: roomName, - jwt: jwt, - width: "100%", - height: "100%", - parentNode: cowebsiteDiv, - configOverwrite: mergeConfig(config), - interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig }, - }; - if (!options.jwt) { - delete options.jwt; - } + // Jitsi meet external API maintains some data in local storage + // which is sent via the appData URL parameter when joining a + // conference. Problem is that this data grows indefinitely. Thus + // after some time the URLs get so huge that loading the iframe + // becomes slow and eventually breaks completely. Thus lets just + // clear jitsi local storage before starting a new conference. + window.localStorage.removeItem("jitsiLocalStorage"); - return new Promise((resolve, reject) => { - const doResolve = (): void => { - const iframe = cowebsiteDiv.querySelector('[id*="jitsi" i]'); - if (iframe === null) { - throw new Error("Could not find Jitsi Iframe"); - } - resolve(iframe); - }; - options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. - setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load - this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); - this.jitsiApi.executeCommand("displayName", playerName); + const domain = jitsiUrl || JITSI_URL; + if (domain === undefined) { + throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map."); + } + await this.loadJitsiScript(domain); - this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); - this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); - }); - }, - jitsiWidth, - 0 - ); + const options: JitsiOptions = { + roomName: roomName, + jwt: jwt, + width: "100%", + height: "100%", + parentNode: coWebsiteManager.getCoWebsiteBuffer(), + configOverwrite: mergeConfig(config), + interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig }, + }; + + if (!options.jwt) { + delete options.jwt; + } + + const doResolve = (): void => { + const iframe = coWebsiteManager.getCoWebsiteBuffer().querySelector('[id*="jitsi" i]'); + if (iframe) { + coWebsiteManager.addCoWebsiteFromIframe(iframe, false, undefined, 0, false, true); + } + + coWebsiteManager.resizeAllIframes(); + }; + + options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. + setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load + this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); + this.jitsiApi.executeCommand("displayName", playerName); + + this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); + this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); } public stop() { @@ -197,12 +197,6 @@ class JitsiFactory { return; } - const jitsiCoWebsite = coWebsiteManager.searchJitsi(); - - if (jitsiCoWebsite) { - coWebsiteManager.closeJitsi().catch((e) => console.error(e)); - } - this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback); this.jitsiApi.removeListener("videoMuteStatusChanged", this.videoCallback); this.jitsiApi?.dispose(); diff --git a/front/style/cowebsite.scss b/front/style/cowebsite.scss index a6af21ee..529bcae1 100644 --- a/front/style/cowebsite.scss +++ b/front/style/cowebsite.scss @@ -1,220 +1,3 @@ -/* A potentially shared website could appear in an iframe in the cowebsite space. */ - -#cowebsite { - position: fixed; - z-index: 200; - transition: transform 0.5s; - background-color: whitesmoke; - display: none; - - &.loading { - background-color: gray; - } - - main { - iframe { - width: 100%; - height: 100%; - max-width: 100vw; - max-height: 100vh; - } - } - - aside { - background: gray; - align-items: center; - display: flex; - flex-direction: column; - justify-content: space-between; - - #cowebsite-aside-holder { - background: gray; - height: 20px; - flex: 1; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - - img { - width: 80%; - pointer-events: none; - } - } - - #cowebsite-aside-buttons { - display: flex; - flex-direction: column; - margin-bottom: auto; - flex: 1; - justify-content: start; - } - - .top-right-btn{ - transform: scale(0.5); - cursor: url('./images/cursor_pointer.png'), pointer; - } - - #cowebsite-sub-icons { - display: flex; - margin-top: auto; - visibility: hidden; - justify-content: end; - flex: 1; - } - } - - &-container { - position: absolute; - display: none; - height: 100%; - width: 100%; - - &-main { - padding: 2% 5%; - height: 50%; - } - - &-sub { - position: absolute !important; - display: inline-flex; - justify-content: center; - align-items: center; - bottom: 23%; - height: 20% !important; - width: 100%; - } - } - - &-slot-0 { - z-index: 70 !important; - background-color: whitesmoke; - } - - @for $i from 1 through 4 { - &-slot-#{$i} { - transition: transform 0.5s; - position: relative; - height: 100%; - display: none; - background-color: #333333; - - @if $i == 1 { - width: 100%; - } @else { - width: 33%; - margin: 5px; - } - - .overlay { - width: 100%; - height: 100%; - z-index: 50; - position: absolute; - display: flex; - flex-direction: column; - - .actions-move { - display: none; - flex-direction: row; - justify-content: center; - align-items: center; - position: absolute; - height: 100%; - width: 100%; - gap: 10%; - } - - &:hover { - background-color: rgba($color: #333333, $alpha: 0.6); - - .actions-move { - display: flex; - } - } - } - - .actions { - pointer-events: all !important; - margin: 3% 2%; - display: flex; - flex-direction: row; - justify-content: end; - position: relative; - z-index: 50; - - button { - width: 32px; - height: 32px; - margin: 8px; - display: flex; - justify-content: center; - align-items: center; - } - } - } - } - - &-buffer { - iframe { - z-index: 45 !important; - pointer-events: none !important; - overflow: hidden; - border: 0; - position: absolute; - } - - .main { - pointer-events: all !important; - z-index: 205 !important; - } - - .sub-main { - pointer-events: all !important; - } - - .thumbnail { - transform: scale(0.5, 0.5); - } - } - - .pixel { - visibility: hidden; - height: 1px; - width: 1px; - } -} - -@media (min-aspect-ratio: 1/1) { - #cowebsite { - right: 0; - top: 0; - width: 50%; - height: 100vh; - display: none; - - &.loading { - transform: translateX(90%); - } - &.hidden { - transform: translateX(100%); - } - - main { - width: 100%; - } - - - aside { - width: 30px; - - img { - transform: rotate(90deg); - } - } - - &-aside-holder { - cursor: ew-resize; - } - } -} +@import "cowebsite/global"; +@import "cowebsite/short-screens"; +@import "cowebsite/wide-screens"; diff --git a/front/style/cowebsite/_global.scss b/front/style/cowebsite/_global.scss new file mode 100644 index 00000000..b9d8e2ee --- /dev/null +++ b/front/style/cowebsite/_global.scss @@ -0,0 +1,119 @@ +#cowebsite { + position: fixed; + z-index: 820; + transition: transform 0.5s; + background-color: rgba(10, 9, 9, 0.8); + display: none; + + main { + iframe { + width: 100%; + height: 100%; + max-width: 100vw; + max-height: 100vh; + } + } + + aside { + background: gray; + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; + + #cowebsite-aside-holder { + background: gray; + height: 20px; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + + img { + width: 80%; + pointer-events: none; + } + } + + #cowebsite-aside-buttons { + display: flex; + flex-direction: column; + margin-bottom: auto; + flex: 1; + justify-content: start; + } + + .top-right-btn { + transform: scale(0.5); + cursor: url("./images/cursor_pointer.png"), pointer; + } + + #cowebsite-other-actions { + display: flex; + margin-top: auto; + visibility: hidden; + justify-content: end; + flex: 1; + } + } + + &-loader { + width: 20%; + + #smoke { + @for $i from 1 through 3 { + #trail-#{$i} { + @for $y from 1 through 3 { + #trail-#{$i}-state-#{$y} { + visibility: hidden; + } + } + } + } + } + } + + &-slot-main { + z-index: 70 !important; + background-color: rgba(10, 9, 9, 0); + display: flex; + justify-content: center; + align-items: center; + } + + &-buffer { + iframe { + z-index: 45 !important; + pointer-events: none !important; + overflow: hidden; + border: 0; + position: absolute; + + &.pixel { + height: 1px !important; + width: 1px !important; + } + } + + .main { + pointer-events: all !important; + z-index: 821 !important; + } + + .highlighted { + pointer-events: all !important; + padding: 4px; + } + + .thumbnail { + transform: scale(0.5, 0.5); + } + } + + .pixel { + visibility: hidden; + height: 1px; + width: 1px; + } +} diff --git a/front/style/cowebsite/_short-screens.scss b/front/style/cowebsite/_short-screens.scss new file mode 100644 index 00000000..89a5d123 --- /dev/null +++ b/front/style/cowebsite/_short-screens.scss @@ -0,0 +1,84 @@ +@include media-breakpoint-up(md) { + #main-container { + display: flex; + flex-direction: column-reverse; + } + + #cowebsite { + left: 0; + top: 0; + width: 100%; + height: 50%; + display: flex; + flex-direction: column-reverse; + + visibility: collapse; + transform: translateY(-100%); + + &.loading { + visibility: visible; + transform: translateY(0%); + } + + &.opened { + visibility: visible; + transform: translateY(0%); + } + + &.closing { + visibility: visible; + } + + &-loader { + height: 20%; + } + + main { + height: 100%; + } + + aside { + height: 50px; + flex-direction: row-reverse; + align-items: center; + display: flex; + justify-content: space-between; + + #cowebsite-aside-holder { + height: 100%; + cursor: ns-resize; + + img { + height: 100%; + } + } + + #cowebsite-aside-buttons { + flex-direction: row-reverse; + margin-left: auto; + margin-bottom: 0; + justify-content: end; + } + + #cowebsite-fullscreen { + padding-top: 0; + } + + #cowebsite-other-actions { + display: inline-flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 0; + height: 100%; + visibility: visible; + } + + .top-right-btn { + img { + width: 15px; + } + } + } + } +} diff --git a/front/style/cowebsite/_wide-screens.scss b/front/style/cowebsite/_wide-screens.scss new file mode 100644 index 00000000..432a4dec --- /dev/null +++ b/front/style/cowebsite/_wide-screens.scss @@ -0,0 +1,41 @@ +@include media-breakpoint-down(lg) { + #cowebsite { + right: 0; + top: 0; + width: 50%; + height: 100vh; + display: flex; + visibility: collapse; + transform: translateX(100%); + + &.loading { + visibility: visible; + transform: translateX(0%); + } + + &.opened { + visibility: visible; + transform: translateX(0%); + } + + &.closing { + visibility: visible; + } + + main { + width: 100%; + } + + aside { + width: 30px; + + img { + transform: rotate(90deg); + } + } + + &-aside-holder { + cursor: ew-resize; + } + } +} From 4436db0d3df202b98399f634432bd1f84e038063 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Mon, 10 Jan 2022 10:43:57 +0100 Subject: [PATCH 36/60] Add new cowebsite properties on documention --- docs/maps/api-nav.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index 47ee416e..2743d1ad 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -52,17 +52,17 @@ WA.nav.goToRoom("/_/global/.json#start-layer-2") ### Opening/closing web page in Co-Websites ``` -WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number = 0): Promise +WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number, closable: boolean, lazy: boolean): Promise ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open. -You can have only 5 co-wbesites open simultaneously. +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy +it's to add the cowebsite but don't load it. Example: ```javascript const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/'); -const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1); +const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1, true, true); // ... coWebsite.close(); ``` From 873c33505437e48c1c98db4d4a88d5cd6b16862f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 11 Jan 2022 13:52:38 +0100 Subject: [PATCH 37/60] Removing too wide border in videos --- front/src/Components/MyCamera.svelte | 17 ++++------- front/src/Components/Video/MediaBox.svelte | 29 ++++++++++++++----- .../src/Components/Video/VideoMediaBox.svelte | 4 +++ 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte index c9062008..06a86e58 100644 --- a/front/src/Components/MyCamera.svelte +++ b/front/src/Components/MyCamera.svelte @@ -65,8 +65,12 @@ max-height: 20%; transition: transform 1000ms; padding: 0; - background-color: #00000099; overflow: hidden; + line-height: 0; + + &.nes-container.is-rounded { + border-image-outset: 1; + } } .my-cam-video-container.hide { @@ -76,6 +80,7 @@ .my-cam-video { background-color: #00000099; max-height: 20vh; + max-width: max(25vw, 150px); width: 100%; -webkit-transform: scaleX(-1); transform: scaleX(-1); @@ -86,14 +91,4 @@ color: white; padding: 40px 20px; } - - @include media-breakpoint-up(md) { - .my-cam-video { - width: 150px; - } - - .my-cam-video-container.hide { - right: -160px; - } - } diff --git a/front/src/Components/Video/MediaBox.svelte b/front/src/Components/Video/MediaBox.svelte index a59420c2..eca34aa5 100644 --- a/front/src/Components/Video/MediaBox.svelte +++ b/front/src/Components/Video/MediaBox.svelte @@ -5,6 +5,7 @@ import { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer"; import LocalStreamMediaBox from "./LocalStreamMediaBox.svelte"; import type { Streamable } from "../../Stores/StreamableCollectionStore"; + import PixelContainer from "../Container/PixelContainer.svelte"; export let streamable: Streamable; export let isHightlighted = false; @@ -19,13 +20,15 @@ class:mozaic-full-width={mozaicFullWidth} class:mozaic-quarter={mozaicQuarter} > - {#if streamable instanceof VideoPeer} - - {:else if streamable instanceof ScreenSharingPeer} - - {:else} - - {/if} +
+ {#if streamable instanceof VideoPeer} + + {:else if streamable instanceof ScreenSharingPeer} + + {:else} + + {/if} +
From 78e816c6fb282e193912a0773e8ef64e8c971f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 11 Jan 2022 13:53:01 +0100 Subject: [PATCH 38/60] Removing optimization in dev to get back normal compilation times in watch mode --- front/webpack.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/webpack.config.ts b/front/webpack.config.ts index 3185b0a6..d6e54c68 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -163,7 +163,7 @@ module.exports = { mainFields: ["svelte", "browser", "module", "main"], }, optimization: { - minimize: true, + minimize: isProduction, minimizer: [new CssMinimizerPlugin(), "..."], }, output: { From 06dca9813c1a2d652e43819ff3a065d2e0843b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 12 Jan 2022 15:36:24 +0100 Subject: [PATCH 39/60] Removing weird border in cowebsites and cowebsite button --- .../CoWebsiteThumbnailSlot.svelte | 28 ++++++++++++++++++- .../Layouts/PresentationLayout.svelte | 6 +++- front/src/Components/Video/MediaBox.svelte | 1 - 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte index a3895a52..2fee5fd0 100644 --- a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte +++ b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte @@ -46,7 +46,7 @@
diff --git a/front/src/Components/Video/MediaBox.svelte b/front/src/Components/Video/MediaBox.svelte index eca34aa5..2abfa953 100644 --- a/front/src/Components/Video/MediaBox.svelte +++ b/front/src/Components/Video/MediaBox.svelte @@ -5,7 +5,6 @@ import { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer"; import LocalStreamMediaBox from "./LocalStreamMediaBox.svelte"; import type { Streamable } from "../../Stores/StreamableCollectionStore"; - import PixelContainer from "../Container/PixelContainer.svelte"; export let streamable: Streamable; export let isHightlighted = false; From a4b4710f878532495fa6c938ce60f9b0420225f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 17 Jan 2022 10:04:54 +0100 Subject: [PATCH 40/60] Menu bar for buttons --- .../dist/resources/logos/cowebsite-swipe.svg | 1 + front/src/Components/CameraControls.svelte | 1 + .../CoWebsiteThumbnailSlot.svelte | 115 ++++++++++++------ .../EmbedScreens/CoWebsitesContainer.svelte | 16 ++- .../Layouts/PresentationLayout.svelte | 34 +----- front/src/Components/MainLayout.svelte | 14 ++- front/src/Components/MyCamera.svelte | 3 + front/src/Stores/CoWebsiteStore.ts | 15 --- front/src/WebRtc/CoWebsiteManager.ts | 33 ++++- front/style/cowebsite/_global.scss | 8 ++ 10 files changed, 146 insertions(+), 94 deletions(-) create mode 100644 front/dist/resources/logos/cowebsite-swipe.svg diff --git a/front/dist/resources/logos/cowebsite-swipe.svg b/front/dist/resources/logos/cowebsite-swipe.svg new file mode 100644 index 00000000..1d4f9ebc --- /dev/null +++ b/front/dist/resources/logos/cowebsite-swipe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/Components/CameraControls.svelte b/front/src/Components/CameraControls.svelte index adac23b0..32f4e2fc 100644 --- a/front/src/Components/CameraControls.svelte +++ b/front/src/Components/CameraControls.svelte @@ -139,6 +139,7 @@ text-align: center; align-content: center; justify-content: flex-end; + z-index: 251; &:hover { div.hide { diff --git a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte index 2fee5fd0..a668bcf6 100644 --- a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte +++ b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte @@ -2,7 +2,7 @@ import { onMount } from "svelte"; import { ICON_URL } from "../../Enum/EnvironmentVariable"; - import { mainCoWebsite } from "../../Stores/CoWebsiteStore"; + import { coWebsitesNotAsleep, mainCoWebsite } from "../../Stores/CoWebsiteStore"; import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; import type { CoWebsite } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; @@ -22,14 +22,22 @@ icon.alt = urlObject.hostname; }); - async function toggleHighlightEmbedScreen() { + async function onClick() { if (vertical) { coWebsiteManager.goToMain(coWebsite); } else if ($mainCoWebsite) { - highlightedEmbedScreen.toggleHighlight({ - type: "cowebsite", - embed: coWebsite, - }); + if ($mainCoWebsite.iframe.id === coWebsite.iframe.id) { + const coWebsites = $coWebsitesNotAsleep; + const newMain = $highlightedEmbedScreen ?? coWebsites.length > 1 ? coWebsites[1] : undefined; + if (newMain) { + coWebsiteManager.goToMain(coWebsite); + } + } else { + highlightedEmbedScreen.toggleHighlight({ + type: "cowebsite", + embed: coWebsite, + }); + } } if ($state === "asleep") { @@ -42,6 +50,16 @@ function noDrag() { return false; } + + let isHighlight: boolean = false; + let isMain: boolean = false; + $: { + isMain = $mainCoWebsite !== undefined && $mainCoWebsite.iframe === coWebsite.iframe; + isHighlight = + $highlightedEmbedScreen !== null && + $highlightedEmbedScreen.type === "cowebsite" && + $highlightedEmbedScreen.embed.iframe === coWebsite.iframe; + }
diff --git a/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte b/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte index 95000daf..03cca902 100644 --- a/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte +++ b/front/src/Components/EmbedScreens/CoWebsitesContainer.svelte @@ -1,13 +1,13 @@ -{#if $coWebsiteThumbails.length > 0} +{#if $coWebsites.length > 0}
- {#each [...$coWebsiteThumbails.values()] as coWebsite, index (coWebsite.iframe.id)} + {#each [...$coWebsites.values()] as coWebsite, index (coWebsite.iframe.id)} {/each}
@@ -16,13 +16,21 @@ diff --git a/front/src/Components/MainLayout.svelte b/front/src/Components/MainLayout.svelte index 6175f540..cf273e50 100644 --- a/front/src/Components/MainLayout.svelte +++ b/front/src/Components/MainLayout.svelte @@ -21,7 +21,7 @@ import ReportMenu from "./ReportMenu/ReportMenu.svelte"; import VisitCard from "./VisitCard/VisitCard.svelte"; import WarningContainer from "./WarningContainer/WarningContainer.svelte"; - import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; + import { isMediaBreakpointDown, isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; import CoWebsitesContainer from "./EmbedScreens/CoWebsitesContainer.svelte"; import FollowMenu from "./FollowMenu/FollowMenu.svelte"; import { followStateStore } from "../Stores/FollowStore"; @@ -39,10 +39,12 @@ let mainLayout: HTMLDivElement; - let displayCoWebsiteContainer = isMediaBreakpointUp("md"); + let displayCoWebsiteContainerMd = isMediaBreakpointUp("md"); + let displayCoWebsiteContainerLg = isMediaBreakpointDown("lg"); const resizeObserver = new ResizeObserver(() => { - displayCoWebsiteContainer = isMediaBreakpointUp("md"); + displayCoWebsiteContainerMd = isMediaBreakpointUp("md"); + displayCoWebsiteContainerLg = isMediaBreakpointDown("lg"); }); onMount(() => { @@ -56,7 +58,7 @@ {/if} - {#if $embedScreenLayout === LayoutMode.VideoChat || displayCoWebsiteContainer} + {#if $embedScreenLayout === LayoutMode.VideoChat || displayCoWebsiteContainerMd} {/if} @@ -118,6 +120,10 @@
+ {#if displayCoWebsiteContainerLg} + + {/if} + {#if $layoutManagerActionVisibilityStore} {/if} diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte index 06a86e58..e84d763d 100644 --- a/front/src/Components/MyCamera.svelte +++ b/front/src/Components/MyCamera.svelte @@ -65,8 +65,11 @@ max-height: 20%; transition: transform 1000ms; padding: 0; + background-color: rgba(#000000, 0.6); + background-clip: content-box; overflow: hidden; line-height: 0; + z-index: 250; &.nes-container.is-rounded { border-image-outset: 1; diff --git a/front/src/Stores/CoWebsiteStore.ts b/front/src/Stores/CoWebsiteStore.ts index 57779e58..4227c405 100644 --- a/front/src/Stores/CoWebsiteStore.ts +++ b/front/src/Stores/CoWebsiteStore.ts @@ -1,6 +1,5 @@ import { derived, get, writable } from "svelte/store"; import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; -import { highlightedEmbedScreen } from "./EmbedScreensStore"; function createCoWebsiteStore() { const { subscribe, set, update } = writable(Array()); @@ -50,17 +49,3 @@ export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) => export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) => $coWebsites.find((coWebsite) => get(coWebsite.state) !== "asleep") ); - -export const coWebsiteThumbails = derived( - [coWebsites, highlightedEmbedScreen, mainCoWebsite], - ([$coWebsites, highlightedEmbedScreen, $mainCoWebsite]) => - $coWebsites.filter((coWebsite, index) => { - return ( - (!$mainCoWebsite || $mainCoWebsite.iframe.id !== coWebsite.iframe.id) && - (!highlightedEmbedScreen || - highlightedEmbedScreen.type !== "cowebsite" || - (highlightedEmbedScreen.type === "cowebsite" && - highlightedEmbedScreen.embed.iframe.id !== coWebsite.iframe.id)) - ); - }) -); diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index ae61f35d..70f70227 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -25,6 +25,7 @@ export const cowebsiteCloseButtonId = "cowebsite-close"; const cowebsiteFullScreenButtonId = "cowebsite-fullscreen"; const cowebsiteOpenFullScreenImageId = "cowebsite-fullscreen-open"; const cowebsiteCloseFullScreenImageId = "cowebsite-fullscreen-close"; +const cowebsiteSwipeButtonId = "cowebsite-swipe"; const cowebsiteSlotBaseDomId = "cowebsite-slot-"; const animationTime = 500; //time used by the css transitions, in ms. @@ -118,8 +119,8 @@ class CoWebsiteManager { this.resizeObserver.observe(this.cowebsiteDom); this.resizeObserver.observe(this.gameOverlayDom); - const buttonCloseCoWebsites = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); - buttonCloseCoWebsites.addEventListener("click", () => { + const buttonCloseCoWebsite = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId); + buttonCloseCoWebsite.addEventListener("click", () => { const coWebsite = this.getMainCoWebsite(); if (!coWebsite) { @@ -142,6 +143,24 @@ class CoWebsiteManager { buttonFullScreenFrame.blur(); this.fullscreen(); }); + + const buttonSwipe = HtmlUtils.getElementByIdOrFail(cowebsiteSwipeButtonId); + + highlightedEmbedScreen.subscribe((value) => { + if (!value || value.type !== "cowebsite") { + buttonSwipe.style.display = "none"; + return; + } + + buttonSwipe.style.display = "block"; + }); + + buttonSwipe.addEventListener("click", () => { + const highlightedEmbed = get(highlightedEmbedScreen); + if (highlightedEmbed?.type === "cowebsite") { + this.goToMain(highlightedEmbed.embed); + } + }); } public getCoWebsiteBuffer(): HTMLDivElement { @@ -671,6 +690,16 @@ class CoWebsiteManager { } this.removeCoWebsiteFromStack(coWebsite); + + const mainCoWebsite = this.getMainCoWebsite(); + + if (mainCoWebsite) { + this.removeHighlightCoWebsite(mainCoWebsite); + this.goToMain(mainCoWebsite); + this.resizeAllIframes(); + } else { + this.closeMain(); + } resolve(); }) ); diff --git a/front/style/cowebsite/_global.scss b/front/style/cowebsite/_global.scss index b9d8e2ee..52ca1e75 100644 --- a/front/style/cowebsite/_global.scss +++ b/front/style/cowebsite/_global.scss @@ -42,6 +42,14 @@ margin-bottom: auto; flex: 1; justify-content: start; + + #cowebsite-swipe { + display: none; + img { + transform: rotate(0deg) !important; + transform: scale(0.5); + } + } } .top-right-btn { From b9ca630a1505223ce28f693960ae35cc3116e4ad Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Tue, 18 Jan 2022 14:37:35 +0100 Subject: [PATCH 41/60] Jitsi cowebsite close on hangup --- front/src/WebRtc/CoWebsiteManager.ts | 3 +- front/src/WebRtc/JitsiFactory.ts | 62 +++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 70f70227..9ca20545 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -618,6 +618,7 @@ class CoWebsiteManager { if (coWebsite.jitsi) { const gameScene = gameManager.getCurrentGameScene(); gameScene.disableMediaBehaviors(); + jitsiFactory.restart(); } this.currentOperationPromise = this.currentOperationPromise @@ -676,7 +677,7 @@ class CoWebsiteManager { () => new Promise((resolve) => { if (coWebsite.jitsi) { - jitsiFactory.stop(); + jitsiFactory.destroy(); const gameScene = gameManager.getCurrentGameScene(); gameScene.enableMediaBehaviors(); } diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 8f9524a2..b273a64c 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -7,6 +7,7 @@ interface jitsiConfigInterface { startWithAudioMuted: boolean; startWithVideoMuted: boolean; prejoinPageEnabled: boolean; + disableDeepLinking: boolean; } interface JitsiOptions { @@ -40,6 +41,7 @@ const getDefaultConfig = (): jitsiConfigInterface => { startWithAudioMuted: !get(requestedMicrophoneState), startWithVideoMuted: !get(requestedCameraState), prejoinPageEnabled: false, + disableDeepLinking: false, }; }; @@ -176,13 +178,23 @@ class JitsiFactory { const doResolve = (): void => { const iframe = coWebsiteManager.getCoWebsiteBuffer().querySelector('[id*="jitsi" i]'); - if (iframe) { - coWebsiteManager.addCoWebsiteFromIframe(iframe, false, undefined, 0, false, true); + if (iframe && this.jitsiApi) { + const coWebsite = coWebsiteManager.addCoWebsiteFromIframe(iframe, false, undefined, 0, false, true); + + this.jitsiApi.addListener("videoConferenceLeft", () => { + this.closeOrUnload(coWebsite); + }); + + this.jitsiApi.addListener("readyToClose", () => { + this.closeOrUnload(coWebsite); + }); } coWebsiteManager.resizeAllIframes(); }; + this.jitsiApi = undefined; + options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options); @@ -192,6 +204,44 @@ class JitsiFactory { this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); } + private closeOrUnload = function (coWebsite: CoWebsite) { + if (coWebsite.closable) { + coWebsiteManager.closeCoWebsite(coWebsite).catch(() => { + console.error("Error during closing a Jitsi Meet"); + }); + } else { + coWebsiteManager.unloadCoWebsite(coWebsite).catch(() => { + console.error("Error during unloading a Jitsi Meet"); + }); + } + }; + + public restart() { + if (!this.jitsiApi) { + return; + } + + this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); + this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); + + const coWebsite = coWebsiteManager.searchJitsi(); + console.log("jitsi api ", this.jitsiApi); + console.log("iframe cowebsite", coWebsite?.iframe); + + if (!coWebsite) { + this.destroy(); + return; + } + + this.jitsiApi.addListener("videoConferenceLeft", () => { + this.closeOrUnload(coWebsite); + }); + + this.jitsiApi.addListener("readyToClose", () => { + this.closeOrUnload(coWebsite); + }); + } + public stop() { if (!this.jitsiApi) { return; @@ -199,6 +249,14 @@ class JitsiFactory { this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback); this.jitsiApi.removeListener("videoMuteStatusChanged", this.videoCallback); + } + + public destroy() { + if (!this.jitsiApi) { + return; + } + + this.stop(); this.jitsiApi?.dispose(); } From 68489956391bf00bf273d4540dc41926fcccdf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 25 Jan 2022 14:52:37 +0100 Subject: [PATCH 42/60] Adding Icon server to CD env. --- deeployer.libsonnet | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 0bbda264..91b79623 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -83,7 +83,8 @@ "SECRET_JITSI_KEY": env.SECRET_JITSI_KEY, "TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443", "JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false", - "START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json" + "START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json", + "ICON_URL": "//icon-"+url, } }, "uploader": { @@ -109,7 +110,15 @@ "redis": { "image": "redis:6", "ports": [6379] - } + }, + "iconserver": { + "image": "matthiasluedtke/iconserver:v3.13.0", + "host": { + "url": "icon-"+url, + "containerPort": 8080, + }, + "ports": [8080] + }, }, "config": { k8sextension(k8sConf):: @@ -210,6 +219,16 @@ } } }, + icon+: { + ingress+: { + spec+: { + tls+: [{ + hosts: ["icon-"+url], + secretName: "certificate-tls" + }] + } + } + }, } } } From 0dc6f41562e61103ae178e3368f2034fa73893fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 25 Jan 2022 15:27:44 +0100 Subject: [PATCH 43/60] Fixing server name in K8S deployment --- deeployer.libsonnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeployer.libsonnet b/deeployer.libsonnet index 91b79623..4012b186 100644 --- a/deeployer.libsonnet +++ b/deeployer.libsonnet @@ -219,7 +219,7 @@ } } }, - icon+: { + iconserver+: { ingress+: { spec+: { tls+: [{ From b01b8b53eb668ed62cb913fcee6372d18d1e61f1 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Tue, 25 Jan 2022 15:24:36 +0100 Subject: [PATCH 44/60] Lowercase warning container hex colors --- front/src/Components/WarningContainer/WarningContainer.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Components/WarningContainer/WarningContainer.svelte b/front/src/Components/WarningContainer/WarningContainer.svelte index b5def355..dd740eb5 100644 --- a/front/src/Components/WarningContainer/WarningContainer.svelte +++ b/front/src/Components/WarningContainer/WarningContainer.svelte @@ -28,8 +28,8 @@ main.warningMain { pointer-events: auto; width: 80%; - background-color: #F9E81E; - color: #14304C; + background-color: #f9e81e; + color: #14304c; text-align: center; position: absolute; From 9efb718545138ce70f9403f5be1bfc92887cf684 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Thu, 27 Jan 2022 15:59:19 +0100 Subject: [PATCH 45/60] Display a loader while cowebsite icon is loading --- .../CoWebsiteThumbnailSlot.svelte | 107 +++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte index a668bcf6..afb1179c 100644 --- a/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte +++ b/front/src/Components/EmbedScreens/CoWebsiteThumbnailSlot.svelte @@ -12,6 +12,7 @@ export let vertical: boolean; let icon: HTMLImageElement; + let iconLoaded = false; let state = coWebsite.state; const coWebsiteUrl = coWebsite.iframe.src; @@ -20,6 +21,9 @@ onMount(() => { icon.src = `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`; icon.alt = urlObject.hostname; + icon.onload = () => { + iconLoaded = true; + }; }); async function onClick() { @@ -72,7 +76,104 @@ class:vertical on:click={onClick} > - + + + + + + + + + + + + + + + + + + + +
From 1572ddc477018a887febac0402697bb5ed964cfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jan 2022 15:50:53 +0000 Subject: [PATCH 46/60] Bump nanoid from 3.1.30 to 3.2.0 in /front Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect ... Signed-off-by: dependabot[bot] --- front/yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/front/yarn.lock b/front/yarn.lock index 89a8a568..329bce4d 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -4259,16 +4259,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.1.23: +nanoid@^3.1.23, nanoid@^3.1.30: version "3.2.0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== -nanoid@^3.1.30: - version "3.1.30" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" - integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" From 12d6d9a50db514129a74ace0e7a923cd91ee3e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 27 Jan 2022 18:38:33 +0100 Subject: [PATCH 47/60] Disabling completely routes if admin token not configured --- back/src/Controller/DebugController.ts | 3 +++ back/src/Enum/EnvironmentVariable.ts | 2 +- pusher/src/Controller/AdminController.ts | 6 ++++++ pusher/src/Controller/DebugController.ts | 3 +++ pusher/src/Controller/IoSocketController.ts | 6 ++++-- pusher/src/Enum/EnvironmentVariable.ts | 4 ++-- pusher/src/Services/AdminApi.ts | 3 +++ 7 files changed, 22 insertions(+), 5 deletions(-) diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index e9fc0743..f571d6b2 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -15,6 +15,9 @@ export class DebugController { (async () => { const query = parse(req.getQuery()); + if (ADMIN_API_TOKEN === "") { + return res.writeStatus("401 Unauthorized").end("No token configured!"); + } if (query.token !== ADMIN_API_TOKEN) { return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); } diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index f7f0b084..f0f46a62 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -2,7 +2,7 @@ const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIM const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48; const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false; const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; -const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; +const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || ""; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ""; diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts index 85116df9..26556698 100644 --- a/pusher/src/Controller/AdminController.ts +++ b/pusher/src/Controller/AdminController.ts @@ -31,6 +31,9 @@ export class AdminController extends BaseController { const token = req.getHeader("admin-token"); const body = await res.json(); + if (ADMIN_API_TOKEN === "") { + return res.writeStatus("401 Unauthorized").end("No token configured!"); + } if (token !== ADMIN_API_TOKEN) { console.error("Admin access refused for token: " + token); res.writeStatus("401 Unauthorized").end("Incorrect token"); @@ -78,6 +81,9 @@ export class AdminController extends BaseController { const token = req.getHeader("admin-token"); const body = await res.json(); + if (ADMIN_API_TOKEN === "") { + return res.writeStatus("401 Unauthorized").end("No token configured!"); + } if (token !== ADMIN_API_TOKEN) { console.error("Admin access refused for token: " + token); res.writeStatus("401 Unauthorized").end("Incorrect token"); diff --git a/pusher/src/Controller/DebugController.ts b/pusher/src/Controller/DebugController.ts index a4f22d80..26b229b6 100644 --- a/pusher/src/Controller/DebugController.ts +++ b/pusher/src/Controller/DebugController.ts @@ -15,6 +15,9 @@ export class DebugController { this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { const query = parse(req.getQuery()); + if (ADMIN_API_TOKEN === "") { + return res.writeStatus("401 Unauthorized").end("No token configured!"); + } if (query.token !== ADMIN_API_TOKEN) { return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 9d1f3887..6db53403 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -29,7 +29,7 @@ import { AdminSocketTokenData, jwtTokenManager, tokenInvalidException } from ".. import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { SocketManager, socketManager } from "../Services/SocketManager"; import { emitInBatch } from "../Services/IoSocketHelpers"; -import { ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; import { Zone } from "_Model/Zone"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; @@ -42,7 +42,9 @@ export class IoSocketController { constructor(private readonly app: TemplatedApp) { this.ioConnection(); - this.adminRoomSocket(); + if (ADMIN_SOCKETS_TOKEN) { + this.adminRoomSocket(); + } } adminRoomSocket() { diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index 127af38f..b3415a82 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -3,8 +3,8 @@ const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLER const API_URL = process.env.API_URL || ""; const ADMIN_API_URL = process.env.ADMIN_API_URL || ""; const ADMIN_URL = process.env.ADMIN_URL || ""; -const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken"; -export const ADMIN_SOCKETS_TOKEN = process.env.ADMIN_SOCKETS_TOKEN || "myapitoken"; +const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || ""; +export const ADMIN_SOCKETS_TOKEN = process.env.ADMIN_SOCKETS_TOKEN || ""; const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ""; diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index f97e144d..c72a6ba8 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -81,6 +81,9 @@ class AdminApi { reporterUserUuid: string, reportWorldSlug: string ) { + if (!ADMIN_API_URL) { + return Promise.reject(new Error("No admin backoffice set!")); + } return Axios.post( `${ADMIN_API_URL}/api/report`, { From f5464fb1af6131ff12cc0044489e1e7e1781f1c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 27 Jan 2022 18:46:28 +0100 Subject: [PATCH 48/60] Fixing bad return in controller --- pusher/src/Controller/AdminController.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pusher/src/Controller/AdminController.ts b/pusher/src/Controller/AdminController.ts index 26556698..6fbf5721 100644 --- a/pusher/src/Controller/AdminController.ts +++ b/pusher/src/Controller/AdminController.ts @@ -32,7 +32,8 @@ export class AdminController extends BaseController { const body = await res.json(); if (ADMIN_API_TOKEN === "") { - return res.writeStatus("401 Unauthorized").end("No token configured!"); + res.writeStatus("401 Unauthorized").end("No token configured!"); + return; } if (token !== ADMIN_API_TOKEN) { console.error("Admin access refused for token: " + token); @@ -82,7 +83,8 @@ export class AdminController extends BaseController { const body = await res.json(); if (ADMIN_API_TOKEN === "") { - return res.writeStatus("401 Unauthorized").end("No token configured!"); + res.writeStatus("401 Unauthorized").end("No token configured!"); + return; } if (token !== ADMIN_API_TOKEN) { console.error("Admin access refused for token: " + token); From b38081515bea9bd9bb9a22b88c3f795f0e6bf7b7 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Thu, 27 Jan 2022 20:10:29 +0100 Subject: [PATCH 49/60] Change style of woka and name on video container Signed-off-by: Gregoire Parant --- .../src/Components/Video/VideoMediaBox.svelte | 29 ++------- front/style/style.scss | 60 +++++++++++++++---- 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/front/src/Components/Video/VideoMediaBox.svelte b/front/src/Components/Video/VideoMediaBox.svelte index 2994041f..3359bc19 100644 --- a/front/src/Components/Video/VideoMediaBox.svelte +++ b/front/src/Components/Video/VideoMediaBox.svelte @@ -58,15 +58,12 @@
{/if} - - {peer.userName} -
+ + {peer.userName} +
+ +
{#if $constraintStore && $constraintStore.audio === false} Date: Thu, 27 Jan 2022 20:33:33 +0100 Subject: [PATCH 50/60] Fix style Signed-off-by: Gregoire Parant --- front/style/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/style/style.scss b/front/style/style.scss index a0719d55..b3a55828 100644 --- a/front/style/style.scss +++ b/front/style/style.scss @@ -69,7 +69,7 @@ body .message-info.warning{ i { position: absolute; width: 100px; - height: 100px; + height: auto; left: -6px; top: calc(100% - 28px); text-align: center; @@ -80,6 +80,7 @@ body .message-info.warning{ background-color: white; border: solid 3px black; border-radius: 8px; + font-style: normal; } } From d1c549335b9d8358dd07166f527c599f8214b397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 27 Jan 2022 19:02:54 +0100 Subject: [PATCH 51/60] Fix the way links are encoded in chat Closes #1776 --- front/src/WebRtc/HtmlUtils.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts index c266d558..1c6b8817 100644 --- a/front/src/WebRtc/HtmlUtils.ts +++ b/front/src/WebRtc/HtmlUtils.ts @@ -40,6 +40,7 @@ export class HtmlUtils { const urlRegex = /(https?:\/\/[^\s]+)/g; text = HtmlUtils.escapeHtml(text); return text.replace(urlRegex, (url: string) => { + url = HtmlUtils.htmlDecode(url); const link = document.createElement("a"); link.href = url; link.target = "_blank"; @@ -50,6 +51,15 @@ export class HtmlUtils { }); } + private static htmlDecode(input: string): string { + const doc = new DOMParser().parseFromString(input, "text/html"); + const text = doc.documentElement.textContent; + if (text === null) { + throw new Error("Unexpected non parseable string"); + } + return text; + } + public static isClickedInside(event: MouseEvent, target: HTMLElement): boolean { return !!event.composedPath().find((et) => et === target); } From e43c4cd5aec0d1c9f2c7eeaab35f2adf296fd3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Jan 2022 09:58:24 +0100 Subject: [PATCH 52/60] Fixing a freeze in MapStore on several unsubscribes For some reason (I could not reproduce this in unit tests alas), the unsubscribe function could be called several times in a row, leading to a complete map freeze. Closes #1736 --- front/src/Stores/Utils/MapStore.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/src/Stores/Utils/MapStore.ts b/front/src/Stores/Utils/MapStore.ts index 63c6c819..54c7b793 100644 --- a/front/src/Stores/Utils/MapStore.ts +++ b/front/src/Stores/Utils/MapStore.ts @@ -96,6 +96,7 @@ export class MapStore extends Map implements Readable> { const unsubscribe = storeByKey.subscribe((newMapValue) => { if (unsubscribeDeepStore) { unsubscribeDeepStore(); + unsubscribeDeepStore = undefined; } if (newMapValue === undefined) { set(undefined); @@ -115,6 +116,7 @@ export class MapStore extends Map implements Readable> { unsubscribe(); if (unsubscribeDeepStore) { unsubscribeDeepStore(); + unsubscribeDeepStore = undefined; } }; }); From 9a7140b027cea15cb84d17ab9fb1876306671c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Jan 2022 11:10:47 +0100 Subject: [PATCH 53/60] Fixing users walking infinitely Now, if no event is received from the Pusher after MAX_EXTRAPOLATION_TIME, the moving variable of the user is set to false. So if a client does not send an update on time (for instance if the user switched tab while walking), on the screen of others, the woka will appear to stop. --- front/src/Phaser/Game/PlayerMovement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/PlayerMovement.ts b/front/src/Phaser/Game/PlayerMovement.ts index 274cbee1..fc14078d 100644 --- a/front/src/Phaser/Game/PlayerMovement.ts +++ b/front/src/Phaser/Game/PlayerMovement.ts @@ -41,7 +41,7 @@ export class PlayerMovement { oldX: this.startPosition.x, oldY: this.startPosition.y, direction: this.endPosition.direction, - moving: this.endPosition.moving, + moving: this.isOutdated(tick) ? false : this.endPosition.moving, }; } } From fc8330a3ad856cd6511e59165fd39b96e08d1960 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 28 Jan 2022 12:01:58 +0100 Subject: [PATCH 54/60] Remove hideMyCamera function, not working correctly Signed-off-by: Gregoire Parant --- front/src/Phaser/Game/GameScene.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 5b10f5c3..e24a01a6 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1603,7 +1603,9 @@ ${escapedMessage} this.sharedVariablesManager?.close(); this.embeddedWebsiteManager?.close(); - mediaManager.hideMyCamera(); + //When we leave game, the camera is stop to be reopen after. + // I think that we could keep camera status and the scene can manage camera setup + //mediaManager.hideMyCamera(); for (const iframeEvents of this.iframeSubscriptionList) { iframeEvents.unsubscribe(); From bea99711e4736cade0c6e3a6d4cf110c77f6bb32 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Fri, 28 Jan 2022 15:11:57 +0100 Subject: [PATCH 55/60] Fix i18n error message --- front/src/Phaser/Login/EntryScene.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/front/src/Phaser/Login/EntryScene.ts b/front/src/Phaser/Login/EntryScene.ts index 4d1b0502..f3ab3a08 100644 --- a/front/src/Phaser/Login/EntryScene.ts +++ b/front/src/Phaser/Login/EntryScene.ts @@ -8,8 +8,6 @@ import LL from "../../i18n/i18n-svelte"; import { get } from "svelte/store"; import { localeDetector } from "../../i18n/locales"; -const $LL = get(LL); - export const EntrySceneName = "EntryScene"; /** @@ -43,6 +41,7 @@ export class EntryScene extends Scene { this.scene.start(nextSceneName); }) .catch((err) => { + const $LL = get(LL); if (err.response && err.response.status == 404) { ErrorScene.showError( new WAError( From dfd594ec172810cdd6c7194369ae2b972c66d674 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 28 Jan 2022 18:33:42 +0100 Subject: [PATCH 56/60] Fix screen sharing spinner Check if the peer connection is already connected status. In this case, the status store must be set to 'connected'. In the case or player A send stream and player B send a stream, it's same peer connection, also the status must be changed to connect. Signed-off-by: Gregoire Parant --- front/src/Phaser/Game/GameScene.ts | 1 + front/src/WebRtc/ScreenSharingPeer.ts | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index e24a01a6..23a211a6 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1605,6 +1605,7 @@ ${escapedMessage} //When we leave game, the camera is stop to be reopen after. // I think that we could keep camera status and the scene can manage camera setup + //TODO find wy chrome don't manage correctly a multiple ask mediaDevices //mediaManager.hideMyCamera(); for (const iframeEvents of this.iframeSubscriptionList) { diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 738e2515..292dc35d 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -2,7 +2,7 @@ import type * as SimplePeerNamespace from "simple-peer"; import type { RoomConnection } from "../Connexion/RoomConnection"; import { MESSAGE_TYPE_CONSTRAINT, PeerStatus } from "./VideoPeer"; import type { UserSimplePeerInterface } from "./SimplePeer"; -import { Readable, readable } from "svelte/store"; +import { Readable, readable, writable, Writable } from "svelte/store"; import { getIceServersConfig } from "../Components/Video/utils"; import { highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils"; @@ -22,7 +22,7 @@ export class ScreenSharingPeer extends Peer { public readonly userId: number; public readonly uniqueId: string; public readonly streamStore: Readable; - public readonly statusStore: Readable; + public readonly statusStore: Writable; constructor( user: UserSimplePeerInterface, @@ -70,7 +70,7 @@ export class ScreenSharingPeer extends Peer { }; }); - this.statusStore = readable("connecting", (set) => { + this.statusStore = writable("connecting", (set) => { const onConnect = () => { set("connected"); }; @@ -141,6 +141,11 @@ export class ScreenSharingPeer extends Peer { if (!stream) { this.isReceivingStream = false; } else { + //Check if the peer connection is already in connect status. In this case, the status store must be set to 'connected'. + //In the case or player A send stream and player B send a stream, it's same peer connection, also the status must be changed to connected. + if (this._connected) { + this.statusStore.set("connected"); + } this.isReceivingStream = true; } } From 15be76655fca1f0d60b029648059fb38d06a6947 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 28 Jan 2022 18:42:48 +0100 Subject: [PATCH 57/60] Add todo to improve status management Signed-off-by: Gregoire Parant --- front/src/WebRtc/ScreenSharingPeer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index 292dc35d..3f953709 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -141,8 +141,9 @@ export class ScreenSharingPeer extends Peer { if (!stream) { this.isReceivingStream = false; } else { - //Check if the peer connection is already in connect status. In this case, the status store must be set to 'connected'. - //In the case or player A send stream and player B send a stream, it's same peer connection, also the status must be changed to connected. + //Check if the peer connection is already connected status. In this case, the status store must be set to 'connected'. + //In the case or player A send stream and player B send a stream, it's same peer connection, also the status must be changed to connect. + //TODO add event listening when the stream is ready for displaying and change the status if (this._connected) { this.statusStore.set("connected"); } From 31b7b5aa0854108efe141c8a5088a76dbfe64eb8 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 28 Jan 2022 19:17:40 +0100 Subject: [PATCH 58/60] Add name on screen sharing Signed-off-by: Gregoire Parant --- .../Components/Video/ScreenSharingMediaBox.svelte | 12 +++++++++--- front/src/Components/Video/VideoMediaBox.svelte | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/front/src/Components/Video/ScreenSharingMediaBox.svelte b/front/src/Components/Video/ScreenSharingMediaBox.svelte index 022770bb..9b27bc8d 100644 --- a/front/src/Components/Video/ScreenSharingMediaBox.svelte +++ b/front/src/Components/Video/ScreenSharingMediaBox.svelte @@ -30,10 +30,11 @@ {#if $statusStore === "error"}
{/if} - {#if $streamStore === null} - {name} - {:else} + {#if $streamStore !== null} + + {name} +