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

This commit is contained in:
GRL 2021-05-31 17:53:44 +02:00
commit 047337ae81
16 changed files with 237 additions and 331 deletions

View File

@ -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

View File

@ -1,126 +0,0 @@
<style>
#enableCameraScene {
background: #000000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 20px auto 0;
color: #ebeeee;
max-height: 48vh;
width: 42vw;
max-width: 300px;
overflow: hidden;
}
#enableCameraScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#enableCameraScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#enableCameraScene section.title {
position: absolute;
top: 1vh;
width: 100%;
}
#enableCameraScene section.action{
text-align: center;
margin: 0;
position: absolute;
top: 40vh;
width: 100%;
}
#enableCameraScene button {
margin: 10px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
}
#enableCameraScene button#enableCameraSceneFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#enableCameraScene section h6,
#enableCameraScene section h5{
margin: 1px;
}
#enableCameraScene section.text-center{
text-align: center;
}
#enableCameraScene section a{
font-size: 8px;
text-decoration: underline;
color: #ebeeee;
}
#enableCameraScene section a:hover{
font-weight: 700;
}
#enableCameraScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#enableCameraScene section p.err{
color: red;
text-align: center;
}
#enableCameraScene section p.info{
display: none;
text-align: center;
}
#enableCameraScene section input#enableCameraSceneLink{
background-color: #a1a3a3;
}
#enableCameraScene section img{
width: 160px;
margin: 20px 0;
}
/*@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#enableCameraScene{
overflow-y: scroll;
}
}*/
</style>
<form id="enableCameraScene" hidden>
<!-- FIX me -->
<section class="title text-center">
<h5>Turn on your camera and microphone</h5>
</section>
<!--<section class="text-center">
<video id="myCamVideoSetup" autoplay muted></video>
</section>
<section class="text-center">
<h5>Select your camera</h3>
<select id="camera">
</select>
</section>
<section class="text-center">
<h5>Select your michrophone</h3>
<select id="michrophone">
</select>
</section>-->
<section class="action">
<button type="submit" id="enableCameraSceneFormSubmit">Let's go!</button>
</section>
</form>

View File

@ -1,117 +0,0 @@
<style>
#loginScene {
background: #000000;
/*border: 1px solid #ebeeee;*/
border-radius: 6px;
margin: 20px auto 0;
width: 90%;
max-width: 200px;
color: #ebeeee;
max-height: 40vh;
overflow: hidden;
}
#loginScene h1 {
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
border-bottom: 1px solid #a6abaf;
border-radius: 6px 6px 0 0;
box-sizing: border-box;
color: #727678;
display: block;
height: 43px;
padding-top: 10px;
margin: 0;
text-align: center;
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
}
#loginScene input {
font-size: 70%;
background: linear-gradient(top, #d6d7d7, #dee0e0);
border: 1px solid #a1a3a3;
border-radius: 4px;
box-shadow: 0 1px #fff;
box-sizing: border-box;
color: #696969;
height: 30px;
transition: box-shadow 0.3s;
width: 100%;
text-align: center;
}
#loginScene section {
margin: 10px;
}
#loginScene section.action{
text-align: center;
margin: 0;
}
#loginScene button {
margin: 10px;
background-color: black;;
color: #ebeeee;
border-radius: 7px;
padding-bottom: 4px;
width: 100px;
}
#loginScene button#loginSceneFormCancel {
background-color: #c7c7c700;
color: #292929;
}
#loginScene section h6,
#loginScene section h5{
margin: 1px;
}
#loginScene section.text-center{
text-align: center;
}
#loginScene section a{
font-size: 8px;
text-decoration: underline;
color: #ebeeee;
}
#loginScene section a:hover{
font-weight: 700;
}
#loginScene section p{
text-align: left;
font-size: 8px;
margin: 10px 10px;
}
#loginScene section p.err{
color: red;
text-align: center;
}
#loginScene section p.info{
display: none;
text-align: center;
}
#loginScene section input#loginSceneLink{
background-color: #a1a3a3;
}
#loginScene section img{
width: 160px;
margin: 20px 0;
}
@media only screen and (max-width: 800px),
only screen and (max-height: 600px) {
#loginScene{
overflow-y: scroll;
}
}
</style>
<form id="loginScene" hidden>
<section class="text-center">
<img src="resources/logos/logo.png">
</section>
<section class="text-center">
<h5>Enter your name</h5>
<p class="info">9 chars maximum</p>
<p class="err" id="errorLoginScene"></p>
</section>
<section>
<input type="text" name="email" id="loginSceneName">
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
</section>
<section class="action">
<button type="submit" id="loginSceneFormSubmit">Continue</button>
</section>
</form>

