Merge branch 'develop' of github.com:thecodingmachine/workadventure into metadataScriptingApi

This commit is contained in:
GRL 2021-05-18 11:50:03 +02:00
commit 2ee62c9e9e
84 changed files with 2218 additions and 1648 deletions

View file

@ -10,6 +10,8 @@ START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
# 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=
DISABLE_NOTIFICATIONS=true
SKIP_RENDER_OPTIMIZATIONS=false
# The email address used by Let's encrypt to send renewal warnings (compulsory)
ACME_EMAIL=

View file

@ -2,7 +2,7 @@ name: Build, push and deploy Docker image
on:
push:
branch: [master]
branches: [master]
release:
types: [created]
pull_request:
@ -16,7 +16,7 @@ env:
jobs:
build-front:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -36,11 +36,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-front
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-back:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -59,11 +59,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-back
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-pusher:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -82,11 +82,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-pusher
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-uploader:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -105,11 +105,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-uploader
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
build-maps:
if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest
steps:
@ -129,7 +129,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-maps
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
add_git_labels: true
deeploy:
@ -140,7 +140,7 @@ jobs:
- build-maps
- build-uploader
runs-on: ubuntu-latest
if: ${{ github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
steps:
- name: Checkout
@ -158,13 +158,13 @@ jobs:
JITSI_URL: ${{ secrets.JITSI_URL }}
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
DEPLOY_REF: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }}
DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
with:
namespace: workadventure-${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }}
namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
- name: Add a comment in PR
uses: unsplash/comment-on-pr@v1.2.0
if: ${{ github.event.pull_request }}
if: ${{ github.event_name == 'pull_request' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View file

@ -49,7 +49,7 @@ jobs:
- name: "Build"
run: yarn run build
env:
API_URL: "localhost:8080"
PUSHER_URL: "//localhost:8080"
working-directory: "front"
- name: "Lint"

View file

@ -2,7 +2,7 @@
local env = std.extVar("env"),
local namespace = env.DEPLOY_REF,
local tag = namespace,
local url = if namespace == "master" then "workadventu.re" else namespace+".test.workadventu.re",
local url = namespace+".test.workadventu.re",
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
@ -25,10 +25,7 @@
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
} else {})
},
"back2": {
"image": "thecodingmachine/workadventure-back:"+tag,
@ -47,10 +44,7 @@
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
} else {})
},
"pusher": {
"replicas": 2,
@ -69,10 +63,7 @@
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
} + (if adminUrl != null then {
"ADMIN_API_URL": adminUrl,
} else {}) + if namespace != "master" then {
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
} else {})
},
"front": {
"image": "thecodingmachine/workadventure-front:"+tag,

View file

@ -33,6 +33,8 @@ services:
STARTUP_COMMAND_1: ./templater.sh
STARTUP_COMMAND_2: yarn install
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
TURN_USER: ""

View file

@ -33,6 +33,8 @@ services:
STARTUP_COMMAND_2: yarn install
STUN_SERVER: "stun:stun.l.google.com:19302"
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
TURN_USER: ""

View file

@ -25,6 +25,15 @@
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "error"
"@typescript-eslint/no-explicit-any": "error",
// TODO: remove those ignored rules and write a stronger code!
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/restrict-plus-operands": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/restrict-template-expressions": "off"
}
}

View file

@ -38,6 +38,8 @@
<div class="main-container" id="main-container">
<!-- Create the editor container -->
<div id="game" class="game">
<div id="svelte-overlay">
</div>
<div id="game-overlay" class="game-overlay">
<div id="main-section" class="main-section">
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -4,25 +4,34 @@
"main": "index.js",
"license": "SEE LICENSE IN LICENSE.txt",
"devDependencies": {
"@tsconfig/svelte": "^1.0.10",
"@types/google-protobuf": "^3.7.3",
"@types/jasmine": "^3.5.10",
"@types/mini-css-extract-plugin": "^1.4.3",
"@types/node": "^15.3.0",
"@types/quill": "^1.3.7",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"css-loader": "^5.1.3",
"eslint": "^6.8.0",
"html-webpack-plugin": "^4.3.0",
"@types/webpack-dev-server": "^3.11.4",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"css-loader": "^5.2.4",
"eslint": "^7.26.0",
"fork-ts-checker-webpack-plugin": "^6.2.9",
"html-webpack-plugin": "^5.3.1",
"jasmine": "^3.5.0",
"mini-css-extract-plugin": "^1.3.9",
"sass": "^1.32.8",
"sass-loader": "10.1.1",
"ts-loader": "^6.2.2",
"ts-node": "^8.10.2",
"typescript": "^3.8.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2"
"mini-css-extract-plugin": "^1.6.0",
"node-polyfill-webpack-plugin": "^1.1.2",
"sass": "^1.32.12",
"sass-loader": "^11.1.0",
"svelte": "^3.38.2",
"svelte-loader": "^3.1.1",
"svelte-preprocess": "^4.7.3",
"ts-loader": "^9.1.2",
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.2.4",
"webpack": "^5.37.0",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2"
},
"dependencies": {
"@types/simple-peer": "^9.6.0",
@ -36,13 +45,12 @@
"quill": "1.3.6",
"rxjs": "^6.6.3",
"simple-peer": "^9.6.2",
"socket.io-client": "^2.3.0",
"webpack-require-http": "^0.4.3"
"socket.io-client": "^2.3.0"
},
"scripts": {
"start": "webpack-dev-server --open",
"build": "webpack --config webpack.prod.js",
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
"build": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
}

View file

@ -1,7 +1,7 @@
import {HtmlUtils} from "../WebRtc/HtmlUtils";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {RoomConnection} from "../Connexion/RoomConnection";
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import type {RoomConnection} from "../Connexion/RoomConnection";
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
export const CLASS_CONSOLE_MESSAGE = 'main-console';

View file

@ -1,8 +1,8 @@
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
import type {RoomConnection} from "../Connexion/RoomConnection";
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
export class GlobalMessageManager {

View file

@ -1,4 +1,4 @@
import {TypeMessageInterface} from "./UserMessageManager";
import type {TypeMessageInterface} from "./UserMessageManager";
import {HtmlUtils} from "../WebRtc/HtmlUtils";
let modalTimeOut : NodeJS.Timeout;
@ -86,4 +86,4 @@ export class Banned extends TypeMessageExt {
showMessage(message: string){
super.showMessage(message, false);
}
}
}

View file

