diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
index 48a7bae9..3bf00b99 100644
--- a/.github/workflows/build-and-deploy.yml
+++ b/.github/workflows/build-and-deploy.yml
@@ -2,7 +2,7 @@ name: Build, push and deploy Docker image
on:
push:
- branches: [master]
+ branches: [master, develop]
release:
types: [created]
pull_request:
@@ -149,6 +149,37 @@ jobs:
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@3.1.0
+ - name: Write certificate
+ run: echo "${CERTS_PRIVATE_KEY}" > secret.key && chmod 0600 secret.key
+ env:
+ CERTS_PRIVATE_KEY: ${{ secrets.CERTS_PRIVATE_KEY }}
+
+ - name: Download certificate
+ run: mkdir secrets && scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i secret.key ubuntu@cert.workadventu.re:./config/live/workadventu.re/* secrets/
+
+ - name: Create namespace
+ uses: steebchen/kubectl@v1.0.0
+ env:
+ KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
+ with:
+ args: create namespace workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
+ continue-on-error: true
+
+ - name: Delete old certificates in namespace
+ uses: steebchen/kubectl@v1.0.0
+ env:
+ KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
+ with:
+ args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} delete secret certificate-tls
+ continue-on-error: true
+
+ - name: Install certificates in namespace
+ uses: steebchen/kubectl@v1.0.0
+ env:
+ KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_FILE_BASE64 }}
+ with:
+ args: -n workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} create secret tls certificate-tls --key="secrets/privkey.pem" --cert="secrets/fullchain.pem"
+
- name: Deploy
uses: thecodingmachine/deeployer-action@master
env:
@@ -168,4 +199,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
- msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
+ msg: Environment deployed at https://play-${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml
index 06c1c58e..2036e4e6 100644
--- a/.github/workflows/continuous_integration.yml
+++ b/.github/workflows/continuous_integration.yml
@@ -52,6 +52,10 @@ jobs:
PUSHER_URL: "//localhost:8080"
working-directory: "front"
+ - name: "Svelte check"
+ run: yarn run svelte-check
+ working-directory: "front"
+
- name: "Lint"
run: yarn run lint
working-directory: "front"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dec14540..1dd2c973 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,11 +12,25 @@
- The emote menu can be opened by clicking on your character.
- Clicking on one of its element will close the menu and play an emote above your character.
- This emote can be seen by other players.
+- Player names were improved. (@Kharhamel)
+ - We now create a GameObject.Text instead of GameObject.BitmapText
+ - now use the 'Press Start 2P' font family and added an outline
+ - As a result, we can now allow non-standard letters like french accents or chinese characters!
+
+- Added the contact card feature. (@Kharhamel)
+ - Click on another player to see its contact info.
+ - Premium-only feature unfortunately. I need to find a way to make it available for all.
+ - If no contact data is found (either because the user is anonymous or because no admin backend), display an error card.
+
- Mobile support has been improved
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
- Mouse wheel support to zoom in / out
- Pinch support on mobile to zoom in / out
- Improved virtual joystick size (adapts to the zoom level)
+- Redesigned intermediate scenes
+ - Redesigned Select Companion scene
+ - Redesigned Enter Your Name scene
+ - Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use
- New scripting API features:
- Use `WA.loadSound(): Sound` to load / play / stop a sound
diff --git a/back/src/Model/Admin.ts b/back/src/Model/Admin.ts
index 0be74b85..29b53385 100644
--- a/back/src/Model/Admin.ts
+++ b/back/src/Model/Admin.ts
@@ -1,9 +1,6 @@
import {
- BatchMessage,
- PusherToBackMessage,
ServerToAdminClientMessage,
- ServerToClientMessage,
- SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
+ UserJoinedRoomMessage, UserLeftRoomMessage
} from "../Messages/generated/messages_pb";
import {AdminSocket} from "../RoomManager";
diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts
index be3e5cd3..53d0a855 100644
--- a/back/src/Model/GameRoom.ts
+++ b/back/src/Model/GameRoom.ts
@@ -89,7 +89,10 @@ export class GameRoom {
public getUserByUuid(uuid: string): User|undefined {
return this.usersByUuid.get(uuid);
}
-
+ public getUserById(id: number): User|undefined {
+ return this.users.get(id);
+ }
+
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
const positionMessage = joinRoomMessage.getPositionmessage();
if (positionMessage === undefined) {
@@ -105,6 +108,7 @@ export class GameRoom {
this.positionNotifier,
socket,
joinRoomMessage.getTagList(),
+ joinRoomMessage.getVisitcardurl(),
joinRoomMessage.getName(),
ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()),
joinRoomMessage.getCompanion()
diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts
index 52a96b61..4a3e75ec 100644
--- a/back/src/Model/User.ts
+++ b/back/src/Model/User.ts
@@ -22,6 +22,7 @@ export class User implements Movable {
private positionNotifier: PositionNotifier,
public readonly socket: UserSocket,
public readonly tags: string[],
+ public readonly visitCardUrl: string|null,
public readonly name: string,
public readonly characterLayers: CharacterLayer[],
public readonly companion?: CompanionMessage
diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts
index c58b3d9f..a56a1ac4 100644
--- a/back/src/Services/SocketManager.ts
+++ b/back/src/Services/SocketManager.ts
@@ -299,6 +299,9 @@ export class SocketManager {
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
userJoinedZoneMessage.setFromzone(this.toProtoZone(fromZone));
+ if (thing.visitCardUrl) {
+ userJoinedZoneMessage.setVisitcardurl(thing.visitCardUrl);
+ }
userJoinedZoneMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
@@ -604,6 +607,9 @@ export class SocketManager {
userJoinedMessage.setName(thing.name);
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
+ if (thing.visitCardUrl) {
+ userJoinedMessage.setVisitcardurl(thing.visitCardUrl);
+ }
userJoinedMessage.setCompanion(thing.companion);
const subMessage = new SubToPusherMessage();
diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts
index 24b171d9..1aaf2e13 100644
--- a/back/tests/PositionNotifierTest.ts
+++ b/back/tests/PositionNotifierTest.ts
@@ -1,10 +1,6 @@
import "jasmine";
-import {GameRoom, ConnectCallback, DisconnectCallback } from "_Model/GameRoom";
-import {Point} from "../src/Model/Websocket/MessageUserPosition";
-import { Group } from "../src/Model/Group";
import {PositionNotifier} from "../src/Model/PositionNotifier";
import {User, UserSocket} from "../src/Model/User";
-import {PointInterface} from "../src/Model/Websocket/PointInterface";
import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface";
@@ -30,14 +26,14 @@ describe("PositionNotifier", () => {
y: 500,
moving: false,
direction: 'down'
- }, false, positionNotifier, {} as UserSocket, [], 'foo', []);
+ }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
const user2 = new User(2, 'test', '10.0.0.2', {
x: -9999,
y: -9999,
moving: false,
direction: 'down'
- }, false, positionNotifier, {} as UserSocket, [], 'foo', []);
+ }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
@@ -105,14 +101,14 @@ describe("PositionNotifier", () => {
y: 500,
moving: false,
direction: 'down'
- }, false, positionNotifier, {} as UserSocket, [], 'foo', []);
+ }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
const user2 = new User(2, 'test', '10.0.0.2', {
x: 0,
y: 0,
moving: false,
direction: 'down'
- }, false, positionNotifier, {} as UserSocket, [], 'foo', []);
+ }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
const listener = {} as ZoneSocket;
positionNotifier.addZoneListener(listener, 0, 0);
diff --git a/benchmark/package-lock.json b/benchmark/package-lock.json
index 72d0aae4..ba52caf2 100644
--- a/benchmark/package-lock.json
+++ b/benchmark/package-lock.json
@@ -209,9 +209,9 @@
}
},
"glob-parent": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
- "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"requires": {
"is-glob": "^4.0.1"
}
diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock
index f1209dcf..a7f9ec0d 100644
--- a/benchmark/yarn.lock
+++ b/benchmark/yarn.lock
@@ -148,8 +148,8 @@ get-stdin@^4.0.1:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
glob-parent@~5.1.0:
- version "5.1.1"
- resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
dependencies:
is-glob "^4.0.1"
diff --git a/deeployer.libsonnet b/deeployer.libsonnet
index f9dd87bd..8d9c2bfd 100644
--- a/deeployer.libsonnet
+++ b/deeployer.libsonnet
@@ -11,8 +11,7 @@
"back1": {
"image": "thecodingmachine/workadventure-back:"+tag,
"host": {
- "url": "api1."+url,
- "https": "enable",
+ "url": "api1-"+url,
"containerPort": 8080
},
"ports": [8080, 50051],
@@ -30,8 +29,7 @@
"back2": {
"image": "thecodingmachine/workadventure-back:"+tag,
"host": {
- "url": "api2."+url,
- "https": "enable",
+ "url": "api2-"+url,
"containerPort": 8080
},
"ports": [8080, 50051],
@@ -50,8 +48,7 @@
"replicas": 2,
"image": "thecodingmachine/workadventure-pusher:"+tag,
"host": {
- "url": "pusher."+url,
- "https": "enable"
+ "url": "pusher-"+url,
},
"ports": [8080],
"env": {
@@ -68,27 +65,25 @@
"front": {
"image": "thecodingmachine/workadventure-front:"+tag,
"host": {
- "url": "play."+url,
- "https": "enable"
+ "url": "play-"+url,
},
"ports": [80],
"env": {
- "PUSHER_URL": "//pusher."+url,
- "UPLOADER_URL": "//uploader."+url,
+ "PUSHER_URL": "//pusher-"+url,
+ "UPLOADER_URL": "//uploader-"+url,
"ADMIN_URL": "//"+url,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
- "START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
+ "START_ROOM_URL": "/_/global/maps-"+url+"/Floor0/floor0.json"
//"GA_TRACKING_ID": "UA-10196481-11"
}
},
"uploader": {
"image": "thecodingmachine/workadventure-uploader:"+tag,
"host": {
- "url": "uploader."+url,
- "https": "enable",
+ "url": "uploader-"+url,
"containerPort": 8080
},
"ports": [8080],
@@ -98,16 +93,12 @@
"maps": {
"image": "thecodingmachine/workadventure-maps:"+tag,
"host": {
- "url": "maps."+url,
- "https": "enable"
+ "url": "maps-"+url
},
"ports": [80]
},
},
"config": {
- "https": {
- "mail": "d.negrier@thecodingmachine.com"
- },
k8sextension(k8sConf)::
k8sConf + {
back1+: {
@@ -122,6 +113,14 @@
}
}
}
+ },
+ ingress+: {
+ spec+: {
+ tls+: [{
+ hosts: ["api1-"+url],
+ secretName: "certificate-tls"
+ }]
+ }
}
},
back2+: {
@@ -136,6 +135,14 @@
}
}
}
+ },
+ ingress+: {
+ spec+: {
+ tls+: [{
+ hosts: ["api2-"+url],
+ secretName: "certificate-tls"
+ }]
+ }
}
},
pusher+: {
@@ -150,8 +157,46 @@
}
}
}
- }
- }
+ },
+ ingress+: {
+ spec+: {
+ tls+: [{
+ hosts: ["pusher-"+url],
+ secretName: "certificate-tls"
+ }]
+ }
+ }
+ },
+ front+: {
+ ingress+: {
+ spec+: {
+ tls+: [{
+ hosts: ["play-"+url],
+ secretName: "certificate-tls"
+ }]
+ }
+ }
+ },
+ uploader+: {
+ ingress+: {
+ spec+: {
+ tls+: [{
+ hosts: ["uploader-"+url],
+ secretName: "certificate-tls"
+ }]
+ }
+ }
+ },
+ maps+: {
+ ingress+: {
+ spec+: {
+ tls+: [{
+ hosts: ["maps-"+url],
+ secretName: "certificate-tls"
+ }]
+ }
+ }
+ },
}
}
}
diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html
index 7ef44116..aa63229f 100644
--- a/front/dist/index.tmpl.html
+++ b/front/dist/index.tmpl.html
@@ -29,7 +29,6 @@
-
WorkAdventure
@@ -47,32 +46,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/front/dist/resources/fonts/fonts.css b/front/dist/resources/fonts/fonts.css
new file mode 100644
index 00000000..a3d3cf71
--- /dev/null
+++ b/front/dist/resources/fonts/fonts.css
@@ -0,0 +1,5 @@
+/*This file is a workaround to allow phaser to load directly this font */
+@font-face {
+ font-family: "Press Start 2P";
+ src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2');
+}
\ No newline at end of file
diff --git a/front/dist/resources/html/CustomCharacterScene.html b/front/dist/resources/html/CustomCharacterScene.html
deleted file mode 100644
index 0bc050ea..00000000
--- a/front/dist/resources/html/CustomCharacterScene.html
+++ /dev/null
@@ -1,160 +0,0 @@
-
-
-
diff --git a/front/dist/resources/html/EnableCameraScene.html b/front/dist/resources/html/EnableCameraScene.html
deleted file mode 100644
index 2dda6cc1..00000000
--- a/front/dist/resources/html/EnableCameraScene.html
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
diff --git a/front/dist/resources/html/SelectCompanionScene.html b/front/dist/resources/html/SelectCompanionScene.html
deleted file mode 100644
index cffa7880..00000000
--- a/front/dist/resources/html/SelectCompanionScene.html
+++ /dev/null
@@ -1,134 +0,0 @@
-
-
-
diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html
index 6abf2753..26be2a1c 100644
--- a/front/dist/resources/html/gameMenu.html
+++ b/front/dist/resources/html/gameMenu.html
@@ -1,7 +1,4 @@
-
-
diff --git a/front/dist/resources/html/loginScene.html b/front/dist/resources/html/loginScene.html
deleted file mode 100644
index 38e798e5..00000000
--- a/front/dist/resources/html/loginScene.html
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
diff --git a/front/dist/resources/html/selectCharacterScene.html b/front/dist/resources/html/selectCharacterScene.html
deleted file mode 100644
index c51731df..00000000
--- a/front/dist/resources/html/selectCharacterScene.html
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
diff --git a/front/dist/resources/html/warningContainer.html b/front/dist/resources/html/warningContainer.html
index 4989c49d..832ac4da 100644
--- a/front/dist/resources/html/warningContainer.html
+++ b/front/dist/resources/html/warningContainer.html
@@ -1,7 +1,4 @@
diff --git a/front/src/Components/EnableCamera/EnableCameraScene.svelte b/front/src/Components/EnableCamera/EnableCameraScene.svelte
new file mode 100644
index 00000000..1f9ef027
--- /dev/null
+++ b/front/src/Components/EnableCamera/EnableCameraScene.svelte
@@ -0,0 +1,229 @@
+
+
+
+
+
+
diff --git a/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte
new file mode 100644
index 00000000..a22da2fa
--- /dev/null
+++ b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte
@@ -0,0 +1,82 @@
+
+
+
+
+ {#each [...Array(NB_BARS).keys()] as i}
+
+ {/each}
+
+
+
diff --git a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte
new file mode 100644
index 00000000..8f4de785
--- /dev/null
+++ b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte
@@ -0,0 +1,73 @@
+
+
+
+
+
+
diff --git a/front/dist/resources/objects/help-setting-camera-permission-chrome.png b/front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-chrome.png
similarity index 100%
rename from front/dist/resources/objects/help-setting-camera-permission-chrome.png
rename to front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-chrome.png
diff --git a/front/dist/resources/objects/help-setting-camera-permission-firefox.png b/front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-firefox.png
similarity index 100%
rename from front/dist/resources/objects/help-setting-camera-permission-firefox.png
rename to front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-firefox.png
diff --git a/front/src/Components/Login/LoginScene.svelte b/front/src/Components/Login/LoginScene.svelte
new file mode 100644
index 00000000..fe3c2c63
--- /dev/null
+++ b/front/src/Components/Login/LoginScene.svelte
@@ -0,0 +1,122 @@
+
+
+
+
+
diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte
new file mode 100644
index 00000000..ed4154a9
--- /dev/null
+++ b/front/src/Components/MyCamera.svelte
@@ -0,0 +1,46 @@
+
+
+
+
+
+ {#if $localStreamStore.type === "success" && $localStreamStore.stream }
+
+
+ {/if}
+
+
diff --git a/front/src/Components/SelectCompanion/SelectCompanionScene.svelte b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte
new file mode 100644
index 00000000..04efe124
--- /dev/null
+++ b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte
@@ -0,0 +1,87 @@
+
+
+
+
+
diff --git a/front/src/Components/SoundMeterWidget.svelte b/front/src/Components/SoundMeterWidget.svelte
new file mode 100644
index 00000000..30650e3f
--- /dev/null
+++ b/front/src/Components/SoundMeterWidget.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+ 1}>
+ 2}>
+ 3}>
+ 4}>
+ 5}>
+
diff --git a/front/src/Components/UI/AudioPlaying.svelte b/front/src/Components/UI/AudioPlaying.svelte
new file mode 100644
index 00000000..97f77429
--- /dev/null
+++ b/front/src/Components/UI/AudioPlaying.svelte
@@ -0,0 +1,52 @@
+
+
+
+
+
Audio message
+
+
+
+
diff --git a/front/src/Components/UI/ErrorDialog.svelte b/front/src/Components/UI/ErrorDialog.svelte
new file mode 100644
index 00000000..3244de24
--- /dev/null
+++ b/front/src/Components/UI/ErrorDialog.svelte
@@ -0,0 +1,48 @@
+
+
+
+
Error
+
+ {#each $errorStore as error}
+
{error}
+ {/each}
+
+
+
+
+
+
+
diff --git a/front/src/Components/UI/images/megaphone.svg b/front/src/Components/UI/images/megaphone.svg
new file mode 100644
index 00000000..708f860c
--- /dev/null
+++ b/front/src/Components/UI/images/megaphone.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/front/src/Components/VisitCard/VisitCard.svelte b/front/src/Components/VisitCard/VisitCard.svelte
new file mode 100644
index 00000000..78f10359
--- /dev/null
+++ b/front/src/Components/VisitCard/VisitCard.svelte
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+ {#if hidden}
+
+ {/if}
+
+ {#if !hidden}
+
+
+
+ {/if}
+
+
+
+
diff --git a/front/src/Components/images/cinema-close.svg b/front/src/Components/images/cinema-close.svg
new file mode 100644
index 00000000..aa1d9b17
--- /dev/null
+++ b/front/src/Components/images/cinema-close.svg
@@ -0,0 +1,41 @@
+
+
+
diff --git a/front/dist/resources/logos/cinema.svg b/front/src/Components/images/cinema.svg
similarity index 100%
rename from front/dist/resources/logos/cinema.svg
rename to front/src/Components/images/cinema.svg
diff --git a/front/src/Components/images/logo.png b/front/src/Components/images/logo.png
new file mode 100644
index 00000000..f4440ad5
Binary files /dev/null and b/front/src/Components/images/logo.png differ
diff --git a/front/src/Components/images/microphone-close.svg b/front/src/Components/images/microphone-close.svg
new file mode 100644
index 00000000..16731829
--- /dev/null
+++ b/front/src/Components/images/microphone-close.svg
@@ -0,0 +1,27 @@
+
+
+
diff --git a/front/dist/resources/logos/microphone.svg b/front/src/Components/images/microphone.svg
similarity index 100%
rename from front/dist/resources/logos/microphone.svg
rename to front/src/Components/images/microphone.svg
diff --git a/front/dist/resources/logos/monitor-close.svg b/front/src/Components/images/monitor-close.svg
similarity index 100%
rename from front/dist/resources/logos/monitor-close.svg
rename to front/src/Components/images/monitor-close.svg
diff --git a/front/dist/resources/logos/monitor.svg b/front/src/Components/images/monitor.svg
similarity index 100%
rename from front/dist/resources/logos/monitor.svg
rename to front/src/Components/images/monitor.svg
diff --git a/front/src/Components/selectCharacter/SelectCharacterScene.svelte b/front/src/Components/selectCharacter/SelectCharacterScene.svelte
new file mode 100644
index 00000000..5a37efda
--- /dev/null
+++ b/front/src/Components/selectCharacter/SelectCharacterScene.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts
index 932fb1fc..8112ba17 100644
--- a/front/src/Connexion/ConnectionManager.ts
+++ b/front/src/Connexion/ConnectionManager.ts
@@ -4,7 +4,7 @@ import {RoomConnection} from "./RoomConnection";
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
import {localUserStore} from "./LocalUserStore";
-import {LocalUser} from "./LocalUser";
+import {CharacterTexture, LocalUser} from "./LocalUser";
import {Room} from "./Room";
@@ -46,8 +46,8 @@ class ConnectionManager {
urlManager.pushRoomIdToUrl(room);
return Promise.resolve(room);
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
- const localUser = localUserStore.getLocalUser();
+ let localUser = localUserStore.getLocalUser();
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
this.localUser = localUser;
try {
@@ -57,16 +57,42 @@ class ConnectionManager {
console.error('JWT token invalid. Did it expire? Login anonymously instead.');
await this.anonymousLogin();
}
- } else {
+ }else{
await this.anonymousLogin();
}
- let roomId: string
+
+ localUser = localUserStore.getLocalUser();
+ if(!localUser){
+ throw "Error to store local user data";
+ }
+
+ let roomId: string;
if (connexionType === GameConnexionTypes.empty) {
roomId = START_ROOM_URL;
} else {
roomId = window.location.pathname + window.location.search + window.location.hash;
}
- return Promise.resolve(new Room(roomId));
+
+ //get detail map for anonymous login and set texture in local storage
+ const room = new Room(roomId);
+ const mapDetail = await room.getMapDetail();
+ if(mapDetail.textures != undefined && mapDetail.textures.length > 0) {
+ //check if texture was changed
+ if(localUser.textures.length === 0){
+ localUser.textures = mapDetail.textures;
+ }else{
+ mapDetail.textures.forEach((newTexture) => {
+ const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id);
+ if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){
+ return;
+ }
+ localUser?.textures.push(newTexture)
+ });
+ }
+ this.localUser = localUser;
+ localUserStore.saveUser(localUser);
+ }
+ return Promise.resolve(room);
}
return Promise.reject(new Error('Invalid URL'));
diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts
index 09447445..b5a66296 100644
--- a/front/src/Connexion/ConnexionModels.ts
+++ b/front/src/Connexion/ConnexionModels.ts
@@ -1,5 +1,3 @@
-import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
-import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
import type {SignalData} from "simple-peer";
import type {RoomConnection} from "./RoomConnection";
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
@@ -47,6 +45,7 @@ export interface MessageUserPositionInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
+ visitCardUrl: string|null;
companion: string|null;
}
@@ -60,6 +59,7 @@ export interface MessageUserJoined {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
+ visitCardUrl: string | null;
companion: string|null;
}
@@ -85,11 +85,6 @@ export interface WebRtcSignalReceivedMessageInterface {
webRtcPassword: string | undefined
}
-export interface StartMapInterface {
- mapUrlStart: string,
- startInstance: string
-}
-
export interface ViewportInterface {
left: number,
top: number,
diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts
index 0793a938..c877d119 100644
--- a/front/src/Connexion/LocalUser.ts
+++ b/front/src/Connexion/LocalUser.ts
@@ -9,9 +9,8 @@ export interface CharacterTexture {
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
-export function isUserNameValid(value: string): boolean {
- const regexp = new RegExp('^[A-Za-z0-9]{1,'+maxUserNameLength+'}$');
- return regexp.test(value);
+export function isUserNameValid(value: unknown): boolean {
+ return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && value.indexOf(' ') === -1;
}
export function areCharacterLayersValid(value: string[] | null): boolean {
@@ -25,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
}
export class LocalUser {
- constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) {
+ constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) {
}
}
diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts
index 05d94440..3ae8d2ed 100644
--- a/front/src/Connexion/Room.ts
+++ b/front/src/Connexion/Room.ts
@@ -1,10 +1,17 @@
import Axios from "axios";
import {PUSHER_URL} from "../Enum/EnvironmentVariable";
+import type {CharacterTexture} from "./LocalUser";
+
+export class MapDetail{
+ constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) {
+ }
+}
export class Room {
public readonly id: string;
public readonly isPublic: boolean;
private mapUrl: string|undefined;
+ private textures: CharacterTexture[]|undefined;
private instance: string|undefined;
private _search: URLSearchParams;
@@ -50,10 +57,10 @@ export class Room {
return {roomId, hash}
}
- public async getMapUrl(): Promise {
- return new Promise((resolve, reject) => {
- if (this.mapUrl !== undefined) {
- resolve(this.mapUrl);
+ public async getMapDetail(): Promise {
+ return new Promise((resolve, reject) => {
+ if (this.mapUrl !== undefined && this.textures != undefined) {
+ resolve(new MapDetail(this.mapUrl, this.textures));
return;
}
@@ -61,7 +68,7 @@ export class Room {
const match = /_\/[^/]+\/(.+)/.exec(this.id);
if (!match) throw new Error('Could not extract url from "'+this.id+'"');
this.mapUrl = window.location.protocol+'//'+match[1];
- resolve(this.mapUrl);
+ resolve(new MapDetail(this.mapUrl, this.textures));
return;
} else {
// We have a private ID, we need to query the map URL from the server.
@@ -71,7 +78,7 @@ export class Room {
params: urlParts
}).then(({data}) => {
console.log('Map ', this.id, ' resolves to URL ', data.mapUrl);
- resolve(data.mapUrl);
+ resolve(data);
return;
}).catch((reason) => {
reject(reason);
diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts
index a64e5bfd..fe1a78b0 100644
--- a/front/src/Connexion/RoomConnection.ts
+++ b/front/src/Connexion/RoomConnection.ts
@@ -30,7 +30,7 @@ import {
EmoteEventMessage,
EmotePromptMessage,
SendUserMessage,
- BanUserMessage
+ BanUserMessage,
} from "../Messages/generated/messages_pb"
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
@@ -344,6 +344,7 @@ export class RoomConnection implements RoomConnection {
userId: message.getUserid(),
name: message.getName(),
characterLayers,
+ visitCardUrl: message.getVisitcardurl(),
position: ProtobufClientUtils.toPointInterface(position),
companion: companion ? companion.getName() : null
}
diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts
index 85b63335..73f6427c 100644
--- a/front/src/Enum/EnvironmentVariable.ts
+++ b/front/src/Enum/EnvironmentVariable.ts
@@ -14,6 +14,7 @@ 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
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
+export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );
diff --git a/front/src/Phaser/Components/MobileJoystick.ts b/front/src/Phaser/Components/MobileJoystick.ts
index 46efcbc2..b3fc021b 100644
--- a/front/src/Phaser/Components/MobileJoystick.ts
+++ b/front/src/Phaser/Components/MobileJoystick.ts
@@ -28,13 +28,13 @@ export class MobileJoystick extends VirtualJoystick {
this.visible = false;
this.enable = false;
- this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => {
+ this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
if (!pointer.wasTouch) {
return;
}
// Let's only display the joystick if there is one finger on the screen
- if (pointer.event.touches.length === 1) {
+ if ((pointer.event as TouchEvent).touches.length === 1) {
this.x = pointer.x;
this.y = pointer.y;
this.visible = true;
diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts
index 1d6f7eba..53802d31 100644
--- a/front/src/Phaser/Components/SoundMeter.ts
+++ b/front/src/Phaser/Components/SoundMeter.ts
@@ -1,3 +1,5 @@
+import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context';
+
/**
* Class to measure the sound volume of a media stream
*/
@@ -5,10 +7,10 @@ export class SoundMeter {
private instant: number;
private clip: number;
//private script: ScriptProcessorNode;
- private analyser: AnalyserNode|undefined;
+ private analyser: IAnalyserNode|undefined;
private dataArray: Uint8Array|undefined;
- private context: AudioContext|undefined;
- private source: MediaStreamAudioSourceNode|undefined;
+ private context: IAudioContext|undefined;
+ private source: IMediaStreamAudioSourceNode|undefined;
constructor() {
this.instant = 0.0;
@@ -16,7 +18,7 @@ export class SoundMeter {
//this.script = context.createScriptProcessor(2048, 1, 1);
}
- private init(context: AudioContext) {
+ private init(context: IAudioContext) {
this.context = context;
this.analyser = this.context.createAnalyser();
@@ -25,8 +27,12 @@ export class SoundMeter {
this.dataArray = new Uint8Array(bufferLength);
}
- public connectToSource(stream: MediaStream, context: AudioContext): void
+ public connectToSource(stream: MediaStream, context: IAudioContext): void
{
+ if (this.source !== undefined) {
+ this.stop();
+ }
+
this.init(context);
this.source = this.context?.createMediaStreamSource(stream);
@@ -81,56 +87,3 @@ export class SoundMeter {
}
-
-// Meter class that generates a number correlated to audio volume.
-// The meter class itself displays nothing, but it makes the
-// instantaneous and time-decaying volumes available for inspection.
-// It also reports on the fraction of samples that were at or near
-// the top of the measurement range.
-/*function SoundMeter(context) {
- this.context = context;
- this.instant = 0.0;
- this.slow = 0.0;
- this.clip = 0.0;
- this.script = context.createScriptProcessor(2048, 1, 1);
- const that = this;
- this.script.onaudioprocess = function(event) {
- const input = event.inputBuffer.getChannelData(0);
- let i;
- let sum = 0.0;
- let clipcount = 0;
- for (i = 0; i < input.length; ++i) {
- sum += input[i] * input[i];
- if (Math.abs(input[i]) > 0.99) {
- clipcount += 1;
- }
- }
- that.instant = Math.sqrt(sum / input.length);
- that.slow = 0.95 * that.slow + 0.05 * that.instant;
- that.clip = clipcount / input.length;
- };
-}
-
-SoundMeter.prototype.connectToSource = function(stream, callback) {
- console.log('SoundMeter connecting');
- try {
- this.mic = this.context.createMediaStreamSource(stream);
- this.mic.connect(this.script);
- // necessary to make sample run, but should not be.
- this.script.connect(this.context.destination);
- if (typeof callback !== 'undefined') {
- callback(null);
- }
- } catch (e) {
- console.error(e);
- if (typeof callback !== 'undefined') {
- callback(e);
- }
- }
-};
-
-SoundMeter.prototype.stop = function() {
- this.mic.disconnect();
- this.script.disconnect();
-};
-*/
diff --git a/front/src/Phaser/Components/SoundMeterSprite.ts b/front/src/Phaser/Components/SoundMeterSprite.ts
deleted file mode 100644
index 582617f9..00000000
--- a/front/src/Phaser/Components/SoundMeterSprite.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import Container = Phaser.GameObjects.Container;
-import type {Scene} from "phaser";
-import GameObject = Phaser.GameObjects.GameObject;
-import Rectangle = Phaser.GameObjects.Rectangle;
-
-
-export class SoundMeterSprite extends Container {
- private rectangles: Rectangle[] = new Array();
- private static readonly NB_BARS = 20;
-
- constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) {
- super(scene, x, y, children);
-
- for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
- const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16));
- this.add(rectangle);
- this.rectangles.push(rectangle);
- }
- }
-
- /**
- * A number between 0 and 100
- *
- * @param volume
- */
- public setVolume(volume: number): void {
-
- const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS;
- for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) {
- if (normalizedVolume < i) {
- this.rectangles[i].alpha = 0.5;
- } else {
- this.rectangles[i].alpha = 1;
- }
- }
- }
-
- public getWidth(): number {
- return SoundMeterSprite.NB_BARS * 13;
- }
-
-
-
-}
diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts
index b1a85943..2ff66178 100644
--- a/front/src/Phaser/Entity/Character.ts
+++ b/front/src/Phaser/Entity/Character.ts
@@ -1,6 +1,6 @@
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
import {SpeechBubble} from "./SpeechBubble";
-import BitmapText = Phaser.GameObjects.BitmapText;
+import Text = Phaser.GameObjects.Text;
import Container = Phaser.GameObjects.Container;
import Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError";
@@ -23,7 +23,7 @@ const interactiveRadius = 35;
export abstract class Character extends Container {
private bubble: SpeechBubble|null = null;
- private readonly playerName: BitmapText;
+ private readonly playerName: Text;
public PlayerValue: string;
public sprites: Map;
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
@@ -41,6 +41,7 @@ export abstract class Character extends Container {
direction: PlayerAnimationDirections,
moving: boolean,
frame: string | number,
+ isClickable: boolean,
companion: string|null,
companionTexturePromise?: Promise
) {
@@ -56,11 +57,11 @@ export abstract class Character extends Container {
this.invisible = false
})
- this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
- this.playerName.setOrigin(0.5).setCenterAlign().setDepth(DEPTH_INGAME_TEXT_INDEX);
+ this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"});
+ this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName);
- if (this.isClickable()) {
+ if (isClickable) {
this.setInteractive({
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
@@ -79,7 +80,7 @@ export abstract class Character extends Container {
this.setDepth(-1);
this.playAnimation(direction, moving);
-
+
if (typeof companion === 'string') {
this.addCompanion(companion, companionTexturePromise);
}
@@ -90,12 +91,10 @@ export abstract class Character extends Container {
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
}
}
-
- public abstract isClickable(): boolean;
public addTextures(textures: string[], frame?: string | number): void {
for (const texture of textures) {
- if(!this.scene.textures.exists(texture)){
+ if(this.scene && !this.scene.textures.exists(texture)){
throw new TextureError('texture not found');
}
const sprite = new Sprite(this.scene, 0, 0, texture, frame);
@@ -240,28 +239,28 @@ export abstract class Character extends Container {
this.scene.sys.updateList.remove(sprite);
}
}
- this.list.forEach(objectContaining => objectContaining.destroy())
+ this.list.forEach(objectContaining => objectContaining.destroy())
super.destroy();
}
-
+
playEmote(emoteKey: string) {
this.cancelPreviousEmote();
const scalingFactor = waScaleManager.uiScalingFactor * 0.05;
const emoteY = -30 - scalingFactor * 10;
-
+
this.playerName.setVisible(false);
this.emote = new Sprite(this.scene, 0, 0, emoteKey);
this.emote.setAlpha(0);
this.emote.setScale(0.1 * scalingFactor);
this.add(this.emote);
this.scene.sys.updateList.add(this.emote);
-
+
this.createStartTransition(scalingFactor, emoteY);
}
private createStartTransition(scalingFactor: number, emoteY: number) {
- this.emoteTween = this.scene.tweens.add({
+ this.emoteTween = this.scene?.tweens.add({
targets: this.emote,
props: {
scale: scalingFactor,
@@ -277,7 +276,7 @@ export abstract class Character extends Container {
}
private startPulseTransition(emoteY: number, scalingFactor: number) {
- this.emoteTween = this.scene.tweens.add({
+ this.emoteTween = this.scene?.tweens.add({
targets: this.emote,
props: {
y: emoteY * 1.3,
@@ -294,7 +293,7 @@ export abstract class Character extends Container {
}
private startExitTransition(emoteY: number) {
- this.emoteTween = this.scene.tweens.add({
+ this.emoteTween = this.scene?.tweens.add({
targets: this.emote,
props: {
alpha: 0,
diff --git a/front/src/Phaser/Entity/CustomizedCharacter.ts b/front/src/Phaser/Entity/CustomizedCharacter.ts
new file mode 100644
index 00000000..3a7f1597
--- /dev/null
+++ b/front/src/Phaser/Entity/CustomizedCharacter.ts
@@ -0,0 +1,20 @@
+import Container = Phaser.GameObjects.Container;
+import type {Scene} from "phaser";
+import Sprite = Phaser.GameObjects.Sprite;
+
+/**
+ * A sprite of a customized character (used in the Customize Scene only)
+ */
+export class CustomizedCharacter extends Container {
+ public constructor(scene: Scene, x: number, y: number, layers: string[]) {
+ super(scene, x, y);
+ this.updateSprites(layers);
+ }
+
+ public updateSprites(layers: string[]): void {
+ this.removeAll(true);
+ for (const layer of layers) {
+ this.add(new Sprite(this.scene, 0, 0, layer));
+ }
+ }
+}
diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts
index 4e00f102..32881e97 100644
--- a/front/src/Phaser/Entity/RemotePlayer.ts
+++ b/front/src/Phaser/Entity/RemotePlayer.ts
@@ -2,12 +2,15 @@ import type {GameScene} from "../Game/GameScene";
import type {PointInterface} from "../../Connexion/ConnexionModels";
import {Character} from "../Entity/Character";
import type {PlayerAnimationDirections} from "../Player/Animation";
+import {requestVisitCardsStore} from "../../Stores/GameStore";
+
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
*/
export class RemotePlayer extends Character {
userId: number;
+ private visitCardUrl: string|null;
constructor(
userId: number,
@@ -18,13 +21,19 @@ export class RemotePlayer extends Character {
texturesPromise: Promise,
direction: PlayerAnimationDirections,
moving: boolean,
+ visitCardUrl: string|null,
companion: string|null,
companionTexturePromise?: Promise
) {
- super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
+ super(Scene, x, y, texturesPromise, name, direction, moving, 1, !!visitCardUrl, companion, companionTexturePromise);
//set data
this.userId = userId;
+ this.visitCardUrl = visitCardUrl;
+
+ this.on('pointerdown', () => {
+ requestVisitCardsStore.set(this.visitCardUrl);
+ })
}
updatePosition(position: PointInterface): void {
@@ -38,8 +47,4 @@ export class RemotePlayer extends Character {
this.companion.setTarget(position.x, position.y, position.direction as PlayerAnimationDirections);
}
}
-
- isClickable(): boolean {
- return false; //todo: make remote players clickable if they are logged in.
- }
}
diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts
index 70c341ee..1a5176f0 100644
--- a/front/src/Phaser/Game/AddPlayerInterface.ts
+++ b/front/src/Phaser/Game/AddPlayerInterface.ts
@@ -6,5 +6,6 @@ export interface AddPlayerInterface {
name: string;
characterLayers: BodyResourceDescriptionInterface[];
position: PointInterface;
+ visitCardUrl: string|null;
companion: string|null;
}
diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts
index 2e94aa66..3e1f3cdf 100644
--- a/front/src/Phaser/Game/DirtyScene.ts
+++ b/front/src/Phaser/Game/DirtyScene.ts
@@ -69,7 +69,7 @@ export abstract class DirtyScene extends ResizableScene {
return this.dirty || this.objectListChanged;
}
- public onResize(ev: UIEvent): void {
+ public onResize(): void {
this.objectListChanged = true;
}
}
diff --git a/front/src/Phaser/Game/Game.ts b/front/src/Phaser/Game/Game.ts
index 01aecf9f..e267e80a 100644
--- a/front/src/Phaser/Game/Game.ts
+++ b/front/src/Phaser/Game/Game.ts
@@ -21,14 +21,22 @@ export class Game extends Phaser.Game {
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
super(GameConfig);
- window.addEventListener('resize', (event) => {
+ this.scale.on(Phaser.Scale.Events.RESIZE, () => {
+ for (const scene of this.scene.getScenes(true)) {
+ if (scene instanceof ResizableScene) {
+ scene.onResize();
+ }
+ }
+ })
+
+ /*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)
diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts
index 2a1d3d8a..cd2575c0 100644
--- a/front/src/Phaser/Game/GameManager.ts
+++ b/front/src/Phaser/Game/GameManager.ts
@@ -2,11 +2,13 @@ import {GameScene} from "./GameScene";
import {connectionManager} from "../../Connexion/ConnectionManager";
import type {Room} from "../../Connexion/Room";
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
-import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
import {LoginSceneName} from "../Login/LoginScene";
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
import {localUserStore} from "../../Connexion/LocalUserStore";
+import {get} from "svelte/store";
+import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
+import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
export interface HasMovedEvent {
direction: string;
@@ -76,11 +78,11 @@ export class GameManager {
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise {
const roomID = room.id;
- const mapUrl = await room.getMapUrl();
+ const mapDetail = await room.getMapDetail();
const gameIndex = scenePlugin.getIndex(roomID);
if(gameIndex === -1){
- const game : Phaser.Scene = new GameScene(room, mapUrl);
+ const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl);
scenePlugin.add(roomID, game, false);
}
}
@@ -89,7 +91,11 @@ export class GameManager {
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
scenePlugin.launch(MenuSceneName);
- scenePlugin.launch(HelpCameraSettingsSceneName);//700
+
+ if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
+ helpCameraSettingsVisibleStore.set(true);
+ localUserStore.setHelpCameraSettingsShown();
+ }
}
public gameSceneIsCreated(scene: GameScene) {
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index 4de5ffd9..72279c61 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -21,7 +21,6 @@ import type {
ITiledMapLayer,
ITiledMapLayerProperty,
ITiledMapObject,
- ITiledText,
ITiledMapTileLayer,
ITiledTileSet
} from "../Map/ITiledMap";
@@ -160,6 +159,7 @@ export class GameScene extends DirtyScene implements CenterListener {
private createPromise: Promise;
private createPromiseResolve!: (value?: void | PromiseLike) => void;
private iframeSubscriptionList! : Array;
+ private peerStoreUnsubscribe!: () => void;
MapUrlFile: string;
RoomId: string;
instance: string;
@@ -228,11 +228,11 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg);
}
- //todo: in an emote manager.
- this.load.spritesheet('emote-music', 'resources/emotes/pipo-popupemotes005.png', {
- frameHeight: 32,
- frameWidth: 32,
- });
+ 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-report-message', '/resources/objects/report-message.mp3');
+ this.sound.pauseOnBlur = false;
+
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
@@ -279,6 +279,14 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (this.load as any).rexWebFont({
+ custom: {
+ families: ['Press Start 2P'],
+ urls: ['/resources/fonts/fonts.css'],
+ testString: 'abcdefg'
+ },
+ });
//this function must stay at the end of preload function
addLoader(this);
@@ -516,6 +524,21 @@ export class GameScene extends DirtyScene implements CenterListener {
}
this.emoteManager = new EmoteManager(this);
+
+ let oldPeerNumber = 0;
+ this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
+ const newPeerNumber = peers.size;
+ if (newPeerNumber > oldPeerNumber) {
+ this.sound.play('audio-webrtc-in', {
+ volume: 0.2
+ });
+ } else if (newPeerNumber < oldPeerNumber) {
+ this.sound.play('audio-webrtc-out', {
+ volume: 0.2
+ });
+ }
+ oldPeerNumber = newPeerNumber;
+ });
}
/**
@@ -548,6 +571,7 @@ export class GameScene extends DirtyScene implements CenterListener {
characterLayers: message.characterLayers,
name: message.name,
position: message.position,
+ visitCardUrl: message.visitCardUrl,
companion: message.companion
}
this.addPlayer(userMessage);
@@ -957,6 +981,9 @@ ${escapedMessage}
this.userInputManager.destroy();
this.pinchManager?.destroy();
this.emoteManager.destroy();
+ this.peerStoreUnsubscribe();
+
+ mediaManager.hideGameOverlay();
for(const iframeEvents of this.iframeSubscriptionList){
iframeEvents.unsubscribe();
@@ -1146,7 +1173,10 @@ ${escapedMessage}
this.companion,
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
);
- this.CurrentPlayer.on('pointerdown', () => {
+ this.CurrentPlayer.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
+ 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.
+ }
this.emoteManager.getMenuImages().then((emoteMenuElements) => this.CurrentPlayer.openOrCloseEmoteMenu(emoteMenuElements))
})
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
@@ -1346,6 +1376,7 @@ ${escapedMessage}
texturesPromise,
addPlayerData.position.direction as PlayerAnimationDirections,
addPlayerData.position.moving,
+ addPlayerData.visitCardUrl,
addPlayerData.companion,
addPlayerData.companion !== null ? lazyLoadCompanionResource(this.load, addPlayerData.companion) : undefined
);
@@ -1452,8 +1483,8 @@ ${escapedMessage}
this.connection?.emitActionableEvent(itemId, eventName, state, parameters);
}
- public onResize(ev: UIEvent): void {
- super.onResize(ev);
+ public onResize(): void {
+ super.onResize();
this.reposition();
// Send new viewport to server
diff --git a/front/src/Phaser/Game/SoundManager.ts b/front/src/Phaser/Game/SoundManager.ts
index f0210494..47614fca 100644
--- a/front/src/Phaser/Game/SoundManager.ts
+++ b/front/src/Phaser/Game/SoundManager.ts
@@ -17,7 +17,9 @@ class SoundManager {
return res(sound);
}
loadPlugin.audio(soundUrl, soundUrl);
- loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl)));
+ loadPlugin.once('filecomplete-audio-' + soundUrl, () => {
+ res(soundManager.add(soundUrl));
+ });
loadPlugin.start();
});
this.soundPromises.set(soundUrl,soundPromise);
diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts
index 8b9a9a7a..715cb67d 100644
--- a/front/src/Phaser/Login/CustomizeScene.ts
+++ b/front/src/Phaser/Login/CustomizeScene.ts
@@ -2,30 +2,32 @@ import {EnableCameraSceneName} from "./EnableCameraScene";
import Rectangle = Phaser.GameObjects.Rectangle;
import {loadAllLayers} from "../Entity/PlayerTexturesLoadingManager";
import Sprite = Phaser.GameObjects.Sprite;
-import Container = Phaser.GameObjects.Container;
import {gameManager} from "../Game/GameManager";
import {localUserStore} from "../../Connexion/LocalUserStore";
import {addLoader} from "../Components/Loader";
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
import {AbstractCharacterScene} from "./AbstractCharacterScene";
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
-import { MenuScene } from "../Menu/MenuScene";
import { SelectCharacterSceneName } from "./SelectCharacterScene";
+import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore";
+import {waScaleManager} from "../Services/WaScaleManager";
+import {isMobile} from "../../Enum/EnvironmentVariable";
+import {CustomizedCharacter} from "../Entity/CustomizedCharacter";
export const CustomizeSceneName = "CustomizeScene";
-export const CustomizeSceneKey = "CustomizeScene";
-const customizeSceneKey = 'customizeScene';
-
export class CustomizeScene extends AbstractCharacterScene {
private Rectangle!: Rectangle;
private selectedLayers: number[] = [0];
- private containersRow: Container[][] = [];
- private activeRow:number = 0;
+ private containersRow: CustomizedCharacter[][] = [];
+ public activeRow:number = 0;
private layers: BodyResourceDescriptionInterface[][] = [];
- private customizeSceneElement!: Phaser.GameObjects.DOMElement;
+ protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
+
+ private moveHorizontally: number = 0;
+ private moveVertically: number = 0;
constructor() {
super({
@@ -34,9 +36,7 @@ export class CustomizeScene extends AbstractCharacterScene {
}
preload() {
- this.load.html(customizeSceneKey, 'resources/html/CustomCharacterScene.html');
- this.layers = loadAllLayers(this.load);
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if(bodyResourceDescription.level == undefined || bodyResourceDescription.level < 0 || bodyResourceDescription.level > 5 ){
@@ -44,43 +44,28 @@ export class CustomizeScene extends AbstractCharacterScene {
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
+ this.lazyloadingAttempt = true;
});
+ this.layers = loadAllLayers(this.load);
+ this.lazyloadingAttempt = false;
+
+
//this function must stay at the end of preload function
addLoader(this);
}
create() {
- this.customizeSceneElement = this.add.dom(-1000, 0).createFromCache(customizeSceneKey);
- this.centerXDomElement(this.customizeSceneElement, 150);
- MenuScene.revealMenusAfterInit(this.customizeSceneElement, customizeSceneKey);
-
- this.customizeSceneElement.addListener('click');
- this.customizeSceneElement.on('click', (event:MouseEvent) => {
- event.preventDefault();
- if((event?.target as HTMLInputElement).id === 'customizeSceneButtonLeft') {
- this.moveCursorHorizontally(-1);
- }else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonRight') {
- this.moveCursorHorizontally(1);
- }else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonDown') {
- this.moveCursorVertically(1);
- }else if((event?.target as HTMLInputElement).id === 'customizeSceneButtonUp') {
- this.moveCursorVertically(-1);
- }else if((event?.target as HTMLInputElement).id === 'customizeSceneFormBack') {
- if(this.activeRow > 0){
- this.moveCursorVertically(-1);
- }else{
- this.backToPreviousScene();
- }
- }else if((event?.target as HTMLButtonElement).id === 'customizeSceneFormSubmit') {
- if(this.activeRow < 5){
- this.moveCursorVertically(1);
- }else{
- this.nextSceneToCamera();
- }
- }
+ customCharacterSceneVisibleStore.set(true);
+ this.events.addListener('wake', () => {
+ waScaleManager.saveZoom();
+ waScaleManager.zoomModifier = isMobile() ? 3 : 1;
+ customCharacterSceneVisibleStore.set(true);
});
+ waScaleManager.saveZoom();
+ waScaleManager.zoomModifier = isMobile() ? 3 : 1;
+
this.Rectangle = this.add.rectangle(this.cameras.main.worldView.x + this.cameras.main.width / 2, this.cameras.main.worldView.y + this.cameras.main.height / 3, 32, 33)
this.Rectangle.setStrokeStyle(2, 0xFFFFFF);
this.add.existing(this.Rectangle);
@@ -100,10 +85,13 @@ export class CustomizeScene extends AbstractCharacterScene {
this.backToPreviousScene();
});
- this.input.keyboard.on('keyup-RIGHT', () => this.moveCursorHorizontally(1));
- this.input.keyboard.on('keyup-LEFT', () => this.moveCursorHorizontally(-1));
- this.input.keyboard.on('keyup-DOWN', () => this.moveCursorVertically(1));
- this.input.keyboard.on('keyup-UP', () => this.moveCursorVertically(-1));
+ // Note: the key bindings are not directly put on the moveCursorVertically or moveCursorHorizontally methods
+ // because if 2 such events are fired close to one another, it makes the whole application crawl to a halt (for a reason I cannot
+ // explain, the list of sprites managed by the update list become immense
+ this.input.keyboard.on('keyup-RIGHT', () => this.moveHorizontally = 1);
+ this.input.keyboard.on('keyup-LEFT', () => this.moveHorizontally = -1);
+ this.input.keyboard.on('keyup-DOWN', () => this.moveVertically = 1);
+ this.input.keyboard.on('keyup-UP', () => this.moveVertically = -1);
const customCursorPosition = localUserStore.getCustomCursorPosition();
if (customCursorPosition) {
@@ -116,7 +104,15 @@ export class CustomizeScene extends AbstractCharacterScene {
this.onResize();
}
- private moveCursorHorizontally(index: number): void {
+ public moveCursorHorizontally(index: number): void {
+ this.moveHorizontally = index;
+ }
+
+ public moveCursorVertically(index: number): void {
+ this.moveVertically = index;
+ }
+
+ private doMoveCursorHorizontally(index: number): void {
this.selectedLayers[this.activeRow] += index;
if (this.selectedLayers[this.activeRow] < 0) {
this.selectedLayers[this.activeRow] = 0
@@ -128,27 +124,7 @@ export class CustomizeScene extends AbstractCharacterScene {
this.saveInLocalStorage();
}
- private moveCursorVertically(index:number): void {
-
- if(index === -1 && this.activeRow === 5){
- const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
- button.innerHTML = `Next `;
- }
-
- if(index === 1 && this.activeRow === 4){
- const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement;
- button.innerText = 'Finish';
- }
-
- if(index === -1 && this.activeRow === 1){
- const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement;
- button.innerText = `Return`;
- }
-
- if(index === 1 && this.activeRow === 0){
- const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement;
- button.innerHTML = `Back `;
- }
+ private doMoveCursorVertically(index:number): void {
this.activeRow += index;
if (this.activeRow < 0) {
@@ -197,20 +173,20 @@ export class CustomizeScene extends AbstractCharacterScene {
* @param selectedItem, The number of the item select (0 for black body...)
*/
private generateCharacter(x: number, y: number, layerNumber: number, selectedItem: number) {
- return new Container(this, x, y,this.getContainerChildren(layerNumber,selectedItem));
+ return new CustomizedCharacter(this, x, y, this.getContainerChildren(layerNumber,selectedItem));
}
- private getContainerChildren(layerNumber: number, selectedItem: number): Array {
- const children: Array = new Array();
+ private getContainerChildren(layerNumber: number, selectedItem: number): Array {
+ const children: Array = new Array();
for (let j = 0; j <= layerNumber; j++) {
if (j === layerNumber) {
- children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name));
+ children.push(this.layers[j][selectedItem].name);
} else {
const layer = this.selectedLayers[j];
if (layer === undefined) {
continue;
}
- children.push(this.generateLayers(0, 0, this.layers[j][layer].name));
+ children.push(this.layers[j][layer].name);
}
}
return children;
@@ -247,33 +223,47 @@ export class CustomizeScene extends AbstractCharacterScene {
* @return a new sprite
*/
private generateLayers(x: number, y: number, name: string): Sprite {
- return new Sprite(this, x, y, name);
+ //return new Sprite(this, x, y, name);
+ return this.add.sprite(0, 0, name);
}
private updateSelectedLayer() {
for(let i = 0; i < this.containersRow.length; i++){
for(let j = 0; j < this.containersRow[i].length; j++){
- const children = this.getContainerChildren(i, j);
- this.containersRow[i][j].removeAll(true);
- this.containersRow[i][j].add(children);
+ const children = this.getContainerChildren(i, j);
+ this.containersRow[i][j].updateSprites(children);
}
}
}
update(time: number, delta: number): void {
+ if(this.lazyloadingAttempt){
+ this.moveLayers();
+ this.lazyloadingAttempt = false;
+ }
+
+ if (this.moveHorizontally !== 0) {
+ this.doMoveCursorHorizontally(this.moveHorizontally);
+ this.moveHorizontally = 0;
+ }
+ if (this.moveVertically !== 0) {
+ this.doMoveCursorVertically(this.moveVertically);
+ this.moveVertically = 0;
+ }
}
+
+
+
public onResize(): void {
this.moveLayers();
this.Rectangle.x = this.cameras.main.worldView.x + this.cameras.main.width / 2;
this.Rectangle.y = this.cameras.main.worldView.y + this.cameras.main.height / 3;
-
- this.centerXDomElement(this.customizeSceneElement, 150);
}
- private nextSceneToCamera(){
+ public nextSceneToCamera(){
const layers: string[] = [];
let i = 0;
for (const layerItem of this.selectedLayers) {
@@ -288,12 +278,16 @@ export class CustomizeScene extends AbstractCharacterScene {
gameManager.setCharacterLayers(layers);
this.scene.sleep(CustomizeSceneName);
- this.scene.remove(SelectCharacterSceneName);
+ waScaleManager.restoreZoom();
+ this.events.removeListener('wake');
gameManager.tryResumingGame(this, EnableCameraSceneName);
+ customCharacterSceneVisibleStore.set(false);
}
- private backToPreviousScene(){
+ public backToPreviousScene(){
this.scene.sleep(CustomizeSceneName);
+ waScaleManager.restoreZoom();
this.scene.run(SelectCharacterSceneName);
+ customCharacterSceneVisibleStore.set(false);
}
}
diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts
index 6002da7b..ba27cd07 100644
--- a/front/src/Phaser/Login/EnableCameraScene.ts
+++ b/front/src/Phaser/Login/EnableCameraScene.ts
@@ -3,7 +3,6 @@ import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import {mediaManager} from "../../WebRtc/MediaManager";
import {SoundMeter} from "../Components/SoundMeter";
-import {SoundMeterSprite} from "../Components/SoundMeterSprite";
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
@@ -11,304 +10,41 @@ import Zone = Phaser.GameObjects.Zone;
import { MenuScene } from "../Menu/MenuScene";
import {ResizableScene} from "./ResizableScene";
import {
- audioConstraintStore,
enableCameraSceneVisibilityStore,
- localStreamStore,
- mediaStreamConstraintsStore,
- videoConstraintStore
} from "../../Stores/MediaStore";
-import type {Unsubscriber} from "svelte/store";
export const EnableCameraSceneName = "EnableCameraScene";
-enum LoginTextures {
- playButton = "play_button",
- icon = "icon",
- mainFont = "main_font",
- arrowRight = "arrow_right",
- arrowUp = "arrow_up"
-}
-
-const enableCameraSceneKey = 'enableCameraScene';
export class EnableCameraScene extends ResizableScene {
- private textField!: TextField;
- private cameraNameField!: TextField;
- private arrowLeft!: Image;
- private arrowRight!: Image;
- private arrowDown!: Image;
- private arrowUp!: Image;
- private microphonesList: MediaDeviceInfo[] = new Array();
- private camerasList: MediaDeviceInfo[] = new Array();
- private cameraSelected: number = 0;
- private microphoneSelected: number = 0;
- private soundMeter: SoundMeter;
- private soundMeterSprite!: SoundMeterSprite;
- private microphoneNameField!: TextField;
-
- private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
-
- private mobileTapZone!: Zone;
- private localStreamStoreUnsubscriber!: Unsubscriber;
constructor() {
super({
key: EnableCameraSceneName
});
- this.soundMeter = new SoundMeter();
}
preload() {
-
- this.load.html(enableCameraSceneKey, 'resources/html/EnableCameraScene.html');
-
- this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
- this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png");
- this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png");
- // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
- this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
}
create() {
- this.enableCameraSceneElement = this.add.dom(-1000, 0).createFromCache(enableCameraSceneKey);
- this.centerXDomElement(this.enableCameraSceneElement, 300);
-
- MenuScene.revealMenusAfterInit(this.enableCameraSceneElement, enableCameraSceneKey);
-
- const continuingButton = this.enableCameraSceneElement.getChildByID('enableCameraSceneFormSubmit') as HTMLButtonElement;
- continuingButton.addEventListener('click', (e) => {
- e.preventDefault();
- this.login();
- });
-
- if (touchScreenManager.supportTouchScreen) {
- new PinchManager(this);
- }
- //this.scale.setZoom(ZOOM_LEVEL);
- //Phaser.Display.Align.In.BottomCenter(this.pressReturnField, zone);
-
- /* FIX ME */
- this.textField = new TextField(this, this.scale.width / 2, 20, '');
-
- // For mobile purposes - we need a big enough touchable area.
- this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50)
- .setInteractive().on("pointerdown", () => {
- this.login();
- });
-
- this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, '');
-
- this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
-
- this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
- this.arrowRight.setVisible(false);
- this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
- this.add.existing(this.arrowRight);
-
- this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
- this.arrowLeft.setVisible(false);
- this.arrowLeft.flipX = true;
- this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
- this.add.existing(this.arrowLeft);
-
- this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
- this.arrowUp.setVisible(false);
- this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
- this.add.existing(this.arrowUp);
-
- this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
- this.arrowDown.setVisible(false);
- this.arrowDown.flipY = true;
- this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
- this.add.existing(this.arrowDown);
-
this.input.keyboard.on('keyup-ENTER', () => {
this.login();
});
- HtmlUtils.getElementByIdOrFail('webRtcSetup').classList.add('active');
-
- this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => {
- if (result.type === 'error') {
- // TODO: proper handling of the error
- throw result.error;
- }
-
- this.getDevices();
- if (result.stream !== null) {
- this.setupStream(result.stream);
- }
- });
- /*const mediaPromise = mediaManager.getCamera();
- mediaPromise.then(this.getDevices.bind(this));
- mediaPromise.then(this.setupStream.bind(this));*/
-
- this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this));
- this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this));
- this.input.keyboard.on('keydown-DOWN', this.nextMic.bind(this));
- this.input.keyboard.on('keydown-UP', this.previousMic.bind(this));
-
- this.soundMeterSprite = new SoundMeterSprite(this, 50, 50);
- this.soundMeterSprite.setVisible(false);
- this.add.existing(this.soundMeterSprite);
-
- this.onResize();
-
enableCameraSceneVisibilityStore.showEnableCameraScene();
}
- private previousCam(): void {
- if (this.cameraSelected === 0 || this.camerasList.length === 0) {
- return;
- }
- this.cameraSelected--;
- videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
-
- //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
- }
-
- private nextCam(): void {
- if (this.cameraSelected === this.camerasList.length - 1 || this.camerasList.length === 0) {
- return;
- }
- this.cameraSelected++;
- videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId);
-
- // TODO: the change of camera should be OBSERVED (reactive)
- //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this));
- }
-
- private previousMic(): void {
- if (this.microphoneSelected === 0 || this.microphonesList.length === 0) {
- return;
- }
- this.microphoneSelected--;
- audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
- //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
- }
-
- private nextMic(): void {
- if (this.microphoneSelected === this.microphonesList.length - 1 || this.microphonesList.length === 0) {
- return;
- }
- this.microphoneSelected++;
- audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId);
- // TODO: the change of camera should be OBSERVED (reactive)
- //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this));
- }
-
- /**
- * Function called each time a camera is changed
- */
- private setupStream(stream: MediaStream): void {
- const img = HtmlUtils.getElementByIdOrFail('webRtcSetupNoVideo');
- img.style.display = 'none';
-
- const div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup');
- div.srcObject = stream;
-
- this.soundMeter.connectToSource(stream, new window.AudioContext());
- this.soundMeterSprite.setVisible(true);
-
- this.updateWebCamName();
- }
-
- private updateWebCamName(): void {
- if (this.camerasList.length > 1) {
- let label = this.camerasList[this.cameraSelected].label;
- // remove text in parenthesis
- label = label.replace(/\([^()]*\)/g, '').trim();
- // remove accents
- label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
- this.cameraNameField.text = label;
-
- this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
- this.arrowLeft.setVisible(this.cameraSelected > 0);
- }
- if (this.microphonesList.length > 1) {
- let label = this.microphonesList[this.microphoneSelected].label;
- // remove text in parenthesis
- label = label.replace(/\([^()]*\)/g, '').trim();
- // remove accents
- label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
-
- this.microphoneNameField.text = label;
-
- this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
- this.arrowUp.setVisible(this.microphoneSelected > 0);
-
- }
- }
-
public onResize(): void {
- let div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup');
- let bounds = div.getBoundingClientRect();
- if (!div.srcObject) {
- div = HtmlUtils.getElementByIdOrFail('webRtcSetup');
- bounds = div.getBoundingClientRect();
- }
-
- this.textField.x = this.game.renderer.width / 2;
- this.mobileTapZone.x = this.game.renderer.width / 2;
- this.cameraNameField.x = this.game.renderer.width / 2;
- this.microphoneNameField.x = this.game.renderer.width / 2;
-
- this.cameraNameField.y = bounds.top / this.scale.zoom - 8;
-
- this.soundMeterSprite.x = this.game.renderer.width / 2 - this.soundMeterSprite.getWidth() / 2;
- this.soundMeterSprite.y = bounds.bottom / this.scale.zoom + 16;
-
- this.microphoneNameField.y = this.soundMeterSprite.y + 22;
-
- this.arrowRight.x = bounds.right / this.scale.zoom + 16;
- this.arrowRight.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
-
- this.arrowLeft.x = bounds.left / this.scale.zoom - 16;
- this.arrowLeft.y = (bounds.top + bounds.height / 2) / this.scale.zoom;
-
- this.arrowDown.x = this.microphoneNameField.x + this.microphoneNameField.width / 2 + 16;
- this.arrowDown.y = this.microphoneNameField.y;
-
- this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16;
- this.arrowUp.y = this.microphoneNameField.y;
-
- const actionBtn = document.querySelector('#enableCameraScene .action');
- if (actionBtn !== null) {
- actionBtn.style.top = (this.scale.height - 65) + 'px';
- }
}
update(time: number, delta: number): void {
- this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
-
- this.centerXDomElement(this.enableCameraSceneElement, 300);
}
- private login(): void {
- HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none';
- this.soundMeter.stop();
-
+ public login(): void {
enableCameraSceneVisibilityStore.hideEnableCameraScene();
- this.localStreamStoreUnsubscriber();
- //mediaManager.stopCamera();
- //mediaManager.stopMicrophone();
this.scene.sleep(EnableCameraSceneName);
gameManager.goToStartingMap(this.scene);
}
-
- private async getDevices() {
- // TODO: switch this in a store.
- const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
- this.microphonesList = [];
- this.camerasList = [];
- for (const mediaDeviceInfo of mediaDeviceInfos) {
- if (mediaDeviceInfo.kind === 'audioinput') {
- this.microphonesList.push(mediaDeviceInfo);
- } else if (mediaDeviceInfo.kind === 'videoinput') {
- this.camerasList.push(mediaDeviceInfo);
- }
- }
- this.updateWebCamName();
- }
}
diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts
index 435592f2..39a8f5f3 100644
--- a/front/src/Phaser/Login/LoginScene.ts
+++ b/front/src/Phaser/Login/LoginScene.ts
@@ -1,17 +1,12 @@
import {gameManager} from "../Game/GameManager";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
-import { localUserStore } from "../../Connexion/LocalUserStore";
-import {MenuScene} from "../Menu/MenuScene";
-import { isUserNameValid } from "../../Connexion/LocalUser";
+import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
export const LoginSceneName = "LoginScene";
-const loginSceneKey = 'loginScene';
-
export class LoginScene extends ResizableScene {
- private loginSceneElement!: Phaser.GameObjects.DOMElement;
private name: string = '';
constructor() {
@@ -22,65 +17,25 @@ export class LoginScene extends ResizableScene {
}
preload() {
- this.load.html(loginSceneKey, 'resources/html/loginScene.html');
}
create() {
- this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey);
- this.centerXDomElement(this.loginSceneElement, 200);
- MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey);
-
- const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
- const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement;
- inputElement.value = localUserStore.getName() ?? '';
- inputElement.focus();
- inputElement.addEventListener('keypress', (event: KeyboardEvent) => {
- if(inputElement.value.length > 7){
- event.preventDefault();
- return;
- }
- pErrorElement.innerHTML = '';
- if(inputElement.value && !isUserNameValid(inputElement.value)){
- pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
- }
- if (event.key === 'Enter') {
- event.preventDefault();
- this.login(inputElement);
- return;
- }
- });
-
- const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement;
- continuingButton.addEventListener('click', (e) => {
- e.preventDefault();
- this.login(inputElement);
- });
+ loginSceneVisibleStore.set(true);
}
- private login(inputElement: HTMLInputElement): void {
- const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
- this.name = inputElement.value;
- if (this.name === '') {
- pErrorElement.innerHTML = 'The name is empty';
- return
- }
- if(!isUserNameValid(this.name)){
- pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
- return
- }
- if (this.name === '') return
- gameManager.setPlayerName(this.name);
+ public login(name: string): void {
+ name = name.trim();
+ gameManager.setPlayerName(name);
this.scene.stop(LoginSceneName)
gameManager.tryResumingGame(this, SelectCharacterSceneName);
- this.scene.remove(LoginSceneName)
+ this.scene.remove(LoginSceneName);
+ loginSceneVisibleStore.set(false);
}
update(time: number, delta: number): void {
-
}
- public onResize(ev: UIEvent): void {
- this.centerXDomElement(this.loginSceneElement, 200);
+ public onResize(): void {
}
}
diff --git a/front/src/Phaser/Login/ResizableScene.ts b/front/src/Phaser/Login/ResizableScene.ts
index 39e2d74b..d06cb66c 100644
--- a/front/src/Phaser/Login/ResizableScene.ts
+++ b/front/src/Phaser/Login/ResizableScene.ts
@@ -2,7 +2,7 @@ import {Scene} from "phaser";
import DOMElement = Phaser.GameObjects.DOMElement;
export abstract class ResizableScene extends Scene {
- public abstract onResize(ev: UIEvent): void;
+ public abstract onResize(): void;
/**
* Centers the DOM element on the X axis.
@@ -17,7 +17,7 @@ export abstract class ResizableScene extends Scene {
&& object.node
&& object.node.getBoundingClientRect().width > 0
? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom)
- : (300 / this.scale.zoom)
+ : (defaultWidth / this.scale.zoom)
);
}
}
diff --git a/front/src/Phaser/Login/SelectCharacterMobileScene.ts b/front/src/Phaser/Login/SelectCharacterMobileScene.ts
index b9c4b5a8..0d8e49d5 100644
--- a/front/src/Phaser/Login/SelectCharacterMobileScene.ts
+++ b/front/src/Phaser/Login/SelectCharacterMobileScene.ts
@@ -4,49 +4,50 @@ export class SelectCharacterMobileScene extends SelectCharacterScene {
create(){
super.create();
+ this.onResize();
this.selectedRectangle.destroy();
}
- protected defineSetupPlayer(numero: number){
+ protected defineSetupPlayer(num: number){
const deltaX = 30;
const deltaY = 2;
let [playerX, playerY] = this.getCharacterPosition();
let playerVisible = true;
let playerScale = 1.5;
- let playserOpactity = 1;
+ let playerOpacity = 1;
- if( this.currentSelectUser !== numero ){
+ if( this.currentSelectUser !== num ){
playerVisible = false;
}
- if( numero === (this.currentSelectUser + 1) ){
+ if( num === (this.currentSelectUser + 1) ){
playerY -= deltaY;
playerX += deltaX;
playerScale = 0.8;
- playserOpactity = 0.6;
+ playerOpacity = 0.6;
playerVisible = true;
}
- if( numero === (this.currentSelectUser + 2) ){
+ if( num === (this.currentSelectUser + 2) ){
playerY -= deltaY;
playerX += (deltaX * 2);
playerScale = 0.8;
- playserOpactity = 0.6;
+ playerOpacity = 0.6;
playerVisible = true;
}
- if( numero === (this.currentSelectUser - 1) ){
+ if( num === (this.currentSelectUser - 1) ){
playerY -= deltaY;
playerX -= deltaX;
playerScale = 0.8;
- playserOpactity = 0.6;
+ playerOpacity = 0.6;
playerVisible = true;
}
- if( numero === (this.currentSelectUser - 2) ){
+ if( num === (this.currentSelectUser - 2) ){
playerY -= deltaY;
playerX -= (deltaX * 2);
playerScale = 0.8;
- playserOpactity = 0.6;
+ playerOpacity = 0.6;
playerVisible = true;
}
- return {playerX, playerY, playerScale, playserOpactity, playerVisible}
+ return {playerX, playerY, playerScale, playerOpacity, playerVisible}
}
/**
diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts
index ecbb9c64..0f590840 100644
--- a/front/src/Phaser/Login/SelectCharacterScene.ts
+++ b/front/src/Phaser/Login/SelectCharacterScene.ts
@@ -10,13 +10,13 @@ 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 {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
+import {waScaleManager} from "../Services/WaScaleManager";
+import {isMobile} from "../../Enum/EnvironmentVariable";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";
-const selectCharacterKey = 'selectCharacterScene';
-
export class SelectCharacterScene extends AbstractCharacterScene {
protected readonly nbCharactersPerRow = 6;
protected selectedPlayer!: Phaser.Physics.Arcade.Sprite|null; // null if we are selecting the "customize" option
@@ -25,8 +25,11 @@ export class SelectCharacterScene extends AbstractCharacterScene {
protected selectedRectangle!: Rectangle;
- protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement;
protected currentSelectUser = 0;
+ protected pointerClicked: boolean = false;
+ protected pointerTimer: number = 0;
+
+ protected lazyloadingAttempt = true; //permit to update texture loaded after renderer
constructor() {
super({
@@ -35,50 +38,41 @@ export class SelectCharacterScene extends AbstractCharacterScene {
}
preload() {
- this.load.html(selectCharacterKey, 'resources/html/selectCharacterScene.html');
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
- })
+ this.lazyloadingAttempt = true;
+ });
this.playerModels = loadAllDefaultModels(this.load);
+ this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
addLoader(this);
}
create() {
-
- this.selectCharacterSceneElement = this.add.dom(-1000, 0).createFromCache(selectCharacterKey);
- this.centerXDomElement(this.selectCharacterSceneElement, 150);
- MenuScene.revealMenusAfterInit(this.selectCharacterSceneElement, selectCharacterKey);
-
- this.selectCharacterSceneElement.addListener('click');
- this.selectCharacterSceneElement.on('click', (event:MouseEvent) => {
- event.preventDefault();
- if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') {
- this.moveToLeft();
- }else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') {
- this.moveToRight();
- }else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormSubmit') {
- this.nextSceneToCameraScene();
- }else if((event?.target as HTMLInputElement).id === 'selectCharacterSceneFormCustomYourOwnSubmit') {
- this.nextSceneToCustomizeScene();
- }
+ selectCharacterSceneVisibleStore.set(true);
+ this.events.addListener('wake', () => {
+ waScaleManager.saveZoom();
+ waScaleManager.zoomModifier = isMobile() ? 2 : 1;
+ selectCharacterSceneVisibleStore.set(true);
});
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
+ waScaleManager.saveZoom();
+ waScaleManager.zoomModifier = isMobile() ? 2 : 1;
+
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.setDepth(2);
/*create user*/
this.createCurrentPlayer();
- const playerNumber = localUserStore.getPlayerCharacterIndex();
this.input.keyboard.on('keyup-ENTER', () => {
return this.nextSceneToCameraScene();
@@ -98,7 +92,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
});
}
- protected nextSceneToCameraScene(): void {
+ public nextSceneToCameraScene(): void {
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
return;
}
@@ -106,23 +100,33 @@ export class SelectCharacterScene extends AbstractCharacterScene {
return;
}
this.scene.stop(SelectCharacterSceneName);
+ waScaleManager.restoreZoom();
gameManager.setCharacterLayers([this.selectedPlayer.texture.key]);
gameManager.tryResumingGame(this, EnableCameraSceneName);
- this.scene.remove(SelectCharacterSceneName);
+ this.players = [];
+ selectCharacterSceneVisibleStore.set(false);
+ this.events.removeListener('wake');
}
- protected nextSceneToCustomizeScene(): void {
+ public nextSceneToCustomizeScene(): void {
if (this.selectedPlayer !== null && !areCharacterLayersValid([this.selectedPlayer.texture.key])) {
return;
}
this.scene.sleep(SelectCharacterSceneName);
+ waScaleManager.restoreZoom();
this.scene.run(CustomizeSceneName);
+ selectCharacterSceneVisibleStore.set(false);
}
createCurrentPlayer(): void {
for (let i = 0; i c.texture.key === playerResource.name)){
+ continue;
+ }
+
const [middleX, middleY] = this.getCharacterPosition();
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
this.setUpPlayer(player, i);
@@ -133,15 +137,22 @@ export class SelectCharacterScene extends AbstractCharacterScene {
repeat: -1
});
player.setInteractive().on("pointerdown", () => {
- if(this.currentSelectUser === i){
+ if (this.pointerClicked) {
return;
}
+ if (this.currentSelectUser === i) {
+ return;
+ }
+ //To not trigger two time the pointerdown events :
+ // We set a boolean to true so that pointerdown events does nothing when the boolean is true
+ // We set a timer that we decrease in update function to not trigger the pointerdown events twice
+ this.pointerClicked = true;
+ this.pointerTimer = 250;
this.currentSelectUser = i;
this.moveUser();
});
this.players.push(player);
}
-
this.selectedPlayer = this.players[this.currentSelectUser];
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
}
@@ -154,7 +165,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.updateSelectedPlayer();
}
- protected moveToLeft(){
+ public moveToLeft(){
if(this.currentSelectUser === 0){
return;
}
@@ -162,7 +173,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.moveUser();
}
- protected moveToRight(){
+ public moveToRight(){
if(this.currentSelectUser === (this.players.length - 1)){
return;
}
@@ -186,35 +197,35 @@ export class SelectCharacterScene extends AbstractCharacterScene {
this.moveUser();
}
- protected defineSetupPlayer(numero: number){
+ protected defineSetupPlayer(num: number){
const deltaX = 32;
const deltaY = 32;
let [playerX, playerY] = this.getCharacterPosition(); // player X and player y are middle of the
- playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (numero % this.nbCharactersPerRow)) ); // calcul position on line users
- playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(numero / this.nbCharactersPerRow) )) ); // calcul position on column users
+ playerX = ( (playerX - (deltaX * 2.5)) + ((deltaX) * (num % this.nbCharactersPerRow)) ); // calcul position on line users
+ playerY = ( (playerY - (deltaY * 2)) + ((deltaY) * ( Math.floor(num / this.nbCharactersPerRow) )) ); // calcul position on column users
const playerVisible = true;
const playerScale = 1;
- const playserOpactity = 1;
+ const playerOpacity = 1;
// if selected
- if( numero === this.currentSelectUser ){
+ if( num === this.currentSelectUser ){
this.selectedRectangle.setX(playerX);
this.selectedRectangle.setY(playerY);
}
- return {playerX, playerY, playerScale, playserOpactity, playerVisible}
+ return {playerX, playerY, playerScale, playerOpacity, playerVisible}
}
- protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, numero: number){
+ protected setUpPlayer(player: Phaser.Physics.Arcade.Sprite, num: number){
- const {playerX, playerY, playerScale, playserOpactity, playerVisible} = this.defineSetupPlayer(numero);
+ const {playerX, playerY, playerScale, playerOpacity, playerVisible} = this.defineSetupPlayer(num);
player.setBounce(0.2);
- player.setCollideWorldBounds(true);
+ player.setCollideWorldBounds(false);
player.setVisible( playerVisible );
player.setScale(playerScale, playerScale);
- player.setAlpha(playserOpactity);
+ player.setAlpha(playerOpacity);
player.setX(playerX);
player.setY(playerY);
}
@@ -238,12 +249,23 @@ export class SelectCharacterScene extends AbstractCharacterScene {
}
update(time: number, delta: number): void {
+ // pointerTimer is set to 250 when pointerdown events is trigger
+ // After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
+ this.pointerTimer -= delta;
+ if (this.pointerTimer <= 0) {
+ this.pointerClicked = false;
+ }
+
+ if(this.lazyloadingAttempt){
+ //re-render players list
+ this.createCurrentPlayer();
+ this.moveUser();
+ this.lazyloadingAttempt = false;
+ }
}
- public onResize(ev: UIEvent): void {
+ public onResize(): void {
//move position of user
this.moveUser();
-
- this.centerXDomElement(this.selectCharacterSceneElement, 150);
}
}
diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts
index 203fd557..4c29f942 100644
--- a/front/src/Phaser/Login/SelectCompanionScene.ts
+++ b/front/src/Phaser/Login/SelectCompanionScene.ts
@@ -10,18 +10,21 @@ import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingM
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import { MenuScene } from "../Menu/MenuScene";
+import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
+import {waScaleManager} from "../Services/WaScaleManager";
+import {isMobile} from "../../Enum/EnvironmentVariable";
export const SelectCompanionSceneName = "SelectCompanionScene";
-const selectCompanionSceneKey = 'selectCompanionScene';
-
export class SelectCompanionScene extends ResizableScene {
private selectedCompanion!: Phaser.Physics.Arcade.Sprite;
private companions: Array = new Array();
private companionModels: Array = [];
+ private saveZoom: number = 0;
- private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement;
private currentCompanion = 0;
+ private pointerClicked: boolean = false;
+ private pointerTimer: number = 0;
constructor() {
super({
@@ -30,8 +33,6 @@ export class SelectCompanionScene extends ResizableScene {
}
preload() {
- this.load.html(selectCompanionSceneKey, 'resources/html/SelectCompanionScene.html');
-
getAllCompanionResources(this.load).forEach(model => {
this.companionModels.push(model);
});
@@ -42,30 +43,17 @@ export class SelectCompanionScene extends ResizableScene {
create() {
- this.selectCompanionSceneElement = this.add.dom(-1000, 0).createFromCache(selectCompanionSceneKey);
- this.centerXDomElement(this.selectCompanionSceneElement, 150);
- MenuScene.revealMenusAfterInit(this.selectCompanionSceneElement, selectCompanionSceneKey);
+ selectCompanionSceneVisibleStore.set(true);
- this.selectCompanionSceneElement.addListener('click');
- this.selectCompanionSceneElement.on('click', (event:MouseEvent) => {
- event.preventDefault();
- if((event?.target as HTMLInputElement).id === 'selectCharacterButtonLeft') {
- this.moveToLeft();
- }else if((event?.target as HTMLInputElement).id === 'selectCharacterButtonRight') {
- this.moveToRight();
- }else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormSubmit') {
- this.nextScene();
- }else if((event?.target as HTMLInputElement).id === 'selectCompanionSceneFormBack') {
- this._nextScene();
- }
- });
+ waScaleManager.saveZoom();
+ waScaleManager.zoomModifier = isMobile() ? 2 : 1;
if (touchScreenManager.supportTouchScreen) {
new PinchManager(this);
}
// input events
- this.input.keyboard.on('keyup-ENTER', this.nextScene.bind(this));
+ this.input.keyboard.on('keyup-ENTER', this.selectCompanion.bind(this));
this.input.keyboard.on('keydown-RIGHT', this.moveToRight.bind(this));
this.input.keyboard.on('keydown-LEFT', this.moveToLeft.bind(this));
@@ -86,21 +74,28 @@ export class SelectCompanionScene extends ResizableScene {
}
update(time: number, delta: number): void {
-
+ // pointerTimer is set to 250 when pointerdown events is trigger
+ // After 250ms, pointerClicked is set to false and the pointerdown events can be trigger again
+ this.pointerTimer -= delta;
+ if (this.pointerTimer <= 0) {
+ this.pointerClicked = false;
+ }
}
- private nextScene(): void {
+ public selectCompanion(): void {
localUserStore.setCompanion(this.companionModels[this.currentCompanion].name);
gameManager.setCompanion(this.companionModels[this.currentCompanion].name);
- this._nextScene();
+ this.closeScene();
}
- private _nextScene(){
+ public closeScene(){
// next scene
this.scene.stop(SelectCompanionSceneName);
+ waScaleManager.restoreZoom();
gameManager.tryResumingGame(this, EnableCameraSceneName);
this.scene.remove(SelectCompanionSceneName);
+ selectCompanionSceneVisibleStore.set(false);
}
private createCurrentCompanion(): void {
@@ -117,6 +112,14 @@ export class SelectCompanionScene extends ResizableScene {
});
companion.setInteractive().on("pointerdown", () => {
+ if (this.pointerClicked) {
+ return;
+ }
+ //To not trigger two time the pointerdown events :
+ // We set a boolean to true so that pointerdown events does nothing when the boolean is true
+ // We set a timer that we decrease in update function to not trigger the pointerdown events twice
+ this.pointerClicked = true;
+ this.pointerTimer = 250;
this.currentCompanion = i;
this.moveCompanion();
});
@@ -126,10 +129,8 @@ export class SelectCompanionScene extends ResizableScene {
this.selectedCompanion = this.companions[this.currentCompanion];
}
- public onResize(ev: UIEvent): void {
+ public onResize(): void {
this.moveCompanion();
-
- this.centerXDomElement(this.selectCompanionSceneElement, 150);
}
private updateSelectedCompanion(): void {
@@ -147,15 +148,7 @@ export class SelectCompanionScene extends ResizableScene {
this.updateSelectedCompanion();
}
- private moveToLeft(){
- if(this.currentCompanion === 0){
- return;
- }
- this.currentCompanion -= 1;
- this.moveCompanion();
- }
-
- private moveToRight(){
+ public moveToRight(){
if(this.currentCompanion === (this.companions.length - 1)){
return;
}
@@ -163,38 +156,46 @@ export class SelectCompanionScene extends ResizableScene {
this.moveCompanion();
}
- private defineSetupCompanion(numero: number){
+ public moveToLeft(){
+ if(this.currentCompanion === 0){
+ return;
+ }
+ this.currentCompanion -= 1;
+ this.moveCompanion();
+ }
+
+ private defineSetupCompanion(num: number){
const deltaX = 30;
const deltaY = 2;
let [companionX, companionY] = this.getCompanionPosition();
let companionVisible = true;
let companionScale = 1.5;
let companionOpactity = 1;
- if( this.currentCompanion !== numero ){
+ if( this.currentCompanion !== num ){
companionVisible = false;
}
- if( numero === (this.currentCompanion + 1) ){
+ if( num === (this.currentCompanion + 1) ){
companionY -= deltaY;
companionX += deltaX;
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
- if( numero === (this.currentCompanion + 2) ){
+ if( num === (this.currentCompanion + 2) ){
companionY -= deltaY;
companionX += (deltaX * 2);
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
- if( numero === (this.currentCompanion - 1) ){
+ if( num === (this.currentCompanion - 1) ){
companionY -= deltaY;
companionX -= deltaX;
companionScale = 0.8;
companionOpactity = 0.6;
companionVisible = true;
}
- if( numero === (this.currentCompanion - 2) ){
+ if( num === (this.currentCompanion - 2) ){
companionY -= deltaY;
companionX -= (deltaX * 2);
companionScale = 0.8;
diff --git a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts b/front/src/Phaser/Menu/HelpCameraSettingsScene.ts
deleted file mode 100644
index 6bc520c0..00000000
--- a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import {mediaManager} from "../../WebRtc/MediaManager";
-import {HtmlUtils} from "../../WebRtc/HtmlUtils";
-import {localUserStore} from "../../Connexion/LocalUserStore";
-import {DirtyScene} from "../Game/DirtyScene";
-import {get} from "svelte/store";
-import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore";
-
-export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
-const helpCameraSettings = 'helpCameraSettings';
-/**
- * The scene that show how to permit Camera and Microphone access if there are not already allowed
- */
-export class HelpCameraSettingsScene extends DirtyScene {
- private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
- private helpCameraSettingsOpened: boolean = false;
-
- constructor() {
- super({key: HelpCameraSettingsSceneName});
- }
-
- preload() {
- this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
- }
-
- create(){
- this.createHelpCameraSettings();
- }
-
- private createHelpCameraSettings() : void {
- const middleX = this.getMiddleX();
- this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
- this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
- this.helpCameraSettingsElement.addListener('click');
- this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
- if((event?.target as HTMLInputElement).id === 'mailto') {
- return;
- }
- event.preventDefault();
- if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
- window.location.reload();
- }else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
- this.closeHelpCameraSettingsOpened();
- }
- });
-
- if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){
- this.openHelpCameraSettingsOpened();
- localUserStore.setHelpCameraSettingsShown();
- }
-
- mediaManager.setHelpCameraSettingsCallBack(() => {
- this.openHelpCameraSettingsOpened();
- });
- }
-
- private openHelpCameraSettingsOpened(): void{
- HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none';
- this.helpCameraSettingsOpened = true;
- try{
- if(window.navigator.userAgent.includes('Firefox')){
- HtmlUtils.getElementByIdOrFail('browserHelpSetting').innerHTML ='';
- }else if(window.navigator.userAgent.includes('Chrome')){
- HtmlUtils.getElementByIdOrFail('browserHelpSetting').innerHTML ='';
- }
- }catch(err) {
- console.error('openHelpCameraSettingsOpened => getElementByIdOrFail => error', err);
- }
- const middleY = this.getMiddleY();
- const middleX = this.getMiddleX();
- this.tweens.add({
- targets: this.helpCameraSettingsElement,
- y: middleY,
- x: middleX,
- duration: 1000,
- ease: 'Power3',
- overflow: 'scroll'
- });
-
- this.dirty = true;
- }
-
- private closeHelpCameraSettingsOpened(): void{
- const middleX = this.getMiddleX();
- /*const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
- helpCameraSettingsInfo.innerText = '';
- helpCameraSettingsInfo.style.display = 'none';*/
- this.helpCameraSettingsOpened = false;
- this.tweens.add({
- targets: this.helpCameraSettingsElement,
- y: -1000,
- x: middleX,
- duration: 1000,
- ease: 'Power3',
- overflow: 'scroll'
- });
-
- this.dirty = true;
- }
-
- private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
- //Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
- //To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
- setTimeout(() => {
- (menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
- }, 250);
- }
-
- update(time: number, delta: number): void {
- this.dirty = false;
- }
-
- public onResize(ev: UIEvent): void {
- super.onResize(ev);
- 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{
- return (this.scale.width / 2) -
- (
- this.helpCameraSettingsElement
- && this.helpCameraSettingsElement.node
- && this.helpCameraSettingsElement.node.getBoundingClientRect().width > 0
- ? (this.helpCameraSettingsElement.node.getBoundingClientRect().width / (2 * this.scale.zoom))
- : (400 / 2)
- );
- }
-
- private getMiddleY() : number{
- const middleY = ((this.scale.height) - (
- (this.helpCameraSettingsElement
- && this.helpCameraSettingsElement.node
- && this.helpCameraSettingsElement.node.getBoundingClientRect().height > 0
- ? this.helpCameraSettingsElement.node.getBoundingClientRect().height : 400 /*FIXME to use a const will be injected in HTMLElement*/)/this.scale.zoom)) / 2;
- return (middleY > 0 ? middleY : 0);
- }
-
- public isDirty(): boolean {
- return this.dirty;
- }
-}
-
diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts
index b7f31aad..7ed366f7 100644
--- a/front/src/Phaser/Player/Player.ts
+++ b/front/src/Phaser/Player/Player.ts
@@ -26,7 +26,7 @@ export class Player extends Character {
companion: string|null,
companionTexturePromise?: Promise
) {
- super(Scene, x, y, texturesPromise, name, direction, moving, 1, companion, companionTexturePromise);
+ super(Scene, x, y, texturesPromise, name, direction, moving, 1, true, companion, companionTexturePromise);
//the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false);
@@ -102,10 +102,6 @@ export class Player extends Character {
}
}
- isClickable(): boolean {
- return true;
- }
-
openEmoteMenu(emotes:RadialMenuItem[]): void {
this.cancelPreviousEmote();
this.emoteMenu = new RadialMenu(this.scene, this.x, this.y, emotes)
diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts
index ca8b668d..abfd2a8b 100644
--- a/front/src/Phaser/Services/WaScaleManager.ts
+++ b/front/src/Phaser/Services/WaScaleManager.ts
@@ -2,6 +2,8 @@ import {HdpiManager} from "./HdpiManager";
import ScaleManager = Phaser.Scale.ScaleManager;
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import type {Game} from "../Game/Game";
+import {ResizableScene} from "../Login/ResizableScene";
+import {HtmlUtils} from "../../WebRtc/HtmlUtils";
class WaScaleManager {
@@ -9,6 +11,7 @@ class WaScaleManager {
private scaleManager!: ScaleManager;
private game!: Game;
private actualZoom: number = 1;
+ private _saveZoom: number = 1;
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
@@ -30,7 +33,7 @@ class WaScaleManager {
const { game: gameSize, real: realSize } = this.hdpiManager.getOptimalGameSize({width: width * devicePixelRatio, height: height * devicePixelRatio});
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
- this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);
+ this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio)
this.scaleManager.resize(gameSize.width, gameSize.height);
// Override bug in canvas resizing in Phaser. Let's resize the canvas ourselves
@@ -38,6 +41,19 @@ class WaScaleManager {
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
+ // Resize the game element at the same size at the canvas
+ const gameStyle = HtmlUtils.getElementByIdOrFail('game').style;
+ gameStyle.width = style.width;
+ gameStyle.height = style.height;
+
+ // Note: onResize will be called twice (once here and once in Game.ts), but we have no better way.
+ for (const scene of this.game.scene.getScenes(true)) {
+ if (scene instanceof ResizableScene) {
+ // We are delaying the call to the "render" event because otherwise, the "camera" coordinates are not correctly updated.
+ scene.events.once(Phaser.Scenes.Events.RENDER, () => scene.onResize());
+ }
+ }
+
this.game.markDirty();
}
@@ -50,6 +66,15 @@ class WaScaleManager {
this.applyNewSize();
}
+ public saveZoom(): void {
+ this._saveZoom = this.hdpiManager.zoomModifier;
+ }
+
+ public restoreZoom(): void{
+ this.hdpiManager.zoomModifier = this._saveZoom;
+ this.applyNewSize();
+ }
+
/**
* This is used to scale back the ui components to counter-act the zoom.
*/
diff --git a/front/src/Stores/CustomCharacterStore.ts b/front/src/Stores/CustomCharacterStore.ts
new file mode 100644
index 00000000..4bef7768
--- /dev/null
+++ b/front/src/Stores/CustomCharacterStore.ts
@@ -0,0 +1,3 @@
+import { derived, writable, Writable } from "svelte/store";
+
+export const customCharacterSceneVisibleStore = writable(false);
\ No newline at end of file
diff --git a/front/src/Stores/ErrorStore.ts b/front/src/Stores/ErrorStore.ts
new file mode 100644
index 00000000..2f1e3e40
--- /dev/null
+++ b/front/src/Stores/ErrorStore.ts
@@ -0,0 +1,33 @@
+import {writable} from "svelte/store";
+
+/**
+ * A store that contains a list of error messages to be displayed.
+ */
+function createErrorStore() {
+ const { subscribe, set, update } = writable([]);
+
+ return {
+ subscribe,
+ addErrorMessage: (e: string|Error): void => {
+ update((messages: string[]) => {
+ let message: string;
+ if (e instanceof Error) {
+ message = e.message;
+ } else {
+ message = e;
+ }
+
+ if (!messages.includes(message)) {
+ messages.push(message);
+ }
+
+ return messages;
+ });
+ },
+ clearMessages: (): void => {
+ set([]);
+ }
+ };
+}
+
+export const errorStore = createErrorStore();
diff --git a/front/src/Stores/Errors/BrowserTooOldError.ts b/front/src/Stores/Errors/BrowserTooOldError.ts
new file mode 100644
index 00000000..bf934443
--- /dev/null
+++ b/front/src/Stores/Errors/BrowserTooOldError.ts
@@ -0,0 +1,8 @@
+export class BrowserTooOldError extends Error {
+ static NAME = 'BrowserTooOldError';
+
+ constructor() {
+ super('Unable to access your camera or microphone. Your browser is too old. Please consider upgrading your browser or try using a recent version of Chrome.');
+ this.name = BrowserTooOldError.NAME;
+ }
+}
diff --git a/front/src/Stores/Errors/WebviewOnOldIOS.ts b/front/src/Stores/Errors/WebviewOnOldIOS.ts
new file mode 100644
index 00000000..06c03f0e
--- /dev/null
+++ b/front/src/Stores/Errors/WebviewOnOldIOS.ts
@@ -0,0 +1,8 @@
+export class WebviewOnOldIOS extends Error {
+ static NAME = 'WebviewOnOldIOS';
+
+ constructor() {
+ super('Your iOS version cannot use video/audio in the browser unless you are using Safari. Please switch to Safari or upgrade iOS to 14.3 or above.');
+ this.name = WebviewOnOldIOS.NAME;
+ }
+}
diff --git a/front/src/Stores/GameStore.ts b/front/src/Stores/GameStore.ts
index ee975f23..8899aa12 100644
--- a/front/src/Stores/GameStore.ts
+++ b/front/src/Stores/GameStore.ts
@@ -1,3 +1,5 @@
-import { derived, writable, Writable } from "svelte/store";
+import { writable } from "svelte/store";
export const userMovingStore = writable(false);
+
+export const requestVisitCardsStore = writable(null);
diff --git a/front/src/Stores/HelpCameraSettingsStore.ts b/front/src/Stores/HelpCameraSettingsStore.ts
new file mode 100644
index 00000000..88373dab
--- /dev/null
+++ b/front/src/Stores/HelpCameraSettingsStore.ts
@@ -0,0 +1,3 @@
+import { writable } from "svelte/store";
+
+export const helpCameraSettingsVisibleStore = writable(false);
diff --git a/front/src/Stores/LoginSceneStore.ts b/front/src/Stores/LoginSceneStore.ts
new file mode 100644
index 00000000..6e2ea18b
--- /dev/null
+++ b/front/src/Stores/LoginSceneStore.ts
@@ -0,0 +1,3 @@
+import { writable } from "svelte/store";
+
+export const loginSceneVisibleStore = writable(false);
diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts
index 9e53aa3b..d622511e 100644
--- a/front/src/Stores/MediaStore.ts
+++ b/front/src/Stores/MediaStore.ts
@@ -4,6 +4,10 @@ import {localUserStore} from "../Connexion/LocalUserStore";
import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
import {userMovingStore} from "./GameStore";
import {HtmlUtils} from "../WebRtc/HtmlUtils";
+import {BrowserTooOldError} from "./Errors/BrowserTooOldError";
+import {errorStore} from "./ErrorStore";
+import {isIOS} from "../WebRtc/DeviceUtils";
+import {WebviewOnOldIOS} from "./Errors/WebviewOnOldIOS";
/**
* A store that contains the camera state requested by the user (on or off).
@@ -209,10 +213,14 @@ function createVideoConstraintStore() {
return {
subscribe,
- setDeviceId: (deviceId: string) => update((constraints) => {
- constraints.deviceId = {
- exact: deviceId
- };
+ setDeviceId: (deviceId: string|undefined) => update((constraints) => {
+ if (deviceId !== undefined) {
+ constraints.deviceId = {
+ exact: deviceId
+ };
+ } else {
+ delete constraints.deviceId;
+ }
return constraints;
}),
@@ -241,15 +249,19 @@ function createAudioConstraintStore() {
return {
subscribe,
- setDeviceId: (deviceId: string) => update((constraints) => {
+ setDeviceId: (deviceId: string|undefined) => update((constraints) => {
selectedDeviceId = deviceId;
if (typeof(constraints) === 'boolean') {
constraints = {}
}
- constraints.deviceId = {
- exact: selectedDeviceId
- };
+ if (deviceId !== undefined) {
+ constraints.deviceId = {
+ exact: selectedDeviceId
+ };
+ } else {
+ delete constraints.deviceId;
+ }
return constraints;
})
@@ -410,13 +422,21 @@ export const localStreamStore = derived, LocalS
error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'),
constraints
});
- } else {
- //throw new Error('Unable to access your camera or microphone. Your browser is too old.');
+ return;
+ } else if (isIOS()) {
set({
type: 'error',
- error: new Error('Unable to access your camera or microphone. Your browser is too old.'),
+ error: new WebviewOnOldIOS(),
constraints
});
+ return;
+ } else {
+ set({
+ type: 'error',
+ error: new BrowserTooOldError(),
+ constraints
+ });
+ return;
}
}
@@ -508,3 +528,87 @@ export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStr
return $localStreamStore.constraints;
});
+/**
+ * Device list
+ */
+export const deviceListStore = readable([], function start(set) {
+ let deviceListCanBeQueried = false;
+
+ const queryDeviceList = () => {
+ // Note: so far, we are ignoring any failures.
+ navigator.mediaDevices.enumerateDevices().then((mediaDeviceInfos) => {
+ set(mediaDeviceInfos);
+ }).catch((e) => {
+ console.error(e);
+ throw e;
+ });
+ };
+
+ const unsubscribe = localStreamStore.subscribe((streamResult) => {
+ if (streamResult.type === "success" && streamResult.stream !== null) {
+ if (deviceListCanBeQueried === false) {
+ queryDeviceList();
+ deviceListCanBeQueried = true;
+ }
+ }
+ });
+
+ if (navigator.mediaDevices) {
+ navigator.mediaDevices.addEventListener('devicechange', queryDeviceList);
+ }
+
+ return function stop() {
+ unsubscribe();
+ if (navigator.mediaDevices) {
+ navigator.mediaDevices.removeEventListener('devicechange', queryDeviceList);
+ }
+ };
+});
+
+export const cameraListStore = derived(deviceListStore, ($deviceListStore) => {
+ return $deviceListStore.filter(device => device.kind === 'videoinput');
+});
+
+export const microphoneListStore = derived(deviceListStore, ($deviceListStore) => {
+ return $deviceListStore.filter(device => device.kind === 'audioinput');
+});
+
+// TODO: detect the new webcam and automatically switch on it.
+cameraListStore.subscribe((devices) => {
+ // If the selected camera is unplugged, let's remove the constraint on deviceId
+ const constraints = get(videoConstraintStore);
+ if (!constraints.deviceId) {
+ return;
+ }
+
+ // If we cannot find the device ID, let's remove it.
+ // @ts-ignore
+ if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) {
+ videoConstraintStore.setDeviceId(undefined);
+ }
+});
+
+microphoneListStore.subscribe((devices) => {
+ // If the selected camera is unplugged, let's remove the constraint on deviceId
+ const constraints = get(audioConstraintStore);
+ if (typeof constraints === 'boolean') {
+ return;
+ }
+ if (!constraints.deviceId) {
+ return;
+ }
+
+ // If we cannot find the device ID, let's remove it.
+ // @ts-ignore
+ if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) {
+ audioConstraintStore.setDeviceId(undefined);
+ }
+});
+
+localStreamStore.subscribe(streamResult => {
+ if (streamResult.type === 'error') {
+ if (streamResult.error.name === BrowserTooOldError.NAME || streamResult.error.name === WebviewOnOldIOS.NAME) {
+ errorStore.addErrorMessage(streamResult.error);
+ }
+ }
+});
diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts
index a55f5ab2..ec5aa46f 100644
--- a/front/src/Stores/ScreenSharingStore.ts
+++ b/front/src/Stores/ScreenSharingStore.ts
@@ -126,7 +126,7 @@ export const screenSharingLocalStreamStore = derived;
if (navigator.getDisplayMedia) {
currentStreamPromise = navigator.getDisplayMedia({constraints});
- } else if (navigator.mediaDevices.getDisplayMedia) {
+ } else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints});
} else {
stopScreenSharing();
@@ -169,6 +169,7 @@ export const screenSharingLocalStreamStore = derived {
- if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) {
+ if (!navigator.getDisplayMedia && (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia)) {
set(false);
return;
}
diff --git a/front/src/Stores/SelectCharacterStore.ts b/front/src/Stores/SelectCharacterStore.ts
new file mode 100644
index 00000000..094eaef3
--- /dev/null
+++ b/front/src/Stores/SelectCharacterStore.ts
@@ -0,0 +1,3 @@
+import { derived, writable, Writable } from "svelte/store";
+
+export const selectCharacterSceneVisibleStore = writable(false);
\ No newline at end of file
diff --git a/front/src/Stores/SelectCompanionStore.ts b/front/src/Stores/SelectCompanionStore.ts
new file mode 100644
index 00000000..e66f5de3
--- /dev/null
+++ b/front/src/Stores/SelectCompanionStore.ts
@@ -0,0 +1,3 @@
+import { derived, writable, Writable } from "svelte/store";
+
+export const selectCompanionSceneVisibleStore = writable(false);
diff --git a/front/src/Stores/SoundPlayingStore.ts b/front/src/Stores/SoundPlayingStore.ts
new file mode 100644
index 00000000..cf1d681c
--- /dev/null
+++ b/front/src/Stores/SoundPlayingStore.ts
@@ -0,0 +1,22 @@
+import { writable } from "svelte/store";
+
+/**
+ * A store that contains the URL of the sound currently playing
+ */
+function createSoundPlayingStore() {
+ const { subscribe, set, update } = writable(null);
+
+ return {
+ subscribe,
+ playSound: (url: string) => {
+ set(url);
+ },
+ soundEnded: () => {
+ set(null);
+ }
+
+
+ };
+}
+
+export const soundPlayingStore = createSoundPlayingStore();
diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts
index f00f6ecb..1fb28487 100644
--- a/front/src/WebRtc/CoWebsiteManager.ts
+++ b/front/src/WebRtc/CoWebsiteManager.ts
@@ -1,6 +1,7 @@
import {HtmlUtils} from "./HtmlUtils";
import {Subject} from "rxjs";
import {iframeListener} from "../Api/IframeListener";
+import {touchScreenManager} from "../Touch/TouchScreenManager";
enum iframeStates {
closed = 1,
@@ -11,12 +12,17 @@ enum iframeStates {
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
-const cowebsiteCloseButtonId = 'cowebsite-close';
+export const cowebsiteCloseButtonId = 'cowebsite-close';
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
const animationTime = 500; //time used by the css transitions, in ms.
+interface TouchMoveCoordinates {
+ x: number;
+ y: number;
+}
+
class CoWebsiteManager {
private opened: iframeStates = iframeStates.closed;
@@ -32,6 +38,7 @@ class CoWebsiteManager {
private resizing: boolean = false;
private cowebsiteMainDom: HTMLDivElement;
private cowebsiteAsideDom: HTMLDivElement;
+ private previousTouchMoveCoordinates: TouchMoveCoordinates|null = null; //only use on touchscreens to track touch movement
get width(): number {
return this.cowebsiteDiv.clientWidth;
@@ -62,32 +69,61 @@ class CoWebsiteManager {
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail(cowebsiteMainDomId);
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail(cowebsiteAsideDomId);
- this.initResizeListeners();
+ if (touchScreenManager.supportTouchScreen) {
+ this.initResizeListeners(true);
+ }
+ this.initResizeListeners(false);
- HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => {
+ const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
+ buttonCloseFrame.addEventListener('click', () => {
+ buttonCloseFrame.blur();
this.closeCoWebsite();
});
- HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => {
+
+ const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId);
+ buttonFullScreenFrame.addEventListener('click', () => {
+ buttonFullScreenFrame.blur();
this.fullscreen();
});
}
- private initResizeListeners() {
- const movecallback = (event:MouseEvent) => {
- this.verticalMode ? this.height += event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
+ private initResizeListeners(touchMode:boolean) {
+ const movecallback = (event:MouseEvent|TouchEvent) => {
+ let x, y;
+ if (event.type === 'mousemove') {
+ x = (event as MouseEvent).movementX / this.getDevicePixelRatio();
+ y = (event as MouseEvent).movementY / this.getDevicePixelRatio();
+ } else {
+ const touchEvent = (event as TouchEvent).touches[0];
+ const last = {x: touchEvent.pageX, y: touchEvent.pageY};
+ const previous = this.previousTouchMoveCoordinates as TouchMoveCoordinates;
+ this.previousTouchMoveCoordinates = last;
+ x = last.x - previous.x;
+ y = last.y - previous.y;
+ }
+
+
+ this.verticalMode ? this.height += y : this.width -= x;
this.fire();
}
- this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
+ this.cowebsiteAsideDom.addEventListener( touchMode ? 'touchstart' : 'mousedown', (event) => {
this.resizing = true;
this.getIframeDom().style.display = 'none';
+ if (touchMode) {
+ const touchEvent = (event as TouchEvent).touches[0];
+ this.previousTouchMoveCoordinates = {x: touchEvent.pageX, y: touchEvent.pageY};
+ }
- document.addEventListener('mousemove', movecallback);
+ document.addEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback);
});
- document.addEventListener('mouseup', (event) => {
+ document.addEventListener(touchMode ? 'touchend' : 'mouseup', (event) => {
if (!this.resizing) return;
- document.removeEventListener('mousemove', movecallback);
+ if (touchMode) {
+ this.previousTouchMoveCoordinates = null;
+ }
+ document.removeEventListener(touchMode ? 'touchmove' : 'mousemove', movecallback);
this.getIframeDom().style.display = 'block';
this.resizing = false;
});
@@ -152,7 +188,10 @@ class CoWebsiteManager {
setTimeout(() => {
this.fire();
}, animationTime)
- }).catch(() => this.closeCoWebsite());
+ }).catch((err) => {
+ console.error('Error loadCoWebsite => ', err);
+ this.closeCoWebsite()
+ });
}
/**
@@ -166,7 +205,10 @@ class CoWebsiteManager {
setTimeout(() => {
this.fire();
}, animationTime);
- }).catch(() => this.closeCoWebsite());
+ }).catch((err) => {
+ console.error('Error insertCoWebsite => ', err);
+ this.closeCoWebsite();
+ });
}
public closeCoWebsite(): Promise {
diff --git a/front/src/WebRtc/DeviceUtils.ts b/front/src/WebRtc/DeviceUtils.ts
new file mode 100644
index 00000000..91a3dc31
--- /dev/null
+++ b/front/src/WebRtc/DeviceUtils.ts
@@ -0,0 +1,12 @@
+export function isIOS(): boolean {
+ return [
+ 'iPad Simulator',
+ 'iPhone Simulator',
+ 'iPod Simulator',
+ 'iPad',
+ 'iPhone',
+ 'iPod'
+ ].includes(navigator.platform)
+ // iPad on iOS 13 detection
+ || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
+}
diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts
index 9bbfac5a..504ee91b 100644
--- a/front/src/WebRtc/DiscussionManager.ts
+++ b/front/src/WebRtc/DiscussionManager.ts
@@ -171,6 +171,8 @@ export class DiscussionManager {
const date = new Date();
if(isMe){
name = 'Me';
+ } else {
+ name = HtmlUtils.escapeHtml(name);
}
pMessage.innerHTML = `${name}
diff --git a/front/src/WebRtc/HtmlUtils.ts b/front/src/WebRtc/HtmlUtils.ts
index 86f38216..942e553f 100644
--- a/front/src/WebRtc/HtmlUtils.ts
+++ b/front/src/WebRtc/HtmlUtils.ts
@@ -35,7 +35,12 @@ export class HtmlUtils {
const urlRegex = /(https?:\/\/[^\s]+)/g;
text = HtmlUtils.escapeHtml(text);
return text.replace(urlRegex, (url: string) => {
- return '' + url + '';
+ const link = document.createElement('a');
+ link.href = url;
+ link.target = "_blank";
+ const text = document.createTextNode(url);
+ link.appendChild(text);
+ return link.outerHTML;
});
}
diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts
index fad33de3..efc9660a 100644
--- a/front/src/WebRtc/MediaManager.ts
+++ b/front/src/WebRtc/MediaManager.ts
@@ -8,32 +8,11 @@ import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
import {
gameOverlayVisibilityStore, localStreamStore,
- mediaStreamConstraintsStore,
- requestedCameraState,
- requestedMicrophoneState
} from "../Stores/MediaStore";
import {
- requestedScreenSharingState,
- screenSharingAvailableStore,
screenSharingLocalStreamStore
} from "../Stores/ScreenSharingStore";
-
-declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
-
-const videoConstraint: boolean|MediaTrackConstraints = {
- width: { min: 640, ideal: 1280, max: 1920 },
- height: { min: 400, ideal: 720 },
- frameRate: { ideal: localUserStore.getVideoQualityValue() },
- facingMode: "user",
- resizeMode: 'crop-and-scale',
- aspectRatio: 1.777777778
-};
-const audioConstraint: boolean|MediaTrackConstraints = {
- //TODO: make these values configurable in the game settings menu and store them in localstorage
- autoGainControl: false,
- echoCancellation: true,
- noiseSuppression: true
-};
+import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
export type StartScreenSharingCallback = (media: MediaStream) => void;
@@ -42,30 +21,15 @@ export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
export type HelpCameraSettingsCallBack = () => void;
+import {cowebsiteCloseButtonId} from "./CoWebsiteManager";
+
export class MediaManager {
- localStream: MediaStream|null = null;
- localScreenCapture: MediaStream|null = null;
private remoteVideo: Map = new Map();
- myCamVideo: HTMLVideoElement;
- cinemaClose: HTMLImageElement;
- cinema: HTMLImageElement;
- monitorClose: HTMLImageElement;
- monitor: HTMLImageElement;
- microphoneClose: HTMLImageElement;
- microphone: HTMLImageElement;
- webrtcInAudio: HTMLAudioElement;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
- private webrtcOutAudio: HTMLAudioElement;
- updatedLocalStreamCallBacks : Set = new Set();
startScreenSharingCallBacks : Set = new Set();
stopScreenSharingCallBacks : Set = new Set();
showReportModalCallBacks : Set = new Set();
- helpCameraSettingsCallBacks : Set = new Set();
-
- private microphoneBtn: HTMLDivElement;
- private cinemaBtn: HTMLDivElement;
- private monitorBtn: HTMLDivElement;
private focused : boolean = true;
@@ -80,54 +44,6 @@ export class MediaManager {
constructor() {
- this.myCamVideo = HtmlUtils.getElementByIdOrFail('myCamVideo');
- this.webrtcInAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-in');
- this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail('audio-webrtc-out');
- this.webrtcInAudio.volume = 0.2;
- this.webrtcOutAudio.volume = 0.2;
-
- this.microphoneBtn = HtmlUtils.getElementByIdOrFail('btn-micro');
- this.microphoneClose = HtmlUtils.getElementByIdOrFail('microphone-close');
- this.microphoneClose.style.display = "none";
- this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
- e.preventDefault();
- requestedMicrophoneState.enableMicrophone();
- });
- this.microphone = HtmlUtils.getElementByIdOrFail('microphone');
- this.microphone.addEventListener('click', (e: MouseEvent) => {
- e.preventDefault();
- requestedMicrophoneState.disableMicrophone();
- });
-
- this.cinemaBtn = HtmlUtils.getElementByIdOrFail('btn-video');
- this.cinemaClose = HtmlUtils.getElementByIdOrFail('cinema-close');
- this.cinemaClose.style.display = "none";
- this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
- e.preventDefault();
- requestedCameraState.enableWebcam();
- });
- this.cinema = HtmlUtils.getElementByIdOrFail('cinema');
- this.cinema.addEventListener('click', (e: MouseEvent) => {
- e.preventDefault();
- requestedCameraState.disableWebcam();
- });
-
- this.monitorBtn = HtmlUtils.getElementByIdOrFail('btn-monitor');
- this.monitorClose = HtmlUtils.getElementByIdOrFail('monitor-close');
- this.monitorClose.style.display = "block";
- this.monitorClose.addEventListener('click', (e: MouseEvent) => {
- e.preventDefault();
- //this.enableScreenSharing();
- requestedScreenSharingState.enableScreenSharing();
- });
- this.monitor = HtmlUtils.getElementByIdOrFail('monitor');
- this.monitor.style.display = "none";
- this.monitor.addEventListener('click', (e: MouseEvent) => {
- e.preventDefault();
- //this.disableScreenSharing();
- requestedScreenSharingState.disableScreenSharing();
- });
-
this.pingCameraStatus();
//FIX ME SOUNDMETER: check stability of sound meter calculation
@@ -142,87 +58,43 @@ export class MediaManager {
localStreamStore.subscribe((result) => {
if (result.type === 'error') {
console.error(result.error);
- layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
- this.showHelpCameraSettingsCallBack();
+ layoutManager.addInformation('warning', 'Camera access denied. Click here and check your browser permissions.', () => {
+ helpCameraSettingsVisibleStore.set(true);
}, this.userInputManager);
return;
}
-
- if (result.constraints.video !== false) {
- HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide');
- } else {
- HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide');
- }/*
- if (result.constraints.audio !== false) {
- this.enableMicrophoneStyle();
- } else {
- this.disableMicrophoneStyle();
- }*/
-
- this.localStream = result.stream;
- this.myCamVideo.srcObject = this.localStream;
-
- // TODO: migrate all listeners to the store directly.
- this.triggerUpdatedLocalStreamCallbacks(result.stream);
});
- requestedCameraState.subscribe((enabled) => {
- if (enabled) {
- this.enableCameraStyle();
- } else {
- this.disableCameraStyle();
- }
- });
- requestedMicrophoneState.subscribe((enabled) => {
- if (enabled) {
- this.enableMicrophoneStyle();
- } else {
- this.disableMicrophoneStyle();
- }
- });
- //let screenSharingStream : MediaStream|null;
+ let isScreenSharing = false;
screenSharingLocalStreamStore.subscribe((result) => {
if (result.type === 'error') {
console.error(result.error);
- layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => {
- this.showHelpCameraSettingsCallBack();
+ layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check your browser permissions.', () => {
+ helpCameraSettingsVisibleStore.set(true);
}, this.userInputManager);
return;
}
if (result.stream !== null) {
- this.enableScreenSharingStyle();
- mediaManager.localScreenCapture = result.stream;
-
- // TODO: migrate this out of MediaManager
- this.triggerStartedScreenSharingCallbacks(result.stream);
-
- //screenSharingStream = result.stream;
-
+ isScreenSharing = true;
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = result.stream;
} else {
- this.disableScreenSharingStyle();
- this.removeActiveScreenSharingVideo('me');
-
- // FIXME: we need the old stream that is being stopped!
- if (this.localScreenCapture) {
- this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture);
- this.localScreenCapture = null;
+ if (isScreenSharing) {
+ isScreenSharing = false;
+ this.removeActiveScreenSharingVideo('me');
}
-
- //screenSharingStream = null;
}
});
- screenSharingAvailableStore.subscribe((available) => {
+ /*screenSharingAvailableStore.subscribe((available) => {
if (available) {
document.querySelector('.btn-monitor')?.classList.remove('hide');
} else {
document.querySelector('.btn-monitor')?.classList.add('hide');
}
- });
+ });*/
}
public updateScene(){
@@ -230,49 +102,18 @@ export class MediaManager {
//this.updateSoudMeter();
}
- public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
- this.updatedLocalStreamCallBacks.add(callback);
- }
-
- public onStartScreenSharing(callback: StartScreenSharingCallback): void {
- this.startScreenSharingCallBacks.add(callback);
- }
-
- public onStopScreenSharing(callback: StopScreenSharingCallback): void {
- this.stopScreenSharingCallBacks.add(callback);
- }
-
- removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void {
- this.updatedLocalStreamCallBacks.delete(callback);
- }
-
- private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void {
- for (const callback of this.updatedLocalStreamCallBacks) {
- callback(stream);
- }
- }
-
- private triggerStartedScreenSharingCallbacks(stream: MediaStream): void {
- for (const callback of this.startScreenSharingCallBacks) {
- callback(stream);
- }
- }
-
- private triggerStoppedScreenSharingCallbacks(stream: MediaStream): void {
- for (const callback of this.stopScreenSharingCallBacks) {
- callback(stream);
- }
- }
-
public showGameOverlay(): void {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active');
- const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
+ const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
const functionTrigger = () => {
this.triggerCloseJitsiFrameButton();
}
- buttonCloseFrame.removeEventListener('click', functionTrigger);
+ buttonCloseFrame.removeEventListener('click', () => {
+ buttonCloseFrame.blur();
+ functionTrigger();
+ });
gameOverlayVisibilityStore.showGameOverlay();
}
@@ -281,53 +122,19 @@ export class MediaManager {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.remove('active');
- const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
+ const buttonCloseFrame = HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId);
const functionTrigger = () => {
this.triggerCloseJitsiFrameButton();
}
- buttonCloseFrame.addEventListener('click', functionTrigger);
+ buttonCloseFrame.addEventListener('click', () => {
+ buttonCloseFrame.blur();
+ functionTrigger();
+ });
gameOverlayVisibilityStore.hideGameOverlay();
}
- private enableCameraStyle(){
- this.cinemaClose.style.display = "none";
- this.cinemaBtn.classList.remove("disabled");
- this.cinema.style.display = "block";
- }
-
- private disableCameraStyle(){
- this.cinemaClose.style.display = "block";
- this.cinema.style.display = "none";
- this.cinemaBtn.classList.add("disabled");
- }
-
- private enableMicrophoneStyle(){
- this.microphoneClose.style.display = "none";
- this.microphone.style.display = "block";
- this.microphoneBtn.classList.remove("disabled");
- }
-
- private disableMicrophoneStyle(){
- this.microphoneClose.style.display = "block";
- this.microphone.style.display = "none";
- this.microphoneBtn.classList.add("disabled");
- }
-
- private enableScreenSharingStyle(){
- this.monitorClose.style.display = "none";
- this.monitor.style.display = "block";
- this.monitorBtn.classList.add("enabled");
- }
-
- private disableScreenSharingStyle(){
- this.monitorClose.style.display = "block";
- this.monitor.style.display = "none";
- this.monitorBtn.classList.remove("enabled");
- }
-
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
- this.webrtcInAudio.play();
const userId = ''+user.userId
userName = userName.toUpperCase();
@@ -343,7 +150,7 @@ export class MediaManager {
Report/Block
-
+
@@ -380,7 +187,7 @@ export class MediaManager {
userId = this.getScreenSharingId(userId);
const html = `
-
+
`;
@@ -475,10 +282,6 @@ export class MediaManager {
this.removeActiveVideo(this.getScreenSharingId(userId))
}
- playWebrtcOutSound(): void {
- this.webrtcOutAudio.play();
- }
-
isConnecting(userId: string): void {
const connectingSpinnerDiv = this.getSpinner(userId);
if (connectingSpinnerDiv === null) {
@@ -593,16 +396,6 @@ export class MediaManager {
this.showReportModalCallBacks.add(callback);
}
- public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){
- this.helpCameraSettingsCallBacks.add(callback);
- }
-
- private showHelpCameraSettingsCallBack(){
- for(const callBack of this.helpCameraSettingsCallBacks){
- callBack();
- }
- }
-
//FIX ME SOUNDMETER: check stalability of sound meter calculation
/*updateSoudMeter(){
try{
@@ -648,12 +441,32 @@ export class MediaManager {
public getNotification(){
//Get notification
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
- Notification.requestPermission().catch((err) => {
- console.error(`Notification permission error`, err);
- });
+ if (this.checkNotificationPromise()) {
+ Notification.requestPermission().catch((err) => {
+ console.error(`Notification permission error`, err);
+ });
+ } else {
+ Notification.requestPermission();
+ }
}
}
+ /**
+ * Return true if the browser supports the modern version of the Notification API (which is Promise based) or false
+ * if we are on Safari...
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
+ */
+ private checkNotificationPromise(): boolean {
+ try {
+ Notification.requestPermission().then();
+ } catch(e) {
+ return false;
+ }
+
+ return true;
+ }
+
public createNotification(userName: string){
if(this.focused){
return;
diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts
index f1786ef3..d797f59b 100644
--- a/front/src/WebRtc/ScreenSharingPeer.ts
+++ b/front/src/WebRtc/ScreenSharingPeer.ts
@@ -19,7 +19,7 @@ export class ScreenSharingPeer extends Peer {
public _connected: boolean = false;
private userId: number;
- constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
+ constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, stream: MediaStream | null) {
super({
initiator: initiator ? initiator : false,
//reconnectTimer: 10000,
@@ -60,6 +60,7 @@ export class ScreenSharingPeer extends Peer {
const message = JSON.parse(chunk.toString('utf8'));
if (message.streamEnded !== true) {
console.error('Unexpected message on screen sharing peer connection');
+ return;
}
mediaManager.removeActiveScreenSharingVideo("" + this.userId);
});
@@ -81,7 +82,9 @@ export class ScreenSharingPeer extends Peer {
this._onFinish();
});
- this.pushScreenSharingToRemoteUser();
+ if (stream) {
+ this.addStream(stream);
+ }
}
private sendWebrtcScreenSharingSignal(data: unknown) {
@@ -141,16 +144,6 @@ export class ScreenSharingPeer extends Peer {
}
}
- private pushScreenSharingToRemoteUser() {
- const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture;
- if(!localScreenCapture){
- return;
- }
-
- this.addStream(localScreenCapture);
- return;
- }
-
public stopPushingScreenSharingToRemoteUser(stream: MediaStream) {
this.removeStream(stream);
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true})));
diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts
index 4633374d..2a502bab 100644
--- a/front/src/WebRtc/SimplePeer.ts
+++ b/front/src/WebRtc/SimplePeer.ts
@@ -15,7 +15,10 @@ import {connectionManager} from "../Connexion/ConnectionManager";
import {GameConnexionTypes} from "../Url/UrlManager";
import {blackListManager} from "./BlackListManager";
import {get} from "svelte/store";
-import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore";
+import {localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore} from "../Stores/MediaStore";
+import {screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore";
+import {DivImportance, layoutManager} from "./LayoutManager";
+import {HtmlUtils} from "./HtmlUtils";
export interface UserSimplePeerInterface{
userId: number;
@@ -39,9 +42,9 @@ export class SimplePeer {
private PeerScreenSharingConnectionArray: Map
= new Map();
private PeerConnectionArray: Map = new Map();
- private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback;
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
+ private readonly unsubscribers: (() => void)[] = [];
private readonly peerConnectionListeners: Array = new Array();
private readonly userId: number;
private lastWebrtcUserName: string|undefined;
@@ -49,13 +52,32 @@ export class SimplePeer {
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
- this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this);
this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this);
this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this);
- mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
- mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
- mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
+ this.unsubscribers.push(localStreamStore.subscribe((streamResult) => {
+ this.sendLocalVideoStream(streamResult);
+ }));
+
+ let localScreenCapture: MediaStream|null = null;
+
+ this.unsubscribers.push(screenSharingLocalStreamStore.subscribe((streamResult) => {
+ if (streamResult.type === 'error') {
+ // Let's ignore screen sharing errors, we will deal with those in a different way.
+ return;
+ }
+
+ if (streamResult.stream !== null) {
+ localScreenCapture = streamResult.stream;
+ this.sendLocalScreenSharingStream(localScreenCapture);
+ } else {
+ if (localScreenCapture) {
+ this.stopLocalScreenSharingStream(localScreenCapture);
+ localScreenCapture = null;
+ }
+ }
+ }));
+
this.userId = Connection.getUserId();
this.initialise();
}
@@ -106,13 +128,19 @@ export class SimplePeer {
if(!user.initiator){
return;
}
- this.createPeerConnection(user);
+ const streamResult = get(localStreamStore);
+ let stream : MediaStream | null = null;
+ if (streamResult.type === 'success' && streamResult.stream) {
+ stream = streamResult.stream;
+ }
+
+ this.createPeerConnection(user, stream);
}
/**
* create peer connection to bind users
*/
- private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null {
+ private createPeerConnection(user : UserSimplePeerInterface, localStream: MediaStream | null) : VideoPeer | null {
const peerConnection = this.PeerConnectionArray.get(user.userId)
if (peerConnection) {
if (peerConnection.destroyed) {
@@ -122,11 +150,11 @@ export class SimplePeer {
if (!peerConnexionDeleted) {
throw 'Error to delete peer connection';
}
- this.createPeerConnection(user);
+ //return this.createPeerConnection(user, localStream);
} else {
peerConnection.toClose = false;
+ return null;
}
- return null;
}
let name = user.name;
@@ -144,7 +172,7 @@ export class SimplePeer {
this.lastWebrtcUserName = user.webRtcUser;
this.lastWebrtcPassword = user.webRtcPassword;
- const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
+ const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection, localStream);
//permit to send message
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
@@ -155,8 +183,9 @@ export class SimplePeer {
// When a connection is established to a video stream, and if a screen sharing is taking place,
// the user sharing screen should also initiate a connection to the remote user!
peer.on('connect', () => {
- if (mediaManager.localScreenCapture) {
- this.sendLocalScreenSharingStreamToUser(user.userId);
+ const streamResult = get(screenSharingLocalStreamStore);
+ if (streamResult.type === 'success' && streamResult.stream !== null) {
+ this.sendLocalScreenSharingStreamToUser(user.userId, streamResult.stream);
}
});
@@ -175,7 +204,7 @@ export class SimplePeer {
/**
* create peer connection to bind users
*/
- private createPeerScreenSharingConnection(user : UserSimplePeerInterface) : ScreenSharingPeer | null{
+ private createPeerScreenSharingConnection(user : UserSimplePeerInterface, stream: MediaStream | null) : ScreenSharingPeer | null{
const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId);
if(peerConnection){
if(peerConnection.destroyed){
@@ -185,7 +214,7 @@ export class SimplePeer {
if(!peerConnexionDeleted){
throw 'Error to delete peer connection';
}
- this.createPeerConnection(user);
+ this.createPeerConnection(user, stream);
}else {
peerConnection.toClose = false;
}
@@ -204,7 +233,7 @@ export class SimplePeer {
user.webRtcPassword = this.lastWebrtcPassword;
}
- const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection);
+ const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection, stream);
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
for (const peerConnectionListener of this.peerConnectionListeners) {
@@ -217,7 +246,6 @@ export class SimplePeer {
* This is triggered twice. Once by the server, and once by a remote client disconnecting
*/
private closeConnection(userId : number) {
- mediaManager.playWebrtcOutSound();
try {
const peer = this.PeerConnectionArray.get(userId);
if (peer === undefined) {
@@ -234,7 +262,7 @@ export class SimplePeer {
const userIndex = this.Users.findIndex(user => user.userId === userId);
if(userIndex < 0){
- throw 'Couln\'t delete user';
+ throw 'Couldn\'t delete user';
} else {
this.Users.splice(userIndex, 1);
}
@@ -294,7 +322,9 @@ export class SimplePeer {
* Unregisters any held event handler.
*/
public unregister() {
- mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback);
+ for (const unsubscriber of this.unsubscribers) {
+ unsubscriber();
+ }
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -302,7 +332,13 @@ export class SimplePeer {
try {
//if offer type, create peer connection
if(data.signal.type === "offer"){
- this.createPeerConnection(data);
+ const streamResult = get(localStreamStore);
+ let stream : MediaStream | null = null;
+ if (streamResult.type === 'success' && streamResult.stream) {
+ stream = streamResult.stream;
+ }
+
+ this.createPeerConnection(data, stream);
}
const peer = this.PeerConnectionArray.get(data.userId);
if (peer !== undefined) {
@@ -318,18 +354,26 @@ export class SimplePeer {
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
if (blackListManager.isBlackListed(data.userId)) return;
console.log("receiveWebrtcScreenSharingSignal", data);
+ const streamResult = get(screenSharingLocalStreamStore);
+ let stream : MediaStream | null = null;
+ if (streamResult.type === 'success' && streamResult.stream !== null) {
+ stream = streamResult.stream;
+ }
+
try {
//if offer type, create peer connection
if(data.signal.type === "offer"){
- this.createPeerScreenSharingConnection(data);
+ this.createPeerScreenSharingConnection(data, stream);
}
const peer = this.PeerScreenSharingConnectionArray.get(data.userId);
if (peer !== undefined) {
peer.signal(data.signal);
} else {
console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal');
- console.info('tentative to create new peer connexion');
- this.sendLocalScreenSharingStreamToUser(data.userId);
+ console.info('Attempt to create new peer connexion');
+ if (stream) {
+ this.sendLocalScreenSharingStreamToUser(data.userId, stream);
+ }
}
} catch (e) {
console.error(`receiveWebrtcSignal => ${data.userId}`, e);
@@ -339,21 +383,19 @@ export class SimplePeer {
}
}
- private pushVideoToRemoteUser(userId : number) {
+ private pushVideoToRemoteUser(userId: number, streamResult: LocalStreamStoreValue) {
try {
const PeerConnection = this.PeerConnectionArray.get(userId);
if (!PeerConnection) {
throw new Error('While adding media, cannot find user with ID ' + userId);
}
- const result = get(localStreamStore);
+ PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...streamResult.constraints})));
- PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints})));
-
- if (result.type === 'error') {
+ if (streamResult.type === 'error') {
return;
}
- const localStream: MediaStream | null = result.stream;
+ const localStream: MediaStream | null = streamResult.stream;
if(!localStream){
return;
@@ -370,15 +412,11 @@ export class SimplePeer {
}
}
- private pushScreenSharingToRemoteUser(userId : number) {
+ private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) {
const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId);
if (!PeerConnection) {
throw new Error('While pushing screen sharing, cannot find user with ID ' + userId);
}
- const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture;
- if(!localScreenCapture){
- return;
- }
for (const track of localScreenCapture.getTracks()) {
PeerConnection.addTrack(track, localScreenCapture);
@@ -386,23 +424,18 @@ export class SimplePeer {
return;
}
- public sendLocalVideoStream(){
+ public sendLocalVideoStream(streamResult: LocalStreamStoreValue){
for (const user of this.Users) {
- this.pushVideoToRemoteUser(user.userId);
+ this.pushVideoToRemoteUser(user.userId, streamResult);
}
}
/**
* Triggered locally when clicking on the screen sharing button
*/
- public sendLocalScreenSharingStream() {
- if (!mediaManager.localScreenCapture) {
- console.error('Could not find localScreenCapture to share')
- return;
- }
-
+ public sendLocalScreenSharingStream(localScreenCapture: MediaStream) {
for (const user of this.Users) {
- this.sendLocalScreenSharingStreamToUser(user.userId);
+ this.sendLocalScreenSharingStreamToUser(user.userId, localScreenCapture);
}
}
@@ -415,11 +448,11 @@ export class SimplePeer {
}
}
- private sendLocalScreenSharingStreamToUser(userId: number): void {
+ private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void {
if (blackListManager.isBlackListed(userId)) return;
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
if (this.PeerScreenSharingConnectionArray.has(userId)) {
- this.pushScreenSharingToRemoteUser(userId);
+ this.pushScreenSharingToRemoteUser(userId, localScreenCapture);
return;
}
@@ -427,7 +460,7 @@ export class SimplePeer {
userId,
initiator: true
};
- const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser);
+ const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser, localScreenCapture);
if (!PeerConnectionScreenSharing) {
return;
}
diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts
index 32e8e97f..5ca8952c 100644
--- a/front/src/WebRtc/VideoPeer.ts
+++ b/front/src/WebRtc/VideoPeer.ts
@@ -27,7 +27,7 @@ export class VideoPeer extends Peer {
private onBlockSubscribe: Subscription;
private onUnBlockSubscribe: Subscription;
- constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
+ constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, localStream: MediaStream | null) {
super({
initiator: initiator ? initiator : false,
//reconnectTimer: 10000,
@@ -107,7 +107,7 @@ export class VideoPeer extends Peer {
this._onFinish();
});
- this.pushVideoToRemoteUser();
+ this.pushVideoToRemoteUser(localStream);
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
if (userId === this.userId) {
this.toggleRemoteStream(false);
@@ -190,9 +190,8 @@ export class VideoPeer extends Peer {
}
}
- private pushVideoToRemoteUser() {
+ private pushVideoToRemoteUser(localStream: MediaStream | null) {
try {
- const localStream: MediaStream | null = mediaManager.localStream;
this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)})));
if(!localStream){
diff --git a/front/src/ambient.d.ts b/front/src/ambient.d.ts
new file mode 100644
index 00000000..eee8d6c1
--- /dev/null
+++ b/front/src/ambient.d.ts
@@ -0,0 +1,38 @@
+/**
+ * These declarations tell TypeScript that we allow import of images, e.g.
+ * ```
+
+
+ ```
+ */
+declare module "*.gif" {
+ const value: string;
+ export = value;
+}
+
+declare module "*.jpg" {
+ const value: string;
+ export = value;
+}
+
+declare module "*.jpeg" {
+ const value: string;
+ export = value;
+}
+
+declare module "*.png" {
+ const value: string;
+ export = value;
+}
+
+declare module "*.svg" {
+ const value: string;
+ export = value;
+}
+
+declare module "*.webp" {
+ const value: string;
+ export = value;
+}
diff --git a/front/src/index.ts b/front/src/index.ts
index 2cdcaa19..90d4c612 100644
--- a/front/src/index.ts
+++ b/front/src/index.ts
@@ -9,11 +9,10 @@ import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {SelectCompanionScene} from "./Phaser/Login/SelectCompanionScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
-import {ResizableScene} from "./Phaser/Login/ResizableScene";
+import WebFontLoaderPlugin from 'phaser3-rex-plugins/plugins/webfontloader-plugin.js';
import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
-import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
import {localUserStore} from "./Connexion/LocalUserStore";
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
import {iframeListener} from "./Api/IframeListener";
@@ -96,7 +95,7 @@ const config: GameConfig = {
ErrorScene,
CustomizeScene,
MenuScene,
- HelpCameraSettingsScene],
+ ],
//resolution: window.devicePixelRatio / 2,
fps: fps,
dom: {
@@ -107,6 +106,13 @@ const config: GameConfig = {
roundPixels: true,
antialias: false
},
+ plugins: {
+ global: [{
+ key: 'rexWebFontLoader',
+ plugin: WebFontLoaderPlugin,
+ start: true
+ }]
+ },
physics: {
default: "arcade",
arcade: {
@@ -145,7 +151,9 @@ iframeListener.init();
const app = new App({
target: HtmlUtils.getElementByIdOrFail('svelte-overlay'),
- props: { },
+ props: {
+ game: game
+ },
})
export default app
diff --git a/front/src/rex-plugins.d.ts b/front/src/rex-plugins.d.ts
index d5457702..2e160315 100644
--- a/front/src/rex-plugins.d.ts
+++ b/front/src/rex-plugins.d.ts
@@ -7,6 +7,10 @@ declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' {
const content: any; // eslint-disable-line
export default content;
}
+declare module 'phaser3-rex-plugins/plugins/webfontloader-plugin.js' {
+ const content: any; // eslint-disable-line
+ export default content;
+}
declare module 'phaser3-rex-plugins/plugins/gestures.js' {
export const Pinch: any; // eslint-disable-line
}
diff --git a/front/style/cowebsite-mobile.scss b/front/style/cowebsite-mobile.scss
index 85b41511..48a6689e 100644
--- a/front/style/cowebsite-mobile.scss
+++ b/front/style/cowebsite-mobile.scss
@@ -28,6 +28,7 @@
aside {
height: 30px;
+ min-height: 30px;
cursor: ns-resize;
flex-direction: column;
diff --git a/front/style/cowebsite.scss b/front/style/cowebsite.scss
index 2ed1ef69..e508b58d 100644
--- a/front/style/cowebsite.scss
+++ b/front/style/cowebsite.scss
@@ -13,6 +13,8 @@
iframe {
width: 100%;
height: 100%;
+ max-width: 100vw;
+ max-height: 100vh;
}
}
diff --git a/front/style/fonts.scss b/front/style/fonts.scss
new file mode 100644
index 00000000..a49d3967
--- /dev/null
+++ b/front/style/fonts.scss
@@ -0,0 +1,9 @@
+@import "~@fontsource/press-start-2p/index.css";
+
+*{
+ font-family: PixelFont-7,monospace;
+}
+
+.nes-btn {
+ font-family: "Press Start 2P";
+}
diff --git a/front/style/index.scss b/front/style/index.scss
index 67e85c5b..7ed141cd 100644
--- a/front/style/index.scss
+++ b/front/style/index.scss
@@ -1,4 +1,5 @@
@import "cowebsite.scss";
@import "cowebsite-mobile.scss";
-@import "style.css";
-@import "mobile-style.scss";
\ No newline at end of file
+@import "style";
+@import "mobile-style.scss";
+@import "fonts.scss";
diff --git a/front/style/mobile-style.scss b/front/style/mobile-style.scss
index 21753ebd..1b37053a 100644
--- a/front/style/mobile-style.scss
+++ b/front/style/mobile-style.scss
@@ -1,9 +1,24 @@
+@media (hover: none) {
+ /**
+ * If we cannot hover over elements, let's display camera button in full.
+ */
+ .btn-cam-action {
+ div {
+ transform: translateY(0px);
+ }
+ }
+}
+
@media screen and (max-width: 700px),
screen and (max-height: 700px){
- video#myCamVideo {
+ video.myCamVideo {
width: 150px;
}
+ .div-myCamVideo.hide {
+ right: -160px;
+ }
+
.sidebar {
width: 20%;
min-width: 200px;
@@ -22,21 +37,6 @@
}
}
- .btn-cam-action {
- min-width: 150px;
-
- &:hover{
- transform: translateY(20px);
- }
- div {
- margin: 0 1%;
- &:hover {
- background-color: #666;
- }
- margin-bottom: 30px;
- }
- }
-
.main-section {
position: absolute;
width: 100%;
diff --git a/front/style/style.css b/front/style/style.scss
similarity index 94%
rename from front/style/style.css
rename to front/style/style.scss
index a3bbfa1d..beed1db5 100644
--- a/front/style/style.css
+++ b/front/style/style.scss
@@ -133,11 +133,12 @@ body .message-info.warning{
outline: none;
}
-.video-container#div-myCamVideo{
+.video-container.div-myCamVideo{
border: none;
+ background-color: transparent;
}
-#div-myCamVideo {
+.div-myCamVideo {
position: absolute;
right: 15px;
bottom: 30px;
@@ -146,11 +147,13 @@ body .message-info.warning{
transition: right 350ms;
}
-#div-myCamVideo.hide {
+.div-myCamVideo.hide {
right: -20vw;
}
-video#myCamVideo{
+video.myCamVideo{
+ background-color: #00000099;
+ max-height: 20vh;
width: 15vw;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
@@ -212,7 +215,9 @@ video#myCamVideo{
/*btn animation*/
.btn-cam-action div{
cursor: url('./images/cursor_pointer.png'), pointer;
- /*position: absolute;*/
+ display: flex;
+ align-items: center;
+ justify-content: center;
border: solid 0px black;
width: 44px;
height: 44px;
@@ -267,8 +272,6 @@ video#myCamVideo{
.btn-cam-action div img{
height: 22px;
width: 30px;
- top: calc(48px - 37px);
- left: calc(48px - 41px);
position: relative;
cursor: url('./images/cursor_pointer.png'), pointer;
}
@@ -331,48 +334,15 @@ video#myCamVideo{
}
}
-.webrtcsetup{
- display: none;
- position: absolute;
- top: 140px;
- left: 0;
- right: 0;
- margin-left: auto;
- margin-right: auto;
- height: 50%;
- width: 50%;
- border: white 6px solid;
-}
-.webrtcsetup .background-img {
- position: relative;
- display: block;
- width: 40%;
- height: 60%;
- margin-left: auto;
- margin-right: auto;
- top: 50%;
- transform: translateY(-50%);
-}
-#myCamVideoSetup {
- width: 100%;
- height: 100%;
- -webkit-transform: scaleX(-1);
- transform: scaleX(-1);
-}
-.webrtcsetup.active{
- display: block;
-}
-
-
/* New layout */
body {
margin: 0;
- height: 100vh;
- width: 100vw;
+ height: 100%;
+ width: 100%;
}
.main-container {
- height: 100vh;
- width: 100vw;
+ height: 100%;
+ width: 100%;
position: absolute;
}
@@ -397,7 +367,6 @@ body {
}
#game {
- width: 100%;
position: relative; /* Position relative is needed for the game-overlay. */
}
@@ -618,7 +587,6 @@ input[type=range]:focus::-ms-fill-upper {
/* Let's make sure videos are vertically centered if they need to be cropped */
.media-container {
display: flex;
- justify-content: center;
flex-direction: column;
overflow: hidden;
border-radius: 15px;
@@ -840,35 +808,6 @@ input[type=range]:focus::-ms-fill-upper {
}
-
-/*audio html when audio message playing*/
-.main-container .audio-playing {
- position: absolute;
- width: 200px;
- height: 54px;
- right: -210px;
- top: 40px;
- transition: all 0.1s ease-out;
- background-color: black;
- border-radius: 30px 0 0 30px;
- display: inline-flex;
-}
-
-.main-container .audio-playing.active{
- right: 0;
-}
-.main-container .audio-playing img{
- /*width: 30px;*/
- border-radius: 50%;
- background-color: #ffda01;
- padding: 10px;
-}
-.main-container .audio-playing p{
- color: white;
- margin-left: 10px;
- margin-top: 14px;
-}
-
/* VIDEO QUALITY */
.main-console div.setting h1{
color: white;
@@ -1136,6 +1075,10 @@ div.modal-report-user{
color: white;
}
+.discussion .messages .message p a:visited{
+ color: white;
+}
+
.discussion .send-message{
position: absolute;
bottom: 45px;
@@ -1244,4 +1187,22 @@ div.action.danger p.action-body{
width: 100%;
height: 100%;
pointer-events: none;
+
+ & > div {
+ position: relative;
+ width: 100%;
+ height: 100%;
+
+ & > div {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ }
+
+ & > div.scrollable {
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ pointer-events: auto;
+ }
+ }
}
diff --git a/front/tests/Phaser/Connexion/LocalUserTest.ts b/front/tests/Phaser/Connexion/LocalUserTest.ts
index 25b54005..4ba20745 100644
--- a/front/tests/Phaser/Connexion/LocalUserTest.ts
+++ b/front/tests/Phaser/Connexion/LocalUserTest.ts
@@ -19,8 +19,14 @@ describe("isUserNameValid()", () => {
it("should not validate spaces", () => {
expect(isUserNameValid(' ')).toBe(false);
});
- it("should not validate special characters", () => {
- expect(isUserNameValid('a&-')).toBe(false);
+ it("should validate special characters", () => {
+ expect(isUserNameValid('%&-')).toBe(true);
+ });
+ it("should validate accents", () => {
+ expect(isUserNameValid('éàëè')).toBe(true);
+ });
+ it("should validate chinese characters", () => {
+ expect(isUserNameValid('中文鍵盤')).toBe(true);
});
});
diff --git a/front/webpack.config.ts b/front/webpack.config.ts
index cc87572a..3a69b74a 100644
--- a/front/webpack.config.ts
+++ b/front/webpack.config.ts
@@ -7,6 +7,7 @@ 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';
+import {DISPLAY_TERMS_OF_USE} from "./src/Enum/EnvironmentVariable";
const mode = process.env.NODE_ENV ?? 'development';
const isProduction = mode === 'production';
@@ -88,7 +89,16 @@ module.exports = {
preprocess: sveltePreprocess({
scss: true,
sass: true,
- })
+ }),
+ onwarn: function (warning: { code: string }, handleWarning: (warning: { code: string }) => void) {
+ // See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782
+
+ if (warning.code === 'a11y-no-onchange') { return }
+ if (warning.code === 'a11y-autofocus') { return }
+
+ // process as usual
+ handleWarning(warning);
+ }
}
}
},
@@ -102,9 +112,17 @@ module.exports = {
}
},
{
- test: /\.(ttf|eot|svg|png|gif|jpg)$/,
+ test: /\.(eot|svg|png|gif|jpg)$/,
exclude: /node_modules/,
type: 'asset'
+ },
+ {
+ test: /\.(woff(2)?|ttf)$/,
+ type: 'asset',
+ generator: {
+ filename: 'fonts/[name][ext]'
+ }
+
}
],
},
@@ -167,7 +185,8 @@ module.exports = {
'JITSI_PRIVATE_MODE': null,
'START_ROOM_URL': null,
'MAX_USERNAME_LENGTH': 8,
- 'MAX_PER_GROUP': 4
+ 'MAX_PER_GROUP': 4,
+ 'DISPLAY_TERMS_OF_USE': false,
})
],
diff --git a/front/yarn.lock b/front/yarn.lock
index bbdf0e06..e64a76c1 100644
--- a/front/yarn.lock
+++ b/front/yarn.lock
@@ -30,6 +30,13 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/runtime@^7.14.0":
+ version "7.14.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
+ integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@discoveryjs/json-ext@^0.5.0":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d"
@@ -50,6 +57,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
+"@fontsource/press-start-2p@^4.3.0":
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/@fontsource/press-start-2p/-/press-start-2p-4.3.0.tgz#37124387f7fbfe7792b5fc9a1906b80d9aeda4c6"
+ integrity sha512-gmS4070EoZp5/6NUJ+tBnvtDiSmFcR+S+ClAOJ8NGFXDWOkO12yMnyGJEJaDCNCAMX0s2TQCcmr6qWKx5ad3RQ==
+
"@nodelib/fs.scandir@2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"
@@ -639,7 +651,7 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
-anymatch@~3.1.1:
+anymatch@~3.1.1, anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
@@ -773,6 +785,14 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+automation-events@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/automation-events/-/automation-events-4.0.1.tgz#93acef8a457cbea65f16fdcef8f349fd2fafe298"
+ integrity sha512-8bQx+PVtNDMD5F2H40cQs7oexZve3Z0xC9fWRQT4fltF65f/WsSpjM4jpulL4K4yLLB71oi4/xVJJCJ5I/Kjbw==
+ dependencies:
+ "@babel/runtime" "^7.14.0"
+ tslib "^2.2.0"
+
available-typed-arrays@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5"
@@ -1092,7 +1112,7 @@ caniuse-lite@^1.0.30001219:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
-chalk@^2.0.0:
+chalk@^2.0.0, chalk@^2.4.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1143,6 +1163,21 @@ chokidar@^2.1.8:
optionalDependencies:
fsevents "^1.2.7"
+chokidar@^3.4.1:
+ version "3.5.2"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
+ integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
chrome-trace-event@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@@ -1382,7 +1417,7 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
-cross-spawn@^6.0.0:
+cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
@@ -1621,9 +1656,9 @@ dns-equal@^1.0.0:
integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=
dns-packet@^1.3.1:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
- integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f"
+ integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==
dependencies:
ip "^1.1.0"
safe-buffer "^5.0.1"
@@ -2345,7 +2380,7 @@ fsevents@^1.2.7:
bindings "^1.5.0"
nan "^2.12.1"
-fsevents@~2.3.1:
+fsevents@~2.3.1, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -2409,7 +2444,7 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
-glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
+glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2586,6 +2621,11 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
+hosted-git-info@^2.1.4:
+ version "2.8.9"
+ resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
+
hpack.js@^2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
@@ -3157,7 +3197,7 @@ js-yaml@^3.13.1:
argparse "^1.0.7"
esprima "^4.0.0"
-json-parse-better-errors@^1.0.2:
+json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@@ -3262,6 +3302,16 @@ linked-list-typescript@^1.0.11:
resolved "https://registry.yarnpkg.com/linked-list-typescript/-/linked-list-typescript-1.0.15.tgz#faeed93cf9203f102e2158c29edcddda320abe82"
integrity sha512-RIyUu9lnJIyIaMe63O7/aFv/T2v3KsMFuXMBbUQCHX+cgtGro86ETDj5ed0a8gQL2+DFjzYYsgVG4I36/cUwgw==
+load-json-file@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
+ integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^4.0.0"
+ pify "^3.0.0"
+ strip-bom "^3.0.0"
+
loader-runner@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
@@ -3376,6 +3426,11 @@ memory-fs@^0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+memorystream@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
+ integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
+
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -3509,6 +3564,11 @@ mkdirp@^0.5.1, mkdirp@^0.5.5:
dependencies:
minimist "^1.2.5"
+mri@^1.1.0:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6"
+ integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==
+
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -3637,6 +3697,16 @@ node-releases@^1.1.71:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
+normalize-package-data@^2.3.2:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
+ integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
+ dependencies:
+ hosted-git-info "^2.1.4"
+ resolve "^1.10.0"
+ semver "2 || 3 || 4 || 5"
+ validate-npm-package-license "^3.0.1"
+
normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
@@ -3649,6 +3719,21 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+npm-run-all@^4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
+ integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ chalk "^2.4.1"
+ cross-spawn "^6.0.5"
+ memorystream "^0.3.1"
+ minimatch "^3.0.4"
+ pidtree "^0.3.0"
+ read-pkg "^3.0.0"
+ shell-quote "^1.6.1"
+ string.prototype.padend "^3.0.0"
+
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -3879,6 +3964,14 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
+parse-json@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
+ integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
+ dependencies:
+ error-ex "^1.3.1"
+ json-parse-better-errors "^1.0.1"
+
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@@ -3967,6 +4060,13 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+path-type@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+ integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
+ dependencies:
+ pify "^3.0.0"
+
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
@@ -4014,11 +4114,21 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d"
integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==
+pidtree@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
+ integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
+
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+ integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
@@ -4268,6 +4378,15 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
+read-pkg@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
+ integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
+ dependencies:
+ load-json-file "^4.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^3.0.0"
+
readable-stream@^2.0.1, readable-stream@^2.0.2:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
@@ -4306,6 +4425,13 @@ readdirp@~3.5.0:
dependencies:
picomatch "^2.2.1"
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
rechoir@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca"
@@ -4313,6 +4439,11 @@ rechoir@^0.7.0:
dependencies:
resolve "^1.9.0"
+regenerator-runtime@^0.13.4:
+ version "0.13.7"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+ integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -4419,7 +4550,7 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-resolve@^1.9.0:
+resolve@^1.10.0, resolve@^1.9.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
@@ -4478,6 +4609,13 @@ rxjs@^6.6.3:
dependencies:
tslib "^1.9.0"
+sade@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691"
+ integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==
+ dependencies:
+ mri "^1.1.0"
+
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -4554,7 +4692,7 @@ selfsigned@^1.10.8:
dependencies:
node-forge "^0.10.0"
-semver@^5.5.0:
+"semver@2 || 3 || 4 || 5", semver@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -4689,6 +4827,11 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+shell-quote@^1.6.1:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
+ integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
+
signal-exit@^3.0.0, signal-exit@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@@ -4842,6 +4985,32 @@ source-map@^0.7.3, source-map@~0.7.2:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+spdx-correct@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
+ integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==
+ dependencies:
+ spdx-expression-parse "^3.0.0"
+ spdx-license-ids "^3.0.0"
+
+spdx-exceptions@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+ integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
+
+spdx-expression-parse@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
+ integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
+ dependencies:
+ spdx-exceptions "^2.1.0"
+ spdx-license-ids "^3.0.0"
+
+spdx-license-ids@^3.0.0:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f"
+ integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==
+
spdy-transport@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
@@ -4877,6 +5046,15 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+standardized-audio-context@^25.2.4:
+ version "25.2.4"
+ resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.2.4.tgz#d64dbdd70615171ec90d1b7151a0d945af94cf3d"
+ integrity sha512-uQKZXRnXrPVO1V6SwZ7PiV3RkQqRY3n7i6Q8nbTXYvoz8NftRNzfOIlwefpuC8LVLUUs9dhbKTpP+WOA82dkBw==
+ dependencies:
+ "@babel/runtime" "^7.14.0"
+ automation-events "^4.0.1"
+ tslib "^2.2.0"
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
@@ -4926,6 +5104,15 @@ string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
+string.prototype.padend@^3.0.0:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz#6858ca4f35c5268ebd5e8615e1327d55f59ee311"
+ integrity sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.3"
+ es-abstract "^1.18.0-next.2"
+
string.prototype.trimend@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
@@ -5025,6 +5212,21 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
+svelte-check@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-2.1.0.tgz#3ee8ad86068256346ebca862bbee8417a757fc53"
+ integrity sha512-kKLXkFt0XZTn+O1fnilGTQ1SFLsOFF+lXp1YjPfeN9nX+Y3ZpELtZSQCkbuK6HMkWugFvsOM17FCOSa1mfrEFA==
+ dependencies:
+ chalk "^4.0.0"
+ chokidar "^3.4.1"
+ glob "^7.1.6"
+ import-fresh "^3.2.1"
+ minimist "^1.2.5"
+ sade "^1.7.4"
+ source-map "^0.7.3"
+ svelte-preprocess "^4.0.0"
+ typescript "*"
+
svelte-dev-helper@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/svelte-dev-helper/-/svelte-dev-helper-1.1.9.tgz#7d187db5c6cdbbd64d75a32f91b8998bde3273c3"
@@ -5044,7 +5246,7 @@ svelte-loader@^3.1.1:
svelte-dev-helper "^1.1.9"
svelte-hmr "^0.12.3"
-svelte-preprocess@^4.7.3:
+svelte-preprocess@^4.0.0, svelte-preprocess@^4.7.3:
version "4.7.3"
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.7.3.tgz#454fa059c2400b15e7a3caeca18993cff9df0e96"
integrity sha512-Zx1/xLeGOIBlZMGPRCaXtlMe4ZA0faato5Dc3CosEqwu75MIEPuOstdkH6cy+RYTUYynoxzNaDxkPX4DbrPwRA==
@@ -5207,7 +5409,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.3:
+tslib@^2.0.3, tslib@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
@@ -5249,6 +5451,11 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
+typescript@*:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
+ integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
+
typescript@^4.2.4:
version "4.2.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
@@ -5374,6 +5581,14 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0:
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
+validate-npm-package-license@^3.0.1:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
+ integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
+ dependencies:
+ spdx-correct "^3.0.0"
+ spdx-expression-parse "^3.0.0"
+
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -5616,9 +5831,9 @@ wrappy@1:
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^6.2.1:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
- integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
+ version "6.2.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e"
+ integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==
dependencies:
async-limiter "~1.0.0"
diff --git a/maps/tests/help_camera_setting.json b/maps/tests/help_camera_setting.json
new file mode 100644
index 00000000..2dcdec3a
--- /dev/null
+++ b/maps/tests/help_camera_setting.json
@@ -0,0 +1,164 @@
+{ "compressionlevel":-1,
+ "height":10,
+ "infinite":false,
+ "layers":[
+ {
+ "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51],
+ "height":10,
+ "id":3,
+ "name":"bottom",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ "height":10,
+ "id":1,
+ "name":"start",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "draworder":"topdown",
+ "id":2,
+ "name":"floorLayer",
+ "objects":[
+ {
+ "height":254.57168784029,
+ "id":1,
+ "name":"",
+ "rotation":0,
+ "text":
+ {
+ "fontfamily":"Sans Serif",
+ "pixelsize":12,
+ "text":"Test 1 : \nBlock permission to camera and\/or microphone access.\n\nResult 1 :\nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n\nTest 2 : \nReload the page and block permission to camera and\/or microphone access on the camera setting page.\n\nResult 2 : \nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":295.278811252269,
+ "x":12.2517014519056,
+ "y":49.3021778584392
+ }],
+ "opacity":1,
+ "type":"objectgroup",
+ "visible":true,
+ "x":0,
+ "y":0
+ }],
+ "nextlayerid":6,
+ "nextobjectid":2,
+ "orientation":"orthogonal",
+ "renderorder":"right-down",
+ "tiledversion":"1.4.3",
+ "tileheight":32,
+ "tilesets":[
+ {
+ "columns":8,
+ "firstgid":1,
+ "image":"Validation\/tileset_dungeon.png",
+ "imageheight":256,
+ "imagewidth":256,
+ "margin":0,
+ "name":"dungeon",
+ "spacing":0,
+ "tilecount":64,
+ "tileheight":32,
+ "tiles":[
+ {
+ "id":0,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":1,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":2,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":5,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":8,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":10,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":16,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":17,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":18,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ }],
+ "tilewidth":32
+ }],
+ "tilewidth":32,
+ "type":"map",
+ "version":1.4,
+ "width":10
+}
\ No newline at end of file
diff --git a/maps/tests/index.html b/maps/tests/index.html
index a17a3b5d..9c95c281 100644
--- a/maps/tests/index.html
+++ b/maps/tests/index.html
@@ -82,6 +82,14 @@
Test energy consumption
+
+
+ Success Failure Pending
+ |
+
+ Test the HelpCameraSettingScene
+ |
+