Compare commits

...

62 commits

Author SHA1 Message Date
Ludwig Behm 2be30c101d front: rename title and description
make clear distinction that this app isn't the official WorkAdventure
make clear that this is just a WorkAdventure instance
2022-02-14 00:20:54 +01:00
Ludwig Behm a5a6c06b74 pusher: add missing Content-Type header for json responses 2022-02-12 22:19:48 +01:00
Ludwig Behm 9e30039551 Merge branch 'develop' of https://github.com/thecodingmachine/workadventure into develop 2022-02-12 00:16:44 +01:00
Ludwig Behm 6b846fd793 docker: fix domain prefix for dev environment 2022-02-12 00:15:49 +01:00
David Négrier 55c2c2e555
Merge pull request #1843 from thecodingmachine/move-to-improvements
Move to improvements
2022-02-11 18:28:50 +01:00
David Négrier 0b82df0d41 Merge branch 'develop' of github.com:thecodingmachine/workadventure into move-to-improvements 2022-02-11 16:58:40 +01:00
David Négrier 80bfeb823e
Merge pull request #1852 from thecodingmachine/stabilize-cowebsite
Stabilize cowebsite
2022-02-11 16:05:19 +01:00
David Négrier 62f8a131a9 Adding translation for "Walk automatically to my position" 2022-02-11 15:52:24 +01:00
David Négrier a91f022fd2 Making typesafe-i18n watch by default 2022-02-11 15:47:57 +01:00
David Négrier 664cce87b8 Improving rendering of share screen 2022-02-11 15:37:23 +01:00
Alexis Faizeau 5137190558 Change cowebsite closing animation 2022-02-11 15:06:41 +01:00
Alexis Faizeau b7f4c0eecc Fix bad type base i18n translations 2022-02-10 19:30:41 +01:00
Alexis Faizeau a5e0c2a9cf Add main cowebsite minimize indicator 2022-02-10 19:30:41 +01:00
David Négrier e6e99c1911
Merge pull request #1844 from Lithimlin/develop
Update production templates
2022-02-10 17:34:47 +01:00
Alexis Faizeau b0c0d22f25 Translate game map properties trigger messages 2022-02-10 17:30:03 +01:00
Alexis Faizeau 460d67534a Move all game scene game map properties listeners 2022-02-10 17:02:10 +01:00
Alexis Faizeau aa9b5e37c7
Merge pull request #1850 from thecodingmachine/stabilize-cowebsite
Fix Jitsi co-website reloading
2022-02-10 15:50:59 +01:00
Alexis Faizeau 666b6df588 Fix Jitsi co-website reloading 2022-02-10 15:37:04 +01:00
David Négrier b851dd1f52
Merge pull request #1838 from thecodingmachine/stabilize-cowebsite
Fix cowebsite closing on fast opening/closing
2022-02-10 12:16:04 +01:00
Alexis Faizeau 7b6a3949bc Move CoWebsite to generic class 2022-02-10 11:47:56 +01:00
Julian Euler 92f4728677 prod template: remove maps container
As stated by moufmouf, this is only meant for testing and hosting maps
2022-02-10 10:26:46 +01:00
Julian Euler 5aa46f6d84 prod template: move admin api URL and remove duplicate 2022-02-10 10:18:44 +01:00
David Négrier 769d63d6eb
Merge pull request #1841 from thecodingmachine/fix-issue-1835
collisionGrid now updates on showing / hiding layers
2022-02-09 18:18:04 +01:00
Hanusiak Piotr 470e3ca1ce cr fixes #1 2022-02-09 15:15:14 +01:00
Julian Euler f8f6b8aad9 prod templates: add contact url & openID, move vars
The old template was outdated.
Added additional explanations to variables and divided them into 
sections.
2022-02-09 14:18:03 +01:00
Julian Euler fb65510413 update docker production templates 2022-02-09 14:11:28 +01:00
Hanusiak Piotr c13672c9dc updated invitation link creator 2022-02-09 13:03:14 +01:00
Hanusiak Piotr 9cccdca3dc docs update 2022-02-09 11:09:20 +01:00
Hanusiak Piotr 71e18d1838 update moveTo test map 2022-02-09 11:05:43 +01:00
Hanusiak Piotr b565080312 parse x and y position from moveTo param 2022-02-09 10:52:44 +01:00
Hanusiak Piotr fe570c9117 moveTo object position 2022-02-09 10:17:31 +01:00
Hanusiak Piotr 20267d3483 Merge branch 'develop' into fix-issue-1835 2022-02-09 09:35:43 +01:00
Hanusiak Piotr b32a2970e4 collisionGrid now updates on showing / hiding layers 2022-02-08 15:46:41 +01:00
David Négrier c39f23de6f
Merge pull request #1839 from thecodingmachine/parallax-effect
Parallax effect
2022-02-08 14:07:58 +01:00
Hanusiak Piotr 9cfebecde2 parallaxx and parallaxy made optional 2022-02-08 13:32:02 +01:00
Hanusiak Piotr 57bd8783e0 scroll factor applied automatically 2022-02-08 13:17:35 +01:00
Alexis Faizeau f5f71f32ee Fix cowebsite closing on fast opening/closing 2022-02-08 11:19:29 +01:00
Hanusiak Piotr a53cbbff34 create test map 2022-02-08 11:04:12 +01:00
Alexis Faizeau 31f3b2b48e
Merge pull request #1834 from thecodingmachine/stabilize-cowebsite
Remove priority on iframe add to cowebsites
2022-02-07 18:42:52 +01:00
Alexis Faizeau 7334d59c4f Remove priority on iframe add to cowebsites 2022-02-07 18:41:24 +01:00
David Négrier 3953b78cfe
Merge pull request #1823 from lukashass/fix-vscode-prettier
Remove explicit definition of prettier-plugin-svelte
2022-02-07 18:39:32 +01:00
Alexis Faizeau 5dc3fbcce5
Merge pull request #1833 from thecodingmachine/stabilize-cowebsite
Fix multi cowebsite creation by trigger
2022-02-07 18:24:04 +01:00
Alexis Faizeau c770846558 Fix multi cowebsite creation by trigger 2022-02-07 18:23:02 +01:00
David Négrier 41183e8bc2
Merge pull request #1828 from thecodingmachine/stabilize-cowebsite
Restore trigger message action on co-websites
2022-02-07 17:58:44 +01:00
Alexis Faizeau e51300e850 Add cowebsite percentWidth property to the documention 2022-02-07 17:53:36 +01:00
Alexis Faizeau 21c198a882
Merge pull request #1831 from thecodingmachine/fix-language-display
Somes bugs due to i18n implementation
2022-02-07 17:33:59 +01:00
Alexis Faizeau 433d3a20c6 Fix raw html not displayed buy terms translation key 2022-02-07 17:32:05 +01:00
Alexis Faizeau c2da4c3906 Fix active custom menu button 2022-02-07 17:25:55 +01:00
Alexis Faizeau 75d42209f4 Hide main cowebsite 2022-02-07 17:09:52 +01:00
Alexis Faizeau 60c17ecea2 Re-implement set width of main cowebsite 2022-02-07 14:55:51 +01:00
David Négrier cffafe9f15
Merge pull request #1827 from thecodingmachine/activatable-fixes
Activatable fixes
2022-02-07 14:39:40 +01:00
Hanusiak Piotr c29ce6e9a9 prettier 2022-02-07 14:23:34 +01:00
Hanusiak Piotr d480150728 cleaner approach to disable activatablesManager distance check if space-event 2022-02-07 14:22:43 +01:00
Hanusiak Piotr 81272fbb3c yarn.lock update 2022-02-07 13:47:33 +01:00
Hanusiak Piotr 4bae6e75b1 enable activating by distance if in JITSI and JITSI was already opened 2022-02-07 12:37:59 +01:00
Hanusiak Piotr aec7790875 disable activation by distance if in JITSI 2022-02-07 12:36:33 +01:00
Alexis Faizeau 9571a52f1e Re-implement action message on cowebsite trigger 2022-02-07 11:21:04 +01:00
Hanusiak Piotr bf0d2eb412 directional shift for current player when trying to activate entities 2022-02-07 11:08:52 +01:00
Hanusiak Piotr 72b4438d1e outline color is decided from particular system level 2022-02-07 10:39:03 +01:00
Alexis Faizeau 862c502bea
Merge pull request #1824 from thecodingmachine/fix-focus-iframe
Fix game unfocusable after clicking on iframe
2022-02-04 14:59:15 +01:00
Alexis Faizeau 6863fa3764 Fix game unfocusable after clicking on iframe 2022-02-04 14:46:57 +01:00
Lukas Hass de5a505296
remove explicit definition of prettier-plugin-svelte
fixes prettier in vscode
2022-02-03 19:26:25 +01:00
62 changed files with 1846 additions and 828 deletions

View file

@ -1,20 +1,118 @@
# Security
#
SECRET_KEY=
ADMIN_API_TOKEN=
#
# Networking
#
# The base domain # The base domain
DOMAIN=workadventure.localhost DOMAIN=workadventure.localhost
DEBUG_MODE=false # Subdomains
# MUST match the DOMAIN variable above
FRONT_HOST=front.workadventure.localhost
PUSHER_HOST=pusher.workadventure.localhost
BACK_HOST=api.workadventure.localhost
MAPS_HOST=maps.workadventure.localhost
ICON_HOST=icon.workadventure.localhost
# SAAS admin panel
ADMIN_API_URL=
#
# Basic configuration
#
# The directory to store data in
DATA_DIR=./wa
# The URL used by default, in the form: "/_/global/map/url.json"
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
# 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=
MAX_PER_GROUP=4
MAX_USERNAME_LENGTH=8
DISABLE_ANONYMOUS=false
# The version of the docker image to use
# MUST uncomment "image" keys in the docker-compose file for it to be effective
VERSION=master
TZ=Europe/Paris
#
# Jitsi
#
JITSI_URL=meet.jit.si JITSI_URL=meet.jit.si
# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret # If your Jitsi environment has authentication set up,
# you MUST set JITSI_PRIVATE_MODE to "true"
# and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret
JITSI_PRIVATE_MODE=false JITSI_PRIVATE_MODE=false
JITSI_ISS= JITSI_ISS=
SECRET_JITSI_KEY= SECRET_JITSI_KEY=
#
# Turn/Stun
#
# URL of the TURN server (needed to "punch a hole" through some networks for P2P connections) # URL of the TURN server (needed to "punch a hole" through some networks for P2P connections)
TURN_SERVER= TURN_SERVER=
TURN_USER= TURN_USER=
TURN_PASSWORD= TURN_PASSWORD=
# If your Turn server is configured to use the Turn REST API, you MUST 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.
TURN_STATIC_AUTH_SECRET=
# URL of the STUN server
STUN_SERVER=
# The URL used by default, in the form: "/_/global/map/url.json" #
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json # Certificate config
#
# The email address used by Let's encrypt to send renewal warnings (compulsory) # The email address used by Let's encrypt to send renewal warnings (compulsory)
ACME_EMAIL= ACME_EMAIL=
#
# Additional app configs
# Configuration for apps which are not workadventure itself
#
# openID
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=
#
# Advanced configuration
# Generally does not need to be changed
#
# Networking
HTTP_PORT=80
HTTPS_PORT=443
# Workadventure settings
DISABLE_NOTIFICATIONS=false
SKIP_RENDER_OPTIMIZATIONS=false
STORE_VARIABLES_FOR_LOCAL_MAPS=true
# Debugging options
DEBUG_MODE=false
LOG_LEVEL=WARN
# Internal URLs
API_URL=back:50051
RESTART_POLICY=unless-stopped

View file

@ -11,9 +11,9 @@ services:
DEBUG_MODE: "$DEBUG_MODE" DEBUG_MODE: "$DEBUG_MODE"
JITSI_URL: "$JITSI_URL" JITSI_URL: "$JITSI_URL"
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
PUSHER_URL: "https://pusher.dev.${DOMAIN}" PUSHER_URL: "https://pusher.${DOMAIN}"
ICON_URL: "https://icon.${DOMAIN}" ICON_URL: "https://icon.${DOMAIN}"
API_URL: "pusher.dev.${DOMAIN}" API_URL: "pusher.${DOMAIN}"
STUN_SERVER: "${STUN_SERVER}" STUN_SERVER: "${STUN_SERVER}"
TURN_SERVER: "${TURN_SERVER}" TURN_SERVER: "${TURN_SERVER}"
TURN_USER: "${TURN_USER}" TURN_USER: "${TURN_USER}"
@ -38,7 +38,7 @@ services:
API_URL: back-dev:50051 API_URL: back-dev:50051
JITSI_URL: $JITSI_URL JITSI_URL: $JITSI_URL
JITSI_ISS: $JITSI_ISS JITSI_ISS: $JITSI_ISS
FRONT_URL: https://play.dev.${DOMAIN} FRONT_URL: https://play.${DOMAIN}
ports: ports:
- "127.0.0.1:8012:8080" - "127.0.0.1:8012:8080"
restart: unless-stopped restart: unless-stopped

View file

