diff --git a/CHANGELOG.md b/CHANGELOG.md index d9afd71e..b9b0a926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ - Mouse wheel support to zoom in / out - Pinch support on mobile to zoom in / out - Improved virtual joystick size (adapts to the zoom level) +- Redesigned intermediate scenes + - Redesigned Select Companion scene + - Redesigned Enter Your Name scene + - Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use - New scripting API features: - Use `WA.loadSound(): Sound` to load / play / stop a sound diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index 570dd995..3ce6789a 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -108,17 +108,10 @@ -
- -
-
- - -
diff --git a/front/dist/resources/html/CustomCharacterScene.html b/front/dist/resources/html/CustomCharacterScene.html deleted file mode 100644 index 0bc050ea..00000000 --- a/front/dist/resources/html/CustomCharacterScene.html +++ /dev/null @@ -1,160 +0,0 @@ - - - diff --git a/front/dist/resources/html/EnableCameraScene.html b/front/dist/resources/html/EnableCameraScene.html deleted file mode 100644 index 2dda6cc1..00000000 --- a/front/dist/resources/html/EnableCameraScene.html +++ /dev/null @@ -1,129 +0,0 @@ - - - diff --git a/front/dist/resources/html/SelectCompanionScene.html b/front/dist/resources/html/SelectCompanionScene.html deleted file mode 100644 index cffa7880..00000000 --- a/front/dist/resources/html/SelectCompanionScene.html +++ /dev/null @@ -1,134 +0,0 @@ - - - diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html index 45bfbd66..4aab7ab3 100644 --- a/front/dist/resources/html/gameMenu.html +++ b/front/dist/resources/html/gameMenu.html @@ -1,7 +1,4 @@ - - diff --git a/front/dist/resources/html/loginScene.html b/front/dist/resources/html/loginScene.html deleted file mode 100644 index 38e798e5..00000000 --- a/front/dist/resources/html/loginScene.html +++ /dev/null @@ -1,120 +0,0 @@ - - - diff --git a/front/dist/resources/html/selectCharacterScene.html b/front/dist/resources/html/selectCharacterScene.html deleted file mode 100644 index c51731df..00000000 --- a/front/dist/resources/html/selectCharacterScene.html +++ /dev/null @@ -1,142 +0,0 @@ - - - diff --git a/front/dist/resources/html/warningContainer.html b/front/dist/resources/html/warningContainer.html index 4989c49d..832ac4da 100644 --- a/front/dist/resources/html/warningContainer.html +++ b/front/dist/resources/html/warningContainer.html @@ -1,7 +1,4 @@ + + + + + + + + + + + + + + diff --git a/front/dist/resources/objects/arrow_down.png b/front/dist/resources/objects/arrow_down.png new file mode 100644 index 00000000..c89fb5f3 Binary files /dev/null and b/front/dist/resources/objects/arrow_down.png differ diff --git a/front/dist/resources/objects/arrow_up.png b/front/dist/resources/objects/arrow_up.png index b9f81ebc..28c15bc2 100644 Binary files a/front/dist/resources/objects/arrow_up.png and b/front/dist/resources/objects/arrow_up.png differ diff --git a/front/dist/resources/objects/arrow_up_black.png b/front/dist/resources/objects/arrow_up_black.png new file mode 100644 index 00000000..c0a732cc Binary files /dev/null and b/front/dist/resources/objects/arrow_up_black.png differ diff --git a/front/package.json b/front/package.json index cc774760..537fd976 100644 --- a/front/package.json +++ b/front/package.json @@ -48,7 +48,8 @@ "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", diff --git a/front/src/Administration/GlobalMessageManager.ts b/front/src/Administration/GlobalMessageManager.ts index 95364e4c..1500a6ec 100644 --- a/front/src/Administration/GlobalMessageManager.ts +++ b/front/src/Administration/GlobalMessageManager.ts @@ -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('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){ diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 9b072e8d..97cb5071 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -1,18 +1,76 @@
+ {#if $loginSceneVisibleStore} +
+ +
+ {/if} + {#if $selectCharacterSceneVisibleStore} +
+ +
+ {/if} + {#if $customCharacterSceneVisibleStore} +
+ +
+ {/if} + {#if $selectCompanionSceneVisibleStore} +
+ +
+ {/if} + {#if $enableCameraSceneVisibilityStore} +
+ +
+ {/if} + {#if $soundPlayingStore} +
+ +
+ {/if} + + {#if $gameOverlayVisibilityStore} - - - +
+ + +
+ {/if} + {#if $helpCameraSettingsVisibleStore} +
+ +
{/if}
diff --git a/front/src/Components/CameraControls.svelte b/front/src/Components/CameraControls.svelte index 98dfa4c5..5c17a9fe 100644 --- a/front/src/Components/CameraControls.svelte +++ b/front/src/Components/CameraControls.svelte @@ -34,26 +34,28 @@ -
-
- {#if $requestedScreenSharingState} - Start screen sharing - {:else} - Stop screen sharing - {/if} -
-
- {#if $requestedCameraState} - Turn on webcam - {:else} - Turn off webcam - {/if} -
-
- {#if $requestedMicrophoneState} - Turn on microphone - {:else} - Turn off microphone - {/if} +
+
+
+ {#if $requestedScreenSharingState} + Start screen sharing + {:else} + Stop screen sharing + {/if} +
+
+ {#if $requestedCameraState} + Turn on webcam + {:else} + Turn off webcam + {/if} +
+
+ {#if $requestedMicrophoneState} + Turn on microphone + {:else} + Turn off microphone + {/if} +
diff --git a/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte new file mode 100644 index 00000000..f9e3a66b --- /dev/null +++ b/front/src/Components/CustomCharacterScene/CustomCharacterScene.svelte @@ -0,0 +1,119 @@ + + +
+
+

Customize your WOKA

+
+
+ + +
+
+ {#if activeRow === 0} + + {/if} + {#if activeRow !== 0} + + {/if} + {#if activeRow === 5} + + {/if} + {#if activeRow !== 5} + + {/if} +
+
+ + \ No newline at end of file diff --git a/front/src/Components/EnableCamera/EnableCameraScene.svelte b/front/src/Components/EnableCamera/EnableCameraScene.svelte new file mode 100644 index 00000000..537e8bdb --- /dev/null +++ b/front/src/Components/EnableCamera/EnableCameraScene.svelte @@ -0,0 +1,217 @@ + + +
+
+

Turn on your camera and microphone

+
+ {#if $localStreamStore.stream} + + {:else } +
+ +
+ {/if} + + +
+ + {#if $cameraListStore.length > 1 } +
+ Camera +
+ +
+
+ {/if} + + {#if $microphoneListStore.length > 1 } +
+ Microphone +
+ +
+
+ {/if} + +
+
+ +
+
+ + + diff --git a/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte new file mode 100644 index 00000000..84352ebb --- /dev/null +++ b/front/src/Components/EnableCamera/HorizontalSoundMeterWidget.svelte @@ -0,0 +1,82 @@ + + + +
+ {#each [...Array(NB_BARS).keys()] as i} +
+ {/each} +
+ + diff --git a/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte new file mode 100644 index 00000000..8f4de785 --- /dev/null +++ b/front/src/Components/HelpCameraSettings/HelpCameraSettingsPopup.svelte @@ -0,0 +1,73 @@ + + +
+
+

Camera / Microphone access needed

+

Permission denied

+

You must allow camera and microphone access in your browser.

+

+ {#if isFirefox } +

Please click the "Remember this decision" checkbox, if you don't want Firefox to keep asking you the authorization.

+ + {:else if isChrome && !isAndroid } + + {/if} +

+
+
+ + +
+
+ + + diff --git a/front/dist/resources/objects/help-setting-camera-permission-chrome.png b/front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-chrome.png similarity index 100% rename from front/dist/resources/objects/help-setting-camera-permission-chrome.png rename to front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-chrome.png diff --git a/front/dist/resources/objects/help-setting-camera-permission-firefox.png b/front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-firefox.png similarity index 100% rename from front/dist/resources/objects/help-setting-camera-permission-firefox.png rename to front/src/Components/HelpCameraSettings/images/help-setting-camera-permission-firefox.png diff --git a/front/src/Components/Login/LoginScene.svelte b/front/src/Components/Login/LoginScene.svelte new file mode 100644 index 00000000..dbe3daaf --- /dev/null +++ b/front/src/Components/Login/LoginScene.svelte @@ -0,0 +1,123 @@ + + +
+
+ WorkAdventure logo +
+
+

Enter your name

+
+ {startValidating = true}} class:is-error={name.trim() === '' && startValidating} /> +
+ {#if name.trim() === '' && startValidating } +

The name is empty

+ {/if} +
+ + {#if DISPLAY_TERMS_OF_USE} +
+

By continuing, you are agreeing our terms of use, privacy policy and cookie policy.

+
+ {/if} +
+ +
+
+ + diff --git a/front/src/Components/MyCamera.svelte b/front/src/Components/MyCamera.svelte index 6e1e50ef..3ff88d89 100644 --- a/front/src/Components/MyCamera.svelte +++ b/front/src/Components/MyCamera.svelte @@ -38,7 +38,7 @@
- + diff --git a/front/src/Components/SelectCompanion/SelectCompanionScene.svelte b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte new file mode 100644 index 00000000..205a18ee --- /dev/null +++ b/front/src/Components/SelectCompanion/SelectCompanionScene.svelte @@ -0,0 +1,87 @@ + + +
+
+

Select your companion

+ + +
+
+ + +
+
+ + diff --git a/front/src/Components/SoundMeterWidget.svelte b/front/src/Components/SoundMeterWidget.svelte index cff6be86..40c467b1 100644 --- a/front/src/Components/SoundMeterWidget.svelte +++ b/front/src/Components/SoundMeterWidget.svelte @@ -1,33 +1,50 @@ -
0}> +
1}> 2}> 3}> diff --git a/front/src/Components/UI/AudioPlaying.svelte b/front/src/Components/UI/AudioPlaying.svelte new file mode 100644 index 00000000..8889ac52 --- /dev/null +++ b/front/src/Components/UI/AudioPlaying.svelte @@ -0,0 +1,52 @@ + + +
+ Audio playing +

Audio message

+ +
+ + diff --git a/front/src/Components/UI/images/megaphone.svg b/front/src/Components/UI/images/megaphone.svg new file mode 100644 index 00000000..708f860c --- /dev/null +++ b/front/src/Components/UI/images/megaphone.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/front/src/Components/images/logo.png b/front/src/Components/images/logo.png new file mode 100644 index 00000000..f4440ad5 Binary files /dev/null and b/front/src/Components/images/logo.png differ diff --git a/front/src/Components/selectCharacter/SelectCharacterScene.svelte b/front/src/Components/selectCharacter/SelectCharacterScene.svelte new file mode 100644 index 00000000..e227771c --- /dev/null +++ b/front/src/Components/selectCharacter/SelectCharacterScene.svelte @@ -0,0 +1,92 @@ + + +
+
+

Select your WOKA

+ + +
+
+ + +
+
+ + \ No newline at end of file diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 7bac4ba0..e09547f3 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -4,7 +4,7 @@ import {RoomConnection} from "./RoomConnection"; import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels"; import {GameConnexionTypes, urlManager} from "../Url/UrlManager"; import {localUserStore} from "./LocalUserStore"; -import {LocalUser} from "./LocalUser"; +import {CharacterTexture, LocalUser} from "./LocalUser"; import {Room} from "./Room"; @@ -46,8 +46,8 @@ class ConnectionManager { urlManager.pushRoomIdToUrl(room); return Promise.resolve(room); } else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) { - const localUser = localUserStore.getLocalUser(); + let localUser = localUserStore.getLocalUser(); if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) { this.localUser = localUser; try { @@ -57,16 +57,42 @@ class ConnectionManager { console.error('JWT token invalid. Did it expire? Login anonymously instead.'); await this.anonymousLogin(); } - } else { + }else{ await this.anonymousLogin(); } - let roomId: string + + localUser = localUserStore.getLocalUser(); + if(!localUser){ + throw "Error to store local user data"; + } + + let roomId: string; if (connexionType === GameConnexionTypes.empty) { roomId = START_ROOM_URL; } else { roomId = window.location.pathname + window.location.search + window.location.hash; } - return Promise.resolve(new Room(roomId)); + + //get detail map for anonymous login and set texture in local storage + const room = new Room(roomId); + const mapDetail = await room.getMapDetail(); + if(mapDetail.textures != undefined && mapDetail.textures.length > 0) { + //check if texture was changed + if(localUser.textures.length === 0){ + localUser.textures = mapDetail.textures; + }else{ + mapDetail.textures.forEach((newTexture) => { + const alreadyExistTexture = localUser?.textures.find((c) => newTexture.id === c.id); + if(localUser?.textures.findIndex((c) => newTexture.id === c.id) !== -1){ + return; + } + localUser?.textures.push(newTexture) + }); + } + this.localUser = localUser; + localUserStore.saveUser(localUser); + } + return Promise.resolve(room); } return Promise.reject(new Error('Invalid URL')); diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index 43b184cf..c877d119 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -10,7 +10,7 @@ export interface CharacterTexture { export const maxUserNameLength: number = MAX_USERNAME_LENGTH; export function isUserNameValid(value: unknown): boolean { - return typeof value === "string" && value.length > 0 && value.length < maxUserNameLength && value.indexOf(' ') === -1; + return typeof value === "string" && value.length > 0 && value.length <= maxUserNameLength && value.indexOf(' ') === -1; } export function areCharacterLayersValid(value: string[] | null): boolean { @@ -24,6 +24,6 @@ export function areCharacterLayersValid(value: string[] | null): boolean { } export class LocalUser { - constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) { + constructor(public readonly uuid:string, public readonly jwtToken: string, public textures: CharacterTexture[]) { } } diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 05d94440..3ae8d2ed 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,10 +1,17 @@ import Axios from "axios"; import {PUSHER_URL} from "../Enum/EnvironmentVariable"; +import type {CharacterTexture} from "./LocalUser"; + +export class MapDetail{ + constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) { + } +} export class Room { public readonly id: string; public readonly isPublic: boolean; private mapUrl: string|undefined; + private textures: CharacterTexture[]|undefined; private instance: string|undefined; private _search: URLSearchParams; @@ -50,10 +57,10 @@ export class Room { return {roomId, hash} } - public async getMapUrl(): Promise { - return new Promise((resolve, reject) => { - if (this.mapUrl !== undefined) { - resolve(this.mapUrl); + public async getMapDetail(): Promise { + return new Promise((resolve, reject) => { + if (this.mapUrl !== undefined && this.textures != undefined) { + resolve(new MapDetail(this.mapUrl, this.textures)); return; } @@ -61,7 +68,7 @@ export class Room { const match = /_\/[^/]+\/(.+)/.exec(this.id); if (!match) throw new Error('Could not extract url from "'+this.id+'"'); this.mapUrl = window.location.protocol+'//'+match[1]; - resolve(this.mapUrl); + resolve(new MapDetail(this.mapUrl, this.textures)); return; } else { // We have a private ID, we need to query the map URL from the server. @@ -71,7 +78,7 @@ export class Room { params: urlParts }).then(({data}) => { console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); - resolve(data.mapUrl); + resolve(data); return; }).catch((reason) => { reject(reason); diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 47fe0a30..73f6427c 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -12,8 +12,9 @@ const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true"; const POSITION_DELAY = 200; // Wait 200ms between sending position events const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player -const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '8'); -const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4'); +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 ) ); @@ -31,7 +32,5 @@ export { TURN_USER, TURN_PASSWORD, JITSI_URL, - JITSI_PRIVATE_MODE, - MAX_USERNAME_LENGTH, - MAX_PER_GROUP + JITSI_PRIVATE_MODE } diff --git a/front/src/Phaser/Components/MobileJoystick.ts b/front/src/Phaser/Components/MobileJoystick.ts index 46efcbc2..b3fc021b 100644 --- a/front/src/Phaser/Components/MobileJoystick.ts +++ b/front/src/Phaser/Components/MobileJoystick.ts @@ -28,13 +28,13 @@ export class MobileJoystick extends VirtualJoystick { this.visible = false; this.enable = false; - this.scene.input.on('pointerdown', (pointer: { x: number; y: number; wasTouch: boolean; event: TouchEvent }) => { + this.scene.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => { if (!pointer.wasTouch) { return; } // Let's only display the joystick if there is one finger on the screen - if (pointer.event.touches.length === 1) { + if ((pointer.event as TouchEvent).touches.length === 1) { this.x = pointer.x; this.y = pointer.y; this.visible = true; diff --git a/front/src/Phaser/Components/SoundMeter.ts b/front/src/Phaser/Components/SoundMeter.ts index 1d6f7eba..53802d31 100644 --- a/front/src/Phaser/Components/SoundMeter.ts +++ b/front/src/Phaser/Components/SoundMeter.ts @@ -1,3 +1,5 @@ +import type {IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode} from 'standardized-audio-context'; + /** * Class to measure the sound volume of a media stream */ @@ -5,10 +7,10 @@ export class SoundMeter { private instant: number; private clip: number; //private script: ScriptProcessorNode; - private analyser: AnalyserNode|undefined; + private analyser: IAnalyserNode|undefined; private dataArray: Uint8Array|undefined; - private context: AudioContext|undefined; - private source: MediaStreamAudioSourceNode|undefined; + private context: IAudioContext|undefined; + private source: IMediaStreamAudioSourceNode|undefined; constructor() { this.instant = 0.0; @@ -16,7 +18,7 @@ export class SoundMeter { //this.script = context.createScriptProcessor(2048, 1, 1); } - private init(context: AudioContext) { + private init(context: IAudioContext) { this.context = context; this.analyser = this.context.createAnalyser(); @@ -25,8 +27,12 @@ export class SoundMeter { this.dataArray = new Uint8Array(bufferLength); } - public connectToSource(stream: MediaStream, context: AudioContext): void + public connectToSource(stream: MediaStream, context: IAudioContext): void { + if (this.source !== undefined) { + this.stop(); + } + this.init(context); this.source = this.context?.createMediaStreamSource(stream); @@ -81,56 +87,3 @@ export class SoundMeter { } - -// Meter class that generates a number correlated to audio volume. -// The meter class itself displays nothing, but it makes the -// instantaneous and time-decaying volumes available for inspection. -// It also reports on the fraction of samples that were at or near -// the top of the measurement range. -/*function SoundMeter(context) { - this.context = context; - this.instant = 0.0; - this.slow = 0.0; - this.clip = 0.0; - this.script = context.createScriptProcessor(2048, 1, 1); - const that = this; - this.script.onaudioprocess = function(event) { - const input = event.inputBuffer.getChannelData(0); - let i; - let sum = 0.0; - let clipcount = 0; - for (i = 0; i < input.length; ++i) { - sum += input[i] * input[i]; - if (Math.abs(input[i]) > 0.99) { - clipcount += 1; - } - } - that.instant = Math.sqrt(sum / input.length); - that.slow = 0.95 * that.slow + 0.05 * that.instant; - that.clip = clipcount / input.length; - }; -} - -SoundMeter.prototype.connectToSource = function(stream, callback) { - console.log('SoundMeter connecting'); - try { - this.mic = this.context.createMediaStreamSource(stream); - this.mic.connect(this.script); - // necessary to make sample run, but should not be. - this.script.connect(this.context.destination); - if (typeof callback !== 'undefined') { - callback(null); - } - } catch (e) { - console.error(e); - if (typeof callback !== 'undefined') { - callback(e); - } - } -}; - -SoundMeter.prototype.stop = function() { - this.mic.disconnect(); - this.script.disconnect(); -}; -*/ diff --git a/front/src/Phaser/Components/SoundMeterSprite.ts b/front/src/Phaser/Components/SoundMeterSprite.ts deleted file mode 100644 index 582617f9..00000000 --- a/front/src/Phaser/Components/SoundMeterSprite.ts +++ /dev/null @@ -1,44 +0,0 @@ -import Container = Phaser.GameObjects.Container; -import type {Scene} from "phaser"; -import GameObject = Phaser.GameObjects.GameObject; -import Rectangle = Phaser.GameObjects.Rectangle; - - -export class SoundMeterSprite extends Container { - private rectangles: Rectangle[] = new Array(); - private static readonly NB_BARS = 20; - - constructor(scene: Scene, x?: number, y?: number, children?: GameObject[]) { - super(scene, x, y, children); - - for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) { - const rectangle = new Rectangle(scene, i * 13, 0, 10, 20, (Math.round(255 - i * 255 / SoundMeterSprite.NB_BARS) << 8) + (Math.round(i * 255 / SoundMeterSprite.NB_BARS) << 16)); - this.add(rectangle); - this.rectangles.push(rectangle); - } - } - - /** - * A number between 0 and 100 - * - * @param volume - */ - public setVolume(volume: number): void { - - const normalizedVolume = volume / 100 * SoundMeterSprite.NB_BARS; - for (let i = 0; i < SoundMeterSprite.NB_BARS; i++) { - if (normalizedVolume < i) { - this.rectangles[i].alpha = 0.5; - } else { - this.rectangles[i].alpha = 1; - } - } - } - - public getWidth(): number { - return SoundMeterSprite.NB_BARS * 13; - } - - - -} diff --git a/front/src/Phaser/Game/DirtyScene.ts b/front/src/Phaser/Game/DirtyScene.ts index 2e94aa66..3e1f3cdf 100644 --- a/front/src/Phaser/Game/DirtyScene.ts +++ b/front/src/Phaser/Game/DirtyScene.ts @@ -69,7 +69,7 @@ export abstract class DirtyScene extends ResizableScene { return this.dirty || this.objectListChanged; } - public onResize(ev: UIEvent): void { + public onResize(): void { this.objectListChanged = true; } } diff --git a/front/src/Phaser/Game/Game.ts b/front/src/Phaser/Game/Game.ts index 01aecf9f..e267e80a 100644 --- a/front/src/Phaser/Game/Game.ts +++ b/front/src/Phaser/Game/Game.ts @@ -21,14 +21,22 @@ export class Game extends Phaser.Game { constructor(GameConfig: Phaser.Types.Core.GameConfig) { super(GameConfig); - window.addEventListener('resize', (event) => { + this.scale.on(Phaser.Scale.Events.RESIZE, () => { + for (const scene of this.scene.getScenes(true)) { + if (scene instanceof ResizableScene) { + scene.onResize(); + } + } + }) + + /*window.addEventListener('resize', (event) => { // Let's trigger the onResize method of any active scene that is a ResizableScene for (const scene of this.scene.getScenes(true)) { if (scene instanceof ResizableScene) { scene.onResize(event); } } - }); + });*/ } public step(time: number, delta: number) diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 2a1d3d8a..cd2575c0 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -2,11 +2,13 @@ import {GameScene} from "./GameScene"; import {connectionManager} from "../../Connexion/ConnectionManager"; import type {Room} from "../../Connexion/Room"; import {MenuScene, MenuSceneName} from "../Menu/MenuScene"; -import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene"; import {LoginSceneName} from "../Login/LoginScene"; import {SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import {EnableCameraSceneName} from "../Login/EnableCameraScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; +import {get} from "svelte/store"; +import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore"; +import {helpCameraSettingsVisibleStore} from "../../Stores/HelpCameraSettingsStore"; export interface HasMovedEvent { direction: string; @@ -76,11 +78,11 @@ export class GameManager { public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise { const roomID = room.id; - const mapUrl = await room.getMapUrl(); + const mapDetail = await room.getMapDetail(); const gameIndex = scenePlugin.getIndex(roomID); if(gameIndex === -1){ - const game : Phaser.Scene = new GameScene(room, mapUrl); + const game : Phaser.Scene = new GameScene(room, mapDetail.mapUrl); scenePlugin.add(roomID, game, false); } } @@ -89,7 +91,11 @@ export class GameManager { console.log('starting '+ (this.currentGameSceneName || this.startRoom.id)) scenePlugin.start(this.currentGameSceneName || this.startRoom.id); scenePlugin.launch(MenuSceneName); - scenePlugin.launch(HelpCameraSettingsSceneName);//700 + + if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){ + helpCameraSettingsVisibleStore.set(true); + localUserStore.setHelpCameraSettingsShown(); + } } public gameSceneIsCreated(scene: GameScene) { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d227a7f0..f16ee988 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -253,11 +253,6 @@ 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.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) { @@ -306,7 +301,7 @@ 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({ + (this.load as any).rexWebFont({ custom: { families: ['Press Start 2P'], urls: ['/resources/fonts/fonts.css'], @@ -1052,6 +1047,8 @@ ${escapedMessage} this.pinchManager?.destroy(); this.emoteManager.destroy(); + mediaManager.hideGameOverlay(); + for(const iframeEvents of this.iframeSubscriptionList){ iframeEvents.unsubscribe(); } @@ -1252,7 +1249,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) => { @@ -1558,8 +1558,8 @@ ${escapedMessage} this.connection?.emitActionableEvent(itemId, eventName, state, parameters); } - public onResize(ev: UIEvent): void { - super.onResize(ev); + public onResize(): void { + super.onResize(); this.reposition(); // Send new viewport to server diff --git a/front/src/Phaser/Game/SoundManager.ts b/front/src/Phaser/Game/SoundManager.ts index f0210494..47614fca 100644 --- a/front/src/Phaser/Game/SoundManager.ts +++ b/front/src/Phaser/Game/SoundManager.ts @@ -17,7 +17,9 @@ class SoundManager { return res(sound); } loadPlugin.audio(soundUrl, soundUrl); - loadPlugin.once('filecomplete-audio-' + soundUrl, () => res(soundManager.add(soundUrl))); + loadPlugin.once('filecomplete-audio-' + soundUrl, () => { + res(soundManager.add(soundUrl)); + }); loadPlugin.start(); }); this.soundPromises.set(soundUrl,soundPromise); diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 8b9a9a7a..3d85cdd5 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -11,6 +11,10 @@ import {AbstractCharacterScene} from "./AbstractCharacterScene"; import {areCharacterLayersValid} from "../../Connexion/LocalUser"; import { MenuScene } from "../Menu/MenuScene"; import { SelectCharacterSceneName } from "./SelectCharacterScene"; +import {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore"; +import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {isMobile} from "../../Enum/EnvironmentVariable"; export const CustomizeSceneName = "CustomizeScene"; @@ -22,10 +26,10 @@ export class CustomizeScene extends AbstractCharacterScene { private selectedLayers: number[] = [0]; private containersRow: Container[][] = []; - private activeRow:number = 0; + public activeRow:number = 0; private layers: BodyResourceDescriptionInterface[][] = []; - private customizeSceneElement!: Phaser.GameObjects.DOMElement; + protected lazyloadingAttempt = true; //permit to update texture loaded after renderer constructor() { super({ @@ -36,7 +40,6 @@ 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 +47,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); @@ -116,7 +104,7 @@ export class CustomizeScene extends AbstractCharacterScene { this.onResize(); } - private moveCursorHorizontally(index: number): void { + public moveCursorHorizontally(index: number): void { this.selectedLayers[this.activeRow] += index; if (this.selectedLayers[this.activeRow] < 0) { this.selectedLayers[this.activeRow] = 0 @@ -128,27 +116,7 @@ export class CustomizeScene extends AbstractCharacterScene { this.saveInLocalStorage(); } - private moveCursorVertically(index:number): void { - - if(index === -1 && this.activeRow === 5){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement; - button.innerHTML = `Next `; - } - - if(index === 1 && this.activeRow === 4){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormSubmit') as HTMLButtonElement; - button.innerText = 'Finish'; - } - - if(index === -1 && this.activeRow === 1){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement; - button.innerText = `Return`; - } - - if(index === 1 && this.activeRow === 0){ - const button = this.customizeSceneElement.getChildByID('customizeSceneFormBack') as HTMLButtonElement; - button.innerHTML = `Back `; - } + public moveCursorVertically(index:number): void { this.activeRow += index; if (this.activeRow < 0) { @@ -262,6 +230,10 @@ export class CustomizeScene extends AbstractCharacterScene { update(time: number, delta: number): void { + if(this.lazyloadingAttempt){ + this.moveLayers(); + this.lazyloadingAttempt = false; + } } public onResize(): void { @@ -269,8 +241,6 @@ export class CustomizeScene extends AbstractCharacterScene { 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(){ @@ -288,12 +258,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(){ this.scene.sleep(CustomizeSceneName); + waScaleManager.restoreZoom(); this.scene.run(SelectCharacterSceneName); + customCharacterSceneVisibleStore.set(false); } } diff --git a/front/src/Phaser/Login/EnableCameraScene.ts b/front/src/Phaser/Login/EnableCameraScene.ts index 6002da7b..ba27cd07 100644 --- a/front/src/Phaser/Login/EnableCameraScene.ts +++ b/front/src/Phaser/Login/EnableCameraScene.ts @@ -3,7 +3,6 @@ import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import {mediaManager} from "../../WebRtc/MediaManager"; import {SoundMeter} from "../Components/SoundMeter"; -import {SoundMeterSprite} from "../Components/SoundMeterSprite"; import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; @@ -11,304 +10,41 @@ import Zone = Phaser.GameObjects.Zone; import { MenuScene } from "../Menu/MenuScene"; import {ResizableScene} from "./ResizableScene"; import { - audioConstraintStore, enableCameraSceneVisibilityStore, - localStreamStore, - mediaStreamConstraintsStore, - videoConstraintStore } from "../../Stores/MediaStore"; -import type {Unsubscriber} from "svelte/store"; export const EnableCameraSceneName = "EnableCameraScene"; -enum LoginTextures { - playButton = "play_button", - icon = "icon", - mainFont = "main_font", - arrowRight = "arrow_right", - arrowUp = "arrow_up" -} - -const enableCameraSceneKey = 'enableCameraScene'; export class EnableCameraScene extends ResizableScene { - private textField!: TextField; - private cameraNameField!: TextField; - private arrowLeft!: Image; - private arrowRight!: Image; - private arrowDown!: Image; - private arrowUp!: Image; - private microphonesList: MediaDeviceInfo[] = new Array(); - private camerasList: MediaDeviceInfo[] = new Array(); - private cameraSelected: number = 0; - private microphoneSelected: number = 0; - private soundMeter: SoundMeter; - private soundMeterSprite!: SoundMeterSprite; - private microphoneNameField!: TextField; - - private enableCameraSceneElement!: Phaser.GameObjects.DOMElement; - - private mobileTapZone!: Zone; - private localStreamStoreUnsubscriber!: Unsubscriber; constructor() { super({ key: EnableCameraSceneName }); - this.soundMeter = new SoundMeter(); } preload() { - - this.load.html(enableCameraSceneKey, 'resources/html/EnableCameraScene.html'); - - this.load.image(LoginTextures.playButton, "resources/objects/play_button.png"); - this.load.image(LoginTextures.arrowRight, "resources/objects/arrow_right.png"); - this.load.image(LoginTextures.arrowUp, "resources/objects/arrow_up.png"); - // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap - this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); } create() { - this.enableCameraSceneElement = this.add.dom(-1000, 0).createFromCache(enableCameraSceneKey); - this.centerXDomElement(this.enableCameraSceneElement, 300); - - MenuScene.revealMenusAfterInit(this.enableCameraSceneElement, enableCameraSceneKey); - - const continuingButton = this.enableCameraSceneElement.getChildByID('enableCameraSceneFormSubmit') as HTMLButtonElement; - continuingButton.addEventListener('click', (e) => { - e.preventDefault(); - this.login(); - }); - - if (touchScreenManager.supportTouchScreen) { - new PinchManager(this); - } - //this.scale.setZoom(ZOOM_LEVEL); - //Phaser.Display.Align.In.BottomCenter(this.pressReturnField, zone); - - /* FIX ME */ - this.textField = new TextField(this, this.scale.width / 2, 20, ''); - - // For mobile purposes - we need a big enough touchable area. - this.mobileTapZone = this.add.zone(this.scale.width / 2,this.scale.height - 30,200,50) - .setInteractive().on("pointerdown", () => { - this.login(); - }); - - this.cameraNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 60, ''); - - this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, ''); - - this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight); - this.arrowRight.setVisible(false); - this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this)); - this.add.existing(this.arrowRight); - - this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight); - this.arrowLeft.setVisible(false); - this.arrowLeft.flipX = true; - this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this)); - this.add.existing(this.arrowLeft); - - this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp); - this.arrowUp.setVisible(false); - this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this)); - this.add.existing(this.arrowUp); - - this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp); - this.arrowDown.setVisible(false); - this.arrowDown.flipY = true; - this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this)); - this.add.existing(this.arrowDown); - this.input.keyboard.on('keyup-ENTER', () => { this.login(); }); - HtmlUtils.getElementByIdOrFail('webRtcSetup').classList.add('active'); - - this.localStreamStoreUnsubscriber = localStreamStore.subscribe((result) => { - if (result.type === 'error') { - // TODO: proper handling of the error - throw result.error; - } - - this.getDevices(); - if (result.stream !== null) { - this.setupStream(result.stream); - } - }); - /*const mediaPromise = mediaManager.getCamera(); - mediaPromise.then(this.getDevices.bind(this)); - mediaPromise.then(this.setupStream.bind(this));*/ - - this.input.keyboard.on('keydown-RIGHT', this.nextCam.bind(this)); - this.input.keyboard.on('keydown-LEFT', this.previousCam.bind(this)); - this.input.keyboard.on('keydown-DOWN', this.nextMic.bind(this)); - this.input.keyboard.on('keydown-UP', this.previousMic.bind(this)); - - this.soundMeterSprite = new SoundMeterSprite(this, 50, 50); - this.soundMeterSprite.setVisible(false); - this.add.existing(this.soundMeterSprite); - - this.onResize(); - enableCameraSceneVisibilityStore.showEnableCameraScene(); } - private previousCam(): void { - if (this.cameraSelected === 0 || this.camerasList.length === 0) { - return; - } - this.cameraSelected--; - videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId); - - //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this)); - } - - private nextCam(): void { - if (this.cameraSelected === this.camerasList.length - 1 || this.camerasList.length === 0) { - return; - } - this.cameraSelected++; - videoConstraintStore.setDeviceId(this.camerasList[this.cameraSelected].deviceId); - - // TODO: the change of camera should be OBSERVED (reactive) - //mediaManager.setCamera(this.camerasList[this.cameraSelected].deviceId).then(this.setupStream.bind(this)); - } - - private previousMic(): void { - if (this.microphoneSelected === 0 || this.microphonesList.length === 0) { - return; - } - this.microphoneSelected--; - audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId); - //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this)); - } - - private nextMic(): void { - if (this.microphoneSelected === this.microphonesList.length - 1 || this.microphonesList.length === 0) { - return; - } - this.microphoneSelected++; - audioConstraintStore.setDeviceId(this.microphonesList[this.microphoneSelected].deviceId); - // TODO: the change of camera should be OBSERVED (reactive) - //mediaManager.setMicrophone(this.microphonesList[this.microphoneSelected].deviceId).then(this.setupStream.bind(this)); - } - - /** - * Function called each time a camera is changed - */ - private setupStream(stream: MediaStream): void { - const img = HtmlUtils.getElementByIdOrFail('webRtcSetupNoVideo'); - img.style.display = 'none'; - - const div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); - div.srcObject = stream; - - this.soundMeter.connectToSource(stream, new window.AudioContext()); - this.soundMeterSprite.setVisible(true); - - this.updateWebCamName(); - } - - private updateWebCamName(): void { - if (this.camerasList.length > 1) { - let label = this.camerasList[this.cameraSelected].label; - // remove text in parenthesis - label = label.replace(/\([^()]*\)/g, '').trim(); - // remove accents - label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - this.cameraNameField.text = label; - - this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1); - this.arrowLeft.setVisible(this.cameraSelected > 0); - } - if (this.microphonesList.length > 1) { - let label = this.microphonesList[this.microphoneSelected].label; - // remove text in parenthesis - label = label.replace(/\([^()]*\)/g, '').trim(); - // remove accents - label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - - this.microphoneNameField.text = label; - - this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1); - this.arrowUp.setVisible(this.microphoneSelected > 0); - - } - } - public onResize(): void { - let div = HtmlUtils.getElementByIdOrFail('myCamVideoSetup'); - let bounds = div.getBoundingClientRect(); - if (!div.srcObject) { - div = HtmlUtils.getElementByIdOrFail('webRtcSetup'); - bounds = div.getBoundingClientRect(); - } - - this.textField.x = this.game.renderer.width / 2; - this.mobileTapZone.x = this.game.renderer.width / 2; - this.cameraNameField.x = this.game.renderer.width / 2; - this.microphoneNameField.x = this.game.renderer.width / 2; - - this.cameraNameField.y = bounds.top / this.scale.zoom - 8; - - this.soundMeterSprite.x = this.game.renderer.width / 2 - this.soundMeterSprite.getWidth() / 2; - this.soundMeterSprite.y = bounds.bottom / this.scale.zoom + 16; - - this.microphoneNameField.y = this.soundMeterSprite.y + 22; - - this.arrowRight.x = bounds.right / this.scale.zoom + 16; - this.arrowRight.y = (bounds.top + bounds.height / 2) / this.scale.zoom; - - this.arrowLeft.x = bounds.left / this.scale.zoom - 16; - this.arrowLeft.y = (bounds.top + bounds.height / 2) / this.scale.zoom; - - this.arrowDown.x = this.microphoneNameField.x + this.microphoneNameField.width / 2 + 16; - this.arrowDown.y = this.microphoneNameField.y; - - this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16; - this.arrowUp.y = this.microphoneNameField.y; - - const actionBtn = document.querySelector('#enableCameraScene .action'); - if (actionBtn !== null) { - actionBtn.style.top = (this.scale.height - 65) + 'px'; - } } update(time: number, delta: number): void { - this.soundMeterSprite.setVolume(this.soundMeter.getVolume()); - - this.centerXDomElement(this.enableCameraSceneElement, 300); } - private login(): void { - HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none'; - this.soundMeter.stop(); - + public login(): void { enableCameraSceneVisibilityStore.hideEnableCameraScene(); - this.localStreamStoreUnsubscriber(); - //mediaManager.stopCamera(); - //mediaManager.stopMicrophone(); this.scene.sleep(EnableCameraSceneName); gameManager.goToStartingMap(this.scene); } - - private async getDevices() { - // TODO: switch this in a store. - const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices(); - this.microphonesList = []; - this.camerasList = []; - for (const mediaDeviceInfo of mediaDeviceInfos) { - if (mediaDeviceInfo.kind === 'audioinput') { - this.microphonesList.push(mediaDeviceInfo); - } else if (mediaDeviceInfo.kind === 'videoinput') { - this.camerasList.push(mediaDeviceInfo); - } - } - this.updateWebCamName(); - } } diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 1219ebb7..39a8f5f3 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -1,18 +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 { MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable"; +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() { @@ -23,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 >= MAX_USERNAME_LENGTH){ - event.preventDefault(); - return; - } - pErrorElement.innerHTML = ''; - if(inputElement.value && !isUserNameValid(inputElement.value)){ - pErrorElement.innerHTML = 'Invalid user name: No spaces are allowed.'; - } - if (event.key === 'Enter') { - event.preventDefault(); - this.login(inputElement); - return; - } - }); - - const continuingButton = this.loginSceneElement.getChildByID('loginSceneFormSubmit') as HTMLButtonElement; - continuingButton.addEventListener('click', (e) => { - e.preventDefault(); - this.login(inputElement); - }); + loginSceneVisibleStore.set(true); } - private login(inputElement: HTMLInputElement): void { - const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement; - this.name = inputElement.value; - if (this.name === '') { - pErrorElement.innerHTML = 'The name is empty'; - return - } - if(!isUserNameValid(this.name)){ - pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.'; - return - } - if (this.name === '') return - gameManager.setPlayerName(this.name); + public login(name: string): void { + name = name.trim(); + gameManager.setPlayerName(name); this.scene.stop(LoginSceneName) gameManager.tryResumingGame(this, SelectCharacterSceneName); - this.scene.remove(LoginSceneName) + this.scene.remove(LoginSceneName); + loginSceneVisibleStore.set(false); } update(time: number, delta: number): void { - } - public onResize(ev: UIEvent): void { - this.centerXDomElement(this.loginSceneElement, 200); + public onResize(): void { } } diff --git a/front/src/Phaser/Login/ResizableScene.ts b/front/src/Phaser/Login/ResizableScene.ts index 39e2d74b..d06cb66c 100644 --- a/front/src/Phaser/Login/ResizableScene.ts +++ b/front/src/Phaser/Login/ResizableScene.ts @@ -2,7 +2,7 @@ import {Scene} from "phaser"; import DOMElement = Phaser.GameObjects.DOMElement; export abstract class ResizableScene extends Scene { - public abstract onResize(ev: UIEvent): void; + public abstract onResize(): void; /** * Centers the DOM element on the X axis. @@ -17,7 +17,7 @@ export abstract class ResizableScene extends Scene { && object.node && object.node.getBoundingClientRect().width > 0 ? (object.node.getBoundingClientRect().width / 2 / this.scale.zoom) - : (300 / this.scale.zoom) + : (defaultWidth / this.scale.zoom) ); } } diff --git a/front/src/Phaser/Login/SelectCharacterMobileScene.ts b/front/src/Phaser/Login/SelectCharacterMobileScene.ts index b9c4b5a8..0d8e49d5 100644 --- a/front/src/Phaser/Login/SelectCharacterMobileScene.ts +++ b/front/src/Phaser/Login/SelectCharacterMobileScene.ts @@ -4,49 +4,50 @@ export class SelectCharacterMobileScene extends SelectCharacterScene { create(){ super.create(); + this.onResize(); this.selectedRectangle.destroy(); } - protected defineSetupPlayer(numero: number){ + protected defineSetupPlayer(num: number){ const deltaX = 30; const deltaY = 2; let [playerX, playerY] = this.getCharacterPosition(); let playerVisible = true; let playerScale = 1.5; - let playserOpactity = 1; + let playerOpacity = 1; - if( this.currentSelectUser !== numero ){ + if( this.currentSelectUser !== num ){ playerVisible = false; } - if( numero === (this.currentSelectUser + 1) ){ + if( num === (this.currentSelectUser + 1) ){ playerY -= deltaY; playerX += deltaX; playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - if( numero === (this.currentSelectUser + 2) ){ + if( num === (this.currentSelectUser + 2) ){ playerY -= deltaY; playerX += (deltaX * 2); playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - if( numero === (this.currentSelectUser - 1) ){ + if( num === (this.currentSelectUser - 1) ){ playerY -= deltaY; playerX -= deltaX; playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - if( numero === (this.currentSelectUser - 2) ){ + if( num === (this.currentSelectUser - 2) ){ playerY -= deltaY; playerX -= (deltaX * 2); playerScale = 0.8; - playserOpactity = 0.6; + playerOpacity = 0.6; playerVisible = true; } - return {playerX, playerY, playerScale, playserOpactity, playerVisible} + return {playerX, playerY, playerScale, playerOpacity, playerVisible} } /** diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index ecbb9c64..0cfbbdef 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -11,6 +11,10 @@ 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 {customCharacterSceneVisibleStore} from "../../Stores/CustomCharacterStore"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {isMobile} from "../../Enum/EnvironmentVariable"; //todo: put this constants in a dedicated file export const SelectCharacterSceneName = "SelectCharacterScene"; @@ -27,6 +31,9 @@ export class SelectCharacterScene extends AbstractCharacterScene { protected selectCharacterSceneElement!: Phaser.GameObjects.DOMElement; protected currentSelectUser = 0; + protected pointerClicked: boolean = false; + + protected lazyloadingAttempt = true; //permit to update texture loaded after renderer constructor() { super({ @@ -41,44 +48,36 @@ export class SelectCharacterScene extends AbstractCharacterScene { 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(); @@ -106,9 +105,12 @@ 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 { @@ -116,7 +118,9 @@ export class SelectCharacterScene extends AbstractCharacterScene { return; } this.scene.sleep(SelectCharacterSceneName); + waScaleManager.restoreZoom(); this.scene.run(CustomizeSceneName); + selectCharacterSceneVisibleStore.set(false); } createCurrentPlayer(): void { @@ -133,15 +137,16 @@ export class SelectCharacterScene extends AbstractCharacterScene { repeat: -1 }); player.setInteractive().on("pointerdown", () => { - if(this.currentSelectUser === i){ + if (this.pointerClicked || this.currentSelectUser === i) { return; } + this.pointerClicked = true; this.currentSelectUser = i; this.moveUser(); + setTimeout(() => {this.pointerClicked = false;}, 100); }); this.players.push(player); } - this.selectedPlayer = this.players[this.currentSelectUser]; this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name); } @@ -186,35 +191,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 +243,14 @@ export class SelectCharacterScene extends AbstractCharacterScene { } update(time: number, delta: number): void { + if(this.lazyloadingAttempt){ + this.createCurrentPlayer(); + this.lazyloadingAttempt = false; + } } - public onResize(ev: UIEvent): void { + public onResize(): void { //move position of user this.moveUser(); - - this.centerXDomElement(this.selectCharacterSceneElement, 150); } } diff --git a/front/src/Phaser/Login/SelectCompanionScene.ts b/front/src/Phaser/Login/SelectCompanionScene.ts index 203fd557..9caa88f7 100644 --- a/front/src/Phaser/Login/SelectCompanionScene.ts +++ b/front/src/Phaser/Login/SelectCompanionScene.ts @@ -10,17 +10,18 @@ import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingM import {touchScreenManager} from "../../Touch/TouchScreenManager"; import {PinchManager} from "../UserInput/PinchManager"; import { MenuScene } from "../Menu/MenuScene"; +import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore"; +import {waScaleManager} from "../Services/WaScaleManager"; +import {isMobile} from "../../Enum/EnvironmentVariable"; export const SelectCompanionSceneName = "SelectCompanionScene"; -const selectCompanionSceneKey = 'selectCompanionScene'; - export class SelectCompanionScene extends ResizableScene { private selectedCompanion!: Phaser.Physics.Arcade.Sprite; private companions: Array = new Array(); private companionModels: Array = []; + private saveZoom: number = 0; - private selectCompanionSceneElement!: Phaser.GameObjects.DOMElement; private currentCompanion = 0; constructor() { @@ -30,8 +31,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 +41,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)); @@ -89,18 +75,20 @@ export class SelectCompanionScene extends ResizableScene { } - 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 { @@ -126,10 +114,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 +133,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 +141,46 @@ export class SelectCompanionScene extends ResizableScene { this.moveCompanion(); } - private defineSetupCompanion(numero: number){ + public moveToLeft(){ + if(this.currentCompanion === 0){ + return; + } + this.currentCompanion -= 1; + this.moveCompanion(); + } + + private defineSetupCompanion(num: number){ const deltaX = 30; const deltaY = 2; let [companionX, companionY] = this.getCompanionPosition(); let companionVisible = true; let companionScale = 1.5; let companionOpactity = 1; - if( this.currentCompanion !== numero ){ + if( this.currentCompanion !== num ){ companionVisible = false; } - if( numero === (this.currentCompanion + 1) ){ + if( num === (this.currentCompanion + 1) ){ companionY -= deltaY; companionX += deltaX; companionScale = 0.8; companionOpactity = 0.6; companionVisible = true; } - if( numero === (this.currentCompanion + 2) ){ + if( num === (this.currentCompanion + 2) ){ companionY -= deltaY; companionX += (deltaX * 2); companionScale = 0.8; companionOpactity = 0.6; companionVisible = true; } - if( numero === (this.currentCompanion - 1) ){ + if( num === (this.currentCompanion - 1) ){ companionY -= deltaY; companionX -= deltaX; companionScale = 0.8; companionOpactity = 0.6; companionVisible = true; } - if( numero === (this.currentCompanion - 2) ){ + if( num === (this.currentCompanion - 2) ){ companionY -= deltaY; companionX -= (deltaX * 2); companionScale = 0.8; diff --git a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts b/front/src/Phaser/Menu/HelpCameraSettingsScene.ts deleted file mode 100644 index 6bc520c0..00000000 --- a/front/src/Phaser/Menu/HelpCameraSettingsScene.ts +++ /dev/null @@ -1,152 +0,0 @@ -import {mediaManager} from "../../WebRtc/MediaManager"; -import {HtmlUtils} from "../../WebRtc/HtmlUtils"; -import {localUserStore} from "../../Connexion/LocalUserStore"; -import {DirtyScene} from "../Game/DirtyScene"; -import {get} from "svelte/store"; -import {requestedCameraState, requestedMicrophoneState} from "../../Stores/MediaStore"; - -export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene'; -const helpCameraSettings = 'helpCameraSettings'; -/** - * The scene that show how to permit Camera and Microphone access if there are not already allowed - */ -export class HelpCameraSettingsScene extends DirtyScene { - private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement; - private helpCameraSettingsOpened: boolean = false; - - constructor() { - super({key: HelpCameraSettingsSceneName}); - } - - preload() { - this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html'); - } - - create(){ - this.createHelpCameraSettings(); - } - - private createHelpCameraSettings() : void { - const middleX = this.getMiddleX(); - this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings); - this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings); - this.helpCameraSettingsElement.addListener('click'); - this.helpCameraSettingsElement.on('click', (event:MouseEvent) => { - if((event?.target as HTMLInputElement).id === 'mailto') { - return; - } - event.preventDefault(); - if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') { - window.location.reload(); - }else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') { - this.closeHelpCameraSettingsOpened(); - } - }); - - if(!localUserStore.getHelpCameraSettingsShown() && (!get(requestedMicrophoneState) || !get(requestedCameraState))){ - this.openHelpCameraSettingsOpened(); - localUserStore.setHelpCameraSettingsShown(); - } - - mediaManager.setHelpCameraSettingsCallBack(() => { - this.openHelpCameraSettingsOpened(); - }); - } - - private openHelpCameraSettingsOpened(): void{ - HtmlUtils.getElementByIdOrFail('webRtcSetup').style.display = 'none'; - this.helpCameraSettingsOpened = true; - try{ - if(window.navigator.userAgent.includes('Firefox')){ - HtmlUtils.getElementByIdOrFail('browserHelpSetting').innerHTML =''; - }else if(window.navigator.userAgent.includes('Chrome')){ - HtmlUtils.getElementByIdOrFail('browserHelpSetting').innerHTML =''; - } - }catch(err) { - console.error('openHelpCameraSettingsOpened => getElementByIdOrFail => error', err); - } - const middleY = this.getMiddleY(); - const middleX = this.getMiddleX(); - this.tweens.add({ - targets: this.helpCameraSettingsElement, - y: middleY, - x: middleX, - duration: 1000, - ease: 'Power3', - overflow: 'scroll' - }); - - this.dirty = true; - } - - private closeHelpCameraSettingsOpened(): void{ - const middleX = this.getMiddleX(); - /*const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement; - helpCameraSettingsInfo.innerText = ''; - helpCameraSettingsInfo.style.display = 'none';*/ - this.helpCameraSettingsOpened = false; - this.tweens.add({ - targets: this.helpCameraSettingsElement, - y: -1000, - x: middleX, - duration: 1000, - ease: 'Power3', - overflow: 'scroll' - }); - - this.dirty = true; - } - - private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) { - //Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect. - //To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done. - setTimeout(() => { - (menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false; - }, 250); - } - - update(time: number, delta: number): void { - this.dirty = false; - } - - public onResize(ev: UIEvent): void { - super.onResize(ev); - if (this.helpCameraSettingsOpened) { - const middleX = this.getMiddleX(); - const middleY = this.getMiddleY(); - this.tweens.add({ - targets: this.helpCameraSettingsElement, - x: middleX, - y: middleY, - duration: 1000, - ease: 'Power3' - }); - this.dirty = true; - } - } - - private getMiddleX() : number{ - return (this.scale.width / 2) - - ( - this.helpCameraSettingsElement - && this.helpCameraSettingsElement.node - && this.helpCameraSettingsElement.node.getBoundingClientRect().width > 0 - ? (this.helpCameraSettingsElement.node.getBoundingClientRect().width / (2 * this.scale.zoom)) - : (400 / 2) - ); - } - - private getMiddleY() : number{ - const middleY = ((this.scale.height) - ( - (this.helpCameraSettingsElement - && this.helpCameraSettingsElement.node - && this.helpCameraSettingsElement.node.getBoundingClientRect().height > 0 - ? this.helpCameraSettingsElement.node.getBoundingClientRect().height : 400 /*FIXME to use a const will be injected in HTMLElement*/)/this.scale.zoom)) / 2; - return (middleY > 0 ? middleY : 0); - } - - public isDirty(): boolean { - return this.dirty; - } -} - diff --git a/front/src/Phaser/Services/WaScaleManager.ts b/front/src/Phaser/Services/WaScaleManager.ts index ca8b668d..4e0e9208 100644 --- a/front/src/Phaser/Services/WaScaleManager.ts +++ b/front/src/Phaser/Services/WaScaleManager.ts @@ -2,6 +2,7 @@ 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"; class WaScaleManager { @@ -9,6 +10,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,13 +32,19 @@ 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 const style = this.scaleManager.canvas.style; style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px'; style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px'; + // Note: onResize will be called twice (once here and once is Game.ts), but we have no better way. + for (const scene of this.game.scene.getScenes(true)) { + if (scene instanceof ResizableScene) { + scene.onResize(); + } + } this.game.markDirty(); } @@ -50,6 +58,16 @@ class WaScaleManager { this.applyNewSize(); } + public saveZoom(): void { + this._saveZoom = this.hdpiManager.zoomModifier; + console.log(this._saveZoom); + } + + public restoreZoom(): void{ + this.hdpiManager.zoomModifier = this._saveZoom; + this.applyNewSize(); + } + /** * This is used to scale back the ui components to counter-act the zoom. */ diff --git a/front/src/Stores/CustomCharacterStore.ts b/front/src/Stores/CustomCharacterStore.ts new file mode 100644 index 00000000..4bef7768 --- /dev/null +++ b/front/src/Stores/CustomCharacterStore.ts @@ -0,0 +1,3 @@ +import { derived, writable, Writable } from "svelte/store"; + +export const customCharacterSceneVisibleStore = writable(false); \ No newline at end of file diff --git a/front/src/Stores/HelpCameraSettingsStore.ts b/front/src/Stores/HelpCameraSettingsStore.ts new file mode 100644 index 00000000..88373dab --- /dev/null +++ b/front/src/Stores/HelpCameraSettingsStore.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const helpCameraSettingsVisibleStore = writable(false); diff --git a/front/src/Stores/LoginSceneStore.ts b/front/src/Stores/LoginSceneStore.ts new file mode 100644 index 00000000..6e2ea18b --- /dev/null +++ b/front/src/Stores/LoginSceneStore.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const loginSceneVisibleStore = writable(false); diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index 9e53aa3b..7d1911a4 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -209,10 +209,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 +245,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 +418,15 @@ export const localStreamStore = derived, LocalS error: new Error('Unable to access your camera or microphone. You need to use a HTTPS connection.'), constraints }); + return; } else { //throw new Error('Unable to access your camera or microphone. Your browser is too old.'); set({ type: 'error', - error: new Error('Unable to access your camera or microphone. Your browser is too old.'), + error: new Error('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.'), constraints }); + return; } } @@ -508,3 +518,79 @@ export const obtainedMediaConstraintStore = derived(localStreamStore, ($localStr return $localStreamStore.constraints; }); +/** + * Device list + */ +export const deviceListStore = readable([], function start(set) { + let deviceListCanBeQueried = false; + + const queryDeviceList = () => { + // Note: so far, we are ignoring any failures. + navigator.mediaDevices.enumerateDevices().then((mediaDeviceInfos) => { + set(mediaDeviceInfos); + }).catch((e) => { + console.error(e); + throw e; + }); + }; + + const unsubscribe = localStreamStore.subscribe((streamResult) => { + if (streamResult.type === "success" && streamResult.stream !== null) { + if (deviceListCanBeQueried === false) { + queryDeviceList(); + deviceListCanBeQueried = true; + } + } + }); + + if (navigator.mediaDevices) { + navigator.mediaDevices.addEventListener('devicechange', queryDeviceList); + } + + return function stop() { + unsubscribe(); + if (navigator.mediaDevices) { + navigator.mediaDevices.removeEventListener('devicechange', queryDeviceList); + } + }; +}); + +export const cameraListStore = derived(deviceListStore, ($deviceListStore) => { + return $deviceListStore.filter(device => device.kind === 'videoinput'); +}); + +export const microphoneListStore = derived(deviceListStore, ($deviceListStore) => { + return $deviceListStore.filter(device => device.kind === 'audioinput'); +}); + +// TODO: detect the new webcam and automatically switch on it. +cameraListStore.subscribe((devices) => { + // If the selected camera is unplugged, let's remove the constraint on deviceId + const constraints = get(videoConstraintStore); + if (!constraints.deviceId) { + return; + } + + // If we cannot find the device ID, let's remove it. + // @ts-ignore + if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) { + videoConstraintStore.setDeviceId(undefined); + } +}); + +microphoneListStore.subscribe((devices) => { + // If the selected camera is unplugged, let's remove the constraint on deviceId + const constraints = get(audioConstraintStore); + if (typeof constraints === 'boolean') { + return; + } + if (!constraints.deviceId) { + return; + } + + // If we cannot find the device ID, let's remove it. + // @ts-ignore + if (!devices.find(device => device.deviceId === constraints.deviceId.exact)) { + audioConstraintStore.setDeviceId(undefined); + } +}); diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts index a55f5ab2..0a7ef3e6 100644 --- a/front/src/Stores/ScreenSharingStore.ts +++ b/front/src/Stores/ScreenSharingStore.ts @@ -126,7 +126,7 @@ export const screenSharingLocalStreamStore = derived; if (navigator.getDisplayMedia) { currentStreamPromise = navigator.getDisplayMedia({constraints}); - } else if (navigator.mediaDevices.getDisplayMedia) { + } else if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints}); } else { stopScreenSharing(); @@ -183,7 +183,7 @@ export const screenSharingLocalStreamStore = derived { - if (!navigator.getDisplayMedia && !navigator.mediaDevices.getDisplayMedia) { + if (!navigator.getDisplayMedia && (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia)) { set(false); return; } diff --git a/front/src/Stores/SelectCharacterStore.ts b/front/src/Stores/SelectCharacterStore.ts new file mode 100644 index 00000000..094eaef3 --- /dev/null +++ b/front/src/Stores/SelectCharacterStore.ts @@ -0,0 +1,3 @@ +import { derived, writable, Writable } from "svelte/store"; + +export const selectCharacterSceneVisibleStore = writable(false); \ No newline at end of file diff --git a/front/src/Stores/SelectCompanionStore.ts b/front/src/Stores/SelectCompanionStore.ts new file mode 100644 index 00000000..e66f5de3 --- /dev/null +++ b/front/src/Stores/SelectCompanionStore.ts @@ -0,0 +1,3 @@ +import { derived, writable, Writable } from "svelte/store"; + +export const selectCompanionSceneVisibleStore = writable(false); diff --git a/front/src/Stores/SoundPlayingStore.ts b/front/src/Stores/SoundPlayingStore.ts new file mode 100644 index 00000000..cf1d681c --- /dev/null +++ b/front/src/Stores/SoundPlayingStore.ts @@ -0,0 +1,22 @@ +import { writable } from "svelte/store"; + +/** + * A store that contains the URL of the sound currently playing + */ +function createSoundPlayingStore() { + const { subscribe, set, update } = writable(null); + + return { + subscribe, + playSound: (url: string) => { + set(url); + }, + soundEnded: () => { + set(null); + } + + + }; +} + +export const soundPlayingStore = createSoundPlayingStore(); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 03990ce8..7b527962 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -8,32 +8,11 @@ import {SoundMeter} from "../Phaser/Components/SoundMeter"; import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable"; import { gameOverlayVisibilityStore, localStreamStore, - mediaStreamConstraintsStore, - requestedCameraState, - requestedMicrophoneState } from "../Stores/MediaStore"; import { - requestedScreenSharingState, - screenSharingAvailableStore, screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; - -declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any - -const videoConstraint: boolean|MediaTrackConstraints = { - width: { min: 640, ideal: 1280, max: 1920 }, - height: { min: 400, ideal: 720 }, - frameRate: { ideal: localUserStore.getVideoQualityValue() }, - facingMode: "user", - resizeMode: 'crop-and-scale', - aspectRatio: 1.777777778 -}; -const audioConstraint: MediaTrackConstraints = { - // TODO: Make these values configurable in the game settings menu and store them in local storage - autoGainControl: false, - echoCancellation: true, - noiseSuppression: true -}; +import {helpCameraSettingsVisibleStore} from "../Stores/HelpCameraSettingsStore"; export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; @@ -43,18 +22,14 @@ export type ShowReportCallBack = (userId: string, userName: string|undefined) => export type HelpCameraSettingsCallBack = () => void; export class MediaManager { - localStream: MediaStream|null = null; - localScreenCapture: MediaStream|null = null; private remoteVideo: Map = new Map(); webrtcInAudio: HTMLAudioElement; //FIX ME SOUNDMETER: check stalability of sound meter calculation //mySoundMeterElement: HTMLDivElement; private webrtcOutAudio: HTMLAudioElement; - updatedLocalStreamCallBacks : Set = new Set(); startScreenSharingCallBacks : Set = new Set(); stopScreenSharingCallBacks : Set = new Set(); showReportModalCallBacks : Set = new Set(); - helpCameraSettingsCallBacks : Set = new Set(); private focused : boolean = true; @@ -88,76 +63,27 @@ export class MediaManager { localStreamStore.subscribe((result) => { if (result.type === 'error') { console.error(result.error); - layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => { - this.showHelpCameraSettingsCallBack(); + layoutManager.addInformation('warning', 'Camera access denied. Click here and check your browser permissions.', () => { + helpCameraSettingsVisibleStore.set(true); }, this.userInputManager); return; } - - /*if (result.constraints.video !== false) { - HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.remove('hide'); - } else { - HtmlUtils.getElementByIdOrFail('div-myCamVideo').classList.add('hide'); - } - if (result.constraints.audio !== false) { - this.enableMicrophoneStyle(); - } else { - this.disableMicrophoneStyle(); - }*/ - - this.localStream = result.stream; - //this.myCamVideo.srcObject = this.localStream; - - // TODO: migrate all listeners to the store directly. - this.triggerUpdatedLocalStreamCallbacks(result.stream); }); - /*requestedCameraState.subscribe((enabled) => { - if (enabled) { - this.enableCameraStyle(); - } else { - this.disableCameraStyle(); - } - }); - requestedMicrophoneState.subscribe((enabled) => { - if (enabled) { - this.enableMicrophoneStyle(); - } else { - this.disableMicrophoneStyle(); - } - });*/ - //let screenSharingStream : MediaStream|null; screenSharingLocalStreamStore.subscribe((result) => { if (result.type === 'error') { console.error(result.error); - layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => { - this.showHelpCameraSettingsCallBack(); + layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check your browser permissions.', () => { + helpCameraSettingsVisibleStore.set(true); }, this.userInputManager); return; } if (result.stream !== null) { - //this.enableScreenSharingStyle(); - mediaManager.localScreenCapture = result.stream; - - // TODO: migrate this out of MediaManager - this.triggerStartedScreenSharingCallbacks(result.stream); - - //screenSharingStream = result.stream; - this.addScreenSharingActiveVideo('me', DivImportance.Normal); HtmlUtils.getElementByIdOrFail('screen-sharing-me').srcObject = result.stream; } else { - //this.disableScreenSharingStyle(); this.removeActiveScreenSharingVideo('me'); - - // FIXME: we need the old stream that is being stopped! - if (this.localScreenCapture) { - this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture); - this.localScreenCapture = null; - } - - //screenSharingStream = null; } }); @@ -176,40 +102,6 @@ export class MediaManager { //this.updateSoudMeter(); } - public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void { - this.updatedLocalStreamCallBacks.add(callback); - } - - public onStartScreenSharing(callback: StartScreenSharingCallback): void { - this.startScreenSharingCallBacks.add(callback); - } - - public onStopScreenSharing(callback: StopScreenSharingCallback): void { - this.stopScreenSharingCallBacks.add(callback); - } - - removeUpdateLocalStreamEventListener(callback: UpdatedLocalStreamCallback): void { - this.updatedLocalStreamCallBacks.delete(callback); - } - - private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void { - for (const callback of this.updatedLocalStreamCallBacks) { - callback(stream); - } - } - - private triggerStartedScreenSharingCallbacks(stream: MediaStream): void { - for (const callback of this.startScreenSharingCallBacks) { - callback(stream); - } - } - - private triggerStoppedScreenSharingCallbacks(stream: MediaStream): void { - for (const callback of this.stopScreenSharingCallBacks) { - callback(stream); - } - } - public showGameOverlay(): void { const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); gameOverlay.classList.add('active'); @@ -236,42 +128,6 @@ export class MediaManager { gameOverlayVisibilityStore.hideGameOverlay(); } - /*private enableCameraStyle(){ - this.cinemaClose.style.display = "none"; - this.cinemaBtn.classList.remove("disabled"); - this.cinema.style.display = "block"; - } - - private disableCameraStyle(){ - this.cinemaClose.style.display = "block"; - this.cinema.style.display = "none"; - this.cinemaBtn.classList.add("disabled"); - } - - private enableMicrophoneStyle(){ - this.microphoneClose.style.display = "none"; - this.microphone.style.display = "block"; - this.microphoneBtn.classList.remove("disabled"); - } - - private disableMicrophoneStyle(){ - this.microphoneClose.style.display = "block"; - this.microphone.style.display = "none"; - this.microphoneBtn.classList.add("disabled"); - } - - private enableScreenSharingStyle(){ - this.monitorClose.style.display = "none"; - this.monitor.style.display = "block"; - this.monitorBtn.classList.add("enabled"); - } - - private disableScreenSharingStyle(){ - this.monitorClose.style.display = "block"; - this.monitor.style.display = "none"; - this.monitorBtn.classList.remove("enabled"); - }*/ - addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ this.webrtcInAudio.play(); const userId = ''+user.userId @@ -539,16 +395,6 @@ export class MediaManager { this.showReportModalCallBacks.add(callback); } - public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){ - this.helpCameraSettingsCallBacks.add(callback); - } - - private showHelpCameraSettingsCallBack(){ - for(const callBack of this.helpCameraSettingsCallBacks){ - callBack(); - } - } - //FIX ME SOUNDMETER: check stalability of sound meter calculation /*updateSoudMeter(){ try{ @@ -594,12 +440,32 @@ export class MediaManager { public getNotification(){ //Get notification if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") { - Notification.requestPermission().catch((err) => { - console.error(`Notification permission error`, err); - }); + if (this.checkNotificationPromise()) { + Notification.requestPermission().catch((err) => { + console.error(`Notification permission error`, err); + }); + } else { + Notification.requestPermission(); + } } } + /** + * Return true if the browser supports the modern version of the Notification API (which is Promise based) or false + * if we are on Safari... + * + * See https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API + */ + private checkNotificationPromise(): boolean { + try { + Notification.requestPermission().then(); + } catch(e) { + return false; + } + + return true; + } + public createNotification(userName: string){ if(this.focused){ return; diff --git a/front/src/WebRtc/ScreenSharingPeer.ts b/front/src/WebRtc/ScreenSharingPeer.ts index f1786ef3..d797f59b 100644 --- a/front/src/WebRtc/ScreenSharingPeer.ts +++ b/front/src/WebRtc/ScreenSharingPeer.ts @@ -19,7 +19,7 @@ export class ScreenSharingPeer extends Peer { public _connected: boolean = false; private userId: number; - constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { + constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, stream: MediaStream | null) { super({ initiator: initiator ? initiator : false, //reconnectTimer: 10000, @@ -60,6 +60,7 @@ export class ScreenSharingPeer extends Peer { const message = JSON.parse(chunk.toString('utf8')); if (message.streamEnded !== true) { console.error('Unexpected message on screen sharing peer connection'); + return; } mediaManager.removeActiveScreenSharingVideo("" + this.userId); }); @@ -81,7 +82,9 @@ export class ScreenSharingPeer extends Peer { this._onFinish(); }); - this.pushScreenSharingToRemoteUser(); + if (stream) { + this.addStream(stream); + } } private sendWebrtcScreenSharingSignal(data: unknown) { @@ -141,16 +144,6 @@ export class ScreenSharingPeer extends Peer { } } - private pushScreenSharingToRemoteUser() { - const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - if(!localScreenCapture){ - return; - } - - this.addStream(localScreenCapture); - return; - } - public stopPushingScreenSharingToRemoteUser(stream: MediaStream) { this.removeStream(stream); this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, streamEnded: true}))); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 4633374d..9193f18b 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -15,7 +15,10 @@ import {connectionManager} from "../Connexion/ConnectionManager"; import {GameConnexionTypes} from "../Url/UrlManager"; import {blackListManager} from "./BlackListManager"; import {get} from "svelte/store"; -import {localStreamStore, obtainedMediaConstraintStore} from "../Stores/MediaStore"; +import {localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore} from "../Stores/MediaStore"; +import {screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore"; +import {DivImportance, layoutManager} from "./LayoutManager"; +import {HtmlUtils} from "./HtmlUtils"; export interface UserSimplePeerInterface{ userId: number; @@ -39,9 +42,9 @@ export class SimplePeer { private PeerScreenSharingConnectionArray: Map = new Map(); private PeerConnectionArray: Map = new Map(); - private readonly sendLocalVideoStreamCallback: UpdatedLocalStreamCallback; private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback; private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback; + private readonly unsubscribers: (() => void)[] = []; private readonly peerConnectionListeners: Array = new Array(); private readonly userId: number; private lastWebrtcUserName: string|undefined; @@ -49,13 +52,32 @@ export class SimplePeer { constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) { // We need to go through this weird bound function pointer in order to be able to "free" this reference later. - this.sendLocalVideoStreamCallback = this.sendLocalVideoStream.bind(this); this.sendLocalScreenSharingStreamCallback = this.sendLocalScreenSharingStream.bind(this); this.stopLocalScreenSharingStreamCallback = this.stopLocalScreenSharingStream.bind(this); - mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback); - mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback); - mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback); + this.unsubscribers.push(localStreamStore.subscribe((streamResult) => { + this.sendLocalVideoStream(streamResult); + })); + + let localScreenCapture: MediaStream|null = null; + + this.unsubscribers.push(screenSharingLocalStreamStore.subscribe((streamResult) => { + if (streamResult.type === 'error') { + // Let's ignore screen sharing errors, we will deal with those in a different way. + return; + } + + if (streamResult.stream !== null) { + localScreenCapture = streamResult.stream; + this.sendLocalScreenSharingStream(localScreenCapture); + } else { + if (localScreenCapture) { + this.stopLocalScreenSharingStream(localScreenCapture); + localScreenCapture = null; + } + } + })); + this.userId = Connection.getUserId(); this.initialise(); } @@ -106,13 +128,19 @@ export class SimplePeer { if(!user.initiator){ return; } - this.createPeerConnection(user); + const streamResult = get(localStreamStore); + let stream : MediaStream | null = null; + if (streamResult.type === 'success' && streamResult.stream) { + stream = streamResult.stream; + } + + this.createPeerConnection(user, stream); } /** * create peer connection to bind users */ - private createPeerConnection(user : UserSimplePeerInterface) : VideoPeer | null { + private createPeerConnection(user : UserSimplePeerInterface, localStream: MediaStream | null) : VideoPeer | null { const peerConnection = this.PeerConnectionArray.get(user.userId) if (peerConnection) { if (peerConnection.destroyed) { @@ -122,11 +150,11 @@ export class SimplePeer { if (!peerConnexionDeleted) { throw 'Error to delete peer connection'; } - this.createPeerConnection(user); + //return this.createPeerConnection(user, localStream); } else { peerConnection.toClose = false; + return null; } - return null; } let name = user.name; @@ -144,7 +172,7 @@ export class SimplePeer { this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcPassword = user.webRtcPassword; - const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection); + const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection, localStream); //permit to send message mediaManager.addSendMessageCallback(user.userId,(message: string) => { @@ -155,8 +183,9 @@ export class SimplePeer { // When a connection is established to a video stream, and if a screen sharing is taking place, // the user sharing screen should also initiate a connection to the remote user! peer.on('connect', () => { - if (mediaManager.localScreenCapture) { - this.sendLocalScreenSharingStreamToUser(user.userId); + const streamResult = get(screenSharingLocalStreamStore); + if (streamResult.type === 'success' && streamResult.stream !== null) { + this.sendLocalScreenSharingStreamToUser(user.userId, streamResult.stream); } }); @@ -175,7 +204,7 @@ export class SimplePeer { /** * create peer connection to bind users */ - private createPeerScreenSharingConnection(user : UserSimplePeerInterface) : ScreenSharingPeer | null{ + private createPeerScreenSharingConnection(user : UserSimplePeerInterface, stream: MediaStream | null) : ScreenSharingPeer | null{ const peerConnection = this.PeerScreenSharingConnectionArray.get(user.userId); if(peerConnection){ if(peerConnection.destroyed){ @@ -185,7 +214,7 @@ export class SimplePeer { if(!peerConnexionDeleted){ throw 'Error to delete peer connection'; } - this.createPeerConnection(user); + this.createPeerConnection(user, stream); }else { peerConnection.toClose = false; } @@ -204,7 +233,7 @@ export class SimplePeer { user.webRtcPassword = this.lastWebrtcPassword; } - const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection); + const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection, stream); this.PeerScreenSharingConnectionArray.set(user.userId, peer); for (const peerConnectionListener of this.peerConnectionListeners) { @@ -234,7 +263,7 @@ export class SimplePeer { const userIndex = this.Users.findIndex(user => user.userId === userId); if(userIndex < 0){ - throw 'Couln\'t delete user'; + throw 'Couldn\'t delete user'; } else { this.Users.splice(userIndex, 1); } @@ -294,7 +323,9 @@ export class SimplePeer { * Unregisters any held event handler. */ public unregister() { - mediaManager.removeUpdateLocalStreamEventListener(this.sendLocalVideoStreamCallback); + for (const unsubscriber of this.unsubscribers) { + unsubscriber(); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -302,7 +333,13 @@ export class SimplePeer { try { //if offer type, create peer connection if(data.signal.type === "offer"){ - this.createPeerConnection(data); + const streamResult = get(localStreamStore); + let stream : MediaStream | null = null; + if (streamResult.type === 'success' && streamResult.stream) { + stream = streamResult.stream; + } + + this.createPeerConnection(data, stream); } const peer = this.PeerConnectionArray.get(data.userId); if (peer !== undefined) { @@ -318,18 +355,26 @@ export class SimplePeer { private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) { if (blackListManager.isBlackListed(data.userId)) return; console.log("receiveWebrtcScreenSharingSignal", data); + const streamResult = get(screenSharingLocalStreamStore); + let stream : MediaStream | null = null; + if (streamResult.type === 'success' && streamResult.stream !== null) { + stream = streamResult.stream; + } + try { //if offer type, create peer connection if(data.signal.type === "offer"){ - this.createPeerScreenSharingConnection(data); + this.createPeerScreenSharingConnection(data, stream); } const peer = this.PeerScreenSharingConnectionArray.get(data.userId); if (peer !== undefined) { peer.signal(data.signal); } else { console.error('Could not find peer whose ID is "'+data.userId+'" in receiveWebrtcScreenSharingSignal'); - console.info('tentative to create new peer connexion'); - this.sendLocalScreenSharingStreamToUser(data.userId); + console.info('Attempt to create new peer connexion'); + if (stream) { + this.sendLocalScreenSharingStreamToUser(data.userId, stream); + } } } catch (e) { console.error(`receiveWebrtcSignal => ${data.userId}`, e); @@ -339,21 +384,19 @@ export class SimplePeer { } } - private pushVideoToRemoteUser(userId : number) { + private pushVideoToRemoteUser(userId: number, streamResult: LocalStreamStoreValue) { try { const PeerConnection = this.PeerConnectionArray.get(userId); if (!PeerConnection) { throw new Error('While adding media, cannot find user with ID ' + userId); } - const result = get(localStreamStore); + PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...streamResult.constraints}))); - PeerConnection.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...result.constraints}))); - - if (result.type === 'error') { + if (streamResult.type === 'error') { return; } - const localStream: MediaStream | null = result.stream; + const localStream: MediaStream | null = streamResult.stream; if(!localStream){ return; @@ -370,15 +413,11 @@ export class SimplePeer { } } - private pushScreenSharingToRemoteUser(userId : number) { + private pushScreenSharingToRemoteUser(userId: number, localScreenCapture: MediaStream) { const PeerConnection = this.PeerScreenSharingConnectionArray.get(userId); if (!PeerConnection) { throw new Error('While pushing screen sharing, cannot find user with ID ' + userId); } - const localScreenCapture: MediaStream | null = mediaManager.localScreenCapture; - if(!localScreenCapture){ - return; - } for (const track of localScreenCapture.getTracks()) { PeerConnection.addTrack(track, localScreenCapture); @@ -386,23 +425,18 @@ export class SimplePeer { return; } - public sendLocalVideoStream(){ + public sendLocalVideoStream(streamResult: LocalStreamStoreValue){ for (const user of this.Users) { - this.pushVideoToRemoteUser(user.userId); + this.pushVideoToRemoteUser(user.userId, streamResult); } } /** * Triggered locally when clicking on the screen sharing button */ - public sendLocalScreenSharingStream() { - if (!mediaManager.localScreenCapture) { - console.error('Could not find localScreenCapture to share') - return; - } - + public sendLocalScreenSharingStream(localScreenCapture: MediaStream) { for (const user of this.Users) { - this.sendLocalScreenSharingStreamToUser(user.userId); + this.sendLocalScreenSharingStreamToUser(user.userId, localScreenCapture); } } @@ -415,11 +449,11 @@ export class SimplePeer { } } - private sendLocalScreenSharingStreamToUser(userId: number): void { + private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void { if (blackListManager.isBlackListed(userId)) return; // If a connection already exists with user (because it is already sharing a screen with us... let's use this connection) if (this.PeerScreenSharingConnectionArray.has(userId)) { - this.pushScreenSharingToRemoteUser(userId); + this.pushScreenSharingToRemoteUser(userId, localScreenCapture); return; } @@ -427,7 +461,7 @@ export class SimplePeer { userId, initiator: true }; - const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser); + const PeerConnectionScreenSharing = this.createPeerScreenSharingConnection(screenSharingUser, localScreenCapture); if (!PeerConnectionScreenSharing) { return; } diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index 32e8e97f..5ca8952c 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -27,7 +27,7 @@ export class VideoPeer extends Peer { private onBlockSubscribe: Subscription; private onUnBlockSubscribe: Subscription; - constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) { + constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection, localStream: MediaStream | null) { super({ initiator: initiator ? initiator : false, //reconnectTimer: 10000, @@ -107,7 +107,7 @@ export class VideoPeer extends Peer { this._onFinish(); }); - this.pushVideoToRemoteUser(); + this.pushVideoToRemoteUser(localStream); this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => { if (userId === this.userId) { this.toggleRemoteStream(false); @@ -190,9 +190,8 @@ export class VideoPeer extends Peer { } } - private pushVideoToRemoteUser() { + private pushVideoToRemoteUser(localStream: MediaStream | null) { try { - const localStream: MediaStream | null = mediaManager.localStream; this.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_CONSTRAINT, ...get(obtainedMediaConstraintStore)}))); if(!localStream){ diff --git a/front/src/index.ts b/front/src/index.ts index f9017c24..90d4c612 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -13,7 +13,6 @@ import WebFontLoaderPlugin from 'phaser3-rex-plugins/plugins/webfontloader-plugi import {EntryScene} from "./Phaser/Login/EntryScene"; import {coWebsiteManager} from "./WebRtc/CoWebsiteManager"; import {MenuScene} from "./Phaser/Menu/MenuScene"; -import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene"; import {localUserStore} from "./Connexion/LocalUserStore"; import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene"; import {iframeListener} from "./Api/IframeListener"; @@ -96,7 +95,7 @@ const config: GameConfig = { ErrorScene, CustomizeScene, MenuScene, - HelpCameraSettingsScene], + ], //resolution: window.devicePixelRatio / 2, fps: fps, dom: { @@ -152,7 +151,9 @@ iframeListener.init(); const app = new App({ target: HtmlUtils.getElementByIdOrFail('svelte-overlay'), - props: { }, + props: { + game: game + }, }) export default app diff --git a/front/style/fonts.scss b/front/style/fonts.scss index 5ef9b9b4..a49d3967 100644 --- a/front/style/fonts.scss +++ b/front/style/fonts.scss @@ -1 +1,9 @@ -@import "~@fontsource/press-start-2p/index.css"; \ No newline at end of file +@import "~@fontsource/press-start-2p/index.css"; + +*{ + font-family: PixelFont-7,monospace; +} + +.nes-btn { + font-family: "Press Start 2P"; +} diff --git a/front/style/index.scss b/front/style/index.scss index 47f13c3b..7ed141cd 100644 --- a/front/style/index.scss +++ b/front/style/index.scss @@ -1,5 +1,5 @@ @import "cowebsite.scss"; @import "cowebsite-mobile.scss"; -@import "style.css"; +@import "style"; @import "mobile-style.scss"; -@import "fonts.scss"; \ No newline at end of file +@import "fonts.scss"; diff --git a/front/style/style.css b/front/style/style.scss similarity index 95% rename from front/style/style.css rename to front/style/style.scss index 13c2331b..6d439bac 100644 --- a/front/style/style.css +++ b/front/style/style.scss @@ -136,6 +136,7 @@ body .message-info.warning{ .video-container.div-myCamVideo{ border: none; + background-color: transparent; } .div-myCamVideo { @@ -152,6 +153,8 @@ body .message-info.warning{ } video.myCamVideo{ + background-color: #00000099; + max-height: 20vh; width: 15vw; -webkit-transform: scaleX(-1); transform: scaleX(-1); @@ -363,39 +366,6 @@ video.myCamVideo{ } } -.webrtcsetup{ - display: none; - position: absolute; - top: 140px; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - height: 50%; - width: 50%; - border: white 6px solid; -} -.webrtcsetup .background-img { - position: relative; - display: block; - width: 40%; - height: 60%; - margin-left: auto; - margin-right: auto; - top: 50%; - transform: translateY(-50%); -} -#myCamVideoSetup { - width: 100%; - height: 100%; - -webkit-transform: scaleX(-1); - transform: scaleX(-1); -} -.webrtcsetup.active{ - display: block; -} - - /* New layout */ body { margin: 0; @@ -909,35 +879,6 @@ input[type=range]:focus::-ms-fill-upper { } - -/*audio html when audio message playing*/ -.main-container .audio-playing { - position: absolute; - width: 200px; - height: 54px; - right: -210px; - top: 40px; - transition: all 0.1s ease-out; - background-color: black; - border-radius: 30px 0 0 30px; - display: inline-flex; -} - -.main-container .audio-playing.active{ - right: 0; -} -.main-container .audio-playing img{ - /*width: 30px;*/ - border-radius: 50%; - background-color: #ffda01; - padding: 10px; -} -.main-container .audio-playing p{ - color: white; - margin-left: 10px; - margin-top: 14px; -} - /* VIDEO QUALITY */ .main-console div.setting h1{ color: white; @@ -1313,4 +1254,22 @@ div.action.danger p.action-body{ width: 100%; height: 100%; pointer-events: none; + + & > div { + position: relative; + width: 100%; + height: 100%; + + & > div { + position: absolute; + width: 100%; + height: 100%; + } + + & > div.scrollable { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + pointer-events: auto; + } + } } diff --git a/front/webpack.config.ts b/front/webpack.config.ts index 6ebf907f..3a69b74a 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,6 +7,7 @@ import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import sveltePreprocess from 'svelte-preprocess'; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import NodePolyfillPlugin from 'node-polyfill-webpack-plugin'; +import {DISPLAY_TERMS_OF_USE} from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? 'development'; const isProduction = mode === 'production'; @@ -88,7 +89,16 @@ module.exports = { preprocess: sveltePreprocess({ scss: true, sass: true, - }) + }), + onwarn: function (warning: { code: string }, handleWarning: (warning: { code: string }) => void) { + // See https://github.com/sveltejs/svelte/issues/4946#issuecomment-662168782 + + if (warning.code === 'a11y-no-onchange') { return } + if (warning.code === 'a11y-autofocus') { return } + + // process as usual + handleWarning(warning); + } } } }, @@ -175,7 +185,8 @@ module.exports = { 'JITSI_PRIVATE_MODE': null, 'START_ROOM_URL': null, 'MAX_USERNAME_LENGTH': 8, - 'MAX_PER_GROUP': 4 + 'MAX_PER_GROUP': 4, + 'DISPLAY_TERMS_OF_USE': false, }) ], diff --git a/front/yarn.lock b/front/yarn.lock index c06e8a04..fde26272 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -30,6 +30,13 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/runtime@^7.14.0": + version "7.14.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" + integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== + dependencies: + regenerator-runtime "^0.13.4" + "@discoveryjs/json-ext@^0.5.0": version "0.5.3" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d" @@ -783,6 +790,14 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +automation-events@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/automation-events/-/automation-events-4.0.1.tgz#93acef8a457cbea65f16fdcef8f349fd2fafe298" + integrity sha512-8bQx+PVtNDMD5F2H40cQs7oexZve3Z0xC9fWRQT4fltF65f/WsSpjM4jpulL4K4yLLB71oi4/xVJJCJ5I/Kjbw== + dependencies: + "@babel/runtime" "^7.14.0" + tslib "^2.2.0" + available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" @@ -4331,6 +4346,11 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -4895,6 +4915,15 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +standardized-audio-context@^25.2.4: + version "25.2.4" + resolved "https://registry.yarnpkg.com/standardized-audio-context/-/standardized-audio-context-25.2.4.tgz#d64dbdd70615171ec90d1b7151a0d945af94cf3d" + integrity sha512-uQKZXRnXrPVO1V6SwZ7PiV3RkQqRY3n7i6Q8nbTXYvoz8NftRNzfOIlwefpuC8LVLUUs9dhbKTpP+WOA82dkBw== + dependencies: + "@babel/runtime" "^7.14.0" + automation-events "^4.0.1" + tslib "^2.2.0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -5225,7 +5254,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: +tslib@^2.0.3, tslib@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== @@ -5639,9 +5668,9 @@ wrappy@1: integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" diff --git a/maps/tests/help_camera_setting.json b/maps/tests/help_camera_setting.json new file mode 100644 index 00000000..2dcdec3a --- /dev/null +++ b/maps/tests/help_camera_setting.json @@ -0,0 +1,164 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], + "height":10, + "id":3, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":1, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"floorLayer", + "objects":[ + { + "height":254.57168784029, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":12, + "text":"Test 1 : \nBlock permission to camera and\/or microphone access.\n\nResult 1 :\nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n\nTest 2 : \nReload the page and block permission to camera and\/or microphone access on the camera setting page.\n\nResult 2 : \nOrange popup show at the bottom of the screen.\nIf you click on it, the HelpCameraSetting popup open.\n", + "wrap":true + }, + "type":"", + "visible":true, + "width":295.278811252269, + "x":12.2517014519056, + "y":49.3021778584392 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":6, + "nextobjectid":2, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.4.3", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"Validation\/tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"dungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":0, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":1, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":2, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":5, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":8, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":10, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":1.4, + "width":10 +} \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index a17a3b5d..9c95c281 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -82,6 +82,14 @@ Test energy consumption + + + Success Failure Pending + + + Test the HelpCameraSettingScene + +