View File

@ -1,7 +1,7 @@
<script lang="typescript">
import MenuIcon from "./Menu/MenuIcon.svelte";
import {menuIconVisible} from "../Stores/MenuStore";
import {gameOverlayVisibilityStore} from "../Stores/MediaStore";
import {enableCameraSceneVisibilityStore, gameOverlayVisibilityStore} from "../Stores/MediaStore";
import CameraControls from "./CameraControls.svelte";
import MyCamera from "./MyCamera.svelte";
import SelectCompanionScene from "./SelectCompanion/SelectCompanionScene.svelte";
@ -9,16 +9,20 @@
import {selectCharacterSceneVisibleStore} from "../Stores/SelectCharacterStore";
import SelectCharacterScene from "./selectCharacter/SelectCharacterScene.svelte";
import {customCharacterSceneVisibleStore} from "../Stores/CustomCharacterStore";
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
import LoginScene from "./Login/LoginScene.svelte";
import {loginSceneVisibleStore} from "../Stores/LoginSceneStore";
import EnableCameraScene from "./EnableCamera/EnableCameraScene.svelte";
import {Game} from "../Phaser/Game/Game";
import CustomCharacterScene from "./CustomCharacterScene/CustomCharacterScene.svelte";
export let game: Game;
</script>
<div>
{#if $loginSceneVisibleStore}
<LoginScene game={game}></LoginScene>
{/if}
{#if $selectCharacterSceneVisibleStore}
<SelectCharacterScene game={ game }></SelectCharacterScene>
{/if}
@ -28,6 +32,9 @@
{#if $selectCompanionSceneVisibleStore}
<SelectCompanionScene game={ game }></SelectCompanionScene>
{/if}
{#if $enableCameraSceneVisibilityStore}
<EnableCameraScene game={game}></EnableCameraScene>
{/if}
<!-- {#if $menuIconVisible}
<MenuIcon />
{/if} -->

View File

@ -0,0 +1,76 @@
<script lang="typescript">
import {Game} from "../../Phaser/Game/Game";
import {EnableCameraScene} from "../../Phaser/Login/EnableCameraScene";
export let game: Game;
const enableCameraScene = game.scene.scenes.find((scene) => scene instanceof EnableCameraScene);
function submit() {
enableCameraScene.login();
}
</script>
<form class="enableCameraScene" on:submit|preventDefault={submit}>
<section class="title text-center">
<h2>Turn on your camera and microphone</h2>
</section>
<!--<section class="text-center">
<video id="myCamVideoSetup" autoplay muted></video>
</section>
<section class="text-center">
<h5>Select your camera</h3>
<select id="camera">
</select>
</section>
<section class="text-center">
<h5>Select your michrophone</h3>
<select id="michrophone">
</select>
</section>-->
<section class="action">
<button type="submit" class="nes-btn is-primary letsgo" >Let's go!</button>
</section>
</form>
<style lang="scss">
.enableCameraScene {
pointer-events: auto;
/*background: #000000;*/
margin: 20px auto 0;
color: #ebeeee;
/*max-height: 48vh;
width: 42vw;
max-width: 300px;*/
overflow: hidden;
section.title {
position: absolute;
top: 1vh;
width: 100%;
}
section.action{
text-align: center;
margin: 0;
position: absolute;
top: 85vh;
width: 100%;
}
h2{
font-family: "Press Start 2P";
margin: 1px;
}
section.text-center{
text-align: center;
}
button.letsgo {
font-size: 200%;
}
}
</style>

View File

@ -0,0 +1,120 @@
<script lang="typescript">
import {Game} from "../../Phaser/Game/Game";
import {LoginScene} from "../../Phaser/Login/LoginScene";
import {DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH} from "../../Enum/EnvironmentVariable";
import logoImg from "../images/logo.png";
import {gameManager} from "../../Phaser/Game/GameManager";
import {maxUserNameLength} from "../../Connexion/LocalUser";
export let game: Game;
const loginScene = game.scene.scenes.find((scene) => scene instanceof LoginScene);
let name = gameManager.getPlayerName() || '';
let startValidating = false;
function submit() {
startValidating = true;
let finalName = name.trim();
if (finalName !== '') {
loginScene.login(finalName);
}
}
</script>
<form class="loginScene" on:submit|preventDefault={submit}>
<section class="text-center">
<img src={logoImg} alt="WorkAdventure logo" />
</section>
<section class="text-center">
<h2>Enter your name</h2>
</section>
<input type="text" name="loginSceneName" class="nes-input is-dark" maxlength={MAX_USERNAME_LENGTH} bind:value={name} on:keypress={() => {startValidating = true}} class:is-error={name.trim() === '' && startValidating} />
<section class="error-section">
{#if name.trim() === '' && startValidating }
<p class="err">The name is empty</p>
{/if}
</section>
{#if !DISPLAY_TERMS_OF_USE}
<section class="terms-and-conditions">
<p>By continuing, you are agreeing our <a href="https://workadventu.re/terms-of-use" target="_blank">terms of use</a>, <a href="https://workadventu.re/privacy-policy" target="_blank">privacy policy</a> and <a href="https://workadventu.re/cookie-policy" target="_blank">cookie policy</a>.</p>
</section>
{/if}
<section class="action">
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">Continue</button>
</section>
</form>
<style lang="scss">
.loginScene {
font-family: "Press Start 2P";
pointer-events: auto;
margin: 20px auto 0;
width: 90%;
color: #ebeeee;
display: flex;
flex-flow: column wrap;
align-items: center;
input {
text-align: center;
font-family: "Press Start 2P";
max-width: 400px;
}
.terms-and-conditions {
max-width: 400px;
}
section.error-section {
min-height: 2rem;
margin: 0;
p {
margin: 0;
}
}
}
.loginScene section {
margin: 10px;
}
.loginScene section.action{
text-align: center;
margin-top: 20px;
}
.loginScene section h2{
font-family: "Press Start 2P";
margin: 1px;
}
.loginScene section.text-center{
text-align: center;
}
.loginScene section a{
text-decoration: underline;
color: #ebeeee;
}
.loginScene section a:hover{
font-weight: 700;
}
.loginScene section p{
text-align: left;
margin: 10px 10px;
}
.loginScene p.err{
color: #ce372b;
text-align: center;
}
/*.loginScene section p.info{
display: none;
text-align: center;
}*/
.loginScene section img{
width: 100%;
margin: 20px 0;
}
</style>

View File

@ -41,9 +41,6 @@
pointer-events: auto;
color: #ebeeee;
}
.selectCompanionScene button {
font-family: "Press Start 2P";
}
.selectCompanionScene section {
margin: 10px;
}
@ -80,4 +77,4 @@
}
</style>
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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 {

View File

@ -14,6 +14,7 @@ const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player
export const MAX_USERNAME_LENGTH = parseInt(process.env.MAX_USERNAME_LENGTH || '') || 8;
export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || '4');
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == 'true';
export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window.innerHeight <= 600 ) );

View File

@ -12,8 +12,8 @@ import { MenuScene } from "../Menu/MenuScene";
import {ResizableScene} from "./ResizableScene";
import {
audioConstraintStore,
enableCameraSceneVisibilityStore,
localStreamStore,
enableCameraSceneVisibilityStore,
mediaStreamConstraintsStore,
videoConstraintStore
} from "../../Stores/MediaStore";
@ -28,8 +28,6 @@ enum LoginTextures {
arrowUp = "arrow_up"
}
const enableCameraSceneKey = 'enableCameraScene';
export class EnableCameraScene extends ResizableScene {
private textField!: TextField;
private cameraNameField!: TextField;
@ -45,8 +43,6 @@ export class EnableCameraScene extends ResizableScene {
private soundMeterSprite!: SoundMeterSprite;
private microphoneNameField!: TextField;
private enableCameraSceneElement!: Phaser.GameObjects.DOMElement;
private mobileTapZone!: Zone;
private localStreamStoreUnsubscriber!: Unsubscriber;
@ -58,9 +54,6 @@ export class EnableCameraScene extends ResizableScene {
}
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");
@ -69,18 +62,6 @@ export class EnableCameraScene extends ResizableScene {
}
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);
}
@ -272,19 +253,17 @@ export class EnableCameraScene extends ResizableScene {
this.arrowUp.x = this.microphoneNameField.x - this.microphoneNameField.width / 2 - 16;
this.arrowUp.y = this.microphoneNameField.y;
const actionBtn = document.querySelector<HTMLDivElement>('#enableCameraScene .action');
/*const actionBtn = document.querySelector<HTMLDivElement>('#enableCameraScene .action');
if (actionBtn !== null) {
actionBtn.style.top = (this.scale.height - 65) + 'px';
}
}*/
}
update(time: number, delta: number): void {
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
this.centerXDomElement(this.enableCameraSceneElement, 300);
}
private login(): void {
public login(): void {
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
this.soundMeter.stop();

View File

@ -1,17 +1,12 @@
import {gameManager} from "../Game/GameManager";
import {SelectCharacterSceneName} from "./SelectCharacterScene";
import {ResizableScene} from "./ResizableScene";
import { localUserStore } from "../../Connexion/LocalUserStore";
import {MenuScene} from "../Menu/MenuScene";
import { isUserNameValid } from "../../Connexion/LocalUser";
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
export const LoginSceneName = "LoginScene";
const loginSceneKey = 'loginScene';
export class LoginScene extends ResizableScene {
private loginSceneElement!: Phaser.GameObjects.DOMElement;
private name: string = '';
constructor() {
@ -22,65 +17,25 @@ export class LoginScene extends ResizableScene {
}
preload() {
this.load.html(loginSceneKey, 'resources/html/loginScene.html');
}
create() {
this.loginSceneElement = this.add.dom(-1000, 0).createFromCache(loginSceneKey);
this.centerXDomElement(this.loginSceneElement, 200);
MenuScene.revealMenusAfterInit(this.loginSceneElement, loginSceneKey);
const pErrorElement = this.loginSceneElement.getChildByID('errorLoginScene') as HTMLInputElement;
const inputElement = this.loginSceneElement.getChildByID('loginSceneName') as HTMLInputElement;
inputElement.value = localUserStore.getName() ?? '';
inputElement.focus();
inputElement.addEventListener('keypress', (event: KeyboardEvent) => {
if(inputElement.value.length > 7){
event.preventDefault();
return;
}
pErrorElement.innerHTML = '';
if(inputElement.value && !isUserNameValid(inputElement.value)){
pErrorElement.innerHTML = 'Invalid user name: 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(): void {
this.centerXDomElement(this.loginSceneElement, 200);
}
}

View File

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

View File

@ -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);
});

View File

@ -2,4 +2,8 @@
*{
font-family: PixelFont-7,monospace;
}
}
.nes-btn {
font-family: "Press Start 2P";
}

View File

@ -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';
@ -175,7 +176,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,
})
],