@ -1,114 +1,128 @@
version: "3.3" version: "3.5"
services: services:
reverse-proxy: reverse-proxy:
image: traefik:v2.3 image: traefik:v2.6
command: command:
- --log.level=WARN - --log.level=${LOG_LEVEL}
#- --api.insecure=true
- --providers.docker - --providers.docker
- --entryPoints.web.address=:80 # Entry points
- --entryPoints.web.address=:${HTTP_PORT}
- --entrypoints.web.http.redirections.entryPoint.to=websecure - --entrypoints.web.http.redirections.entryPoint.to=websecure
- --entrypoints.web.http.redirections.entryPoint.scheme=https - --entrypoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.websecure.address=:443 - --entryPoints.websecure.address=:${HTTPS_PORT}
# HTTP challenge
- --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL} - --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/acme.json - --certificatesresolvers.myresolver.acme.storage=/acme.json
# used during the challenge
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
# Let's Encrypt's staging server
# uncomment during testing to avoid rate limiting
#- --certificatesresolvers.dnsresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
ports: ports:
- "80:80" - "${HTTP_PORT}:80"
- "443:443" - "${HTTPS_PORT}:443"
# The Web UI (enabled by --api.insecure=true)
#- "8080:8080"
depends_on:
- pusher
- front
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ./acme.json:/acme.json - ${DATA_DIR}/letsencrypt/acme.json:/acme.json
restart: unless-stopped restart: ${RESTART_POLICY}
front: front:
build: build:
context: ../.. context: ../..
dockerfile: front/Dockerfile dockerfile: front/Dockerfile
#image: thecodingmachine/workadventure-front:master #image: thecodingmachine/workadventure-front:${VERSION}
environment: environment:
DEBUG_MODE: "$DEBUG_MODE" - DEBUG_MODE
JITSI_URL: $JITSI_URL - JITSI_URL
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE" - JITSI_PRIVATE_MODE
PUSHER_URL: //pusher.${DOMAIN} - PUSHER_URL=//${PUSHER_HOST}
ICON_URL: //icon.${DOMAIN} - ICON_URL=//${ICON_HOST}
TURN_SERVER: "${TURN_SERVER}" - TURN_SERVER
TURN_USER: "${TURN_USER}" - TURN_USER
TURN_PASSWORD: "${TURN_PASSWORD}" - TURN_PASSWORD
START_ROOM_URL: "${START_ROOM_URL}" - TURN_STATIC_AUTH_SECRET
- STUN_SERVER
- START_ROOM_URL
- SKIP_RENDER_OPTIMIZATIONS
- MAX_PER_GROUP
- MAX_USERNAME_LENGTH
- DISABLE_ANONYMOUS
- DISABLE_NOTIFICATIONS
labels: labels:
- "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)" - "traefik.http.routers.front.rule=Host(`${FRONT_HOST}`)"
- "traefik.http.routers.front.entryPoints=web,traefik" - "traefik.http.routers.front.entryPoints=web"
- "traefik.http.services.front.loadbalancer.server.port=80" - "traefik.http.services.front.loadbalancer.server.port=80"
- "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)" - "traefik.http.routers.front-ssl.rule=Host(`${FRONT_HOST}`)"
- "traefik.http.routers.front-ssl.entryPoints=websecure" - "traefik.http.routers.front-ssl.entryPoints=websecure"
- "traefik.http.routers.front-ssl.tls=true"
- "traefik.http.routers.front-ssl.service=front" - "traefik.http.routers.front-ssl.service=front"
- "traefik.http.routers.front-ssl.tls=true"
- "traefik.http.routers.front-ssl.tls.certresolver=myresolver" - "traefik.http.routers.front-ssl.tls.certresolver=myresolver"
restart: unless-stopped restart: ${RESTART_POLICY}
pusher: pusher:
build: build:
context: ../.. context: ../..
dockerfile: pusher/Dockerfile dockerfile: pusher/Dockerfile
#image: thecodingmachine/workadventure-pusher:master #image: thecodingmachine/workadventure-pusher:${VERSION}
command: yarn run runprod command: yarn run runprod
environment: environment:
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" - SECRET_JITSI_KEY
SECRET_KEY: yourSecretKey - SECRET_KEY
API_URL: back:50051 - API_URL
JITSI_URL: $JITSI_URL - FRONT_URL=https://${FRONT_HOST}
JITSI_ISS: $JITSI_ISS - JITSI_URL
FRONT_URL: https://play.${DOMAIN} - JITSI_ISS
- DISABLE_ANONYMOUS
labels: labels:
- "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)" - "traefik.http.routers.pusher.rule=Host(`${PUSHER_HOST}`)"
- "traefik.http.routers.pusher.entryPoints=web,traefik" - "traefik.http.routers.pusher.entryPoints=web"
- "traefik.http.services.pusher.loadbalancer.server.port=8080" - "traefik.http.services.pusher.loadbalancer.server.port=8080"
- "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)" - "traefik.http.routers.pusher-ssl.rule=Host(${PUSHER_HOST}`)"
- "traefik.http.routers.pusher-ssl.entryPoints=websecure" - "traefik.http.routers.pusher-ssl.entryPoints=websecure"
- "traefik.http.routers.pusher-ssl.tls=true"
- "traefik.http.routers.pusher-ssl.service=pusher" - "traefik.http.routers.pusher-ssl.service=pusher"
- "traefik.http.routers.pusher-ssl.tls=true"
- "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver" - "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver"
restart: unless-stopped restart: ${RESTART_POLICY}
back: back:
build: build:
context: ../.. context: ../..
dockerfile: back/Dockerfile dockerfile: back/Dockerfile
#image: thecodingmachine/workadventure-back:master #image: thecodingmachine/workadventure-back:${VERSION}
command: yarn run runprod command: yarn run runprod
environment: environment:
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY" - SECRET_JITSI_KEY
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" - SECRET_KEY
ADMIN_API_URL: "$ADMIN_API_URL" - ADMIN_API_TOKEN
JITSI_URL: $JITSI_URL - ADMIN_API_URL
JITSI_ISS: $JITSI_ISS - TURN_SERVER
- TURN_USER
- TURN_PASSWORD
- TURN_STATIC_AUTH_SECRET
- STUN_SERVER
- JITSI_URL
- JITSI_ISS
- MAX_PER_GROUP
- STORE_VARIABLES_FOR_LOCAL_MAPS
labels: labels:
- "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)" - "traefik.http.routers.back.rule=Host(`${BACK_HOST}`)"
- "traefik.http.routers.back.entryPoints=web" - "traefik.http.routers.back.entryPoints=web"
- "traefik.http.services.back.loadbalancer.server.port=8080" - "traefik.http.services.back.loadbalancer.server.port=8080"
- "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)" - "traefik.http.routers.back-ssl.rule=Host(`${BACK_HOST}`)"
- "traefik.http.routers.back-ssl.entryPoints=websecure" - "traefik.http.routers.back-ssl.entryPoints=websecure"
- "traefik.http.routers.back-ssl.tls=true"
- "traefik.http.routers.back-ssl.service=back" - "traefik.http.routers.back-ssl.service=back"
- "traefik.http.routers.back-ssl.tls=true"
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver" - "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
restart: unless-stopped restart: ${RESTART_POLICY}
icon: icon:
image: matthiasluedtke/iconserver:v3.13.0 image: matthiasluedtke/iconserver:v3.13.0
labels: labels:
- "traefik.http.routers.icon.rule=Host(`icon.${DOMAIN}`)" - "traefik.http.routers.icon.rule=Host(`${ICON_HOST}`)"
- "traefik.http.routers.icon.entryPoints=web,traefik" - "traefik.http.routers.icon.entryPoints=web,traefik"
- "traefik.http.services.icon.loadbalancer.server.port=8080" - "traefik.http.services.icon.loadbalancer.server.port=8080"
- "traefik.http.routers.icon-ssl.rule=Host(`icon.${DOMAIN}`)" - "traefik.http.routers.icon-ssl.rule=Host(`${ICON_HOST}`)"
- "traefik.http.routers.icon-ssl.entryPoints=websecure" - "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.service=icon"
- "traefik.http.routers.icon-ssl.tls=true"
- "traefik.http.routers.icon-ssl.tls.certresolver=myresolver" - "traefik.http.routers.icon-ssl.tls.certresolver=myresolver"

View file

@ -3,7 +3,7 @@
### Opening a web page in a new tab ### Opening a web page in a new tab
``` ```ts
WA.nav.openTab(url: string): void WA.nav.openTab(url: string): void
``` ```
@ -11,13 +11,13 @@ Opens the webpage at "url" in your browser, in a new tab.
Example: Example:
```javascript ```ts
WA.nav.openTab('https://www.wikipedia.org/'); WA.nav.openTab('https://www.wikipedia.org/');
``` ```
### Opening a web page in the current tab ### Opening a web page in the current tab
``` ```ts
WA.nav.goToPage(url: string): void WA.nav.goToPage(url: string): void
``` ```
@ -25,14 +25,13 @@ Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdvent
Example: Example:
```javascript ```ts
WA.nav.goToPage('https://www.wikipedia.org/'); WA.nav.goToPage('https://www.wikipedia.org/');
``` ```
### Going to a different map from the script ### Going to a different map from the script
``` ```ts
WA.nav.goToRoom(url: string): void WA.nav.goToRoom(url: string): void
``` ```
@ -43,7 +42,7 @@ global urls: "/_/global/domain/path/map.json[#start-layer-name]"
Example: Example:
```javascript ```ts
WA.nav.goToRoom("/@/tcm/workadventure/floor0") // workadventure urls WA.nav.goToRoom("/@/tcm/workadventure/floor0") // workadventure urls
WA.nav.goToRoom('../otherMap/map.json'); WA.nav.goToRoom('../otherMap/map.json');
WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2") WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
@ -51,25 +50,25 @@ WA.nav.goToRoom("/_/global/<path to global map>.json#start-layer-2")
### Opening/closing web page in Co-Websites ### Opening/closing web page in Co-Websites
``` ```ts
WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number, closable: boolean, lazy: boolean): Promise<CoWebsite> WA.nav.openCoWebSite(url: string, allowApi?: boolean = false, allowPolicy?: string = "", percentWidth?: number, position?: number, closable?: boolean, lazy?: boolean): 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), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy 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),widthPercent define the width of the main cowebsite beetween the min size and the max size (70% of the viewport), position in whitch slot the web page will be open, closable allow to close the webpage also you need to close it by the api and lazy
it's to add the cowebsite but don't load it. it's to add the cowebsite but don't load it.
Example: Example:
```javascript ```ts
const coWebsite = await 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, true, true); const coWebsiteWorkAdventure = await WA.nav.openCoWebSite('https://workadventu.re/', true, "", 70, 1, true, true);
// ... // ...
coWebsite.close(); coWebsite.close();
``` ```
### Get all Co-Websites ### Get all Co-Websites
``` ```ts
WA.nav.getCoWebSites(): Promise<CoWebsite[]> WA.nav.getCoWebSites(): Promise<CoWebsite[]>
``` ```
@ -77,6 +76,6 @@ Get all opened co-websites with their ids and positions.
Example: Example:
```javascript ```ts
const coWebsites = await WA.nav.getCowebSites(); const coWebsites = await WA.nav.getCowebSites();
``` ```

View file