@ -1,16 +1,16 @@
import { ButtonClickedEvent } from './ButtonClickedEvent';
import { ChatEvent } from './ChatEvent';
import { ClosePopupEvent } from './ClosePopupEvent';
import { EnterLeaveEvent } from './EnterLeaveEvent';
import { GoToPageEvent } from './GoToPageEvent';
import { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
import { OpenPopupEvent } from './OpenPopupEvent';
import { OpenTabEvent } from './OpenTabEvent';
import { UserInputChatEvent } from './UserInputChatEvent';
import { LayerEvent } from './LayerEvent';
import { SetPropertyEvent } from "./setPropertyEvent";
import type { ButtonClickedEvent } from './ButtonClickedEvent';
import type { ChatEvent } from './ChatEvent';
import type { ClosePopupEvent } from './ClosePopupEvent';
import type { EnterLeaveEvent } from './EnterLeaveEvent';
import type { GoToPageEvent } from './GoToPageEvent';
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
import type { OpenPopupEvent } from './OpenPopupEvent';
import type { OpenTabEvent } from './OpenTabEvent';
import type { UserInputChatEvent } from './UserInputChatEvent';
import type { LayerEvent } from './LayerEvent';
import type { SetPropertyEvent } from "./setPropertyEvent";
export interface TypedMessageEvent<T> extends MessageEvent {

View file

@ -1,17 +1,16 @@
import { Subject } from "rxjs";
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
import * as crypto from "crypto";
import { HtmlUtils } from "../WebRtc/HtmlUtils";
import { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
import { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
import { scriptUtils } from "./ScriptUtils";
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
import { UserInputChatEvent } from "./Events/UserInputChatEvent";
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
import { isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
@ -193,7 +192,7 @@ class IframeListener {
}
private getIFrameId(scriptUrl: string): string {
return 'script' + crypto.createHash('md5').update(scriptUrl).digest("hex");
return 'script' + btoa(scriptUrl);
}
unregisterScript(scriptUrl: string): void {

View file

@ -0,0 +1,11 @@
<script lang="typescript">
import MenuIcon from "./Menu/MenuIcon.svelte";
import {menuIconVisible} from "../Stores/MenuStore";
</script>
<div>
<!-- {#if $menuIconVisible}
<MenuIcon />
{/if} -->
</div>

View file

@ -0,0 +1,33 @@
<script lang="typescript">
</script>
<main class="menuIcon">
<section>
<button>
<img src="/static/images/menu.svg" alt="Open menu">
</button>
</section>
</main>
<style lang="scss">
.menuIcon button {
background-color: black;
color: white;
border-radius: 7px;
padding: 2px 8px;
img {
width: 14px;
padding-top: 0;
/*cursor: url('/resources/logos/cursor_pointer.png'), pointer;*/
}
}
.menuIcon section {
margin: 10px;
}
@media only screen and (max-height: 700px) {
.menuIcon section {
margin: 2px;
}
}
</style>

View file

@ -1,5 +1,5 @@
import {Subject} from "rxjs";
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
import type {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
export enum AdminMessageEventTypes {
admin = 'message',
@ -19,11 +19,11 @@ interface AdminMessageEvent {
class AdminMessagesService {
private _messageStream: Subject<AdminMessageEvent> = new Subject();
public messageStream = this._messageStream.asObservable();
constructor() {
this.messageStream.subscribe((event) => console.log('message', event))
}
onSendusermessage(message: SendUserMessage|BanUserMessage) {
this._messageStream.next({
type: message.getType() as unknown as AdminMessageEventTypes,
@ -32,4 +32,4 @@ class AdminMessagesService {
}
}
export const adminMessagesService = new AdminMessagesService();
export const adminMessagesService = new AdminMessagesService();

View file

@ -1,7 +1,7 @@
import Axios from "axios";
import {PUSHER_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "./RoomConnection";
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
import {localUserStore} from "./LocalUserStore";
import {LocalUser} from "./LocalUser";

View file

@ -1,8 +1,8 @@
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import {SignalData} from "simple-peer";
import {RoomConnection} from "./RoomConnection";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
import type {SignalData} from "simple-peer";
import type {RoomConnection} from "./RoomConnection";
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
export enum EventMessage{
CONNECT = "connect",

View file

@ -27,11 +27,11 @@ import {
SendJitsiJwtMessage,
CharacterLayerMessage,
PingMessage,
SendUserMessage,
SendUserMessage,
BanUserMessage
} from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import Direction = PositionMessage.Direction;
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
import {
@ -42,7 +42,7 @@ import {
ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
import {adminMessagesService} from "./AdminMessagesService";
import {worldFullMessageStream} from "./WorldFullMessageStream";
import {worldFullWarningStream} from "./WorldFullWarningStream";
@ -86,7 +86,7 @@ export class RoomConnection implements RoomConnection {
url += '&bottom='+Math.floor(viewport.bottom);
url += '&left='+Math.floor(viewport.left);
url += '&right='+Math.floor(viewport.right);
if (typeof companion === 'string') {
url += '&companion='+encodeURIComponent(companion);
}

View file

@ -1,11 +1,11 @@
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost";
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
const TURN_SERVER: string = process.env.TURN_SERVER || "";
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
const TURN_USER: string = process.env.TURN_USER || '';
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
@ -20,9 +20,10 @@ export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window
export {
DEBUG_MODE,
START_ROOM_URL,
SKIP_RENDER_OPTIMIZATIONS,
DISABLE_NOTIFICATIONS,
PUSHER_URL,
UPLOADER_URL,
ADMIN_URL,
POSITION_DELAY,
MAX_EXTRAPOLATION_TIME,
STUN_SERVER,

View file

@ -1,6 +1,6 @@
import {PositionMessage} from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
import {PointInterface} from "../Connexion/ConnexionModels";
import type {PointInterface} from "../Connexion/ConnexionModels";
export class ProtobufClientUtils {

View file

@ -1,6 +1,5 @@
import Sprite = Phaser.GameObjects.Sprite;
import Container = Phaser.GameObjects.Container;
import { lazyLoadCompanionResource } from "./CompanionTexturesLoadingManager";
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
export interface CompanionStatus {
@ -25,7 +24,7 @@ export class Companion extends Container {
constructor(scene: Phaser.Scene, x: number, y: number, name: string, texturePromise: Promise<string>) {
super(scene, x + 14, y + 4);
this.sprites = new Map<string, Sprite>();
this.delta = 0;
@ -104,7 +103,7 @@ export class Companion extends Container {
}
}
}
this.setDepth(this.y);
this.playAnimation(this.direction, this.animationType);
}
@ -137,7 +136,7 @@ export class Companion extends Container {
this.getAnimations(resource).forEach(animation => {
this.scene.anims.create(animation);
});
this.scene.sys.updateList.add(sprite);
this.sprites.set(resource, sprite);
}

View file

@ -1,5 +1,4 @@
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
import ScaleManager = Phaser.Scale.ScaleManager;
import {waScaleManager} from "../Services/WaScaleManager";
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free

View file

@ -17,14 +17,12 @@ export class SoundMeter {
}
private init(context: AudioContext) {
if (this.context === undefined) {
this.context = context;
this.analyser = this.context.createAnalyser();
this.context = context;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength);
}
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.dataArray = new Uint8Array(bufferLength);
}
public connectToSource(stream: MediaStream, context: AudioContext): void

View file

@ -1,5 +1,5 @@
import Container = Phaser.GameObjects.Container;
import {Scene} from "phaser";
import type {Scene} from "phaser";
import GameObject = Phaser.GameObjects.GameObject;
import Rectangle = Phaser.GameObjects.Rectangle;

View file

@ -1,7 +1,5 @@
import {ITiledMapObject} from "../Map/ITiledMap";
import Text = Phaser.GameObjects.Text;
import {GameScene} from "../Game/GameScene";
import TextStyle = Phaser.GameObjects.TextStyle;
import type {ITiledMapObject} from "../Map/ITiledMap";
import type {GameScene} from "../Game/GameScene";
export class TextUtils {
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {

View file

@ -1,6 +1,5 @@
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import TextureManager = Phaser.Textures.TextureManager;
import {CharacterTexture} from "../../Connexion/LocalUser";
import type {CharacterTexture} from "../../Connexion/LocalUser";
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";

View file

@ -1,7 +1,7 @@
import {GameScene} from "../Game/GameScene";
import {PointInterface} from "../../Connexion/ConnexionModels";
import type {GameScene} from "../Game/GameScene";
import type {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import {PlayerAnimationDirections} from "../Player/Animation";
import type {PlayerAnimationDirections} from "../Player/Animation";
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
@ -22,7 +22,7 @@ export class RemotePlayer extends Character {
companionTexturePromise?: Promise<string>
) {
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
//set data
this.userId = userId;
@ -35,9 +35,9 @@ export class RemotePlayer extends Character {
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
this.setX(position.x);
this.setY(position.y);
this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen).
if (this.companion) {
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
}

View file

@ -1,12 +1,12 @@
import Scene = Phaser.Scene;
import {Character} from "./Character";
import type {Character} from "./Character";
//todo: improve this WIP
export class SpeechBubble {
private bubble: Phaser.GameObjects.Graphics;
private content: Phaser.GameObjects.Text;
constructor(scene: Scene, player: Character, text: string = "") {
const bubbleHeight = 50;

View file

@ -1,5 +1,5 @@
import {PointInterface} from "../../Connexion/ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import type {PointInterface} from "../../Connexion/ConnexionModels";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
export interface AddPlayerInterface {
userId: number;

View file

@ -2,6 +2,8 @@ import {ResizableScene} from "../Login/ResizableScene";
import GameObject = Phaser.GameObjects.GameObject;
import Events = Phaser.Scenes.Events;
import AnimationEvents = Phaser.Animations.Events;
import StructEvents = Phaser.Structs.Events;
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
/**
* A scene that can track its dirty/pristine state.
@ -18,17 +20,16 @@ export abstract class DirtyScene extends ResizableScene {
* Note: this does not work with animations from sprites inside containers.
*/
protected trackDirtyAnims(): void {
if (this.isAlreadyTracking) {
if (this.isAlreadyTracking || SKIP_RENDER_OPTIMIZATIONS) {
return;
}
this.isAlreadyTracking = true;
const trackAnimationFunction = this.trackAnimation.bind(this);
this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => {
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_ADD, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});
this.events.on(Events.REMOVED_FROM_SCENE, (gameObject: GameObject) => {
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_REMOVE, (gameObject: GameObject) => {
this.objectListChanged = true;
gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
});

View file

@ -1,3 +1,8 @@
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {waScaleManager} from "../Services/WaScaleManager";
import {ResizableScene} from "../Login/ResizableScene";
const Events = Phaser.Core.Events;
/**
@ -5,8 +10,27 @@ const Events = Phaser.Core.Events;
* It comes with an optimization to skip rendering.
*
* Beware, the "step" function might vary in future versions of Phaser.
*
* It also automatically calls "onResize" on any scenes extending ResizableScene.
*/
export class Game extends Phaser.Game {
private _isDirty = false;
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
super(GameConfig);
window.addEventListener('resize', (event) => {
// Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of this.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize(event);
}
}
});
}
public step(time: number, delta: number)
{
// @ts-ignore
@ -35,7 +59,7 @@ export class Game extends Phaser.Game {
eventEmitter.emit(Events.POST_STEP, time, delta);
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
if (this.isDirty()) {
if (SKIP_RENDER_OPTIMIZATIONS || this.isDirty()) {
const renderer = this.renderer;
// Run the Pre-render (clearing the canvas, setting background colors, etc)
@ -62,6 +86,11 @@ export class Game extends Phaser.Game {
}
private isDirty(): boolean {
if (this._isDirty) {
this._isDirty = false;
return true;
}
// Loop through the scenes in forward order
for (let i = 0; i < this.scene.scenes.length; i++)
{
@ -85,4 +114,11 @@ export class Game extends Phaser.Game {
return false;
}
/**
* Marks the game as needing to be redrawn.
*/
public markDirty(): void {
this._isDirty = true;
}
}

View file

@ -1,6 +1,6 @@
import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {Room} from "../../Connexion/Room";
import type {Room} from "../../Connexion/Room";
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
import {LoginSceneName} from "../Login/LoginScene";
@ -24,7 +24,7 @@ export class GameManager {
private companion: string|null;
private startRoom!:Room;
currentGameSceneName: string|null = null;
constructor() {
this.playerName = localUserStore.getName();
this.characterLayers = localUserStore.getCharacterLayers();
@ -65,7 +65,7 @@ export class GameManager {
return this.characterLayers;
}
setCompanion(companion: string|null): void {
this.companion = companion;
}
@ -91,7 +91,7 @@ export class GameManager {
scenePlugin.launch(MenuSceneName);
scenePlugin.launch(HelpCameraSettingsSceneName);//700
}
public gameSceneIsCreated(scene: GameScene) {
this.currentGameSceneName = scene.scene.key;
const menuScene: MenuScene = scene.scene.get(MenuSceneName) as MenuScene;
@ -125,7 +125,7 @@ export class GameManager {
scene.scene.run(fallbackSceneName)
}
}
public getCurrentGameScene(scene: Phaser.Scene): GameScene {
if (this.currentGameSceneName === null) throw 'No current scene id set!';
return scene.scene.get(this.currentGameSceneName) as GameScene

View file

@ -1,4 +1,4 @@
import {ITiledMap, ITiledMapLayer, ITiledMapTileLayer} from "../Map/ITiledMap";
import type {ITiledMap, ITiledMapLayer, ITiledMapTileLayer} from "../Map/ITiledMap";
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;

View file

@ -1,5 +1,5 @@
import {gameManager, HasMovedEvent} from "./GameManager";
import {
import type {
GroupCreatedUpdatedMessageInterface,
MessageUserJoined,
MessageUserMovedInterface,
@ -16,7 +16,7 @@ import {
MAX_PER_GROUP,
POSITION_DELAY,
} from "../../Enum/EnvironmentVariable";
import {
import type {
ITiledMap,
ITiledMapLayer,
ITiledMapLayerProperty,
@ -25,7 +25,7 @@ import {
ITiledMapTileLayer,
ITiledTileSet
} from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface";
import type {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
@ -49,13 +49,13 @@ import {
import {GameMap} from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager";
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
import {ActionableItem} from "../Items/ActionableItem";
import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
import type {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager";
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
import type {UserMovedMessage} from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection";
import type {RoomConnection} from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
import {userMessageManager} from "../../Administration/UserMessageManager";
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
@ -80,7 +80,7 @@ import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import DOMElement = Phaser.GameObjects.DOMElement;
import {Subscription} from "rxjs";
import type {Subscription} from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import RenderTexture = Phaser.GameObjects.RenderTexture;
@ -152,7 +152,7 @@ export class GameScene extends DirtyScene implements CenterListener {
private GlobalMessageManager!: GlobalMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
private connectionAnswerPromiseResolve!: (value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
// A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
@ -187,6 +187,9 @@ export class GameScene extends DirtyScene implements CenterListener {
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined;
private pinchManager: PinchManager|undefined;
private physicsEnabled: boolean = true;
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
private onVisibilityChangeCallback: () => void;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({
@ -202,10 +205,11 @@ export class GameScene extends DirtyScene implements CenterListener {
this.createPromise = new Promise<void>((resolve, reject): void => {
this.createPromiseResolve = resolve;
})
});
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve;
});
this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this);
}
//hook preload scene
@ -495,6 +499,8 @@ export class GameScene extends DirtyScene implements CenterListener {
if (!this.room.isDisconnected()) {
this.connect();
}
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
}
/**
@ -616,6 +622,7 @@ export class GameScene extends DirtyScene implements CenterListener {
self.chatModeSprite.setVisible(false);
self.openChatIcon.setVisible(false);
audioManager.restoreVolume();
self.onVisibilityChange();
}
}
})
@ -916,6 +923,8 @@ ${escapedMessage}
}
private onMapExit(exitKey: string) {
if (this.mapTransitioning) return;
this.mapTransitioning = true;
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
urlManager.pushStartLayerNameToUrl(hash);
@ -933,6 +942,7 @@ ${escapedMessage}
this.initPositionFromLayerName(hash || defaultStartLayerName);
this.CurrentPlayer.x = this.startX;
this.CurrentPlayer.y = this.startY;
setTimeout(() => this.mapTransitioning = false, 500);
}
}
@ -958,6 +968,8 @@ ${escapedMessage}
for(const iframeEvents of this.iframeSubscriptionList){
iframeEvents.unsubscribe();
}
document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback);
}
private removeAllRemotePlayers(): void {
@ -1107,6 +1119,7 @@ ${escapedMessage}
createCollisionWithPlayer() {
this.physics.disableUpdate();
this.physicsEnabled = false;
//add collision layer
for (const Layer of this.gameMap.flatLayers) {
if (Layer.type == "tilelayer") {
@ -1251,12 +1264,15 @@ ${escapedMessage}
this.CurrentPlayer.moveUser(delta);
if (this.CurrentPlayer.isMoving()) {
this.dirty = true;
this.physics.enableUpdate();
} else {
if (!this.physicsEnabled) {
this.physics.enableUpdate();
this.physicsEnabled = true;
}
} else if (this.physicsEnabled) {
this.physics.disableUpdate();
this.physicsEnabled = false;
}
// Let's handle all events
while (this.pendingEvents.length !== 0) {
this.dirty = true;
@ -1523,6 +1539,8 @@ ${escapedMessage}
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
this.stopJitsi();
});
this.onVisibilityChange();
}
public stopJitsi(): void {
@ -1531,6 +1549,7 @@ ${escapedMessage}
mediaManager.showGameOverlay();
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
this.onVisibilityChange();
}
//todo: put this into an 'orchestrator' scene (EntryScene?)
@ -1570,4 +1589,20 @@ ${escapedMessage}
waScaleManager.zoomModifier *= zoomFactor;
this.updateCameraOffset();
}
private onVisibilityChange(): void {
// If the overlay is not displayed, we are in Jitsi. We don't need the webcam.
if (!mediaManager.isGameOverlayVisible()) {
mediaManager.blurCamera();
return;
}
if (document.visibilityState === 'visible') {
mediaManager.focusCamera();
} else {
if (this.simplePeer.getNbConnections() === 0) {
mediaManager.blurCamera();
}
}
}
}

View file

@ -1,6 +1,6 @@
import {HasMovedEvent} from "./GameManager";
import type {HasMovedEvent} from "./GameManager";
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
import {PositionInterface} from "../../Connexion/ConnexionModels";
import type {PositionInterface} from "../../Connexion/ConnexionModels";
export class PlayerMovement {
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {

View file

@ -2,8 +2,8 @@
* This class is in charge of computing the position of all players.
* Player movement is delayed by 200ms so position depends on ticks.
*/
import {PlayerMovement} from "./PlayerMovement";
import {HasMovedEvent} from "./GameManager";
import type {PlayerMovement} from "./PlayerMovement";
import type {HasMovedEvent} from "./GameManager";
export class PlayersPositionInterpolator {
playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();

View file

@ -4,7 +4,7 @@
*/
import Sprite = Phaser.GameObjects.Sprite;
import {OutlinePipeline} from "../Shaders/OutlinePipeline";
import {GameScene} from "../Game/GameScene";
import type {GameScene} from "../Game/GameScene";
type EventCallback = (state: unknown, parameters: unknown) => void;

View file

@ -1,9 +1,9 @@
import * as Phaser from 'phaser';
import {Scene} from "phaser";
import Sprite = Phaser.GameObjects.Sprite;
import {ITiledMapObject} from "../../Map/ITiledMap";
import {ItemFactoryInterface} from "../ItemFactoryInterface";
import {GameScene} from "../../Game/GameScene";
import type {ITiledMapObject} from "../../Map/ITiledMap";
import type {ItemFactoryInterface} from "../ItemFactoryInterface";
import type {GameScene} from "../../Game/GameScene";
import {ActionableItem} from "../ActionableItem";
import * as tg from "generic-type-guard";

View file

@ -1,7 +1,7 @@
import type {GameScene} from "../Game/GameScene";
import type {ITiledMapObject} from "../Map/ITiledMap";
import type {ActionableItem} from "./ActionableItem";
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import {GameScene} from "../Game/GameScene";
import {ITiledMapObject} from "../Map/ITiledMap";
import {ActionableItem} from "./ActionableItem";
export interface ItemFactoryInterface {
preload: (loader: LoaderPlugin) => void;

View file

@ -1,8 +1,8 @@
import {ResizableScene} from "./ResizableScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
import {CharacterTexture} from "../../Connexion/LocalUser";
import type {CharacterTexture} from "../../Connexion/LocalUser";
export abstract class AbstractCharacterScene extends ResizableScene {
@ -38,4 +38,4 @@ export abstract class AbstractCharacterScene extends ResizableScene {
const localUser = localUserStore.getLocalUser();
return localUser?.textures;
}
}
}

View file

@ -6,7 +6,7 @@ import Container = Phaser.GameObjects.Container;
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import { MenuScene } from "../Menu/MenuScene";

View file

@ -252,7 +252,6 @@ export class EnableCameraScene extends ResizableScene {
update(time: number, delta: number): void {
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
mediaManager.updateScene();
this.centerXDomElement(this.enableCameraSceneElement, 300);
}

View file

@ -1,18 +1,4 @@
import {gameManager} from "../Game/GameManager";
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {MenuScene} from "../Menu/MenuScene";
import { SelectCharacterScene, SelectCharacterSceneName } from "./SelectCharacterScene";
import { SelectCharacterScene } from "./SelectCharacterScene";
export class SelectCharacterMobileScene extends SelectCharacterScene {
@ -20,7 +6,7 @@ export class SelectCharacterMobileScene extends SelectCharacterScene {
super.create();
this.selectedRectangle.destroy();
}
protected defineSetupPlayer(numero: number){
const deltaX = 30;
const deltaY = 2;

View file

@ -1,18 +1,16 @@
import {gameManager} from "../Game/GameManager";
import Image = Phaser.GameObjects.Image;
import Rectangle = Phaser.GameObjects.Rectangle;
import {EnableCameraSceneName} from "./EnableCameraScene";
import {CustomizeSceneName} from "./CustomizeScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
import {addLoader} from "../Components/Loader";
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {MenuScene} from "../Menu/MenuScene";
import { SelectCharacterMobileScene } from "./SelectCharacterMobileScene";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";

View file

@ -5,7 +5,7 @@ import { gameManager} from "../Game/GameManager";
import { ResizableScene } from "./ResizableScene";
import { EnableCameraSceneName } from "./EnableCameraScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
import type { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";

View file

@ -1,10 +1,10 @@
import {ITiledMap, ITiledMapLayer} from "./ITiledMap";
import type {ITiledMap, ITiledMapLayer} from "./ITiledMap";
/**
* Flatten the grouped layers
*/
export function flattenGroupLayersMap(map: ITiledMap) {
let flatLayers: ITiledMapLayer[] = [];
const flatLayers: ITiledMapLayer[] = [];
flattenGroupLayers(map.layers, '', flatLayers);
return flatLayers;
}

View file

@ -109,15 +109,18 @@ export class HelpCameraSettingsScene extends DirtyScene {
public onResize(ev: UIEvent): void {
super.onResize(ev);
const middleX = this.getMiddleX();
const middleY = this.getMiddleY();
this.tweens.add({
targets: this.helpCameraSettingsElement,
x: middleX,
y: middleY,
duration: 1000,
ease: 'Power3'
});
if (this.helpCameraSettingsOpened) {
const middleX = this.getMiddleX();
const middleY = this.getMiddleY();
this.tweens.add({
targets: this.helpCameraSettingsElement,
x: middleX,
y: middleY,
duration: 1000,
ease: 'Power3'
});
this.dirty = true;
}
}
private getMiddleX() : number{

View file

@ -9,6 +9,7 @@ import {connectionManager} from "../../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../../Url/UrlManager";
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
import {menuIconVisible} from "../../Stores/MenuStore";
export const MenuSceneName = 'MenuScene';
const gameMenuKey = 'gameMenu';
@ -53,6 +54,7 @@ export class MenuScene extends Phaser.Scene {
}
create() {
menuIconVisible.set(true);
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
this.menuElement.setOrigin(0);
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');

View file

@ -1,5 +1,5 @@
import {PlayerAnimationDirections} from "./Animation";
import {GameScene} from "../Game/GameScene";
import type {GameScene} from "../Game/GameScene";
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
import {Character} from "../Entity/Character";

View file

@ -1,4 +1,3 @@
import ScaleManager = Phaser.Scale.ScaleManager;
interface Size {
width: number;
@ -13,8 +12,7 @@ export class HdpiManager {
* @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
* @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
*/
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
}
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {}
/**
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
@ -36,16 +34,12 @@ export class HdpiManager {
};
}
let i = 1;
while (realPixelNumber > this.minRecommendedGamePixelsNumber * i * i) {
i++;
}
const optimalZoomLevel = this.getOptimalZoomLevel(realPixelNumber);
// Has the canvas more pixels than the screen? This is forbidden
if ((i - 1) * this._zoomModifier < 1) {
if (optimalZoomLevel * this._zoomModifier < 1) {
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
this._zoomModifier = 1 / (i - 1);
this._zoomModifier = 1 / optimalZoomLevel;
return {
game: {
@ -59,8 +53,8 @@ export class HdpiManager {
}
}
const gameWidth = Math.ceil(realPixelScreenSize.width / (i - 1) / this._zoomModifier);
const gameHeight = Math.ceil(realPixelScreenSize.height / (i - 1) / this._zoomModifier);
const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier);
const gameHeight = Math.ceil(realPixelScreenSize.height / optimalZoomLevel / this._zoomModifier);
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
@ -68,7 +62,7 @@ export class HdpiManager {
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
this._zoomModifier = realPixelScreenSize.width / minGameWidth / (i - 1);
this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel;
return {
game: {
@ -89,12 +83,24 @@ export class HdpiManager {
height: gameHeight,
},
real: {
width: Math.ceil(realPixelScreenSize.width / (i - 1)) * (i - 1),
height: Math.ceil(realPixelScreenSize.height / (i - 1)) * (i - 1),
width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel,
height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel,
}
}
}
/**
* We only accept integer but we make an exception for 1.5
*/
private getOptimalZoomLevel(realPixelNumber: number): number {
const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
if (1.5 <= result && result < 2) {
return 1.5
} else {
return Math.floor(result);
}
}
public get zoomModifier(): number {
return this._zoomModifier;
}

View file

@ -1,18 +1,21 @@
import {HdpiManager} from "./HdpiManager";
import ScaleManager = Phaser.Scale.ScaleManager;
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import type {Game} from "../Game/Game";
class WaScaleManager {
private hdpiManager: HdpiManager;
private scaleManager!: ScaleManager;
private game!: Game;
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
}
public setScaleManager(scaleManager: ScaleManager) {
this.scaleManager = scaleManager;
public setGame(game: Game): void {
this.scaleManager = game.scale;
this.game = game;
}
public applyNewSize() {
@ -32,6 +35,8 @@ class WaScaleManager {
const style = this.scaleManager.canvas.style;
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
this.game.markDirty();
}
public get zoomModifier(): number {
@ -42,6 +47,7 @@ class WaScaleManager {
this.hdpiManager.zoomModifier = zoomModifier;
this.applyNewSize();
}
}
export const waScaleManager = new WaScaleManager(640*480, 196*196);

View file

@ -1,5 +1,5 @@
import { Direction } from "../../types";
import {GameScene} from "../Game/GameScene";
import type { Direction } from "../../types";
import type {GameScene} from "../Game/GameScene";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {MobileJoystick} from "../Components/MobileJoystick";
@ -173,7 +173,7 @@ export class UserInputManager {
}
destroy(): void {
this.joystick.destroy();
this.joystick?.destroy();
}
private initMouseWheel() {

View file

@ -0,0 +1,3 @@
import { derived, writable, Writable } from "svelte/store";
export const menuIconVisible = writable(false);

View file

@ -1,4 +1,4 @@
import {Room} from "../Connexion/Room";
import type {Room} from "../Connexion/Room";
export enum GameConnexionTypes {
anonymous=1,

View file

@ -32,7 +32,7 @@ class CoWebsiteManager {
private resizing: boolean = false;
private cowebsiteMainDom: HTMLDivElement;
private cowebsiteAsideDom: HTMLDivElement;
get width(): number {
return this.cowebsiteDiv.clientWidth;
}
@ -137,14 +137,14 @@ class CoWebsiteManager {
if (allowPolicy) {
iframe.allow = allowPolicy;
}
const onloadPromise = new Promise((resolve) => {
const onloadPromise = new Promise<void>((resolve) => {
iframe.onload = () => resolve();
});
if (allowApi) {
iframeListener.registerIframe(iframe);
}
this.cowebsiteMainDom.appendChild(iframe);
const onTimeoutPromise = new Promise((resolve) => {
const onTimeoutPromise = new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000);
});
this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => {

View file

@ -1,6 +1,6 @@
import {HtmlUtils} from "./HtmlUtils";
import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import type {ShowReportCallBack} from "./MediaManager";
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {iframeListener} from "../Api/IframeListener";

View file

@ -10,9 +10,10 @@ interface jitsiConfigInterface {
}
const getDefaultConfig = () : jitsiConfigInterface => {
const constraints = mediaManager.getConstraintRequestedByUser();
return {
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
startWithAudioMuted: !constraints.audio,
startWithVideoMuted: constraints.video === false,
prejoinPageEnabled: false
}
}
@ -71,7 +72,7 @@ class JitsiFactory {
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
private audioCallback = this.onAudioChange.bind(this);
private videoCallback = this.onVideoChange.bind(this);
private previousConfigMeet? : jitsiConfigInterface;
private previousConfigMeet! : jitsiConfigInterface;
private jitsiScriptLoaded: boolean = false;
/**
@ -136,32 +137,24 @@ class JitsiFactory {
//restore previous config
if(this.previousConfigMeet?.startWithAudioMuted){
mediaManager.disableMicrophone();
await mediaManager.disableMicrophone();
}else{
mediaManager.enableMicrophone();
await mediaManager.enableMicrophone();
}
if(this.previousConfigMeet?.startWithVideoMuted){
mediaManager.disableCamera();
await mediaManager.disableCamera();
}else{
mediaManager.enableCamera();
await mediaManager.enableCamera();
}
}
private onAudioChange({muted}: {muted: boolean}): void {
if (muted && mediaManager.constraintsMedia.audio === true) {
mediaManager.disableMicrophone();
} else if(!muted && mediaManager.constraintsMedia.audio === false) {
mediaManager.enableMicrophone();
}
this.previousConfigMeet.startWithAudioMuted = muted;
}
private onVideoChange({muted}: {muted: boolean}): void {
if (muted && mediaManager.constraintsMedia.video !== false) {
mediaManager.disableCamera();
} else if(!muted && mediaManager.constraintsMedia.video === false) {
mediaManager.enableCamera();
}
this.previousConfigMeet.startWithVideoMuted = muted;
}
private async loadJitsiScript(domain: string): Promise<void> {

View file

@ -1,4 +1,4 @@
import { UserInputManager } from "../Phaser/UserInput/UserInputManager";
import type { UserInputManager } from "../Phaser/UserInput/UserInputManager";
import {HtmlUtils} from "./HtmlUtils";
export enum LayoutMode {
@ -324,7 +324,7 @@ class LayoutManager {
public addActionButton(id: string, text: string, callBack: Function, userInputManager: UserInputManager){
//delete previous element
this.removeActionButton(id, userInputManager);
//create div and text html component
const p = document.createElement('p');
p.classList.add('action-body');

View file

@ -1,10 +1,11 @@
import {DivImportance, layoutManager} from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils";
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
import {localUserStore} from "../Connexion/LocalUserStore";
import {UserSimplePeerInterface} from "./SimplePeer";
import type {UserSimplePeerInterface} from "./SimplePeer";
import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
@ -20,7 +21,7 @@ const audioConstraint: boolean|MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl: false,
echoCancellation: true,
noiseSuppression: false
noiseSuppression: true
};
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
@ -43,7 +44,8 @@ export class MediaManager {
microphoneClose: HTMLImageElement;
microphone: HTMLImageElement;
webrtcInAudio: HTMLAudioElement;
mySoundMeterElement: HTMLDivElement;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement;
constraintsMedia : MediaStreamConstraints = {
audio: audioConstraint,
@ -54,7 +56,7 @@ export class MediaManager {
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
@ -62,18 +64,16 @@ export class MediaManager {
private previousConstraint : MediaStreamConstraints;
private focused : boolean = true;
private lastUpdateScene : Date = new Date();
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
private hasCamera = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
private userInputManager?: UserInputManager;
private mySoundMeter?: SoundMeter|null;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*private mySoundMeter?: SoundMeter|null;
private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>();
private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();*/
constructor() {
@ -132,17 +132,19 @@ export class MediaManager {
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
this.pingCameraStatus();
this.checkActiveUser(); //todo: desactivated in case of bug
this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
});
});*/
//Check of ask notification navigator permission
this.getNotification();
}
public updateScene(){
this.lastUpdateScene = new Date();
this.updateSoudMeter();
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//this.updateSoudMeter();
}
public blurCamera() {
@ -154,6 +156,13 @@ export class MediaManager {
this.disableCamera();
}
/**
* Returns the constraint that the user wants (independently of the visibility / jitsi state...)
*/
public getConstraintRequestedByUser(): MediaStreamConstraints {
return this.previousConstraint ?? this.constraintsMedia;
}
public focusCamera() {
if(this.focused){
return;
@ -196,7 +205,7 @@ export class MediaManager {
}
}
public showGameOverlay(){
public showGameOverlay(): void {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active');
@ -207,7 +216,7 @@ export class MediaManager {
buttonCloseFrame.removeEventListener('click', functionTrigger);
}
public hideGameOverlay(){
public hideGameOverlay(): void {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.remove('active');
@ -218,6 +227,11 @@ export class MediaManager {
buttonCloseFrame.addEventListener('click', functionTrigger);
}
public isGameOverlayVisible(): boolean {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
return gameOverlay.classList.contains('active');
}
public updateCameraQuality(value: number) {
this.enableCameraStyle();
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
@ -229,29 +243,32 @@ export class MediaManager {
});
}
public enableCamera() {
public async enableCamera() {
this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => {
try {
const stream = await this.getCamera()
//TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator
if(stream.getVideoTracks().length === 0) {
throw Error('Video track is empty, please check camera permission of your navigator')
throw new Error('Video track is empty, please check camera permission of your navigator')
}
this.enableCameraStyle();
this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => {
} catch(err) {
console.error(err);
this.disableCameraStyle();
this.stopCamera();
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
});
}
}
public async disableCamera() {
this.disableCameraStyle();
this.stopCamera();
if (this.constraintsMedia.audio !== false) {
const stream = await this.getCamera();
@ -261,25 +278,27 @@ export class MediaManager {
}
}
public enableMicrophone() {
public async enableMicrophone() {
this.constraintsMedia.audio = audioConstraint;
this.getCamera().then((stream) => {
try {
const stream = await this.getCamera();
//TODO show error message tooltip upper of camera button
//TODO message : please check microphone permission of your navigator
if(stream.getAudioTracks().length === 0) {
if (stream.getAudioTracks().length === 0) {
throw Error('Audio track is empty, please check microphone permission of your navigator')
}
this.enableMicrophoneStyle();
this.triggerUpdatedLocalStreamCallbacks(stream);
}).catch((err) => {
} catch(err) {
console.error(err);
this.disableMicrophoneStyle();
layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
this.showHelpCameraSettingsCallBack();
}, this.userInputManager);
});
}
}
public async disableMicrophone() {
@ -324,7 +343,6 @@ export class MediaManager {
this.cinemaBtn.classList.add("disabled");
this.constraintsMedia.video = false;
this.myCamVideo.srcObject = null;
this.stopCamera();
}
private enableMicrophoneStyle(){
@ -411,7 +429,7 @@ export class MediaManager {
}
private _startScreenCapture() {
if (navigator.getDisplayMedia) {
if (navigator.getDisplayMedia) {
return navigator.getDisplayMedia({video: true});
} else if (navigator.mediaDevices.getDisplayMedia) {
return navigator.mediaDevices.getDisplayMedia({video: true});
@ -435,6 +453,8 @@ export class MediaManager {
return this.getLocalStream().catch((err) => {
console.info('Error get camera, trying with video option at null =>', err);
this.disableCameraStyle();
this.stopCamera();
return this.getLocalStream().then((stream : MediaStream) => {
this.hasCamera = false;
return stream;
@ -457,12 +477,12 @@ export class MediaManager {
this.localStream = stream;
this.myCamVideo.srcObject = this.localStream;
//init sound meter
this.mySoundMeter = null;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.mySoundMeter = null;
if(this.constraintsMedia.audio){
this.mySoundMeter = new SoundMeter();
this.mySoundMeter.connectToSource(stream, new AudioContext());
}
}*/
return stream;
}).catch((err: Error) => {
throw err;
@ -489,7 +509,7 @@ export class MediaManager {
track.stop();
}
}
this.mySoundMeter?.stop();
//this.mySoundMeter?.stop();
}
setCamera(id: string): Promise<MediaStream> {
@ -546,7 +566,7 @@ export class MediaManager {
`;
layoutManager.add(DivImportance.Normal, userId, html);
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
//permit to create participant in discussion part
@ -564,7 +584,7 @@ export class MediaManager {
showReportUser();
});
}
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
userId = this.getScreenSharingId(userId);
@ -590,7 +610,7 @@ export class MediaManager {
}
element.classList.add('active') //todo: why does a method 'disable' add a class 'active'?
}
enabledMicrophoneByUserId(userId: number){
const element = document.getElementById(`microphone-${userId}`);
if(!element){
@ -598,7 +618,7 @@ export class MediaManager {
}
element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'?
}
disabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`);
if (element) {
@ -609,7 +629,7 @@ export class MediaManager {
element.style.display = "block";
}
}
enabledVideoByUserId(userId: number){
let element = document.getElementById(`${userId}`);
if(element){
@ -632,11 +652,12 @@ export class MediaManager {
}
remoteVideo.srcObject = stream;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//sound metter
const soundMeter = new SoundMeter();
/*const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext());
this.soundMeters.set(userId, soundMeter);
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));*/
}
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
@ -647,14 +668,15 @@ export class MediaManager {
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
}
removeActiveVideo(userId: string){
layoutManager.remove(userId);
this.remoteVideo.delete(userId);
this.soundMeters.get(userId)?.stop();
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*this.soundMeters.get(userId)?.stop();
this.soundMeters.delete(userId);
this.soundMeterElements.delete(userId);
this.soundMeterElements.delete(userId);*/
//permit to remove user in discussion part
this.removeParticipant(userId);
@ -662,7 +684,7 @@ export class MediaManager {
removeActiveScreenSharingVideo(userId: string) {
this.removeActiveVideo(this.getScreenSharingId(userId))
}
playWebrtcOutSound(): void {
this.webrtcOutAudio.play();
}
@ -708,7 +730,7 @@ export class MediaManager {
const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null;
return connnectingSpinnerDiv;
}
private getColorByString(str: String) : String|null {
let hash = 0;
if (str.length === 0) return null;
@ -776,22 +798,6 @@ export class MediaManager {
this.userInputManager = userInputManager;
discussionManager.setUserInputManager(userInputManager);
}
//check if user is active
private checkActiveUser(){
if(this.setTimeOutlastUpdateScene){
clearTimeout(this.setTimeOutlastUpdateScene);
}
this.setTimeOutlastUpdateScene = setTimeout(() => {
const now = new Date();
//if last update is more of 10 sec
if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) {
this.blurCamera();
}else{
this.focusCamera();
}
this.checkActiveUser();
}, this.focused ? 10000 : 1000);
}
public setShowReportModalCallBacks(callback: ShowReportCallBack){
this.showReportModalCallBacks.add(callback);
@ -807,11 +813,12 @@ export class MediaManager {
}
}
updateSoudMeter(){
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*updateSoudMeter(){
try{
const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0));
this.setVolumeSoundMeter(volume, this.mySoundMeterElement);
for(const indexUserId of this.soundMeters.keys()){
const soundMeter = this.soundMeters.get(indexUserId);
const soundMeterElement = this.soundMeterElements.get(indexUserId);
@ -824,7 +831,7 @@ export class MediaManager {
}catch(err){
//console.error(err);
}
}
}*/
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){
if(volume <= 0 && !element.classList.contains('active')){
@ -847,6 +854,32 @@ export class MediaManager {
elementChildre.classList.add('active');
});
}
public getNotification(){
//Get notification
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
Notification.requestPermission().catch((err) => {
console.error(`Notification permission error`, err);
});
}
}
public createNotification(userName: string){
if(this.focused){
return;
}
if (window.Notification && Notification.permission === "granted") {
const title = 'WorkAdventure';
const options = {
body: `Hi! ${userName} wants to discuss with you, don't be afraid!`,
icon: '/resources/logos/logo-WA-min.png',
image: '/resources/logos/logo-WA-min.png',
badge: '/resources/logos/logo-WA-min.png',
};
new Notification(title, options);
//new Notification(`Hi! ${userName} wants to discuss with you, don't be afraid!`);
}
}
}
export const mediaManager = new MediaManager();

View file

@ -1,9 +1,9 @@
import * as SimplePeerNamespace from "simple-peer";
import type * as SimplePeerNamespace from "simple-peer";
import {mediaManager} from "./MediaManager";
import {STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import type {RoomConnection} from "../Connexion/RoomConnection";
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
import {UserSimplePeerInterface} from "./SimplePeer";
import type {UserSimplePeerInterface} from "./SimplePeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');

View file

@ -1,4 +1,4 @@
import {
import type {
WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface,
} from "../Connexion/ConnexionModels";
@ -10,7 +10,7 @@ import {
} from "./MediaManager";
import {ScreenSharingPeer} from "./ScreenSharingPeer";
import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
import {RoomConnection} from "../Connexion/RoomConnection";
import type {RoomConnection} from "../Connexion/RoomConnection";
import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager";
@ -158,6 +158,11 @@ export class SimplePeer {
this.sendLocalScreenSharingStreamToUser(user.userId);
}
});
//Create a notification for first user in circle discussion
if(this.PeerConnectionArray.size === 0){
mediaManager.createNotification(user.name??'');
}
this.PeerConnectionArray.set(user.userId, peer);
for (const peerConnectionListener of this.peerConnectionListeners) {

View file

@ -1,10 +1,10 @@
import * as SimplePeerNamespace from "simple-peer";
import type * as SimplePeerNamespace from "simple-peer";
import {mediaManager} from "./MediaManager";
import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
import {RoomConnection} from "../Connexion/RoomConnection";
import type {RoomConnection} from "../Connexion/RoomConnection";
import {blackListManager} from "./BlackListManager";
import {Subscription} from "rxjs";
import {UserSimplePeerInterface} from "./SimplePeer";
import type {Subscription} from "rxjs";
import type {UserSimplePeerInterface} from "./SimplePeer";
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');

View file

@ -1,16 +1,16 @@
import { ChatEvent } from "./Api/Events/ChatEvent";
import type { ChatEvent } from "./Api/Events/ChatEvent";
import { isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
import { Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent";
import { OpenPopupEvent } from "./Api/Events/OpenPopupEvent";
import type { OpenPopupEvent } from "./Api/Events/OpenPopupEvent";
import { isButtonClickedEvent } from "./Api/Events/ButtonClickedEvent";
import { ClosePopupEvent } from "./Api/Events/ClosePopupEvent";
import { OpenTabEvent } from "./Api/Events/OpenTabEvent";
import { GoToPageEvent } from "./Api/Events/GoToPageEvent";
import { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
import { LayerEvent } from "./Api/Events/LayerEvent";
import { SetPropertyEvent } from "./Api/Events/setPropertyEvent";
import type { ClosePopupEvent } from "./Api/Events/ClosePopupEvent";
import type { OpenTabEvent } from "./Api/Events/OpenTabEvent";
import type { GoToPageEvent } from "./Api/Events/GoToPageEvent";
import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
import type { LayerEvent } from "./Api/Events/LayerEvent";
import type { SetPropertyEvent } from "./Api/Events/setPropertyEvent";
interface WorkAdventureApi {
sendChatMessage(message: string, author: string): void;

View file

@ -1,6 +1,6 @@
import 'phaser';
import GameConfig = Phaser.Types.Core.GameConfig;
import "../dist/resources/style/index.scss";
import "../style/index.scss";
import {DEBUG_MODE, isMobile} from "./Enum/EnvironmentVariable";
import {LoginScene} from "./Phaser/Login/LoginScene";
@ -21,6 +21,8 @@ import { SelectCharacterMobileScene } from './Phaser/Login/SelectCharacterMobile
import {HdpiManager} from "./Phaser/Services/HdpiManager";
import {waScaleManager} from "./Phaser/Services/WaScaleManager";
import {Game} from "./Phaser/Game/Game";
import App from './Components/App.svelte';
import {HtmlUtils} from "./WebRtc/HtmlUtils";
const {width, height} = coWebsiteManager.getGameSize();
@ -127,19 +129,12 @@ const config: GameConfig = {
//const game = new Phaser.Game(config);
const game = new Game(config);
waScaleManager.setScaleManager(game.scale);
waScaleManager.setGame(game);
window.addEventListener('resize', function (event) {
coWebsiteManager.resetStyle();
waScaleManager.applyNewSize();
// Let's trigger the onResize method of any active scene that is a ResizableScene
for (const scene of game.scene.getScenes(true)) {
if (scene instanceof ResizableScene) {
scene.onResize(event);
}
}
});
coWebsiteManager.onResize.subscribe(() => {
@ -147,3 +142,10 @@ coWebsiteManager.onResize.subscribe(() => {
});
iframeListener.init();
const app = new App({
target: HtmlUtils.getElementByIdOrFail('svelte-overlay'),
props: { },
})
export default app

View file

@ -1,4 +1,4 @@
import Phaser from "phaser";
import type Phaser from "phaser";
export type CursorKey = {
isDown: boolean

View file

@ -4,7 +4,7 @@
position: fixed;
transition: transform 0.5s;
background-color: white;
&.loading {
background-color: gray;
}
@ -15,7 +15,7 @@
height: 100%;
}
}
aside {
background: gray;
align-items: center;
@ -32,7 +32,7 @@
position: absolute;
background: none;
border: none;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
img {
height: 25px;

View file

Before

Width:  |  Height:  |  Size: 979 B

After

Width:  |  Height:  |  Size: 979 B

View file

Before

Width:  |  Height:  |  Size: 937 B

After

Width:  |  Height:  |  Size: 937 B

View file

@ -1,9 +1,9 @@
*{
font-family: 'Open Sans', sans-serif;
cursor: url('/resources/logos/cursor_normal.png'), auto;
cursor: url('./images/cursor_normal.png'), auto;
}
* a, button, select{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
body{
overflow: hidden;
@ -39,7 +39,7 @@ body .message-info.warning{
position: relative;
transition: all 0.2s ease;
background-color: #00000099;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.video-container i{
position: absolute;
@ -75,7 +75,7 @@ body .message-info.warning{
.video-container button.report{
display: block;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
background: none;
background-color: rgba(0, 0, 0, 0);
border: none;
@ -108,7 +108,7 @@ body .message-info.warning{
left: 5px;
margin: 0;
padding: 0;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
width: 25px;
height: 25px;
}
@ -118,7 +118,7 @@ body .message-info.warning{
left: 36px;
color: white;
font-size: 16px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.video-container img.active {
display: block !important;
@ -126,7 +126,7 @@ body .message-info.warning{
.video-container video{
height: 100%;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.video-container video:focus{
@ -206,7 +206,7 @@ video#myCamVideo{
}
/*btn animation*/
.btn-cam-action div{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
/*position: absolute;*/
border: solid 0px black;
width: 44px;
@ -260,7 +260,7 @@ video#myCamVideo{
top: calc(48px - 37px);
left: calc(48px - 41px);
position: relative;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
/* Spinner */
@ -572,7 +572,7 @@ input[type=range]:focus::-ms-fill-upper {
margin: 2%;
flex-basis: 96%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, flex-basis 0.2s;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
pointer-events: auto;
/*flex-shrink: 2;*/
}
@ -590,7 +590,7 @@ input[type=range]:focus::-ms-fill-upper {
.sidebar > div {
margin: 2%;
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;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
border-radius: 15px 15px 15px 15px;
pointer-events: auto;
}
@ -600,7 +600,7 @@ input[type=range]:focus::-ms-fill-upper {
}
.sidebar > div video {
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
/* Let's make sure videos are vertically centered if they need to be cropped */
@ -625,7 +625,7 @@ input[type=range]:focus::-ms-fill-upper {
margin: 1%;
max-height: 96%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.chat-mode > div:hover {
@ -715,7 +715,7 @@ input[type=range]:focus::-ms-fill-upper {
margin-top: 6px;
width: 30px;
height: 30px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
padding: 0 5px;
transition: all .5s ease;
transform: rotateY(0);
@ -739,7 +739,7 @@ input[type=range]:focus::-ms-fill-upper {
.main-console div.console:hover,
.message-container div.clear:hover {
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
top: calc(100% + 5px);
transform: scale(1.2) translateY(3px);
}
@ -772,7 +772,7 @@ input[type=range]:focus::-ms-fill-upper {
transition: all .2s ease;
}
.main-console .btn-action .btn:hover{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
background-color: #ffda01;
color: black;
border: 1px solid black;
@ -787,7 +787,7 @@ input[type=range]:focus::-ms-fill-upper {
.main-console .menu span {
margin: 20px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.main-console .menu span.active {
@ -821,10 +821,10 @@ input[type=range]:focus::-ms-fill-upper {
}
.main-console section div.upload label img{
height: 150px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.main-console section div.upload label img{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
@ -917,7 +917,7 @@ div.modal-report-user{
right: 0;
left: auto;
top: 0;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
width: 15px;
height: 15px;
margin: 10px;
@ -936,7 +936,7 @@ div.modal-report-user{
transition: all .2s ease;
}
.modal-report-user button:hover{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
background-color: #ffda01;
color: black;
border: 1px solid black;
@ -979,7 +979,7 @@ div.modal-report-user{
}
.discussion .active-btn{
display: none;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
height: 50px;
width: 50px;
background-color: #2d2d2dba;
@ -1008,7 +1008,7 @@ div.modal-report-user{
right: 10px;
background: none;
border: none;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.discussion .close-btn img{
height: 15px;
@ -1033,7 +1033,7 @@ div.modal-report-user{
background-color: #ffffff69;
padding: 5px;
border-radius: 15px;
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
}
.discussion .participants .participant:hover{
@ -1066,7 +1066,7 @@ div.modal-report-user{
}
.discussion .participants .participant button.report-btn{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
position: absolute;
background-color: #2d2d2dba;
right: 34px;
@ -1176,7 +1176,7 @@ div.action.danger{
animation-timing-function: ease-in-out;
}
div.action p.action-body{
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
cursor: url('./images/cursor_pointer.png'), pointer;
padding: 10px;
background-color: #2d2d2dba;
color: #fff;
@ -1225,3 +1225,11 @@ div.action.danger p.action-body{
50% {bottom: 30px;}
100% {bottom: 40px;}
}
#svelte-overlay {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
}

View file

@ -1,7 +1,7 @@
import "jasmine";
import {Room} from "../../../src/Connexion/Room";
import {flattenGroupLayersMap} from "../../../src/Phaser/Map/LayersFlattener";
import {ITiledMapLayer} from "../../../src/Phaser/Map/ITiledMap";
import type {ITiledMapLayer} from "../../../src/Phaser/Map/ITiledMap";
describe("Layers flattener", () => {
it("should iterate maps with no group", () => {

View file

@ -50,6 +50,6 @@ describe("Test HdpiManager", () => {
const result = hdpiManager.getOptimalGameSize({ width: 1280, height: 768 });
expect(result.game.width).toEqual(1280);
expect(result.game.height).toEqual(768);
expect(hdpiManager.zoomModifier).toEqual(1);
expect(hdpiManager.zoomModifier).toEqual(2 / 3);
});
});

View file

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"include": ["./src/**/*", "./tests/**/*"],
"compilerOptions": {
"module": "CommonJS",
"lib": ["es2015","dom"],
"types": ["svelte", "node"]
},
}

View file

@ -0,0 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"esModuleInterop": true
}
}

View file

@ -1,16 +1,22 @@
{
// "include": ["src/**/*", "webpack.config.ts"],
"extends": "@tsconfig/svelte/tsconfig.json",
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"moduleResolution": "node",
"module": "CommonJS",
"target": "ES2015",
//"module": "CommonJS",
"module": "ESNext",
"target": "ES2017",
"declaration": false,
"downlevelIteration": true,
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"importsNotUsedAsValues": "error",
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */

View file

@ -1,88 +0,0 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
'main': './src/index.ts',
'iframe_api': './src/iframe_api.ts'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
host: '0.0.0.0',
sockPort: 80,
disableHostCheck: true,
historyApiFallback: {
rewrites: [
{ from: /^_\/.*$/, to: '/index.html' }
],
disableDotRule: true
},
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
filename: (pathData) => {
// Add a content hash only for the main bundle.
// We want the iframe_api.js file to keep its name as it will be referenced from outside iframes.
return pathData.chunk.name === 'main' ? 'js/[name].[contenthash].js': '[name].js';
},
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
externals:[
require('webpack-require-http')
],
plugins: [
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.tmpl.html.tmp',
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
},
chunks: ['main']
}
),
new webpack.ProvidePlugin({
Phaser: 'phaser'
}),
new webpack.EnvironmentPlugin({
'API_URL': null,
'PUSHER_URL': undefined,
'UPLOADER_URL': null,
'ADMIN_URL': null,
'DEBUG_MODE': null,
'STUN_SERVER': null,
'TURN_SERVER': null,
'TURN_USER': null,
'TURN_PASSWORD': null,
'JITSI_URL': null,
'JITSI_PRIVATE_MODE': null,
'START_ROOM_URL': null
})
],
};

174
front/webpack.config.ts Normal file
View file

@ -0,0 +1,174 @@
import type {Configuration} from "webpack";
import type WebpackDevServer from "webpack-dev-server";
import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import sveltePreprocess from 'svelte-preprocess';
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin';
const mode = process.env.NODE_ENV ?? 'development';
const isProduction = mode === 'production';
const isDevelopment = !isProduction;
module.exports = {
entry: {
'main': './src/index.ts',
'iframe_api': './src/iframe_api.ts'
},
mode: mode,
devtool: isDevelopment ? 'inline-source-map' : 'source-map',
devServer: {
contentBase: './dist',
host: '0.0.0.0',
sockPort: 80,
disableHostCheck: true,
historyApiFallback: {
rewrites: [
{ from: /^_\/.*$/, to: '/index.html' }
],
disableDotRule: true
},
},
module: {
rules: [
{
test: /\.tsx?$/,
//use: 'ts-loader',
exclude: /node_modules/,
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader, {
loader: 'css-loader',
options: {
//url: false,
sourceMap: true
}
}, 'sass-loader'],
},
{
test: /\.css$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
//url: false,
sourceMap: true
}
}
]
},
{
test: /\.(html|svelte)$/,
exclude: /node_modules/,
use: {
loader: 'svelte-loader',
options: {
compilerOptions: {
// Dev mode must be enabled for HMR to work!
dev: isDevelopment
},
emitCss: isProduction,
hotReload: isDevelopment,
hotOptions: {
// List of options and defaults: https://www.npmjs.com/package/svelte-loader-hot#usage
noPreserveState: false,
optimistic: true,
},
preprocess: sveltePreprocess({
scss: true,
sass: true,
})
}
}
},
// Required to prevent errors from Svelte on Webpack 5+, omit on Webpack 4
// See: https://github.com/sveltejs/svelte-loader#usage
{
test: /node_modules\/svelte\/.*\.mjs$/,
resolve: {
fullySpecified: false
}
},
{
test: /\.(ttf|eot|svg|png|gif|jpg)$/,
exclude: /node_modules/,
type: 'asset'
}
],
},
resolve: {
alias: {
svelte: path.resolve('node_modules', 'svelte')
},
extensions: [ '.tsx', '.ts', '.js', '.svelte' ],
mainFields: ['svelte', 'browser', 'module', 'main']
},
output: {
filename: (pathData) => {
// Add a content hash only for the main bundle.
// We want the iframe_api.js file to keep its name as it will be referenced from outside iframes.
return pathData.chunk?.name === 'main' ? 'js/[name].[contenthash].js': '[name].js';
},
path: path.resolve(__dirname, 'dist'),
publicPath: '/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ForkTsCheckerWebpackPlugin({
eslint: {
files: './src/**/*.ts'
}
}),
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
new HtmlWebpackPlugin(
{
template: './dist/index.tmpl.html.tmp',
minify: {
collapseWhitespace: true,
keepClosingSlash: true,
removeComments: false,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true
},
chunks: ['main']
}
),
new webpack.ProvidePlugin({
Phaser: 'phaser'
}),
new NodePolyfillPlugin(),
new webpack.EnvironmentPlugin({
'API_URL': null,
'SKIP_RENDER_OPTIMIZATIONS': false,
'DISABLE_NOTIFICATIONS': false,
'PUSHER_URL': undefined,
'UPLOADER_URL': null,
'ADMIN_URL': null,
'DEBUG_MODE': null,
'STUN_SERVER': null,
'TURN_SERVER': null,
'TURN_USER': null,
'TURN_PASSWORD': null,
'JITSI_URL': null,
'JITSI_PRIVATE_MODE': null,
'START_ROOM_URL': null,
'MAX_USERNAME_LENGTH': 8,
'MAX_PER_GROUP': 4
})
],
} as Configuration & WebpackDevServer.Configuration;

View file

@ -1,7 +0,0 @@
const merge = require('webpack-merge');
const common = require('./webpack.config.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map'
});

File diff suppressed because it is too large Load diff