Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Thomas Basler 2021-05-28 20:58:11 +02:00
commit cb8971c7c6
28 changed files with 289 additions and 129 deletions

View File

@ -12,6 +12,11 @@
- The emote menu can be opened by clicking on your character.
- Clicking on one of its element will close the menu and play an emote above your character.
- This emote can be seen by other players.
- Player names were improved. (@Kharhamel)
- We now create a GameObject.Text instead of GameObject.BitmapText
- now use the 'Press Start 2P' font family and added an outline
- As a result, we can now allow non-standard letters like french accents or chinese characters!
- Mobile support has been improved
- WorkAdventure automatically sets the zoom level based on the viewport size to ensure a sensible size of the map is visible, whatever the viewport used
- Mouse wheel support to zoom in / out

View File

@ -29,7 +29,6 @@
<base href="/">
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
<link href="https://unpkg.com/nes.css@2.3.0/css/nes.min.css" rel="stylesheet" />
<title>Binary Kitchen World</title>
@ -53,32 +52,6 @@
<img id="copyright-close" src="resources/logos/gray-noncopy.svg">
</div>
</div>
<div id="activeCam" class="activeCam">
<div id="div-myCamVideo" class="video-container">
<video id="myCamVideo" autoplay muted></video>
<div id="mySoundMeter" class="sound-progress">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
<div class="btn-cam-action">
<div id="btn-monitor" class="btn-monitor">
<img id="monitor" src="resources/logos/monitor.svg">
<img id="monitor-close" src="resources/logos/monitor-close.svg">
</div>
<div id="btn-video" class="btn-video">
<img id="cinema" src="resources/logos/cinema.svg">
<img id="cinema-close" src="resources/logos/cinema-close.svg">
</div>
<div id="btn-micro" class="btn-micro">
<img id="microphone" src="resources/logos/microphone.svg">
<img id="microphone-close" src="resources/logos/microphone-close.svg">
</div>
</div>
</div>
</div>
<div id="cowebsite" class="cowebsite hidden">

5
front/dist/resources/fonts/fonts.css vendored Normal file
View File

@ -0,0 +1,5 @@
/*This file is a workaround to allow phaser to load directly this font */
@font-face {
font-family: "Press Start 2P";
src: url("/fonts/press-start-2p-latin-400-normal.woff2") format('woff2');
}

View File