@ -82,7 +82,11 @@ We are able to direct a Woka to the desired place immediately after spawn. To ma
``` ```
.../my_map.json#moveTo=meeting-room&start .../my_map.json#moveTo=meeting-room&start
``` ```
*...or even like this!*
```
.../my_map.json#start&moveTo=200,100
```
For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. For this to work, moveTo must be equal to the x and y position, layer name, or object name of interest. Layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected.
![](images/moveTo-layer-example.png) ![](images/moveTo-layer-example.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -52,6 +52,13 @@ If you set `openWebsiteTrigger: onaction`, when the user walks on the layer, an
If you set `openWebsiteTriggerMessage: your message action` you can edit alert message displayed. If is not defined, the default message displayed is 'Press on SPACE to open the web site'. If you set `openWebsiteTriggerMessage: your message action` you can edit alert message displayed. If is not defined, the default message displayed is 'Press on SPACE to open the web site'.
If you set `openWebsiteTrigger: onicon`, when the user walks on the layer, an icon will be displayed at the bottom of the screen:
<figure class="figure">
<img src="images/icon_open_website.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The iFrame will only open if the user clicks on icon</figcaption>
</figure>
### Setting the iFrame "allow" attribute ### Setting the iFrame "allow" attribute
By default, iFrames have limited rights in browsers. For instance, they cannot put their content in fullscreen, they cannot start your webcam, etc... By default, iFrames have limited rights in browsers. For instance, they cannot put their content in fullscreen, they cannot start your webcam, etc...

View file

@ -1,5 +1,4 @@
{ {
"printWidth": 120, "printWidth": 120,
"tabWidth": 4, "tabWidth": 4
"plugins": ["prettier-plugin-svelte"]
} }

View file

@ -26,12 +26,15 @@
<meta name="msapplication-TileColor" content="#000000"> <meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png"> <meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
<meta name="theme-color" content="#000000"> <meta name="theme-color" content="#000000">
<meta property="og:title" content="Kraut.World" />
<meta property="og:description" content="WorkAdventure World powered by kraut.space and KABI.tk" />
<meta property="og:image" content="https://kraut.world/images/pic07.jpg" />
<script src="/env-config.js"></script> <script src="/env-config.js"></script>
<base href="/"> <base href="/">
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" /> <link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
<title>WorkAdventure</title> <title>Kraut.World</title>
</head> </head>
<body id="body" style="margin: 0; background-color: #000"> <body id="body" style="margin: 0; background-color: #000">
<div class="main-container" id="main-container"> <div class="main-container" id="main-container">

View file

@ -1,6 +1,6 @@
{ {
"short_name": "WA", "short_name": "KrautWorld",
"name": "WorkAdventure", "name": "Kraut.World powered by WorkAdventure",
"icons": [ "icons": [
{ {
"src": "/static/images/favicons/apple-icon-57x57.png", "src": "/static/images/favicons/apple-icon-57x57.png",
@ -126,20 +126,23 @@
"theme_color": "#000000", "theme_color": "#000000",
"shortcuts": [ "shortcuts": [
{ {
"name": "WorkAdventures", "name": "Kraut.World",
"short_name": "WA", "short_name": "KrautWorld",
"description": "WorkAdventure application", "description": "A WorkAdventure World provided by kraut.space and KABI.tk",
"url": "/", "url": "/",
"icons": [{ "src": "/static/images/favicons/android-icon-192x192.png", "sizes": "192x192", "type": "image/png" }] "icons": [{ "src": "/static/images/favicons/android-icon-192x192.png", "sizes": "192x192", "type": "image/png" }]
} }
], ],
"description": "WorkAdventure application", "description": "A WorkAdventure World provided by kraut.space and KABI.tk",
"screenshots": [], "screenshots": [],
"related_applications": [{ "related_applications": [{
"platform": "web", "platform": "web",
"url": "https://workadventu.re" "url": "https://kraut.world"
}, { }, {
"platform": "play", "platform": "play",
"url": "https://play.workadventu.re" "url": "https://play.kraut.world"
}, {
"platform": "WorkAdventure",
"url": "https://workadventu.re"
}] }]
} }

View file

@ -71,7 +71,7 @@
"zod": "^3.11.6" "zod": "^3.11.6"
}, },
"scripts": { "scripts": {
"start": "run-p templater serve svelte-check-watch typesafe-i18n", "start": "run-p templater serve svelte-check-watch typesafe-i18n-watch",
"templater": "cross-env ./templater.sh", "templater": "cross-env ./templater.sh",
"serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
"build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
@ -84,7 +84,8 @@
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"", "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"",
"pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'", "pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'",
"pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'", "pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'",
"typesafe-i18n": "typesafe-i18n --no-watch" "typesafe-i18n": "typesafe-i18n --no-watch",
"typesafe-i18n-watch": "typesafe-i18n"
}, },
"lint-staged": { "lint-staged": {
"*.svelte": [ "*.svelte": [

View file

@ -5,6 +5,7 @@ export const isOpenCoWebsiteEvent = new tg.IsInterface()
url: tg.isString, url: tg.isString,
allowApi: tg.isOptional(tg.isBoolean), allowApi: tg.isOptional(tg.isBoolean),
allowPolicy: tg.isOptional(tg.isString), allowPolicy: tg.isOptional(tg.isString),
widthPercent: tg.isOptional(tg.isNumber),
position: tg.isOptional(tg.isNumber), position: tg.isOptional(tg.isNumber),
closable: tg.isOptional(tg.isBoolean), closable: tg.isOptional(tg.isBoolean),
lazy: tg.isOptional(tg.isBoolean), lazy: tg.isOptional(tg.isBoolean),

View file

@ -45,6 +45,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
url: string, url: string,
allowApi?: boolean, allowApi?: boolean,
allowPolicy?: string, allowPolicy?: string,
widthPercent?: number,
position?: number, position?: number,
closable?: boolean, closable?: boolean,
lazy?: boolean lazy?: boolean
@ -55,6 +56,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
url, url,
allowApi, allowApi,
allowPolicy, allowPolicy,
widthPercent,
position, position,
closable, closable,
lazy, lazy,

View file

@ -2,9 +2,11 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { ICON_URL } from "../../Enum/EnvironmentVariable"; import { ICON_URL } from "../../Enum/EnvironmentVariable";
import { coWebsitesNotAsleep, mainCoWebsite } from "../../Stores/CoWebsiteStore"; import { mainCoWebsite } from "../../Stores/CoWebsiteStore";
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager"; import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
export let index: number; export let index: number;
@ -13,16 +15,15 @@
let icon: HTMLImageElement; let icon: HTMLImageElement;
let iconLoaded = false; let iconLoaded = false;
let state = coWebsite.state; let state = coWebsite.getStateSubscriber();
let isJitsi: boolean = coWebsite instanceof JitsiCoWebsite;
const coWebsiteUrl = coWebsite.iframe.src; const mainState = coWebsiteManager.getMainStateSubscriber();
const urlObject = new URL(coWebsiteUrl);
onMount(() => { onMount(() => {
icon.src = coWebsite.jitsi icon.src = isJitsi
? "/resources/logos/meet.svg" ? "/resources/logos/meet.svg"
: `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`; : `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
icon.alt = coWebsite.altMessage ?? urlObject.hostname; icon.alt = coWebsite.getUrl().hostname;
icon.onload = () => { icon.onload = () => {
iconLoaded = true; iconLoaded = true;
}; };
@ -32,17 +33,24 @@
if (vertical) { if (vertical) {
coWebsiteManager.goToMain(coWebsite); coWebsiteManager.goToMain(coWebsite);
} else if ($mainCoWebsite) { } else if ($mainCoWebsite) {
if ($mainCoWebsite.iframe.id === coWebsite.iframe.id) { if ($mainCoWebsite.getId() === coWebsite.getId()) {
const coWebsites = $coWebsitesNotAsleep; if (coWebsiteManager.getMainState() === iframeStates.closed) {
const newMain = $highlightedEmbedScreen ?? coWebsites.length > 1 ? coWebsites[1] : undefined; coWebsiteManager.displayMain();
if (newMain) { } else if ($highlightedEmbedScreen?.type === "cowebsite") {
coWebsiteManager.goToMain(newMain); coWebsiteManager.goToMain($highlightedEmbedScreen.embed);
} else {
coWebsiteManager.hideMain();
} }
} else { } else {
highlightedEmbedScreen.toggleHighlight({ if (coWebsiteManager.getMainState() === iframeStates.closed) {
type: "cowebsite", coWebsiteManager.goToMain(coWebsite);
embed: coWebsite, coWebsiteManager.displayMain();
}); } else {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: coWebsite,
});
}
} }
} }
@ -60,11 +68,14 @@
let isHighlight: boolean = false; let isHighlight: boolean = false;
let isMain: boolean = false; let isMain: boolean = false;
$: { $: {
isMain = $mainCoWebsite !== undefined && $mainCoWebsite.iframe === coWebsite.iframe; isMain =
$mainState === iframeStates.opened &&
$mainCoWebsite !== undefined &&
$mainCoWebsite.getId() === coWebsite.getId();
isHighlight = isHighlight =
$highlightedEmbedScreen !== null && $highlightedEmbedScreen !== null &&
$highlightedEmbedScreen.type === "cowebsite" && $highlightedEmbedScreen.type === "cowebsite" &&
$highlightedEmbedScreen.embed.iframe === coWebsite.iframe; $highlightedEmbedScreen.embed.getId() === coWebsite.getId();
} }
</script> </script>
@ -81,7 +92,7 @@
<img <img
class="cowebsite-icon noselect nes-pointer" class="cowebsite-icon noselect nes-pointer"
class:hide={!iconLoaded} class:hide={!iconLoaded}
class:jitsi={coWebsite.jitsi} class:jitsi={isJitsi}
bind:this={icon} bind:this={icon}
on:dragstart|preventDefault={noDrag} on:dragstart|preventDefault={noDrag}
alt="" alt=""
@ -208,7 +219,8 @@
} }
&:not(.vertical) { &:not(.vertical) {
animation: bounce 0.35s ease 6 alternate; transition: all 300ms;
transform: translateY(0px);
} }
&.vertical { &.vertical {
@ -229,7 +241,7 @@
&.displayed { &.displayed {
&:not(.vertical) { &:not(.vertical) {
animation: activeThumbnail 300ms ease-in 0s forwards; transform: translateY(-15px);
} }
} }
@ -258,16 +270,6 @@
} }
} }
@keyframes activeThumbnail {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-15px);
}
}
@keyframes bounce { @keyframes bounce {
from { from {
transform: translateY(0); transform: translateY(0);

View file

@ -7,7 +7,7 @@
{#if $coWebsites.length > 0} {#if $coWebsites.length > 0}
<div id="cowebsite-thumbnail-container" class:vertical> <div id="cowebsite-thumbnail-container" class:vertical>
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.iframe.id)} {#each [...$coWebsites.values()] as coWebsite, index (coWebsite.getId())}
<CoWebsiteThumbnail {index} {coWebsite} {vertical} /> <CoWebsiteThumbnail {index} {coWebsite} {vertical} />
{/each} {/each}
</div> </div>

View file

@ -9,13 +9,11 @@
function closeCoWebsite() { function closeCoWebsite() {
if ($highlightedEmbedScreen?.type === "cowebsite") { if ($highlightedEmbedScreen?.type === "cowebsite") {
if ($highlightedEmbedScreen.embed.closable) { if ($highlightedEmbedScreen.embed.isClosable()) {
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed).catch(() => { coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed);
console.error("Error during co-website highlighted closing");
});
} else { } else {
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch(() => { coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch((err) => {
console.error("Error during co-website highlighted unloading"); console.error("Cannot unload co-website", err);
}); });
} }
} }
@ -68,9 +66,9 @@
/> />
{/key} {/key}
{:else if $highlightedEmbedScreen.type === "cowebsite"} {:else if $highlightedEmbedScreen.type === "cowebsite"}
{#key $highlightedEmbedScreen.embed.iframe.id} {#key $highlightedEmbedScreen.embed.getId()}
<div <div
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.iframe.id} id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
class="highlighted-cowebsite nes-container is-rounded" class="highlighted-cowebsite nes-container is-rounded"
> >
<div class="actions"> <div class="actions">

View file

@ -53,7 +53,7 @@
<section class="terms-and-conditions"> <section class="terms-and-conditions">
<a style="display: none;" href="traduction">Need for traduction</a> <a style="display: none;" href="traduction">Need for traduction</a>
<p> <p>
{$LL.login.terms()} {@html $LL.login.terms()}
</p> </p>
</section> </section>
{/if} {/if}

View file

@ -1,5 +1,12 @@
<script lang="ts"> <script lang="ts">
import LL from "../../i18n/i18n-svelte"; import LL from "../../i18n/i18n-svelte";
import { gameManager } from "../../Phaser/Game/GameManager";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
let entryPoint: string = $startLayerNamesStore[0];
let walkAutomatically: boolean = false;
const currentPlayer = gameManager.getCurrentGameScene().CurrentPlayer;
const playerPos = { x: Math.floor(currentPlayer.x), y: Math.floor(currentPlayer.y) };
function copyLink() { function copyLink() {
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement; const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
@ -8,8 +15,23 @@
document.execCommand("copy"); document.execCommand("copy");
} }
function getLink() {
return `${location.origin}${location.pathname}#${entryPoint}${
walkAutomatically ? `&moveTo=${playerPos.x},${playerPos.y}` : ""
}`;
}
function updateInputFieldValue() {
const input = document.getElementById("input-share-link");
if (input) {
(input as HTMLInputElement).value = getLink();
}
}
let canShare = navigator.share !== undefined;
async function shareLink() { async function shareLink() {
const shareData = { url: location.toString() }; const shareData = { url: getLink() };
try { try {
await navigator.share(shareData); await navigator.share(shareData);
@ -22,16 +44,43 @@
<div class="guest-main"> <div class="guest-main">
<section class="container-overflow"> <section class="container-overflow">
<section class="share-url not-mobile"> {#if !canShare}
<h3>{$LL.menu.invite.description()}</h3> <section class="share-url not-mobile">
<input type="text" readonly id="input-share-link" value={location.toString()} /> <h3>{$LL.menu.invite.description()}</h3>
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button> <input type="text" readonly id="input-share-link" class="link-url" value={location.toString()} />
</section> <button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
<section class="is-mobile"> </section>
<h3>{$LL.menu.invite.description()}</h3> {:else}
<input type="hidden" readonly id="input-share-link" value={location.toString()} /> <section class="is-mobile">
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button> <h3>{$LL.menu.invite.description()}</h3>
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
</section>
{/if}
<h3>Select an entry point</h3>
<section class="nes-select is-dark starting-points">
<select
bind:value={entryPoint}
on:blur={() => {
updateInputFieldValue();
}}
>
{#each $startLayerNamesStore as entryPointName}
<option value={entryPointName}>{entryPointName}</option>
{/each}
</select>
</section> </section>
<label>
<input
type="checkbox"
class="nes-checkbox is-dark"
bind:checked={walkAutomatically}
on:change={() => {
updateInputFieldValue();
}}
/>
<span>{$LL.menu.invite.walk_automatically_to_position()}</span>
</label>
</section> </section>
</div> </div>
@ -39,14 +88,27 @@
@import "../../../style/breakpoints.scss"; @import "../../../style/breakpoints.scss";
div.guest-main { div.guest-main {
width: 50%;
margin-left: auto;
margin-right: auto;
height: calc(100% - 56px); height: calc(100% - 56px);
text-align: center; input.link-url {
width: calc(100% - 200px);
}
.starting-points {
width: 80%;
}
section { section {
margin-bottom: 50px; margin-bottom: 50px;
} }
section.nes-select select:focus {
outline: none;
}
section.container-overflow { section.container-overflow {
height: 100%; height: 100%;
margin: 0; margin: 0;
@ -55,25 +117,23 @@
} }
section.is-mobile { section.is-mobile {
display: none; display: block;
text-align: center;
margin-bottom: 20px;
} }
} }
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
div.guest-main { div.guest-main {
section.share-url.not-mobile {
display: none;
}
section.is-mobile {
display: block;
text-align: center;
margin-bottom: 20px;
}
section.container-overflow { section.container-overflow {
height: calc(100% - 120px); height: calc(100% - 120px);
} }
} }
} }
@include media-breakpoint-up(lg) {
div.guest-main {
width: 100%;
}
}
</style> </style>

View file

@ -69,6 +69,7 @@
} else { } else {
const customMenu = customMenuIframe.get(menu.label); const customMenu = customMenuIframe.get(menu.label);
if (customMenu !== undefined) { if (customMenu !== undefined) {
activeSubMenu = menu;
props = { url: customMenu.url, allowApi: customMenu.allowApi }; props = { url: customMenu.url, allowApi: customMenu.allowApi };
activeComponent = CustomSubMenu; activeComponent = CustomSubMenu;
} else { } else {

View file

@ -9,4 +9,7 @@ export interface UserInputHandlerInterface {
handlePointerUpEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void; handlePointerUpEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void;
handlePointerDownEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void; handlePointerDownEvent: (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => void;
handleSpaceKeyUpEvent: (event: Event) => Event; handleSpaceKeyUpEvent: (event: Event) => Event;
addSpaceEventListener: (callback: Function) => void;
removeSpaceEventListner: (callback: Function) => void;
} }

View file

@ -159,6 +159,27 @@ export abstract class Character extends Container implements OutlineableInterfac
return { x: this.x, y: this.y }; return { x: this.x, y: this.y };
} }
/**
* Returns position based on where player is currently facing
* @param shift How far from player should the point of interest be.
*/
public getDirectionalActivationPosition(shift: number): { x: number; y: number } {
switch (this.lastDirection) {
case PlayerAnimationDirections.Down: {
return { x: this.x, y: this.y + shift };
}
case PlayerAnimationDirections.Left: {
return { x: this.x - shift, y: this.y };
}
case PlayerAnimationDirections.Right: {
return { x: this.x + shift, y: this.y };
}
case PlayerAnimationDirections.Up: {
return { x: this.x, y: this.y - shift };
}
}
}
public getObjectToOutline(): Phaser.GameObjects.GameObject { public getObjectToOutline(): Phaser.GameObjects.GameObject {
return this.playerNameText; return this.playerNameText;
} }
@ -455,16 +476,16 @@ export abstract class Character extends Container implements OutlineableInterfac
this.outlineColorStore.removeApiColor(); this.outlineColorStore.removeApiColor();
} }
public pointerOverOutline(): void { public pointerOverOutline(color: number): void {
this.outlineColorStore.pointerOver(); this.outlineColorStore.pointerOver(color);
} }
public pointerOutOutline(): void { public pointerOutOutline(): void {
this.outlineColorStore.pointerOut(); this.outlineColorStore.pointerOut();
} }
public characterCloseByOutline(): void { public characterCloseByOutline(color: number): void {
this.outlineColorStore.characterCloseBy(); this.outlineColorStore.characterCloseBy(color);
} }
public characterFarAwayOutline(): void { public characterFarAwayOutline(): void {

View file

@ -11,6 +11,11 @@ export class ActivatablesManager {
private currentPlayer: Player; private currentPlayer: Player;
private canSelectByDistance: boolean = true;
private readonly outlineColor = 0xffff00;
private readonly directionalActivationPositionShift = 50;
constructor(currentPlayer: Player) { constructor(currentPlayer: Player) {
this.currentPlayer = currentPlayer; this.currentPlayer = currentPlayer;
} }
@ -27,7 +32,7 @@ export class ActivatablesManager {
} }
this.selectedActivatableObjectByPointer = object; this.selectedActivatableObjectByPointer = object;
if (isOutlineable(this.selectedActivatableObjectByPointer)) { if (isOutlineable(this.selectedActivatableObjectByPointer)) {
this.selectedActivatableObjectByPointer?.pointerOverOutline(); this.selectedActivatableObjectByPointer?.pointerOverOutline(this.outlineColor);
} }
} }
@ -37,7 +42,7 @@ export class ActivatablesManager {
} }
this.selectedActivatableObjectByPointer = undefined; this.selectedActivatableObjectByPointer = undefined;
if (isOutlineable(this.selectedActivatableObjectByDistance)) { if (isOutlineable(this.selectedActivatableObjectByDistance)) {
this.selectedActivatableObjectByDistance?.characterCloseByOutline(); this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor);
} }
} }
@ -46,6 +51,9 @@ export class ActivatablesManager {
} }
public deduceSelectedActivatableObjectByDistance(): void { public deduceSelectedActivatableObjectByDistance(): void {
if (!this.canSelectByDistance) {
return;
}
const newNearestObject = this.findNearestActivatableObject(); const newNearestObject = this.findNearestActivatableObject();
if (this.selectedActivatableObjectByDistance === newNearestObject) { if (this.selectedActivatableObjectByDistance === newNearestObject) {
return; return;
@ -60,10 +68,42 @@ export class ActivatablesManager {
} }
this.selectedActivatableObjectByDistance = newNearestObject; this.selectedActivatableObjectByDistance = newNearestObject;
if (isOutlineable(this.selectedActivatableObjectByDistance)) { if (isOutlineable(this.selectedActivatableObjectByDistance)) {
this.selectedActivatableObjectByDistance?.characterCloseByOutline(); this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor);
} }
} }
public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void {
const currentPlayerPos = this.currentPlayer.getDirectionalActivationPosition(
this.directionalActivationPositionShift
);
for (const object of objects) {
const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition());
this.activatableObjectsDistances.set(object, distance);
}
}
public updateDistanceForSingleActivatableObject(object: ActivatableInterface): void {
this.activatableObjectsDistances.set(
object,
MathUtils.distanceBetween(
this.currentPlayer.getDirectionalActivationPosition(this.directionalActivationPositionShift),
object.getPosition()
)
);
}
public disableSelectingByDistance(): void {
this.canSelectByDistance = false;
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
}
this.selectedActivatableObjectByDistance = undefined;
}
public enableSelectingByDistance(): void {
this.canSelectByDistance = true;
}
private findNearestActivatableObject(): ActivatableInterface | undefined { private findNearestActivatableObject(): ActivatableInterface | undefined {
let shortestDistance: number = Infinity; let shortestDistance: number = Infinity;
let closestObject: ActivatableInterface | undefined = undefined; let closestObject: ActivatableInterface | undefined = undefined;
@ -76,18 +116,8 @@ export class ActivatablesManager {
} }
return closestObject; return closestObject;
} }
public updateActivatableObjectsDistances(objects: ActivatableInterface[]): void {
const currentPlayerPos = this.currentPlayer.getPosition();
for (const object of objects) {
const distance = MathUtils.distanceBetween(currentPlayerPos, object.getPosition());
this.activatableObjectsDistances.set(object, distance);
}
}
public updateDistanceForSingleActivatableObject(object: ActivatableInterface): void { public isSelectingByDistanceEnabled(): boolean {
this.activatableObjectsDistances.set( return this.canSelectByDistance;
object,
MathUtils.distanceBetween(this.currentPlayer.getPosition(), object.getPosition())
);
} }
} }

View file

@ -26,15 +26,6 @@ export class Game extends Phaser.Game {
} }
} }
}); });
/*window.addEventListener('resize', (event) => {
// Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of this.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize(event);
}
}
});*/
} }
public step(time: number, delta: number) { public step(time: number, delta: number) {

View file

@ -85,6 +85,7 @@ export class GameMap {
phaserMap phaserMap
.createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32) .createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32)
.setDepth(depth) .setDepth(depth)
.setScrollFactor(layer.parallaxx ?? 1, layer.parallaxy ?? 1)
.setAlpha(layer.opacity) .setAlpha(layer.opacity)
.setVisible(layer.visible) .setVisible(layer.visible)
.setSize(layer.width, layer.height) .setSize(layer.width, layer.height)
@ -120,7 +121,7 @@ export class GameMap {
return []; return [];
} }
public getCollisionsGrid(): number[][] { public getCollisionGrid(): number[][] {
const grid: number[][] = []; const grid: number[][] = [];
for (let y = 0; y < this.map.height; y += 1) { for (let y = 0; y < this.map.height; y += 1) {
const row: number[] = []; const row: number[] = [];
@ -322,12 +323,19 @@ export class GameMap {
throw new Error("No possible position found"); throw new Error("No possible position found");
} }
public getObjectWithName(name: string): ITiledMapObject | undefined {
return this.tiledObjects.find((object) => object.name === name);
}
private getLayersByKey(key: number): Array<ITiledMapLayer> { private getLayersByKey(key: number): Array<ITiledMapLayer> {
return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0); return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0);
} }
private isCollidingAt(x: number, y: number): boolean { private isCollidingAt(x: number, y: number): boolean {
for (const layer of this.phaserLayers) { for (const layer of this.phaserLayers) {
if (!layer.visible) {
continue;
}
if (layer.getTileAt(x, y)?.properties[GameMapProperties.COLLIDES]) { if (layer.getTileAt(x, y)?.properties[GameMapProperties.COLLIDES]) {
return true; return true;
} }

View file

@ -20,6 +20,7 @@ export enum GameMapProperties {
OPEN_WEBSITE = "openWebsite", OPEN_WEBSITE = "openWebsite",
OPEN_WEBSITE_ALLOW_API = "openWebsiteAllowApi", OPEN_WEBSITE_ALLOW_API = "openWebsiteAllowApi",
OPEN_WEBSITE_POLICY = "openWebsitePolicy", OPEN_WEBSITE_POLICY = "openWebsitePolicy",
OPEN_WEBSITE_WIDTH = "openWebsiteWidth",
OPEN_WEBSITE_POSITION = "openWebsitePosition", OPEN_WEBSITE_POSITION = "openWebsitePosition",
OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger", OPEN_WEBSITE_TRIGGER = "openWebsiteTrigger",
OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage", OPEN_WEBSITE_TRIGGER_MESSAGE = "openWebsiteTriggerMessage",

View file

@ -1,33 +1,36 @@
import type { GameScene } from "./GameScene"; import type { GameScene } from "./GameScene";
import type { GameMap } from "./GameMap"; import type { GameMap } from "./GameMap";
import { scriptUtils } from "../../Api/ScriptUtils"; import { scriptUtils } from "../../Api/ScriptUtils";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { localUserStore } from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager"; import { ON_ACTION_TRIGGER_BUTTON, ON_ICON_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
import type { ITiledMapLayer } from "../Map/ITiledMap"; import type { ITiledMapLayer } from "../Map/ITiledMap";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore"; import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
enum OpenCoWebsiteState { import { jitsiFactory } from "../../WebRtc/JitsiFactory";
ASLEEP, import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable";
OPENED, import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
MUST_BE_CLOSE, import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
} import { iframeListener } from "../../Api/IframeListener";
import { Room } from "../../Connexion/Room";
import LL from "../../i18n/i18n-svelte";
interface OpenCoWebsite { interface OpenCoWebsite {
coWebsite: CoWebsite; actionId: string;
state: OpenCoWebsiteState; coWebsite?: CoWebsite;
} }
export class GameMapPropertiesListener { export class GameMapPropertiesListener {
private coWebsitesOpenByLayer = new Map<ITiledMapLayer, OpenCoWebsite>(); private coWebsitesOpenByLayer = new Map<ITiledMapLayer, OpenCoWebsite>();
private coWebsitesActionTriggerByLayer = new Map<ITiledMapLayer, string>();
constructor(private scene: GameScene, private gameMap: GameMap) {} constructor(private scene: GameScene, private gameMap: GameMap) {}
register() { register() {
// Website on new tab
this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => {
if (newValue === undefined) { if (newValue === undefined) {
layoutManagerActionStore.removeAction("openTab"); layoutManagerActionStore.removeAction("openTab");
@ -38,7 +41,7 @@ export class GameMapPropertiesListener {
if (forceTrigger || openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { if (forceTrigger || openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE); let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE);
if (message === undefined) { if (message === undefined) {
message = "Press SPACE or touch here to open web site in new tab"; message = get(LL).trigger.newTab();
} }
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({
uuid: "openTab", uuid: "openTab",
@ -53,6 +56,129 @@ export class GameMapPropertiesListener {
} }
}); });
// Jitsi room
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("jitsi");
coWebsiteManager.getCoWebsites().forEach((coWebsite) => {
if (coWebsite instanceof JitsiCoWebsite) {
coWebsiteManager.closeCoWebsite(coWebsite);
}
});
} else {
const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.instance);
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
this.scene.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else {
let domain = jitsiUrl || JITSI_URL;
if (domain === undefined) {
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
}
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
domain = `${location.protocol}//${domain}`;
}
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
this.scene.initialiseJitsi(coWebsite, roomName, undefined);
}
layoutManagerActionStore.removeAction("jitsi");
};
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
if (message === undefined) {
message = get(LL).trigger.jitsiRoom();
}
layoutManagerActionStore.addAction({
uuid: "jitsi",
type: "message",
message: message,
callback: () => openJitsiRoomFunction(),
userInputManager: this.scene.userInputManager,
});
} else {
openJitsiRoomFunction();
}
}
});
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
if (newValue) {
this.scene
.onMapExit(
Room.getRoomPathFromExitSceneUrl(
newValue as string,
window.location.toString(),
this.scene.MapUrlFile
)
)
.catch((e) => console.error(e));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
if (newValue) {
this.scene
.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString()))
.catch((e) => console.error(e));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === "") {
this.scene.connection?.setSilent(false);
this.scene.CurrentPlayer.noSilent();
} else {
this.scene.connection?.setSilent(true);
this.scene.CurrentPlayer.isSilent();
}
});
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), volume, loop);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: This legacy property should be removed at some point
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string);
}
if (newValue) {
iframeListener.sendEnterEvent(newValue as string);
}
});
// Open a new co-website by the property. // Open a new co-website by the property.
this.gameMap.onEnterLayer((newLayers) => { this.gameMap.onEnterLayer((newLayers) => {
const handler = () => { const handler = () => {
@ -64,6 +190,7 @@ export class GameMapPropertiesListener {
let openWebsiteProperty: string | undefined; let openWebsiteProperty: string | undefined;
let allowApiProperty: boolean | undefined; let allowApiProperty: boolean | undefined;
let websitePolicyProperty: string | undefined; let websitePolicyProperty: string | undefined;
let websiteWidthProperty: number | undefined;
let websitePositionProperty: number | undefined; let websitePositionProperty: number | undefined;
let websiteTriggerProperty: string | undefined; let websiteTriggerProperty: string | undefined;
let websiteTriggerMessageProperty: string | undefined; let websiteTriggerMessageProperty: string | undefined;
@ -79,6 +206,9 @@ export class GameMapPropertiesListener {
case GameMapProperties.OPEN_WEBSITE_POLICY: case GameMapProperties.OPEN_WEBSITE_POLICY:
websitePolicyProperty = property.value as string | undefined; websitePolicyProperty = property.value as string | undefined;
break; break;
case GameMapProperties.OPEN_WEBSITE_WIDTH:
websiteWidthProperty = property.value as number | undefined;
break;
case GameMapProperties.OPEN_WEBSITE_POSITION: case GameMapProperties.OPEN_WEBSITE_POSITION:
websitePositionProperty = property.value as number | undefined; websitePositionProperty = property.value as number | undefined;
break; break;
@ -95,55 +225,75 @@ export class GameMapPropertiesListener {
return; return;
} }
const actionUuid = "openWebsite-" + (Math.random() + 1).toString(36).substring(7); const actionId = "openWebsite-" + (Math.random() + 1).toString(36).substring(7);
if (this.coWebsitesOpenByLayer.has(layer)) { if (this.coWebsitesOpenByLayer.has(layer)) {
return; return;
} }
const coWebsite = coWebsiteManager.addCoWebsite( const coWebsiteOpen: OpenCoWebsite = {
openWebsiteProperty, actionId: actionId,
this.scene.MapUrlFile, };
allowApiProperty,
websitePolicyProperty,
websitePositionProperty,
false
);
this.coWebsitesOpenByLayer.set(layer, { this.coWebsitesOpenByLayer.set(layer, coWebsiteOpen);
coWebsite: coWebsite,
state: OpenCoWebsiteState.ASLEEP,
});
const openWebsiteFunction = () => { const loadCoWebsiteFunction = (coWebsite: CoWebsite) => {
coWebsiteManager coWebsiteManager.loadCoWebsite(coWebsite).catch(() => {
.loadCoWebsite(coWebsite) console.error("Error during loading a co-website: " + coWebsite.getUrl());
.then((coWebsite) => { });
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
coWebsiteManager.closeCoWebsite(coWebsite).catch(() => {
console.error("Error during a co-website closing");
});
this.coWebsitesOpenByLayer.delete(layer);
} else {
this.coWebsitesOpenByLayer.set(layer, {
coWebsite,
state: OpenCoWebsiteState.OPENED,
});
}
})
.catch(() => {
console.error("Error during loading a co-website: " + coWebsite.url);
});
layoutManagerActionStore.removeAction(actionUuid); layoutManagerActionStore.removeAction(actionId);
};
const openCoWebsiteFunction = () => {
const coWebsite = new SimpleCoWebsite(
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
false
);
coWebsiteOpen.coWebsite = coWebsite;
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
loadCoWebsiteFunction(coWebsite);
}; };
if ( if (
!localUserStore.getForceCowebsiteTrigger() && localUserStore.getForceCowebsiteTrigger() ||
websiteTriggerProperty !== ON_ACTION_TRIGGER_BUTTON websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON
) { ) {
openWebsiteFunction(); if (!websiteTriggerMessageProperty) {
websiteTriggerMessageProperty = get(LL).trigger.cowebsite();
}
this.coWebsitesActionTriggerByLayer.set(layer, actionId);
layoutManagerActionStore.addAction({
uuid: actionId,
type: "message",
message: websiteTriggerMessageProperty,
callback: () => openCoWebsiteFunction(),
userInputManager: this.scene.userInputManager,
});
} else if (websiteTriggerProperty === ON_ICON_TRIGGER_BUTTON) {
const coWebsite = new SimpleCoWebsite(
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
allowApiProperty,
websitePolicyProperty,
websiteWidthProperty,
false
);
coWebsiteOpen.coWebsite = coWebsite;
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
}
if (!websiteTriggerProperty) {
openCoWebsiteFunction();
} }
}); });
}; };
@ -183,15 +333,35 @@ export class GameMapPropertiesListener {
return; return;
} }
if (coWebsiteOpen.state === OpenCoWebsiteState.ASLEEP) { const coWebsite = coWebsiteOpen.coWebsite;
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
}
if (coWebsiteOpen.coWebsite !== undefined) { if (coWebsite) {
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e)); coWebsiteManager.closeCoWebsite(coWebsite);
} }
this.coWebsitesOpenByLayer.delete(layer); this.coWebsitesOpenByLayer.delete(layer);
if (!websiteTriggerProperty) {
return;
}
const actionStore = get(layoutManagerActionStore);
const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer);
if (!actionTriggerUuid) {
return;
}
const action =
actionStore && actionStore.length > 0
? actionStore.find((action) => action.uuid === actionTriggerUuid)
: undefined;
if (action) {
layoutManagerActionStore.removeAction(actionTriggerUuid);
}
this.coWebsitesActionTriggerByLayer.delete(layer);
}); });
}; };

