Merge branch 'develop' of ssh://git.bstly.de:222/_Bastler/partey_workadventure
This commit is contained in:
@@ -15,6 +15,9 @@ export class DebugController {
|
|||||||
(async () => {
|
(async () => {
|
||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
|
if (ADMIN_API_TOKEN === "") {
|
||||||
|
return res.writeStatus("401 Unauthorized").end("No token configured!");
|
||||||
|
}
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIM
|
|||||||
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
||||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "";
|
||||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||||
const JITSI_ISS = process.env.JITSI_ISS || "";
|
const JITSI_ISS = process.env.JITSI_ISS || "";
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
+21
-2
@@ -83,7 +83,8 @@
|
|||||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||||
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
|
||||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
||||||
"START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json"
|
"START_ROOM_URL": "/_/global/maps-"+url+"/starter/map.json",
|
||||||
|
"ICON_URL": "//icon-"+url,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uploader": {
|
"uploader": {
|
||||||
@@ -109,7 +110,15 @@
|
|||||||
"redis": {
|
"redis": {
|
||||||
"image": "redis:6",
|
"image": "redis:6",
|
||||||
"ports": [6379]
|
"ports": [6379]
|
||||||
}
|
},
|
||||||
|
"iconserver": {
|
||||||
|
"image": "matthiasluedtke/iconserver:v3.13.0",
|
||||||
|
"host": {
|
||||||
|
"url": "icon-"+url,
|
||||||
|
"containerPort": 8080,
|
||||||
|
},
|
||||||
|
"ports": [8080]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
k8sextension(k8sConf)::
|
k8sextension(k8sConf)::
|
||||||
@@ -210,6 +219,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
iconserver+: {
|
||||||
|
ingress+: {
|
||||||
|
spec+: {
|
||||||
|
tls+: [{
|
||||||
|
hosts: ["icon-"+url],
|
||||||
|
secretName: "certificate-tls"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-15
@@ -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 = 0): 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.
|
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
|
||||||
You can have only 5 co-wbesites open simultaneously.
|
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);
|
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();
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.8 KiB |
@@ -141,6 +141,12 @@ return [
|
|||||||
'markdown' => 'maps.api-controls',
|
'markdown' => 'maps.api-controls',
|
||||||
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-controls.md',
|
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-controls.md',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'title' => 'Camera',
|
||||||
|
'url' => '/map-building/api-camera.md',
|
||||||
|
'markdown' => 'maps.api-camera',
|
||||||
|
'editUrl' => 'https://github.com/thecodingmachine/workadventure/edit/develop/docs/maps/api-camera.md',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Deprecated',
|
'title' => 'Deprecated',
|
||||||
'url' => '/map-building/api-deprecated.md',
|
'url' => '/map-building/api-deprecated.md',
|
||||||
|
|||||||
@@ -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...
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"tabWidth": 4,
|
"tabWidth": 4
|
||||||
"plugins": ["prettier-plugin-svelte"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
index.html
|
index.html
|
||||||
index.tmpl.html.tmp
|
|
||||||
/js/
|
/js/
|
||||||
|
/fonts/
|
||||||
style.*.css
|
style.*.css
|
||||||
|
!env-config.template.js
|
||||||
|
*.png
|
||||||
|
|||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
window.env = {
|
||||||
|
SKIP_RENDER_OPTIMIZATIONS: '${SKIP_RENDER_OPTIMIZATIONS}',
|
||||||
|
DISABLE_NOTIFICATIONS: '${DISABLE_NOTIFICATIONS}',
|
||||||
|
PUSHER_URL: '${PUSHER_URL}',
|
||||||
|
UPLOADER_URL: '${UPLOADER_URL}',
|
||||||
|
ADMIN_URL: '${ADMIN_URL}',
|
||||||
|
CONTACT_URL: '${CONTACT_URL}',
|
||||||
|
PROFILE_URL: '${PROFILE_URL}',
|
||||||
|
ICON_URL: '${ICON_URL}',
|
||||||
|
DEBUG_MODE: '${DEBUG_MODE}',
|
||||||
|
STUN_SERVER: '${STUN_SERVER}',
|
||||||
|
TURN_SERVER: '${TURN_SERVER}',
|
||||||
|
TURN_USER: '${TURN_USER}',
|
||||||
|
TURN_PASSWORD: '${TURN_PASSWORD}',
|
||||||
|
JITSI_URL: '${JITSI_URL}',
|
||||||
|
JITSI_PRIVATE_MODE: '${JITSI_PRIVATE_MODE}',
|
||||||
|
START_ROOM_URL: '${START_ROOM_URL}',
|
||||||
|
MAX_USERNAME_LENGTH: '${MAX_USERNAME_LENGTH}',
|
||||||
|
MAX_PER_GROUP: '${MAX_PER_GROUP}',
|
||||||
|
DISPLAY_TERMS_OF_USE: '${DISPLAY_TERMS_OF_USE}',
|
||||||
|
POSTHOG_API_KEY: '${POSTHOG_API_KEY}',
|
||||||
|
POSTHOG_URL: '${POSTHOG_URL}',
|
||||||
|
NODE_ENV: '${NODE_ENV}',
|
||||||
|
DISABLE_ANONYMOUS: '${DISABLE_ANONYMOUS}',
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER: '${OPID_LOGIN_SCREEN_PROVIDER}',
|
||||||
|
FALLBACK_LOCALE: '${FALLBACK_LOCALE}',
|
||||||
|
};
|
||||||
Vendored
+68
File diff suppressed because one or more lines are too long
Vendored
-124
@@ -1,124 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport"
|
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
||||||
|
|
||||||
<!-- TRACK CODE -->
|
|
||||||
<!-- END TRACK CODE -->
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="static/images/favicons/apple-icon-57x57.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="60x60" href="static/images/favicons/apple-icon-60x60.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="static/images/favicons/apple-icon-72x72.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href="static/images/favicons/apple-icon-76x76.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="static/images/favicons/apple-icon-114x114.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="120x120" href="static/images/favicons/apple-icon-120x120.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="static/images/favicons/apple-icon-144x144.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href="static/images/favicons/apple-icon-152x152.png">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="static/images/favicons/apple-icon-180x180.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="static/images/favicons/android-icon-192x192.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="static/images/favicons/favicon-32x32.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href="static/images/favicons/favicon-96x96.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="static/images/favicons/favicon-16x16.png">
|
|
||||||
<link rel="manifest" href="static/images/favicons/manifest.json">
|
|
||||||
<meta name="msapplication-TileColor" content="#000000">
|
|
||||||
<meta name="msapplication-TileImage" content="static/images/favicons/ms-icon-144x144.png">
|
|
||||||
<meta name="theme-color" content="#000000">
|
|
||||||
|
|
||||||
<base href="/">
|
|
||||||
|
|
||||||
<title>Partey</title>
|
|
||||||
</head>
|
|
||||||
<body id="body" style="margin: 0; background-color: #000">
|
|
||||||
<div class="main-container" id="main-container">
|
|
||||||
<!-- Create the editor container -->
|
|
||||||
<div id="game" class="game">
|
|
||||||
<div id="cowebsite-container">
|
|
||||||
<div id="cowebsite-container-main">
|
|
||||||
<div id="cowebsite-slot-1">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="cowebsite-container-sub">
|
|
||||||
<div id="cowebsite-slot-2">
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="actions-move">
|
|
||||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="cowebsite-slot-3">
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="actions-move">
|
|
||||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="cowebsite-slot-4">
|
|
||||||
<div class="overlay">
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" title="Close" class="nes-btn is-error close">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="actions-move">
|
|
||||||
<button type="button" title="Expand" class="nes-btn is-primary expand">></button>
|
|
||||||
<button type="button" title="Hightlight" class="nes-btn is-secondary hightlight">Ξ</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="svelte-overlay"></div>
|
|
||||||
<div id="game-overlay" class="game-overlay">
|
|
||||||
<div id="main-section" class="main-section">
|
|
||||||
</div>
|
|
||||||
<aside id="sidebar" class="sidebar">
|
|
||||||
</aside>
|
|
||||||
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite" class="cowebsite hidden">
|
|
||||||
<aside id="cowebsite-aside" class="noselect">
|
|
||||||
<div id="cowebsite-aside-buttons">
|
|
||||||
<button class="top-right-btn nes-btn is-error" id="cowebsite-close" alt="close all co-websites">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
<button class="top-right-btn nes-btn is-primary" id="cowebsite-fullscreen" alt="fullscreen mode">
|
|
||||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/fullscreen-exit.svg"/>
|
|
||||||
<img id="cowebsite-fullscreen-open" src="resources/logos/fullscreen.svg"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite-aside-holder">
|
|
||||||
<img src="/static/images/menu.svg" alt="hold to resize"/>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite-sub-icons"></div>
|
|
||||||
</aside>
|
|
||||||
<main id="cowebsite-slot-0"></main>
|
|
||||||
</div>
|
|
||||||
<div id="cowebsite-buffer"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
|
||||||
</div>
|
|
||||||
<audio id="report-message">
|
|
||||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
|
||||||
</audio>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><defs><image width="12" height="14" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAOAQMAAAAhc2+vAAAAAXNSR0IB2cksfwAAAAZQTFRFAAAA////pdmf3QAAAAJ0Uk5TAP9bkSK1AAAAJUlEQVR4nGNgOMDAoMDw/wMDCDgwMDQwQIAASBgE/j8ACRswAACLjwYPIknTggAAAABJRU5ErkJggg=="/><image width="12" height="12" id="img2" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMAgMAAAArG7R0AAAAAXNSR0IB2cksfwAAAAlQTFRFAAAA/////wAAzV63nAAAAAN0Uk5TAP8BHlUJOgAAACJJREFUeJxjYGAQYGBgYGEIDQ0F0xA+BwMDEi80NASqigEAOD8CVqGVgwsAAAAASUVORK5CYII="/></defs><style></style><use href="#img1" x="2" y="1" /><use href="#img2" x="2" y="2" /></svg>
|
||||||
|
After Width: | Height: | Size: 717 B |
Vendored
+49
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="452.388px" height="452.388px" viewBox="0 0 452.388 452.388" style="enable-background:new 0 0 452.388 452.388;"
|
||||||
|
xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g id="Layer_8_38_">
|
||||||
|
<path d="M441.677,43.643H10.687C4.785,43.643,0,48.427,0,54.329v297.425c0,5.898,4.785,10.676,10.687,10.676h162.069v25.631
|
||||||
|
c0,0.38,0.074,0.722,0.112,1.089h-23.257c-5.407,0-9.796,4.389-9.796,9.795c0,5.408,4.389,9.801,9.796,9.801h158.506
|
||||||
|
c5.406,0,9.795-4.389,9.795-9.801c0-5.406-4.389-9.795-9.795-9.795h-23.256c0.032-0.355,0.115-0.709,0.115-1.089V362.43H441.7
|
||||||
|
c5.898,0,10.688-4.782,10.688-10.676V54.329C452.37,48.427,447.589,43.643,441.677,43.643z M422.089,305.133
|
||||||
|
c0,5.903-4.784,10.687-10.683,10.687H40.96c-5.898,0-10.684-4.783-10.684-10.687V79.615c0-5.898,4.786-10.684,10.684-10.684
|
||||||
|
h370.446c5.898,0,10.683,4.785,10.683,10.684V305.133z M303.942,290.648H154.025c0-29.872,17.472-55.661,42.753-67.706
|
||||||
|
c-15.987-10.501-26.546-28.571-26.546-49.13c0-32.449,26.306-58.755,58.755-58.755c32.448,0,58.753,26.307,58.753,58.755
|
||||||
|
c0,20.553-10.562,38.629-26.545,49.13C286.475,234.987,303.942,260.781,303.942,290.648z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
+5
-2
@@ -15,6 +15,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||||
"@typescript-eslint/parser": "^5.6.0",
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
"css-loader": "^5.2.4",
|
"css-loader": "^5.2.4",
|
||||||
|
"css-minimizer-webpack-plugin": "^3.3.1",
|
||||||
"eslint": "^8.4.1",
|
"eslint": "^8.4.1",
|
||||||
"eslint-plugin-svelte3": "^3.2.1",
|
"eslint-plugin-svelte3": "^3.2.1",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
"fork-ts-checker-webpack-plugin": "^6.5.0",
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
"@types/simple-peer": "^9.11.1",
|
"@types/simple-peer": "^9.11.1",
|
||||||
"@types/socket.io-client": "^1.4.32",
|
"@types/socket.io-client": "^1.4.32",
|
||||||
"axios": "^0.21.2",
|
"axios": "^0.21.2",
|
||||||
|
"cancelable-promise": "^4.2.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"deep-copy-ts": "^0.5.0",
|
"deep-copy-ts": "^0.5.0",
|
||||||
"easystarjs": "^0.4.4",
|
"easystarjs": "^0.4.4",
|
||||||
@@ -71,7 +73,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 +86,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": [
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ 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),
|
||||||
|
lazy: tg.isOptional(tg.isBoolean),
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
export const isCoWebsite = new tg.IsInterface()
|
export const isCoWebsite = new tg.IsInterface()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
id: tg.isString,
|
id: tg.isString,
|
||||||
position: tg.isNumber,
|
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
|
import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
|
||||||
|
|
||||||
export class CoWebsite {
|
export class CoWebsite {
|
||||||
constructor(private readonly id: string, public readonly position: number) {}
|
constructor(private readonly id: string) {}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
return queryWorkadventure({
|
return queryWorkadventure({
|
||||||
@@ -41,17 +41,28 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async openCoWebSite(url: string, allowApi?: boolean, allowPolicy?: string, position?: number): Promise<CoWebsite> {
|
async openCoWebSite(
|
||||||
|
url: string,
|
||||||
|
allowApi?: boolean,
|
||||||
|
allowPolicy?: string,
|
||||||
|
widthPercent?: number,
|
||||||
|
position?: number,
|
||||||
|
closable?: boolean,
|
||||||
|
lazy?: boolean
|
||||||
|
): Promise<CoWebsite> {
|
||||||
const result = await queryWorkadventure({
|
const result = await queryWorkadventure({
|
||||||
type: "openCoWebsite",
|
type: "openCoWebsite",
|
||||||
data: {
|
data: {
|
||||||
url,
|
url,
|
||||||
allowApi,
|
allowApi,
|
||||||
allowPolicy,
|
allowPolicy,
|
||||||
|
widthPercent,
|
||||||
position,
|
position,
|
||||||
|
closable,
|
||||||
|
lazy,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return new CoWebsite(result.id, result.position);
|
return new CoWebsite(result.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCoWebSites(): Promise<CoWebsite[]> {
|
async getCoWebSites(): Promise<CoWebsite[]> {
|
||||||
@@ -59,7 +70,7 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
|
|||||||
type: "getCoWebsites",
|
type: "getCoWebsites",
|
||||||
data: undefined,
|
data: undefined,
|
||||||
});
|
});
|
||||||
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id, cowebsiteEvent.position));
|
return result.map((cowebsiteEvent) => new CoWebsite(cowebsiteEvent.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||||
|
import { onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import type { Unsubscriber } from "svelte/store";
|
||||||
|
import type { ActionsMenuData } from "../../Stores/ActionsMenuStore";
|
||||||
|
|
||||||
|
let actionsMenuData: ActionsMenuData | undefined;
|
||||||
|
|
||||||
|
let actionsMenuStoreUnsubscriber: Unsubscriber | null;
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
closeActionsMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeActionsMenu() {
|
||||||
|
actionsMenuStore.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => {
|
||||||
|
actionsMenuData = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (actionsMenuStoreUnsubscriber) {
|
||||||
|
actionsMenuStoreUnsubscriber();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={onKeyDown} />
|
||||||
|
|
||||||
|
{#if actionsMenuData}
|
||||||
|
<div class="actions-menu nes-container is-rounded">
|
||||||
|
<button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>×</button>
|
||||||
|
<h2>{actionsMenuData.playerName}</h2>
|
||||||
|
<div class="actions">
|
||||||
|
{#each [...actionsMenuData.actions] as { actionName, callback }}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="nes-btn"
|
||||||
|
on:click|preventDefault={() => {
|
||||||
|
callback();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{actionName}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.actions-menu {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 260px !important;
|
||||||
|
height: max-content !important;
|
||||||
|
max-height: 40vh;
|
||||||
|
margin-top: 200px;
|
||||||
|
|
||||||
|
pointer-events: auto;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
background-color: #333333;
|
||||||
|
color: whitesmoke;
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
max-height: calc(100% - 50px);
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
.nes-btn.is-error.close {
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+41
-155
@@ -1,166 +1,52 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
|
||||||
import { menuIconVisiblilityStore, menuVisiblilityStore } from "../Stores/MenuStore";
|
|
||||||
import { emoteMenuStore } from "../Stores/EmoteStore";
|
|
||||||
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
|
||||||
import CameraControls from "./CameraControls.svelte";
|
|
||||||
import MyCamera from "./MyCamera.svelte";
|
|
||||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
|
||||||
import { selectCompanionSceneVisibleStore } from "../Stores/SelectCompanionStore";
|
|
||||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
|
||||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
|
||||||
import { customCharacterSceneVisibleStore } from "../Stores/CustomCharacterStore";
|
|
||||||
import { errorStore } from "../Stores/ErrorStore";
|
|
||||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
|
||||||
import LoginScene from "./Login/LoginScene.svelte";
|
|
||||||
import Chat from "./Chat/Chat.svelte";
|
|
||||||
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
|
||||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
|
||||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
|
||||||
import { requestVisitCardsStore } from "../Stores/GameStore";
|
|
||||||
|
|
||||||
import type { Game } from "../Phaser/Game/Game";
|
import type { Game } from "../Phaser/Game/Game";
|
||||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||||
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
import { customCharacterSceneVisibleStore } from "../Stores/CustomCharacterStore";
|
||||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
import { errorStore } from "../Stores/ErrorStore";
|
||||||
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
import { loginSceneVisibleStore } from "../Stores/LoginSceneStore";
|
||||||
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
import { enableCameraSceneVisibilityStore } from "../Stores/MediaStore";
|
||||||
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
import { selectCompanionSceneVisibleStore } from "../Stores/SelectCompanionStore";
|
||||||
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
import Chat from "./Chat/Chat.svelte";
|
||||||
|
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||||
|
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||||
|
import LoginScene from "./Login/LoginScene.svelte";
|
||||||
|
import MainLayout from "./MainLayout.svelte";
|
||||||
|
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||||
|
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||||
import Menu from "./Menu/Menu.svelte";
|
|
||||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
|
||||||
import VideoOverlay from "./Video/VideoOverlay.svelte";
|
|
||||||
import { gameOverlayVisibilityStore } from "../Stores/GameOverlayStoreVisibility";
|
|
||||||
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
|
||||||
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
|
||||||
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
|
||||||
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
|
||||||
import { warningContainerStore } from "../Stores/MenuStore";
|
|
||||||
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
|
||||||
import { layoutManagerVisibilityStore } from "../Stores/LayoutManagerStore";
|
|
||||||
import LayoutManager from "./LayoutManager/LayoutManager.svelte";
|
|
||||||
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
|
||||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
|
||||||
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
|
||||||
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
|
||||||
import { followStateStore } from "../Stores/FollowStore";
|
|
||||||
import { peerStore } from "../Stores/PeerStore";
|
|
||||||
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
|
||||||
|
|
||||||
export let game: Game;
|
export let game: Game;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
{#if $errorStore.length > 0}
|
||||||
{#if $loginSceneVisibleStore}
|
<div>
|
||||||
<div class="scrollable">
|
<ErrorDialog />
|
||||||
<LoginScene {game} />
|
</div>
|
||||||
</div>
|
{:else if $loginSceneVisibleStore}
|
||||||
{/if}
|
<div class="scrollable">
|
||||||
{#if $selectCharacterSceneVisibleStore}
|
<LoginScene {game} />
|
||||||
<div>
|
</div>
|
||||||
<SelectCharacterScene {game} />
|
{:else if $selectCharacterSceneVisibleStore}
|
||||||
</div>
|
<div>
|
||||||
{/if}
|
<SelectCharacterScene {game} />
|
||||||
{#if $customCharacterSceneVisibleStore}
|
</div>
|
||||||
<div>
|
{:else if $customCharacterSceneVisibleStore}
|
||||||
<CustomCharacterScene {game} />
|
<div>
|
||||||
</div>
|
<CustomCharacterScene {game} />
|
||||||
{/if}
|
</div>
|
||||||
{#if $selectCompanionSceneVisibleStore}
|
{:else if $selectCompanionSceneVisibleStore}
|
||||||
<div>
|
<div>
|
||||||
<SelectCompanionScene {game} />
|
<SelectCompanionScene {game} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{:else if $enableCameraSceneVisibilityStore}
|
||||||
{#if $enableCameraSceneVisibilityStore}
|
<div class="scrollable">
|
||||||
<div class="scrollable">
|
<EnableCameraScene {game} />
|
||||||
<EnableCameraScene {game} />
|
</div>
|
||||||
</div>
|
{:else}
|
||||||
{/if}
|
<MainLayout />
|
||||||
{#if $banMessageStore.length > 0}
|
|
||||||
<div>
|
|
||||||
<BanMessageContainer />
|
|
||||||
</div>
|
|
||||||
{:else if $textMessageStore.length > 0}
|
|
||||||
<div>
|
|
||||||
<TextMessageContainer />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $soundPlayingStore}
|
|
||||||
<div>
|
|
||||||
<AudioPlaying url={$soundPlayingStore} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $audioManagerVisibilityStore}
|
|
||||||
<div>
|
|
||||||
<AudioManager />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $layoutManagerVisibilityStore}
|
|
||||||
<div>
|
|
||||||
<LayoutManager />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $showReportScreenStore !== userReportEmpty}
|
|
||||||
<div>
|
|
||||||
<ReportMenu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
|
||||||
<div>
|
|
||||||
<FollowMenu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $menuIconVisiblilityStore}
|
|
||||||
<div>
|
|
||||||
<MenuIcon />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $menuVisiblilityStore}
|
|
||||||
<div>
|
|
||||||
<Menu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $emoteMenuStore}
|
|
||||||
<div>
|
|
||||||
<EmoteMenu />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $gameOverlayVisibilityStore}
|
|
||||||
<div>
|
|
||||||
<VideoOverlay />
|
|
||||||
<MyCamera />
|
|
||||||
<CameraControls />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $helpCameraSettingsVisibleStore}
|
|
||||||
<div>
|
|
||||||
<HelpCameraSettingsPopup />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $showLimitRoomModalStore}
|
|
||||||
<div>
|
|
||||||
<LimitRoomModal />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $showShareLinkMapModalStore}
|
|
||||||
<div>
|
|
||||||
<ShareLinkMapModal />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $requestVisitCardsStore}
|
|
||||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
|
||||||
{/if}
|
|
||||||
{#if $errorStore.length > 0}
|
|
||||||
<div>
|
|
||||||
<ErrorDialog />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if $chatVisibilityStore}
|
{#if $chatVisibilityStore}
|
||||||
<Chat />
|
<Chat />
|
||||||
{/if}
|
{/if}
|
||||||
{#if $warningContainerStore}
|
{/if}
|
||||||
<WarningContainer />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -157,13 +157,16 @@
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.main-audio-manager.nes-container.is-rounded {
|
div.main-audio-manager.nes-container.is-rounded {
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 1%;
|
||||||
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen
|
max-height: clamp(150px, 10vh, 15vh); //replace @media for small screen
|
||||||
width: clamp(200px, 15vw, 15vw);
|
width: clamp(200px, 15vw, 15vw);
|
||||||
padding: 3px 3px;
|
padding: 3px 3px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 550;
|
||||||
|
|
||||||
background-color: rgb(0, 0, 0, 0.5);
|
background-color: rgb(0, 0, 0, 0.5);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -9,10 +9,15 @@
|
|||||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||||
import layoutPresentationImg from "./images/layout-presentation.svg";
|
import layoutPresentationImg from "./images/layout-presentation.svg";
|
||||||
import layoutChatImg from "./images/layout-chat.svg";
|
import layoutChatImg from "./images/layout-chat.svg";
|
||||||
import { layoutModeStore } from "../Stores/StreamableCollectionStore";
|
import followImg from "./images/follow.svg";
|
||||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||||
import { peerStore } from "../Stores/PeerStore";
|
import { peerStore } from "../Stores/PeerStore";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
|
import { embedScreenLayout } from "../Stores/EmbedScreensStore";
|
||||||
|
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
|
||||||
|
import { gameManager } from "../Phaser/Game/GameManager";
|
||||||
|
|
||||||
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
|
||||||
function screenSharingClick(): void {
|
function screenSharingClick(): void {
|
||||||
if (isSilent) return;
|
if (isSilent) return;
|
||||||
@@ -42,10 +47,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function switchLayoutMode() {
|
function switchLayoutMode() {
|
||||||
if ($layoutModeStore === LayoutMode.Presentation) {
|
if ($embedScreenLayout === LayoutMode.Presentation) {
|
||||||
$layoutModeStore = LayoutMode.VideoChat;
|
$embedScreenLayout = LayoutMode.VideoChat;
|
||||||
} else {
|
} else {
|
||||||
$layoutModeStore = LayoutMode.Presentation;
|
$embedScreenLayout = LayoutMode.Presentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function followClick() {
|
||||||
|
switch ($followStateStore) {
|
||||||
|
case "off":
|
||||||
|
gameScene.connection?.emitFollowRequest();
|
||||||
|
followRoleStore.set("leader");
|
||||||
|
followStateStore.set("active");
|
||||||
|
break;
|
||||||
|
case "requesting":
|
||||||
|
case "active":
|
||||||
|
case "ending":
|
||||||
|
gameScene.connection?.emitFollowAbort();
|
||||||
|
followUsersStore.stopFollowing();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,41 +77,156 @@
|
|||||||
onDestroy(unsubscribeIsSilent);
|
onDestroy(unsubscribeIsSilent);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="btn-cam-action">
|
||||||
<div class="btn-cam-action">
|
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
||||||
<div class="btn-layout nes-btn is-dark" on:click={switchLayoutMode} class:hide={$peerStore.size === 0}>
|
{#if $embedScreenLayout === LayoutMode.Presentation}
|
||||||
{#if $layoutModeStore === LayoutMode.Presentation}
|
<img class="noselect" src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode" />
|
||||||
<img src={layoutPresentationImg} style="padding: 2px" alt="Switch to mosaic mode" />
|
{:else}
|
||||||
{:else}
|
<img class="noselect" src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode" />
|
||||||
<img src={layoutChatImg} style="padding: 2px" alt="Switch to presentation mode" />
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="btn-monitor nes-btn is-dark"
|
class="btn-follow nes-btn is-dark"
|
||||||
on:click={screenSharingClick}
|
class:hide={($peerStore.size === 0 && $followStateStore === "off") || isSilent}
|
||||||
class:hide={!$screenSharingAvailableStore || isSilent}
|
class:disabled={$followStateStore !== "off"}
|
||||||
class:enabled={$requestedScreenSharingState}
|
on:click={followClick}
|
||||||
>
|
>
|
||||||
{#if $requestedScreenSharingState && !isSilent}
|
<img class="noselect" src={followImg} alt="" />
|
||||||
<img src={monitorImg} alt="Start screen sharing" />
|
</div>
|
||||||
{:else}
|
|
||||||
<img src={monitorCloseImg} alt="Stop screen sharing" />
|
<div
|
||||||
{/if}
|
class="btn-monitor nes-btn is-dark"
|
||||||
</div>
|
on:click={screenSharingClick}
|
||||||
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState ||
|
class:hide={!$screenSharingAvailableStore || isSilent}
|
||||||
isSilent}>
|
class:enabled={$requestedScreenSharingState}
|
||||||
{#if $requestedCameraState && !isSilent}
|
>
|
||||||
<img src={cinemaImg} alt="Turn on webcam" />
|
{#if $requestedScreenSharingState && !isSilent}
|
||||||
{:else}
|
<img class="noselect" src={monitorImg} alt="Start screen sharing" />
|
||||||
<img src={cinemaCloseImg} alt="Turn off webcam" />
|
{:else}
|
||||||
{/if}
|
<img class="noselect" src={monitorCloseImg} alt="Stop screen sharing" />
|
||||||
</div>
|
{/if}
|
||||||
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
</div>
|
||||||
{#if $requestedMicrophoneState && !isSilent}
|
|
||||||
<img src={microphoneImg} alt="Turn on microphone" />
|
<div class="btn-video nes-btn is-dark" on:click={cameraClick} class:disabled={!$requestedCameraState || isSilent}>
|
||||||
{:else}
|
{#if $requestedCameraState && !isSilent}
|
||||||
<img src={microphoneCloseImg} alt="Turn off microphone" />
|
<img class="noselect" src={cinemaImg} alt="Turn on webcam" />
|
||||||
{/if}
|
{:else}
|
||||||
</div>
|
<img class="noselect" src={cinemaCloseImg} alt="Turn off webcam" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-micro nes-btn is-dark" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState || isSilent}>
|
||||||
|
{#if $requestedMicrophoneState && !isSilent}
|
||||||
|
<img class="noselect" src={microphoneImg} alt="Turn on microphone" />
|
||||||
|
{:else}
|
||||||
|
<img class="noselect" src={microphoneCloseImg} alt="Turn off microphone" />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
.btn-cam-action {
|
||||||
|
pointer-events: all;
|
||||||
|
position: absolute;
|
||||||
|
display: inline-flex;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 15px;
|
||||||
|
width: 360px;
|
||||||
|
height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
z-index: 251;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
div.hide {
|
||||||
|
transform: translateY(60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*btn animation*/
|
||||||
|
.btn-cam-action div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
transform: translateY(15px);
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
transition: all 0.3s;
|
||||||
|
margin: 0 4%;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
transform: translateY(60px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-cam-action div.disabled {
|
||||||
|
background: #d75555;
|
||||||
|
}
|
||||||
|
.btn-cam-action div.enabled {
|
||||||
|
background: #73c973;
|
||||||
|
}
|
||||||
|
.btn-cam-action:hover div {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
.btn-cam-action div:hover {
|
||||||
|
background: #407cf7;
|
||||||
|
box-shadow: 4px 4px 48px #666;
|
||||||
|
transition: 120ms;
|
||||||
|
}
|
||||||
|
.btn-micro {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.btn-video {
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.25s;
|
||||||
|
}
|
||||||
|
.btn-monitor {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.btn-layout {
|
||||||
|
pointer-events: auto;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.btn-cam-action div img {
|
||||||
|
height: 22px;
|
||||||
|
width: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-follow {
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: none) {
|
||||||
|
/**
|
||||||
|
* If we cannot hover over elements, let's display camera button in full.
|
||||||
|
*/
|
||||||
|
.btn-cam-action {
|
||||||
|
div {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.btn-cam-action {
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 40%;
|
||||||
|
max-height: 40px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
width: 20%;
|
||||||
|
max-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -42,9 +42,8 @@
|
|||||||
|
|
||||||
<svelte:window on:keydown={onKeyDown} on:click={onClick} />
|
<svelte:window on:keydown={onKeyDown} on:click={onClick} />
|
||||||
|
|
||||||
<aside class="chatWindow nes-container is-rounded is-dark" transition:fly={{ x: -1000, duration: 500 }}
|
<aside class="chatWindow nes-container is-rounded is-dark" transition:fly={{ x: -1000, duration: 500 }} bind:this={chatWindowElement}>
|
||||||
bind:this={chatWindowElement}>
|
<p class="close-icon noselect" on:click={closeChat}>×</p>
|
||||||
<p class="close-icon" on:click={closeChat}>×</p>
|
|
||||||
<section class="messagesList" bind:this={listDom}>
|
<section class="messagesList" bind:this={listDom}>
|
||||||
<ul>
|
<ul>
|
||||||
<li><p class="system-text">{$LL.chat.intro()}</p></li>
|
<li><p class="system-text">{$LL.chat.intro()}</p></li>
|
||||||
@@ -78,7 +77,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
aside.chatWindow {
|
aside.chatWindow {
|
||||||
z-index: 100;
|
z-index: 1000;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -83,6 +83,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.customCharacterScene {
|
form.customCharacterScene {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -129,7 +131,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
form.customCharacterScene button.customCharacterSceneButtonLeft {
|
form.customCharacterScene button.customCharacterSceneButtonLeft {
|
||||||
left: 5vw;
|
left: 5vw;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
||||||
|
import MediaBox from "../Video/MediaBox.svelte";
|
||||||
|
|
||||||
|
export let highlightedEmbedScreen: EmbedScreen | null;
|
||||||
|
export let full = false;
|
||||||
|
$: clickable = !full;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<aside class="cameras-container" class:full>
|
||||||
|
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||||
|
{#if !highlightedEmbedScreen || highlightedEmbedScreen.type !== "streamable" || (highlightedEmbedScreen.type === "streamable" && highlightedEmbedScreen.embed !== peer)}
|
||||||
|
<MediaBox streamable={peer} isClickable={clickable} />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cameras-container {
|
||||||
|
flex: 0 0 25%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import { ICON_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
import { mainCoWebsite } from "../../Stores/CoWebsiteStore";
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||||
|
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||||
|
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
|
||||||
|
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||||
|
|
||||||
|
export let index: number;
|
||||||
|
export let coWebsite: CoWebsite;
|
||||||
|
export let vertical: boolean;
|
||||||
|
|
||||||
|
let icon: HTMLImageElement;
|
||||||
|
let iconLoaded = false;
|
||||||
|
let state = coWebsite.getStateSubscriber();
|
||||||
|
let isJitsi: boolean = coWebsite instanceof JitsiCoWebsite;
|
||||||
|
const mainState = coWebsiteManager.getMainStateSubscriber();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
icon.src = isJitsi
|
||||||
|
? "/resources/logos/meet.svg"
|
||||||
|
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
|
||||||
|
icon.alt = coWebsite.getUrl().hostname;
|
||||||
|
icon.onload = () => {
|
||||||
|
iconLoaded = true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function onClick() {
|
||||||
|
if (vertical) {
|
||||||
|
coWebsiteManager.goToMain(coWebsite);
|
||||||
|
} else if ($mainCoWebsite) {
|
||||||
|
if ($mainCoWebsite.getId() === coWebsite.getId()) {
|
||||||
|
if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||||
|
coWebsiteManager.displayMain();
|
||||||
|
} else if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||||
|
coWebsiteManager.goToMain($highlightedEmbedScreen.embed);
|
||||||
|
} else {
|
||||||
|
coWebsiteManager.hideMain();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||||
|
coWebsiteManager.goToMain(coWebsite);
|
||||||
|
coWebsiteManager.displayMain();
|
||||||
|
} else {
|
||||||
|
highlightedEmbedScreen.toggleHighlight({
|
||||||
|
type: "cowebsite",
|
||||||
|
embed: coWebsite,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state === "asleep") {
|
||||||
|
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||||
|
}
|
||||||
|
|
||||||
|
coWebsiteManager.resizeAllIframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isHighlight: boolean = false;
|
||||||
|
let isMain: boolean = false;
|
||||||
|
$: {
|
||||||
|
isMain =
|
||||||
|
$mainState === iframeStates.opened &&
|
||||||
|
$mainCoWebsite !== undefined &&
|
||||||
|
$mainCoWebsite.getId() === coWebsite.getId();
|
||||||
|
isHighlight =
|
||||||
|
$highlightedEmbedScreen !== null &&
|
||||||
|
$highlightedEmbedScreen.type === "cowebsite" &&
|
||||||
|
$highlightedEmbedScreen.embed.getId() === coWebsite.getId();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id={"cowebsite-thumbnail-" + index}
|
||||||
|
class="cowebsite-thumbnail nes-pointer"
|
||||||
|
class:asleep={$state === "asleep"}
|
||||||
|
class:loading={$state === "loading"}
|
||||||
|
class:ready={$state === "ready"}
|
||||||
|
class:displayed={isMain || isHighlight}
|
||||||
|
class:vertical
|
||||||
|
on:click={onClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="cowebsite-icon noselect nes-pointer"
|
||||||
|
class:hide={!iconLoaded}
|
||||||
|
class:jitsi={isJitsi}
|
||||||
|
bind:this={icon}
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
class="cowebsite-icon"
|
||||||
|
class:hide={iconLoaded}
|
||||||
|
style="margin: auto; background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%; shape-rendering: auto;"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
preserveAspectRatio="xMidYMid"
|
||||||
|
>
|
||||||
|
<rect x="19" y="19" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="40" y="19" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.125s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="61" y="19" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.25s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="19" y="40" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.875s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="61" y="40" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.375s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="19" y="61" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.75s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="40" y="61" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.625s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect><rect x="61" y="61" width="20" height="20" fill="#14304c">
|
||||||
|
<animate
|
||||||
|
attributeName="fill"
|
||||||
|
values="#365dff;#14304c;#14304c"
|
||||||
|
keyTimes="0;0.125;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
begin="0.5s"
|
||||||
|
calcMode="discrete"
|
||||||
|
/>
|
||||||
|
</rect>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cowebsite-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
margin: 12px;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 58px;
|
||||||
|
height: 58px;
|
||||||
|
left: -8px;
|
||||||
|
top: -8px;
|
||||||
|
|
||||||
|
margin: 4px;
|
||||||
|
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 4px;
|
||||||
|
border-image-slice: 3;
|
||||||
|
border-image-width: 3;
|
||||||
|
border-image-repeat: stretch;
|
||||||
|
border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M3 1 h1 v1 h-1 z M4 1 h1 v1 h-1 z M2 2 h1 v1 h-1 z M5 2 h1 v1 h-1 z M1 3 h1 v1 h-1 z M6 3 h1 v1 h-1 z M1 4 h1 v1 h-1 z M6 4 h1 v1 h-1 z M2 5 h1 v1 h-1 z M5 5 h1 v1 h-1 z M3 6 h1 v1 h-1 z M4 6 h1 v1 h-1 z" fill="rgb(33,37,41)" /></svg>');
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.vertical) {
|
||||||
|
transition: all 300ms;
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
margin: 7px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cowebsite-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
animation: shake 0.35s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.displayed {
|
||||||
|
&:not(.vertical) {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.asleep {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
--webkit-filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
animation: 2500ms ease-in-out 0s infinite alternate backgroundLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ready {
|
||||||
|
&::before {
|
||||||
|
border-image-source: url('data:image/svg+xml;utf8,<?xml version="1.0" encoding="UTF-8" ?><svg version="1.1" width="8" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M3 1 h1 v1 h-1 z M4 1 h1 v1 h-1 z M2 2 h1 v1 h-1 z M5 2 h1 v1 h-1 z M1 3 h1 v1 h-1 z M6 3 h1 v1 h-1 z M1 4 h1 v1 h-1 z M6 4 h1 v1 h-1 z M2 5 h1 v1 h-1 z M5 5 h1 v1 h-1 z M3 6 h1 v1 h-1 z M4 6 h1 v1 h-1 z" fill="rgb(38, 74, 110)" /></svg>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backgroundLoading {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: #25598e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
20% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
60% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cowebsite-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
object-fit: cover;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.jitsi {
|
||||||
|
filter: invert(100%);
|
||||||
|
-webkit-filter: invert(100%);
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { coWebsites } from "../../Stores/CoWebsiteStore";
|
||||||
|
import CoWebsiteThumbnail from "./CoWebsiteThumbnailSlot.svelte";
|
||||||
|
|
||||||
|
export let vertical = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $coWebsites.length > 0}
|
||||||
|
<div id="cowebsite-thumbnail-container" class:vertical>
|
||||||
|
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.getId())}
|
||||||
|
<CoWebsiteThumbnail {index} {coWebsite} {vertical} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#cowebsite-thumbnail-container {
|
||||||
|
pointer-events: all;
|
||||||
|
height: 100px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
left: 2%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
height: auto !important;
|
||||||
|
width: auto !important;
|
||||||
|
bottom: auto !important;
|
||||||
|
left: auto !important;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import PresentationLayout from "./Layouts/PresentationLayout.svelte";
|
||||||
|
import MozaicLayout from "./Layouts/MozaicLayout.svelte";
|
||||||
|
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
||||||
|
import { embedScreenLayout } from "../../Stores/EmbedScreensStore";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="embedScreensContainer">
|
||||||
|
{#if $embedScreenLayout === LayoutMode.Presentation}
|
||||||
|
<PresentationLayout />
|
||||||
|
{:else}
|
||||||
|
<MozaicLayout />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#embedScreensContainer {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 2%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { highlightedEmbedScreen } from "../../../Stores/EmbedScreensStore";
|
||||||
|
import { streamableCollectionStore } from "../../../Stores/StreamableCollectionStore";
|
||||||
|
import MediaBox from "../../Video/MediaBox.svelte";
|
||||||
|
|
||||||
|
let layoutDom: HTMLDivElement;
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(layoutDom);
|
||||||
|
highlightedEmbedScreen.removeHighlight();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="mozaic-layout" bind:this={layoutDom}>
|
||||||
|
<div
|
||||||
|
class="media-container"
|
||||||
|
class:full-width={$streamableCollectionStore.size === 1 || $streamableCollectionStore.size === 2}
|
||||||
|
class:quarter={$streamableCollectionStore.size === 3 || $streamableCollectionStore.size === 4}
|
||||||
|
>
|
||||||
|
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
||||||
|
<MediaBox
|
||||||
|
streamable={peer}
|
||||||
|
mozaicSolo={$streamableCollectionStore.size === 1}
|
||||||
|
mozaicFullWidth={$streamableCollectionStore.size === 1 || $streamableCollectionStore.size === 2}
|
||||||
|
mozaicQuarter={$streamableCollectionStore.size === 3 || $streamableCollectionStore.size >= 4}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#mozaic-layout {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.media-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 33.3% 33.3% 33.3%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.quarter {
|
||||||
|
grid-template-columns: 50% 50%;
|
||||||
|
grid-template-rows: 50% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { highlightedEmbedScreen } from "../../../Stores/EmbedScreensStore";
|
||||||
|
import CamerasContainer from "../CamerasContainer.svelte";
|
||||||
|
import MediaBox from "../../Video/MediaBox.svelte";
|
||||||
|
import { coWebsiteManager } from "../../../WebRtc/CoWebsiteManager";
|
||||||
|
import { afterUpdate, onMount } from "svelte";
|
||||||
|
import { isMediaBreakpointDown, isMediaBreakpointUp } from "../../../Utils/BreakpointsUtils";
|
||||||
|
import { peerStore } from "../../../Stores/PeerStore";
|
||||||
|
|
||||||
|
function closeCoWebsite() {
|
||||||
|
if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||||
|
if ($highlightedEmbedScreen.embed.isClosable()) {
|
||||||
|
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed);
|
||||||
|
} else {
|
||||||
|
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch((err) => {
|
||||||
|
console.error("Cannot unload co-website", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if ($highlightedEmbedScreen) {
|
||||||
|
coWebsiteManager.resizeAllIframes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let layoutDom: HTMLDivElement;
|
||||||
|
|
||||||
|
let displayCoWebsiteContainer = isMediaBreakpointDown("lg");
|
||||||
|
let displayFullMedias = isMediaBreakpointUp("sm");
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
displayCoWebsiteContainer = isMediaBreakpointDown("lg");
|
||||||
|
displayFullMedias = isMediaBreakpointUp("sm");
|
||||||
|
|
||||||
|
if (!displayCoWebsiteContainer && $highlightedEmbedScreen && $highlightedEmbedScreen.type === "cowebsite") {
|
||||||
|
highlightedEmbedScreen.removeHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (displayFullMedias) {
|
||||||
|
highlightedEmbedScreen.removeHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(layoutDom);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="presentation-layout" bind:this={layoutDom} class:full-medias={displayFullMedias}>
|
||||||
|
{#if displayFullMedias}
|
||||||
|
<div id="full-medias">
|
||||||
|
<CamerasContainer full={true} highlightedEmbedScreen={$highlightedEmbedScreen} />
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div id="embed-left-block" class:full={$peerStore.size === 0}>
|
||||||
|
<div id="main-embed-screen">
|
||||||
|
{#if $highlightedEmbedScreen}
|
||||||
|
{#if $highlightedEmbedScreen.type === "streamable"}
|
||||||
|
{#key $highlightedEmbedScreen.embed.uniqueId}
|
||||||
|
<MediaBox
|
||||||
|
isHightlighted={true}
|
||||||
|
isClickable={true}
|
||||||
|
streamable={$highlightedEmbedScreen.embed}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
{:else if $highlightedEmbedScreen.type === "cowebsite"}
|
||||||
|
{#key $highlightedEmbedScreen.embed.getId()}
|
||||||
|
<div
|
||||||
|
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
|
||||||
|
class="highlighted-cowebsite nes-container is-rounded"
|
||||||
|
>
|
||||||
|
<div class="actions">
|
||||||
|
<button type="button" class="nes-btn is-error close" on:click={closeCoWebsite}
|
||||||
|
>×</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/key}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $peerStore.size > 0}
|
||||||
|
<CamerasContainer highlightedEmbedScreen={$highlightedEmbedScreen} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#presentation-layout {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&.full-medias {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#embed-left-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 0 0 75%;
|
||||||
|
height: 100%;
|
||||||
|
width: 75%;
|
||||||
|
|
||||||
|
&.full {
|
||||||
|
flex: 0 0 98% !important;
|
||||||
|
width: 98% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-embed-screen {
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 3%;
|
||||||
|
|
||||||
|
.highlighted-cowebsite {
|
||||||
|
height: 100% !important;
|
||||||
|
width: 96%;
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
z-index: 200;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: end;
|
||||||
|
gap: 2%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { EmojiButton } from "@joeattardi/emoji-button";
|
import { EmojiButton } from "@joeattardi/emoji-button";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
let emojiContainer: HTMLElement;
|
let emojiContainer: HTMLElement;
|
||||||
let picker: EmojiButton;
|
let picker: EmojiButton;
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"--secondary-text-color": "whitesmoke",
|
"--secondary-text-color": "whitesmoke",
|
||||||
"--category-button-color": "whitesmoke",
|
"--category-button-color": "whitesmoke",
|
||||||
},
|
},
|
||||||
emojisPerRow: isMobile() ? 6 : 8,
|
emojisPerRow: isMediaBreakpointUp("md") ? 6 : 8,
|
||||||
autoFocusSearch: false,
|
autoFocusSearch: false,
|
||||||
style: "native",
|
style: "native",
|
||||||
showPreview: false,
|
showPreview: false,
|
||||||
@@ -86,6 +86,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emote-menu {
|
.emote-menu {
|
||||||
|
|||||||
@@ -127,6 +127,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.enableCameraScene {
|
.enableCameraScene {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
margin: 20px auto 0;
|
margin: 20px auto 0;
|
||||||
@@ -214,7 +216,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
.enableCameraScene h2 {
|
.enableCameraScene h2 {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import followImg from "../images/follow.svg";
|
||||||
|
|
||||||
|
export let hidden: Boolean;
|
||||||
|
|
||||||
|
let cancelButton = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="btn-follow" class:hide={hidden} class:cancel={cancelButton}>
|
||||||
|
<img src={followImg} alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.btn-follow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: solid 0px black;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: #666;
|
||||||
|
box-shadow: 2px 2px 24px #444;
|
||||||
|
border-radius: 48px;
|
||||||
|
transform: translateY(15px);
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
margin: 0 4%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
<!--
|
|
||||||
vim: ft=typescript
|
|
||||||
-->
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import followImg from "../images/follow.svg";
|
|
||||||
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
@@ -11,11 +7,7 @@ vim: ft=typescript
|
|||||||
|
|
||||||
function name(userId: number): string {
|
function name(userId: number): string {
|
||||||
const user = gameScene.MapPlayersByKey.get(userId);
|
const user = gameScene.MapPlayersByKey.get(userId);
|
||||||
return user ? user.PlayerValue : "";
|
return user ? user.playerName : "";
|
||||||
}
|
|
||||||
|
|
||||||
function sendFollowRequest() {
|
|
||||||
gameScene.CurrentPlayer.sendFollowRequest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function acceptFollowRequest() {
|
function acceptFollowRequest() {
|
||||||
@@ -83,7 +75,7 @@ vim: ft=typescript
|
|||||||
|
|
||||||
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
||||||
<div class="interact-status nes-container is-rounded">
|
<div class="interact-status nes-container is-rounded">
|
||||||
<section class="interact-status">
|
<section>
|
||||||
{#if $followRoleStore === "follower"}
|
{#if $followRoleStore === "follower"}
|
||||||
<p>{$LL.follow.interactStatus.following({ leader: name($followUsersStore[0]) })}</p>
|
<p>{$LL.follow.interactStatus.following({ leader: name($followUsersStore[0]) })}</p>
|
||||||
{:else if $followUsersStore.length === 0}
|
{:else if $followUsersStore.length === 0}
|
||||||
@@ -109,48 +101,27 @@ vim: ft=typescript
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $followStateStore === "off"}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="nes-btn is-primary follow-menu-button"
|
|
||||||
on:click|preventDefault={sendFollowRequest}
|
|
||||||
title="Ask others to follow"><img class="background-img" src={followImg} alt="" /></button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if $followStateStore === "active" || $followStateStore === "ending"}
|
|
||||||
{#if $followRoleStore === "follower"}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="nes-btn is-error follow-menu-button"
|
|
||||||
on:click|preventDefault={reset}
|
|
||||||
title="Stop following"><img class="background-img" src={followImg} alt="" /></button
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="nes-btn is-error follow-menu-button"
|
|
||||||
on:click|preventDefault={reset}
|
|
||||||
title="Stop leading the way"><img class="background-img" src={followImg} alt="" /></button
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.nes-container {
|
.nes-container {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.interact-status {
|
.interact-status {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
|
||||||
position: relative;
|
position: absolute;
|
||||||
height: 2.7em;
|
max-height: 2.7em;
|
||||||
width: 40vw;
|
width: 40vw;
|
||||||
top: 87vh;
|
top: 87vh;
|
||||||
margin: auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
z-index: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.interact-menu {
|
div.interact-menu {
|
||||||
@@ -158,10 +129,14 @@ vim: ft=typescript
|
|||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
|
||||||
position: relative;
|
position: absolute;
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
top: 60vh;
|
top: 60vh;
|
||||||
margin: auto;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
z-index: 150;
|
||||||
|
|
||||||
section.interact-menu-title {
|
section.interact-menu-title {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@@ -189,23 +164,16 @@ vim: ft=typescript
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.follow-menu-button {
|
@include media-breakpoint-up(md) {
|
||||||
position: absolute;
|
.interact-status {
|
||||||
bottom: 10px;
|
width: 90vw;
|
||||||
left: 10px;
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
div.interact-status {
|
|
||||||
width: 100vw;
|
|
||||||
top: 78vh;
|
top: 78vh;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.interact-menu {
|
div.interact-menu {
|
||||||
height: 21vh;
|
max-height: 21vh;
|
||||||
width: 100vw;
|
width: 90vw;
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<form
|
<form
|
||||||
class="helpCameraSettings nes-container"
|
class="helpCameraSettings nes-container"
|
||||||
on:submit|preventDefault={close}
|
on:submit|preventDefault={close}
|
||||||
transition:fly={{ y: -900, duration: 500 }}
|
transition:fly={{ y: -50, duration: 500 }}
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<h2>{$LL.camera.help.title()}</h2>
|
<h2>{$LL.camera.help.title()}</h2>
|
||||||
@@ -55,9 +55,12 @@
|
|||||||
background: #eceeee;
|
background: #eceeee;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-top: 10vh;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-top: 4%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
z-index: 600;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -35,9 +35,11 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 40px;
|
bottom: 40px;
|
||||||
margin: 0 auto;
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: clamp(200px, 20vw, 20vw);
|
width: clamp(200px, 20vw, 20vw);
|
||||||
|
z-index: 155;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -45,6 +47,10 @@
|
|||||||
animation: moveMessage 0.5s;
|
animation: moveMessage 0.5s;
|
||||||
animation-iteration-count: infinite;
|
animation-iteration-count: infinite;
|
||||||
animation-timing-function: ease-in-out;
|
animation-timing-function: ease-in-out;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-bottom: 5%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.nes-container {
|
div.nes-container {
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<script lang="typescript">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
||||||
|
import { embedScreenLayout, hasEmbedScreen } from "../Stores/EmbedScreensStore";
|
||||||
|
import { emoteMenuStore } from "../Stores/EmoteStore";
|
||||||
|
import { myCameraVisibilityStore } from "../Stores/MyCameraStoreVisibility";
|
||||||
|
import { requestVisitCardsStore } from "../Stores/GameStore";
|
||||||
|
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
|
||||||
|
import { layoutManagerActionVisibilityStore } from "../Stores/LayoutManagerStore";
|
||||||
|
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
|
||||||
|
import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore";
|
||||||
|
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||||
|
import CameraControls from "./CameraControls.svelte";
|
||||||
|
import EmbedScreensContainer from "./EmbedScreens/EmbedScreensContainer.svelte";
|
||||||
|
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||||
|
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||||
|
import LayoutActionManager from "./LayoutActionManager/LayoutActionManager.svelte";
|
||||||
|
import Menu from "./Menu/Menu.svelte";
|
||||||
|
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||||
|
import MyCamera from "./MyCamera.svelte";
|
||||||
|
import ReportMenu from "./ReportMenu/ReportMenu.svelte";
|
||||||
|
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||||
|
import WarningContainer from "./WarningContainer/WarningContainer.svelte";
|
||||||
|
import { isMediaBreakpointDown, isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
|
||||||
|
import CoWebsitesContainer from "./EmbedScreens/CoWebsitesContainer.svelte";
|
||||||
|
import FollowMenu from "./FollowMenu/FollowMenu.svelte";
|
||||||
|
import { followStateStore } from "../Stores/FollowStore";
|
||||||
|
import { peerStore } from "../Stores/PeerStore";
|
||||||
|
import { banMessageStore } from "../Stores/TypeMessageStore/BanMessageStore";
|
||||||
|
import BanMessageContainer from "./TypeMessage/BanMessageContainer.svelte";
|
||||||
|
import { textMessageStore } from "../Stores/TypeMessageStore/TextMessageStore";
|
||||||
|
import TextMessageContainer from "./TypeMessage/TextMessageContainer.svelte";
|
||||||
|
import { soundPlayingStore } from "../Stores/SoundPlayingStore";
|
||||||
|
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||||
|
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
|
||||||
|
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
|
||||||
|
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
|
||||||
|
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||||
|
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
||||||
|
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
||||||
|
|
||||||
|
let mainLayout: HTMLDivElement;
|
||||||
|
|
||||||
|
let displayCoWebsiteContainerMd = isMediaBreakpointUp("md");
|
||||||
|
let displayCoWebsiteContainerLg = isMediaBreakpointDown("lg");
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
displayCoWebsiteContainerMd = isMediaBreakpointUp("md");
|
||||||
|
displayCoWebsiteContainerLg = isMediaBreakpointDown("lg");
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(mainLayout);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="main-layout" bind:this={mainLayout}>
|
||||||
|
<aside id="main-layout-left-aside">
|
||||||
|
{#if $menuIconVisiblilityStore}
|
||||||
|
<MenuIcon />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $embedScreenLayout === LayoutMode.VideoChat || displayCoWebsiteContainerMd}
|
||||||
|
<CoWebsitesContainer vertical={true} />
|
||||||
|
{/if}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section id="main-layout-main">
|
||||||
|
{#if $menuVisiblilityStore}
|
||||||
|
<Menu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $banMessageStore.length > 0}
|
||||||
|
<BanMessageContainer />
|
||||||
|
{:else if $textMessageStore.length > 0}
|
||||||
|
<TextMessageContainer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $soundPlayingStore}
|
||||||
|
<AudioPlaying url={$soundPlayingStore} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $warningContainerStore}
|
||||||
|
<WarningContainer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $showReportScreenStore !== userReportEmpty}
|
||||||
|
<ReportMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $helpCameraSettingsVisibleStore}
|
||||||
|
<HelpCameraSettingsPopup />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $audioManagerVisibilityStore}
|
||||||
|
<AudioManager />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $showLimitRoomModalStore}
|
||||||
|
<LimitRoomModal />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $showShareLinkMapModalStore}
|
||||||
|
<ShareLinkMapModal />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $followStateStore !== "off" || $peerStore.size > 0}
|
||||||
|
<FollowMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $actionsMenuStore}
|
||||||
|
<ActionsMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $requestVisitCardsStore}
|
||||||
|
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $emoteMenuStore}
|
||||||
|
<EmoteMenu />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if hasEmbedScreen}
|
||||||
|
<EmbedScreensContainer />
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="main-layout-baseline">
|
||||||
|
{#if displayCoWebsiteContainerLg}
|
||||||
|
<CoWebsitesContainer />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $layoutManagerActionVisibilityStore}
|
||||||
|
<LayoutActionManager />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $myCameraVisibilityStore}
|
||||||
|
<MyCamera />
|
||||||
|
<CameraControls />
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
#main-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 120px calc(100% - 120px);
|
||||||
|
grid-template-rows: 80% 20%;
|
||||||
|
|
||||||
|
&-left-aside {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-baseline {
|
||||||
|
grid-column: 1/3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
#main-layout {
|
||||||
|
grid-template-columns: 15% 85%;
|
||||||
|
|
||||||
|
&-left-aside {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
#main-layout {
|
||||||
|
grid-template-columns: 20% 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -100,6 +100,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.string-HTML {
|
.string-HTML {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
@@ -126,7 +128,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.about-room-main {
|
div.about-room-main {
|
||||||
section.container-overflow {
|
section.container-overflow {
|
||||||
height: calc(100% - 120px);
|
height: calc(100% - 120px);
|
||||||
|
|||||||
@@ -67,6 +67,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.global-message-main {
|
div.global-message-main {
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -109,7 +111,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
.global-message-content {
|
.global-message-content {
|
||||||
height: calc(100% - 5px);
|
height: calc(100% - 5px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,29 +44,71 @@
|
|||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="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;
|
||||||
@@ -53,25 +117,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
section.is-mobile {
|
section.is-mobile {
|
||||||
display: none;
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 900px), only screen and (max-height: 600px) {
|
@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>
|
||||||
|
|||||||
@@ -73,6 +73,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 {
|
||||||
@@ -129,6 +130,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
.nes-container {
|
.nes-container {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
@@ -140,11 +143,15 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
top: 10%;
|
top: 4%;
|
||||||
|
|
||||||
position: relative;
|
left: 0;
|
||||||
z-index: 80;
|
right: 0;
|
||||||
margin: auto;
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
z-index: 900;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: var(--size-first-columns-grid) calc(100% - var(--size-first-columns-grid));
|
grid-template-columns: var(--size-first-columns-grid) calc(100% - var(--size-first-columns-grid));
|
||||||
@@ -177,12 +184,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.menu-container-main {
|
div.menu-container-main {
|
||||||
--size-first-columns-grid: 120px;
|
--size-first-columns-grid: 120px;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
top: 55px;
|
top: 55px;
|
||||||
width: 100%;
|
width: 95%;
|
||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
|
|
||||||
div.menu-nav-sidebar {
|
div.menu-nav-sidebar {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
function showMenu() {
|
function showMenu() {
|
||||||
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
menuVisiblilityStore.set(!get(menuVisiblilityStore));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showChat() {
|
function showChat() {
|
||||||
chatVisibilityStore.set(true);
|
chatVisibilityStore.set(true);
|
||||||
}
|
}
|
||||||
@@ -21,68 +22,104 @@
|
|||||||
function register() {
|
function register() {
|
||||||
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
window.open(`${ADMIN_URL}/second-step-register`, "_self");
|
||||||
}
|
}
|
||||||
|
|
||||||
function showInvite() {
|
function showInvite() {
|
||||||
showShareLinkMapModalStore.set(true);
|
showShareLinkMapModalStore.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window />
|
<svelte:window />
|
||||||
|
|
||||||
<main class="menuIcon">
|
<main class="menuIcon noselect">
|
||||||
{#if $limitMapStore}
|
{#if $limitMapStore}
|
||||||
<span class="nes-btn is-dark">
|
<span class="nes-btn is-dark">
|
||||||
<img
|
<img
|
||||||
src={logoInvite}
|
src={logoInvite}
|
||||||
alt={$LL.menu.icon.open.invite()}
|
alt={$LL.menu.icon.open.invite()}
|
||||||
class="nes-pointer"
|
class="nes-pointer"
|
||||||
on:click|preventDefault={showInvite}
|
draggable="false"
|
||||||
/>
|
on:dragstart|preventDefault={noDrag}
|
||||||
</span>
|
on:click|preventDefault={showInvite}
|
||||||
|
/>
|
||||||
<span class="nes-btn is-dark">
|
</span>
|
||||||
<img
|
<span class="nes-btn is-dark">
|
||||||
src={logoRegister}
|
<img
|
||||||
alt={$LL.menu.icon.open.register()}
|
src={logoRegister}
|
||||||
class="nes-pointer"
|
alt={$LL.menu.icon.open.register()}
|
||||||
on:click|preventDefault={register}
|
class="nes-pointer"
|
||||||
/>
|
draggable="false"
|
||||||
</span>
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={register}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="nes-btn is-dark">
|
<span class="nes-btn is-dark">
|
||||||
<img src={logoWA} alt={$LL.menu.icon.open.menu()} class="nes-pointer" on:click|preventDefault={showMenu} />
|
<img
|
||||||
</span>
|
src={logoWA}
|
||||||
<span class="nes-btn is-dark">
|
alt={$LL.menu.icon.open.menu()}
|
||||||
<img src={logoTalk} alt={$LL.menu.icon.open.chat()} class="nes-pointer" on:click|preventDefault={showChat} />
|
class="nes-pointer"
|
||||||
</span>
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={showMenu}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span class="nes-btn is-dark">
|
||||||
|
<img
|
||||||
|
src={logoTalk}
|
||||||
|
alt={$LL.menu.icon.open.chat()}
|
||||||
|
class="nes-pointer"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
on:click|preventDefault={showChat}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.menuIcon {
|
@import "../../../style/breakpoints.scss";
|
||||||
display: inline-grid;
|
|
||||||
z-index: 90;
|
|
||||||
position: relative;
|
|
||||||
margin: 25px;
|
|
||||||
img {
|
|
||||||
pointer-events: auto;
|
|
||||||
width: 24px;
|
|
||||||
padding-top: 0;
|
|
||||||
margin: 3px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menuIcon img:hover{
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 800px),
|
|
||||||
only screen and (max-height: 800px) {
|
|
||||||
.menuIcon {
|
.menuIcon {
|
||||||
margin: 6px;
|
display: inline-grid;
|
||||||
|
z-index: 90;
|
||||||
img {
|
position: relative;
|
||||||
width: 16px;
|
margin: 25px;
|
||||||
}
|
img {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 24px;
|
||||||
|
padding-top: 0;
|
||||||
|
margin: 3px
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.menuIcon img:hover {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.menuIcon {
|
||||||
|
margin-top: 10%;
|
||||||
|
img {
|
||||||
|
pointer-events: auto;
|
||||||
|
width: 60px;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menuIcon img:hover {
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.menuIcon {
|
||||||
|
img {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene";
|
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene";
|
||||||
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
|
||||||
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
|
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
|
||||||
import btnProfileSubMenuIdentity from "../images/btn-menu-profile-identity.svg";
|
|
||||||
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
||||||
import Woka from "../Woka/Woka.svelte";
|
import Woka from "../Woka/Woka.svelte";
|
||||||
import Companion from "../Companion/Companion.svelte";
|
import Companion from "../Companion/Companion.svelte";
|
||||||
@@ -30,12 +29,6 @@
|
|||||||
gameManager.leaveGame(SelectCompanionSceneName, new SelectCompanionScene());
|
gameManager.leaveGame(SelectCompanionSceneName, new SelectCompanionScene());
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditNameScene() {
|
|
||||||
disableMenuStores();
|
|
||||||
loginSceneVisibleStore.set(true);
|
|
||||||
gameManager.leaveGame(LoginSceneName, new LoginScene());
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEditSkinScene() {
|
function openEditSkinScene() {
|
||||||
disableMenuStores();
|
disableMenuStores();
|
||||||
selectCharacterSceneVisibleStore.set(true);
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
@@ -104,6 +97,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.customize-main {
|
div.customize-main {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -163,7 +158,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.customize-main.content section button {
|
div.customize-main.content section button {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
|
||||||
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
import { menuVisiblilityStore } from "../../Stores/MenuStore";
|
||||||
import LL, { locale } from "../../i18n/i18n-svelte";
|
import LL, { locale } from "../../i18n/i18n-svelte";
|
||||||
import type { Locales } from "../../i18n/i18n-types";
|
import type { Locales } from "../../i18n/i18n-types";
|
||||||
import { displayableLocales, setCurrentLocale } from "../../i18n/locales";
|
import { displayableLocales, setCurrentLocale } from "../../i18n/locales";
|
||||||
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
let fullscreen: boolean = localUserStore.getFullscreen();
|
let fullscreen: boolean = localUserStore.getFullscreen();
|
||||||
let notification: boolean = localUserStore.getNotification() === "granted";
|
let notification: boolean = localUserStore.getNotification() === "granted";
|
||||||
@@ -85,6 +85,8 @@
|
|||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
menuVisiblilityStore.set(false);
|
menuVisiblilityStore.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMobile = isMediaBreakpointUp("md");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="settings-main" on:submit|preventDefault={saveSetting}>
|
<div class="settings-main" on:submit|preventDefault={saveSetting}>
|
||||||
@@ -93,22 +95,22 @@
|
|||||||
<div class="nes-select is-dark">
|
<div class="nes-select is-dark">
|
||||||
<select bind:value={valueGame}>
|
<select bind:value={valueGame}>
|
||||||
<option value={120}
|
<option value={120}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.gameQuality.short.high()
|
? $LL.menu.settings.gameQuality.short.high()
|
||||||
: $LL.menu.settings.gameQuality.long.high()}</option
|
: $LL.menu.settings.gameQuality.long.high()}</option
|
||||||
>
|
>
|
||||||
<option value={60}
|
<option value={60}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.gameQuality.short.medium()
|
? $LL.menu.settings.gameQuality.short.medium()
|
||||||
: $LL.menu.settings.gameQuality.long.medium()}</option
|
: $LL.menu.settings.gameQuality.long.medium()}</option
|
||||||
>
|
>
|
||||||
<option value={40}
|
<option value={40}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.gameQuality.short.small()
|
? $LL.menu.settings.gameQuality.short.small()
|
||||||
: $LL.menu.settings.gameQuality.long.small()}</option
|
: $LL.menu.settings.gameQuality.long.small()}</option
|
||||||
>
|
>
|
||||||
<option value={20}
|
<option value={20}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.gameQuality.short.minimum()
|
? $LL.menu.settings.gameQuality.short.minimum()
|
||||||
: $LL.menu.settings.gameQuality.long.minimum()}</option
|
: $LL.menu.settings.gameQuality.long.minimum()}</option
|
||||||
>
|
>
|
||||||
@@ -120,22 +122,22 @@
|
|||||||
<div class="nes-select is-dark">
|
<div class="nes-select is-dark">
|
||||||
<select bind:value={valueVideo}>
|
<select bind:value={valueVideo}>
|
||||||
<option value={30}
|
<option value={30}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.videoQuality.short.high()
|
? $LL.menu.settings.videoQuality.short.high()
|
||||||
: $LL.menu.settings.videoQuality.long.high()}</option
|
: $LL.menu.settings.videoQuality.long.high()}</option
|
||||||
>
|
>
|
||||||
<option value={20}
|
<option value={20}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.videoQuality.short.medium()
|
? $LL.menu.settings.videoQuality.short.medium()
|
||||||
: $LL.menu.settings.videoQuality.long.medium()}</option
|
: $LL.menu.settings.videoQuality.long.medium()}</option
|
||||||
>
|
>
|
||||||
<option value={10}
|
<option value={10}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.videoQuality.short.small()
|
? $LL.menu.settings.videoQuality.short.small()
|
||||||
: $LL.menu.settings.videoQuality.long.small()}</option
|
: $LL.menu.settings.videoQuality.long.small()}</option
|
||||||
>
|
>
|
||||||
<option value={5}
|
<option value={5}
|
||||||
>{isMobile()
|
>{isMobile
|
||||||
? $LL.menu.settings.videoQuality.short.minimum()
|
? $LL.menu.settings.videoQuality.short.minimum()
|
||||||
: $LL.menu.settings.videoQuality.long.minimum()}</option
|
: $LL.menu.settings.videoQuality.long.minimum()}</option
|
||||||
>
|
>
|
||||||
@@ -199,6 +201,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
div.settings-main {
|
div.settings-main {
|
||||||
height: calc(100% - 40px);
|
height: calc(100% - 40px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -236,7 +240,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px), only screen and (max-height: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
div.settings-main {
|
div.settings-main {
|
||||||
section {
|
section {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||||
import type { World } from "../../Connexion/World";
|
import type { World } from "../../Connexion/World";
|
||||||
|
|
||||||
|
let worlds : Promise<World[]> = connectionManager.getWorlds();
|
||||||
let worlds = connectionManager.getWorlds();
|
|
||||||
|
|
||||||
|
|
||||||
function worldRoomId(world: World) {
|
function worldRoomId(world: World) {
|
||||||
return world.roomId;
|
return world.roomId;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 500;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 450;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||||
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
||||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { srcObject } from "./Video/utils";
|
import { srcObject } from "./Video/utils";
|
||||||
import LL from "../i18n/i18n-svelte";
|
import LL from "../i18n/i18n-svelte";
|
||||||
|
|
||||||
@@ -23,16 +23,75 @@
|
|||||||
isSilent = value;
|
isSilent = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let cameraContainer: HTMLDivElement;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
cameraContainer.addEventListener("transitionend", () => {
|
||||||
|
if (cameraContainer.classList.contains("hide")) {
|
||||||
|
cameraContainer.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cameraContainer.addEventListener("transitionstart", () => {
|
||||||
|
if (!cameraContainer.classList.contains("hide")) {
|
||||||
|
cameraContainer.style.visibility = "visible";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onDestroy(unsubscribeIsSilent);
|
onDestroy(unsubscribeIsSilent);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div
|
||||||
<div class="video-container nes-container is-rounded is-dark div-myCamVideo"
|
class="nes-container is-rounded my-cam-video-container"
|
||||||
class:hide={!$obtainedMediaConstraintStore.video || isSilent}>
|
class:hide={($localStreamStore.type !== "success" || !$obtainedMediaConstraintStore.video) && !isSilent}
|
||||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream}
|
bind:this={cameraContainer}
|
||||||
<video class="myCamVideo" use:srcObject={stream} autoplay muted playsinline />
|
>
|
||||||
<SoundMeterWidget {stream} />
|
{#if isSilent}
|
||||||
{/if}
|
<div class="is-silent">{$LL.camera.my.silentZone()}</div>
|
||||||
</div>
|
{:else if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||||
<div class="nes-container is-dark is-silent" class:hide={isSilent}>{$LL.camera.my.silentZone()}</div>
|
<video class="my-cam-video" use:srcObject={stream} autoplay muted playsinline />
|
||||||
|
<SoundMeterWidget {stream} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
.my-cam-video-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 30px;
|
||||||
|
max-height: 20%;
|
||||||
|
transition: transform 1000ms;
|
||||||
|
padding: 0;
|
||||||
|
background-color: rgba(#000000, 0.6);
|
||||||
|
background-clip: content-box;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 0;
|
||||||
|
z-index: 250;
|
||||||
|
|
||||||
|
&.nes-container.is-rounded {
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-cam-video-container.hide {
|
||||||
|
transform: translateX(200%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-cam-video {
|
||||||
|
background-color: #00000099;
|
||||||
|
max-height: 20vh;
|
||||||
|
max-width: max(25vw, 150px);
|
||||||
|
width: 100%;
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-silent {
|
||||||
|
font-size: 2em;
|
||||||
|
color: white;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -108,12 +108,16 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
|
z-index: 650;
|
||||||
position: relative;
|
position: absolute;
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
top: 10vh;
|
top: 4%;
|
||||||
margin: auto;
|
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
section.report-menu-title {
|
section.report-menu-title {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -137,13 +141,4 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
div.report-menu-main {
|
|
||||||
top: 21vh;
|
|
||||||
height: 60vh;
|
|
||||||
width: 100vw;
|
|
||||||
font-size: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -47,6 +47,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.selectCompanionScene {
|
form.selectCompanionScene {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -85,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
form.selectCompanionScene button.selectCharacterButtonLeft {
|
form.selectCompanionScene button.selectCharacterButtonLeft {
|
||||||
left: 5vw;
|
left: 5vw;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly, fade } from "svelte/transition";
|
import { fly, fade } from "svelte/transition";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||||
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
import type { Message } from "../../Stores/TypeMessageStore/MessageStore";
|
||||||
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
import { banMessageStore } from "../../Stores/TypeMessageStore/BanMessageStore";
|
||||||
import LL from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
@@ -13,6 +14,8 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
timeToRead();
|
timeToRead();
|
||||||
|
const gameScene = gameManager.getCurrentGameScene();
|
||||||
|
gameScene.playSound("audio-report-message");
|
||||||
});
|
});
|
||||||
|
|
||||||
function timeToRead() {
|
function timeToRead() {
|
||||||
@@ -53,18 +56,19 @@
|
|||||||
on:click|preventDefault={closeBanMessage}>{nameButton}</button
|
on:click|preventDefault={closeBanMessage}>{nameButton}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
|
||||||
<audio id="report-message" autoplay>
|
|
||||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3" />
|
|
||||||
</audio>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.main-ban-message {
|
div.main-ban-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: relative;
|
position: absolute;
|
||||||
top: 15vh;
|
top: 4%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
z-index: 850;
|
||||||
|
|
||||||
height: 70vh;
|
height: 70vh;
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
|
|||||||
@@ -11,3 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.main-ban-message-container {
|
||||||
|
z-index: 800;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -42,14 +42,17 @@
|
|||||||
div.main-text-message {
|
div.main-text-message {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
max-height: 25vh;
|
max-height: 25%;
|
||||||
width: 80vw;
|
width: 60%;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-bottom: 16px;
|
top: 6%;
|
||||||
margin-top: 0;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
z-index: 240;
|
||||||
|
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.main-text-message-container {
|
.main-text-message-container {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
z-index: 800;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
background-color: black;
|
background-color: black;
|
||||||
border-radius: 30px 0 0 30px;
|
border-radius: 30px 0 0 30px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
z-index: 750;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|||||||
@@ -25,11 +25,17 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.error-div {
|
div.error-div {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
margin-top: 10vh;
|
margin-top: 4%;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
|
z-index: 230;
|
||||||
|
height: auto !important;
|
||||||
|
background-clip: padding-box;
|
||||||
|
|
||||||
.button-bar {
|
.button-bar {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,15 +1,33 @@
|
|||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
import type { ScreenSharingLocalMedia } from "../../Stores/ScreenSharingStore";
|
import type { ScreenSharingLocalMedia } from "../../Stores/ScreenSharingStore";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
import { srcObject } from "./utils";
|
import { srcObject } from "./utils";
|
||||||
|
|
||||||
|
export let clickable = false;
|
||||||
|
|
||||||
export let peer: ScreenSharingLocalMedia;
|
export let peer: ScreenSharingLocalMedia;
|
||||||
let stream = peer.stream;
|
let stream = peer.stream;
|
||||||
export let cssClass: string | undefined;
|
export let cssClass: string | undefined;
|
||||||
|
let embedScreen: EmbedScreen;
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
embedScreen = {
|
||||||
|
type: "streamable",
|
||||||
|
embed: peer as unknown as Streamable,
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-container {cssClass ? cssClass : ''}" class:hide={!stream}>
|
<div class="video-container {cssClass ? cssClass : ''}" class:hide={!stream}>
|
||||||
{#if stream}
|
{#if stream}
|
||||||
<video use:srcObject={stream} autoplay muted playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
<video
|
||||||
|
use:srcObject={stream}
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
playsinline
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,14 +7,115 @@
|
|||||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
|
|
||||||
export let streamable: Streamable;
|
export let streamable: Streamable;
|
||||||
|
export let isHightlighted = false;
|
||||||
|
export let isClickable = false;
|
||||||
|
export let mozaicSolo = false;
|
||||||
|
export let mozaicFullWidth = false;
|
||||||
|
export let mozaicQuarter = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="media-container">
|
<div
|
||||||
{#if streamable instanceof VideoPeer}
|
class="media-container nes-container is-rounded {isHightlighted ? 'hightlighted' : ''}"
|
||||||
<VideoMediaBox peer={streamable} />
|
class:clickable={isClickable}
|
||||||
{:else if streamable instanceof ScreenSharingPeer}
|
class:mozaic-solo={mozaicSolo}
|
||||||
<ScreenSharingMediaBox peer={streamable} />
|
class:mozaic-full-width={mozaicFullWidth}
|
||||||
{:else}
|
class:mozaic-quarter={mozaicQuarter}
|
||||||
<LocalStreamMediaBox peer={streamable} cssClass="" />
|
>
|
||||||
{/if}
|
<div>
|
||||||
|
{#if streamable instanceof VideoPeer}
|
||||||
|
<VideoMediaBox peer={streamable} clickable={isClickable} />
|
||||||
|
{:else if streamable instanceof ScreenSharingPeer}
|
||||||
|
<ScreenSharingMediaBox peer={streamable} clickable={isClickable} />
|
||||||
|
{:else}
|
||||||
|
<LocalStreamMediaBox peer={streamable} clickable={isClickable} cssClass="" />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
|
.media-container {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 4%;
|
||||||
|
margin-bottom: 4%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s,
|
||||||
|
max-width 0.2s;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
max-height: 200px;
|
||||||
|
max-width: 85%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: 2%;
|
||||||
|
margin-bottom: 2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hightlighted {
|
||||||
|
margin-top: 0% !important;
|
||||||
|
margin-bottom: 0% !important;
|
||||||
|
margin-left: 0% !important;
|
||||||
|
|
||||||
|
max-height: 100% !important;
|
||||||
|
max-width: 96% !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: 0% !important;
|
||||||
|
margin-bottom: 0% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mozaic-solo {
|
||||||
|
max-height: inherit !important;
|
||||||
|
width: 90% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mozaic-full-width {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 95%;
|
||||||
|
margin-left: 3%;
|
||||||
|
margin-right: 3%;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
max-height: 95%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mozaic-quarter {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 95%;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
max-height: 95%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.nes-container.is-rounded {
|
||||||
|
border-image-outset: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-only(md) {
|
||||||
|
.media-container {
|
||||||
|
margin-top: 10%;
|
||||||
|
margin-bottom: 10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
||||||
import { afterUpdate } from "svelte";
|
|
||||||
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
|
||||||
import MediaBox from "./MediaBox.svelte";
|
|
||||||
|
|
||||||
afterUpdate(() => {
|
|
||||||
biggestAvailableAreaStore.recompute();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="main-section">
|
|
||||||
{#if $videoFocusStore}
|
|
||||||
{#key $videoFocusStore.uniqueId}
|
|
||||||
<MediaBox streamable={$videoFocusStore} />
|
|
||||||
{/key}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<aside class="sidebar">
|
|
||||||
{#each [...$streamableCollectionStore.values()] as peer (peer.uniqueId)}
|
|
||||||
{#if peer !== $videoFocusStore}
|
|
||||||
<MediaBox streamable={peer} />
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</aside>
|
|
||||||
@@ -1,12 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
|
|
||||||
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
|
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
||||||
import { getColorByString, srcObject } from "./utils";
|
import { getColorByString, srcObject } from "./utils";
|
||||||
|
|
||||||
|
export let clickable = false;
|
||||||
|
|
||||||
export let peer: ScreenSharingPeer;
|
export let peer: ScreenSharingPeer;
|
||||||
let streamStore = peer.streamStore;
|
let streamStore = peer.streamStore;
|
||||||
let name = peer.userName;
|
let name = peer.userName;
|
||||||
let statusStore = peer.statusStore;
|
let statusStore = peer.statusStore;
|
||||||
|
|
||||||
|
let embedScreen: EmbedScreen;
|
||||||
|
|
||||||
|
if (peer) {
|
||||||
|
embedScreen = {
|
||||||
|
type: "streamable",
|
||||||
|
embed: peer as unknown as Streamable,
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
@@ -16,11 +30,17 @@
|
|||||||
{#if $statusStore === "error"}
|
{#if $statusStore === "error"}
|
||||||
<div class="rtc-error" />
|
<div class="rtc-error" />
|
||||||
{/if}
|
{/if}
|
||||||
{#if $streamStore === null}
|
{#if $streamStore !== null}
|
||||||
<i style="background-color: {getColorByString(name)};">{name}</i>
|
<i class="container">
|
||||||
{:else}
|
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||||
|
</i>
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
<video
|
||||||
|
use:srcObject={$streamStore}
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -29,5 +49,10 @@
|
|||||||
video {
|
video {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
i {
|
||||||
|
span {
|
||||||
|
padding: 2px 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,11 +4,17 @@
|
|||||||
import microphoneCloseImg from "../images/microphone-close.svg";
|
import microphoneCloseImg from "../images/microphone-close.svg";
|
||||||
import reportImg from "./images/report.svg";
|
import reportImg from "./images/report.svg";
|
||||||
import blockSignImg from "./images/blockSign.svg";
|
import blockSignImg from "./images/blockSign.svg";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
||||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||||
import { getColorByString, srcObject } from "./utils";
|
import { getColorByString, srcObject } from "./utils";
|
||||||
|
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||||
|
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||||
|
|
||||||
import Woka from "../Woka/Woka.svelte";
|
import Woka from "../Woka/Woka.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { isMediaBreakpointOnly } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
|
export let clickable = false;
|
||||||
|
|
||||||
export let peer: VideoPeer;
|
export let peer: VideoPeer;
|
||||||
let streamStore = peer.streamStore;
|
let streamStore = peer.streamStore;
|
||||||
@@ -19,9 +25,37 @@
|
|||||||
function openReport(peer: VideoPeer): void {
|
function openReport(peer: VideoPeer): void {
|
||||||
showReportScreenStore.set({ userId: peer.userId, userName: peer.userName });
|
showReportScreenStore.set({ userId: peer.userId, userName: peer.userName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let embedScreen: EmbedScreen;
|
||||||
|
let videoContainer: HTMLDivElement;
|
||||||
|
let minimized = isMediaBreakpointOnly("md");
|
||||||
|
|
||||||
|
if (peer) {
|
||||||
|
embedScreen = {
|
||||||
|
type: "streamable",
|
||||||
|
embed: peer as unknown as Streamable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
minimized = isMediaBreakpointOnly("md");
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
resizeObserver.observe(videoContainer);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-container nes-container is-rounded is-dark">
|
<div
|
||||||
|
class="video-container"
|
||||||
|
class:no-clikable={!clickable}
|
||||||
|
bind:this={videoContainer}
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
>
|
||||||
{#if $statusStore === "connecting"}
|
{#if $statusStore === "connecting"}
|
||||||
<div class="connecting-spinner" />
|
<div class="connecting-spinner" />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -29,43 +63,46 @@
|
|||||||
<div class="rtc-error" />
|
<div class="rtc-error" />
|
||||||
{/if}
|
{/if}
|
||||||
<!-- {#if !$constraintStore || $constraintStore.video === false} -->
|
<!-- {#if !$constraintStore || $constraintStore.video === false} -->
|
||||||
<i
|
<i class="container">
|
||||||
class="container {!$constraintStore || $constraintStore.video === false ? '' : 'minimized'}"
|
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||||
style="background-color: {getColorByString(name)};"
|
|
||||||
>
|
|
||||||
<span>{peer.userName}</span>
|
|
||||||
<div class="woka-icon"><Woka userId={peer.userId} placeholderSrc={""} /></div>
|
|
||||||
</i>
|
</i>
|
||||||
|
<div class="woka-icon {($constraintStore && $constraintStore.video !== false) || minimized ? '' : 'no-video'}">
|
||||||
|
<Woka userId={peer.userId} placeholderSrc={""} />
|
||||||
|
</div>
|
||||||
<!-- {/if} -->
|
<!-- {/if} -->
|
||||||
{#if $constraintStore && $constraintStore.audio === false}
|
{#if $constraintStore && $constraintStore.audio === false}
|
||||||
<img src={microphoneCloseImg} class="active" alt="Muted" />
|
<img
|
||||||
|
src={microphoneCloseImg}
|
||||||
|
class="active noselect"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
alt="Muted"
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="report nes-button is-dark" on:click={() => openReport(peer)}>
|
<button class="report nes-button is-dark" on:click={() => openReport(peer)}>
|
||||||
<img alt="Report this user" src={reportImg} />
|
<img alt="Report this user" draggable="false" on:dragstart|preventDefault={noDrag} src={reportImg} />
|
||||||
<span>Report/Block</span>
|
<span class="noselect">Report/Block</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- svelte-ignore a11y-media-has-caption -->
|
<!-- svelte-ignore a11y-media-has-caption -->
|
||||||
<video use:srcObject={$streamStore} autoplay playsinline on:click={() => videoFocusStore.toggleFocus(peer)} />
|
<video
|
||||||
<img src={blockSignImg} class="block-logo" alt="Block" />
|
class:no-video={!$constraintStore || $constraintStore.video === false}
|
||||||
|
use:srcObject={$streamStore}
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
on:click={() => (clickable ? highlightedEmbedScreen.toggleHighlight(embedScreen) : null)}
|
||||||
|
/>
|
||||||
|
<img src={blockSignImg} draggable="false" on:dragstart|preventDefault={noDrag} class="block-logo" alt="Block" />
|
||||||
{#if $constraintStore && $constraintStore.audio !== false}
|
{#if $constraintStore && $constraintStore.audio !== false}
|
||||||
<SoundMeterWidget stream={$streamStore} />
|
<SoundMeterWidget stream={$streamStore} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 15px;
|
|
||||||
}
|
}
|
||||||
|
video.no-video {
|
||||||
.minimized {
|
visibility: collapse;
|
||||||
left: auto;
|
|
||||||
transform: scale(0.5);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.woka-icon {
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
// import {LayoutMode} from "../../WebRtc/LayoutManager";
|
||||||
import { layoutModeStore } from "../../Stores/StreamableCollectionStore";
|
// import {layoutModeStore} from "../../Stores/StreamableCollectionStore";
|
||||||
import PresentationLayout from "./PresentationLayout.svelte";
|
// import PresentationLayout from "./PresentationLayout.svelte";
|
||||||
import ChatLayout from "./ChatLayout.svelte";
|
// import ChatLayout from "./ChatLayout.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="video-overlay">
|
<div class="video-overlay">
|
||||||
{#if $layoutModeStore === LayoutMode.Presentation}
|
<!-- {#if $layoutModeStore === LayoutMode.Presentation }
|
||||||
<PresentationLayout />
|
<PresentationLayout />
|
||||||
{:else}
|
{:else}
|
||||||
<ChatLayout />
|
<ChatLayout />
|
||||||
{/if}
|
{/if} -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
height: 120px;
|
height: 120px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
|
z-index: 350;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
|
|||||||
@@ -27,18 +27,21 @@
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main.warningMain {
|
main.warningMain {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
width: 100vw;
|
width: 80%;
|
||||||
background-color: #f9e81e;
|
background-color: #f9e81e;
|
||||||
color: #14304c;
|
color: #14304c;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0);
|
top: 4%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
z-index: 2;
|
z-index: 700;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,21 @@
|
|||||||
src = source ?? placeholderSrc;
|
src = source ?? placeholderSrc;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function noDrag() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
onDestroy(unsubscribe);
|
onDestroy(unsubscribe);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img {src} alt="" class="nes-pointer" style="--theme-width: {width}; --theme-height: {height}" />
|
<img
|
||||||
|
{src}
|
||||||
|
alt=""
|
||||||
|
class="nes-pointer noselect"
|
||||||
|
style="--theme-width: {width}; --theme-height: {height}"
|
||||||
|
draggable="false"
|
||||||
|
on:dragstart|preventDefault={noDrag}
|
||||||
|
/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
img {
|
img {
|
||||||
|
|||||||
@@ -49,6 +49,8 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "../../../style/breakpoints.scss";
|
||||||
|
|
||||||
form.selectCharacterScene {
|
form.selectCharacterScene {
|
||||||
font-family: "Press Start 2P";
|
font-family: "Press Start 2P";
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -91,7 +93,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@include media-breakpoint-up(md) {
|
||||||
form.selectCharacterScene button.selectCharacterButtonLeft {
|
form.selectCharacterScene button.selectCharacterButtonLeft {
|
||||||
left: 5vw;
|
left: 5vw;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { showLimitRoomModalStore } from "../Stores/ModalStore";
|
|||||||
import { locales } from "../i18n/i18n-util";
|
import { locales } from "../i18n/i18n-util";
|
||||||
import type { Locales } from "../i18n/i18n-types";
|
import type { Locales } from "../i18n/i18n-types";
|
||||||
import { setCurrentLocale } from "../i18n/locales";
|
import { setCurrentLocale } from "../i18n/locales";
|
||||||
|
import type { World } from "./World";
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!: LocalUser;
|
private localUser!: LocalUser;
|
||||||
@@ -187,14 +188,14 @@ class ConnectionManager {
|
|||||||
window.location.hash;
|
window.location.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||||
|
//use href to keep # value
|
||||||
|
await localUserStore.setLastRoomUrl(new URL(roomPath).href);
|
||||||
|
|
||||||
//get detail map for anonymous login and set texture in local storage
|
//get detail map for anonymous login and set texture in local storage
|
||||||
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
//before set token of user we must load room and all information. For example the mandatory authentication could be require on current room
|
||||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||||
|
|
||||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
|
||||||
//use href to keep # value
|
|
||||||
await localUserStore.setLastRoomUrl(this._currentRoom.href);
|
|
||||||
|
|
||||||
//todo: add here some kind of warning if authToken has expired.
|
//todo: add here some kind of warning if authToken has expired.
|
||||||
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
if (!this.authToken && !this._currentRoom.authenticationMandatory) {
|
||||||
await this.anonymousLogin();
|
await this.anonymousLogin();
|
||||||
@@ -384,7 +385,7 @@ class ConnectionManager {
|
|||||||
userIsConnected.set(true);
|
userIsConnected.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorlds() {
|
async getWorlds() : Promise<World[]> {
|
||||||
const token = localUserStore.getAuthToken();
|
const token = localUserStore.getAuthToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error("No token provided");
|
throw new Error("No token provided");
|
||||||
|
|||||||
@@ -1,33 +1,46 @@
|
|||||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
declare global {
|
||||||
const START_ROOM_URL: string =
|
interface Window {
|
||||||
process.env.START_ROOM_URL || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
env?: Record<string, string>;
|
||||||
const PUSHER_URL = process.env.PUSHER_URL || "//pusher.workadventure.localhost";
|
}
|
||||||
export const ADMIN_URL = process.env.ADMIN_URL || "//workadventu.re";
|
}
|
||||||
const UPLOADER_URL = process.env.UPLOADER_URL || "//uploader.workadventure.localhost";
|
|
||||||
const ICON_URL = process.env.ICON_URL || "//icon.workadventure.localhost";
|
const getEnv = (key: string): string | undefined => {
|
||||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
if (global.window?.env) {
|
||||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
return global.window.env[key];
|
||||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
}
|
||||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
if (global.process?.env) {
|
||||||
const TURN_USER: string = process.env.TURN_USER || "";
|
return global.process.env[key];
|
||||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || "";
|
}
|
||||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
return;
|
||||||
const JITSI_PRIVATE_MODE: boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
};
|
||||||
|
|
||||||
|
const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true";
|
||||||
|
const START_ROOM_URL: string = getEnv("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||||
|
const PUSHER_URL = getEnv("PUSHER_URL") || "//pusher.workadventure.localhost";
|
||||||
|
export const ADMIN_URL = getEnv("ADMIN_URL") || "//workadventu.re";
|
||||||
|
const UPLOADER_URL = getEnv("UPLOADER_URL") || "//uploader.workadventure.localhost";
|
||||||
|
const ICON_URL = getEnv("ICON_URL") || "//icon.workadventure.localhost";
|
||||||
|
const STUN_SERVER: string = getEnv("STUN_SERVER") || "stun:stun.l.google.com:19302";
|
||||||
|
const TURN_SERVER: string = getEnv("TURN_SERVER") || "";
|
||||||
|
const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnv("SKIP_RENDER_OPTIMIZATIONS") == "true";
|
||||||
|
const DISABLE_NOTIFICATIONS: boolean = getEnv("DISABLE_NOTIFICATIONS") == "true";
|
||||||
|
const TURN_USER: string = getEnv("TURN_USER") || "";
|
||||||
|
const TURN_PASSWORD: string = getEnv("TURN_PASSWORD") || "";
|
||||||
|
const JITSI_URL: string | undefined = getEnv("JITSI_URL") === "" ? undefined : getEnv("JITSI_URL");
|
||||||
|
const JITSI_PRIVATE_MODE: boolean = getEnv("JITSI_PRIVATE_MODE") == "true";
|
||||||
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
const POSITION_DELAY = 200; // Wait 200ms between sending position events
|
||||||
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
|
||||||
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || "") || 8;
|
export const MAX_USERNAME_LENGTH = parseInt(getEnv("MAX_USERNAME_LENGTH") || "") || 8;
|
||||||
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
export const MAX_PER_GROUP = parseInt(getEnv("MAX_PER_GROUP") || "4");
|
||||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
export const DISPLAY_TERMS_OF_USE = getEnv("DISPLAY_TERMS_OF_USE") == "true";
|
||||||
export const NODE_ENV = process.env.NODE_ENV || "development";
|
export const NODE_ENV = getEnv("NODE_ENV") || "development";
|
||||||
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
export const CONTACT_URL = getEnv("CONTACT_URL") || undefined;
|
||||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
export const PROFILE_URL = getEnv("PROFILE_URL") || undefined;
|
||||||
export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
export const POSTHOG_API_KEY: string = (getEnv("POSTHOG_API_KEY") as string) || "";
|
||||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
export const POSTHOG_URL = getEnv("POSTHOG_URL") || undefined;
|
||||||
export const DISABLE_ANONYMOUS: boolean = process.env.DISABLE_ANONYMOUS === "true";
|
export const DISABLE_ANONYMOUS: boolean = getEnv("DISABLE_ANONYMOUS") === "true";
|
||||||
export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER;
|
export const OPID_LOGIN_SCREEN_PROVIDER = getEnv("OPID_LOGIN_SCREEN_PROVIDER");
|
||||||
const FALLBACK_LOCALE = process.env.FALLBACK_LOCALE || undefined;
|
const FALLBACK_LOCALE = getEnv("FALLBACK_LOCALE") || undefined;
|
||||||
|
|
||||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DEBUG_MODE,
|
DEBUG_MODE,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Anima
|
|||||||
import { TexturesHelper } from "../Helpers/TexturesHelper";
|
import { TexturesHelper } from "../Helpers/TexturesHelper";
|
||||||
import { Writable, writable } from "svelte/store";
|
import { Writable, writable } from "svelte/store";
|
||||||
import type { PictureStore } from "../../Stores/PictureStore";
|
import type { PictureStore } from "../../Stores/PictureStore";
|
||||||
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
export interface CompanionStatus {
|
export interface CompanionStatus {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -25,8 +26,9 @@ export class Companion extends Container {
|
|||||||
private direction: PlayerAnimationDirections;
|
private direction: PlayerAnimationDirections;
|
||||||
private animationType: PlayerAnimationTypes;
|
private animationType: PlayerAnimationTypes;
|
||||||
private readonly _pictureStore: Writable<string | undefined>;
|
private readonly _pictureStore: Writable<string | undefined>;
|
||||||
|
private texturePromise: CancelablePromise<string | void> | undefined;
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
|
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: CancelablePromise<string>) {
|
||||||
super(scene, x + 14, y + 4);
|
super(scene, x + 14, y + 4);
|
||||||
|
|
||||||
this.sprites = new Map<string, Sprite>();
|
this.sprites = new Map<string, Sprite>();
|
||||||
@@ -41,7 +43,7 @@ export class Companion extends Container {
|
|||||||
this.companionName = name;
|
this.companionName = name;
|
||||||
this._pictureStore = writable(undefined);
|
this._pictureStore = writable(undefined);
|
||||||
|
|
||||||
texturePromise
|
this.texturePromise = texturePromise
|
||||||
.then((resource) => {
|
.then((resource) => {
|
||||||
this.addResource(resource);
|
this.addResource(resource);
|
||||||
this.invisible = false;
|
this.invisible = false;
|
||||||
@@ -234,6 +236,7 @@ export class Companion extends Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
|
this.texturePromise?.cancel();
|
||||||
for (const sprite of this.sprites.values()) {
|
for (const sprite of this.sprites.values()) {
|
||||||
if (this.scene) {
|
if (this.scene) {
|
||||||
this.scene.sys.updateList.remove(sprite);
|
this.scene.sys.updateList.remove(sprite);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
|
import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./CompanionTextures";
|
||||||
|
import CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
|
||||||
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
|
||||||
@@ -9,8 +10,12 @@ export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourc
|
|||||||
return COMPANION_RESOURCES;
|
return COMPANION_RESOURCES;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): Promise<string> => {
|
export const lazyLoadCompanionResource = (loader: LoaderPlugin, name: string): CancelablePromise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new CancelablePromise((resolve, reject, cancel) => {
|
||||||
|
cancel(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
const resource = COMPANION_RESOURCES.find((item) => item.name === name);
|
const resource = COMPANION_RESOURCES.find((item) => item.name === name);
|
||||||
|
|
||||||
if (typeof resource === "undefined") {
|
if (typeof resource === "undefined") {
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ export class SoundMeter {
|
|||||||
//this.slow = 0.95 * that.slow + 0.05 * that.instant;
|
//this.slow = 0.95 * that.slow + 0.05 * that.instant;
|
||||||
//this.clip = clipcount / input.length;
|
//this.clip = clipcount / input.length;
|
||||||
|
|
||||||
//console.log('instant', this.instant, 'clip', this.clip);
|
|
||||||
|
|
||||||
return this.instant;
|
return this.instant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { TexturesHelper } from "../Helpers/TexturesHelper";
|
|||||||
import type { PictureStore } from "../../Stores/PictureStore";
|
import type { PictureStore } from "../../Stores/PictureStore";
|
||||||
import { Unsubscriber, Writable, writable } from "svelte/store";
|
import { Unsubscriber, Writable, writable } from "svelte/store";
|
||||||
import { createColorStore } from "../../Stores/OutlineColorStore";
|
import { createColorStore } from "../../Stores/OutlineColorStore";
|
||||||
|
import type { OutlineableInterface } from "../Game/OutlineableInterface";
|
||||||
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
const playerNameY = -25;
|
const playerNameY = -25;
|
||||||
|
|
||||||
@@ -28,14 +30,15 @@ interface AnimationData {
|
|||||||
|
|
||||||
const interactiveRadius = 35;
|
const interactiveRadius = 35;
|
||||||
|
|
||||||
export abstract class Character extends Container {
|
export abstract class Character extends Container implements OutlineableInterface {
|
||||||
private bubble: SpeechBubble | null = null;
|
private bubble: SpeechBubble | null = null;
|
||||||
private readonly playerName: Text;
|
private readonly playerNameText: Text;
|
||||||
public PlayerValue: string;
|
public playerName: string;
|
||||||
public sprites: Map<string, Sprite>;
|
public sprites: Map<string, Sprite>;
|
||||||
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||||
//private teleportation: Sprite;
|
//private teleportation: Sprite;
|
||||||
private invisible: boolean;
|
private invisible: boolean;
|
||||||
|
private clickable: boolean;
|
||||||
public companion?: Companion;
|
public companion?: Companion;
|
||||||
private emote: Phaser.GameObjects.Text | null = null;
|
private emote: Phaser.GameObjects.Text | null = null;
|
||||||
private emoteTween: Phaser.Tweens.Tween | null = null;
|
private emoteTween: Phaser.Tweens.Tween | null = null;
|
||||||
@@ -43,30 +46,32 @@ export abstract class Character extends Container {
|
|||||||
private readonly _pictureStore: Writable<string | undefined>;
|
private readonly _pictureStore: Writable<string | undefined>;
|
||||||
private readonly outlineColorStore = createColorStore();
|
private readonly outlineColorStore = createColorStore();
|
||||||
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
|
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
|
||||||
|
private texturePromise: CancelablePromise<string[] | void> | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
scene: GameScene,
|
scene: GameScene,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: CancelablePromise<string[]>,
|
||||||
name: string,
|
name: string,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
frame: string | number,
|
frame: string | number,
|
||||||
isClickable: boolean,
|
isClickable: boolean,
|
||||||
companion: string | null,
|
companion: string | null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: CancelablePromise<string>
|
||||||
) {
|
) {
|
||||||
super(scene, x, y /*, texture, frame*/);
|
super(scene, x, y /*, texture, frame*/);
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.PlayerValue = name;
|
this.playerName = name;
|
||||||
this.invisible = true;
|
this.invisible = true;
|
||||||
|
this.clickable = false;
|
||||||
|
|
||||||
this.sprites = new Map<string, Sprite>();
|
this.sprites = new Map<string, Sprite>();
|
||||||
this._pictureStore = writable(undefined);
|
this._pictureStore = writable(undefined);
|
||||||
|
|
||||||
//textures are inside a Promise in case they need to be lazyloaded before use.
|
//textures are inside a Promise in case they need to be lazyloaded before use.
|
||||||
texturesPromise
|
this.texturePromise = texturesPromise
|
||||||
.then((textures) => {
|
.then((textures) => {
|
||||||
this.addTextures(textures, frame);
|
this.addTextures(textures, frame);
|
||||||
this.invisible = false;
|
this.invisible = false;
|
||||||
@@ -81,9 +86,12 @@ export abstract class Character extends Container {
|
|||||||
this.invisible = false;
|
this.invisible = false;
|
||||||
this.playAnimation(direction, moving);
|
this.playAnimation(direction, moving);
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.texturePromise = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.playerName = new Text(scene, 0, playerNameY, name, {
|
this.playerNameText = new Text(scene, 0, playerNameY, name, {
|
||||||
fontFamily: '"Press Start 2P"',
|
fontFamily: '"Press Start 2P"',
|
||||||
fontSize: "8px",
|
fontSize: "8px",
|
||||||
strokeThickness: 2,
|
strokeThickness: 2,
|
||||||
@@ -94,30 +102,17 @@ export abstract class Character extends Container {
|
|||||||
fontSize: 35,
|
fontSize: 35,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||||
this.add(this.playerName);
|
this.add(this.playerNameText);
|
||||||
|
|
||||||
if (isClickable) {
|
this.setClickable(isClickable);
|
||||||
this.setInteractive({
|
|
||||||
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
|
||||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
|
||||||
useHandCursor: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on("pointerover", () => {
|
|
||||||
this.outlineColorStore.pointerOver();
|
|
||||||
});
|
|
||||||
this.on("pointerout", () => {
|
|
||||||
this.outlineColorStore.pointerOut();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
|
this.outlineColorStoreUnsubscribe = this.outlineColorStore.subscribe((color) => {
|
||||||
if (color === undefined) {
|
if (color === undefined) {
|
||||||
this.getOutlinePlugin()?.remove(this.playerName);
|
this.getOutlinePlugin()?.remove(this.playerNameText);
|
||||||
} else {
|
} else {
|
||||||
this.getOutlinePlugin()?.remove(this.playerName);
|
this.getOutlinePlugin()?.remove(this.playerNameText);
|
||||||
this.getOutlinePlugin()?.add(this.playerName, {
|
this.getOutlinePlugin()?.add(this.playerNameText, {
|
||||||
thickness: 2,
|
thickness: 2,
|
||||||
outlineColor: color,
|
outlineColor: color,
|
||||||
});
|
});
|
||||||
@@ -140,6 +135,55 @@ export abstract class Character extends Container {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setClickable(clickable: boolean = true): void {
|
||||||
|
if (this.clickable === clickable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.clickable = clickable;
|
||||||
|
if (clickable) {
|
||||||
|
this.setInteractive({
|
||||||
|
hitArea: new Phaser.Geom.Circle(8, 8, interactiveRadius),
|
||||||
|
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||||
|
useHandCursor: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.disableInteractive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isClickable() {
|
||||||
|
return this.clickable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPosition(): { x: number; y: number } {
|
||||||
|
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 {
|
||||||
|
return this.playerNameText;
|
||||||
|
}
|
||||||
|
|
||||||
private async getSnapshot(): Promise<string> {
|
private async getSnapshot(): Promise<string> {
|
||||||
const sprites = Array.from(this.sprites.values()).map((sprite) => {
|
const sprites = Array.from(this.sprites.values()).map((sprite) => {
|
||||||
return { sprite, frame: 1 };
|
return { sprite, frame: 1 };
|
||||||
@@ -156,7 +200,7 @@ export abstract class Character extends Container {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public addCompanion(name: string, texturePromise?: Promise<string>): void {
|
public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void {
|
||||||
if (typeof texturePromise !== "undefined") {
|
if (typeof texturePromise !== "undefined") {
|
||||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||||
}
|
}
|
||||||
@@ -326,6 +370,7 @@ export abstract class Character extends Container {
|
|||||||
this.scene.sys.updateList.remove(sprite);
|
this.scene.sys.updateList.remove(sprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.texturePromise?.cancel();
|
||||||
this.list.forEach((objectContaining) => objectContaining.destroy());
|
this.list.forEach((objectContaining) => objectContaining.destroy());
|
||||||
this.outlineColorStoreUnsubscribe();
|
this.outlineColorStoreUnsubscribe();
|
||||||
super.destroy();
|
super.destroy();
|
||||||
@@ -405,18 +450,42 @@ export abstract class Character extends Container {
|
|||||||
private destroyEmote() {
|
private destroyEmote() {
|
||||||
this.emote?.destroy();
|
this.emote?.destroy();
|
||||||
this.emote = null;
|
this.emote = null;
|
||||||
this.playerName.setVisible(true);
|
this.playerNameText.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get pictureStore(): PictureStore {
|
public get pictureStore(): PictureStore {
|
||||||
return this._pictureStore;
|
return this._pictureStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setOutlineColor(color: number): void {
|
public setFollowOutlineColor(color: number): void {
|
||||||
this.outlineColorStore.setColor(color);
|
this.outlineColorStore.setFollowColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeOutlineColor(): void {
|
public removeFollowOutlineColor(): void {
|
||||||
this.outlineColorStore.removeColor();
|
this.outlineColorStore.removeFollowColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setApiOutlineColor(color: number): void {
|
||||||
|
this.outlineColorStore.setApiColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeApiOutlineColor(): void {
|
||||||
|
this.outlineColorStore.removeApiColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public pointerOverOutline(color: number): void {
|
||||||
|
this.outlineColorStore.pointerOver(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pointerOutOutline(): void {
|
||||||
|
this.outlineColorStore.pointerOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
public characterCloseByOutline(color: number): void {
|
||||||
|
this.outlineColorStore.characterCloseBy(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public characterFarAwayOutline(): void {
|
||||||
|
this.outlineColorStore.characterFarAway();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||||
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
|
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
|
||||||
|
import CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
export interface FrameConfig {
|
export interface FrameConfig {
|
||||||
frameWidth: number;
|
frameWidth: number;
|
||||||
@@ -30,7 +31,7 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
|||||||
export const loadCustomTexture = (
|
export const loadCustomTexture = (
|
||||||
loaderPlugin: LoaderPlugin,
|
loaderPlugin: LoaderPlugin,
|
||||||
texture: CharacterTexture
|
texture: CharacterTexture
|
||||||
): Promise<BodyResourceDescriptionInterface> => {
|
): CancelablePromise<BodyResourceDescriptionInterface> => {
|
||||||
const name = "customCharacterTexture" + texture.id;
|
const name = "customCharacterTexture" + texture.id;
|
||||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
|
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
|
||||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||||
@@ -42,8 +43,8 @@ export const loadCustomTexture = (
|
|||||||
export const lazyLoadPlayerCharacterTextures = (
|
export const lazyLoadPlayerCharacterTextures = (
|
||||||
loadPlugin: LoaderPlugin,
|
loadPlugin: LoaderPlugin,
|
||||||
texturekeys: Array<string | BodyResourceDescriptionInterface>
|
texturekeys: Array<string | BodyResourceDescriptionInterface>
|
||||||
): Promise<string[]> => {
|
): CancelablePromise<string[]> => {
|
||||||
const promisesList: Promise<unknown>[] = [];
|
const promisesList: CancelablePromise<unknown>[] = [];
|
||||||
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
|
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
|
||||||
try {
|
try {
|
||||||
//TODO refactor
|
//TODO refactor
|
||||||
@@ -60,12 +61,12 @@ export const lazyLoadPlayerCharacterTextures = (
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let returnPromise: Promise<Array<string | BodyResourceDescriptionInterface>>;
|
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
|
||||||
if (promisesList.length > 0) {
|
if (promisesList.length > 0) {
|
||||||
loadPlugin.start();
|
loadPlugin.start();
|
||||||
returnPromise = Promise.all(promisesList).then(() => texturekeys);
|
returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys);
|
||||||
} else {
|
} else {
|
||||||
returnPromise = Promise.resolve(texturekeys);
|
returnPromise = CancelablePromise.resolve(texturekeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
//If the loading fail, we render the default model instead.
|
//If the loading fail, we render the default model instead.
|
||||||
@@ -98,10 +99,17 @@ export const createLoadingPromise = (
|
|||||||
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
||||||
frameConfig: FrameConfig
|
frameConfig: FrameConfig
|
||||||
) => {
|
) => {
|
||||||
return new Promise<BodyResourceDescriptionInterface>((res, rej) => {
|
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
|
||||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||||
return res(playerResourceDescriptor);
|
return res(playerResourceDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancel(() => {
|
||||||
|
loadPlugin.off("loaderror");
|
||||||
|
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||||
const errorCallback = (file: { src: string }) => {
|
const errorCallback = (file: { src: string }) => {
|
||||||
if (file.src !== playerResourceDescriptor.img) return;
|
if (file.src !== playerResourceDescriptor.img) return;
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
|
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||||
|
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||||
|
import { Character } from "../Entity/Character";
|
||||||
import type { GameScene } from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
import type { PointInterface } from "../../Connexion/ConnexionModels";
|
import type { PointInterface } from "../../Connexion/ConnexionModels";
|
||||||
import { Character } from "../Entity/Character";
|
|
||||||
import type { PlayerAnimationDirections } from "../Player/Animation";
|
import type { PlayerAnimationDirections } from "../Player/Animation";
|
||||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
import type { Unsubscriber } from "svelte/store";
|
||||||
|
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||||
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||||
*/
|
*/
|
||||||
export class RemotePlayer extends Character {
|
export class RemotePlayer extends Character implements ActivatableInterface {
|
||||||
userId: number;
|
public userId: number;
|
||||||
|
public readonly activationRadius: number;
|
||||||
|
|
||||||
|
private registeredActions: { actionName: string; callback: Function }[];
|
||||||
private visitCardUrl: string | null;
|
private visitCardUrl: string | null;
|
||||||
|
private isActionsMenuInitialized: boolean = false;
|
||||||
|
private actionsMenuStoreUnsubscriber: Unsubscriber;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
userId: number,
|
userId: number,
|
||||||
@@ -17,39 +26,31 @@ export class RemotePlayer extends Character {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
name: string,
|
name: string,
|
||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: CancelablePromise<string[]>,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
visitCardUrl: string | null,
|
visitCardUrl: string | null,
|
||||||
companion: string | null,
|
companion: string | null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: CancelablePromise<string>,
|
||||||
|
activationRadius?: number
|
||||||
) {
|
) {
|
||||||
super(
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||||
Scene,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
texturesPromise,
|
|
||||||
name,
|
|
||||||
direction,
|
|
||||||
moving,
|
|
||||||
1,
|
|
||||||
!!visitCardUrl,
|
|
||||||
companion,
|
|
||||||
companionTexturePromise
|
|
||||||
);
|
|
||||||
|
|
||||||
//set data
|
//set data
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.visitCardUrl = visitCardUrl;
|
this.visitCardUrl = visitCardUrl;
|
||||||
|
this.registeredActions = [];
|
||||||
this.on("pointerdown", (event: Phaser.Input.Pointer) => {
|
this.registerDefaultActionsMenuActions();
|
||||||
if (event.downElement.nodeName === "CANVAS") {
|
this.setClickable(this.registeredActions.length > 0);
|
||||||
requestVisitCardsStore.set(this.visitCardUrl);
|
this.activationRadius = activationRadius ?? 96;
|
||||||
}
|
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
|
||||||
|
this.isActionsMenuInitialized = value ? true : false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.bindEventHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(position: PointInterface): void {
|
public updatePosition(position: PointInterface): void {
|
||||||
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
||||||
this.setX(position.x);
|
this.setX(position.x);
|
||||||
this.setY(position.y);
|
this.setY(position.y);
|
||||||
@@ -60,4 +61,66 @@ export class RemotePlayer extends Character {
|
|||||||
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void {
|
||||||
|
this.registeredActions.push(action);
|
||||||
|
this.updateIsClickable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregisterActionsMenuAction(actionName: string) {
|
||||||
|
const index = this.registeredActions.findIndex((action) => action.actionName === actionName);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.registeredActions.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.updateIsClickable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public activate(): void {
|
||||||
|
this.toggleActionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
this.actionsMenuStoreUnsubscriber();
|
||||||
|
actionsMenuStore.clear();
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isActivatable(): boolean {
|
||||||
|
return this.isClickable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateIsClickable(): void {
|
||||||
|
this.setClickable(this.registeredActions.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleActionsMenu(): void {
|
||||||
|
if (this.isActionsMenuInitialized) {
|
||||||
|
actionsMenuStore.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
actionsMenuStore.initialize(this.playerName);
|
||||||
|
for (const action of this.registeredActions) {
|
||||||
|
actionsMenuStore.addAction(action.actionName, action.callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerDefaultActionsMenuActions(): void {
|
||||||
|
if (this.visitCardUrl) {
|
||||||
|
this.registeredActions.push({
|
||||||
|
actionName: "Visiting Card",
|
||||||
|
callback: () => {
|
||||||
|
requestVisitCardsStore.set(this.visitCardUrl);
|
||||||
|
actionsMenuStore.clear();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindEventHandlers(): void {
|
||||||
|
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
|
||||||
|
if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) {
|
||||||
|
this.toggleActionsMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface ActivatableInterface {
|
||||||
|
readonly activationRadius: number;
|
||||||
|
isActivatable: () => boolean;
|
||||||
|
activate: () => void;
|
||||||
|
getPosition: () => { x: number; y: number };
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { isOutlineable } from "../../Utils/CustomTypeGuards";
|
||||||
|
import { MathUtils } from "../../Utils/MathUtils";
|
||||||
|
import type { Player } from "../Player/Player";
|
||||||
|
import type { ActivatableInterface } from "./ActivatableInterface";
|
||||||
|
|
||||||
|
export class ActivatablesManager {
|
||||||
|
// The item that can be selected by pressing the space key.
|
||||||
|
private selectedActivatableObjectByDistance?: ActivatableInterface;
|
||||||
|
private selectedActivatableObjectByPointer?: ActivatableInterface;
|
||||||
|
private activatableObjectsDistances: Map<ActivatableInterface, number> = new Map<ActivatableInterface, number>();
|
||||||
|
|
||||||
|
private currentPlayer: Player;
|
||||||
|
|
||||||
|
private canSelectByDistance: boolean = true;
|
||||||
|
|
||||||
|
private readonly outlineColor = 0xffff00;
|
||||||
|
private readonly directionalActivationPositionShift = 50;
|
||||||
|
|
||||||
|
constructor(currentPlayer: Player) {
|
||||||
|
this.currentPlayer = currentPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public handlePointerOverActivatableObject(object: ActivatableInterface): void {
|
||||||
|
if (this.selectedActivatableObjectByPointer === object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||||
|
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||||
|
}
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||||
|
this.selectedActivatableObjectByPointer?.pointerOutOutline();
|
||||||
|
}
|
||||||
|
this.selectedActivatableObjectByPointer = object;
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||||
|
this.selectedActivatableObjectByPointer?.pointerOverOutline(this.outlineColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public handlePointerOutActivatableObject(): void {
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByPointer)) {
|
||||||
|
this.selectedActivatableObjectByPointer?.pointerOutOutline();
|
||||||
|
}
|
||||||
|
this.selectedActivatableObjectByPointer = undefined;
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||||
|
this.selectedActivatableObjectByDistance?.characterCloseByOutline(this.outlineColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSelectedActivatableObject(): ActivatableInterface | undefined {
|
||||||
|
return this.selectedActivatableObjectByPointer ?? this.selectedActivatableObjectByDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public deduceSelectedActivatableObjectByDistance(): void {
|
||||||
|
if (!this.canSelectByDistance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newNearestObject = this.findNearestActivatableObject();
|
||||||
|
if (this.selectedActivatableObjectByDistance === newNearestObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// update value but do not change the outline
|
||||||
|
if (this.selectedActivatableObjectByPointer) {
|
||||||
|
this.selectedActivatableObjectByDistance = newNearestObject;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||||
|
this.selectedActivatableObjectByDistance?.characterFarAwayOutline();
|
||||||
|
}
|
||||||
|
this.selectedActivatableObjectByDistance = newNearestObject;
|
||||||
|
if (isOutlineable(this.selectedActivatableObjectByDistance)) {
|
||||||
|
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 {
|
||||||
|
let shortestDistance: number = Infinity;
|
||||||
|
let closestObject: ActivatableInterface | undefined = undefined;
|
||||||
|
|
||||||
|
for (const [object, distance] of this.activatableObjectsDistances.entries()) {
|
||||||
|
if (object.isActivatable() && object.activationRadius > distance && shortestDistance > distance) {
|
||||||
|
shortestDistance = distance;
|
||||||
|
closestObject = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closestObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isSelectingByDistanceEnabled(): boolean {
|
||||||
|
return this.canSelectByDistance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ export class Game extends Phaser.Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public step(time: number, delta: number) {
|
public step(time: number, delta: number) {
|
||||||
|
|||||||
@@ -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[] = [];
|
||||||
@@ -335,12 +336,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +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, CoWebsiteState } 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, ON_ACTION_TRIGGER_DISABLE } 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 type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||||
|
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
|
||||||
|
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||||
|
import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable";
|
||||||
|
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||||
|
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
||||||
import { iframeListener } from "../../Api/IframeListener";
|
import { iframeListener } from "../../Api/IframeListener";
|
||||||
import type { Subscription } from "rxjs";
|
import { Room } from "../../Connexion/Room";
|
||||||
import { LL } from "../../i18n/i18n-svelte";
|
import LL from "../../i18n/i18n-svelte";
|
||||||
|
|
||||||
enum OpenCoWebsiteState {
|
|
||||||
LOADING,
|
|
||||||
OPENED,
|
|
||||||
MUST_BE_CLOSE,
|
|
||||||
TRIGGER,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OpenCoWebsite {
|
interface OpenCoWebsite {
|
||||||
coWebsite: CoWebsite | undefined;
|
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 coWebsitesIframeListeners = new Map<ITiledMapLayer, Subscription>();
|
|
||||||
private coWebsitesActionTriggerByLayer = new Map<ITiledMapLayer, string>();
|
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");
|
||||||
@@ -43,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 = get(LL).message.openWebsiteTabTrigger();
|
message = get(LL).trigger.newTab();
|
||||||
}
|
}
|
||||||
layoutManagerActionStore.addAction({
|
layoutManagerActionStore.addAction({
|
||||||
uuid: "openTab",
|
uuid: "openTab",
|
||||||
@@ -58,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 = () => {
|
||||||
@@ -104,92 +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;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coWebsitesOpenByLayer.set(layer, {
|
const coWebsiteOpen: OpenCoWebsite = {
|
||||||
coWebsite: undefined,
|
actionId: actionId,
|
||||||
state: OpenCoWebsiteState.LOADING,
|
|
||||||
});
|
|
||||||
|
|
||||||
const openWebsiteFunction = () => {
|
|
||||||
coWebsiteManager
|
|
||||||
.loadCoWebsite(
|
|
||||||
openWebsiteProperty as string,
|
|
||||||
this.scene.MapUrlFile,
|
|
||||||
allowApiProperty,
|
|
||||||
websitePolicyProperty,
|
|
||||||
websiteWidthProperty,
|
|
||||||
websitePositionProperty
|
|
||||||
)
|
|
||||||
.then((coWebsite) => {
|
|
||||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
|
||||||
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
|
|
||||||
coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => console.error(e));
|
|
||||||
this.coWebsitesOpenByLayer.delete(layer);
|
|
||||||
this.coWebsitesActionTriggerByLayer.delete(layer);
|
|
||||||
} else {
|
|
||||||
this.coWebsitesOpenByLayer.set(layer, {
|
|
||||||
coWebsite,
|
|
||||||
state: OpenCoWebsiteState.OPENED,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => console.error(e));
|
|
||||||
|
|
||||||
layoutManagerActionStore.removeAction(actionUuid);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createWebsiteTrigger = () => {
|
this.coWebsitesOpenByLayer.set(layer, coWebsiteOpen);
|
||||||
|
|
||||||
|
const loadCoWebsiteFunction = (coWebsite: CoWebsite) => {
|
||||||
|
coWebsiteManager.loadCoWebsite(coWebsite).catch(() => {
|
||||||
|
console.error("Error during loading a co-website: " + coWebsite.getUrl());
|
||||||
|
});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
localUserStore.getForceCowebsiteTrigger() ||
|
||||||
|
websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON
|
||||||
|
) {
|
||||||
if (!websiteTriggerMessageProperty) {
|
if (!websiteTriggerMessageProperty) {
|
||||||
websiteTriggerMessageProperty = get(LL).message.openWebsiteTrigger();
|
websiteTriggerMessageProperty = get(LL).trigger.cowebsite();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coWebsitesOpenByLayer.set(layer, {
|
this.coWebsitesActionTriggerByLayer.set(layer, actionId);
|
||||||
coWebsite: undefined,
|
|
||||||
state: OpenCoWebsiteState.TRIGGER,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.coWebsitesActionTriggerByLayer.set(layer, actionUuid);
|
|
||||||
|
|
||||||
layoutManagerActionStore.addAction({
|
layoutManagerActionStore.addAction({
|
||||||
uuid: actionUuid,
|
uuid: actionId,
|
||||||
type: "message",
|
type: "message",
|
||||||
message: websiteTriggerMessageProperty,
|
message: websiteTriggerMessageProperty,
|
||||||
callback: () => openWebsiteFunction(),
|
callback: () => openCoWebsiteFunction(),
|
||||||
userInputManager: this.scene.userInputManager,
|
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
|
||||||
|
);
|
||||||
|
|
||||||
this.coWebsitesIframeListeners.set(
|
coWebsiteOpen.coWebsite = coWebsite;
|
||||||
layer,
|
|
||||||
iframeListener.unregisterIFrameStream.subscribe(() => {
|
|
||||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
|
||||||
if (
|
|
||||||
coWebsiteOpen?.coWebsite?.state == CoWebsiteState.CLOSED &&
|
|
||||||
(!websiteTriggerProperty || websiteTriggerProperty !== ON_ACTION_TRIGGER_DISABLE)
|
|
||||||
) {
|
|
||||||
createWebsiteTrigger();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
|
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
|
||||||
if (
|
}
|
||||||
forceTrigger ||
|
|
||||||
(websiteTriggerProperty && websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON)
|
|
||||||
) {
|
|
||||||
createWebsiteTrigger();
|
|
||||||
} else {
|
|
||||||
this.coWebsitesOpenByLayer.set(layer, {
|
|
||||||
coWebsite: undefined,
|
|
||||||
state: OpenCoWebsiteState.LOADING,
|
|
||||||
});
|
|
||||||
|
|
||||||
openWebsiteFunction();
|
if (!websiteTriggerProperty) {
|
||||||
|
openCoWebsiteFunction();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -223,37 +327,24 @@ export class GameMapPropertiesListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coWebsiteIframeListener = this.coWebsitesIframeListeners.get(layer);
|
|
||||||
|
|
||||||
if (coWebsiteIframeListener) {
|
|
||||||
coWebsiteIframeListener.unsubscribe();
|
|
||||||
this.coWebsitesIframeListeners.delete(layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
||||||
|
|
||||||
if (!coWebsiteOpen) {
|
if (!coWebsiteOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) {
|
const coWebsite = coWebsiteOpen.coWebsite;
|
||||||
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (coWebsite) {
|
||||||
coWebsiteOpen.state !== OpenCoWebsiteState.OPENED &&
|
coWebsiteManager.closeCoWebsite(coWebsite);
|
||||||
coWebsiteOpen.state !== OpenCoWebsiteState.TRIGGER
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coWebsiteOpen.coWebsite !== undefined) {
|
|
||||||
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.coWebsitesOpenByLayer.delete(layer);
|
this.coWebsitesOpenByLayer.delete(layer);
|
||||||
|
|
||||||
|
if (!websiteTriggerProperty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const actionStore = get(layoutManagerActionStore);
|
const actionStore = get(layoutManagerActionStore);
|
||||||
const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer);
|
const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer);
|
||||||
|
|
||||||
@@ -269,6 +360,8 @@ export class GameMapPropertiesListener {
|
|||||||
if (action) {
|
if (action) {
|
||||||
layoutManagerActionStore.removeAction(actionTriggerUuid);
|
layoutManagerActionStore.removeAction(actionTriggerUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.coWebsitesActionTriggerByLayer.delete(layer);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+176
-287
@@ -5,14 +5,14 @@ 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";
|
||||||
import { gameManager } from "./GameManager";
|
import { gameManager } from "./GameManager";
|
||||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||||
import { PinchManager } from "../UserInput/PinchManager";
|
import { PinchManager } from "../UserInput/PinchManager";
|
||||||
import { waScaleManager, WaScaleManagerEvent } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { EmoteManager } from "./EmoteManager";
|
import { EmoteManager } from "./EmoteManager";
|
||||||
import { soundManager } from "./SoundManager";
|
import { soundManager } from "./SoundManager";
|
||||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||||
@@ -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";
|
||||||
@@ -49,6 +48,7 @@ import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
|
|||||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
import { GameMapProperties } from "./GameMapProperties";
|
import { GameMapProperties } from "./GameMapProperties";
|
||||||
import { PathfindingManager } from "../../Utils/PathfindingManager";
|
import { PathfindingManager } from "../../Utils/PathfindingManager";
|
||||||
|
import { ActivatablesManager } from "./ActivatablesManager";
|
||||||
import type {
|
import type {
|
||||||
GroupCreatedUpdatedMessageInterface,
|
GroupCreatedUpdatedMessageInterface,
|
||||||
MessageUserMovedInterface,
|
MessageUserMovedInterface,
|
||||||
@@ -75,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;
|
||||||
@@ -89,11 +89,14 @@ import { deepCopy } from "deep-copy-ts";
|
|||||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||||
import { MapStore } from "../../Stores/Utils/MapStore";
|
import { MapStore } from "../../Stores/Utils/MapStore";
|
||||||
import { followUsersColorStore } from "../../Stores/FollowStore";
|
import { followUsersColorStore } from "../../Stores/FollowStore";
|
||||||
import Camera = Phaser.Cameras.Scene2D.Camera;
|
|
||||||
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
|
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
|
||||||
import { locale, LL } from "../../i18n/i18n-svelte";
|
import { locale } from "../../i18n/i18n-svelte";
|
||||||
import { i18nJson } from "../../i18n/locales";
|
import { i18nJson } from "../../i18n/locales";
|
||||||
|
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;
|
||||||
@@ -190,8 +193,6 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
private gameMap!: GameMap;
|
private gameMap!: GameMap;
|
||||||
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
||||||
// The item that can be selected by pressing the space key.
|
|
||||||
private outlinedItem: ActionableItem | null = null;
|
|
||||||
public userInputManager!: UserInputManager;
|
public userInputManager!: UserInputManager;
|
||||||
private isReconnecting: boolean | undefined = undefined;
|
private isReconnecting: boolean | undefined = undefined;
|
||||||
private playerName!: string;
|
private playerName!: string;
|
||||||
@@ -205,6 +206,7 @@ export class GameScene extends DirtyScene {
|
|||||||
private emoteManager!: EmoteManager;
|
private emoteManager!: EmoteManager;
|
||||||
private cameraManager!: CameraManager;
|
private cameraManager!: CameraManager;
|
||||||
private pathfindingManager!: PathfindingManager;
|
private pathfindingManager!: PathfindingManager;
|
||||||
|
private activatablesManager!: ActivatablesManager;
|
||||||
private preloading: boolean = true;
|
private preloading: boolean = true;
|
||||||
private startPositionCalculator!: StartPositionCalculator;
|
private startPositionCalculator!: StartPositionCalculator;
|
||||||
private sharedVariablesManager!: SharedVariablesManager;
|
private sharedVariablesManager!: SharedVariablesManager;
|
||||||
@@ -255,7 +257,7 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
this.load.audio("audio-webrtc-in", "/resources/objects/webrtc-in.mp3");
|
this.load.audio("audio-webrtc-in", "/resources/objects/webrtc-in.mp3");
|
||||||
this.load.audio("audio-webrtc-out", "/resources/objects/webrtc-out.mp3");
|
this.load.audio("audio-webrtc-out", "/resources/objects/webrtc-out.mp3");
|
||||||
//this.load.audio('audio-report-message', '/resources/objects/report-message.mp3');
|
this.load.audio("audio-report-message", "/resources/objects/report-message.mp3");
|
||||||
this.sound.pauseOnBlur = false;
|
this.sound.pauseOnBlur = false;
|
||||||
|
|
||||||
this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
|
this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
|
||||||
@@ -561,6 +563,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>();
|
||||||
|
|
||||||
@@ -580,7 +584,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()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -588,12 +592,22 @@ 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 },
|
||||||
waScaleManager
|
waScaleManager
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.pathfindingManager = new PathfindingManager(
|
||||||
|
this,
|
||||||
|
this.gameMap.getCollisionGrid(),
|
||||||
|
this.gameMap.getTileDimensions()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
|
||||||
|
|
||||||
biggestAvailableAreaStore.recompute();
|
biggestAvailableAreaStore.recompute();
|
||||||
this.cameraManager.startFollowPlayer(this.CurrentPlayer);
|
this.cameraManager.startFollowPlayer(this.CurrentPlayer);
|
||||||
|
|
||||||
@@ -639,7 +653,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();
|
||||||
@@ -650,13 +663,9 @@ export class GameScene extends DirtyScene {
|
|||||||
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
|
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
|
||||||
const newPeerNumber = peers.size;
|
const newPeerNumber = peers.size;
|
||||||
if (newPeerNumber > oldPeerNumber) {
|
if (newPeerNumber > oldPeerNumber) {
|
||||||
this.sound.play("audio-webrtc-in", {
|
this.playSound("audio-webrtc-in");
|
||||||
volume: 0.2,
|
|
||||||
});
|
|
||||||
} else if (newPeerNumber < oldPeerNumber) {
|
} else if (newPeerNumber < oldPeerNumber) {
|
||||||
this.sound.play("audio-webrtc-out", {
|
this.playSound("audio-webrtc-out");
|
||||||
volume: 0.2,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
oldPeerNumber = newPeerNumber;
|
oldPeerNumber = newPeerNumber;
|
||||||
});
|
});
|
||||||
@@ -679,10 +688,10 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => {
|
this.followUsersColorStoreUnsubscribe = followUsersColorStore.subscribe((color) => {
|
||||||
if (color !== undefined) {
|
if (color !== undefined) {
|
||||||
this.CurrentPlayer.setOutlineColor(color);
|
this.CurrentPlayer.setFollowOutlineColor(color);
|
||||||
this.connection?.emitPlayerOutlineColor(color);
|
this.connection?.emitPlayerOutlineColor(color);
|
||||||
} else {
|
} else {
|
||||||
this.CurrentPlayer.removeOutlineColor();
|
this.CurrentPlayer.removeFollowOutlineColor();
|
||||||
this.connection?.emitPlayerOutlineColor(null);
|
this.connection?.emitPlayerOutlineColor(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -699,10 +708,6 @@ export class GameScene extends DirtyScene {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public activateOutlinedItem(): void {
|
|
||||||
this.outlinedItem?.activate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the connection to Pusher.
|
* Initializes the connection to Pusher.
|
||||||
*/
|
*/
|
||||||
@@ -814,7 +819,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) => {
|
||||||
@@ -825,11 +842,8 @@ export class GameScene extends DirtyScene {
|
|||||||
this.simplePeer = new SimplePeer(this.connection);
|
this.simplePeer = new SimplePeer(this.connection);
|
||||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||||
|
|
||||||
//listen event to share position of user
|
|
||||||
this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this));
|
|
||||||
this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this));
|
|
||||||
this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => {
|
this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => {
|
||||||
this.gameMap.setPosition(event.x, event.y);
|
this.handleCurrentPlayerHasMovedEvent(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up variables manager
|
// Set up variables manager
|
||||||
@@ -954,103 +968,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 = get(LL).message.openJitsiTrigger();
|
|
||||||
}
|
|
||||||
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(
|
||||||
@@ -1296,21 +1213,20 @@ export class GameScene extends DirtyScene {
|
|||||||
throw new Error("Unknown query source");
|
throw new Error("Unknown query source");
|
||||||
}
|
}
|
||||||
|
|
||||||
const coWebsite = await coWebsiteManager.loadCoWebsite(
|
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
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!coWebsite) {
|
if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
|
||||||
throw new Error("Error on opening co-website");
|
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: coWebsite.iframe.id,
|
id: coWebsite.getId(),
|
||||||
position: coWebsite.position,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1319,28 +1235,23 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
return coWebsites.map((coWebsite: CoWebsite) => {
|
return coWebsites.map((coWebsite: CoWebsite) => {
|
||||||
return {
|
return {
|
||||||
id: coWebsite.iframe.id,
|
id: coWebsite.getId(),
|
||||||
position: coWebsite.position,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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("getProperty", (data) => {
|
iframeListener.registerAnswerer("getProperty", (data) => {
|
||||||
@@ -1439,7 +1350,7 @@ export class GameScene extends DirtyScene {
|
|||||||
//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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1493,12 +1404,12 @@ export class GameScene extends DirtyScene {
|
|||||||
const green = normalizeColor(message.green);
|
const green = normalizeColor(message.green);
|
||||||
const blue = normalizeColor(message.blue);
|
const blue = normalizeColor(message.blue);
|
||||||
const color = (red << 16) | (green << 8) | blue;
|
const color = (red << 16) | (green << 8) | blue;
|
||||||
this.CurrentPlayer.setOutlineColor(color);
|
this.CurrentPlayer.setApiOutlineColor(color);
|
||||||
this.connection?.emitPlayerOutlineColor(color);
|
this.connection?.emitPlayerOutlineColor(color);
|
||||||
});
|
});
|
||||||
|
|
||||||
iframeListener.registerAnswerer("removePlayerOutline", (message) => {
|
iframeListener.registerAnswerer("removePlayerOutline", (message) => {
|
||||||
this.CurrentPlayer.removeOutlineColor();
|
this.CurrentPlayer.removeApiOutlineColor();
|
||||||
this.connection?.emitPlayerOutlineColor(null);
|
this.connection?.emitPlayerOutlineColor(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1510,9 +1421,9 @@ export class GameScene extends DirtyScene {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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");
|
||||||
@@ -1552,14 +1463,15 @@ export class GameScene extends DirtyScene {
|
|||||||
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;
|
||||||
|
|
||||||
@@ -1610,16 +1522,21 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public playSound(sound: string) {
|
||||||
|
this.sound.play(sound, {
|
||||||
|
volume: 0.2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -1647,7 +1564,10 @@ export class GameScene extends DirtyScene {
|
|||||||
this.sharedVariablesManager?.close();
|
this.sharedVariablesManager?.close();
|
||||||
this.embeddedWebsiteManager?.close();
|
this.embeddedWebsiteManager?.close();
|
||||||
|
|
||||||
mediaManager.hideGameOverlay();
|
//When we leave game, the camera is stop to be reopen after.
|
||||||
|
// I think that we could keep camera status and the scene can manage camera setup
|
||||||
|
//TODO find wy chrome don't manage correctly a multiple ask mediaDevices
|
||||||
|
//mediaManager.hideMyCamera();
|
||||||
|
|
||||||
for (const iframeEvents of this.iframeSubscriptionList) {
|
for (const iframeEvents of this.iframeSubscriptionList) {
|
||||||
iframeEvents.unsubscribe();
|
iframeEvents.unsubscribe();
|
||||||
@@ -1667,6 +1587,36 @@ export class GameScene extends DirtyScene {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@@ -1722,7 +1672,18 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createCollisionWithPlayer() {
|
private handleCurrentPlayerHasMovedEvent(event: HasPlayerMovedEvent): void {
|
||||||
|
//listen event to share position of user
|
||||||
|
this.pushPlayerPosition(event);
|
||||||
|
this.gameMap.setPosition(event.x, event.y);
|
||||||
|
this.activatablesManager.updateActivatableObjectsDistances([
|
||||||
|
...Array.from(this.MapPlayersByKey.values()),
|
||||||
|
...this.actionableItems.values(),
|
||||||
|
]);
|
||||||
|
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createCollisionWithPlayer() {
|
||||||
//add collision layer
|
//add collision layer
|
||||||
for (const phaserLayer of this.gameMap.phaserLayers) {
|
for (const phaserLayer of this.gameMap.phaserLayers) {
|
||||||
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
|
this.physics.add.collider(this.CurrentPlayer, phaserLayer, (object1: GameObject, object2: GameObject) => {
|
||||||
@@ -1741,7 +1702,7 @@ export class GameScene extends DirtyScene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createCurrentPlayer() {
|
private createCurrentPlayer() {
|
||||||
//TODO create animation moving between exit and start
|
//TODO create animation moving between exit and start
|
||||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
||||||
try {
|
try {
|
||||||
@@ -1756,7 +1717,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.companion,
|
this.companion,
|
||||||
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
|
||||||
);
|
);
|
||||||
this.CurrentPlayer.on("pointerdown", (pointer: Phaser.Input.Pointer) => {
|
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_DOWN, (pointer: Phaser.Input.Pointer) => {
|
||||||
if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) {
|
if (pointer.wasTouch && (pointer.event as TouchEvent).touches.length > 1) {
|
||||||
return; //we don't want the menu to open when pinching on a touch screen.
|
return; //we don't want the menu to open when pinching on a touch screen.
|
||||||
}
|
}
|
||||||
@@ -1768,26 +1729,16 @@ export class GameScene extends DirtyScene {
|
|||||||
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());
|
||||||
@@ -1799,7 +1750,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.createCollisionWithPlayer();
|
this.createCollisionWithPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
pushPlayerPosition(event: HasPlayerMovedEvent) {
|
private pushPlayerPosition(event: HasPlayerMovedEvent) {
|
||||||
if (this.lastMoveEventSent === event) {
|
if (this.lastMoveEventSent === event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1825,49 +1776,6 @@ export class GameScene extends DirtyScene {
|
|||||||
// Otherwise, do nothing.
|
// Otherwise, do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the correct item to outline and outline it (if there is an item to be outlined)
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
private outlineItem(event: HasPlayerMovedEvent): void {
|
|
||||||
let x = event.x;
|
|
||||||
let y = event.y;
|
|
||||||
switch (event.direction) {
|
|
||||||
case PlayerAnimationDirections.Up:
|
|
||||||
y -= 32;
|
|
||||||
break;
|
|
||||||
case PlayerAnimationDirections.Down:
|
|
||||||
y += 32;
|
|
||||||
break;
|
|
||||||
case PlayerAnimationDirections.Left:
|
|
||||||
x -= 32;
|
|
||||||
break;
|
|
||||||
case PlayerAnimationDirections.Right:
|
|
||||||
x += 32;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Unexpected direction "' + event.direction + '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
let shortestDistance: number = Infinity;
|
|
||||||
let selectedItem: ActionableItem | null = null;
|
|
||||||
for (const item of this.actionableItems.values()) {
|
|
||||||
const distance = item.actionableDistance(x, y);
|
|
||||||
if (distance !== null && distance < shortestDistance) {
|
|
||||||
shortestDistance = distance;
|
|
||||||
selectedItem = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.outlinedItem === selectedItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outlinedItem?.notSelectable();
|
|
||||||
this.outlinedItem = selectedItem;
|
|
||||||
this.outlinedItem?.selectable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private doPushPlayerPosition(event: HasPlayerMovedEvent): void {
|
private doPushPlayerPosition(event: HasPlayerMovedEvent): void {
|
||||||
this.lastMoveEventSent = event;
|
this.lastMoveEventSent = event;
|
||||||
this.lastSentTick = this.currentTick;
|
this.lastSentTick = this.currentTick;
|
||||||
@@ -1885,7 +1793,7 @@ export class GameScene extends DirtyScene {
|
|||||||
* @param time
|
* @param time
|
||||||
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
||||||
*/
|
*/
|
||||||
update(time: number, delta: number): void {
|
public update(time: number, delta: number): void {
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
this.currentTick = time;
|
this.currentTick = time;
|
||||||
this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick());
|
this.CurrentPlayer.moveUser(delta, this.userInputManager.getEventListForGameTick());
|
||||||
@@ -1904,9 +1812,15 @@ export class GameScene extends DirtyScene {
|
|||||||
case "RemovePlayerEvent":
|
case "RemovePlayerEvent":
|
||||||
this.doRemovePlayer(event.userId);
|
this.doRemovePlayer(event.userId);
|
||||||
break;
|
break;
|
||||||
case "UserMovedEvent":
|
case "UserMovedEvent": {
|
||||||
this.doUpdatePlayerPosition(event.event);
|
this.doUpdatePlayerPosition(event.event);
|
||||||
|
const remotePlayer = this.MapPlayersByKey.get(event.event.userId);
|
||||||
|
if (remotePlayer) {
|
||||||
|
this.activatablesManager.updateDistanceForSingleActivatableObject(remotePlayer);
|
||||||
|
this.activatablesManager.deduceSelectedActivatableObjectByDistance();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "GroupCreatedUpdatedEvent":
|
case "GroupCreatedUpdatedEvent":
|
||||||
this.doShareGroupPosition(event.event);
|
this.doShareGroupPosition(event.event);
|
||||||
break;
|
break;
|
||||||
@@ -1993,11 +1907,21 @@ export class GameScene extends DirtyScene {
|
|||||||
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
|
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
|
||||||
);
|
);
|
||||||
if (addPlayerData.outlineColor !== undefined) {
|
if (addPlayerData.outlineColor !== undefined) {
|
||||||
player.setOutlineColor(addPlayerData.outlineColor);
|
player.setApiOutlineColor(addPlayerData.outlineColor);
|
||||||
}
|
}
|
||||||
this.MapPlayers.add(player);
|
this.MapPlayers.add(player);
|
||||||
this.MapPlayersByKey.set(player.userId, player);
|
this.MapPlayersByKey.set(player.userId, player);
|
||||||
player.updatePosition(addPlayerData.position);
|
player.updatePosition(addPlayerData.position);
|
||||||
|
|
||||||
|
player.on(Phaser.Input.Events.POINTER_OVER, () => {
|
||||||
|
this.activatablesManager.handlePointerOverActivatableObject(player);
|
||||||
|
this.markDirty();
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on(Phaser.Input.Events.POINTER_OUT, () => {
|
||||||
|
this.activatablesManager.handlePointerOutActivatableObject();
|
||||||
|
this.markDirty();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2027,7 +1951,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.playersPositionInterpolator.removePlayer(userId);
|
this.playersPositionInterpolator.removePlayer(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updatePlayerPosition(message: MessageUserMovedInterface): void {
|
private updatePlayerPosition(message: MessageUserMovedInterface): void {
|
||||||
this.pendingEvents.enqueue({
|
this.pendingEvents.enqueue({
|
||||||
type: "UserMovedEvent",
|
type: "UserMovedEvent",
|
||||||
event: message,
|
event: message,
|
||||||
@@ -2057,7 +1981,7 @@ export class GameScene extends DirtyScene {
|
|||||||
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
|
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
private shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) {
|
||||||
this.pendingEvents.enqueue({
|
this.pendingEvents.enqueue({
|
||||||
type: "GroupCreatedUpdatedEvent",
|
type: "GroupCreatedUpdatedEvent",
|
||||||
event: groupPositionMessage,
|
event: groupPositionMessage,
|
||||||
@@ -2109,9 +2033,9 @@ export class GameScene extends DirtyScene {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message.removeOutlineColor) {
|
if (message.removeOutlineColor) {
|
||||||
character.removeOutlineColor();
|
character.removeApiOutlineColor();
|
||||||
} else {
|
} else {
|
||||||
character.setOutlineColor(message.outlineColor);
|
character.setApiOutlineColor(message.outlineColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2156,7 +2080,18 @@ export class GameScene extends DirtyScene {
|
|||||||
biggestAvailableAreaStore.recompute();
|
biggestAvailableAreaStore.recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public startJitsi(roomName: string, jwt?: string): void {
|
public enableMediaBehaviors() {
|
||||||
|
const silent = this.gameMap.getCurrentProperties().get(GameMapProperties.SILENT);
|
||||||
|
this.connection?.setSilent(!!silent);
|
||||||
|
mediaManager.showMyCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
public disableMediaBehaviors() {
|
||||||
|
this.connection?.setSilent(true);
|
||||||
|
mediaManager.hideMyCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -2167,71 +2102,21 @@ export class GameScene extends DirtyScene {
|
|||||||
GameMapProperties.JITSI_INTERFACE_CONFIG
|
GameMapProperties.JITSI_INTERFACE_CONFIG
|
||||||
);
|
);
|
||||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||||
const jitsiWidth = allProps.get(GameMapProperties.JITSI_WIDTH) as number | undefined;
|
|
||||||
|
|
||||||
const jitsiKeepCircle = allProps.get("jitsiKeepCircle") as boolean | false;
|
coWebsite.setJitsiLoadPromise(() => {
|
||||||
|
return jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||||
jitsiFactory
|
|
||||||
.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth)
|
|
||||||
.catch((e) => console.error(e));
|
|
||||||
this.connection?.setSilent(true);
|
|
||||||
mediaManager.hideGameOverlay();
|
|
||||||
|
|
||||||
if (jitsiKeepCircle) {
|
|
||||||
const silent = this.gameMap.getCurrentProperties().get("silent");
|
|
||||||
this.connection?.setSilent(!!silent);
|
|
||||||
mediaManager.showGameOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
analyticsClient.enteredJitsi(roomName, this.room.id);
|
|
||||||
|
|
||||||
//permit to stop jitsi when user close iframe
|
|
||||||
mediaManager.addTriggerCloseJitsiFrameButton("close-jitsi", () => {
|
|
||||||
this.stopJitsi();
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
public stopJitsi(): void {
|
coWebsiteManager.loadCoWebsite(coWebsite).catch((err) => {
|
||||||
const silent = this.gameMap.getCurrentProperties().get(GameMapProperties.SILENT);
|
console.error(err);
|
||||||
this.connection?.setSilent(!!silent);
|
});
|
||||||
jitsiFactory.stop();
|
|
||||||
mediaManager.showGameOverlay();
|
|
||||||
|
|
||||||
const allProps = this.gameMap.getCurrentProperties();
|
if (allProps.get("jitsiKeepCircle") as boolean | false) {
|
||||||
|
this.enableMediaBehaviors();
|
||||||
if (allProps.get("jitsiRoom") === undefined) {
|
|
||||||
layoutManagerActionStore.removeAction("jitsi");
|
|
||||||
} else {
|
} else {
|
||||||
const openJitsiRoomFunction = () => {
|
this.disableMediaBehaviors();
|
||||||
const roomName = jitsiFactory.getRoomName(
|
|
||||||
allProps.get(GameMapProperties.JITSI_ROOM) as string,
|
|
||||||
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 && this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
|
||||||
} else {
|
|
||||||
this.startJitsi(roomName, undefined);
|
|
||||||
}
|
|
||||||
layoutManagerActionStore.removeAction("jitsi");
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
analyticsClient.enteredJitsi(roomName, this.room.id);
|
||||||
mediaManager.removeTriggerCloseJitsiFrameButton("close-jitsi");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||||
@@ -2307,4 +2192,8 @@ export class GameScene extends DirtyScene {
|
|||||||
public getPathfindingManager(): PathfindingManager {
|
public getPathfindingManager(): PathfindingManager {
|
||||||
return this.pathfindingManager;
|
return this.pathfindingManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getActivatablesManager(): ActivatablesManager {
|
||||||
|
return this.activatablesManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface OutlineableInterface {
|
||||||
|
setFollowOutlineColor(color: number): void;
|
||||||
|
removeFollowOutlineColor(): void;
|
||||||
|
setApiOutlineColor(color: number): void;
|
||||||
|
removeApiOutlineColor(): void;
|
||||||
|
pointerOverOutline(color: number): void;
|
||||||
|
pointerOutOutline(): void;
|
||||||
|
characterCloseByOutline(color: number): void;
|
||||||
|
characterFarAwayOutline(): void;
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ export class PlayerMovement {
|
|||||||
oldX: this.startPosition.x,
|
oldX: this.startPosition.x,
|
||||||
oldY: this.startPosition.y,
|
oldY: this.startPosition.y,
|
||||||
direction: this.endPosition.direction,
|
direction: this.endPosition.direction,
|
||||||
moving: this.endPosition.moving,
|
moving: this.isOutdated(tick) ? false : this.endPosition.moving,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -77,6 +51,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import type { GameScene } from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||||
|
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||||
|
|
||||||
type EventCallback = (state: unknown, parameters: unknown) => void;
|
type EventCallback = (state: unknown, parameters: unknown) => void;
|
||||||
|
|
||||||
export class ActionableItem {
|
export class ActionableItem implements ActivatableInterface {
|
||||||
private readonly activationRadiusSquared: number;
|
private readonly activationRadiusSquared: number;
|
||||||
private isSelectable: boolean = false;
|
private isSelectable: boolean = false;
|
||||||
private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>();
|
private callbacks: Map<string, Array<EventCallback>> = new Map<string, Array<EventCallback>>();
|
||||||
@@ -17,7 +18,7 @@ export class ActionableItem {
|
|||||||
private id: number,
|
private id: number,
|
||||||
private sprite: Sprite,
|
private sprite: Sprite,
|
||||||
private eventHandler: GameScene,
|
private eventHandler: GameScene,
|
||||||
private activationRadius: number,
|
public readonly activationRadius: number,
|
||||||
private onActivateCallback: (item: ActionableItem) => void
|
private onActivateCallback: (item: ActionableItem) => void
|
||||||
) {
|
) {
|
||||||
this.activationRadiusSquared = activationRadius * activationRadius;
|
this.activationRadiusSquared = activationRadius * activationRadius;
|
||||||
@@ -40,6 +41,10 @@ export class ActionableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPosition(): { x: number; y: number } {
|
||||||
|
return { x: this.sprite.x, y: this.sprite.y };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the outline of the sprite.
|
* Show the outline of the sprite.
|
||||||
*/
|
*/
|
||||||
@@ -70,9 +75,10 @@ export class ActionableItem {
|
|||||||
return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
return this.sprite.scene.plugins.get("rexOutlinePipeline") as unknown as OutlinePipelinePlugin | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public isActivatable(): boolean {
|
||||||
* Triggered when the "space" key is pressed and the object is in range of being activated.
|
return this.isSelectable;
|
||||||
*/
|
}
|
||||||
|
|
||||||
public activate(): void {
|
public activate(): void {
|
||||||
this.onActivateCallback(this);
|
this.onActivateCallback(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
|||||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||||
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||||
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||||
const textures = this.getTextures();
|
const textures = this.getTextures();
|
||||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||||
if (textures) {
|
if (textures) {
|
||||||
for (const texture of textures) {
|
for (const texture of textures) {
|
||||||
if (texture.level === -1) {
|
if (texture.level === -1) {
|
||||||
@@ -21,7 +22,7 @@ export abstract class AbstractCharacterScene extends ResizableScene {
|
|||||||
|
|
||||||
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||||
const textures = this.getTextures();
|
const textures = this.getTextures();
|
||||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||||
if (textures) {
|
if (textures) {
|
||||||
for (const texture of textures) {
|
for (const texture of textures) {
|
||||||
if (texture.level !== -1) {
|
if (texture.level !== -1) {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
|||||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||||
import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
|
import { activeRowStore, customCharacterSceneVisibleStore } from "../../Stores/CustomCharacterStore";
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
|
||||||
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
export const CustomizeSceneName = "CustomizeScene";
|
export const CustomizeSceneName = "CustomizeScene";
|
||||||
|
|
||||||
@@ -67,12 +67,12 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
customCharacterSceneVisibleStore.set(true);
|
customCharacterSceneVisibleStore.set(true);
|
||||||
this.events.addListener("wake", () => {
|
this.events.addListener("wake", () => {
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||||
customCharacterSceneVisibleStore.set(true);
|
customCharacterSceneVisibleStore.set(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 3 : 1;
|
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 3 : 1;
|
||||||
|
|
||||||
this.Rectangle = this.add.rectangle(
|
this.Rectangle = this.add.rectangle(
|
||||||
this.cameras.main.worldView.x + this.cameras.main.width / 2,
|
this.cameras.main.worldView.x + this.cameras.main.width / 2,
|
||||||
@@ -289,7 +289,6 @@ export class CustomizeScene extends AbstractCharacterScene {
|
|||||||
gameManager.setCharacterLayers(layers);
|
gameManager.setCharacterLayers(layers);
|
||||||
this.scene.sleep(CustomizeSceneName);
|
this.scene.sleep(CustomizeSceneName);
|
||||||
waScaleManager.restoreZoom();
|
waScaleManager.restoreZoom();
|
||||||
this.events.removeListener("wake");
|
|
||||||
gameManager.tryResumingGame(EnableCameraSceneName);
|
gameManager.tryResumingGame(EnableCameraSceneName);
|
||||||
customCharacterSceneVisibleStore.set(false);
|
customCharacterSceneVisibleStore.set(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import LL from "../../i18n/i18n-svelte";
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { localeDetector } from "../../i18n/locales";
|
import { localeDetector } from "../../i18n/locales";
|
||||||
|
|
||||||
const $LL = get(LL);
|
|
||||||
|
|
||||||
export const EntrySceneName = "EntryScene";
|
export const EntrySceneName = "EntryScene";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +41,7 @@ export class EntryScene extends Scene {
|
|||||||
this.scene.start(nextSceneName);
|
this.scene.start(nextSceneName);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
const $LL = get(LL);
|
||||||
if (err.response && err.response.status == 404) {
|
if (err.response && err.response.status == 404) {
|
||||||
ErrorScene.showError(
|
ErrorScene.showError(
|
||||||
new WAError(
|
new WAError(
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
|||||||
import { PinchManager } from "../UserInput/PinchManager";
|
import { PinchManager } from "../UserInput/PinchManager";
|
||||||
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterStore";
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
|
||||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||||
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
//todo: put this constants in a dedicated file
|
//todo: put this constants in a dedicated file
|
||||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||||
@@ -60,7 +60,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
selectCharacterSceneVisibleStore.set(true);
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
this.events.addListener("wake", () => {
|
this.events.addListener("wake", () => {
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
||||||
selectCharacterSceneVisibleStore.set(true);
|
selectCharacterSceneVisibleStore.set(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
||||||
|
|
||||||
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16;
|
||||||
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xffffff);
|
this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xffffff);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
|||||||
import { PinchManager } from "../UserInput/PinchManager";
|
import { PinchManager } from "../UserInput/PinchManager";
|
||||||
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
|
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
|
||||||
import { waScaleManager } from "../Services/WaScaleManager";
|
import { waScaleManager } from "../Services/WaScaleManager";
|
||||||
import { isMobile } from "../../Enum/EnvironmentVariable";
|
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||||
|
|
||||||
export const SelectCompanionSceneName = "SelectCompanionScene";
|
export const SelectCompanionSceneName = "SelectCompanionScene";
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export class SelectCompanionScene extends ResizableScene {
|
|||||||
selectCompanionSceneVisibleStore.set(true);
|
selectCompanionSceneVisibleStore.set(true);
|
||||||
|
|
||||||
waScaleManager.saveZoom();
|
waScaleManager.saveZoom();
|
||||||
waScaleManager.zoomModifier = isMobile() ? 2 : 1;
|
waScaleManager.zoomModifier = isMediaBreakpointUp("md") ? 2 : 1;
|
||||||
|
|
||||||
if (touchScreenManager.supportTouchScreen) {
|
if (touchScreenManager.supportTouchScreen) {
|
||||||
new PinchManager(this);
|
new PinchManager(this);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Character } from "../Entity/Character";
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { userMovingStore } from "../../Stores/GameStore";
|
import { userMovingStore } from "../../Stores/GameStore";
|
||||||
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
import { followStateStore, followRoleStore, followUsersStore } from "../../Stores/FollowStore";
|
||||||
|
import type CancelablePromise from "cancelable-promise";
|
||||||
|
|
||||||
export const hasMovedEventName = "hasMoved";
|
export const hasMovedEventName = "hasMoved";
|
||||||
export const requestEmoteEventName = "requestEmote";
|
export const requestEmoteEventName = "requestEmote";
|
||||||
@@ -20,11 +21,11 @@ export class Player extends Character {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
name: string,
|
name: string,
|
||||||
texturesPromise: Promise<string[]>,
|
texturesPromise: CancelablePromise<string[]>,
|
||||||
direction: PlayerAnimationDirections,
|
direction: PlayerAnimationDirections,
|
||||||
moving: boolean,
|
moving: boolean,
|
||||||
companion: string | null,
|
companion: string | null,
|
||||||
companionTexturePromise?: Promise<string>
|
companionTexturePromise?: CancelablePromise<string>
|
||||||
) {
|
) {
|
||||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
|
||||||
|
|
||||||
|
|||||||
@@ -37,19 +37,17 @@ export class WaScaleManager {
|
|||||||
height: height * devicePixelRatio,
|
height: height * devicePixelRatio,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (gameSize.width == 0) {
|
if (realSize.width !== 0 && gameSize.width !== 0 && devicePixelRatio !== 0) {
|
||||||
return;
|
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
|
|
||||||
|
|
||||||
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
|
|
||||||
this.scaleManager.resize(gameSize.width, gameSize.height);
|
this.scaleManager.resize(gameSize.width, gameSize.height);
|
||||||
|
this.scaleManager.setZoom(this.actualZoom);
|
||||||
|
|
||||||
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
|
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
|
||||||
const style = this.scaleManager.canvas.style;
|
const style = this.scaleManager.canvas.style;
|
||||||
style.width = Math.ceil(realSize.width / devicePixelRatio) + "px";
|
style.width = Math.ceil(realSize.width !== 0 ? realSize.width / devicePixelRatio : 0) + "px";
|
||||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + "px";
|
style.height = Math.ceil(realSize.height !== 0 ? realSize.height / devicePixelRatio : 0) + "px";
|
||||||
|
|
||||||
// Resize the game element at the same size at the canvas
|
// Resize the game element at the same size at the canvas
|
||||||
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>("game").style;
|
const gameStyle = HtmlUtils.getElementByIdOrFail<HTMLDivElement>("game").style;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Player } from "../Player/Player";
|
||||||
|
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||||
|
|
||||||
import type { UserInputHandlerInterface } from "../../Interfaces/UserInputHandlerInterface";
|
import type { UserInputHandlerInterface } from "../../Interfaces/UserInputHandlerInterface";
|
||||||
import type { GameScene } from "../Game/GameScene";
|
import type { GameScene } from "../Game/GameScene";
|
||||||
|
|
||||||
@@ -23,10 +26,16 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public handlePointerUpEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {
|
public handlePointerUpEvent(pointer: Phaser.Input.Pointer, gameObjects: Phaser.GameObjects.GameObject[]): void {
|
||||||
if (pointer.rightButtonReleased() || pointer.getDuration() > 250) {
|
if ((!pointer.wasTouch && pointer.leftButtonReleased()) || pointer.getDuration() > 250) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const object of gameObjects) {
|
||||||
|
if (object instanceof Player || object instanceof RemotePlayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.lastTime > 0 &&
|
this.lastTime > 0 &&
|
||||||
pointer.time - this.lastTime < 500 &&
|
pointer.time - this.lastTime < 500 &&
|
||||||
@@ -46,7 +55,7 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
|||||||
.then((path) => {
|
.then((path) => {
|
||||||
// Remove first step as it is for the tile we are currently standing on
|
// Remove first step as it is for the tile we are currently standing on
|
||||||
path.shift();
|
path.shift();
|
||||||
this.gameScene.CurrentPlayer.setPathToFollow(path).catch((reason) => {});
|
this.gameScene.CurrentPlayer.setPathToFollow(path).catch((reason) => { });
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
console.warn(reason);
|
console.warn(reason);
|
||||||
@@ -58,10 +67,23 @@ export class GameSceneUserInputHandler implements UserInputHandlerInterface {
|
|||||||
this.lastY = pointer.y;
|
this.lastY = pointer.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
this.gameScene.activateOutlinedItem();
|
const activatableManager = this.gameScene.getActivatablesManager();
|
||||||
|
const activatable = activatableManager.getSelectedActivatableObject();
|
||||||
|
if (activatable && activatable.isActivatable() && activatableManager.isSelectingByDistanceEnabled()) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export interface ActionsMenuData {
|
||||||
|
playerName: string;
|
||||||
|
actions: { actionName: string; callback: Function }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createActionsMenuStore() {
|
||||||
|
const { subscribe, update, set } = writable<ActionsMenuData | undefined>(undefined);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
initialize: (playerName: string) => {
|
||||||
|
set({
|
||||||
|
playerName,
|
||||||
|
actions: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addAction: (actionName: string, callback: Function) => {
|
||||||
|
update((data) => {
|
||||||
|
data?.actions.push({ actionName, callback });
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeAction: (actionName: string) => {
|
||||||
|
update((data) => {
|
||||||
|
const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName);
|
||||||
|
if (actionIndex !== undefined && actionIndex != -1) {
|
||||||
|
data?.actions.splice(actionIndex, 1);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Hides menu
|
||||||
|
*/
|
||||||
|
clear: () => {
|
||||||
|
set(undefined);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actionsMenuStore = createActionsMenuStore();
|
||||||
@@ -2,14 +2,14 @@ import { get, writable } from "svelte/store";
|
|||||||
import type { Box } from "../WebRtc/LayoutManager";
|
import type { Box } from "../WebRtc/LayoutManager";
|
||||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||||
import { layoutModeStore } from "./StreamableCollectionStore";
|
import { embedScreenLayout } from "./EmbedScreensStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
||||||
*/
|
*/
|
||||||
function findBiggestAvailableArea(): Box {
|
function findBiggestAvailableArea(): Box {
|
||||||
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>("#game canvas");
|
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>("#game canvas");
|
||||||
if (get(layoutModeStore) === LayoutMode.VideoChat) {
|
if (get(embedScreenLayout) === LayoutMode.VideoChat) {
|
||||||
const children = document.querySelectorAll<HTMLDivElement>("div.chat-mode > div");
|
const children = document.querySelectorAll<HTMLDivElement>("div.chat-mode > div");
|
||||||
const htmlChildren = Array.from(children.values());
|
const htmlChildren = Array.from(children.values());
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user