Merge remote-tracking branch 'remotes/workadventure-main/develop' into iframe-api-refactor
35
.github/workflows/build-and-deploy.yml
vendored
@ -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
|
||||
|
4
.github/workflows/continuous_integration.yml
vendored
@ -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"
|
||||
|
14
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
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
import {
|
||||
BatchMessage,
|
||||
PusherToBackMessage,
|
||||
ServerToAdminClientMessage,
|
||||
ServerToClientMessage,
|
||||
SubMessage, UserJoinedRoomMessage, UserLeftRoomMessage
|
||||
UserJoinedRoomMessage, UserLeftRoomMessage
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {AdminSocket} from "../RoomManager";
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
6
benchmark/package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
40
front/dist/index.tmpl.html
vendored
@ -29,7 +29,6 @@
|
||||
|
||||
|
||||
<base href="/">
|
||||
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
|
||||
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
|
||||
|
||||
<title>WorkAdventure</title>
|
||||
@ -47,32 +46,6 @@
|
||||
</aside>
|
||||
<div id="chat-mode" class="chat-mode three-col" style="display: none;">
|
||||
</div>
|
||||
<div id="activeCam" class="activeCam">
|
||||
<div id="div-myCamVideo" class="video-container">
|
||||
<video id="myCamVideo" autoplay muted></video>
|
||||
<div id="mySoundMeter" class="sound-progress">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-cam-action">
|
||||
<div id="btn-monitor" class="btn-monitor">
|
||||
<img id="monitor" src="resources/logos/monitor.svg">
|
||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
||||
</div>
|
||||
<div id="btn-video" class="btn-video">
|
||||
<img id="cinema" src="resources/logos/cinema.svg">
|
||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||
</div>
|
||||
<div id="btn-micro" class="btn-micro">
|
||||
<img id="microphone" src="resources/logos/microphone.svg">
|
||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cowebsite" class="cowebsite hidden">
|
||||
@ -114,23 +87,10 @@
|
||||
<div id="audioplayer" style="visibility: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-playing">
|
||||
<img src="/resources/logos/megaphone.svg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||
</div>
|
||||
<div id="webRtcSetup" class="webrtcsetup">
|
||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</div>
|
||||
<audio id="audio-webrtc-in">
|
||||
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
<audio id="audio-webrtc-out">
|
||||
<source src="/resources/objects/webrtc-out.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
<audio id="report-message">
|
||||
<source src="/resources/objects/report-message.mp3" type="audio/mp3">
|
||||
</audio>
|
||||
|
5
front/dist/resources/fonts/fonts.css
vendored
Normal file
@ -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');
|
||||
}
|
160
front/dist/resources/html/CustomCharacterScene.html
vendored
@ -1,160 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#customizeScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
width: 42vw;
|
||||
height: 48vh;
|
||||
/*max-width: 300px;
|
||||
max-height: 48vh;*/
|
||||
overflow: hidden;
|
||||
}
|
||||
#customizeScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#customizeScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#customizeScene section.action {
|
||||
text-align: center;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
top: 100%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 55%;
|
||||
}
|
||||
#customizeScene button {
|
||||
margin: 2px 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#customizeScene button#customizeSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#customizeScene section h6,
|
||||
#customizeScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#customizeScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#customizeScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#customizeScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#customizeScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#customizeScene section input#customizeSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton{
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
top: -8vh;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonLeft{
|
||||
left: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonRight{
|
||||
right: 0vw;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonUp{
|
||||
left: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: -20px;
|
||||
}
|
||||
#customizeScene section button.customizeSceneButton#customizeSceneButtonDown{
|
||||
right: calc(2vw + 40px);
|
||||
transform: rotate(90deg);
|
||||
margin-top: 20px;
|
||||
}
|
||||
#customizeScene section.action img{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
#customizeScene section.action a#customizeSceneFormBack img{
|
||||
margin-top: -2px;
|
||||
}
|
||||
#customizeScene section.action button#customizeSceneFormSubmit img{
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
#customizeScene {
|
||||
max-width: 160px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-height: 400px) {
|
||||
#customizeScene section.action {
|
||||
top: 92%;
|
||||
}
|
||||
#customizeScene section.action.action-move {
|
||||
top: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="customizeScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Custom your WOKA</h3>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonLeft"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonRight"> > </button>
|
||||
<!--<button class="customizeSceneButton" id="customizeSceneButtonUp"> < </button>
|
||||
<button class="customizeSceneButton" id="customizeSceneButtonDown"> > </button>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a type="submit" id="customizeSceneFormBack">Back <img src="resources/objects/arrow_up.png"/></a>
|
||||
<button type="submit" id="customizeSceneFormSubmit">Next <img src="resources/objects/arrow_up.png"/></button>
|
||||
</section>
|
||||
</form>
|
129
front/dist/resources/html/EnableCameraScene.html
vendored
@ -1,129 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#enableCameraScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
width: 42vw;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#enableCameraScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#enableCameraScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section.title {
|
||||
position: absolute;
|
||||
top: 1vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 40vh;
|
||||
width: 100%;
|
||||
}
|
||||
#enableCameraScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#enableCameraScene button#enableCameraSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#enableCameraScene section h6,
|
||||
#enableCameraScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#enableCameraScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#enableCameraScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#enableCameraScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#enableCameraScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#enableCameraScene section input#enableCameraSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#enableCameraScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
/*@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#enableCameraScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}*/
|
||||
</style>
|
||||
|
||||
<form id="enableCameraScene" hidden>
|
||||
<!-- FIX me -->
|
||||
<section class="title text-center">
|
||||
<h5>Turn on your camera and microphone</h5>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your camera</h3>
|
||||
<select id="camera">
|
||||
</select>
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Select your michrophone</h3>
|
||||
<select id="michrophone">
|
||||
</select>
|
||||
</section>-->
|
||||
<section class="action">
|
||||
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
|
||||
</section>
|
||||
</form>
|
134
front/dist/resources/html/SelectCompanionScene.html
vendored
@ -1,134 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCompanionScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCompanionScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCompanionScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCompanionScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 20vh;
|
||||
}
|
||||
#selectCompanionScene button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCompanionScene button#selectCompanionSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCompanionScene section h6,
|
||||
#selectCompanionScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCompanionScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section a{
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCompanionScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCompanionScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCompanionScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCompanionScene section input#selectCompanionSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCompanionScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCompanionScene section button#selectCompanionSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCompanionScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCompanionScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<a herf="#" id="selectCompanionSceneFormBack">No companion</a>
|
||||
<button type="submit" id="selectCompanionSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
5
front/dist/resources/html/gameMenu.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameMenu main{
|
||||
margin-top: 15px;
|
||||
}
|
||||
@ -67,7 +64,7 @@
|
||||
<button id="adminConsoleButton">Admin console</button>
|
||||
</section>
|
||||
<section id="socialLinks" hidden>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventurebytcm" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventure.WA" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
|
||||
</section>
|
||||
</main>
|
||||
|
3
front/dist/resources/html/gameMenuIcon.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#menuIcon button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameQuality {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
3
front/dist/resources/html/gameReport.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameReport {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
3
front/dist/resources/html/gameShare.html
vendored
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#gameShare {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
109
front/dist/resources/html/helpCameraSettings.html
vendored
@ -1,109 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#helpCameraSettings {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 25px auto 0;
|
||||
width: 400px;
|
||||
max-height: calc(48vh - 50px);
|
||||
max-width: 48vw;
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#helpCameraSettings h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#helpCameraSettings section {
|
||||
margin: 10px;
|
||||
}
|
||||
#helpCameraSettings section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#helpCameraSettings button {
|
||||
margin: 10px 4px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#helpCameraSettings section a{
|
||||
font-size: 12px;
|
||||
text-decoration: underline;
|
||||
color: black;
|
||||
}
|
||||
#helpCameraSettings section h6,
|
||||
#helpCameraSettings section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#helpCameraSettings section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#helpCameraSettings section p{
|
||||
font-size: 8px;
|
||||
margin: 0px 20px;
|
||||
}
|
||||
#helpCameraSettings section p a{
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section p.err{
|
||||
color: #ff0000;
|
||||
}
|
||||
#helpCameraSettings section ul{
|
||||
margin: 6px;
|
||||
}
|
||||
#helpCameraSettings section li{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section img {
|
||||
width: 200px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#helpCameraSettings{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="helpCameraSettings" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Camera/Microphone access needed</h5>
|
||||
<p class="err" id="permissionError">Permission denied</p>
|
||||
<p class="info">You must allow camera and microphone access in your browser.</p>
|
||||
<ul>
|
||||
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
||||
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
||||
</ul>
|
||||
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||
<p id='browserHelpSetting'></p>
|
||||
</section>
|
||||
<!--<section class="text-center">
|
||||
<p>If your problem persist, please contact us: <a id="mailto" href="mailto:workadventure@thecodingmachine.com?subject=Support camera and microphone settings" target="_blank"> workadventure@thecodingmachine.com</a>.</p>
|
||||
</section>-->
|
||||
</section>
|
||||
<section class="action">
|
||||
<a href="#" id="helpCameraSettingsFormRefresh">Refresh</a>
|
||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||
</section>
|
||||
</form>
|
120
front/dist/resources/html/loginScene.html
vendored
@ -1,120 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#loginScene {
|
||||
background: #000000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
max-width: 200px;
|
||||
color: #ebeeee;
|
||||
max-height: 40vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
#loginScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#loginScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#loginScene section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#loginScene button {
|
||||
margin: 10px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#loginScene button#loginSceneFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#loginScene section h6,
|
||||
#loginScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#loginScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#loginScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#loginScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#loginScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#loginScene section input#loginSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#loginScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#loginScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="loginScene" hidden>
|
||||
<section class="text-center">
|
||||
<img src="resources/logos/logo.png">
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h5>Enter your name</h5>
|
||||
<p class="info">9 chars maximum</p>
|
||||
<p class="err" id="errorLoginScene"></p>
|
||||
</section>
|
||||
<section>
|
||||
<input type="text" name="email" id="loginSceneName">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
142
front/dist/resources/html/selectCharacterScene.html
vendored
@ -1,142 +0,0 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#selectCharacterScene {
|
||||
background: #0000;
|
||||
/*border: 1px solid #ebeeee;*/
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
color: #ebeeee;
|
||||
max-height: 48vh;
|
||||
max-width: 300px;
|
||||
width: 40vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
#selectCharacterScene h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#selectCharacterScene input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section {
|
||||
margin: 10px;
|
||||
}
|
||||
#selectCharacterScene section.action {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
margin-top: 28vh;
|
||||
}
|
||||
#selectCharacterScene button {
|
||||
margin: 10px 0px;
|
||||
background-color: black;;
|
||||
color: #ebeeee;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
width: 100px;
|
||||
}
|
||||
#selectCharacterScene button#selectCharacterSceneFormCancel {
|
||||
background-color: #aca6a600;
|
||||
color: #292929;
|
||||
}
|
||||
#selectCharacterScene section h6,
|
||||
#selectCharacterScene section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#selectCharacterScene section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section a{
|
||||
font-size: 8px;
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
#selectCharacterScene section a:hover{
|
||||
font-weight: 700;
|
||||
}
|
||||
#selectCharacterScene section p{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
#selectCharacterScene section p.err{
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section p.info{
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
#selectCharacterScene section input#selectCharacterSceneLink{
|
||||
background-color: #a1a3a3;
|
||||
}
|
||||
#selectCharacterScene section img{
|
||||
width: 160px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton{
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
margin: 0;
|
||||
width: 25px;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: none;
|
||||
left: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: none;
|
||||
right: 1vw;
|
||||
}
|
||||
#selectCharacterScene section button#selectCharacterSceneFormCustomYourOwnSubmit{
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
margin-top: -2px;
|
||||
background-color: #ffd700;
|
||||
color: black;
|
||||
}
|
||||
@media only screen and (max-width: 800px),
|
||||
only screen and (max-height: 600px) {
|
||||
#selectCharacterScene{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonRight{
|
||||
display: block;
|
||||
}
|
||||
#selectCharacterScene section button.selectCharacterButton#selectCharacterButtonLeft{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="selectCharacterScene" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Select your WOKA</h5>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonLeft"> < </button>
|
||||
<button class="selectCharacterButton" id="selectCharacterButtonRight"> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="selectCharacterSceneFormSubmit">Continue</button>
|
||||
<button type="submit" id="selectCharacterSceneFormCustomYourOwnSubmit">Custom your WOKA</button>
|
||||
</section>
|
||||
</form>
|
@ -1,7 +1,4 @@
|
||||
<style>
|
||||
*{
|
||||
font-family: PixelFont-7,monospace!important;
|
||||
}
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
|
BIN
front/dist/resources/objects/arrow_down.png
vendored
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
front/dist/resources/objects/arrow_up.png
vendored
Before Width: | Height: | Size: 149 B After Width: | Height: | Size: 4.9 KiB |
BIN
front/dist/resources/objects/arrow_up_black.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
@ -20,9 +20,11 @@
|
||||
"jasmine": "^3.5.0",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.1.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-check": "^2.1.0",
|
||||
"svelte-loader": "^3.1.1",
|
||||
"svelte-preprocess": "^4.7.3",
|
||||
"ts-loader": "^9.1.2",
|
||||
@ -34,6 +36,7 @@
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/press-start-2p": "^4.3.0",
|
||||
"@types/simple-peer": "^9.6.0",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"axios": "^0.21.1",
|
||||
@ -45,13 +48,17 @@
|
||||
"quill": "1.3.6",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.6.2",
|
||||
"socket.io-client": "^2.3.0"
|
||||
"socket.io-client": "^2.3.0",
|
||||
"standardized-audio-context": "^25.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"start": "run-p serve svelte-check-watch",
|
||||
"serve": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open",
|
||||
"build": "TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack",
|
||||
"test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts",
|
||||
"svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch",
|
||||
"svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\""
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||
import {soundManager} from "../Phaser/Game/SoundManager";
|
||||
|
||||
export class GlobalMessageManager {
|
||||
|
||||
@ -43,45 +45,8 @@ export class GlobalMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
private playAudioMessage(messageId : string, urlMessage: string){
|
||||
//delete previous elements
|
||||
const previousDivAudio = document.getElementsByClassName('audio-playing');
|
||||
for(let i = 0; i < previousDivAudio.length; i++){
|
||||
previousDivAudio[i].remove();
|
||||
}
|
||||
|
||||
//create new element
|
||||
const divAudio : HTMLDivElement = document.createElement('div');
|
||||
divAudio.id = `audio-playing-${messageId}`;
|
||||
divAudio.classList.add('audio-playing');
|
||||
const imgAudio : HTMLImageElement = document.createElement('img');
|
||||
imgAudio.src = '/resources/logos/megaphone.svg';
|
||||
const pAudio : HTMLParagraphElement = document.createElement('p');
|
||||
pAudio.textContent = 'Message audio'
|
||||
divAudio.appendChild(imgAudio);
|
||||
divAudio.appendChild(pAudio);
|
||||
|
||||
const mainSectionDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
mainSectionDiv.appendChild(divAudio);
|
||||
|
||||
const messageAudio : HTMLAudioElement = document.createElement('audio');
|
||||
messageAudio.id = this.getHtmlMessageId(messageId);
|
||||
messageAudio.autoplay = true;
|
||||
messageAudio.style.display = 'none';
|
||||
messageAudio.onended = () => {
|
||||
divAudio.classList.remove('active');
|
||||
messageAudio.remove();
|
||||
setTimeout(() => {
|
||||
divAudio.remove();
|
||||
}, 1000);
|
||||
}
|
||||
messageAudio.onplay = () => {
|
||||
divAudio.classList.add('active');
|
||||
}
|
||||
const messageAudioSource : HTMLSourceElement = document.createElement('source');
|
||||
messageAudioSource.src = `${UPLOADER_URL}${urlMessage}`;
|
||||
messageAudio.appendChild(messageAudioSource);
|
||||
mainSectionDiv.appendChild(messageAudio);
|
||||
private playAudioMessage(messageId : string, urlMessage: string) {
|
||||
soundPlayingStore.playSound(UPLOADER_URL + urlMessage);
|
||||
}
|
||||
|
||||
private playTextMessage(messageId : string, htmlMessage: string){
|
||||
|
@ -44,7 +44,13 @@ export class TypeMessageExt implements TypeMessageInterface{
|
||||
mainSectionDiv.appendChild(div);
|
||||
|
||||
const reportMessageAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('report-message');
|
||||
reportMessageAudio.play();
|
||||
// FIXME: this will fail on iOS
|
||||
// We should move the sound playing into the GameScene and listen to the event of a report using a store
|
||||
try {
|
||||
reportMessageAudio.play();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
this.nbSecond = this.maxNbSecond;
|
||||
setTimeout((c) => {
|
||||
|
@ -70,15 +70,23 @@ class IframeListener {
|
||||
// Do we trust the sender of this message?
|
||||
// Let's only accept messages from the iframe that are allowed.
|
||||
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
|
||||
let foundSrc: string | null = null;
|
||||
for (const iframe of this.iframes) {
|
||||
if (iframe.contentWindow === message.source) {
|
||||
foundSrc = iframe.src;
|
||||
break;
|
||||
let foundSrc: string | undefined;
|
||||
|
||||
foundSrc = [...this.scripts.keys()].find(key => {
|
||||
return this.scripts.get(key)?.contentWindow == message.source
|
||||
});
|
||||
|
||||
if (foundSrc === undefined) {
|
||||
for (const iframe of this.iframes) {
|
||||
if (iframe.contentWindow === message.source) {
|
||||
foundSrc = iframe.src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundSrc === undefined) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!foundSrc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = message.data;
|
||||
@ -106,11 +114,7 @@ class IframeListener {
|
||||
this._loadSoundStream.next(payload.data);
|
||||
}
|
||||
else if (payload.type === 'openCoWebSite' && isOpenCoWebsite(payload.data)) {
|
||||
const scriptUrl = [...this.scripts.keys()].find(key => {
|
||||
return this.scripts.get(key)?.contentWindow == message.source
|
||||
})
|
||||
|
||||
scriptUtils.openCoWebsite(payload.data.url, scriptUrl || foundSrc);
|
||||
scriptUtils.openCoWebsite(payload.data.url, foundSrc);
|
||||
}
|
||||
|
||||
else if (payload.type === 'closeCoWebSite') {
|
||||
|
@ -1,11 +1,86 @@
|
||||
<script lang="typescript">
|
||||
import MenuIcon from "./Menu/MenuIcon.svelte";
|
||||
import {menuIconVisible} from "../Stores/MenuStore";
|
||||
import {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
|
||||
import CameraControls from "./CameraControls.svelte";
|
||||
import MyCamera from "./MyCamera.svelte";
|
||||
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
|
||||
import {selectCompanionSceneVisibleStore} from "../Stores/SelectCompanionStore";
|
||||
import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore";
|
||||
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
|
||||
import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore";
|
||||
import {errorStore} from "../Stores/ErrorStore";
|
||||
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
|
||||
import LoginScene from "./Login/LoginScene.svelte";
|
||||
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
|
||||
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
|
||||
import VisitCard from "./VisitCard/VisitCard.svelte";
|
||||
import {requestVisitCardsStore} from "../Stores/GameStore";
|
||||
|
||||
import type {Game} from "../Phaser/Game/Game";
|
||||
import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore";
|
||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||
import AudioPlaying from "./UI/AudioPlaying.svelte";
|
||||
import {soundPlayingStore} from "../Stores/SoundPlayingStore";
|
||||
import ErrorDialog from "./UI/ErrorDialog.svelte";
|
||||
|
||||
export let game: Game;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<!-- {#if $menuIconVisible}
|
||||
<MenuIcon />
|
||||
{/if} -->
|
||||
{#if $loginSceneVisibleStore}
|
||||
<div class="scrollable">
|
||||
<LoginScene game={game}></LoginScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCharacterScene game={ game }></SelectCharacterScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $customCharacterSceneVisibleStore}
|
||||
<div>
|
||||
<CustomCharacterScene game={ game }></CustomCharacterScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $selectCompanionSceneVisibleStore}
|
||||
<div>
|
||||
<SelectCompanionScene game={ game }></SelectCompanionScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $enableCameraSceneVisibilityStore}
|
||||
<div class="scrollable">
|
||||
<EnableCameraScene game={game}></EnableCameraScene>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $soundPlayingStore}
|
||||
<div>
|
||||
<AudioPlaying url={$soundPlayingStore} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!--
|
||||
{#if $menuIconVisible}
|
||||
<div>
|
||||
<MenuIcon />
|
||||
</div>
|
||||
{/if}
|
||||
-->
|
||||
{#if $gameOverlayVisibilityStore}
|
||||
<div>
|
||||
<MyCamera></MyCamera>
|
||||
<CameraControls></CameraControls>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $helpCameraSettingsVisibleStore}
|
||||
<div>
|
||||
<HelpCameraSettingsPopup></HelpCameraSettingsPopup>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $requestVisitCardsStore}
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore}></VisitCard>
|
||||
{/if}
|
||||
{#if $errorStore.length > 0}
|
||||
<div>
|
||||
<ErrorDialog></ErrorDialog>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
61
front/src/Components/CameraControls.svelte
Normal file
@ -0,0 +1,61 @@
|
||||
<script lang="typescript">
|
||||
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
|
||||
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
|
||||
import monitorImg from "./images/monitor.svg";
|
||||
import monitorCloseImg from "./images/monitor-close.svg";
|
||||
import cinemaImg from "./images/cinema.svg";
|
||||
import cinemaCloseImg from "./images/cinema-close.svg";
|
||||
import microphoneImg from "./images/microphone.svg";
|
||||
import microphoneCloseImg from "./images/microphone-close.svg";
|
||||
|
||||
function screenSharingClick(): void {
|
||||
if ($requestedScreenSharingState === true) {
|
||||
requestedScreenSharingState.disableScreenSharing();
|
||||
} else {
|
||||
requestedScreenSharingState.enableScreenSharing();
|
||||
}
|
||||
}
|
||||
|
||||
function cameraClick(): void {
|
||||
if ($requestedCameraState === true) {
|
||||
requestedCameraState.disableWebcam();
|
||||
} else {
|
||||
requestedCameraState.enableWebcam();
|
||||
}
|
||||
}
|
||||
|
||||
function microphoneClick(): void {
|
||||
if ($requestedMicrophoneState === true) {
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
} else {
|
||||
requestedMicrophoneState.enableMicrophone();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore} class:enabled={$requestedScreenSharingState}>
|
||||
{#if $requestedScreenSharingState}
|
||||
<img src={monitorImg} alt="Start screen sharing">
|
||||
{:else}
|
||||
<img src={monitorCloseImg} alt="Stop screen sharing">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-video" on:click={cameraClick} class:disabled={!$requestedCameraState}>
|
||||
{#if $requestedCameraState}
|
||||
<img src={cinemaImg} alt="Turn on webcam">
|
||||
{:else}
|
||||
<img src={cinemaCloseImg} alt="Turn off webcam">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="btn-micro" on:click={microphoneClick} class:disabled={!$requestedMicrophoneState}>
|
||||
{#if $requestedMicrophoneState}
|
||||
<img src={microphoneImg} alt="Turn on microphone">
|
||||
{:else}
|
||||
<img src={microphoneCloseImg} alt="Turn off microphone">
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,119 @@
|
||||
<script lang="typescript">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import {CustomizeScene, CustomizeSceneName} from "../../Phaser/Login/CustomizeScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const customCharacterScene = game.scene.getScene(CustomizeSceneName) as CustomizeScene;
|
||||
let activeRow = customCharacterScene.activeRow;
|
||||
|
||||
function selectLeft() {
|
||||
customCharacterScene.moveCursorHorizontally(-1);
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
customCharacterScene.moveCursorHorizontally(1);
|
||||
}
|
||||
|
||||
function selectUp() {
|
||||
customCharacterScene.moveCursorVertically(-1);
|
||||
activeRow = customCharacterScene.activeRow;
|
||||
}
|
||||
|
||||
function selectDown() {
|
||||
customCharacterScene.moveCursorVertically(1);
|
||||
activeRow = customCharacterScene.activeRow;
|
||||
}
|
||||
|
||||
function previousScene() {
|
||||
customCharacterScene.backToPreviousScene();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
customCharacterScene.nextSceneToCamera();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="customCharacterScene">
|
||||
<section class="text-center">
|
||||
<h2>Customize your WOKA</h2>
|
||||
</section>
|
||||
<section class="action action-move">
|
||||
<button class="customCharacterSceneButton customCharacterSceneButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||
<button class="customCharacterSceneButton customCharacterSceneButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
{#if activeRow === 0}
|
||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ previousScene }>Return</button>
|
||||
{/if}
|
||||
{#if activeRow !== 0}
|
||||
<button type="submit" class="customCharacterSceneFormBack nes-btn" on:click|preventDefault={ selectUp }>Back <img src="resources/objects/arrow_up_black.png" alt=""/></button>
|
||||
{/if}
|
||||
{#if activeRow === 5}
|
||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ finish }>Finish</button>
|
||||
{/if}
|
||||
{#if activeRow !== 5}
|
||||
<button type="submit" class="customCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ selectDown }>Next <img src="resources/objects/arrow_down.png" alt=""/></button>
|
||||
{/if}
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.customCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.customCharacterSceneButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button.customCharacterSceneFormBack {
|
||||
color: #292929;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.customCharacterSceneButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
&.customCharacterSceneButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.customCharacterScene button.customCharacterSceneButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.customCharacterScene button.customCharacterSceneButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
229
front/src/Components/EnableCamera/EnableCameraScene.svelte
Normal file
@ -0,0 +1,229 @@
|
||||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {
|
||||
audioConstraintStore,
|
||||
cameraListStore,
|
||||
localStreamStore,
|
||||
microphoneListStore,
|
||||
videoConstraintStore
|
||||
} from "../../Stores/MediaStore";
|
||||
import {onDestroy} from "svelte";
|
||||
import HorizontalSoundMeterWidget from "./HorizontalSoundMeterWidget.svelte";
|
||||
import cinemaCloseImg from "../images/cinema-close.svg";
|
||||
import cinemaImg from "../images/cinema.svg";
|
||||
import microphoneImg from "../images/microphone.svg";
|
||||
|
||||
export let game: Game;
|
||||
let selectedCamera : string|undefined = undefined;
|
||||
let selectedMicrophone : string|undefined = undefined;
|
||||
|
||||
const enableCameraScene = game.scene.getScene(EnableCameraSceneName) as EnableCameraScene;
|
||||
|
||||
function submit() {
|
||||
enableCameraScene.login();
|
||||
}
|
||||
|
||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
update(newStream: MediaStream) {
|
||||
if (node.srcObject != newStream) {
|
||||
node.srcObject = newStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stream: MediaStream | null;
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
stream = value.stream;
|
||||
|
||||
if (stream !== null) {
|
||||
const videoTracks = stream.getVideoTracks();
|
||||
if (videoTracks.length > 0) {
|
||||
selectedCamera = videoTracks[0].getSettings().deviceId;
|
||||
}
|
||||
const audioTracks = stream.getAudioTracks();
|
||||
if (audioTracks.length > 0) {
|
||||
selectedMicrophone = audioTracks[0].getSettings().deviceId;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stream = null;
|
||||
selectedCamera = undefined;
|
||||
selectedMicrophone = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
function normalizeDeviceName(label: string): string {
|
||||
// remove IDs (that can appear in Chrome, like: "HD Pro Webcam (4df7:4eda)"
|
||||
return label.replace(/(\([[0-9a-f]{4}:[0-9a-f]{4}\))/g, '').trim();
|
||||
}
|
||||
|
||||
function selectCamera() {
|
||||
videoConstraintStore.setDeviceId(selectedCamera);
|
||||
}
|
||||
|
||||
function selectMicrophone() {
|
||||
audioConstraintStore.setDeviceId(selectedMicrophone);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="enableCameraScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<h2>Turn on your camera and microphone</h2>
|
||||
</section>
|
||||
{#if $localStreamStore.type === 'success' && $localStreamStore.stream}
|
||||
<video class="myCamVideoSetup" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||
{:else }
|
||||
<div class="webrtcsetup">
|
||||
<img class="background-img" src={cinemaCloseImg} alt="">
|
||||
</div>
|
||||
{/if}
|
||||
<HorizontalSoundMeterWidget stream={stream}></HorizontalSoundMeterWidget>
|
||||
|
||||
<section class="selectWebcamForm">
|
||||
|
||||
{#if $cameraListStore.length > 1 }
|
||||
<div class="control-group">
|
||||
<img src={cinemaImg} alt="Camera" />
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={selectedCamera} on:change={selectCamera}>
|
||||
{#each $cameraListStore as camera}
|
||||
<option value={camera.deviceId}>
|
||||
{normalizeDeviceName(camera.label)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $microphoneListStore.length > 1 }
|
||||
<div class="control-group">
|
||||
<img src={microphoneImg} alt="Microphone" />
|
||||
<div class="nes-select is-dark">
|
||||
<select bind:value={selectedMicrophone} on:change={selectMicrophone}>
|
||||
{#each $microphoneListStore as microphone}
|
||||
<option value={microphone.deviceId}>
|
||||
{normalizeDeviceName(microphone.label)}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary letsgo" >Let's go!</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.enableCameraScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
color: #ebeeee;
|
||||
|
||||
section.selectWebcamForm {
|
||||
margin-top: 3vh;
|
||||
margin-bottom: 3vh;
|
||||
min-height: 10vh;
|
||||
width: 50vw;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
select {
|
||||
font-family: "Press Start 2P";
|
||||
margin-top: 1vh;
|
||||
margin-bottom: 1vh;
|
||||
}
|
||||
|
||||
option {
|
||||
font-family: "Press Start 2P";
|
||||
}
|
||||
}
|
||||
|
||||
section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2{
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.letsgo {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-height: 60px;
|
||||
margin-top: 10px;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.webrtcsetup{
|
||||
margin-top: 2vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 28.125vw;
|
||||
width: 50vw;
|
||||
border: white 6px solid;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img.background-img {
|
||||
width: 40%;
|
||||
}
|
||||
}
|
||||
.myCamVideoSetup {
|
||||
margin-top: 2vh;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: 50vh;
|
||||
width: 50vw;
|
||||
border: white 6px solid;
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.enableCameraScene h2 {
|
||||
font-size: 80%;
|
||||
}
|
||||
.enableCameraScene .control-group .nes-select {
|
||||
font-size: 80%;
|
||||
}
|
||||
.enableCameraScene button.letsgo {
|
||||
font-size: 160%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -0,0 +1,82 @@
|
||||
<script lang="typescript">
|
||||
import { AudioContext } from 'standardized-audio-context';
|
||||
import {SoundMeter} from "../../Phaser/Components/SoundMeter";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
export let stream: MediaStream | null;
|
||||
let volume = 0;
|
||||
|
||||
const NB_BARS = 20;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try{
|
||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||
//console.log(volume);
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function color(i: number, volume: number) {
|
||||
const red = 255 * i / NB_BARS;
|
||||
const green = 255 * (1 - i / NB_BARS);
|
||||
|
||||
let alpha = 1;
|
||||
if (i >= volume) {
|
||||
alpha = 0.5;
|
||||
}
|
||||
|
||||
return 'background-color:rgba('+red+', '+green+', 0, '+alpha+')';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="horizontal-sound-meter" class:active={display}>
|
||||
{#each [...Array(NB_BARS).keys()] as i}
|
||||
<div style={color(i, volume)}></div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.horizontal-sound-meter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 50%;
|
||||
height: 30px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.horizontal-sound-meter div {
|
||||
margin-left: 5px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,73 @@
|
||||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore";
|
||||
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
|
||||
|
||||
let isAndroid = window.navigator.userAgent.includes('Android');
|
||||
let isFirefox = window.navigator.userAgent.includes('Firefox');
|
||||
let isChrome = window.navigator.userAgent.includes('Chrome');
|
||||
|
||||
function refresh() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function close() {
|
||||
helpCameraSettingsVisibleStore.set(false);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="helpCameraSettings nes-container" on:submit|preventDefault={close} transition:fly="{{ y: -900, duration: 500 }}">
|
||||
<section>
|
||||
<h2>Camera / Microphone access needed</h2>
|
||||
<p class="err">Permission denied</p>
|
||||
<p>You must allow camera and microphone access in your browser.</p>
|
||||
<p>
|
||||
{#if isFirefox }
|
||||
<p class="err">Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.</p>
|
||||
<img src={firefoxImg} alt="" />
|
||||
{:else if isChrome && !isAndroid }
|
||||
<img src={chromeImg} alt="" />
|
||||
{/if}
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<button class="helpCameraSettingsFormRefresh nes-btn" on:click|preventDefault={refresh}>Refresh</button>
|
||||
<button type="submit" class="helpCameraSettingsFormContinue nes-btn is-primary" on:click|preventDefault={close}>Continue without webcam</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
.helpCameraSettings {
|
||||
pointer-events: auto;
|
||||
background: #eceeee;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 10vh;
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
overflow: auto;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-family: 'Press Start 2P';
|
||||
}
|
||||
|
||||
section {
|
||||
p {
|
||||
margin: 15px;
|
||||
font-family: 'Press Start 2P';
|
||||
|
||||
& .err {
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
img {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
122
front/src/Components/Login/LoginScene.svelte
Normal file
@ -0,0 +1,122 @@
|
||||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
import {DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH} from "../../Enum/EnvironmentVariable";
|
||||
import logoImg from "../images/logo.png";
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const loginScene = game.scene.getScene(LoginSceneName) as LoginScene;
|
||||
|
||||
let name = gameManager.getPlayerName() || '';
|
||||
let startValidating = false;
|
||||
|
||||
function submit() {
|
||||
startValidating = true;
|
||||
|
||||
let finalName = name.trim();
|
||||
if (finalName !== '') {
|
||||
loginScene.login(finalName);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="loginScene" on:submit|preventDefault={submit}>
|
||||
<section class="text-center">
|
||||
<img src={logoImg} alt="WorkAdventure logo" />
|
||||
</section>
|
||||
<section class="text-center">
|
||||
<h2>Enter your name</h2>
|
||||
</section>
|
||||
<input type="text" name="loginSceneName" class="nes-input is-dark" autofocus maxlength={MAX_USERNAME_LENGTH} bind:value={name} on:keypress={() => {startValidating = true}} class:is-error={name.trim() === '' && startValidating} />
|
||||
<section class="error-section">
|
||||
{#if name.trim() === '' && startValidating }
|
||||
<p class="err">The name is empty</p>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if DISPLAY_TERMS_OF_USE}
|
||||
<section class="terms-and-conditions">
|
||||
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
|
||||
</section>
|
||||
{/if}
|
||||
<section class="action">
|
||||
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
.loginScene {
|
||||
pointer-events: auto;
|
||||
margin: 20px auto 0;
|
||||
width: 90%;
|
||||
color: #ebeeee;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
text-align: center;
|
||||
font-family: "Press Start 2P";
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.terms-and-conditions {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
p.err {
|
||||
color: #ce372b;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.error-section {
|
||||
min-height: 2rem;
|
||||
margin: 0;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: #ebeeee;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
margin: 10px 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
46
front/src/Components/MyCamera.svelte
Normal file
@ -0,0 +1,46 @@
|
||||
<script lang="typescript">
|
||||
import {localStreamStore} from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
function srcObject(node: HTMLVideoElement, stream: MediaStream) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
update(newStream: MediaStream) {
|
||||
if (node.srcObject != newStream) {
|
||||
node.srcObject = newStream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stream : MediaStream|null;
|
||||
/*$: {
|
||||
if ($localStreamStore.type === 'success') {
|
||||
stream = $localStreamStore.stream;
|
||||
} else {
|
||||
stream = null;
|
||||
}
|
||||
}*/
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe(value => {
|
||||
if (value.type === 'success') {
|
||||
stream = value.stream;
|
||||
} else {
|
||||
stream = null;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div>
|
||||
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
|
||||
{#if $localStreamStore.type === "success" && $localStreamStore.stream }
|
||||
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted playsinline></video>
|
||||
<SoundMeterWidget stream={stream}></SoundMeterWidget>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,87 @@
|
||||
<script lang="typescript">
|
||||
import type {Game} from "../../Phaser/Game/Game";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const selectCompanionScene = game.scene.getScene(SelectCompanionSceneName) as SelectCompanionScene;
|
||||
|
||||
function selectLeft() {
|
||||
selectCompanionScene.moveToLeft();
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
selectCompanionScene.moveToRight();
|
||||
}
|
||||
|
||||
function noCompanion() {
|
||||
selectCompanionScene.closeScene();
|
||||
}
|
||||
|
||||
function selectCompanion() {
|
||||
selectCompanionScene.selectCompanion();
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="selectCompanionScene">
|
||||
<section class="text-center">
|
||||
<h2>Select your companion</h2>
|
||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={selectLeft}> < </button>
|
||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={selectRight}> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button href="/" class="selectCompanionSceneFormBack nes-btn" on:click|preventDefault={noCompanion}>No companion</button>
|
||||
<button type="submit" class="selectCompanionSceneFormSubmit nes-btn is-primary" on:click|preventDefault={selectCompanion}>Continue</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.selectCompanionScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.selectCharacterButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.selectCharacterButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
button.selectCharacterButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.selectCompanionScene button.selectCharacterButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.selectCompanionScene button.selectCharacterButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
53
front/src/Components/SoundMeterWidget.svelte
Normal file
@ -0,0 +1,53 @@
|
||||
<script lang="typescript">
|
||||
import { AudioContext } from 'standardized-audio-context';
|
||||
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
||||
import {onDestroy} from "svelte";
|
||||
|
||||
export let stream: MediaStream|null;
|
||||
let volume = 0;
|
||||
|
||||
const NB_BARS = 5;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try{
|
||||
volume = parseInt((soundMeter.getVolume() / 100 * NB_BARS).toFixed(0));
|
||||
//console.log(volume);
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}, 100);
|
||||
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
<div class="sound-progress" class:active={display}>
|
||||
<span class:active={volume > 1}></span>
|
||||
<span class:active={volume > 2}></span>
|
||||
<span class:active={volume > 3}></span>
|
||||
<span class:active={volume > 4}></span>
|
||||
<span class:active={volume > 5}></span>
|
||||
</div>
|
52
front/src/Components/UI/AudioPlaying.svelte
Normal file
@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import megaphoneImg from "./images/megaphone.svg";
|
||||
import {soundPlayingStore} from "../../Stores/SoundPlayingStore";
|
||||
import {afterUpdate} from "svelte";
|
||||
|
||||
export let url: string;
|
||||
let audio: HTMLAudioElement;
|
||||
|
||||
function soundEnded() {
|
||||
soundPlayingStore.soundEnded();
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
audio.play();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="audio-playing" transition:fly="{{ x: 210, duration: 500 }}">
|
||||
<img src={megaphoneImg} alt="Audio playing" />
|
||||
<p>Audio message</p>
|
||||
<audio bind:this={audio} src={url} on:ended={soundEnded} >
|
||||
<track kind="captions">
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
/*audio html when audio message playing*/
|
||||
.audio-playing {
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
height: 54px;
|
||||
right: 0;
|
||||
top: 40px;
|
||||
transition: all 0.1s ease-out;
|
||||
background-color: black;
|
||||
border-radius: 30px 0 0 30px;
|
||||
display: inline-flex;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
background-color: #ffda01;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: white;
|
||||
margin-left: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
48
front/src/Components/UI/ErrorDialog.svelte
Normal file
@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import {errorStore} from "../../Stores/ErrorStore";
|
||||
|
||||
function close(): boolean {
|
||||
errorStore.clearMessages();
|
||||
return false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="error-div nes-container is-dark is-rounded" open>
|
||||
<p class="nes-text is-error title">Error</p>
|
||||
<div class="body">
|
||||
{#each $errorStore as error}
|
||||
<p>{error}</p>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="nes-btn is-error" on:click={close}>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
div.error-div {
|
||||
pointer-events: auto;
|
||||
margin-top: 10vh;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: max-content;
|
||||
max-width: 80vw;
|
||||
|
||||
.button-bar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body {
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.title {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
18
front/src/Components/UI/images/megaphone.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 451.7 512" style="enable-background:new 0 0 451.7 512;" xml:space="preserve">
|
||||
<path d="M436.9,212.6L237.2,12.9c-11.7-11.7-30.7-11.7-42.4,0s-11.7,30.7,0,42.4L394.5,255c11.5,11.9,30.5,12.2,42.4,0.7
|
||||
c11.9-11.5,12.2-30.5,0.7-42.4C437.4,213.1,437.2,212.8,436.9,212.6z"/>
|
||||
<path d="M179.5,83.1l-1.5,7.5c-10.4,53-36,103.4-70.6,144.3l109,108.3c40.7-34.9,90.2-61.5,143.1-72.3l7.5-1.5L179.5,83.1z"/>
|
||||
<path d="M87.4,257l-74.2,74.2c-17.6,17.6-17.6,46.1,0,63.6c0,0,0,0,0,0l42.4,42.4c17.6,17.6,46.1,17.6,63.6,0c0,0,0,0,0,0l74.2-74.2
|
||||
L87.4,257z M98,373.7c-6.1,5.6-15.6,5.3-21.2-0.8c-5.4-5.8-5.4-14.7,0-20.5l21.2-21.2c6-5.8,15.5-5.6,21.2,0.4
|
||||
c5.6,5.8,5.6,15,0,20.8L98,373.7z"/>
|
||||
<path d="M256.1,445.3l20.4-20.4c17.6-17.6,17.6-46.1,0-63.6l-15.1-15.2c-8.4,5.7-16.4,11.7-24.2,18.3l18.1,18.1
|
||||
c5.8,5.9,5.8,15.3,0,21.2l-20.7,20.8l-30.5-29.5l-42.4,42.4l68.1,65.9c11.7,11.7,30.7,11.7,42.4,0c11.7-11.7,11.7-30.7,0-42.4l0,0
|
||||
L256.1,445.3z"/>
|
||||
<path d="M316.7,0c-8.3,0-15,6.7-15,15v30c0,8.3,6.7,15,15,15c8.3,0,15-6.7,15-15V15C331.7,6.7,325,0,316.7,0z"/>
|
||||
<path d="M436.7,120h-30c-8.3,0-15,6.7-15,15s6.7,15,15,15h30c8.3,0,15-6.7,15-15S445,120,436.7,120z"/>
|
||||
<path d="M417.3,34.4c-5.9-5.9-15.4-5.9-21.2,0l-30,30c-6,5.8-6.1,15.3-0.4,21.2c5.8,6,15.3,6.1,21.2,0.4c0.1-0.1,0.2-0.2,0.4-0.4
|
||||
l30-30C423.2,49.7,423.2,40.2,417.3,34.4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
84
front/src/Components/VisitCard/VisitCard.svelte
Normal file
@ -0,0 +1,84 @@
|
||||
<script lang="typescript">
|
||||
import { fly } from 'svelte/transition';
|
||||
import {requestVisitCardsStore} from "../../Stores/GameStore";
|
||||
import {onMount} from "svelte";
|
||||
|
||||
export let visitCardUrl: string;
|
||||
let w = '500px';
|
||||
let h = '250px';
|
||||
let hidden = true;
|
||||
let cvIframe: HTMLIFrameElement;
|
||||
|
||||
function closeCard() {
|
||||
requestVisitCardsStore.set(null);
|
||||
}
|
||||
|
||||
function handleIframeMessage(message:any) {
|
||||
if (message.data.type === 'cvIframeSize') {
|
||||
w = (message.data.data.w) + 'px';
|
||||
h = (message.data.data.h) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
cvIframe.onload = () => hidden = false
|
||||
cvIframe.onerror = () => hidden = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin:auto;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.visitCard {
|
||||
pointer-events: all;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 200px;
|
||||
max-width: 80vw;
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
max-width: 80vw;
|
||||
overflow: hidden;
|
||||
|
||||
&.hidden {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<section class="visitCard" transition:fly="{{ y: -200, duration: 1000 }}" style="width: {w}">
|
||||
{#if hidden}
|
||||
<div class="loader"></div>
|
||||
{/if}
|
||||
<iframe title="visitCard" src={visitCardUrl} allow="clipboard-read; clipboard-write self {visitCardUrl}" style="width: {w}; height: {h}" class:hidden={hidden} bind:this={cvIframe}></iframe>
|
||||
{#if !hidden}
|
||||
<div class="buttonContainer">
|
||||
<button class="nes-btn is-popUpElement" on:click={closeCard}>Close</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</section>
|
||||
|
||||
<svelte:window on:message={handleIframeMessage}/>
|
41
front/src/Components/images/cinema-close.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
|
||||
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
|
||||
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
|
||||
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
|
||||
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
</g>
|
||||
</g>
|
||||
<rect x="134.8" y="-45.3" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 376.0669 224.8258)" class="st0" width="19.6" height="460.7"/>
|
||||
<path class="st0" d="M90.6,83.3c-0.2-2.2-1.3-8.9-6.7-14.9c-5.4-5.9-11.9-7.6-14.1-8.1C59.7,49.2,49.5,38,39.4,26.8
|
||||
c24.3-9.8,52-4.4,70.2,13.6c19.9,19.7,24.7,50.8,11.5,76.4C110.9,105.6,100.8,94.5,90.6,83.3z"/>
|
||||
<path class="st0" d="M10.1,51.6c9.4,10.2,18.8,20.4,28.2,30.6c-0.2,1.8-1.4,11.7,5.5,20.5c8.2,10.3,20.7,10.2,22.1,10.1
|
||||
c9.2,10.3,18.5,20.6,27.7,30.8c-4.8,2.3-24.6,11.2-48.3,4.1c-6-1.8-20.7-7.3-32.1-22C-0.3,108.1-0.2,89.1,0.1,83.4
|
||||
C0.8,68,6.8,56.8,10.1,51.6z"/>
|
||||
<g>
|
||||
<path class="st0" d="M243.4,178.2c0.1,24.5,0.2,49,0.2,73.5c-30.7-33.8-61.3-67.7-92-101.5c5.9,3.9,20.9,12.4,41.6,12.4
|
||||
c16,0,28.2-5.2,34.4-8.4c2.5,1.5,7,4.6,10.7,10.3C242,170,243,175.4,243.4,178.2z"/>
|
||||
<g>
|
||||
<path class="st0" d="M211.2,311C150.8,258.7,90.4,206.5,30,154.2c6.1,3.1,18.2,8.4,34.4,8.4c18.1,0,31.5-6.5,37.5-9.9
|
||||
c44.5,49,89.1,98.1,133.6,147.1c-1.8,2.1-5.3,5.5-10.6,8.1C219.2,310.6,214,311,211.2,311z"/>
|
||||
<path class="st0" d="M46.8,311C36,267.7,25.2,224.3,14.4,181c0.1-3.2,0.7-11.3,6.5-18.8c3.1-4.1,6.7-6.6,9.1-8
|
||||
C90.4,206.5,150.8,258.7,211.2,311C156.4,311,101.6,311,46.8,311z"/>
|
||||
<path class="st0" d="M14.4,278.6L14.4,278.6c0-32.5,0-65.1,0-97.6c10.8,43.3,21.6,86.7,32.4,130c-2.6,0-12.7-0.4-21.5-8.1
|
||||
C14.7,293.5,14.4,280.7,14.4,278.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
front/src/Components/images/logo.png
Normal file
After Width: | Height: | Size: 16 KiB |
27
front/src/Components/images/microphone-close.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<rect x="257" y="-47.9" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 643.9641 283.6469)" class="st0" width="20.4" height="628.3"/>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M333.6,250.3c-52.6-43.9-105.1-87.9-157.7-131.8c0-17.9,0-35.8,0-53.6c6.5-38.6,40.3-67,79.3-66.8
|
||||
c38.6,0.2,71.9,28.5,78.4,66.8C333.6,126.7,333.6,188.5,333.6,250.3z"/>
|
||||
<path class="st0" d="M322.6,279.9c-48.9-53.8-97.8-107.6-146.6-161.4l0,0c52.6,43.9,105.1,87.9,157.7,131.8
|
||||
c-0.2,1.6-0.5,3.3-0.9,5C330.5,265.2,326.6,273.5,322.6,279.9z"/>
|
||||
</g>
|
||||
<path class="st0" d="M292.5,308.1c-2.3,1.2-39.5,20.3-76.7-1c-36.4-20.8-39.4-61.2-39.6-64.1c-0.1-21-0.1-42.1-0.2-63.1
|
||||
C214.8,222.6,253.6,265.3,292.5,308.1z"/>
|
||||
</g>
|
||||
<path class="st0" d="M431.6,238.5c-0.9-8.4-8.5-14.4-16.6-13.5c-7.9,0.9-13.9,8.1-13.2,16.3c-0.1,13.3-2.2,34.6-12.6,57.9
|
||||
c-6.3,14.2-14,25.2-20.6,33.1c6.8,7.5,13.6,14.9,20.3,22.4c9.5-10.9,23.4-29.7,32.8-56.3C430.3,273.9,431.8,252.5,431.6,238.5z"/>
|
||||
<line class="st0" x1="354.5" y1="347.2" x2="374.6" y2="369.4"/>
|
||||
<path class="st0" d="M338.5,359.9c6.8,7.4,13.5,14.9,20.3,22.3c-52.6,37.6-121.5,43.7-179.2,15.8c-60.3-29.1-98.9-90.7-99.3-158.2
|
||||
c0-8.2,6.8-15,15-15s15,6.8,15,15c0.1,13.5,2.4,54.4,32.4,91.6c4.2,5.2,45.1,54.1,113.3,54.1C297,385.6,326.7,367.9,338.5,359.9z"/>
|
||||
<rect x="241" y="409.6" class="st0" width="29.9" height="102.3"/>
|
||||
<path class="st0" d="M304.2,511.9h-97.1c-8-0.4-14.3-7.1-14.3-15c0-8.1,6.7-14.9,15-15c31.7,0,63.4,0.1,95.1,0.1
|
||||
c8.9-0.6,16.3,6.5,16.3,14.9C319.2,504.8,312.6,511.7,304.2,511.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 937 B After Width: | Height: | Size: 937 B |
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 884 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,92 @@
|
||||
<script lang="typescript">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
const selectCharacterScene = game.scene.getScene(SelectCharacterSceneName) as SelectCharacterScene;
|
||||
|
||||
function selectLeft() {
|
||||
selectCharacterScene.moveToLeft();
|
||||
}
|
||||
|
||||
function selectRight() {
|
||||
selectCharacterScene.moveToRight();
|
||||
}
|
||||
|
||||
function cameraScene() {
|
||||
selectCharacterScene.nextSceneToCameraScene();
|
||||
}
|
||||
|
||||
function customizeScene() {
|
||||
selectCharacterScene.nextSceneToCustomizeScene();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<form class="selectCharacterScene">
|
||||
<section class="text-center">
|
||||
<h2>Select your WOKA</h2>
|
||||
<button class="selectCharacterButton selectCharacterButtonLeft nes-btn" on:click|preventDefault={ selectLeft }> < </button>
|
||||
<button class="selectCharacterButton selectCharacterButtonRight nes-btn" on:click|preventDefault={ selectRight }> > </button>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" class="selectCharacterSceneFormSubmit nes-btn is-primary" on:click|preventDefault={ cameraScene }>Continue</button>
|
||||
<button type="submit" class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn" on:click|preventDefault={ customizeScene }>Customize your WOKA</button>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
form.selectCharacterScene {
|
||||
font-family: "Press Start 2P";
|
||||
pointer-events: auto;
|
||||
color: #ebeeee;
|
||||
|
||||
section {
|
||||
margin: 10px;
|
||||
|
||||
&.action {
|
||||
text-align: center;
|
||||
margin-top: 55vh;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: "Press Start 2P";
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.selectCharacterButton {
|
||||
position: absolute;
|
||||
top: 33vh;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: "Press Start 2P";
|
||||
|
||||
&.selectCharacterButtonLeft {
|
||||
left: 33vw;
|
||||
}
|
||||
|
||||
&.selectCharacterButtonRight {
|
||||
right: 33vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
form.selectCharacterScene button.selectCharacterButtonLeft{
|
||||
left: 5vw;
|
||||
}
|
||||
form.selectCharacterScene button.selectCharacterButtonRight{
|
||||
right: 5vw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -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'));
|
||||
|
@ -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,
|
||||
|
@ -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[]) {
|
||||
}
|
||||
}
|
||||
|
@ -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<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (this.mapUrl !== undefined) {
|
||||
resolve(this.mapUrl);
|
||||
public async getMapDetail(): Promise<MapDetail> {
|
||||
return new Promise<MapDetail>((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);
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 ) );
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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<IAudioContext>|undefined;
|
||||
private dataArray: Uint8Array|undefined;
|
||||
private context: AudioContext|undefined;
|
||||
private source: MediaStreamAudioSourceNode|undefined;
|
||||
private context: IAudioContext|undefined;
|
||||
private source: IMediaStreamAudioSourceNode<IAudioContext>|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();
|
||||
};
|
||||
*/
|
||||
|
@ -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<Rectangle>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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<string, Sprite>;
|
||||
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<string>
|
||||
) {
|
||||
@ -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,
|
||||
|
20
front/src/Phaser/Entity/CustomizedCharacter.ts
Normal file
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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<string[]>,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
visitCardUrl: string|null,
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,6 @@ export interface AddPlayerInterface {
|
||||
name: string;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
position: PointInterface;
|
||||
visitCardUrl: string|null;
|
||||
companion: string|null;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<void> {
|
||||
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) {
|
||||
|
@ -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<void>;
|
||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||
private iframeSubscriptionList! : Array<Subscription>;
|
||||
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
|
||||
|
@ -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);
|
||||
|
@ -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 <img src="resources/objects/arrow_up.png"/>`;
|
||||
}
|
||||
|
||||
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 <img src="resources/objects/arrow_up.png"/>`;
|
||||
}
|
||||
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<Sprite> {
|
||||
const children: Array<Sprite> = new Array<Sprite>();
|
||||
private getContainerChildren(layerNumber: number, selectedItem: number): Array<string> {
|
||||
const children: Array<string> = new Array<string>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<MediaDeviceInfo>();
|
||||
private camerasList: MediaDeviceInfo[] = new Array<MediaDeviceInfo>();
|
||||
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<HTMLDivElement>('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<HTMLDivElement>('webRtcSetupNoVideo');
|
||||
img.style.display = 'none';
|
||||
|
||||
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('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<HTMLVideoElement>('myCamVideoSetup');
|
||||
let bounds = div.getBoundingClientRect();
|
||||
if (!div.srcObject) {
|
||||
div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('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<HTMLDivElement>('#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<HTMLDivElement>('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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 <this.playerModels.length; i++) {
|
||||
const playerResource = this.playerModels[i];
|
||||
|
||||
//check already exist texture
|
||||
if(this.players.find((c) => 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Phaser.Physics.Arcade.Sprite> = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
private companionModels: Array<CompanionResourceDescriptionInterface> = [];
|
||||
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;
|
||||
|
@ -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<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.helpCameraSettingsOpened = true;
|
||||
try{
|
||||
if(window.navigator.userAgent.includes('Firefox')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
|
||||
}else if(window.navigator.userAgent.includes('Chrome')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
|
||||
}
|
||||
}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;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export class Player extends Character {
|
||||
companion: string|null,
|
||||
companionTexturePromise?: Promise<string>
|
||||
) {
|
||||
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)
|
||||
|
@ -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<HTMLDivElement>('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.
|
||||
*/
|
||||
|
3
front/src/Stores/CustomCharacterStore.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { derived, writable, Writable } from "svelte/store";
|
||||
|
||||
export const customCharacterSceneVisibleStore = writable(false);
|
33
front/src/Stores/ErrorStore.ts
Normal file
@ -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<string[]>([]);
|
||||
|
||||
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();
|
8
front/src/Stores/Errors/BrowserTooOldError.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
8
front/src/Stores/Errors/WebviewOnOldIOS.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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<string|null>(null);
|
||||
|
3
front/src/Stores/HelpCameraSettingsStore.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const helpCameraSettingsVisibleStore = writable(false);
|
3
front/src/Stores/LoginSceneStore.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const loginSceneVisibleStore = writable(false);
|
@ -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<Readable<MediaStreamConstraints>, 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<MediaDeviceInfo[]>([], 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -126,7 +126,7 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
||||
let currentStreamPromise: Promise<MediaStream>;
|
||||
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<Readable<MediaStreamConstra
|
||||
return;
|
||||
} catch (e) {
|
||||
currentStream = null;
|
||||
requestedScreenSharingState.disableScreenSharing();
|
||||
console.info("Error. Unable to share screen.", e);
|
||||
set({
|
||||
type: 'error',
|
||||
@ -183,7 +184,7 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
|
||||
* A store containing whether the screen sharing button should be displayed or hidden.
|
||||
*/
|
||||
export const screenSharingAvailableStore = derived(peerStore, ($peerStore, set) => {
|
||||
if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) {
|
||||
if (!navigator.getDisplayMedia && (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia)) {
|
||||
set(false);
|
||||
return;
|
||||
}
|
||||
|
3
front/src/Stores/SelectCharacterStore.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { derived, writable, Writable } from "svelte/store";
|
||||
|
||||
export const selectCharacterSceneVisibleStore = writable(false);
|
3
front/src/Stores/SelectCompanionStore.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { derived, writable, Writable } from "svelte/store";
|
||||
|
||||
export const selectCompanionSceneVisibleStore = writable(false);
|
22
front/src/Stores/SoundPlayingStore.ts
Normal file
@ -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<string|null>(null);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
playSound: (url: string) => {
|
||||
set(url);
|
||||
},
|
||||
soundEnded: () => {
|
||||
set(null);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export const soundPlayingStore = createSoundPlayingStore();
|
@ -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<HTMLDivElement>(cowebsiteMainDomId);
|
||||
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(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<void> {
|
||||
|
12
front/src/WebRtc/DeviceUtils.ts
Normal file
@ -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)
|
||||
}
|
@ -171,6 +171,8 @@ export class DiscussionManager {
|
||||
const date = new Date();
|
||||
if(isMe){
|
||||
name = 'Me';
|
||||
} else {
|
||||
name = HtmlUtils.escapeHtml(name);
|
||||
}
|
||||
pMessage.innerHTML = `<span style="font-weight: bold">${name}</span>
|
||||
<span style="color:#bac2cc;display:inline-block;font-size:12px;">
|
||||
|
@ -35,7 +35,12 @@ export class HtmlUtils {
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
text = HtmlUtils.escapeHtml(text);
|
||||
return text.replace(urlRegex, (url: string) => {
|
||||
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.target = "_blank";
|
||||
const text = document.createTextNode(url);
|
||||
link.appendChild(text);
|
||||
return link.outerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
|