View file

@ -5,7 +5,7 @@ import { get, Unsubscriber } from "svelte/store";
import { userMessageManager } from "../../Administration/UserMessageManager"; import { userMessageManager } from "../../Administration/UserMessageManager";
import { connectionManager } from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { urlManager } from "../../Url/UrlManager"; import { urlManager } from "../../Url/UrlManager";
import { mediaManager } from "../../WebRtc/MediaManager"; import { mediaManager } from "../../WebRtc/MediaManager";
import { UserInputManager } from "../UserInput/UserInputManager"; import { UserInputManager } from "../UserInput/UserInputManager";
@ -20,9 +20,8 @@ import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
import { iframeListener } from "../../Api/IframeListener"; import { iframeListener } from "../../Api/IframeListener";
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
import { Room } from "../../Connexion/Room"; import { Room } from "../../Connexion/Room";
import { jitsiFactory } from "../../WebRtc/JitsiFactory"; import { jitsiFactory } from "../../WebRtc/JitsiFactory";
@ -76,7 +75,7 @@ import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
import { userIsAdminStore } from "../../Stores/GameStore"; import { userIsAdminStore } from "../../Stores/GameStore";
import { contactPageStore } from "../../Stores/MenuStore"; import { contactPageStore } from "../../Stores/MenuStore";
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore"; import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
import EVENT_TYPE = Phaser.Scenes.Events; import EVENT_TYPE = Phaser.Scenes.Events;
import Texture = Phaser.Textures.Texture; import Texture = Phaser.Textures.Texture;
@ -92,6 +91,11 @@ import { MapStore } from "../../Stores/Utils/MapStore";
import { followUsersColorStore } from "../../Stores/FollowStore"; import { followUsersColorStore } from "../../Stores/FollowStore";
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler"; import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
import { locale } from "../../i18n/i18n-svelte"; import { locale } from "../../i18n/i18n-svelte";
import { StringUtils } from "../../Utils/StringUtils";
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
reconnecting: boolean; reconnecting: boolean;
@ -542,6 +546,8 @@ export class GameScene extends DirtyScene {
urlManager.getStartLayerNameFromUrl() urlManager.getStartLayerNameFromUrl()
); );
startLayerNamesStore.set(this.startPositionCalculator.getStartPositionNames());
//add entities //add entities
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>(); this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
@ -561,7 +567,7 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager( this.pathfindingManager = new PathfindingManager(
this, this,
this.gameMap.getCollisionsGrid(), this.gameMap.getCollisionGrid(),
this.gameMap.getTileDimensions() this.gameMap.getTileDimensions()
); );
@ -569,6 +575,8 @@ export class GameScene extends DirtyScene {
this.createCurrentPlayer(); this.createCurrentPlayer();
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
this.tryMovePlayerWithMoveToParameter();
this.cameraManager = new CameraManager( this.cameraManager = new CameraManager(
this, this,
{ x: this.Map.widthInPixels, y: this.Map.heightInPixels }, { x: this.Map.widthInPixels, y: this.Map.heightInPixels },
@ -577,7 +585,7 @@ export class GameScene extends DirtyScene {
this.pathfindingManager = new PathfindingManager( this.pathfindingManager = new PathfindingManager(
this, this,
this.gameMap.getCollisionsGrid(), this.gameMap.getCollisionGrid(),
this.gameMap.getTileDimensions() this.gameMap.getTileDimensions()
); );
@ -628,7 +636,6 @@ export class GameScene extends DirtyScene {
); );
new GameMapPropertiesListener(this, this.gameMap).register(); new GameMapPropertiesListener(this, this.gameMap).register();
this.triggerOnMapLayerPropertyChange();
if (!this.room.isDisconnected()) { if (!this.room.isDisconnected()) {
this.scene.sleep(); this.scene.sleep();
@ -798,7 +805,19 @@ export class GameScene extends DirtyScene {
* Triggered when we receive the JWT token to connect to Jitsi * Triggered when we receive the JWT token to connect to Jitsi
*/ */
this.connection.sendJitsiJwtMessageStream.subscribe((message) => { this.connection.sendJitsiJwtMessageStream.subscribe((message) => {
this.startJitsi(message.jitsiRoom, message.jwt); if (!JITSI_URL) {
throw new Error("Missing JITSI_URL environment variable.");
}
let domain = JITSI_URL;
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
domain = `${location.protocol}//${domain}`;
}
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
this.initialiseJitsi(coWebsite, message.jitsiRoom, message.jwt);
}); });
this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => { this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => {
@ -935,103 +954,6 @@ export class GameScene extends DirtyScene {
} }
} }
private triggerOnMapLayerPropertyChange() {
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
if (newValue) {
this.onMapExit(
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
).catch((e) => console.error(e));
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
if (newValue) {
this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) =>
console.error(e)
);
} else {
setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied");
}, 2000);
}
});
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManagerActionStore.removeAction("jitsi");
this.stopJitsi();
} else {
const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else {
this.startJitsi(roomName, undefined);
}
layoutManagerActionStore.removeAction("jitsi");
};
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
if (message === undefined) {
message = "Press SPACE or touch here to enter Jitsi Meet room";
}
layoutManagerActionStore.addAction({
uuid: "jitsi",
type: "message",
message: message,
callback: () => openJitsiRoomFunction(),
userInputManager: this.userInputManager,
});
} else {
openJitsiRoomFunction();
}
}
});
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === "") {
this.connection?.setSilent(false);
this.CurrentPlayer.noSilent();
} else {
this.connection?.setSilent(true);
this.CurrentPlayer.isSilent();
}
});
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), volume, loop);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: This legacy property should be removed at some point
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
newValue === undefined
? audioManagerFileStore.unloadAudio()
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), undefined, true);
audioManagerVisibilityStore.set(!(newValue === undefined));
});
// TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string);
}
if (newValue) {
iframeListener.sendEnterEvent(newValue as string);
}
});
}
private listenToIframeEvents(): void { private listenToIframeEvents(): void {
this.iframeSubscriptionList = []; this.iframeSubscriptionList = [];
this.iframeSubscriptionList.push( this.iframeSubscriptionList.push(
@ -1264,12 +1186,11 @@ ${escapedMessage}
throw new Error("Unknown query source"); throw new Error("Unknown query source");
} }
const coWebsite = coWebsiteManager.addCoWebsite( const coWebsite: SimpleCoWebsite = new SimpleCoWebsite(
openCoWebsite.url, new URL(openCoWebsite.url, iframeListener.getBaseUrlFromSource(source)),
iframeListener.getBaseUrlFromSource(source),
openCoWebsite.allowApi, openCoWebsite.allowApi,
openCoWebsite.allowPolicy, openCoWebsite.allowPolicy,
openCoWebsite.position, openCoWebsite.widthPercent,
openCoWebsite.closable ?? true openCoWebsite.closable ?? true
); );
@ -1278,7 +1199,7 @@ ${escapedMessage}
} }
return { return {
id: coWebsite.iframe.id, id: coWebsite.getId(),
}; };
}); });
@ -1287,27 +1208,23 @@ ${escapedMessage}
return coWebsites.map((coWebsite: CoWebsite) => { return coWebsites.map((coWebsite: CoWebsite) => {
return { return {
id: coWebsite.iframe.id, id: coWebsite.getId(),
}; };
}); });
}); });
iframeListener.registerAnswerer("closeCoWebsite", async (coWebsiteId) => { iframeListener.registerAnswerer("closeCoWebsite", (coWebsiteId) => {
const coWebsite = coWebsiteManager.getCoWebsiteById(coWebsiteId); const coWebsite = coWebsiteManager.getCoWebsiteById(coWebsiteId);
if (!coWebsite) { if (!coWebsite) {
throw new Error("Unknown co-website"); throw new Error("Unknown co-website");
} }
return coWebsiteManager.closeCoWebsite(coWebsite).catch((error) => { return coWebsiteManager.closeCoWebsite(coWebsite);
throw new Error("Error on closing co-website");
});
}); });
iframeListener.registerAnswerer("closeCoWebsites", async () => { iframeListener.registerAnswerer("closeCoWebsites", () => {
return await coWebsiteManager.closeCoWebsites().catch((error) => { return coWebsiteManager.closeCoWebsites();
throw new Error("Error on closing all co-websites");
});
}); });
iframeListener.registerAnswerer("getMapData", () => { iframeListener.registerAnswerer("getMapData", () => {
@ -1392,7 +1309,7 @@ ${escapedMessage}
//Create new colliders with the new GameMap //Create new colliders with the new GameMap
this.createCollisionWithPlayer(); this.createCollisionWithPlayer();
//Create new trigger with the new GameMap //Create new trigger with the new GameMap
this.triggerOnMapLayerPropertyChange(); new GameMapPropertiesListener(this, this.gameMap).register();
resolve(newFirstgid); resolve(newFirstgid);
}); });
}); });
@ -1463,9 +1380,9 @@ ${escapedMessage}
}); });
iframeListener.registerAnswerer("movePlayerTo", async (message) => { iframeListener.registerAnswerer("movePlayerTo", async (message) => {
const index = this.getGameMap().getTileIndexAt(message.x, message.y); const startTileIndex = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y);
const startTile = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y); const destinationTileIndex = this.getGameMap().getTileIndexAt(message.x, message.y);
const path = await this.getPathfindingManager().findPath(startTile, index, true, true); const path = await this.getPathfindingManager().findPath(startTileIndex, destinationTileIndex, true, true);
path.shift(); path.shift();
if (path.length === 0) { if (path.length === 0) {
throw new Error("no path available"); throw new Error("no path available");
@ -1505,14 +1422,15 @@ ${escapedMessage}
phaserLayers[i].setCollisionByProperty({ collides: true }, visible); phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
} }
} }
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
this.markDirty(); this.markDirty();
} }
private getMapDirUrl(): string { public getMapDirUrl(): string {
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/")); return this.MapUrlFile.substring(0, this.MapUrlFile.lastIndexOf("/"));
} }
private async onMapExit(roomUrl: URL) { public async onMapExit(roomUrl: URL) {
if (this.mapTransitioning) return; if (this.mapTransitioning) return;
this.mapTransitioning = true; this.mapTransitioning = true;
@ -1571,14 +1489,13 @@ ${escapedMessage}
public cleanupClosingScene(): void { public cleanupClosingScene(): void {
// stop playing audio, close any open website, stop any open Jitsi // stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager.closeCoWebsites().catch((e) => console.error(e)); coWebsiteManager.closeCoWebsites();
// Stop the script, if any // Stop the script, if any
const scripts = this.getScriptUrls(this.mapFile); const scripts = this.getScriptUrls(this.mapFile);
for (const script of scripts) { for (const script of scripts) {
iframeListener.unregisterScript(script); iframeListener.unregisterScript(script);
} }
this.stopJitsi();
audioManagerFileStore.unloadAudio(); audioManagerFileStore.unloadAudio();
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map. // We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection?.closeConnection(); this.connection?.closeConnection();
@ -1629,6 +1546,36 @@ ${escapedMessage}
this.MapPlayersByKey.clear(); this.MapPlayersByKey.clear();
} }
private tryMovePlayerWithMoveToParameter(): void {
const moveToParam = urlManager.getHashParameter("moveTo");
if (moveToParam) {
try {
let endPos;
const posFromParam = StringUtils.parsePointFromParam(moveToParam);
if (posFromParam) {
endPos = this.gameMap.getTileIndexAt(posFromParam.x, posFromParam.y);
} else {
const destinationObject = this.gameMap.getObjectWithName(moveToParam);
if (destinationObject) {
endPos = this.gameMap.getTileIndexAt(destinationObject.x, destinationObject.y);
} else {
endPos = this.gameMap.getRandomPositionFromLayer(moveToParam);
}
}
this.pathfindingManager
.findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos)
.then((path) => {
if (path && path.length > 0) {
this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason));
}
})
.catch((reason) => console.warn(reason));
} catch (err) {
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
}
}
}
private getExitUrl(layer: ITiledMapLayer): string | undefined { private getExitUrl(layer: ITiledMapLayer): string | undefined {
return this.getProperty(layer, GameMapProperties.EXIT_URL) as string | undefined; return this.getProperty(layer, GameMapProperties.EXIT_URL) as string | undefined;
} }
@ -1745,26 +1692,16 @@ ${escapedMessage}
emoteMenuStore.openEmoteMenu(); emoteMenuStore.openEmoteMenu();
} }
}); });
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
this.CurrentPlayer.pointerOverOutline(0x00ffff);
});
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
this.CurrentPlayer.pointerOutOutline();
});
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => { this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
this.connection?.emitEmoteEvent(emoteKey); this.connection?.emitEmoteEvent(emoteKey);
analyticsClient.launchEmote(emoteKey); analyticsClient.launchEmote(emoteKey);
}); });
const moveToParam = urlManager.getHashParameter("moveTo");
if (moveToParam) {
try {
const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam);
this.pathfindingManager
.findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos)
.then((path) => {
if (path && path.length > 0) {
this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason));
}
})
.catch((reason) => console.warn(reason));
} catch (err) {
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
}
}
} catch (err) { } catch (err) {
if (err instanceof TextureError) { if (err instanceof TextureError) {
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
@ -2117,7 +2054,7 @@ ${escapedMessage}
mediaManager.hideMyCamera(); mediaManager.hideMyCamera();
} }
public startJitsi(roomName: string, jwt?: string): void { public initialiseJitsi(coWebsite: JitsiCoWebsite, roomName: string, jwt?: string): void {
const allProps = this.gameMap.getCurrentProperties(); const allProps = this.gameMap.getCurrentProperties();
const jitsiConfig = this.safeParseJSONstring( const jitsiConfig = this.safeParseJSONstring(
allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined, allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined,
@ -2129,20 +2066,15 @@ ${escapedMessage}
); );
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl).catch(() => { coWebsite.setJitsiLoadPromise(() => {
console.error("Cannot start a Jitsi co-website"); return jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
}); });
this.disableMediaBehaviors();
analyticsClient.enteredJitsi(roomName, this.room.id);
}
public stopJitsi(): void { coWebsiteManager.loadCoWebsite(coWebsite).catch((err) => {
const coWebsite = coWebsiteManager.searchJitsi(); console.error(err);
if (coWebsite) { });
coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => {
console.error("Error during Jitsi co-website closing", e); analyticsClient.enteredJitsi(roomName, this.room.id);
});
}
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)

