diff --git a/.env.template b/.env.template
index 34537b6b..0bd7bf6d 100644
--- a/.env.template
+++ b/.env.template
@@ -5,7 +5,7 @@ JITSI_PRIVATE_MODE=false
JITSI_ISS=
SECRET_JITSI_KEY=
ADMIN_API_TOKEN=123
-START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
+START_ROOM_URL=/_/global/maps.workadventure.localhost/starter/map.json
# If your Turn server is configured to use the Turn REST API, you should put the shared auth secret here.
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
# Keep empty if you are sharing hard coded / clear text credentials.
@@ -22,3 +22,10 @@ MAX_USERNAME_LENGTH=8
OPID_CLIENT_ID=
OPID_CLIENT_SECRET=
OPID_CLIENT_ISSUER=
+OPID_CLIENT_REDIRECT_URL=
+OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen
+OPID_PROFILE_SCREEN_PROVIDER=
+DISABLE_ANONYMOUS=
+
+# If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want
+CONTACT_URL=
\ No newline at end of file
diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index e33346d0..9c91e0ca 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -190,6 +190,8 @@ jobs:
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
+ POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
+ POSTHOG_URL: ${{ secrets.POSTHOG_URL }}
with:
namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml
new file mode 100644
index 00000000..4360d5e1
--- /dev/null
+++ b/.github/workflows/end_to_end_tests.yml
@@ -0,0 +1,51 @@
+# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
+
+name: "End to end tests"
+
+on:
+ push:
+ branches:
+ - master
+ - develop
+ pull_request:
+
+jobs:
+
+ end-to-end-tests:
+ name: "End-to-end testcafe tests"
+
+ runs-on: "ubuntu-latest"
+
+ steps:
+ - name: "Checkout"
+ uses: "actions/checkout@v2.0.0"
+
+ - name: "Setup .env file"
+ run: cp .env.template .env
+
+ - name: "Edit ownership of file for test cases"
+ run: sudo chown 1000:1000 -R .
+
+ - name: "Start environment"
+ run: docker-compose up -d
+
+ - name: "Wait for environment to build (and downloading testcafe image)"
+ run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -m 1 "Compiled successfully"
+
+ - name: "Run tests"
+ run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe
+
+ - name: Upload failed tests
+ if: ${{ failure() }}
+ uses: actions/upload-artifact@v2
+ with:
+ name: my-artifact
+ path: './tests/screenshots/'
+
+ - name: Display state
+ if: ${{ failure() }}
+ run: docker-compose ps
+
+ - name: Display logs
+ if: ${{ failure() }}
+ run: docker-compose logs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c0bd7335..e33a4f97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,50 @@
## Version develop
+### Updates
+- Added multi Co-Website management
+
+### Bugfix
+- Moving a discussion over a user will now add this user to the discussion
+- Being in a silent zone new forces mediaConstraints to false (#1508)
+- Fixes for the emote menu (#1501)
+- Fixing chat message attributed to wrong user (#1507 #1528)
+
+## Version 1.5.0
+### Updates
+- Added support for login with OpenID Connect
+- New scripting library available to extend WorkAdventure: see [Scripting API Extra](https://github.com/workadventure/scripting-api-extra/)
+- New menu design!
+- New `openTab` property (#1419)
+- Possible integration with Posthog (#1458)
+
+### Bugfix
+- Fixing layers flattened several times (#1427 @Lurkars)
+- Fixing CSS of video elements
+- Chat now scrolls to bottom when opened (#1450)
+- Fixing silent zone not respected when exiting from Jitsi (#1456)
+- Fixing "yarn install" failing because of missing rights on some Docker installs (#1457)
+- Fixing audio not shut down when exiting a room (#1459)
+
+### Misc
+- Finished migrating "Build your map" documentation into the "/docs" directory of this repository (#1417 #1385)
+- Refactoring documentation (dedicated page for variables) (#1414)
+- Front container code is now completely linted (#1413)
+
+## Version 1.4.15
+
### Updates
- New scripting API features :
- Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu` to add a custom menu or an iframe to the menu.
-- New `jitsiWidth` parameter to set the width of Jitsi
+- New `jitsiWidth` parameter to set the width of Jitsi and Cowebsite (#1398 @tabascoeye)
- Refactored the way videos are displayed to better cope for vertical videos (on mobile)
- Fixing reconnection issues after 5 minutes of an inactive tab on Google Chrome
+- Changes performed in `WA.room.setPropertyLayer` now have a real-time impact (#1395)
+
+### Bugfixes
+- Fixing streams in bubbles sometimes improperly muted when there are more than 2 people in the bubble (#1400 #1402)
+- Properly displaying carriage returns in popups (#1388)
+- `WA.state` now answers correctly to "in" keyword (#1393)
+- Variables can now be nested in group layers (#1406)
## Version 1.4.14
diff --git a/README-INTRO.jpg b/README-INTRO.jpg
deleted file mode 100644
index 989b8e78..00000000
Binary files a/README-INTRO.jpg and /dev/null differ
diff --git a/README-LOGO.svg b/README-LOGO.svg
new file mode 100644
index 00000000..f66f0603
--- /dev/null
+++ b/README-LOGO.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/README-MAP.png b/README-MAP.png
new file mode 100644
index 00000000..a592d222
Binary files /dev/null and b/README-MAP.png differ
diff --git a/README.md b/README.md
index ba9e70ce..427c514c 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,18 @@
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
-![WorkAdventure landscape image](README-INTRO.jpg)
+![WorkAdventure logo](README-LOGO.svg)
+![WorkAdventure office image](README-MAP.png)
-Demo here : [https://workadventu.re/](https://workadventu.re/).
+Live demo [here](https://play.workadventu.re/@/tcm/workadventure/wa-village).
-# Work Adventure
+# WorkAdventure
-Work Adventure is a web-based collaborative workspace for small to medium teams (2-100 people) presented in the form of a
+WorkAdventure is a web-based collaborative workspace presented in the form of a
16-bit video game.
-In Work Adventure, you can move around your office and talk to your colleagues (using a video-chat feature that is
-triggered when you move next to a colleague).
+In WorkAdventure you can move around your office and talk to your colleagues (using a video-chat system, triggered when you approach someone).
+See more features for your virtual office: https://workadventu.re/virtual-office
## Setting up a development environment
@@ -20,6 +21,7 @@ Install Docker.
Run:
```
+cp .env.template .env
docker-compose up -d
```
@@ -36,7 +38,7 @@ Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
```
Note: If on the first run you get a page with "network error". Try to ``docker-compose stop`` , then ``docker-compose start``.
-Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.testing and accepting them.
+Note 2: If you are still getting "network error". Make sure you are authorizing the self-signed certificate by entering https://pusher.workadventure.localhost and accepting them.
### MacOS developers, your environment with Vagrant
diff --git a/back/package.json b/back/package.json
index bb54d624..aacd0c96 100644
--- a/back/package.json
+++ b/back/package.json
@@ -40,8 +40,8 @@
},
"homepage": "https://github.com/thecodingmachine/workadventure#readme",
"dependencies": {
- "@workadventure/tiled-map-type-guard": "^1.0.2",
- "axios": "^0.21.1",
+ "@workadventure/tiled-map-type-guard": "^1.0.3",
+ "axios": "^0.21.2",
"busboy": "^0.3.1",
"circular-json": "^0.5.9",
"debug": "^4.3.1",
diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts
index 493f97a2..f7f0b084 100644
--- a/back/src/Enum/EnvironmentVariable.ts
+++ b/back/src/Enum/EnvironmentVariable.ts
@@ -14,6 +14,7 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
export const REDIS_HOST = process.env.REDIS_HOST || undefined;
export const REDIS_PORT = parseInt(process.env.REDIS_PORT || "6379") || 6379;
export const REDIS_PASSWORD = process.env.REDIS_PASSWORD || undefined;
+export const STORE_VARIABLES_FOR_LOCAL_MAPS = process.env.STORE_VARIABLES_FOR_LOCAL_MAPS === "true";
export {
MINIMUM_DISTANCE,
diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts
index 5667146a..5c114f19 100644
--- a/back/src/Model/GameRoom.ts
+++ b/back/src/Model/GameRoom.ts
@@ -26,6 +26,7 @@ import { VariablesManager } from "../Services/VariablesManager";
import { ADMIN_API_URL } from "../Enum/EnvironmentVariable";
import { LocalUrlError } from "../Services/LocalUrlError";
import { emitErrorOnRoomSocket } from "../Services/MessageHelpers";
+import { VariableError } from "../Services/VariableError";
export type ConnectCallback = (user: User, group: Group) => void;
export type DisconnectCallback = (user: User, group: Group) => void;
@@ -181,6 +182,7 @@ export class GameRoom {
private updateUserGroup(user: User): void {
user.group?.updatePosition();
+ user.group?.searchForNearbyUsers();
if (user.silent) {
return;
@@ -206,6 +208,7 @@ export class GameRoom {
const group: Group = new Group(
this.roomUrl,
[user, closestUser],
+ this.groupRadius,
this.connectCallback,
this.disconnectCallback,
this.positionNotifier
@@ -334,30 +337,62 @@ export class GameRoom {
// First, let's check if "user" is allowed to modify the variable.
const variableManager = await this.getVariableManager();
- const readableBy = variableManager.setVariable(name, value, user);
+ try {
+ const readableBy = variableManager.setVariable(name, value, user);
- // If the variable was not changed, let's not dispatch anything.
- if (readableBy === false) {
- return;
- }
+ // If the variable was not changed, let's not dispatch anything.
+ if (readableBy === false) {
+ return;
+ }
- // TODO: should we batch those every 100ms?
- const variableMessage = new VariableWithTagMessage();
- variableMessage.setName(name);
- variableMessage.setValue(value);
- if (readableBy) {
- variableMessage.setReadableby(readableBy);
- }
+ // TODO: should we batch those every 100ms?
+ const variableMessage = new VariableWithTagMessage();
+ variableMessage.setName(name);
+ variableMessage.setValue(value);
+ if (readableBy) {
+ variableMessage.setReadableby(readableBy);
+ }
- const subMessage = new SubToPusherRoomMessage();
- subMessage.setVariablemessage(variableMessage);
+ const subMessage = new SubToPusherRoomMessage();
+ subMessage.setVariablemessage(variableMessage);
- const batchMessage = new BatchToPusherRoomMessage();
- batchMessage.addPayload(subMessage);
+ const batchMessage = new BatchToPusherRoomMessage();
+ batchMessage.addPayload(subMessage);
- // Dispatch the message on the room listeners
- for (const socket of this.roomListeners) {
- socket.write(batchMessage);
+ // Dispatch the message on the room listeners
+ for (const socket of this.roomListeners) {
+ socket.write(batchMessage);
+ }
+ } catch (e) {
+ if (e instanceof VariableError) {
+ // Ok, we have an error setting a variable. Either the user is trying to hack the map... or the map
+ // is not up to date. So let's try to reload the map from scratch.
+ if (this.variableManagerLastLoad === undefined) {
+ throw e;
+ }
+ const lastLoaded = new Date().getTime() - this.variableManagerLastLoad.getTime();
+ if (lastLoaded < 10000) {
+ console.log(
+ 'An error occurred while setting the "' +
+ name +
+ "\" variable. But we tried to reload the map less than 10 seconds ago, so let's fail."
+ );
+ // Do not try to reload if we tried to reload less than 10 seconds ago.
+ throw e;
+ }
+
+ // Reset the variable manager
+ this.variableManagerPromise = undefined;
+ this.mapPromise = undefined;
+
+ console.log(
+ 'An error occurred while setting the "' + name + "\" variable. Let's reload the map and try again"
+ );
+ // Try to set the variable again!
+ await this.setVariable(name, value, user);
+ } else {
+ throw e;
+ }
}
}
@@ -447,9 +482,11 @@ export class GameRoom {
}
private variableManagerPromise: Promise | undefined;
+ private variableManagerLastLoad: Date | undefined;
private getVariableManager(): Promise {
if (!this.variableManagerPromise) {
+ this.variableManagerLastLoad = new Date();
this.variableManagerPromise = this.getMap()
.then((map) => {
const variablesManager = new VariablesManager(this.roomUrl, map);
diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts
index 5a0f3be6..931ddda5 100644
--- a/back/src/Model/Group.ts
+++ b/back/src/Model/Group.ts
@@ -1,10 +1,10 @@
-import { ConnectCallback, DisconnectCallback } from "./GameRoom";
+import { ConnectCallback, DisconnectCallback, GameRoom } from "./GameRoom";
import { User } from "./User";
import { PositionInterface } from "_Model/PositionInterface";
import { Movable } from "_Model/Movable";
import { PositionNotifier } from "_Model/PositionNotifier";
-import { gaugeManager } from "../Services/GaugeManager";
import { MAX_PER_GROUP } from "../Enum/EnvironmentVariable";
+import type { Zone } from "../Model/Zone";
export class Group implements Movable {
private static nextId: number = 1;
@@ -13,13 +13,14 @@ export class Group implements Movable {
private users: Set;
private x!: number;
private y!: number;
- private hasEditedGauge: boolean = false;
private wasDestroyed: boolean = false;
private roomId: string;
+ private currentZone: Zone | null = null;
constructor(
roomId: string,
users: User[],
+ private groupRadius: number,
private connectCallback: ConnectCallback,
private disconnectCallback: DisconnectCallback,
private positionNotifier: PositionNotifier
@@ -28,13 +29,6 @@ export class Group implements Movable {
this.users = new Set();
this.id = Group.nextId;
Group.nextId++;
- //we only send a event for prometheus metrics if the group lives more than 5 seconds
- setTimeout(() => {
- if (!this.wasDestroyed) {
- this.hasEditedGauge = true;
- gaugeManager.incNbGroupsPerRoomGauge(roomId);
- }
- }, 5000);
users.forEach((user: User) => {
this.join(user);
@@ -85,9 +79,22 @@ export class Group implements Movable {
this.y = y;
if (oldX === undefined) {
- this.positionNotifier.enter(this);
+ this.currentZone = this.positionNotifier.enter(this);
} else {
- this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
+ this.currentZone = this.positionNotifier.updatePosition(this, { x, y }, { x: oldX, y: oldY });
+ }
+ }
+
+ searchForNearbyUsers(): void {
+ if (!this.currentZone) return;
+
+ for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) {
+ 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.updatePosition();
+ }
}
}
@@ -126,7 +133,6 @@ export class Group implements Movable {
* Usually used when there is only one user left.
*/
destroy(): void {
- if (this.hasEditedGauge) gaugeManager.decNbGroupsPerRoomGauge(this.roomId);
for (const user of this.users) {
this.leave(user);
}
diff --git a/back/src/Model/PositionNotifier.ts b/back/src/Model/PositionNotifier.ts
index 4f911637..2052f229 100644
--- a/back/src/Model/PositionNotifier.ts
+++ b/back/src/Model/PositionNotifier.ts
@@ -12,7 +12,7 @@ import { EmoteCallback, EntersCallback, LeavesCallback, MovesCallback, Zone } fr
import { Movable } from "_Model/Movable";
import { PositionInterface } from "_Model/PositionInterface";
import { ZoneSocket } from "../RoomManager";
-import { User } from "_Model/User";
+import { User } from "../Model/User";
import { EmoteEventMessage } from "../Messages/generated/messages_pb";
interface ZoneDescriptor {
@@ -20,6 +20,17 @@ interface ZoneDescriptor {
j: number;
}
+export function* getNearbyDescriptorsMatrix(middleZoneDescriptor: ZoneDescriptor): Generator {
+ for (let n = 0; n < 9; n++) {
+ const i = middleZoneDescriptor.i + ((n % 3) - 1);
+ const j = middleZoneDescriptor.j + (Math.floor(n / 3) - 1);
+
+ if (i >= 0 && j >= 0) {
+ yield { i, j };
+ }
+ }
+}
+
export class PositionNotifier {
// TODO: we need a way to clean the zones if no one is in the zone and no one listening (to free memory!)
@@ -41,14 +52,15 @@ export class PositionNotifier {
};
}
- public enter(thing: Movable): void {
+ public enter(thing: Movable): Zone {
const position = thing.getPosition();
const zoneDesc = this.getZoneDescriptorFromCoordinates(position.x, position.y);
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.enter(thing, null, position);
+ return zone;
}
- public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): void {
+ public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): Zone {
// Did we change zone?
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y);
const newZoneDesc = this.getZoneDescriptorFromCoordinates(newPosition.x, newPosition.y);
@@ -62,9 +74,11 @@ export class PositionNotifier {
// Enter new zone
newZone.enter(thing, oldZone, newPosition);
+ return newZone;
} else {
const zone = this.getZone(oldZoneDesc.i, oldZoneDesc.j);
zone.move(thing, newPosition);
+ return zone;
}
}
@@ -106,4 +120,16 @@ export class PositionNotifier {
const zone = this.getZone(zoneDesc.i, zoneDesc.j);
zone.emitEmoteEvent(emoteEventMessage);
}
+
+ public *getAllUsersInSquareAroundZone(zone: Zone): Generator {
+ const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y);
+ for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) {
+ const zone = this.getZone(d.i, d.j);
+ for (const thing of zone.getThings()) {
+ if (thing instanceof User) {
+ yield thing;
+ }
+ }
+ }
+ }
}
diff --git a/back/src/Services/GaugeManager.ts b/back/src/Services/GaugeManager.ts
index 6d2183d8..740692a8 100644
--- a/back/src/Services/GaugeManager.ts
+++ b/back/src/Services/GaugeManager.ts
@@ -52,15 +52,6 @@ class GaugeManager {
this.nbClientsGauge.dec();
this.nbClientsPerRoomGauge.dec({ room: roomId });
}
-
- incNbGroupsPerRoomGauge(roomId: string): void {
- this.nbGroupsPerRoomCounter.inc({ room: roomId });
- this.nbGroupsPerRoomGauge.inc({ room: roomId });
- }
-
- decNbGroupsPerRoomGauge(roomId: string): void {
- this.nbGroupsPerRoomGauge.dec({ room: roomId });
- }
}
export const gaugeManager = new GaugeManager();
diff --git a/back/src/Services/MapFetcher.ts b/back/src/Services/MapFetcher.ts
index a17f722a..15c31517 100644
--- a/back/src/Services/MapFetcher.ts
+++ b/back/src/Services/MapFetcher.ts
@@ -5,12 +5,13 @@ import { promisify } from "util";
import { LocalUrlError } from "./LocalUrlError";
import { ITiledMap } from "@workadventure/tiled-map-type-guard";
import { isTiledMap } from "@workadventure/tiled-map-type-guard/dist";
+import { STORE_VARIABLES_FOR_LOCAL_MAPS } from "../Enum/EnvironmentVariable";
class MapFetcher {
async fetchMap(mapUrl: string): Promise {
// Before trying to make the query, let's verify the map is actually on the open internet (and not a local test map)
- if (await this.isLocalUrl(mapUrl)) {
+ if ((await this.isLocalUrl(mapUrl)) && !STORE_VARIABLES_FOR_LOCAL_MAPS) {
throw new LocalUrlError('URL for map "' + mapUrl + '" targets a local map');
}
diff --git a/back/src/Services/VariableError.ts b/back/src/Services/VariableError.ts
new file mode 100644
index 00000000..183fab2c
--- /dev/null
+++ b/back/src/Services/VariableError.ts
@@ -0,0 +1,9 @@
+/**
+ * Errors related to variable handling.
+ */
+export class VariableError extends Error {
+ constructor(message: string) {
+ super(message);
+ Object.setPrototypeOf(this, VariableError.prototype);
+ }
+}
diff --git a/back/src/Services/VariablesManager.ts b/back/src/Services/VariablesManager.ts
index 915c6c05..00aac3dc 100644
--- a/back/src/Services/VariablesManager.ts
+++ b/back/src/Services/VariablesManager.ts
@@ -10,6 +10,7 @@ import {
import { User } from "_Model/User";
import { variablesRepository } from "./Repository/VariablesRepository";
import { redisClient } from "./RedisClient";
+import { VariableError } from "./VariableError";
interface Variable {
defaultValue?: string;
@@ -174,11 +175,13 @@ export class VariablesManager {
if (this.variableObjects) {
variableObject = this.variableObjects.get(name);
if (variableObject === undefined) {
- throw new Error('Trying to set a variable "' + name + '" that is not defined as an object in the map.');
+ throw new VariableError(
+ 'Trying to set a variable "' + name + '" that is not defined as an object in the map.'
+ );
}
if (variableObject.writableBy && !user.tags.includes(variableObject.writableBy)) {
- throw new Error(
+ throw new VariableError(
'Trying to set a variable "' +
name +
'". User "' +
diff --git a/back/tests/getNearbyDescriptorsMatrixTest.ts b/back/tests/getNearbyDescriptorsMatrixTest.ts
new file mode 100644
index 00000000..6c81db76
--- /dev/null
+++ b/back/tests/getNearbyDescriptorsMatrixTest.ts
@@ -0,0 +1,67 @@
+import "jasmine";
+import { getNearbyDescriptorsMatrix } from "../src/Model/PositionNotifier";
+
+describe("getNearbyDescriptorsMatrix", () => {
+ it("should create a matrix of coordinates in a square around the parameter", () => {
+ const matrix = [];
+ for (const d of getNearbyDescriptorsMatrix({ i: 1, j: 1 })) {
+ matrix.push(d);
+ }
+
+ expect(matrix).toEqual([
+ { i: 0, j: 0 },
+ { i: 1, j: 0 },
+ { i: 2, j: 0 },
+ { i: 0, j: 1 },
+ { i: 1, j: 1 },
+ { i: 2, j: 1 },
+ { i: 0, j: 2 },
+ { i: 1, j: 2 },
+ { i: 2, j: 2 },
+ ]);
+ });
+
+ it("should create a matrix of coordinates in a square around the parameter bis", () => {
+ const matrix = [];
+ for (const d of getNearbyDescriptorsMatrix({ i: 8, j: 3 })) {
+ matrix.push(d);
+ }
+
+ expect(matrix).toEqual([
+ { i: 7, j: 2 },
+ { i: 8, j: 2 },
+ { i: 9, j: 2 },
+ { i: 7, j: 3 },
+ { i: 8, j: 3 },
+ { i: 9, j: 3 },
+ { i: 7, j: 4 },
+ { i: 8, j: 4 },
+ { i: 9, j: 4 },
+ ]);
+ });
+
+ it("should not create a matrix with negative coordinates", () => {
+ const matrix = [];
+ for (const d of getNearbyDescriptorsMatrix({ i: 0, j: 0 })) {
+ matrix.push(d);
+ }
+
+ expect(matrix).toEqual([
+ { i: 0, j: 0 },
+ { i: 1, j: 0 },
+ { i: 0, j: 1 },
+ { i: 1, j: 1 },
+ ]);
+ });
+
+ /*it("should not create a matrix with coordinates bigger than its dimmensions", () => {
+ const matrix = getNearbyDescriptorsMatrix({i: 4, j: 4}, 5, 5);
+
+ expect(matrix).toEqual([
+ {i: 3,j: 3},
+ {i: 4,j: 3},
+ {i: 3,j: 4},
+ {i: 4,j: 4},
+ ])
+ });*/
+});
diff --git a/back/yarn.lock b/back/yarn.lock
index 64dcb9ce..ff9cb180 100644
--- a/back/yarn.lock
+++ b/back/yarn.lock
@@ -194,10 +194,10 @@
semver "^7.3.2"
tsutils "^3.17.1"
-"@workadventure/tiled-map-type-guard@^1.0.2":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.2.tgz#4171550f6cd71be19791faef48360d65d698bcb0"
- integrity sha512-RCtygGV5y9cb7QoyGMINBE9arM5pyXjkxvXgA5uXEv4GDbXKorhFim/rHgwbVR+eFnVF3rDgWbRnk3DIaHt+lQ==
+"@workadventure/tiled-map-type-guard@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@workadventure/tiled-map-type-guard/-/tiled-map-type-guard-1.0.3.tgz#62c2061cacbe1360b84162af0b7e4639ed8bfa7e"
+ integrity sha512-pUMxBBZHYAFkpnGWZAVAE8+M+Wn9UtzqZhXvBBBbB1gEakHIka7ahdTGfh0DgRaWrVszVXOP3tf49Dhdmn9pDg==
dependencies:
generic-type-guard "^3.4.1"
@@ -381,12 +381,12 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
-axios@^0.21.1:
- version "0.21.1"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
- integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
+axios@^0.21.2:
+ version "0.21.2"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
+ integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
dependencies:
- follow-redirects "^1.10.0"
+ follow-redirects "^1.14.0"
balanced-match@^1.0.0:
version "1.0.0"
@@ -1142,10 +1142,10 @@ flatted@^2.0.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
-follow-redirects@^1.10.0:
- version "1.13.0"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
- integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
+follow-redirects@^1.14.0:
+ version "1.14.4"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
+ integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
for-in@^1.0.2:
version "1.0.2"
diff --git a/contrib/docker/docker-compose.prod.yaml b/contrib/docker/docker-compose.prod.yaml
index 6b3b8520..e5a7fb79 100644
--- a/contrib/docker/docker-compose.prod.yaml
+++ b/contrib/docker/docker-compose.prod.yaml
@@ -38,6 +38,7 @@ services:
JITSI_URL: $JITSI_URL
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
PUSHER_URL: //pusher.${DOMAIN}
+ ICON_URL: //icon.${DOMAIN}
TURN_SERVER: "${TURN_SERVER}"
TURN_USER: "${TURN_USER}"
TURN_PASSWORD: "${TURN_PASSWORD}"
@@ -98,3 +99,15 @@ services:
- "traefik.http.routers.back-ssl.service=back"
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
restart: unless-stopped
+
+ icon:
+ image: matthiasluedtke/iconserver:v3.13.0
+ labels:
+ - "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)"
+ - "traefik.http.routers.icon.entryPoints=web,traefik"
+ - "traefik.http.services.icon.loadbalancer.server.port=8080"
+ - "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)"
+ - "traefik.http.routers.icon-ssl.entryPoints=websecure"
+ - "traefik.http.routers.icon-ssl.tls=true"
+ - "traefik.http.routers.icon-ssl.service=icon"
+ - "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"
diff --git a/deeployer.libsonnet b/deeployer.libsonnet
index a2e8970a..c4b34e38 100644
--- a/deeployer.libsonnet
+++ b/deeployer.libsonnet
@@ -4,7 +4,7 @@
local tag = namespace,
local url = namespace+".test.workadventu.re",
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
- local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
+ local adminUrl = if std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
"version": "1.0",
"containers": {
@@ -17,7 +17,6 @@
"ports": [8080, 50051],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange",
- "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
@@ -25,6 +24,7 @@
"REDIS_HOST": "redis",
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
+ "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
} else {})
},
"back2": {
@@ -36,7 +36,6 @@
"ports": [8080, 50051],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange",
- "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
@@ -44,6 +43,7 @@
"REDIS_HOST": "redis",
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
+ "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
} else {})
},
"pusher": {
@@ -55,13 +55,14 @@
"ports": [8080],
"env": {
"SECRET_KEY": "tempSecretKeyNeedsToChange",
- "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
"JITSI_ISS": env.JITSI_ISS,
"JITSI_URL": env.JITSI_URL,
"API_URL": "back1:50051,back2:50051",
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
+ "ADMIN_API_TOKEN": env.ADMIN_API_TOKEN,
+ "ADMIN_SOCKETS_TOKEN": env.ADMIN_SOCKETS_TOKEN,
} else {})
},
"front": {
@@ -75,11 +76,13 @@
"UPLOADER_URL": "//uploader-"+url,
"ADMIN_URL": "//"+url,
"JITSI_URL": env.JITSI_URL,
+ #POSTHOG
+ "POSTHOG_API_KEY": if namespace == "master" then env.POSTHOG_API_KEY else "",
+ "POSTHOG_URL": if namespace == "master" then env.POSTHOG_URL else "",
"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+"/Floor0/floor0.json"
- //"GA_TRACKING_ID": "UA-10196481-11"
+ "START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json"
}
},
"uploader": {
diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml
index 4e85d702..52875ce8 100644
--- a/docker-compose.single-domain.yaml
+++ b/docker-compose.single-domain.yaml
@@ -30,6 +30,7 @@ services:
UPLOADER_URL: /uploader
ADMIN_URL: /admin
MAPS_URL: /maps
+ ICON_URL: /icon
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
@@ -40,6 +41,7 @@ services:
TURN_USER: ""
TURN_PASSWORD: ""
START_ROOM_URL: "$START_ROOM_URL"
+ DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
command: yarn run start
volumes:
- ./front:/usr/src/app
@@ -60,6 +62,8 @@ services:
environment:
DEBUG: "*"
STARTUP_COMMAND_1: yarn install
+ # wait for files generated by "messages" container to exists
+ STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
@@ -70,6 +74,9 @@ services:
OPID_CLIENT_ID: $OPID_CLIENT_ID
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
+ OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
+ OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
+ DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
volumes:
- ./pusher:/usr/src/app
labels:
@@ -117,6 +124,8 @@ services:
environment:
DEBUG: "*"
STARTUP_COMMAND_1: yarn install
+ # wait for files generated by "messages" container to exists
+ STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_KEY: yourSecretKey
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
ALLOW_ARTILLERY: "true"
@@ -177,6 +186,20 @@ services:
redis:
image: redis:6
+ icon:
+ image: matthiasluedtke/iconserver:v3.13.0
+ labels:
+ - "traefik.http.middlewares.strip-icon-prefix.stripprefix.prefixes=/icon"
+ - "traefik.http.routers.icon.rule=PathPrefix(`/icon`)"
+ - "traefik.http.routers.icon.middlewares=strip-icon-prefix@docker"
+ - "traefik.http.routers.icon.entryPoints=web"
+ - "traefik.http.services.icon.loadbalancer.server.port=8080"
+ - "traefik.http.routers.icon-ssl.rule=PathPrefix(`/icon`)"
+ - "traefik.http.routers.icon-ssl.middlewares=strip-icon-prefix@docker"
+ - "traefik.http.routers.icon-ssl.entryPoints=websecure"
+ - "traefik.http.routers.icon-ssl.tls=true"
+ - "traefik.http.routers.icon-ssl.service=icon"
+
# coturn:
# image: coturn/coturn:4.5.2
# command:
diff --git a/docker-compose.testcafe.yml b/docker-compose.testcafe.yml
new file mode 100644
index 00000000..774477a1
--- /dev/null
+++ b/docker-compose.testcafe.yml
@@ -0,0 +1,12 @@
+version: "3"
+services:
+ testcafe:
+ image: testcafe/testcafe:1.17.1
+ working_dir: /tests
+ environment:
+ BROWSER: "chromium --use-fake-device-for-media-stream"
+ volumes:
+ - ./tests:/tests
+ - ./maps:/maps
+ # security_opt:
+ # - seccomp:unconfined
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 4b3904dd..ed5389c0 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -17,6 +17,12 @@ services:
- front
volumes:
- /var/run/docker.sock:/var/run/docker.sock
+ networks:
+ default:
+ aliases:
+ - 'play.workadventure.localhost'
+ - 'pusher.workadventure.localhost'
+ - 'maps.workadventure.localhost'
front:
image: thecodingmachine/nodejs:14
@@ -29,6 +35,7 @@ services:
PUSHER_URL: //pusher.workadventure.localhost
UPLOADER_URL: //uploader.workadventure.localhost
ADMIN_URL: //workadventure.localhost
+ ICON_URL: //icon.workadventure.localhost
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
STUN_SERVER: "stun:stun.l.google.com:19302"
@@ -42,6 +49,8 @@ services:
START_ROOM_URL: "$START_ROOM_URL"
MAX_PER_GROUP: "$MAX_PER_GROUP"
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
+ DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
+ OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
command: yarn run start
volumes:
- ./front:/usr/src/app
@@ -60,6 +69,8 @@ services:
environment:
DEBUG: "socket:*"
STARTUP_COMMAND_1: yarn install
+ # wait for files generated by "messages" container to exists
+ STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
SECRET_KEY: yourSecretKey
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
@@ -70,6 +81,9 @@ services:
OPID_CLIENT_ID: $OPID_CLIENT_ID
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
+ OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
+ OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
+ DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
volumes:
- ./pusher:/usr/src/app
labels:
@@ -111,6 +125,8 @@ services:
environment:
DEBUG: "*"
STARTUP_COMMAND_1: yarn install
+ # wait for files generated by "messages" container to exists
+ STARTUP_COMMAND_2: while [ ! -f /usr/src/app/src/Messages/generated/messages_pb.js ]; do sleep 1; done
SECRET_KEY: yourSecretKey
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
ALLOW_ARTILLERY: "true"
@@ -121,6 +137,7 @@ services:
MAX_PER_GROUP: "MAX_PER_GROUP"
REDIS_HOST: redis
NODE_ENV: development
+ STORE_VARIABLES_FOR_LOCAL_MAPS: "true"
volumes:
- ./back:/usr/src/app
labels:
@@ -177,6 +194,17 @@ services:
- "traefik.http.routers.redisinsight-ssl.tls=true"
- "traefik.http.routers.redisinsight-ssl.service=redisinsight"
+ icon:
+ image: matthiasluedtke/iconserver:v3.13.0
+ labels:
+ - "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)"
+ - "traefik.http.routers.icon.entryPoints=web"
+ - "traefik.http.services.icon.loadbalancer.server.port=8080"
+ - "traefik.http.routers.icon-ssl.rule=Host(`icon.workadventure.localhost`)"
+ - "traefik.http.routers.icon-ssl.entryPoints=websecure"
+ - "traefik.http.routers.icon-ssl.tls=true"
+ - "traefik.http.routers.icon-ssl.service=icon"
+
# coturn:
# image: coturn/coturn:4.5.2
# command:
diff --git a/docs/maps/api-controls.md b/docs/maps/api-controls.md
index dcb0f17b..c2b47262 100644
--- a/docs/maps/api-controls.md
+++ b/docs/maps/api-controls.md
@@ -15,7 +15,7 @@ When controls are disabled, the user cannot move anymore using keyboard input. T
Example:
```javascript
-WA.room.onEnterZone('myZone', () => {
+WA.room.onEnterLayer('myZone').subscribe(() => {
WA.controls.disablePlayerControls();
WA.ui.openPopup("popupRectangle", 'This is an imporant message!', [{
label: "Got it!",
@@ -25,5 +25,5 @@ WA.room.onEnterZone('myZone', () => {
popup.close();
}
}]);
-});
+})
```
diff --git a/docs/maps/api-deprecated.md b/docs/maps/api-deprecated.md
index f2b582a5..ffa8af9e 100644
--- a/docs/maps/api-deprecated.md
+++ b/docs/maps/api-deprecated.md
@@ -3,19 +3,21 @@
The list of functions below is **deprecated**. You should not use those but. use the replacement functions.
-- Method `WA.sendChatMessage` is deprecated. It has been renamed to `WA.chat.sendChatMessage`.
-- Method `WA.disablePlayerControls` is deprecated. It has been renamed to `WA.controls.disablePlayerControls`.
-- Method `WA.restorePlayerControls` is deprecated. It has been renamed to `WA.controls.restorePlayerControls`.
+- Method `WA.sendChatMessage` is deprecated. It has been renamed to [`WA.chat.sendChatMessage`](api-chat.md#sending-a-message-in-the-chat).
+- Method `WA.disablePlayerControls` is deprecated. It has been renamed to [`WA.controls.disablePlayerControls`](api-controls.md#disabling--restoring-controls).
+- Method `WA.restorePlayerControls` is deprecated. It has been renamed to [`WA.controls.restorePlayerControls`](api-controls.md#disabling--restoring-controls).
- Method `WA.displayBubble` is deprecated. It has been renamed to `WA.ui.displayBubble`.
- Method `WA.removeBubble` is deprecated. It has been renamed to `WA.ui.removeBubble`.
-- Method `WA.openTab` is deprecated. It has been renamed to `WA.nav.openTab`.
-- Method `WA.loadSound` is deprecated. It has been renamed to `WA.sound.loadSound`.
-- Method `WA.goToPage` is deprecated. It has been renamed to `WA.nav.goToPage`.
-- Method `WA.goToRoom` is deprecated. It has been renamed to `WA.nav.goToRoom`.
-- Method `WA.openCoWebSite` is deprecated. It has been renamed to `WA.nav.openCoWebSite`.
-- Method `WA.closeCoWebSite` is deprecated. It has been renamed to `WA.nav.closeCoWebSite`.
-- Method `WA.openPopup` is deprecated. It has been renamed to `WA.ui.openPopup`.
-- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
-- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
-- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
-- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`.
\ No newline at end of file
+- Method `WA.openTab` is deprecated. It has been renamed to [`WA.nav.openTab`](api-nav.md#opening-a-web-page-in-a-new-tab).
+- Method `WA.loadSound` is deprecated. It has been renamed to [`WA.sound.loadSound`](api-sound.md#load-a-sound-from-an-url).
+- Method `WA.goToPage` is deprecated. It has been renamed to [`WA.nav.goToPage`](api-nav.md#opening-a-web-page-in-the-current-tab).
+- Method `WA.goToRoom` is deprecated. It has been renamed to [`WA.nav.goToRoom`](api-nav.md#going-to-a-different-map-from-the-script).
+- Method `WA.openCoWebSite` is deprecated. It has been renamed to [`WA.nav.openCoWebSite`](api-nav.md#openingclosing-web-page-in-co-websites).
+- Method `WA.closeCoWebSite` is deprecated. It has been remove and [replace by a function close](api-nav.md#openingclosing-web-page-in-co-websites).
+- Method `WA.openPopup` is deprecated. It has been renamed to [`WA.ui.openPopup`](api-ui.md#opening-a-popup).
+- Method `WA.onChatMessage` is deprecated. It has been renamed to [`WA.chat.onChatMessage`](api-chat.md#listening-to-messages-from-the-chat).
+- Method `WA.onEnterZone` is deprecated. It has been renamed to [`WA.room.onEnterZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
+- Method `WA.onLeaveZone` is deprecated. It has been renamed to [`WA.room.onLeaveZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
+- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use [`WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`](api-ui.md#add-custom-menu).
+- Method `WA.room.onEnterZone` is deprecated. Use instead [`WA.room.onEnterLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
+- Method `WA.room.onLeaveZone` is deprecated. Use instead [`WA.room.onLeaveLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
\ No newline at end of file
diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md
index f5721063..47ee416e 100644
--- a/docs/maps/api-nav.md
+++ b/docs/maps/api-nav.md
@@ -49,19 +49,34 @@ WA.nav.goToRoom('../otherMap/map.json');
WA.nav.goToRoom("/_/global/.json#start-layer-2")
```
-### Opening/closing a web page in an iFrame
+### Opening/closing web page in Co-Websites
```
-WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void
-WA.nav.closeCoWebSite(): void
+WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number = 0): 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).
+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.
Example:
```javascript
-WA.nav.openCoWebSite('https://www.wikipedia.org/');
+const coWebsite = await WA.nav.openCoWebSite('https://www.wikipedia.org/');
+const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 1);
// ...
-WA.nav.closeCoWebSite();
+coWebsite.close();
+```
+
+### Get all Co-Websites
+
+```
+WA.nav.getCoWebSites(): Promise
+```
+
+Get all opened co-websites with their ids and positions.
+
+Example:
+
+```javascript
+const coWebsites = await WA.nav.getCowebSites();
```
diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md
index ed73c32d..39a13d9e 100644
--- a/docs/maps/api-player.md
+++ b/docs/maps/api-player.md
@@ -68,7 +68,9 @@ The event has the following attributes :
* **moving (boolean):** **true** when the current player is moving, **false** otherwise.
* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving.
* **x (number):** coordinate X of the current player.
-* **y (number):** coordinate Y of the current player.
+* **y (number):** coordinate Y of the current player.
+* **oldX (number):** old coordinate X of the current player.
+* **oldY (number):** old coordinate Y of the current player.
**callback:** the function that will be called when the current player is moving. It contains the event.
diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md
index d1a26d2f..72947df8 100644
--- a/docs/maps/api-room.md
+++ b/docs/maps/api-room.md
@@ -17,35 +17,27 @@ The name of the layers of this map are :
* `bottom/build/carpet`
* `wall`
-### Detecting when the user enters/leaves a zone
+### Detecting when the user enters/leaves a layer
```
-WA.room.onEnterZone(name: string, callback: () => void): void
-WA.room.onLeaveZone(name: string, callback: () => void): void
+WA.room.onEnterLayer(name: string): Subscription
+WA.room.onLeaveLayer(name: string): Subscription
```
-Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property.
+Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
-
-
-
-
-* **name**: the name of the zone, as defined in the `zone` property.
-* **callback**: the function that will be called when a user enters or leaves the zone.
+* **name**: the name of the layer who as defined in Tiled.
Example:
```javascript
-WA.room.onEnterZone('myZone', () => {
+WA.room.onEnterLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
-})
+});
-WA.room.onLeaveZone('myZone', () => {
+WA.room.onLeaveLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
-})
+});
```
### Show / Hide a layer
@@ -71,7 +63,7 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`.
-Note :
+Note :
To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
Example :
@@ -131,7 +123,7 @@ console.log("Map generated with Tiled version ", map.tiledversion);
Check the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/).
-### Changing tiles
+### Changing tiles
```
WA.room.setTiles(tiles: TileDescriptor[]): void
```
@@ -144,7 +136,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property
-`TileDescriptor` has the following attributes :
+`TileDescriptor` has the following attributes :
* **x (number) :** The coordinate x of the tile that you want to replace.
* **y (number) :** The coordinate y of the tile that you want to replace.
* **tile (number | string) :** The id of the tile that will be placed in the map.
@@ -154,7 +146,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property
Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
-Example :
+Example :
```javascript
WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
@@ -246,7 +238,7 @@ const website = WA.room.website.create({
WA.room.website.delete(name: string): Promise
```
-Use `WA.room.website.delete` to completely remove an embedded website from your map.
+Use `WA.room.website.delete` to completely remove an embedded website from your map.
### The EmbeddedWebsite class
@@ -271,7 +263,7 @@ When you modify a property of an `EmbeddedWebsite` instance, the iframe is autom
{.alert.alert-warning}
-The websites you add/edit/delete via the scripting API are only shown locally. If you want them
+The websites you add/edit/delete via the scripting API are only shown locally. If you want them
to be displayed for every player, you can use [variables](api-start.md) to share a common state
between all users.
diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md
index dc701500..8583c061 100644
--- a/docs/maps/api-ui.md
+++ b/docs/maps/api-ui.md
@@ -49,7 +49,7 @@ Example:
let helloWorldPopup;
// Open the popup when we enter a given zone
-helloWorldPopup = WA.room.onEnterZone('myZone', () => {
+helloWorldPopup = WA.room.onEnterLayer("myZone").subscribe(() => {
WA.ui.openPopup("popupRectangle", 'Hello world!', [{
label: "Close",
className: "primary",
@@ -57,13 +57,13 @@ helloWorldPopup = WA.room.onEnterZone('myZone', () => {
// Close the popup when the "Close" button is pressed.
popup.close();
}
- });
-}]);
+ }]);
+});
// Close the popup when we leave the zone.
-WA.room.onLeaveZone('myZone', () => {
+WA.room.onLeaveLayer("myZone").subscribe(() => {
helloWorldPopup.close();
-});
+})
```
### Add custom menu
diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md
index bd194138..6f98af93 100644
--- a/docs/maps/entry-exit.md
+++ b/docs/maps/entry-exit.md
@@ -1,7 +1,7 @@
{.section-title.accent.text-primary}
# Entries and exits
-https://www.youtube.com/watch?v=MuhVgu8H7U0
+[Building your map - Defined entries and exits](https://www.youtube.com/watch?v=MuhVgu8H7U0)
## Defining a default entry point
diff --git a/docs/maps/index.md b/docs/maps/index.md
index 7f5e7867..d018dd3c 100644
--- a/docs/maps/index.md
+++ b/docs/maps/index.md
@@ -14,7 +14,7 @@ WorkAdventure comes with a "map starter kit" that we recommend using to start de
{.alert.alert-info}
If you are looking to host your maps on your own webserver, be sure to read the [Self-hosting your map](hosting.md) guide.
-[](https://www.youtube.com/watch?v=lu1IZgBJJD4)
+[Building your map - Create your map](https://www.youtube.com/watch?v=lu1IZgBJJD4)
## Getting started
diff --git a/docs/maps/meeting-rooms.md b/docs/maps/meeting-rooms.md
index 719e0630..88db1621 100644
--- a/docs/maps/meeting-rooms.md
+++ b/docs/maps/meeting-rooms.md
@@ -1,7 +1,7 @@
{.section-title.accent.text-primary}
# Meeting rooms
-https://www.youtube.com/watch?v=cN9VMWHN0eo
+[Building your map - Meeting room](https://www.youtube.com/watch?v=cN9VMWHN0eo)
## Opening a Jitsi meet when walking on the map
diff --git a/docs/maps/menu.php b/docs/maps/menu.php
index 9fb3428f..0bf0a7f9 100644
--- a/docs/maps/menu.php
+++ b/docs/maps/menu.php
@@ -5,64 +5,75 @@ $extraUtilsMenu = require __DIR__.'/../../scripting_api_extra_doc/menu_functions
return [
[
'title' => 'Getting started',
- 'url' => '/map-building',
- 'markdown' => 'maps.index'
+ 'url' => '/map-building/',
+ 'markdown' => 'maps.index',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/index.md',
],
[
'title' => 'WorkAdventure maps',
- 'url' => '/map-building/wa-maps',
- 'markdown' => 'maps.wa-maps'
+ 'url' => '/map-building/wa-maps.md',
+ 'markdown' => 'maps.wa-maps',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/wa-maps.md',
],
[
'title' => 'Entries and exits',
'url' => '/map-building/entry-exit.md',
- 'markdown' => 'maps.entry-exit'
+ 'markdown' => 'maps.entry-exit',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/entry-exit.md',
],
[
'title' => 'Opening a website',
'url' => '/map-building/opening-a-website.md',
- 'markdown' => 'maps.opening-a-website'
+ 'markdown' => 'maps.opening-a-website',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/opening-a-website.md',
],
[
'title' => 'Meeting rooms',
'url' => '/map-building/meeting-rooms.md',
- 'markdown' => 'maps.meeting-rooms'
+ 'markdown' => 'maps.meeting-rooms',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/meeting-rooms.md',
],
[
'title' => 'Special zones',
'url' => '/map-building/special-zones.md',
- 'markdown' => 'maps.special-zones'
+ 'markdown' => 'maps.special-zones',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/special-zones.md',
],
[
'title' => 'Animations',
'url' => '/map-building/animations.md',
- 'markdown' => 'maps.animations'
+ 'markdown' => 'maps.animations',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/animations.md',
],
[
'title' => 'Integrated websites',
'url' => '/map-building/website-in-map.md',
- 'markdown' => 'maps.website-in-map'
+ 'markdown' => 'maps.website-in-map',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/website-in-map.md',
],
[
'title' => 'Variables',
'url' => '/map-building/variables.md',
- 'markdown' => 'maps.variables'
+ 'markdown' => 'maps.variables',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/variables.md',
],
[
'title' => 'Self-hosting your map',
'url' => '/map-building/hosting.md',
- 'markdown' => 'maps.hosting'
+ 'markdown' => 'maps.hosting',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/hosting.md',
],
$extraMenu,
[
'title' => 'Scripting maps',
- 'url' => '/map-building/scripting',
+ 'url' => '/map-building/scripting.md',
'markdown' => 'maps.scripting',
'children' => [
[
'title' => 'Using Typescript',
'url' => '/map-building/using-typescript.md',
- 'markdown' => 'maps.using-typescript'
+ 'markdown' => 'maps.using-typescript',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/using-typescript.md',
],
[
'title' => 'API Reference',
@@ -74,51 +85,61 @@ return [
'title' => 'Initialization',
'url' => '/map-building/api-start.md',
'markdown' => 'maps.api-start',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-start.md',
],
[
'title' => 'Navigation',
'url' => '/map-building/api-nav.md',
'markdown' => 'maps.api-nav',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-nav.md',
],
[
'title' => 'Chat',
'url' => '/map-building/api-chat.md',
'markdown' => 'maps.api-chat',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-chat.md',
],
[
'title' => 'Room',
'url' => '/map-building/api-room.md',
'markdown' => 'maps.api-room',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-room.md',
],
[
'title' => 'State',
'url' => '/map-building/api-state.md',
'markdown' => 'maps.api-state',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-state.md',
],
[
'title' => 'Player',
'url' => '/map-building/api-player.md',
'markdown' => 'maps.api-player',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-player.md',
],
[
'title' => 'UI',
'url' => '/map-building/api-ui.md',
'markdown' => 'maps.api-ui',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-ui.md',
],
[
'title' => 'Sound',
'url' => '/map-building/api-sound.md',
'markdown' => 'maps.api-sound',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-sound.md',
],
[
'title' => 'Controls',
'url' => '/map-building/api-controls.md',
'markdown' => 'maps.api-controls',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-controls.md',
],
[
'title' => 'Deprecated',
'url' => '/map-building/api-deprecated.md',
'markdown' => 'maps.api-deprecated',
+ 'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-deprecated.md',
],
]
],
diff --git a/docs/maps/opening-a-website.md b/docs/maps/opening-a-website.md
index 682306b4..64b19f1c 100644
--- a/docs/maps/opening-a-website.md
+++ b/docs/maps/opening-a-website.md
@@ -1,22 +1,22 @@
{.section-title.accent.text-primary}
# Opening a website when walking on the map
-https://www.youtube.com/watch?v=Me8cu5lLN3A
+[Building your map - Opening a website](https://www.youtube.com/watch?v=Me8cu5lLN3A)
## The openWebsite property
-On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe
+On your map, you can define special zones. When a player will pass over these zones, a website will open (as an iframe
on the right side of the screen)
In order to create a zone that opens websites:
* You must create a specific layer.
* In layer properties, you MUST add a "`openWebsite`" property (of type "`string`"). The value of the property is the URL of the website to open (the URL must start with "https://")
-* You may also use "`openWebsiteWidth`" property (of type "`number`" between 0 and 100) to control the width of the iframe.
+* You may also use "`openWebsiteWidth`" property (of type "`int`" or "`float`" between 0 and 100) to control the width of the iframe.
* You may also use "`openTab`" property (of type "`string`") to open in a new tab instead.
{.alert.alert-warning}
-A website can explicitly forbid another website from loading it in an iFrame using
+A website can explicitly forbid another website from loading it in an iFrame using
the [X-Frame-Options HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options).
## Integrating a Youtube video
@@ -64,3 +64,13 @@ For instance, if you want an iFrame to be able to go in fullscreen, you will use
The generated iFrame will have the allow attribute set to: <iframe allow="fullscreen">
+
+### Open a Jitsi with a co-website
+
+Cowebsites allow you to have several sites open at the same time.
+
+If you want to open a Jitsi and another page it's easy!
+
+You have just to [add a Jitsi to the map](meeting-rooms.md) and [add a co-website](opening-a-website.md#the-openwebsite-property) on the same layer.
+
+It's done!
diff --git a/docs/maps/special-zones.md b/docs/maps/special-zones.md
index fff4e730..30ebebab 100644
--- a/docs/maps/special-zones.md
+++ b/docs/maps/special-zones.md
@@ -3,7 +3,7 @@
## Making a "silent" zone
-https://www.youtube.com/watch?v=z7XLo06o-ow
+[Building your map - Special zones](https://www.youtube.com/watch?v=z7XLo06o-ow)
On your map, you can define special silent zones where nobody is allowed to talk. In these zones, users will not speak to each others, even if they are next to each others.
diff --git a/docs/maps/wa-maps.md b/docs/maps/wa-maps.md
index bdaf8a42..36875719 100644
--- a/docs/maps/wa-maps.md
+++ b/docs/maps/wa-maps.md
@@ -56,6 +56,8 @@ A few things to notice:
## Building walls and "collidable" areas
+[Building your map - Collides](https://www.youtube.com/watch?v=qTK50ymhMIE)
+
By default, the characters can traverse any tiles. If you want to prevent your character from going through a tile (like a wall or a desktop), you must make this tile "collidable". You can do this by settings the `collides` property on a given tile.
To make a tile "collidable", you should:
diff --git a/front/Dockerfile b/front/Dockerfile
index ef724e0f..6fef9dc8 100644
--- a/front/Dockerfile
+++ b/front/Dockerfile
@@ -1,13 +1,13 @@
-FROM thecodingmachine/workadventure-back-base:latest as builder
-WORKDIR /var/www/messages
-COPY --chown=docker:docker messages .
+FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
+WORKDIR /usr/src
+COPY messages .
RUN yarn install && yarn proto
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
FROM thecodingmachine/nodejs:14-apache
COPY --chown=docker:docker front .
-COPY --from=builder --chown=docker:docker /var/www/messages/generated /var/www/html/src/Messages/generated
+COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated
# 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
diff --git a/front/dist/.htaccess b/front/dist/.htaccess
index 522fc2af..b3bdfa2d 100644
--- a/front/dist/.htaccess
+++ b/front/dist/.htaccess
@@ -23,4 +23,4 @@ RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule "^[_@]/" "/index.html" [L]
RewriteRule "^register/" "/index.html" [L]
RewriteRule "^login" "/index.html" [L]
-RewriteRule "^jwt/" "/index.html" [L]
+RewriteRule "^jwt" "/index.html" [L]
diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html
index 0c89b611..3b43a5ef 100644
--- a/front/dist/index.tmpl.html
+++ b/front/dist/index.tmpl.html
@@ -37,6 +37,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -48,19 +96,24 @@
-
+
diff --git a/front/dist/resources/logos/logo.png b/front/dist/resources/logos/logo.png
deleted file mode 100644
index f4440ad5..00000000
Binary files a/front/dist/resources/logos/logo.png and /dev/null differ
diff --git a/front/dist/resources/logos/tcm_full.png b/front/dist/resources/logos/tcm_full.png
deleted file mode 100644
index 3ea27990..00000000
Binary files a/front/dist/resources/logos/tcm_full.png and /dev/null differ
diff --git a/front/dist/resources/logos/tcm_short.png b/front/dist/resources/logos/tcm_short.png
deleted file mode 100644
index ed55c836..00000000
Binary files a/front/dist/resources/logos/tcm_short.png and /dev/null differ
diff --git a/front/dist/resources/objects/talk.png b/front/dist/resources/objects/talk.png
index bc06d3b0..794a6e9d 100644
Binary files a/front/dist/resources/objects/talk.png and b/front/dist/resources/objects/talk.png differ
diff --git a/front/dist/static/images/Logo TCM.png b/front/dist/static/images/Logo TCM.png
deleted file mode 100644
index bf4881b1..00000000
Binary files a/front/dist/static/images/Logo TCM.png and /dev/null differ
diff --git a/front/dist/static/images/favicons/android-icon-144x144-white.png b/front/dist/static/images/favicons/android-icon-144x144-white.png
new file mode 100644
index 00000000..64ba2d0f
Binary files /dev/null and b/front/dist/static/images/favicons/android-icon-144x144-white.png differ
diff --git a/front/dist/static/images/favicons/android-icon-144x144.png b/front/dist/static/images/favicons/android-icon-144x144.png
index 037f624c..c00366f2 100644
Binary files a/front/dist/static/images/favicons/android-icon-144x144.png and b/front/dist/static/images/favicons/android-icon-144x144.png differ
diff --git a/front/dist/static/images/favicons/android-icon-192x192-white.png b/front/dist/static/images/favicons/android-icon-192x192-white.png
new file mode 100644
index 00000000..68e46bc5
Binary files /dev/null and b/front/dist/static/images/favicons/android-icon-192x192-white.png differ
diff --git a/front/dist/static/images/favicons/android-icon-192x192.png b/front/dist/static/images/favicons/android-icon-192x192.png
index 0968b1bd..58b7510a 100644
Binary files a/front/dist/static/images/favicons/android-icon-192x192.png and b/front/dist/static/images/favicons/android-icon-192x192.png differ
diff --git a/front/dist/static/images/favicons/android-icon-36x36.-white.png b/front/dist/static/images/favicons/android-icon-36x36.-white.png
new file mode 100644
index 00000000..8c0d19b7
Binary files /dev/null and b/front/dist/static/images/favicons/android-icon-36x36.-white.png differ
diff --git a/front/dist/static/images/favicons/android-icon-36x36.png b/front/dist/static/images/favicons/android-icon-36x36.png
index fabc0fa4..ff552e77 100644
Binary files a/front/dist/static/images/favicons/android-icon-36x36.png and b/front/dist/static/images/favicons/android-icon-36x36.png differ
diff --git a/front/dist/static/images/favicons/android-icon-48x48-white.png b/front/dist/static/images/favicons/android-icon-48x48-white.png
new file mode 100644
index 00000000..08fa015b
Binary files /dev/null and b/front/dist/static/images/favicons/android-icon-48x48-white.png differ
diff --git a/front/dist/static/images/favicons/android-icon-48x48.png b/front/dist/static/images/favicons/android-icon-48x48.png
index ed206753..79022a1c 100644
Binary files a/front/dist/static/images/favicons/android-icon-48x48.png and b/front/dist/static/images/favicons/android-icon-48x48.png differ
diff --git a/front/dist/static/images/favicons/android-icon-72x72-white.png b/front/dist/static/images/favicons/android-icon-72x72-white.png
new file mode 100644
index 00000000..6af42ddf
Binary files /dev/null and b/front/dist/static/images/favicons/android-icon-72x72-white.png differ
diff --git a/front/dist/static/images/favicons/android-icon-72x72.png b/front/dist/static/images/favicons/android-icon-72x72.png
index f92ffa32..46cd1fa0 100644
Binary files a/front/dist/static/images/favicons/android-icon-72x72.png and b/front/dist/static/images/favicons/android-icon-72x72.png differ
diff --git a/front/dist/static/images/favicons/android-icon-96x96-white.png b/front/dist/static/images/favicons/android-icon-96x96-white.png
new file mode 100644
index 00000000..f654a2f1
Binary files /dev/null and b/front/dist/static/images/favicons/android-icon-96x96-white.png differ
diff --git a/front/dist/static/images/favicons/android-icon-96x96.png b/front/dist/static/images/favicons/android-icon-96x96.png
index fda1933e..486e54df 100644
Binary files a/front/dist/static/images/favicons/android-icon-96x96.png and b/front/dist/static/images/favicons/android-icon-96x96.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-114x114-white.png b/front/dist/static/images/favicons/apple-icon-114x114-white.png
new file mode 100644
index 00000000..1ad6cdfb
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-114x114-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-114x114.png b/front/dist/static/images/favicons/apple-icon-114x114.png
index 3de5f8c4..ed795dff 100644
Binary files a/front/dist/static/images/favicons/apple-icon-114x114.png and b/front/dist/static/images/favicons/apple-icon-114x114.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-120x120-white.png b/front/dist/static/images/favicons/apple-icon-120x120-white.png
new file mode 100644
index 00000000..859dca4b
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-120x120-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-120x120.png b/front/dist/static/images/favicons/apple-icon-120x120.png
index 90ff4b18..6f711b36 100644
Binary files a/front/dist/static/images/favicons/apple-icon-120x120.png and b/front/dist/static/images/favicons/apple-icon-120x120.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-144x144-white.png b/front/dist/static/images/favicons/apple-icon-144x144-white.png
new file mode 100644
index 00000000..64ba2d0f
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-144x144-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-144x144.png b/front/dist/static/images/favicons/apple-icon-144x144.png
index 1fc655bb..c00366f2 100644
Binary files a/front/dist/static/images/favicons/apple-icon-144x144.png and b/front/dist/static/images/favicons/apple-icon-144x144.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-152x152-white.png b/front/dist/static/images/favicons/apple-icon-152x152-white.png
new file mode 100644
index 00000000..f0ad867c
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-152x152-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-152x152.png b/front/dist/static/images/favicons/apple-icon-152x152.png
index ca3a85ba..9b2a481f 100644
Binary files a/front/dist/static/images/favicons/apple-icon-152x152.png and b/front/dist/static/images/favicons/apple-icon-152x152.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-180x180-white.png b/front/dist/static/images/favicons/apple-icon-180x180-white.png
new file mode 100644
index 00000000..a2cc2756
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-180x180-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-180x180.png b/front/dist/static/images/favicons/apple-icon-180x180.png
index 64190d74..26819d17 100644
Binary files a/front/dist/static/images/favicons/apple-icon-180x180.png and b/front/dist/static/images/favicons/apple-icon-180x180.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-57x57-white.png b/front/dist/static/images/favicons/apple-icon-57x57-white.png
new file mode 100644
index 00000000..2abb0bd5
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-57x57-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-57x57.png b/front/dist/static/images/favicons/apple-icon-57x57.png
index 747c5c11..6d4b0588 100644
Binary files a/front/dist/static/images/favicons/apple-icon-57x57.png and b/front/dist/static/images/favicons/apple-icon-57x57.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-60x60-white.png b/front/dist/static/images/favicons/apple-icon-60x60-white.png
new file mode 100644
index 00000000..ceb76061
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-60x60-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-60x60.png b/front/dist/static/images/favicons/apple-icon-60x60.png
index 61a9c874..a22bdd72 100644
Binary files a/front/dist/static/images/favicons/apple-icon-60x60.png and b/front/dist/static/images/favicons/apple-icon-60x60.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-72x72-white.png b/front/dist/static/images/favicons/apple-icon-72x72-white.png
new file mode 100644
index 00000000..6af42ddf
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-72x72-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-72x72.png b/front/dist/static/images/favicons/apple-icon-72x72.png
index da043d44..46cd1fa0 100644
Binary files a/front/dist/static/images/favicons/apple-icon-72x72.png and b/front/dist/static/images/favicons/apple-icon-72x72.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-76x76-white.png b/front/dist/static/images/favicons/apple-icon-76x76-white.png
new file mode 100644
index 00000000..a861a91e
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-76x76-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-76x76.png b/front/dist/static/images/favicons/apple-icon-76x76.png
index 5e51fe2b..f74b85d8 100644
Binary files a/front/dist/static/images/favicons/apple-icon-76x76.png and b/front/dist/static/images/favicons/apple-icon-76x76.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-precomposed-white.png b/front/dist/static/images/favicons/apple-icon-precomposed-white.png
new file mode 100644
index 00000000..68e46bc5
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-precomposed-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-precomposed.png b/front/dist/static/images/favicons/apple-icon-precomposed.png
index e1a4b3ed..58b7510a 100644
Binary files a/front/dist/static/images/favicons/apple-icon-precomposed.png and b/front/dist/static/images/favicons/apple-icon-precomposed.png differ
diff --git a/front/dist/static/images/favicons/apple-icon-white.png b/front/dist/static/images/favicons/apple-icon-white.png
new file mode 100644
index 00000000..68e46bc5
Binary files /dev/null and b/front/dist/static/images/favicons/apple-icon-white.png differ
diff --git a/front/dist/static/images/favicons/apple-icon.png b/front/dist/static/images/favicons/apple-icon.png
index 69d255b1..58b7510a 100644
Binary files a/front/dist/static/images/favicons/apple-icon.png and b/front/dist/static/images/favicons/apple-icon.png differ
diff --git a/front/dist/static/images/favicons/favicon-16x16-white.png b/front/dist/static/images/favicons/favicon-16x16-white.png
new file mode 100644
index 00000000..0a35297d
Binary files /dev/null and b/front/dist/static/images/favicons/favicon-16x16-white.png differ
diff --git a/front/dist/static/images/favicons/favicon-16x16.png b/front/dist/static/images/favicons/favicon-16x16.png
index cd608133..f70bd387 100644
Binary files a/front/dist/static/images/favicons/favicon-16x16.png and b/front/dist/static/images/favicons/favicon-16x16.png differ
diff --git a/front/dist/static/images/favicons/favicon-32x32-white.png b/front/dist/static/images/favicons/favicon-32x32-white.png
new file mode 100644
index 00000000..cd14aee0
Binary files /dev/null and b/front/dist/static/images/favicons/favicon-32x32-white.png differ
diff --git a/front/dist/static/images/favicons/favicon-32x32.png b/front/dist/static/images/favicons/favicon-32x32.png
index a15ffef5..ebdd4c5b 100644
Binary files a/front/dist/static/images/favicons/favicon-32x32.png and b/front/dist/static/images/favicons/favicon-32x32.png differ
diff --git a/front/dist/static/images/favicons/favicon-96x96-white.png b/front/dist/static/images/favicons/favicon-96x96-white.png
new file mode 100644
index 00000000..f654a2f1
Binary files /dev/null and b/front/dist/static/images/favicons/favicon-96x96-white.png differ
diff --git a/front/dist/static/images/favicons/favicon-96x96.png b/front/dist/static/images/favicons/favicon-96x96.png
index 380de08a..486e54df 100644
Binary files a/front/dist/static/images/favicons/favicon-96x96.png and b/front/dist/static/images/favicons/favicon-96x96.png differ
diff --git a/front/dist/static/images/favicons/favicon.ico b/front/dist/static/images/favicons/favicon.ico
index 203ce590..e8264098 100644
Binary files a/front/dist/static/images/favicons/favicon.ico and b/front/dist/static/images/favicons/favicon.ico differ
diff --git a/front/dist/static/images/favicons/icon-512x512-white.png b/front/dist/static/images/favicons/icon-512x512-white.png
new file mode 100644
index 00000000..49637fc8
Binary files /dev/null and b/front/dist/static/images/favicons/icon-512x512-white.png differ
diff --git a/front/dist/static/images/favicons/icon-512x512.png b/front/dist/static/images/favicons/icon-512x512.png
index 41116386..3ef8c4d8 100644
Binary files a/front/dist/static/images/favicons/icon-512x512.png and b/front/dist/static/images/favicons/icon-512x512.png differ
diff --git a/front/dist/static/images/favicons/manifest.json b/front/dist/static/images/favicons/manifest.json
index 1cf2a835..bb782c0e 100644
--- a/front/dist/static/images/favicons/manifest.json
+++ b/front/dist/static/images/favicons/manifest.json
@@ -89,7 +89,7 @@
"sizes": "192x192",
"type": "image\/png",
"density": "4.0",
- "purpose": "any maskable"
+ "purpose": "any"
},
{
diff --git a/front/dist/static/images/favicons/ms-icon-144x144.png b/front/dist/static/images/favicons/ms-icon-144x144.png
index 59a7c4ce..4187486a 100644
Binary files a/front/dist/static/images/favicons/ms-icon-144x144.png and b/front/dist/static/images/favicons/ms-icon-144x144.png differ
diff --git a/front/dist/static/images/favicons/ms-icon-150x150.png b/front/dist/static/images/favicons/ms-icon-150x150.png
index 3515b43a..6e4949b4 100644
Binary files a/front/dist/static/images/favicons/ms-icon-150x150.png and b/front/dist/static/images/favicons/ms-icon-150x150.png differ
diff --git a/front/dist/static/images/favicons/ms-icon-310x310.png b/front/dist/static/images/favicons/ms-icon-310x310.png
index 115ce84d..a92695f3 100644
Binary files a/front/dist/static/images/favicons/ms-icon-310x310.png and b/front/dist/static/images/favicons/ms-icon-310x310.png differ
diff --git a/front/dist/static/images/favicons/ms-icon-70x70.png b/front/dist/static/images/favicons/ms-icon-70x70.png
index 342deff9..e9502e2d 100644
Binary files a/front/dist/static/images/favicons/ms-icon-70x70.png and b/front/dist/static/images/favicons/ms-icon-70x70.png differ
diff --git a/front/dist/static/images/logo-WA-min.png b/front/dist/static/images/logo-WA-min.png
index fe213151..429d75e5 100644
Binary files a/front/dist/static/images/logo-WA-min.png and b/front/dist/static/images/logo-WA-min.png differ
diff --git a/front/dist/static/images/logo.png b/front/dist/static/images/logo.png
index f4440ad5..08e70049 100644
Binary files a/front/dist/static/images/logo.png and b/front/dist/static/images/logo.png differ
diff --git a/front/package.json b/front/package.json
index d60b6fa6..ec81d8a7 100644
--- a/front/package.json
+++ b/front/package.json
@@ -40,15 +40,17 @@
},
"dependencies": {
"@fontsource/press-start-2p": "^4.3.0",
+ "@joeattardi/emoji-button": "^4.6.0",
"@types/simple-peer": "^9.11.1",
"@types/socket.io-client": "^1.4.32",
- "axios": "^0.21.1",
+ "axios": "^0.21.2",
"cross-env": "^7.0.3",
"generic-type-guard": "^3.2.0",
"google-protobuf": "^3.13.0",
"phaser": "^3.54.0",
"phaser-animated-tiles": "workadventure/phaser-animated-tiles#da68bbededd605925621dd4f03bd27e69284b254",
"phaser3-rex-plugins": "^1.1.42",
+ "posthog-js": "^1.14.1",
"queue-typescript": "^1.0.1",
"quill": "1.3.6",
"quill-delta-to-html": "^0.12.0",
diff --git a/front/src/Administration/AnalyticsClient.ts b/front/src/Administration/AnalyticsClient.ts
new file mode 100644
index 00000000..ee85e8f4
--- /dev/null
+++ b/front/src/Administration/AnalyticsClient.ts
@@ -0,0 +1,91 @@
+import { POSTHOG_API_KEY, POSTHOG_URL } from "../Enum/EnvironmentVariable";
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+declare let window: any;
+
+class AnalyticsClient {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private posthogPromise: Promise|undefined;
+
+ constructor() {
+ if (POSTHOG_API_KEY && POSTHOG_URL) {
+ this.posthogPromise = import("posthog-js").then(({ default: posthog }) => {
+ posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL, disable_cookie: true });
+ //the posthog toolbar need a reference in window to be able to work
+ window.posthog = posthog;
+ return posthog;
+ });
+ }
+ }
+
+ identifyUser(uuid: string, email: string | null) {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.identify(uuid, { uuid, email, wa: true });
+ });
+ }
+
+ loggedWithSso() {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-logged-sso");
+ });
+ }
+
+ loggedWithToken() {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-logged-token");
+ });
+ }
+
+ enteredRoom(roomId: string, roomGroup: string | null) {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("$pageView", { roomId, roomGroup });
+ posthog.capture("enteredRoom");
+ });
+ }
+
+ openedMenu() {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-opened-menu");
+ });
+ }
+
+ launchEmote(emote: string) {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-emote-launch", { emote });
+ });
+ }
+
+ enteredJitsi(roomName: string, roomId: string) {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-entered-jitsi", { roomName, roomId });
+ });
+ }
+
+ validationName() {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-name-validation");
+ });
+ }
+
+ validationWoka(scene: string) {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-woka-validation", { scene });
+ });
+ }
+
+ validationVideo() {
+ this.posthogPromise
+ ?.then((posthog) => {
+ posthog.capture("wa-video-validation");
+ });
+ }
+}
+export const analyticsClient = new AnalyticsClient();
diff --git a/front/src/Api/Events/ChangeLayerEvent.ts b/front/src/Api/Events/ChangeLayerEvent.ts
new file mode 100644
index 00000000..77ff8ede
--- /dev/null
+++ b/front/src/Api/Events/ChangeLayerEvent.ts
@@ -0,0 +1,11 @@
+import * as tg from "generic-type-guard";
+
+export const isChangeLayerEvent = new tg.IsInterface()
+ .withProperties({
+ name: tg.isString,
+ })
+ .get();
+/**
+ * A message sent from the game to the iFrame when a user enters or leaves a layer.
+ */
+export type ChangeLayerEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/CloseCoWebsiteEvent.ts b/front/src/Api/Events/CloseCoWebsiteEvent.ts
new file mode 100644
index 00000000..94167d5e
--- /dev/null
+++ b/front/src/Api/Events/CloseCoWebsiteEvent.ts
@@ -0,0 +1,12 @@
+import * as tg from "generic-type-guard";
+
+export const isCloseCoWebsite = new tg.IsInterface()
+ .withProperties({
+ id: tg.isOptional(tg.isString)
+ })
+ .get();
+
+/**
+ * A message sent from the iFrame to the game to add a message in the chat.
+ */
+export type CloseCoWebsiteEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/HasPlayerMovedEvent.ts b/front/src/Api/Events/HasPlayerMovedEvent.ts
index 87b45482..a3f1aa21 100644
--- a/front/src/Api/Events/HasPlayerMovedEvent.ts
+++ b/front/src/Api/Events/HasPlayerMovedEvent.ts
@@ -6,6 +6,8 @@ export const isHasPlayerMovedEvent = new tg.IsInterface()
moving: tg.isBoolean,
x: tg.isNumber,
y: tg.isNumber,
+ oldX: tg.isOptional(tg.isNumber),
+ oldY: tg.isOptional(tg.isNumber),
})
.get();
diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts
index 861acc22..abb492c5 100644
--- a/front/src/Api/Events/IframeEvent.ts
+++ b/front/src/Api/Events/IframeEvent.ts
@@ -5,11 +5,10 @@ import type { ClosePopupEvent } from "./ClosePopupEvent";
import type { EnterLeaveEvent } from "./EnterLeaveEvent";
import type { GoToPageEvent } from "./GoToPageEvent";
import type { LoadPageEvent } from "./LoadPageEvent";
-import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent";
+import { isCoWebsite, isOpenCoWebsiteEvent } from "./OpenCoWebsiteEvent";
import type { OpenPopupEvent } from "./OpenPopupEvent";
import type { OpenTabEvent } from "./OpenTabEvent";
import type { UserInputChatEvent } from "./UserInputChatEvent";
-import type { MapDataEvent } from "./MapDataEvent";
import type { LayerEvent } from "./LayerEvent";
import type { SetPropertyEvent } from "./setPropertyEvent";
import type { LoadSoundEvent } from "./LoadSoundEvent";
@@ -27,12 +26,10 @@ import type { LoadTilesetEvent } from "./LoadTilesetEvent";
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
import type {
MessageReferenceEvent,
- removeActionMessage,
- triggerActionMessage,
- TriggerActionMessageEvent,
} from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
+import type { ChangeLayerEvent } from "./ChangeLayerEvent";
export interface TypedMessageEvent extends MessageEvent {
data: T;
@@ -48,8 +45,6 @@ export type IframeEventMap = {
closePopup: ClosePopupEvent;
openTab: OpenTabEvent;
goToPage: GoToPageEvent;
- openCoWebSite: OpenCoWebSiteEvent;
- closeCoWebSite: null;
disablePlayerControls: null;
restorePlayerControls: null;
displayBubble: null;
@@ -81,6 +76,8 @@ export interface IframeResponseEventMap {
userInputChat: UserInputChatEvent;
enterEvent: EnterLeaveEvent;
leaveEvent: EnterLeaveEvent;
+ enterLayerEvent: ChangeLayerEvent;
+ leaveLayerEvent: ChangeLayerEvent;
buttonClickedEvent: ButtonClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent;
menuItemClicked: MenuItemClickedEvent;
@@ -118,6 +115,22 @@ export const iframeQueryMapTypeGuards = {
query: isLoadTilesetEvent,
answer: tg.isNumber,
},
+ openCoWebsite: {
+ query: isOpenCoWebsiteEvent,
+ answer: isCoWebsite
+ },
+ getCoWebsites: {
+ query: tg.isUndefined,
+ answer: tg.isArray(isCoWebsite)
+ },
+ closeCoWebsite: {
+ query: tg.isString,
+ answer: tg.isUndefined
+ },
+ closeCoWebsites: {
+ query: tg.isUndefined,
+ answer: tg.isUndefined
+ },
triggerActionMessage: {
query: isTriggerActionMessageEvent,
answer: tg.isUndefined,
diff --git a/front/src/Api/Events/OpenCoWebSiteEvent.ts b/front/src/Api/Events/OpenCoWebSiteEvent.ts
deleted file mode 100644
index 7b5e6070..00000000
--- a/front/src/Api/Events/OpenCoWebSiteEvent.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as tg from "generic-type-guard";
-
-export const isOpenCoWebsite = new tg.IsInterface()
- .withProperties({
- url: tg.isString,
- allowApi: tg.isBoolean,
- allowPolicy: tg.isString,
- })
- .get();
-
-/**
- * A message sent from the iFrame to the game to add a message in the chat.
- */
-export type OpenCoWebSiteEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts
new file mode 100644
index 00000000..9c02b7a3
--- /dev/null
+++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts
@@ -0,0 +1,22 @@
+import * as tg from "generic-type-guard";
+
+export const isOpenCoWebsiteEvent = new tg.IsInterface()
+ .withProperties({
+ url: tg.isString,
+ allowApi: tg.isOptional(tg.isBoolean),
+ allowPolicy: tg.isOptional(tg.isString),
+ position: tg.isOptional(tg.isNumber)
+ })
+ .get();
+
+export const isCoWebsite = new tg.IsInterface()
+ .withProperties({
+ id: tg.isString,
+ position: tg.isNumber,
+ })
+ .get();
+
+/**
+ * A message sent from the iFrame to the game to add a message in the chat.
+ */
+export type OpenCoWebsiteEvent = tg.GuardedType;
diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts
index 140d2f34..871ec3b9 100644
--- a/front/src/Api/IframeListener.ts
+++ b/front/src/Api/IframeListener.ts
@@ -1,6 +1,5 @@
import { Subject } from "rxjs";
-import type * as tg from "generic-type-guard";
-import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
+import { isChatEvent } from "./Events/ChatEvent";
import { HtmlUtils } from "../WebRtc/HtmlUtils";
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
@@ -8,18 +7,15 @@ import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
import { scriptUtils } from "./ScriptUtils";
-import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
-import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
+import { isGoToPageEvent } from "./Events/GoToPageEvent";
+import { isCloseCoWebsite, CloseCoWebsiteEvent } from "./Events/CloseCoWebsiteEvent";
import {
IframeErrorAnswerEvent,
- IframeEvent,
- IframeEventMap,
IframeQueryMap,
IframeResponseEvent,
IframeResponseEventMap,
isIframeEventWrapper,
isIframeQueryWrapper,
- TypedMessageEvent,
} from "./Events/IframeEvent";
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
@@ -33,8 +29,8 @@ import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegi
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
import type { SetVariableEvent } from "./Events/SetVariableEvent";
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
-import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
+import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
type AnswererCallback = (
query: IframeQueryMap[T]["query"],
@@ -46,29 +42,14 @@ type AnswererCallback = (
* Also allows to send messages to those iframes.
*/
class IframeListener {
- private readonly _readyStream: Subject = new Subject();
- public readonly readyStream = this._readyStream.asObservable();
-
- private readonly _chatStream: Subject = new Subject();
- public readonly chatStream = this._chatStream.asObservable();
-
private readonly _openPopupStream: Subject = new Subject();
public readonly openPopupStream = this._openPopupStream.asObservable();
private readonly _openTabStream: Subject = new Subject();
public readonly openTabStream = this._openTabStream.asObservable();
- private readonly _goToPageStream: Subject = new Subject();
- public readonly goToPageStream = this._goToPageStream.asObservable();
-
private readonly _loadPageStream: Subject = new Subject();
- public readonly loadPageStream = this._loadPageStream.asObservable();
-
- private readonly _openCoWebSiteStream: Subject = new Subject();
- public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
-
- private readonly _closeCoWebSiteStream: Subject = new Subject();
- public readonly closeCoWebSiteStream = this._closeCoWebSiteStream.asObservable();
+ public readonly loadPageStream = this._loadPageStream.asObservable()
private readonly _disablePlayerControlStream: Subject = new Subject();
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
@@ -150,8 +131,6 @@ class IframeListener {
return;
}
- foundSrc = this.getBaseUrl(foundSrc, message.source);
-
if (isIframeQueryWrapper(payload)) {
const queryId = payload.id;
const query = payload.query;
@@ -219,7 +198,7 @@ class IframeListener {
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
this._setPropertyStream.next(payload.data);
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
- this._chatStream.next(payload.data);
+ scriptUtils.sendAnonymousChat(payload.data);
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
this._openPopupStream.next(payload.data);
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
@@ -236,15 +215,6 @@ class IframeListener {
this._stopSoundStream.next(payload.data);
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
this._loadSoundStream.next(payload.data);
- } else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
- scriptUtils.openCoWebsite(
- payload.data.url,
- foundSrc,
- payload.data.allowApi,
- payload.data.allowPolicy
- );
- } else if (payload.type === "closeCoWebSite") {
- scriptUtils.closeCoWebSite();
} else if (payload.type === "disablePlayerControls") {
this._disablePlayerControlStream.next();
} else if (payload.type === "restorePlayerControls") {
@@ -264,6 +234,9 @@ class IframeListener {
this.iframeCloseCallbacks.get(iframe)?.push(() => {
handleMenuUnregisterEvent(dataName);
});
+
+ foundSrc = this.getBaseUrl(foundSrc, message.source);
+
handleMenuRegistrationEvent(
payload.data.name,
payload.data.iframe,
@@ -296,7 +269,7 @@ class IframeListener {
registerScript(scriptUrl: string): Promise {
return new Promise((resolve, reject) => {
- console.log("Loading map related script at ", scriptUrl);
+ console.info("Loading map related script at ", scriptUrl);
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
// Using external iframe mode (
@@ -366,6 +339,20 @@ class IframeListener {
return src;
}
+ public getBaseUrlFromSource(source: MessageEventSource): string {
+ let foundSrc: string | undefined;
+ let iframe: HTMLIFrameElement | undefined;
+
+ for (iframe of this.iframes) {
+ if (iframe.contentWindow === source) {
+ foundSrc = iframe.src;
+ break;
+ }
+ }
+
+ return this.getBaseUrl(foundSrc ?? "", source);
+ }
+
private static getIFrameId(scriptUrl: string): string {
return "script" + btoa(scriptUrl);
}
@@ -409,6 +396,24 @@ class IframeListener {
});
}
+ sendEnterLayerEvent(layerName: string) {
+ this.postMessage({
+ type: "enterLayerEvent",
+ data: {
+ name: layerName,
+ } as ChangeLayerEvent,
+ });
+ }
+
+ sendLeaveLayerEvent(layerName: string) {
+ this.postMessage({
+ type: "leaveLayerEvent",
+ data: {
+ name: layerName,
+ } as ChangeLayerEvent,
+ });
+ }
+
hasPlayerMoved(event: HasPlayerMovedEvent) {
if (this.sendPlayerMove) {
this.postMessage({
diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts
index 0dbe40fe..10a80c92 100644
--- a/front/src/Api/ScriptUtils.ts
+++ b/front/src/Api/ScriptUtils.ts
@@ -1,4 +1,7 @@
-import { coWebsiteManager } from "../WebRtc/CoWebsiteManager";
+import { coWebsiteManager, CoWebsite } from "../WebRtc/CoWebsiteManager";
+import { playersStore } from "../Stores/PlayersStore";
+import { chatMessagesStore } from "../Stores/ChatStore";
+import type { ChatEvent } from "./Events/ChatEvent";
class ScriptUtils {
public openTab(url: string) {
@@ -9,12 +12,9 @@ class ScriptUtils {
window.location.href = url;
}
- public openCoWebsite(url: string, base: string, api: boolean, policy: string) {
- coWebsiteManager.loadCoWebsite(url, base, api, policy);
- }
-
- public closeCoWebSite() {
- coWebsiteManager.closeCoWebsite();
+ public sendAnonymousChat(chatEvent: ChatEvent) {
+ const userId = playersStore.addFacticePlayer(chatEvent.author);
+ chatMessagesStore.addExternalMessage(userId, chatEvent.message);
}
}
diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts
index f051a7c0..5acfa2a5 100644
--- a/front/src/Api/iframe/nav.ts
+++ b/front/src/Api/iframe/nav.ts
@@ -1,8 +1,15 @@
-import type { GoToPageEvent } from "../Events/GoToPageEvent";
-import type { OpenTabEvent } from "../Events/OpenTabEvent";
-import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
-import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent";
-import type { LoadPageEvent } from "../Events/LoadPageEvent";
+import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
+
+export class CoWebsite {
+ constructor(private readonly id: string, public readonly position: number) {}
+
+ close() {
+ return queryWorkadventure({
+ type: "closeCoWebsite",
+ data: this.id,
+ });
+ }
+}
export class WorkadventureNavigationCommands extends IframeApiContribution {
callbacks = [];
@@ -34,21 +41,34 @@ export class WorkadventureNavigationCommands extends IframeApiContribution {
+ const result = await queryWorkadventure({
+ type: "openCoWebsite",
data: {
url,
allowApi,
allowPolicy,
+ position,
},
});
+ return new CoWebsite(result.id, result.position);
}
- closeCoWebSite(): void {
- sendToWorkadventure({
- type: "closeCoWebSite",
- data: null,
+ async getCoWebSites(): Promise {
+ const result = await queryWorkadventure({
+ type: "getCoWebsites",
+ data: undefined
+ });
+ return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position));
+ }
+
+ /**
+ * @deprecated Use closeCoWebsites instead to close all co-websites
+ */
+ closeCoWebSite() {
+ return queryWorkadventure({
+ type: "closeCoWebsites",
+ data: undefined,
});
}
}
diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts
index 22df49c9..5eeadffe 100644
--- a/front/src/Api/iframe/room.ts
+++ b/front/src/Api/iframe/room.ts
@@ -1,6 +1,7 @@
import { Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
+import { ChangeLayerEvent, isChangeLayerEvent } from "../Events/ChangeLayerEvent";
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks";
@@ -12,6 +13,9 @@ import website from "./website";
const enterStreams: Map> = new Map>();
const leaveStreams: Map> = new Map>();
+const enterLayerStreams: Map> = new Map>();
+const leaveLayerStreams: Map> = new Map>();
+
interface TileDescriptor {
x: number;
y: number;
@@ -47,8 +51,25 @@ export class WorkadventureRoomCommands extends IframeApiContribution {
+ enterLayerStreams.get(payloadData.name)?.next();
+ },
+ }),
+ apiCallback({
+ type: "leaveLayerEvent",
+ typeChecker: isChangeLayerEvent,
+ callback: (payloadData) => {
+ leaveLayerStreams.get(payloadData.name)?.next();
+ },
+ }),
];
+ /**
+ * @deprecated Use onEnterLayer instead
+ */
onEnterZone(name: string, callback: () => void): void {
let subject = enterStreams.get(name);
if (subject === undefined) {
@@ -57,6 +78,10 @@ export class WorkadventureRoomCommands extends IframeApiContribution void): void {
let subject = leaveStreams.get(name);
if (subject === undefined) {
@@ -65,12 +90,35 @@ export class WorkadventureRoomCommands extends IframeApiContribution {
+ let subject = enterLayerStreams.get(layerName);
+ if (subject === undefined) {
+ subject = new Subject();
+ enterLayerStreams.set(layerName, subject);
+ }
+
+ return subject;
+ }
+
+ onLeaveLayer(layerName: string): Subject {
+ let subject = leaveLayerStreams.get(layerName);
+ if (subject === undefined) {
+ subject = new Subject();
+ leaveLayerStreams.set(layerName, subject);
+ }
+
+ return subject;
+ }
+
showLayer(layerName: string): void {
sendToWorkadventure({ type: "showLayer", data: { name: layerName } });
}
+
hideLayer(layerName: string): void {
sendToWorkadventure({ type: "hideLayer", data: { name: layerName } });
}
+
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
sendToWorkadventure({
type: "setProperty",
@@ -81,10 +129,12 @@ export class WorkadventureRoomCommands extends IframeApiContribution {
const event = await queryWorkadventure({ type: "getMapData", data: undefined });
return event.data as ITiledMap;
}
+
setTiles(tiles: TileDescriptor[]) {
sendToWorkadventure({
type: "setTiles",
diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte
index 8b033e5f..ab8b6d3f 100644
--- a/front/src/Components/App.svelte
+++ b/front/src/Components/App.svelte
@@ -1,6 +1,7 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front/src/Components/Menu/AboutRoomSubMenu.svelte b/front/src/Components/Menu/AboutRoomSubMenu.svelte
index 3ccc9669..6c10cc76 100644
--- a/front/src/Components/Menu/AboutRoomSubMenu.svelte
+++ b/front/src/Components/Menu/AboutRoomSubMenu.svelte
@@ -4,7 +4,6 @@
let gameScene = gameManager.getCurrentGameScene();
- let HTMLShareLink: HTMLInputElement;
let expandedMapCopyright = false;
let expandedTilesetCopyright = false;
@@ -38,33 +37,9 @@
}
}
})
-
- function copyLink() {
- HTMLShareLink.select();
- document.execCommand('copy');
- }
-
- async function shareLink() {
- const shareData = {url: location.toString()};
-
- try {
- await navigator.share(shareData);
- } catch (err) {
- console.error('Error: ' + err);
- }
- }
+ WorkAdventure allows you to create an online space to communicate spontaneously with others.
+ And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
+
+
+
+
+
+
Create your map
+
You can also create your own custom map by following the step of the documentation.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front/src/Components/Menu/GuestSubMenu.svelte b/front/src/Components/Menu/GuestSubMenu.svelte
new file mode 100644
index 00000000..bda16ca5
--- /dev/null
+++ b/front/src/Components/Menu/GuestSubMenu.svelte
@@ -0,0 +1,75 @@
+
+
+
+
+
+
Share the link of the room !
+
+
+
+
+
Share the link of the room !
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/front/src/Components/Menu/Menu.svelte b/front/src/Components/Menu/Menu.svelte
index 6cbef9c1..83715304 100644
--- a/front/src/Components/Menu/Menu.svelte
+++ b/front/src/Components/Menu/Menu.svelte
@@ -2,11 +2,11 @@
import {fly} from "svelte/transition";
import SettingsSubMenu from "./SettingsSubMenu.svelte";
import ProfileSubMenu from "./ProfileSubMenu.svelte";
- import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
import ContactSubMenu from "./ContactSubMenu.svelte";
import CustomSubMenu from "./CustomSubMenu.svelte"
+ import GuestSubMenu from "./GuestSubMenu.svelte";
import {
checkSubMenuToShow,
customMenuIframe,
@@ -19,21 +19,21 @@
import type {Unsubscriber} from "svelte/store";
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
- let activeSubMenu: string = SubMenusInterface.settings;
- let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
+ let activeSubMenu: string = SubMenusInterface.profile;
+ let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
let props: { url: string, allowApi: boolean };
let unsubscriberSubMenuStore: Unsubscriber;
onMount(() => {
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
if(!get(subMenusStore).includes(activeSubMenu)) {
- switchMenu(SubMenusInterface.settings);
+ switchMenu(SubMenusInterface.profile);
}
})
checkSubMenuToShow();
- switchMenu(SubMenusInterface.settings);
+ switchMenu(SubMenusInterface.profile);
})
onDestroy(() => {
@@ -52,8 +52,8 @@
case SubMenusInterface.profile:
activeComponent = ProfileSubMenu;
break;
- case SubMenusInterface.createMap:
- activeComponent = CreateMapSubMenu;
+ case SubMenusInterface.invite:
+ activeComponent = GuestSubMenu;
break;
case SubMenusInterface.aboutRoom:
activeComponent = AboutRoomSubMenu;
@@ -124,6 +124,7 @@
top: 10%;
position: relative;
+ z-index: 80;
margin: auto;
display: grid;
diff --git a/front/src/Components/Menu/MenuIcon.svelte b/front/src/Components/Menu/MenuIcon.svelte
index 92a52ba3..4b37238f 100644
--- a/front/src/Components/Menu/MenuIcon.svelte
+++ b/front/src/Components/Menu/MenuIcon.svelte
@@ -1,34 +1,41 @@
-
+
+
+
+
+
+
+
+
+
+ Profile validated by domain: ${domain}
+
+
+ Your email: ${email}
+
+