@ -35,6 +35,7 @@
},
"dependencies": {
"@types/hls.js": "^0.13.3",
"@fontsource/press-start-2p": "^4.3.0",
"@types/simple-peer": "^9.6.0",
"@types/socket.io-client": "^1.4.32",
"axios": "^0.21.1",

View File

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

View File

@ -0,0 +1,59 @@
<script lang="typescript">
import {requestedScreenSharingState, screenSharingAvailableStore} from "../Stores/ScreenSharingStore";
import {requestedCameraState, requestedMicrophoneState} from "../Stores/MediaStore";
import monitorImg from "./images/monitor.svg";
import monitorCloseImg from "./images/monitor-close.svg";
import cinemaImg from "./images/cinema.svg";
import cinemaCloseImg from "./images/cinema-close.svg";
import microphoneImg from "./images/microphone.svg";
import microphoneCloseImg from "./images/microphone-close.svg";
function screenSharingClick(): void {
if ($requestedScreenSharingState === true) {
requestedScreenSharingState.disableScreenSharing();
} else {
requestedScreenSharingState.enableScreenSharing();
}
}
function cameraClick(): void {
if ($requestedCameraState === true) {
requestedCameraState.disableWebcam();
} else {
requestedCameraState.enableWebcam();
}
}
function microphoneClick(): void {
if ($requestedMicrophoneState === true) {
requestedMicrophoneState.disableMicrophone();
} else {
requestedMicrophoneState.enableMicrophone();
}
}
</script>
<div class="btn-cam-action">
<div class="btn-monitor" on:click={screenSharingClick} class:hide={!$screenSharingAvailableStore}>
{#if $requestedScreenSharingState}
<img src={monitorImg} alt="Start screen sharing">
{:else}
<img src={monitorCloseImg} alt="Stop screen sharing">
{/if}
</div>
<div class="btn-video" on:click={cameraClick}>
{#if $requestedCameraState}
<img src={cinemaImg} alt="Turn on webcam">
{:else}
<img src={cinemaCloseImg} alt="Turn off webcam">
{/if}
</div>
<div class="btn-micro" on:click={microphoneClick}>
{#if $requestedMicrophoneState}
<img src={microphoneImg} alt="Turn on microphone">
{:else}
<img src={microphoneCloseImg} alt="Turn off microphone">
{/if}
</div>
</div>

View File

@ -0,0 +1,46 @@
<script lang="typescript">
import {localStreamStore} from "../Stores/MediaStore";
import SoundMeterWidget from "./SoundMeterWidget.svelte";
import {onDestroy} from "svelte";
function srcObject(node, stream) {
node.srcObject = stream;
return {
update(newStream) {
if (node.srcObject != newStream) {
node.srcObject = newStream
}
}
}
}
let stream : MediaStream|null;
/*$: {
if ($localStreamStore.type === 'success') {
stream = $localStreamStore.stream;
} else {
stream = null;
}
}*/
const unsubscribe = localStreamStore.subscribe(value => {
if (value.type === 'success') {
stream = value.stream;
} else {
stream = null;
}
});
onDestroy(unsubscribe);
</script>
<div>
<div class="video-container div-myCamVideo" class:hide={!$localStreamStore.constraints.video}>
<video class="myCamVideo" use:srcObject={$localStreamStore.stream} autoplay muted></video>
<!-- {#if stream}
<SoundMeterWidget stream={stream}></SoundMeterWidget>
{/if} -->
</div>
</div>

View File

@ -0,0 +1,36 @@
<script lang="typescript">
import {SoundMeter} from "../Phaser/Components/SoundMeter";
import {onDestroy} from "svelte";
export let stream: MediaStream;
let volume = 0;
console.log('stream', stream);
if (stream.getAudioTracks().length > 0) {
const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext());
const timeout = setInterval(() => {
try{
volume = parseInt((soundMeter.getVolume() / 10).toFixed(0));
console.log(volume);
}catch(err){
}
}, 100);
onDestroy(() => {
clearInterval(timeout);
})
}
</script>
<div class="sound-progress" class:active={stream?.getAudioTracks().length > 0}>
<span class:active={volume > 1}></span>
<span class:active={volume > 2}></span>
<span class:active={volume > 3}></span>
<span class:active={volume > 4}></span>
<span class:active={volume > 5}></span>
</div>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 332.8 332.8" style="enable-background:new 0 0 332.8 332.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g>
<g>
<path class="st0" d="M330.8,171c-3.6-6.4-12-8.8-18.8-4.8l-45.6,26.4l-11.6,6.8v63.2l10.8,6.4c0.4,0,0.4,0.4,0.8,0.4l44.8,26
c2,1.6,4.8,2.4,7.6,2.4c7.6,0,13.6-6,13.6-13.6v-53.6l0.4-52.8C332.8,175.4,332.4,173,330.8,171z"/>
<path class="st0" d="M193.2,150.6c35.6,0,64.4-28.8,64.4-64.4s-28.8-64.4-64.4-64.4s-64.4,28.8-64.4,64.4
C128.8,121.8,157.6,150.6,193.2,150.6z M193.2,59.8c14.8,0,26.4,12,26.4,26.4c0,14.8-12,26.4-26.4,26.4s-26.4-12-26.4-26.4
C166.8,71.4,178.4,59.8,193.2,59.8z"/>
</g>
</g>
</g>
<g>
<g>
</g>
</g>
<rect x="134.8" y="-45.3" transform="matrix(-0.7402 0.6723 -0.6723 -0.7402 376.0669 224.8258)" class="st0" width="19.6" height="460.7"/>
<path class="st0" d="M90.6,83.3c-0.2-2.2-1.3-8.9-6.7-14.9c-5.4-5.9-11.9-7.6-14.1-8.1C59.7,49.2,49.5,38,39.4,26.8
c24.3-9.8,52-4.4,70.2,13.6c19.9,19.7,24.7,50.8,11.5,76.4C110.9,105.6,100.8,94.5,90.6,83.3z"/>
<path class="st0" d="M10.1,51.6c9.4,10.2,18.8,20.4,28.2,30.6c-0.2,1.8-1.4,11.7,5.5,20.5c8.2,10.3,20.7,10.2,22.1,10.1
c9.2,10.3,18.5,20.6,27.7,30.8c-4.8,2.3-24.6,11.2-48.3,4.1c-6-1.8-20.7-7.3-32.1-22C-0.3,108.1-0.2,89.1,0.1,83.4
C0.8,68,6.8,56.8,10.1,51.6z"/>
<g>
<path class="st0" d="M243.4,178.2c0.1,24.5,0.2,49,0.2,73.5c-30.7-33.8-61.3-67.7-92-101.5c5.9,3.9,20.9,12.4,41.6,12.4
c16,0,28.2-5.2,34.4-8.4c2.5,1.5,7,4.6,10.7,10.3C242,170,243,175.4,243.4,178.2z"/>
<g>
<path class="st0" d="M211.2,311C150.8,258.7,90.4,206.5,30,154.2c6.1,3.1,18.2,8.4,34.4,8.4c18.1,0,31.5-6.5,37.5-9.9
c44.5,49,89.1,98.1,133.6,147.1c-1.8,2.1-5.3,5.5-10.6,8.1C219.2,310.6,214,311,211.2,311z"/>
<path class="st0" d="M46.8,311C36,267.7,25.2,224.3,14.4,181c0.1-3.2,0.7-11.3,6.5-18.8c3.1-4.1,6.7-6.6,9.1-8
C90.4,206.5,150.8,258.7,211.2,311C156.4,311,101.6,311,46.8,311z"/>
<path class="st0" d="M14.4,278.6L14.4,278.6c0-32.5,0-65.1,0-97.6c10.8,43.3,21.6,86.7,32.4,130c-2.6,0-12.7-0.4-21.5-8.1
C14.7,293.5,14.4,280.7,14.4,278.6z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 937 B

After

Width:  |  Height:  |  Size: 937 B

View File

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 884 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -7,9 +7,10 @@ export interface CharacterTexture {
rights: string
}
export function isUserNameValid(value: string): boolean {
const regexp = new RegExp('^[A-Za-z0-9-+?_&!]{1,'+MAX_USERNAME_LENGTH+'}$');
return regexp.test(value);
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;
}
export function areCharacterLayersValid(value: string[] | null): boolean {

View File

@ -1,6 +1,6 @@
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
import {SpeechBubble} from "./SpeechBubble";
import BitmapText = Phaser.GameObjects.BitmapText;
import Text = Phaser.GameObjects.Text;
import Container = Phaser.GameObjects.Container;
import Sprite = Phaser.GameObjects.Sprite;
import {TextureError} from "../../Exception/TextureError";
@ -23,7 +23,7 @@ const interactiveRadius = 35;
export abstract class Character extends Container {
private bubble: SpeechBubble|null = null;
private readonly playerName: BitmapText;
private readonly playerName: Text;
public PlayerValue: string;
public sprites: Map<string, Sprite>;
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
@ -55,9 +55,9 @@ export abstract class Character extends Container {
this.addTextures(textures, frame);
this.invisible = false
})
this.playerName = new BitmapText(scene, 0, playerNameY, 'main_font', name, 7);
this.playerName.setOrigin(0.5).setCenterAlign().setDepth(DEPTH_INGAME_TEXT_INDEX);
this.playerName = new Text(scene, 0, playerNameY, name, {fontFamily: '"Press Start 2P"', fontSize: '8px', strokeThickness: 2, stroke: "gray"});
this.playerName.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
this.add(this.playerName);
if (this.isClickable()) {

View File

@ -305,6 +305,14 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.load as any).rexWebFont({
custom: {
families: ['Press Start 2P'],
urls: ['/resources/fonts/fonts.css'],
testString: 'abcdefg'
},
});
//this function must stay at the end of preload function
addLoader(this);

View File

@ -42,7 +42,7 @@ export class LoginScene extends ResizableScene {
}
pErrorElement.innerHTML = '';
if(inputElement.value && !isUserNameValid(inputElement.value)){
pErrorElement.innerHTML = 'Invalid user name: only letters and numbers are allowed. No spaces.';
pErrorElement.innerHTML = 'Invalid user name: No spaces are allowed.';
}
if (event.key === 'Enter') {
event.preventDefault();

View File

@ -46,13 +46,6 @@ export class MediaManager {
localStream: MediaStream|null = null;
localScreenCapture: MediaStream|null = null;
private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>();
myCamVideo: HTMLVideoElement;
cinemaClose: HTMLImageElement;
cinema: HTMLImageElement;
monitorClose: HTMLImageElement;
monitor: HTMLImageElement;
microphoneClose: HTMLImageElement;
microphone: HTMLImageElement;
webrtcInAudio: HTMLAudioElement;
//FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement;
@ -63,10 +56,6 @@ export class MediaManager {
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement;
private focused : boolean = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
@ -80,54 +69,11 @@ export class MediaManager {
constructor() {
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
this.webrtcInAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-in');
this.webrtcOutAudio = HtmlUtils.getElementByIdOrFail<HTMLAudioElement>('audio-webrtc-out');
this.webrtcInAudio.volume = 0.2;
this.webrtcOutAudio.volume = 0.2;
this.microphoneBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-micro');
this.microphoneClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone-close');
this.microphoneClose.style.display = "none";
this.microphoneClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedMicrophoneState.enableMicrophone();
});
this.microphone = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('microphone');
this.microphone.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedMicrophoneState.disableMicrophone();
});
this.cinemaBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-video');
this.cinemaClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema-close');
this.cinemaClose.style.display = "none";
this.cinemaClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedCameraState.enableWebcam();
});
this.cinema = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('cinema');
this.cinema.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
requestedCameraState.disableWebcam();
});
this.monitorBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('btn-monitor');
this.monitorClose = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor-close');
this.monitorClose.style.display = "block";
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
//this.enableScreenSharing();
requestedScreenSharingState.enableScreenSharing();
});
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
this.monitor.style.display = "none";
this.monitor.addEventListener('click', (e: MouseEvent) => {
e.preventDefault();
//this.disableScreenSharing();
requestedScreenSharingState.disableScreenSharing();
});
this.pingCameraStatus();
//FIX ME SOUNDMETER: check stability of sound meter calculation
@ -148,11 +94,11 @@ export class MediaManager {
return;
}
if (result.constraints.video !== false) {
/*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 {
@ -160,13 +106,13 @@ export class MediaManager {
}*/
this.localStream = result.stream;
this.myCamVideo.srcObject = this.localStream;
//this.myCamVideo.srcObject = this.localStream;
// TODO: migrate all listeners to the store directly.
this.triggerUpdatedLocalStreamCallbacks(result.stream);
});
requestedCameraState.subscribe((enabled) => {
/*requestedCameraState.subscribe((enabled) => {
if (enabled) {
this.enableCameraStyle();
} else {
@ -179,7 +125,7 @@ export class MediaManager {
} else {
this.disableMicrophoneStyle();
}
});
});*/
//let screenSharingStream : MediaStream|null;
screenSharingLocalStreamStore.subscribe((result) => {
if (result.type === 'error') {
@ -191,7 +137,7 @@ export class MediaManager {
}
if (result.stream !== null) {
this.enableScreenSharingStyle();
//this.enableScreenSharingStyle();
mediaManager.localScreenCapture = result.stream;
// TODO: migrate this out of MediaManager
@ -202,7 +148,7 @@ export class MediaManager {
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = result.stream;
} else {
this.disableScreenSharingStyle();
//this.disableScreenSharingStyle();
this.removeActiveScreenSharingVideo('me');
// FIXME: we need the old stream that is being stopped!
@ -216,13 +162,13 @@ export class MediaManager {
});
screenSharingAvailableStore.subscribe((available) => {
/*screenSharingAvailableStore.subscribe((available) => {
if (available) {
document.querySelector('.btn-monitor')?.classList.remove('hide');
} else {
document.querySelector('.btn-monitor')?.classList.add('hide');
}
});
});*/
}
public updateScene(){
@ -290,7 +236,7 @@ export class MediaManager {
gameOverlayVisibilityStore.hideGameOverlay();
}
private enableCameraStyle(){
/*private enableCameraStyle(){
this.cinemaClose.style.display = "none";
this.cinemaBtn.classList.remove("disabled");
this.cinema.style.display = "block";
@ -324,7 +270,7 @@ export class MediaManager {
this.monitorClose.style.display = "block";
this.monitor.style.display = "none";
this.monitorBtn.classList.remove("enabled");
}
}*/
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
this.webrtcInAudio.play();

View File

@ -9,7 +9,7 @@ import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
import {SelectCompanionScene} from "./Phaser/Login/SelectCompanionScene";
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
import {ResizableScene} from "./Phaser/Login/ResizableScene";
import WebFontLoaderPlugin from 'phaser3-rex-plugins/plugins/webfontloader-plugin.js';
import {EntryScene} from "./Phaser/Login/EntryScene";
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
import {MenuScene} from "./Phaser/Menu/MenuScene";
@ -107,6 +107,13 @@ const config: GameConfig = {
roundPixels: true,
antialias: false
},
plugins: {
global: [{
key: 'rexWebFontLoader',
plugin: WebFontLoaderPlugin,
start: true
}]
},
physics: {
default: "arcade",
arcade: {

View File

@ -7,6 +7,10 @@ declare module 'phaser3-rex-plugins/plugins/gestures-plugin.js' {
const content: any; // eslint-disable-line
export default content;
}
declare module 'phaser3-rex-plugins/plugins/webfontloader-plugin.js' {
const content: any; // eslint-disable-line
export default content;
}
declare module 'phaser3-rex-plugins/plugins/gestures.js' {
export const Pinch: any; // eslint-disable-line
}

1
front/style/fonts.scss Normal file
View File

@ -0,0 +1 @@
@import "~@fontsource/press-start-2p/index.css";

View File

@ -1,4 +1,5 @@
@import "cowebsite.scss";
@import "cowebsite-mobile.scss";
@import "style.css";
@import "mobile-style.scss";
@import "mobile-style.scss";
@import "fonts.scss";

View File

@ -1,9 +1,24 @@
@media (hover: none) {
/**
* If we cannot hover over elements, let's display camera button in full.
*/
.btn-cam-action {
div {
transform: translateY(0px);
}
}
}
@media screen and (max-width: 700px),
screen and (max-height: 700px){
video#myCamVideo {
video.myCamVideo {
width: 150px;
}
.div-myCamVideo.hide {
right: -160px;
}
.sidebar {
width: 20%;
min-width: 200px;
@ -22,21 +37,6 @@
}
}
.btn-cam-action {
min-width: 150px;
&:hover{
transform: translateY(20px);
}
div {
margin: 0 1%;
&:hover {
background-color: #666;
}
margin-bottom: 30px;
}
}
.main-section {
position: absolute;
width: 100%;

View File

@ -134,11 +134,11 @@ body .message-info.warning{
outline: none;
}
.video-container#div-myCamVideo{
.video-container.div-myCamVideo{
border: none;
}
#div-myCamVideo {
.div-myCamVideo {
position: absolute;
right: 15px;
bottom: 30px;
@ -147,11 +147,11 @@ body .message-info.warning{
transition: right 350ms;
}
#div-myCamVideo.hide {
.div-myCamVideo.hide {
right: -20vw;
}
video#myCamVideo{
video.myCamVideo{
width: 15vw;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
@ -239,7 +239,9 @@ video#myCamVideo{
/*btn animation*/
.btn-cam-action div{
cursor: pointer;
/*position: absolute;*/
display: flex;
align-items: center;
justify-content: center;
border: solid 0px black;
width: 44px;
height: 44px;
@ -299,8 +301,6 @@ video#myCamVideo{
.btn-cam-action div img{
height: 22px;
width: 30px;
top: calc(48px - 37px);
left: calc(48px - 41px);
position: relative;
cursor: pointer;
}

View File

@ -20,8 +20,14 @@ describe("isUserNameValid()", () => {
it("should not validate spaces", () => {
expect(isUserNameValid(' ')).toBe(false);
});
it("should not validate special characters", () => {
expect(isUserNameValid('a@*')).toBe(false);
it("should validate special characters", () => {
expect(isUserNameValid('%&-')).toBe(true);
});
it("should validate accents", () => {
expect(isUserNameValid('éàëè')).toBe(true);
});
it("should validate chinese characters", () => {
expect(isUserNameValid('中文鍵盤')).toBe(true);
});
});

View File

@ -102,9 +102,17 @@ module.exports = {
}
},
{
test: /\.(ttf|eot|svg|png|gif|jpg)$/,
test: /\.(eot|svg|png|gif|jpg)$/,
exclude: /node_modules/,
type: 'asset'
},
{
test: /\.(woff(2)?|ttf)$/,
type: 'asset',
generator: {
filename: 'fonts/[name][ext]'
}
}
],
},

View File

@ -50,6 +50,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@fontsource/press-start-2p@^4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@fontsource/press-start-2p/-/press-start-2p-4.3.0.tgz#37124387f7fbfe7792b5fc9a1906b80d9aeda4c6"
integrity sha512-gmS4070EoZp5/6NUJ+tBnvtDiSmFcR+S+ClAOJ8NGFXDWOkO12yMnyGJEJaDCNCAMX0s2TQCcmr6qWKx5ad3RQ==
"@nodelib/fs.scandir@2.1.4":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"