View file

@ -3,8 +3,8 @@ export interface OutlineableInterface {
removeFollowOutlineColor(): void; removeFollowOutlineColor(): void;
setApiOutlineColor(color: number): void; setApiOutlineColor(color: number): void;
removeApiOutlineColor(): void; removeApiOutlineColor(): void;
pointerOverOutline(): void; pointerOverOutline(color: number): void;
pointerOutOutline(): void; pointerOutOutline(): void;
characterCloseByOutline(): void; characterCloseByOutline(color: number): void;
characterFarAwayOutline(): void; characterFarAwayOutline(): void;
} }

View file

@ -16,32 +16,6 @@ export class StartPositionCalculator {
) { ) {
this.initStartXAndStartY(); this.initStartXAndStartY();
} }
private initStartXAndStartY() {
// If there is an init position passed
if (this.initPosition !== null) {
this.startPosition = this.initPosition;
} else {
// Now, let's find the start layer
if (this.startLayerName) {
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
}
if (this.startPosition === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startPosition === undefined) {
console.warn(
'This map is missing a layer named "start" that contains the available default start positions.'
);
// Let's start in the middle of the map
this.startPosition = {
x: this.mapFile.width * 16,
y: this.mapFile.height * 16,
};
}
}
/** /**
* *
@ -76,6 +50,47 @@ export class StartPositionCalculator {
} }
} }
public getStartPositionNames(): string[] {
const names: string[] = [];
for (const layer of this.gameMap.flatLayers) {
if (layer.name === "start") {
names.push(layer.name);
continue;
}
if (this.isStartLayer(layer)) {
names.push(layer.name);
}
}
return names;
}
private initStartXAndStartY() {
// If there is an init position passed
if (this.initPosition !== null) {
this.startPosition = this.initPosition;
} else {
// Now, let's find the start layer
if (this.startLayerName) {
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
}
if (this.startPosition === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startPosition === undefined) {
console.warn(
'This map is missing a layer named "start" that contains the available default start positions.'
);
// Let's start in the middle of the map
this.startPosition = {
x: this.mapFile.width * 16,
y: this.mapFile.height * 16,
};
}
}
private isStartLayer(layer: ITiledMapLayer): boolean { private isStartLayer(layer: ITiledMapLayer): boolean {
return this.getProperty(layer, GameMapProperties.START_LAYER) == true; return this.getProperty(layer, GameMapProperties.START_LAYER) == true;
} }

View file

@ -78,6 +78,8 @@ export interface ITiledMapTileLayer {
width: number; width: number;
x: number; x: number;
y: number; y: number;
parallaxx?: number;
parallaxy?: number;
/** /**
* Draw order (topdown (default), index) * Draw order (topdown (default), index)

View file

@ -53,10 +53,20 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {} public handlePointerDownEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {}
public handleSpaceKeyUpEvent(event: Event): Event { public handleSpaceKeyUpEvent(event: Event): Event {
const activatable = this.gameScene.getActivatablesManager().getSelectedActivatableObject(); const activatableManager = this.gameScene.getActivatablesManager();
if (activatable && activatable.isActivatable()) { const activatable = activatableManager.getSelectedActivatableObject();
if (activatable && activatable.isActivatable() && activatableManager.isSelectingByDistanceEnabled()) {
activatable.activate(); activatable.activate();
} }
return event; return event;
} }
public addSpaceEventListener(callback: Function): void {
this.gameScene.input.keyboard.addListener("keyup-SPACE", callback);
this.gameScene.getActivatablesManager().disableSelectingByDistance();
}
public removeSpaceEventListner(callback: Function): void {
this.gameScene.input.keyboard.removeListener("keyup-SPACE", callback);
this.gameScene.getActivatablesManager().enableSelectingByDistance();
}
} }

View file

@ -223,10 +223,10 @@ export class UserInputManager {
} }
addSpaceEventListner(callback: Function) { addSpaceEventListner(callback: Function) {
this.scene.input.keyboard.addListener("keyup-SPACE", callback); this.userInputHandler.addSpaceEventListener(callback);
} }
removeSpaceEventListner(callback: Function) { removeSpaceEventListner(callback: Function) {
this.scene.input.keyboard.removeListener("keyup-SPACE", callback); this.userInputHandler.removeSpaceEventListner(callback);
} }
destroy(): void { destroy(): void {
@ -255,6 +255,11 @@ export class UserInputManager {
(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => { (pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]) => {
this.joystick?.hide(); this.joystick?.hide();
this.userInputHandler.handlePointerUpEvent(pointer, gameObjects); this.userInputHandler.handlePointerUpEvent(pointer, gameObjects);
// Disable focus on iframe (need by Firefox)
if (pointer.downElement.nodeName === "CANVAS" && document.activeElement instanceof HTMLIFrameElement) {
document.activeElement.blur();
}
} }
); );

View file

@ -1,5 +1,5 @@
import { derived, get, writable } from "svelte/store"; import { derived, writable } from "svelte/store";
import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
function createCoWebsiteStore() { function createCoWebsiteStore() {
const { subscribe, set, update } = writable(Array<CoWebsite>()); const { subscribe, set, update } = writable(Array<CoWebsite>());
@ -9,7 +9,7 @@ function createCoWebsiteStore() {
return { return {
subscribe, subscribe,
add: (coWebsite: CoWebsite, position?: number) => { add: (coWebsite: CoWebsite, position?: number) => {
coWebsite.state.subscribe((value) => { coWebsite.getStateSubscriber().subscribe((value) => {
update((currentArray) => currentArray); update((currentArray) => currentArray);
}); });
@ -31,7 +31,7 @@ function createCoWebsiteStore() {
}, },
remove: (coWebsite: CoWebsite) => { remove: (coWebsite: CoWebsite) => {
update((currentArray) => [ update((currentArray) => [
...currentArray.filter((currentCoWebsite) => currentCoWebsite.iframe.id !== coWebsite.iframe.id), ...currentArray.filter((currentCoWebsite) => currentCoWebsite.getId() !== coWebsite.getId()),
]); ]);
}, },
empty: () => { empty: () => {
@ -43,9 +43,9 @@ function createCoWebsiteStore() {
export const coWebsites = createCoWebsiteStore(); export const coWebsites = createCoWebsiteStore();
export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) => export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) =>
$coWebsites.filter((coWebsite) => get(coWebsite.state) !== "asleep") $coWebsites.filter((coWebsite) => coWebsite.getState() !== "asleep")
); );
export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) => export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) =>
$coWebsites.find((coWebsite) => get(coWebsite.state) !== "asleep") $coWebsites.find((coWebsite) => coWebsite.getState() !== "asleep")
); );

View file

@ -1,5 +1,5 @@
import { derived, get, writable } from "svelte/store"; import { derived, get, writable } from "svelte/store";
import type { CoWebsite } from "../WebRtc/CoWebsiteManager"; import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
import { LayoutMode } from "../WebRtc/LayoutManager"; import { LayoutMode } from "../WebRtc/LayoutManager";
import { coWebsites } from "./CoWebsiteStore"; import { coWebsites } from "./CoWebsiteStore";
import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore"; import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore";
@ -31,7 +31,7 @@ function createHighlightedEmbedScreenStore() {
embedScreen.type !== currentEmbedScreen.type || embedScreen.type !== currentEmbedScreen.type ||
(embedScreen.type === "cowebsite" && (embedScreen.type === "cowebsite" &&
currentEmbedScreen.type === "cowebsite" && currentEmbedScreen.type === "cowebsite" &&
embedScreen.embed.iframe.id !== currentEmbedScreen.embed.iframe.id) || embedScreen.embed.getId() !== currentEmbedScreen.embed.getId()) ||
(embedScreen.type === "streamable" && (embedScreen.type === "streamable" &&
currentEmbedScreen.type === "streamable" && currentEmbedScreen.type === "streamable" &&
embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId) embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId)

View file

@ -1,4 +1,5 @@
import { derived, writable } from "svelte/store"; import { derived, writable } from "svelte/store";
import type { ActivatablesManager } from "../Phaser/Game/ActivatablesManager";
import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager";
export interface LayoutManagerAction { export interface LayoutManagerAction {

View file

@ -5,38 +5,33 @@ export function createColorStore() {
let followColor: number | undefined = undefined; let followColor: number | undefined = undefined;
let apiColor: number | undefined = undefined; let apiColor: number | undefined = undefined;
let pointedByPointer: number | undefined = undefined;
let pointedByPointer: boolean = false; let pointedByCharacter: number | undefined = undefined;
let pointedByCharacter: boolean = false;
const updateColor = () => { const updateColor = () => {
if (pointedByPointer || pointedByCharacter) { set(pointedByPointer ?? pointedByCharacter ?? followColor ?? apiColor);
set(0xffff00);
} else {
set(followColor ?? apiColor);
}
}; };
return { return {
subscribe, subscribe,
pointerOver() { pointerOver(color: number) {
pointedByPointer = true; pointedByPointer = color;
updateColor(); updateColor();
}, },
pointerOut() { pointerOut() {
pointedByPointer = false; pointedByPointer = undefined;
updateColor(); updateColor();
}, },
characterCloseBy() { characterCloseBy(color: number) {
pointedByCharacter = true; pointedByCharacter = color;
updateColor(); updateColor();
}, },
characterFarAway() { characterFarAway() {
pointedByCharacter = false; pointedByCharacter = undefined;
updateColor(); updateColor();
}, },

View file

@ -0,0 +1,6 @@
import { Readable, writable } from "svelte/store";
/**
* A store that contains the map starting layers names
*/
export const startLayerNamesStore = writable<string[]>([]);

View file

@ -19,6 +19,10 @@ export class PathfindingManager {
this.setEasyStarGrid(collisionsGrid); this.setEasyStarGrid(collisionsGrid);
} }
public setCollisionGrid(collisionGrid: number[][]): void {
this.setEasyStarGrid(collisionGrid);
}
public async findPath( public async findPath(
start: { x: number; y: number }, start: { x: number; y: number },
end: { x: number; y: number }, end: { x: number; y: number },

View file

@ -0,0 +1,12 @@
export class StringUtils {
public static parsePointFromParam(param: string, separator: string = ","): { x: number; y: number } | undefined {
const values = param.split(separator).map((val) => parseInt(val));
if (values.length !== 2) {
return;
}
if (isNaN(values[0]) || isNaN(values[1])) {
return;
}
return { x: values[0], y: values[1] };
}
}

View file

@ -0,0 +1,17 @@
import type CancelablePromise from "cancelable-promise";
import type { Readable, Writable } from "svelte/store";
export type CoWebsiteState = "asleep" | "loading" | "ready";
export interface CoWebsite {
getId(): string;
getUrl(): URL;
getState(): CoWebsiteState;
getStateSubscriber(): Readable<CoWebsiteState>;
getIframe(): HTMLIFrameElement | undefined;
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined;
getWidthPercent(): number | undefined;
isClosable(): boolean;
load(): CancelablePromise<HTMLIFrameElement>;
unload(): Promise<void>;
}

View file

@ -0,0 +1,49 @@
import CancelablePromise from "cancelable-promise";
import { gameManager } from "../../Phaser/Game/GameManager";
import { jitsiFactory } from "../JitsiFactory";
import { SimpleCoWebsite } from "./SimpleCoWebsite";
export class JitsiCoWebsite extends SimpleCoWebsite {
private jitsiLoadPromise?: () => CancelablePromise<HTMLIFrameElement>;
setJitsiLoadPromise(promise: () => CancelablePromise<HTMLIFrameElement>): void {
this.jitsiLoadPromise = promise;
}
load(): CancelablePromise<HTMLIFrameElement> {
return new CancelablePromise((resolve, reject, cancel) => {
this.state.set("loading");
gameManager.getCurrentGameScene().disableMediaBehaviors();
if (!this.jitsiLoadPromise) {
return reject("Undefined Jitsi start callback");
}
const jitsiLoading = this.jitsiLoadPromise()
.then((iframe) => {
this.iframe = iframe;
this.iframe.classList.add("pixel");
this.state.set("ready");
return resolve(iframe);
})
.catch((err) => {
return reject(err);
});
cancel(() => {
jitsiLoading.cancel();
this.unload().catch((err) => {
console.error("Cannot unload Jitsi co-website while cancel loading", err);
});
});
});
}
unload(): Promise<void> {
jitsiFactory.destroy();
gameManager.getCurrentGameScene().enableMediaBehaviors();
return super.unload();
}
}

View file

@ -0,0 +1,133 @@
import CancelablePromise from "cancelable-promise";
import { get, Readable, writable, Writable } from "svelte/store";
import { iframeListener } from "../../Api/IframeListener";
import { coWebsiteManager } from "../CoWebsiteManager";
import type { CoWebsite, CoWebsiteState } from "./CoWesbite";
export class SimpleCoWebsite implements CoWebsite {
protected id: string;
protected url: URL;
protected state: Writable<CoWebsiteState>;
protected iframe?: HTMLIFrameElement;
protected loadIframe?: CancelablePromise<HTMLIFrameElement>;
protected allowApi?: boolean;
protected allowPolicy?: string;
protected widthPercent?: number;
protected closable: boolean;
constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean) {
this.id = coWebsiteManager.generateUniqueId();
this.url = url;
this.state = writable("asleep" as CoWebsiteState);
this.allowApi = allowApi;
this.allowPolicy = allowPolicy;
this.widthPercent = widthPercent;
this.closable = closable ?? false;
}
getId(): string {
return this.id;
}
getUrl(): URL {
return this.url;
}
getState(): CoWebsiteState {
return get(this.state);
}
getStateSubscriber(): Readable<CoWebsiteState> {
return this.state;
}
getIframe(): HTMLIFrameElement | undefined {
return this.iframe;
}
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined {
return this.loadIframe;
}
getWidthPercent(): number | undefined {
return this.widthPercent;
}
isClosable(): boolean {
return this.closable;
}
load(): CancelablePromise<HTMLIFrameElement> {
this.loadIframe = new CancelablePromise((resolve, reject, cancel) => {
this.state.set("loading");
const iframe = document.createElement("iframe");
this.iframe = iframe;
this.iframe.src = this.url.toString();
this.iframe.id = this.id;
if (this.allowPolicy) {
this.iframe.allow = this.allowPolicy;
}
if (this.allowApi) {
iframeListener.registerIframe(this.iframe);
}
this.iframe.classList.add("pixel");
const onloadPromise = new Promise<void>((resolve) => {
if (this.iframe) {
this.iframe.onload = () => {
this.state.set("ready");
resolve();
};
}
});
const onTimeoutPromise = new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000);
});
coWebsiteManager.getCoWebsiteBuffer().appendChild(this.iframe);
const race = CancelablePromise.race([onloadPromise, onTimeoutPromise])
.then(() => {
return resolve(iframe);
})
.catch((err) => {
console.error("Error on co-website loading => ", err);
return reject();
});
cancel(() => {
race.cancel();
this.unload().catch((err) => {
console.error("Cannot unload co-website while cancel loading", err);
});
});
});
return this.loadIframe;
}
unload(): Promise<void> {
return new Promise((resolve) => {
if (this.iframe) {
if (this.allowApi) {
iframeListener.unregisterIframe(this.iframe);
}
this.iframe.parentNode?.removeChild(this.iframe);
}
if (this.loadIframe) {
this.loadIframe.cancel();
this.loadIframe = undefined;
}
this.state.set("asleep");
resolve();
});
}
}

