Merge pull request #1581 from thecodingmachine/develop
Deploy 2021-12-24
@ -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=
|
2
.github/workflows/build-and-deploy.yml
vendored
@ -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 }}
|
||||
|
||||
|
51
.github/workflows/end_to_end_tests.yml
vendored
Normal file
@ -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
|
41
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
|
||||
|
||||
|
BIN
README-INTRO.jpg
Before Width: | Height: | Size: 386 KiB |
1
README-LOGO.svg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
README-MAP.png
Normal file
After Width: | Height: | Size: 60 KiB |
16
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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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<VariablesManager> | undefined;
|
||||
private variableManagerLastLoad: Date | undefined;
|
||||
|
||||
private getVariableManager(): Promise<VariablesManager> {
|
||||
if (!this.variableManagerPromise) {
|
||||
this.variableManagerLastLoad = new Date();
|
||||
this.variableManagerPromise = this.getMap()
|
||||
.then((map) => {
|
||||
const variablesManager = new VariablesManager(this.roomUrl, map);
|
||||
|
@ -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<User>;
|
||||
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<User>();
|
||||
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);
|
||||
}
|
||||
|
@ -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<ZoneDescriptor> {
|
||||
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<User> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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<ITiledMap> {
|
||||
// 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');
|
||||
}
|
||||
|
||||
|
9
back/src/Services/VariableError.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Errors related to variable handling.
|
||||
*/
|
||||
export class VariableError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, VariableError.prototype);
|
||||
}
|
||||
}
|
@ -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 "' +
|
||||
|
67
back/tests/getNearbyDescriptorsMatrixTest.ts
Normal file
@ -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},
|
||||
])
|
||||
});*/
|
||||
});
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": {
|
||||
|
@ -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:
|
||||
|
12
docker-compose.testcafe.yml
Normal file
@ -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
|
@ -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:
|
||||
|
@ -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();
|
||||
}
|
||||
}]);
|
||||
});
|
||||
})
|
||||
```
|
||||
|
@ -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)`.
|
||||
- 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).
|
@ -49,19 +49,34 @@ WA.nav.goToRoom('../otherMap/map.json');
|
||||
WA.nav.goToRoom("/_/global/<path to global map>.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<CoWebsite>
|
||||
```
|
||||
|
||||
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<CoWebsite[]>
|
||||
```
|
||||
|
||||
Get all opened co-websites with their ids and positions.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
const coWebsites = await WA.nav.getCowebSites();
|
||||
```
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
<div>
|
||||
<figure class="figure">
|
||||
<img src="images/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
* **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
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`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<void>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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',
|
||||
],
|
||||
]
|
||||
],
|
||||
|
@ -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
|
||||
<img src="images/open_website_policy.jpg" class="figure-img img-fluid rounded" alt="" />
|
||||
<figcaption class="figure-caption">The generated iFrame will have the allow attribute set to: <code><iframe allow="fullscreen"></code></figcaption>
|
||||
</figure>
|
||||
|
||||
### 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!
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
2
front/dist/.htaccess
vendored
@ -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]
|
||||
|
75
front/dist/index.tmpl.html
vendored
@ -37,6 +37,54 @@
|
||||
<div class="main-container" id="main-container">
|
||||
<!-- Create the editor container -->
|
||||
<div id="game" class="game">
|
||||
<div id="cowebsite-container">
|
||||
<div id="cowebsite-container-main">
|
||||
<div id="cowebsite-slot-1">
|
||||
<div class="actions">
|
||||
<button type="button" class="nes-btn is-primary expand">></button>
|
||||
<button type="button" class="nes-btn is-error close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cowebsite-container-sub">
|
||||
<div id="cowebsite-slot-2">
|
||||
<div class="overlay">
|
||||
<div class="actions">
|
||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
||||
</div>
|
||||
<div class="actions-move">
|
||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cowebsite-slot-3">
|
||||
<div class="overlay">
|
||||
<div class="actions">
|
||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
||||
</div>
|
||||
<div class="actions-move">
|
||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="cowebsite-slot-4">
|
||||
<div class="overlay">
|
||||
<div class="actions">
|
||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
||||
</div>
|
||||
<div class="actions-move">
|
||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="svelte-overlay"></div>
|
||||
<div id="game-overlay" class="game-overlay">
|
||||
<div id="main-section" class="main-section">
|
||||
@ -48,19 +96,24 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="cowebsite" class="cowebsite hidden">
|
||||
<aside id="cowebsite-aside">
|
||||
<img src="/static/images/menu.svg" alt="hold to resize"/>
|
||||
<aside id="cowebsite-aside" class="noselect">
|
||||
<div id="cowebsite-aside-buttons">
|
||||
<button class="top-right-btn nes-btn is-error" id="cowebsite-close" alt="close all co-websites">
|
||||
×
|
||||
</button>
|
||||
<button class="top-right-btn nes-btn is-primary" id="cowebsite-fullscreen" alt="fullscreen mode">
|
||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
||||
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
||||
</button>
|
||||
</div>
|
||||
<div id="cowebsite-aside-holder">
|
||||
<img src="/static/images/menu.svg" alt="hold to resize"/>
|
||||
</div>
|
||||
<div id="cowebsite-sub-icons"></div>
|
||||
</aside>
|
||||
<main id="cowebsite-main">
|
||||
</main>
|
||||
<button class="top-right-btn" id="cowebsite-fullscreen" alt="fullscreen mode">
|
||||
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
||||
</button>
|
||||
<button class="top-right-btn" id="cowebsite-close" alt="close the iframe">
|
||||
<img src="resources/logos/close.svg"/>
|
||||
</button>
|
||||
<main id="cowebsite-slot-0"></main>
|
||||
</div>
|
||||
<div id="cowebsite-buffer"></div>
|
||||
</div>
|
||||
|
||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||
|
BIN
front/dist/resources/logos/logo.png
vendored
Before Width: | Height: | Size: 16 KiB |
BIN
front/dist/resources/logos/tcm_full.png
vendored
Before Width: | Height: | Size: 300 B |
BIN
front/dist/resources/logos/tcm_short.png
vendored
Before Width: | Height: | Size: 216 B |
BIN
front/dist/resources/objects/talk.png
vendored
Before Width: | Height: | Size: 516 B After Width: | Height: | Size: 389 B |
BIN
front/dist/static/images/Logo TCM.png
vendored
Before Width: | Height: | Size: 12 KiB |
BIN
front/dist/static/images/favicons/android-icon-144x144-white.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
front/dist/static/images/favicons/android-icon-192x192-white.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
front/dist/static/images/favicons/android-icon-36x36.-white.png
vendored
Normal file
After Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 754 B After Width: | Height: | Size: 841 B |
BIN
front/dist/static/images/favicons/android-icon-48x48-white.png
vendored
Normal file
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 922 B After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/favicons/android-icon-72x72-white.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/favicons/android-icon-96x96-white.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
front/dist/static/images/favicons/apple-icon-114x114-white.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
front/dist/static/images/favicons/apple-icon-120x120-white.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
front/dist/static/images/favicons/apple-icon-144x144-white.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
front/dist/static/images/favicons/apple-icon-152x152-white.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
front/dist/static/images/favicons/apple-icon-180x180-white.png
vendored
Normal file
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
front/dist/static/images/favicons/apple-icon-57x57-white.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/favicons/apple-icon-60x60-white.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
front/dist/static/images/favicons/apple-icon-72x72-white.png
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/favicons/apple-icon-76x76-white.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
front/dist/static/images/favicons/apple-icon-precomposed-white.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
front/dist/static/images/favicons/apple-icon-white.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
front/dist/static/images/favicons/apple-icon.png
vendored
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
front/dist/static/images/favicons/favicon-16x16-white.png
vendored
Normal file
After Width: | Height: | Size: 445 B |
BIN
front/dist/static/images/favicons/favicon-16x16.png
vendored
Before Width: | Height: | Size: 713 B After Width: | Height: | Size: 471 B |
BIN
front/dist/static/images/favicons/favicon-32x32-white.png
vendored
Normal file
After Width: | Height: | Size: 792 B |
BIN
front/dist/static/images/favicons/favicon-32x32.png
vendored
Before Width: | Height: | Size: 848 B After Width: | Height: | Size: 848 B |
BIN
front/dist/static/images/favicons/favicon-96x96-white.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
front/dist/static/images/favicons/favicon-96x96.png
vendored
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
front/dist/static/images/favicons/favicon.ico
vendored
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
front/dist/static/images/favicons/icon-512x512-white.png
vendored
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
front/dist/static/images/favicons/icon-512x512.png
vendored
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.2 KiB |
@ -89,7 +89,7 @@
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0",
|
||||
"purpose": "any maskable"
|
||||
"purpose": "any"
|
||||
},
|
||||
|
||||
{
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
front/dist/static/images/favicons/ms-icon-70x70.png
vendored
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
front/dist/static/images/logo-WA-min.png
vendored
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
front/dist/static/images/logo.png
vendored
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 3.4 KiB |
@ -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",
|
||||
|
91
front/src/Administration/AnalyticsClient.ts
Normal file
@ -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<any>|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();
|
11
front/src/Api/Events/ChangeLayerEvent.ts
Normal file
@ -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<typeof isChangeLayerEvent>;
|
12
front/src/Api/Events/CloseCoWebsiteEvent.ts
Normal file
@ -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<typeof isCloseCoWebsite>;
|
@ -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();
|
||||
|
||||
|