View file

@ -1,16 +1,15 @@
import { HtmlUtils } from "./HtmlUtils"; import { HtmlUtils } from "./HtmlUtils";
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { iframeListener } from "../Api/IframeListener";
import { waScaleManager } from "../Phaser/Services/WaScaleManager"; import { waScaleManager } from "../Phaser/Services/WaScaleManager";
import { coWebsites, coWebsitesNotAsleep, mainCoWebsite } from "../Stores/CoWebsiteStore"; import { coWebsites, coWebsitesNotAsleep, mainCoWebsite } from "../Stores/CoWebsiteStore";
import { get, Writable, writable } from "svelte/store"; import { get, Readable, Writable, writable } from "svelte/store";
import { embedScreenLayout, highlightedEmbedScreen } from "../Stores/EmbedScreensStore"; import { embedScreenLayout, highlightedEmbedScreen } from "../Stores/EmbedScreensStore";
import { isMediaBreakpointDown } from "../Utils/BreakpointsUtils"; import { isMediaBreakpointDown } from "../Utils/BreakpointsUtils";
import { jitsiFactory } from "./JitsiFactory";
import { gameManager } from "../Phaser/Game/GameManager";
import { LayoutMode } from "./LayoutManager"; import { LayoutMode } from "./LayoutManager";
import type { CoWebsite } from "./CoWebsite/CoWesbite";
import type CancelablePromise from "cancelable-promise";
enum iframeStates { export enum iframeStates {
closed = 1, closed = 1,
loading, // loading an iframe can be slow, so we show some placeholder until it is ready loading, // loading an iframe can be slow, so we show some placeholder until it is ready
opened, opened,
@ -21,7 +20,7 @@ const gameOverlayDomId = "game-overlay";
const cowebsiteBufferDomId = "cowebsite-buffer"; // the id of the container who contains cowebsite iframes. const cowebsiteBufferDomId = "cowebsite-buffer"; // the id of the container who contains cowebsite iframes.
const cowebsiteAsideHolderDomId = "cowebsite-aside-holder"; const cowebsiteAsideHolderDomId = "cowebsite-aside-holder";
const cowebsiteLoaderDomId = "cowebsite-loader"; const cowebsiteLoaderDomId = "cowebsite-loader";
export const cowebsiteCloseButtonId = "cowebsite-close"; const cowebsiteCloseButtonId = "cowebsite-close";
const cowebsiteFullScreenButtonId = "cowebsite-fullscreen"; const cowebsiteFullScreenButtonId = "cowebsite-fullscreen";
const cowebsiteOpenFullScreenImageId = "cowebsite-fullscreen-open"; const cowebsiteOpenFullScreenImageId = "cowebsite-fullscreen-open";
const cowebsiteCloseFullScreenImageId = "cowebsite-fullscreen-close"; const cowebsiteCloseFullScreenImageId = "cowebsite-fullscreen-close";
@ -34,29 +33,12 @@ interface TouchMoveCoordinates {
y: number; y: number;
} }
export type CoWebsiteState = "asleep" | "loading" | "ready";
export type CoWebsite = {
iframe: HTMLIFrameElement;
url: URL;
state: Writable<CoWebsiteState>;
closable: boolean;
allowPolicy: string | undefined;
allowApi: boolean | undefined;
jitsi?: boolean;
altMessage?: string;
};
class CoWebsiteManager { class CoWebsiteManager {
private openedMain: iframeStates = iframeStates.closed; private openedMain: Writable<iframeStates> = writable(iframeStates.closed);
private _onResize: Subject<void> = new Subject(); private _onResize: Subject<void> = new Subject();
public onResize = this._onResize.asObservable(); public onResize = this._onResize.asObservable();
/**
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
* So we use this promise to queue up every cowebsite state transition
*/
private currentOperationPromise: Promise<void> = Promise.resolve();
private cowebsiteDom: HTMLDivElement; private cowebsiteDom: HTMLDivElement;
private resizing: boolean = false; private resizing: boolean = false;
private gameOverlayDom: HTMLDivElement; private gameOverlayDom: HTMLDivElement;
@ -74,6 +56,14 @@ class CoWebsiteManager {
this.resizeAllIframes(); this.resizeAllIframes();
}); });
public getMainState() {
return get(this.openedMain);
}
public getMainStateSubscriber(): Readable<iframeStates> {
return this.openedMain;
}
get width(): number { get width(): number {
return this.cowebsiteDom.clientWidth; return this.cowebsiteDom.clientWidth;
} }
@ -82,8 +72,13 @@ class CoWebsiteManager {
this.cowebsiteDom.style.width = width + "px"; this.cowebsiteDom.style.width = width + "px";
} }
set widthPercent(width: number) { get maxWidth(): number {
this.cowebsiteDom.style.width = width + "%"; let maxWidth = 75 * window.innerWidth;
if (maxWidth !== 0) {
maxWidth = Math.round(maxWidth / 100);
}
return maxWidth;
} }
get height(): number { get height(): number {
@ -94,6 +89,15 @@ class CoWebsiteManager {
this.cowebsiteDom.style.height = height + "px"; this.cowebsiteDom.style.height = height + "px";
} }
get maxHeight(): number {
let maxHeight = 60 * window.innerHeight;
if (maxHeight !== 0) {
maxHeight = Math.round(maxHeight / 100);
}
return maxHeight;
}
get verticalMode(): boolean { get verticalMode(): boolean {
return window.innerWidth < window.innerHeight; return window.innerWidth < window.innerHeight;
} }
@ -128,13 +132,11 @@ class CoWebsiteManager {
throw new Error("Undefined main co-website on closing"); throw new Error("Undefined main co-website on closing");
} }
if (coWebsite.closable) { if (coWebsite.isClosable()) {
this.closeCoWebsite(coWebsite).catch(() => { this.closeCoWebsite(coWebsite);
console.error("Error during closing a co-website by a button");
});
} else { } else {
this.unloadCoWebsite(coWebsite).catch(() => { this.unloadCoWebsite(coWebsite).catch((err) => {
console.error("Error during unloading a co-website by a button"); console.error("Cannot unload co-website on click on close button", err);
}); });
} }
}); });
@ -191,29 +193,21 @@ class CoWebsiteManager {
if (this.verticalMode) { if (this.verticalMode) {
const tempValue = this.height + y; const tempValue = this.height + y;
let maxHeight = 60 * window.innerHeight;
if (maxHeight !== 0) {
maxHeight = Math.round(maxHeight / 100);
}
if (tempValue < this.cowebsiteAsideHolderDom.offsetHeight) { if (tempValue < this.cowebsiteAsideHolderDom.offsetHeight) {
this.height = this.cowebsiteAsideHolderDom.offsetHeight; this.height = this.cowebsiteAsideHolderDom.offsetHeight;
} else if (tempValue > maxHeight) { } else if (tempValue > this.maxHeight) {
this.height = maxHeight; this.height = this.maxHeight;
} else { } else {
this.height = tempValue; this.height = tempValue;
} }
} else { } else {
const tempValue = this.width - x; const tempValue = this.width - x;
let maxWidth = 75 * window.innerWidth;
if (maxWidth !== 0) {
maxWidth = Math.round(maxWidth / 100);
}
if (tempValue < this.cowebsiteAsideHolderDom.offsetWidth) { if (tempValue < this.cowebsiteAsideHolderDom.offsetWidth) {
this.width = this.cowebsiteAsideHolderDom.offsetWidth; this.width = this.cowebsiteAsideHolderDom.offsetWidth;
} else if (tempValue > maxWidth) { } else if (tempValue > this.maxWidth) {
this.width = maxWidth; this.width = this.maxWidth;
} else { } else {
this.width = tempValue; this.width = tempValue;
} }
@ -230,7 +224,10 @@ class CoWebsiteManager {
return; return;
} }
coWebsite.iframe.style.display = "none"; const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "none";
}
this.resizing = true; this.resizing = true;
document.addEventListener("mousemove", movecallback); document.addEventListener("mousemove", movecallback);
}); });
@ -246,7 +243,10 @@ class CoWebsiteManager {
return; return;
} }
coWebsite.iframe.style.display = "flex"; const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "flex";
}
this.resizing = false; this.resizing = false;
}); });
@ -259,7 +259,10 @@ class CoWebsiteManager {
return; return;
} }
coWebsite.iframe.style.display = "none"; const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "none";
}
this.resizing = true; this.resizing = true;
const touchEvent = event.touches[0]; const touchEvent = event.touches[0];
this.previousTouchMoveCoordinates = { x: touchEvent.pageX, y: touchEvent.pageY }; this.previousTouchMoveCoordinates = { x: touchEvent.pageX, y: touchEvent.pageY };
@ -278,7 +281,10 @@ class CoWebsiteManager {
return; return;
} }
coWebsite.iframe.style.display = "flex"; const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "flex";
}
this.resizing = false; this.resizing = false;
}); });
} }
@ -299,16 +305,43 @@ class CoWebsiteManager {
}); });
} }
public displayMain() {
const coWebsite = this.getMainCoWebsite();
if (coWebsite) {
const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "block";
}
}
this.loadMain(coWebsite?.getWidthPercent());
this.openMain();
this.fire();
}
public hideMain() {
const coWebsite = this.getMainCoWebsite();
if (coWebsite) {
const iframe = coWebsite.getIframe();
if (iframe) {
iframe.style.display = "none";
}
}
this.cowebsiteDom.classList.add("closing");
this.cowebsiteDom.classList.remove("opened");
this.openedMain.set(iframeStates.closed);
this.fire();
}
private closeMain(): void { private closeMain(): void {
this.toggleFullScreenIcon(true); this.toggleFullScreenIcon(true);
this.cowebsiteDom.classList.add("closing"); this.cowebsiteDom.classList.add("closing");
this.cowebsiteDom.classList.remove("opened"); this.cowebsiteDom.classList.remove("opened");
this.openedMain = iframeStates.closed; this.openedMain.set(iframeStates.closed);
this.resetStyleMain(); this.resetStyleMain();
this.fire(); this.fire();
} }
private loadMain(): void { private loadMain(openingWidth?: number): void {
this.loaderAnimationInterval.interval = setInterval(() => { this.loaderAnimationInterval.interval = setInterval(() => {
if (!this.loaderAnimationInterval.trails) { if (!this.loaderAnimationInterval.trails) {
this.loaderAnimationInterval.trails = [0, 1, 2]; this.loaderAnimationInterval.trails = [0, 1, 2];
@ -337,16 +370,34 @@ class CoWebsiteManager {
trail === 3 ? 0 : trail + 1 trail === 3 ? 0 : trail + 1
); );
}, 200); }, 200);
if (!this.verticalMode && openingWidth) {
let newWidth = 50;
if (openingWidth > 100) {
newWidth = 100;
} else if (openingWidth > 1) {
newWidth = openingWidth;
}
newWidth = Math.round((newWidth * this.maxWidth) / 100);
if (newWidth < this.cowebsiteAsideHolderDom.offsetWidth) {
newWidth = this.cowebsiteAsideHolderDom.offsetWidth;
}
this.width = newWidth;
}
this.cowebsiteDom.classList.add("opened"); this.cowebsiteDom.classList.add("opened");
this.openedMain = iframeStates.loading; this.openedMain.set(iframeStates.loading);
} }
private openMain(): void { private openMain(): void {
this.cowebsiteDom.addEventListener("transitionend", () => { this.cowebsiteDom.addEventListener("transitionend", () => {
this.resizeAllIframes(); this.resizeAllIframes();
}); });
this.openedMain = iframeStates.opened; this.openedMain.set(iframeStates.opened);
this.resetStyleMain();
} }
public resetStyleMain() { public resetStyleMain() {
@ -359,7 +410,9 @@ class CoWebsiteManager {
} }
public getCoWebsiteById(coWebsiteId: string): CoWebsite | undefined { public getCoWebsiteById(coWebsiteId: string): CoWebsite | undefined {
return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId); return get(coWebsites).find((coWebsite: CoWebsite) => {
return coWebsite.getId() === coWebsiteId;
});
} }
private getCoWebsiteByPosition(position: number): CoWebsite | undefined { private getCoWebsiteByPosition(position: number): CoWebsite | undefined {
@ -379,7 +432,9 @@ class CoWebsiteManager {
} }
private getPositionByCoWebsite(coWebsite: CoWebsite): number { private getPositionByCoWebsite(coWebsite: CoWebsite): number {
return get(coWebsites).findIndex((currentCoWebsite) => currentCoWebsite.iframe.id === coWebsite.iframe.id); return get(coWebsites).findIndex((currentCoWebsite) => {
return currentCoWebsite.getId() === coWebsite.getId();
});
} }
private getSlotByCowebsite(coWebsite: CoWebsite): HTMLDivElement | undefined { private getSlotByCowebsite(coWebsite: CoWebsite): HTMLDivElement | undefined {
@ -393,7 +448,7 @@ class CoWebsiteManager {
if (index === 0) { if (index === 0) {
id += "main"; id += "main";
} else { } else {
id += coWebsite.iframe.id; id += coWebsite.getId();
} }
const slot = HtmlUtils.getElementById<HTMLDivElement>(id); const slot = HtmlUtils.getElementById<HTMLDivElement>(id);
@ -410,60 +465,72 @@ class CoWebsiteManager {
const bounding = coWebsiteSlot.getBoundingClientRect(); const bounding = coWebsiteSlot.getBoundingClientRect();
coWebsite.iframe.style.top = bounding.top + "px"; const iframe = coWebsite.getIframe();
coWebsite.iframe.style.left = bounding.left + "px";
coWebsite.iframe.style.width = bounding.right - bounding.left + "px"; if (iframe) {
coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px"; iframe.style.top = bounding.top + "px";
iframe.style.left = bounding.left + "px";
iframe.style.width = bounding.right - bounding.left + "px";
iframe.style.height = bounding.bottom - bounding.top + "px";
}
} }
public resizeAllIframes() { public resizeAllIframes() {
const mainCoWebsite = this.getCoWebsiteByPosition(0); const mainCoWebsite = this.getCoWebsiteByPosition(0);
const mainIframe = mainCoWebsite?.getIframe();
const highlightEmbed = get(highlightedEmbedScreen); const highlightEmbed = get(highlightedEmbedScreen);
get(coWebsites).forEach((coWebsite) => { get(coWebsites).forEach((coWebsite: CoWebsite) => {
const notMain = !mainCoWebsite || (mainCoWebsite && mainCoWebsite.iframe.id !== coWebsite.iframe.id); const iframe = coWebsite.getIframe();
if (!iframe) {
return;
}
const notMain = !mainCoWebsite || (mainCoWebsite && mainIframe && mainIframe.id !== iframe.id);
const notHighlighEmbed = const notHighlighEmbed =
!highlightEmbed || !highlightEmbed ||
(highlightEmbed && (highlightEmbed &&
(highlightEmbed.type !== "cowebsite" || (highlightEmbed.type !== "cowebsite" ||
(highlightEmbed.type === "cowebsite" && (highlightEmbed.type === "cowebsite" && highlightEmbed.embed.getId() !== coWebsite.getId())));
highlightEmbed.embed.iframe.id !== coWebsite.iframe.id)));
if (coWebsite.iframe.classList.contains("main") && notMain) { if (iframe.classList.contains("main") && notMain) {
coWebsite.iframe.classList.remove("main"); iframe.classList.remove("main");
} }
if (coWebsite.iframe.classList.contains("highlighted") && notHighlighEmbed) { if (iframe.classList.contains("highlighted") && notHighlighEmbed) {
coWebsite.iframe.classList.remove("highlighted"); iframe.classList.remove("highlighted");
coWebsite.iframe.classList.add("pixel"); iframe.classList.add("pixel");
coWebsite.iframe.style.top = "-1px"; iframe.style.top = "-1px";
coWebsite.iframe.style.left = "-1px"; iframe.style.left = "-1px";
} }
if (notMain && notHighlighEmbed) { if (notMain && notHighlighEmbed) {
coWebsite.iframe.classList.add("pixel"); iframe.classList.add("pixel");
coWebsite.iframe.style.top = "-1px"; iframe.style.top = "-1px";
coWebsite.iframe.style.left = "-1px"; iframe.style.left = "-1px";
} }
this.setIframeOffset(coWebsite); this.setIframeOffset(coWebsite);
}); });
if (mainCoWebsite) { if (mainIframe) {
mainCoWebsite.iframe.classList.add("main"); mainIframe.classList.add("main");
mainCoWebsite.iframe.classList.remove("pixel"); mainIframe.classList.remove("pixel");
} }
if (highlightEmbed && highlightEmbed.type === "cowebsite") { if (highlightEmbed && highlightEmbed.type === "cowebsite") {
highlightEmbed.embed.iframe.classList.add("highlighted"); const highlightEmbedIframe = highlightEmbed.embed.getIframe();
highlightEmbed.embed.iframe.classList.remove("pixel"); if (highlightEmbedIframe) {
highlightEmbedIframe.classList.add("highlighted");
highlightEmbedIframe.classList.remove("pixel");
}
} }
} }
private removeHighlightCoWebsite(coWebsite: CoWebsite) { private removeHighlightCoWebsite(coWebsite: CoWebsite) {
const highlighted = get(highlightedEmbedScreen); const highlighted = get(highlightedEmbedScreen);
if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.iframe.id === coWebsite.iframe.id) { if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.getId() === coWebsite.getId()) {
highlightedEmbedScreen.removeHighlight(); highlightedEmbedScreen.removeHighlight();
} }
} }
@ -476,7 +543,9 @@ class CoWebsiteManager {
this.closeMain(); this.closeMain();
} }
coWebsite.iframe.remove(); coWebsite.unload().catch((err) => {
console.error("Cannot unload cowebsite on remove from stack");
});
} }
public goToMain(coWebsite: CoWebsite) { public goToMain(coWebsite: CoWebsite) {
@ -488,38 +557,21 @@ class CoWebsiteManager {
isMediaBreakpointDown("lg") && isMediaBreakpointDown("lg") &&
get(embedScreenLayout) === LayoutMode.Presentation && get(embedScreenLayout) === LayoutMode.Presentation &&
mainCoWebsite && mainCoWebsite &&
mainCoWebsite.iframe.id !== coWebsite.iframe.id && mainCoWebsite.getId() !== coWebsite.getId() &&
get(mainCoWebsite.state) !== "asleep" mainCoWebsite.getState() !== "asleep"
) { ) {
highlightedEmbedScreen.toggleHighlight({ highlightedEmbedScreen.removeHighlight();
type: "cowebsite",
embed: mainCoWebsite,
});
} }
this.resizeAllIframes(); this.resizeAllIframes();
} }
public searchJitsi(): CoWebsite | undefined { public addCoWebsiteToStore(coWebsite: CoWebsite, position: number | undefined) {
return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.jitsi);
}
private initialiseCowebsite(coWebsite: CoWebsite, position: number | undefined) {
if (coWebsite.allowPolicy) {
coWebsite.iframe.allow = coWebsite.allowPolicy;
}
if (coWebsite.allowApi) {
iframeListener.registerIframe(coWebsite.iframe);
}
coWebsite.iframe.classList.add("pixel");
const coWebsitePosition = position === undefined ? get(coWebsites).length : position; const coWebsitePosition = position === undefined ? get(coWebsites).length : position;
coWebsites.add(coWebsite, coWebsitePosition); coWebsites.add(coWebsite, coWebsitePosition);
} }
private generateUniqueId() { public generateUniqueId() {
let id = undefined; let id = undefined;
do { do {
id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7);
@ -528,201 +580,89 @@ class CoWebsiteManager {
return id; return id;
} }
public addCoWebsite( public loadCoWebsite(coWebsite: CoWebsite): CancelablePromise<void> {
url: string,
base: string,
allowApi?: boolean,
allowPolicy?: string,
position?: number,
closable?: boolean,
altMessage?: string
): CoWebsite {
const iframe = document.createElement("iframe");
const fullUrl = new URL(url, base);
iframe.src = fullUrl.toString();
iframe.id = this.generateUniqueId();
const newCoWebsite: CoWebsite = {
iframe,
url: fullUrl,
state: writable("asleep" as CoWebsiteState),
closable: closable ?? false,
allowPolicy,
allowApi,
altMessage,
};
this.initialiseCowebsite(newCoWebsite, position);
return newCoWebsite;
}
public addCoWebsiteFromIframe(
iframe: HTMLIFrameElement,
allowApi?: boolean,
allowPolicy?: string,
position?: number,
closable?: boolean,
jitsi?: boolean
): CoWebsite {
if (get(coWebsitesNotAsleep).length < 1) {
this.loadMain();
}
iframe.id = this.generateUniqueId();
const newCoWebsite: CoWebsite = {
iframe,
url: new URL(iframe.src),
state: writable("ready" as CoWebsiteState),
closable: closable ?? false,
allowPolicy,
allowApi,
jitsi,
};
if (position === 0) {
this.openMain();
setTimeout(() => {
this.fire();
}, animationTime);
}
this.initialiseCowebsite(newCoWebsite, position);
return newCoWebsite;
}
public loadCoWebsite(coWebsite: CoWebsite): Promise<CoWebsite> {
if (get(coWebsitesNotAsleep).length < 1) { if (get(coWebsitesNotAsleep).length < 1) {
coWebsites.remove(coWebsite); coWebsites.remove(coWebsite);
coWebsites.add(coWebsite, 0); coWebsites.add(coWebsite, 0);
this.loadMain(); this.loadMain(coWebsite.getWidthPercent());
} }
coWebsite.state.set("loading"); // Check if the main is hide
if (this.getMainCoWebsite() && this.getMainState() === iframeStates.closed) {
this.displayMain();
}
const mainCoWebsite = this.getMainCoWebsite(); const coWebsiteLloading = coWebsite
.load()
.then(() => {
const mainCoWebsite = this.getMainCoWebsite();
if (mainCoWebsite && mainCoWebsite.getId() === coWebsite.getId()) {
this.openMain();
return new Promise((resolve, reject) => { setTimeout(() => {
const onloadPromise = new Promise<void>((resolve) => { this.fire();
coWebsite.iframe.onload = () => { }, animationTime);
coWebsite.state.set("ready"); }
resolve(); this.resizeAllIframes();
}; })
.catch((err) => {
console.error("Error on co-website loading => ", err);
this.removeCoWebsiteFromStack(coWebsite);
}); });
const onTimeoutPromise = new Promise<void>((resolve) => { return coWebsiteLloading;
setTimeout(() => resolve(), 2000);
});
this.cowebsiteBufferDom.appendChild(coWebsite.iframe);
if (coWebsite.jitsi) {
const gameScene = gameManager.getCurrentGameScene();
gameScene.disableMediaBehaviors();
jitsiFactory.restart();
}
this.currentOperationPromise = this.currentOperationPromise
.then(() => Promise.race([onloadPromise, onTimeoutPromise]))
.then(() => {
if (mainCoWebsite && mainCoWebsite.iframe.id === coWebsite.iframe.id) {
this.openMain();
setTimeout(() => {
this.fire();
}, animationTime);
}
return resolve(coWebsite);
})
.catch((err) => {
console.error("Error on co-website loading => ", err);
this.removeCoWebsiteFromStack(coWebsite);
return reject();
});
});
} }
public unloadCoWebsite(coWebsite: CoWebsite): Promise<void> { public unloadCoWebsite(coWebsite: CoWebsite): Promise<void> {
return new Promise((resolve, reject) => { this.removeHighlightCoWebsite(coWebsite);
this.removeHighlightCoWebsite(coWebsite);
coWebsite.iframe.parentNode?.removeChild(coWebsite.iframe); return coWebsite
coWebsite.state.set("asleep"); .unload()
coWebsites.remove(coWebsite); .then(() => {
coWebsites.remove(coWebsite);
const mainCoWebsite = this.getMainCoWebsite();
if (coWebsite.jitsi) { if (mainCoWebsite) {
jitsiFactory.stop(); this.removeHighlightCoWebsite(mainCoWebsite);
const gameScene = gameManager.getCurrentGameScene(); this.goToMain(mainCoWebsite);
gameScene.enableMediaBehaviors(); this.resizeAllIframes();
} } else {
this.closeMain();
}
const mainCoWebsite = this.getMainCoWebsite(); coWebsites.add(coWebsite, get(coWebsites).length);
})
.catch(() => {
console.error();
});
}
if (mainCoWebsite) { public closeCoWebsite(coWebsite: CoWebsite): void {
this.removeHighlightCoWebsite(mainCoWebsite); if (get(coWebsites).length === 1) {
this.goToMain(mainCoWebsite); this.fire();
this.resizeAllIframes(); }
} else {
this.closeMain();
}
coWebsites.add(coWebsite, get(coWebsites).length); this.removeCoWebsiteFromStack(coWebsite);
resolve(); const mainCoWebsite = this.getMainCoWebsite();
if (mainCoWebsite) {
this.removeHighlightCoWebsite(mainCoWebsite);
this.goToMain(mainCoWebsite);
this.resizeAllIframes();
} else {
this.closeMain();
}
}
public closeCoWebsites(): void {
get(coWebsites).forEach((coWebsite: CoWebsite) => {
this.closeCoWebsite(coWebsite);
}); });
} }
public closeCoWebsite(coWebsite: CoWebsite): Promise<void> {
this.currentOperationPromise = this.currentOperationPromise.then(
() =>
new Promise((resolve) => {
if (coWebsite.jitsi) {
jitsiFactory.destroy();
const gameScene = gameManager.getCurrentGameScene();
gameScene.enableMediaBehaviors();
}
if (get(coWebsites).length === 1) {
this.fire();
}
if (coWebsite.allowApi) {
iframeListener.unregisterIframe(coWebsite.iframe);
}
this.removeCoWebsiteFromStack(coWebsite);
const mainCoWebsite = this.getMainCoWebsite();
if (mainCoWebsite) {
this.removeHighlightCoWebsite(mainCoWebsite);
this.goToMain(mainCoWebsite);
this.resizeAllIframes();
} else {
this.closeMain();
}
resolve();
})
);
return this.currentOperationPromise;
}
public closeCoWebsites(): Promise<void> {
return (this.currentOperationPromise = this.currentOperationPromise.then(() => {
get(coWebsites).forEach((coWebsite: CoWebsite) => {
this.closeCoWebsite(coWebsite).catch(() => {
console.error("Error during closing a co-website");
});
});
}));
return this.currentOperationPromise;
}
public getGameSize(): { width: number; height: number } { public getGameSize(): { width: number; height: number } {
if (this.openedMain === iframeStates.closed) { if (this.getMainState() === iframeStates.closed) {
return { return {
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight, height: window.innerHeight,

View file

@ -1,7 +1,8 @@
import { JITSI_URL } from "../Enum/EnvironmentVariable"; import { JITSI_URL } from "../Enum/EnvironmentVariable";
import { CoWebsite, coWebsiteManager } from "./CoWebsiteManager"; import { coWebsiteManager } from "./CoWebsiteManager";
import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore"; import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
import CancelablePromise from "cancelable-promise";
interface jitsiConfigInterface { interface jitsiConfigInterface {
startWithAudioMuted: boolean; startWithAudioMuted: boolean;
@ -134,114 +135,96 @@ class JitsiFactory {
return slugify((instance !== 'global') ? instance.replace("/", "-") + "-" + roomName : roomName); return slugify((instance !== 'global') ? instance.replace("/", "-") + "-" + roomName : roomName);
} }
public async start( public start(
roomName: string, roomName: string,
playerName: string, playerName: string,
jwt?: string, jwt?: string,
config?: object, config?: object,
interfaceConfig?: object, interfaceConfig?: object,
jitsiUrl?: string jitsiUrl?: string
) { ): CancelablePromise<HTMLIFrameElement> {
const coWebsite = coWebsiteManager.searchJitsi(); return new CancelablePromise((resolve, reject, cancel) => {
// Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a
// conference. Problem is that this data grows indefinitely. Thus
// after some time the URLs get so huge that loading the iframe
// becomes slow and eventually breaks completely. Thus lets just
// clear jitsi local storage before starting a new conference.
window.localStorage.removeItem("jitsiLocalStorage");
if (coWebsite) { const domain = jitsiUrl || JITSI_URL;
await coWebsiteManager.closeCoWebsite(coWebsite); if (domain === undefined) {
} throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
// Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a
// conference. Problem is that this data grows indefinitely. Thus
// after some time the URLs get so huge that loading the iframe
// becomes slow and eventually breaks completely. Thus lets just
// clear jitsi local storage before starting a new conference.
window.localStorage.removeItem("jitsiLocalStorage");
const domain = jitsiUrl || JITSI_URL;
if (domain === undefined) {
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
}
await this.loadJitsiScript(domain);
const options: JitsiOptions = {
roomName: roomName,
jwt: jwt,
width: "100%",
height: "100%",
parentNode: coWebsiteManager.getCoWebsiteBuffer(),
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
};
if (!options.jwt) {
delete options.jwt;
}
const doResolve = (): void => {
const iframe = coWebsiteManager.getCoWebsiteBuffer().querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
if (iframe && this.jitsiApi) {
const coWebsite = coWebsiteManager.addCoWebsiteFromIframe(iframe, false, undefined, 0, false, true);
this.jitsiApi.addListener("videoConferenceLeft", () => {
this.closeOrUnload(coWebsite);
});
this.jitsiApi.addListener("readyToClose", () => {
this.closeOrUnload(coWebsite);
});
} }
coWebsiteManager.resizeAllIframes(); const loadScript = this.loadJitsiScript(domain).then(() => {
}; const options: JitsiOptions = {
roomName: roomName,
jwt: jwt,
width: "100%",
height: "100%",
parentNode: coWebsiteManager.getCoWebsiteBuffer(),
configOverwrite: mergeConfig(config),
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
};
this.jitsiApi = undefined; if (!options.jwt) {
delete options.jwt;
}
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations. const timemout = setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback); const doResolve = (): void => {
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback); clearTimeout(timemout);
const iframe = coWebsiteManager
.getCoWebsiteBuffer()
.querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
if (iframe && this.jitsiApi) {
this.jitsiApi.addListener("videoConferenceLeft", () => {
this.closeOrUnload(iframe);
});
this.jitsiApi.addListener("readyToClose", () => {
this.closeOrUnload(iframe);
});
return resolve(iframe);
}
};
this.jitsiApi = undefined;
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
});
cancel(() => {
loadScript.cancel();
});
});
} }
private closeOrUnload = function (coWebsite: CoWebsite) { private closeOrUnload = function (iframe: HTMLIFrameElement) {
if (coWebsite.closable) { const coWebsite = coWebsiteManager.getCoWebsites().find((coWebsite) => coWebsite.getIframe() === iframe);
coWebsiteManager.closeCoWebsite(coWebsite).catch(() => {
console.error("Error during closing a Jitsi Meet"); if (!coWebsite) {
}); return;
}
if (coWebsite.isClosable()) {
coWebsiteManager.closeCoWebsite(coWebsite);
} else { } else {
coWebsiteManager.unloadCoWebsite(coWebsite).catch(() => { coWebsiteManager.unloadCoWebsite(coWebsite).catch((err) => {
console.error("Error during unloading a Jitsi Meet"); console.error("Cannot unload co-website from the Jitsi factory", err);
}); });
} }
}; };
public restart() {
if (!this.jitsiApi) {
return;
}
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
const coWebsite = coWebsiteManager.searchJitsi();
console.log("jitsi api ", this.jitsiApi);
console.log("iframe cowebsite", coWebsite?.iframe);
if (!coWebsite) {
this.destroy();
return;
}
this.jitsiApi.addListener("videoConferenceLeft", () => {
this.closeOrUnload(coWebsite);
});
this.jitsiApi.addListener("readyToClose", () => {
this.closeOrUnload(coWebsite);
});
}
public stop() { public stop() {
if (!this.jitsiApi) { if (!this.jitsiApi) {
return; return;
@ -276,8 +259,8 @@ class JitsiFactory {
} }
} }
private async loadJitsiScript(domain: string): Promise<void> { private loadJitsiScript(domain: string): CancelablePromise<void> {
return new Promise<void>((resolve, reject) => { return new CancelablePromise<void>((resolve, reject, cancel) => {
if (this.jitsiScriptLoaded) { if (this.jitsiScriptLoaded) {
resolve(); resolve();
return; return;
@ -296,6 +279,10 @@ class JitsiFactory {
}; };
document.head.appendChild(jitsiScript); document.head.appendChild(jitsiScript);
cancel(() => {
jitsiScript.remove();
});
}); });
} }
} }

View file

@ -13,5 +13,6 @@ export enum DivImportance {
} }
export const ON_ACTION_TRIGGER_BUTTON = "onaction"; export const ON_ACTION_TRIGGER_BUTTON = "onaction";
export const ON_ICON_TRIGGER_BUTTON = "onicon";
export type Box = { xStart: number; yStart: number; xEnd: number; yEnd: number }; export type Box = { xStart: number; yStart: number; xEnd: number; yEnd: number };

View file

@ -14,7 +14,7 @@ import warning from "./warning";
import woka from "./woka"; import woka from "./woka";
const de_DE: Translation = { const de_DE: Translation = {
...en_US, ...(en_US as Translation),
language: "Deutsch", language: "Deutsch",
country: "Deutschland", country: "Deutschland",
audio, audio,

View file

@ -70,6 +70,7 @@ const menu: NonNullable<Translation["menu"]> = {
description: "Link zu diesem Raum teilen!", description: "Link zu diesem Raum teilen!",
copy: "Kopieren", copy: "Kopieren",
share: "Teilen", share: "Teilen",
walk_automatically_to_position: "Walk automatically to my position",
}, },
globalMessage: { globalMessage: {
text: "Text", text: "Text",

View file

@ -11,6 +11,7 @@ import menu from "./menu";
import report from "./report"; import report from "./report";
import warning from "./warning"; import warning from "./warning";
import emoji from "./emoji"; import emoji from "./emoji";
import trigger from "./trigger";
const en_US: BaseTranslation = { const en_US: BaseTranslation = {
language: "English", language: "English",
@ -27,6 +28,7 @@ const en_US: BaseTranslation = {
report, report,
warning, warning,
emoji, emoji,
trigger,
}; };
export default en_US; export default en_US;

View file

@ -70,6 +70,7 @@ const menu: BaseTranslation = {
description: "Share the link of the room!", description: "Share the link of the room!",
copy: "Copy", copy: "Copy",
share: "Share", share: "Share",
walk_automatically_to_position: "Walk automatically to my position",
}, },
globalMessage: { globalMessage: {
text: "Text", text: "Text",

View file

@ -0,0 +1,9 @@
import type { BaseTranslation } from "../i18n-types";
const trigger: BaseTranslation = {
cowebsite: "Press SPACE or touch here to open web site",
jitsiRoom: "Press SPACE or touch here to enter Jitsi Meet room",
newTab: "Press SPACE or touch here to open web site in new tab",
};
export default trigger;

View file

@ -12,9 +12,10 @@ import menu from "./menu";
import report from "./report"; import report from "./report";
import warning from "./warning"; import warning from "./warning";
import woka from "./woka"; import woka from "./woka";
import trigger from "./trigger";
const fr_FR: Translation = { const fr_FR: Translation = {
...en_US, ...(en_US as Translation),
language: "Français", language: "Français",
country: "France", country: "France",
audio, audio,
@ -29,6 +30,7 @@ const fr_FR: Translation = {
report, report,
warning, warning,
emoji, emoji,
trigger,
}; };
export default fr_FR; export default fr_FR;

View file

@ -63,13 +63,14 @@ const menu: NonNullable<Translation["menu"]> = {
}, },
fullscreen: "Plein écran", fullscreen: "Plein écran",
notifications: "Notifications", notifications: "Notifications",
cowebsiteTrigger: "Demander toujours avant d'ouvrir des sites web et des salles de réunion Jitsi", cowebsiteTrigger: "Demander toujours avant d'ouvrir des sites web et des salles de conférence Jitsi",
ignoreFollowRequest: "Ignorer les demandes de suivi des autres utilisateurs", ignoreFollowRequest: "Ignorer les demandes de suivi des autres utilisateurs",
}, },
invite: { invite: {
description: "Partager le lien de la salle!", description: "Partager le lien de la salle!",
copy: "Copier", copy: "Copier",
share: "Partager", share: "Partager",
walk_automatically_to_position: "Marcher automatiquement jusqu'à ma position",
}, },
globalMessage: { globalMessage: {
text: "Texte", text: "Texte",

View file

@ -0,0 +1,9 @@
import type { Translation } from "../i18n-types";
const trigger: NonNullable<Translation["trigger"]> = {
cowebsite: "Appuyez sur ESPACE ou ici pour ouvrir le site Web",
jitsiRoom: "Appuyez sur ESPACE ou ici pour entrer dans la salle conférence Jitsi",
newTab: "Appuyez sur ESPACE ou ici pour ouvrir le site Web dans un nouvel onglet",
};
export default trigger;

View file

@ -47,6 +47,11 @@
"name":"openWebsiteTrigger", "name":"openWebsiteTrigger",
"type":"string", "type":"string",
"value":"onaction" "value":"onaction"
},
{
"name":"openWebsiteWidth",
"type":"int",
"value":100
}], }],
"type":"tilelayer", "type":"tilelayer",
"visible":true, "visible":true,

View file

@ -0,0 +1,197 @@
{ "compressionlevel":-1,
"height":30,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":30,
"id":1,
"name":"floor",
"opacity":1,
"properties":[
{
"name":"openWebsite",
"type":"string",
"value":"script.php"
},
{
"name":"openWebsiteAllowApi",
"type":"bool",
"value":true
}],
"type":"tilelayer",
"visible":true,
"width":30,
"x":0,
"y":0
},
{
"data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 6, 6, 6, 6, 6, 0, 0, 6, 6, 6, 6, 6, 6, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 0, 0, 17, 17, 17, 17, 17, 17, 17, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 28, 28, 28, 0, 0, 28, 28, 28, 28, 28, 28, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":30,
"id":6,
"name":"furnitures",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":30,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":30,
"id":8,
"name":"closedPath",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":30,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":30,
"id":2,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":30,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":101.5,
"id":11,
"name":"",
"rotation":0,
"text":
{
"text":"You should be able to get here only if the path isn't blocked",
"wrap":true
},
"type":"",
"visible":true,
"width":383,
"x":81.226595249185,
"y":297.048206800186
},
{
"height":101.5,
"id":12,
"name":"",
"rotation":0,
"text":
{
"text":"Try to move to the next room by using right-click \/ tap movement. Click \"Toggle Door\" button to block \/ make access.",
"wrap":true
},
"type":"",
"visible":true,
"width":383,
"x":81,
"y":99.75
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":9,
"nextobjectid":13,
"orientation":"orthogonal",
"properties":[
{
"name":"openWebsite",
"type":"string",
"value":"script.php"
},
{
"name":"openWebsiteAllowApi",
"type":"bool",
"value":true
}],
"renderorder":"right-down",
"tiledversion":"1.7.2",
"tileheight":32,
"tilesets":[
{
"columns":11,
"firstgid":1,
"image":"..\/tileset1.png",
"imageheight":352,
"imagewidth":352,
"margin":0,
"name":"tileset1",
"spacing":0,
"tilecount":121,
"tileheight":32,
"tiles":[
{
"id":16,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":17,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":27,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":28,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":72,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":73,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":"1.6",
"width":30
}

View file

@ -0,0 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<script src="<?php echo $_SERVER["FRONT_URL"] ?>/iframe_api.js"></script>
<script>
let closed = true;
window.addEventListener('load', () => {
//@ts-ignore
WA.onInit().then(() => {
console.log('After WA init');
const toogleDoorButton = document.getElementById('toogleDoorButton');
toogleDoorButton.addEventListener('click', async () => {
closed ? WA.room.hideLayer('closedPath') : WA.room.showLayer('closedPath');
closed = !closed;
});
});
})
</script>
</head>
<body>
<button id="toogleDoorButton">Toggle Door</button>
</body>
</html>

View file

@ -32,6 +32,14 @@
<a href="#" class="testLink" data-testmap="jitsi_custom_url.json" target="_blank">Testing jitsiUrl property</a> <a href="#" class="testLink" data-testmap="jitsi_custom_url.json" target="_blank">Testing jitsiUrl property</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-parallax-effect"> Success <input type="radio" name="test-parallax-effect"> Failure <input type="radio" name="test-parallax-effect" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="parallax.json" target="_blank">Test parallax effect</a>
</td>
</tr>
<tr> <tr>
<td> <td>
<input type="radio" name="test-animated-tiles"> Success <input type="radio" name="test-animated-tiles"> Failure <input type="radio" name="test-animated-tiles" checked> Pending <input type="radio" name="test-animated-tiles"> Success <input type="radio" name="test-animated-tiles"> Failure <input type="radio" name="test-animated-tiles" checked> Pending
@ -40,6 +48,14 @@
<a href="#" class="testLink" data-testmap="animated_tiles.json" target="_blank">Test animated tiles</a> <a href="#" class="testLink" data-testmap="animated_tiles.json" target="_blank">Test animated tiles</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-door-map"> Success <input type="radio" name="test-door-map"> Failure <input type="radio" name="test-door-map" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="DoorTest/map.json" target="_blank">Test Doors</a>
</td>
</tr>
<tr> <tr>
<td> <td>
<input type="radio" name="test-start-tile-S1"> Success <input type="radio" name="test-start-tile-S1"> Failure <input type="radio" name="test-start-tile-S1" checked> Pending <input type="radio" name="test-start-tile-S1"> Success <input type="radio" name="test-start-tile-S1"> Failure <input type="radio" name="test-start-tile-S1" checked> Pending

View file

@ -3,7 +3,7 @@
"infinite":false, "infinite":false,
"layers":[ "layers":[
{ {
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
"height":10, "height":10,
"id":1, "id":1,
"name":"floor", "name":"floor",
@ -204,6 +204,71 @@
"width":92.7120717279925, "width":92.7120717279925,
"x":233.848901257569, "x":233.848901257569,
"y":135.845612785282 "y":135.845612785282
},
{
"height":0,
"id":9,
"name":"destination",
"point":true,
"rotation":0,
"type":"",
"visible":true,
"width":0,
"x":207.918025151374,
"y":243.31625523987
},
{
"height":45.0829063809967,
"id":10,
"name":"",
"rotation":0,
"text":
{
"halign":"center",
"text":"destination object",
"wrap":true
},
"type":"",
"visible":true,
"width":83,
"x":167.26,
"y":254.682580344667
},
{
"height":19.6921,
"id":11,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":13,
"text":"...#start&moveTo=destination",
"wrap":true
},
"type":"",
"visible":true,
"width":202.260327899394,
"x":32.2652715416861,
"y":148.51445302748
},
{
"height":19.6921,
"id":12,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":13,
"text":"...#start2&moveTo=200,100",
"wrap":true
},
"type":"",
"visible":true,
"width":202.26,
"x":32.2654354913834,
"y":169.008165183978
}], }],
"opacity":1, "opacity":1,
"type":"objectgroup", "type":"objectgroup",
@ -212,7 +277,7 @@
"y":0 "y":0
}], }],
"nextlayerid":11, "nextlayerid":11,
"nextobjectid":9, "nextobjectid":13,
"orientation":"orthogonal", "orientation":"orthogonal",
"renderorder":"right-down", "renderorder":"right-down",
"tiledversion":"1.7.2", "tiledversion":"1.7.2",

150
maps/tests/parallax.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -79,6 +79,7 @@ export class AuthenticateController extends BaseController {
if (!code && !nonce) { if (!code && !nonce) {
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ ...resUserData, authToken: token })); return res.end(JSON.stringify({ ...resUserData, authToken: token }));
} }
console.error("Token cannot to be check on OpenId provider"); console.error("Token cannot to be check on OpenId provider");
@ -91,6 +92,7 @@ export class AuthenticateController extends BaseController {
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken); const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token })); return res.end(JSON.stringify({ ...resCheckTokenAuth, ...resUserData, authToken: token }));
} catch (err) { } catch (err) {
console.info("User was not connected", err); console.info("User was not connected", err);
@ -121,6 +123,7 @@ export class AuthenticateController extends BaseController {
res.writeStatus("200"); res.writeStatus("200");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
return res.end(JSON.stringify({ ...data, authToken })); return res.end(JSON.stringify({ ...data, authToken }));
} catch (e) { } catch (e) {
console.error("openIDCallback => ERROR", e); console.error("openIDCallback => ERROR", e);
@ -183,6 +186,7 @@ export class AuthenticateController extends BaseController {
const authToken = jwtTokenManager.createAuthToken(email || userUuid); const authToken = jwtTokenManager.createAuthToken(email || userUuid);
res.writeStatus("200 OK"); res.writeStatus("200 OK");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
res.end( res.end(
JSON.stringify({ JSON.stringify({
authToken, authToken,
@ -222,6 +226,7 @@ export class AuthenticateController extends BaseController {
const authToken = jwtTokenManager.createAuthToken(userUuid); const authToken = jwtTokenManager.createAuthToken(userUuid);
res.writeStatus("200 OK"); res.writeStatus("200 OK");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
res.end( res.end(
JSON.stringify({ JSON.stringify({
authToken, authToken,

View file

@ -47,6 +47,7 @@ export class MapController extends BaseController {
if (!match) { if (!match) {
res.writeStatus("404 Not Found"); res.writeStatus("404 Not Found");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
res.end(JSON.stringify({})); res.end(JSON.stringify({}));
return; return;
} }
@ -55,6 +56,7 @@ export class MapController extends BaseController {
res.writeStatus("200 OK"); res.writeStatus("200 OK");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
res.end( res.end(
JSON.stringify({ JSON.stringify({
mapUrl, mapUrl,
@ -106,6 +108,7 @@ export class MapController extends BaseController {
res.writeStatus("200 OK"); res.writeStatus("200 OK");
this.addCorsHeaders(res); this.addCorsHeaders(res);
res.writeHeader('Content-Type', 'application/json');
res.end(JSON.stringify(mapDetails)); res.end(JSON.stringify(mapDetails));
} catch (e) { } catch (e) {
this.errorToResponse(e, res); this.errorToResponse(e, res);

View file

@ -2,6 +2,7 @@
# yarn lockfile v1 # yarn lockfile v1
"husky@^6.0.0": husky@^7.0.1:
"resolved" "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz" version "7.0.4"
"version" "6.0.0" resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==