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

This commit is contained in:
_Bastler 2021-07-29 17:54:15 +02:00
commit 617435ea4c
10 changed files with 339 additions and 215 deletions

35
docs/maps/text.md Normal file
View File

@ -0,0 +1,35 @@
{.section-title.accent.text-primary}
# Writing text on a map
## Solution 1: design a specific tileset (recommended)
If you want to write some text on a map, our recommendation is to create a tileset that contains
your text. You will obtain the most pleasant graphical result with this result, since you will be able
to control the fonts you use, and you will be able to disable the antialiasing of the font to get a
"crispy" result easily.
## Solution 2: using a "text" object in Tiled
On "object" layers, Tiled has support for "Text" objects. You can use these objects to add some
text on your map.
WorkAdventure will do its best to display the text properly. However, you need to know that:
- Tiled displays your system fonts.
- Computers have different sets of fonts. Therefore, browsers never rely on system fonts
- Which means if you select a font in Tiled, it is quite unlikely it will render properly in WorkAdventure
To circumvent this problem, in your text object in Tiled, you can add an additional property: `font-family`.
The `font-family` property can contain any "web-font" that can be loaded by your browser.
{.alert.alert-info}
**Pro-tip:** By default, WorkAdventure uses the **'"Press Start 2P"'** font, which is a great pixelated
font that has support for a variety of accents. It renders great when used at *8px* size.
<div>
<figure class="figure">
<img src="https://workadventu.re/img/docs/text-object.png" class="figure-img img-fluid rounded" alt="" style="width: 70%" />
<figcaption class="figure-caption">The "font-family" property</figcaption>
</figure>
</div>

View File

@ -1,12 +1,27 @@
<script lang="typescript">
import { fly } from 'svelte/transition';
import InputTextGlobalMessage from "./InputTextGlobalMessage.svelte";
import UploadAudioGlobalMessage from "./UploadAudioGlobalMessage.svelte";
import {gameManager} from "../../Phaser/Game/GameManager";
import type {Game} from "../../Phaser/Game/Game";
import { gameManager } from "../../Phaser/Game/GameManager";
import type { Game } from "../../Phaser/Game/Game";
import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
export let game: Game;
let inputSendTextActive = true;
let uploadMusicActive = false;
let handleSendText: { sendTextMessage(): void };
let handleSendAudio: { sendAudioMessage(): Promise<void> };
let broadcastToWorld = false;
function closeConsoleGlobalMessage() {
consoleGlobalMessageManagerVisibleStore.set(false)
}
function onKeyDown(e:KeyboardEvent) {
if (e.key === 'Escape') {
closeConsoleGlobalMessage();
}
}
function inputSendTextActivate() {
inputSendTextActive = true;
@ -17,28 +32,121 @@
uploadMusicActive = true;
inputSendTextActive = false;
}
function send() {
if (inputSendTextActive) {
handleSendText.sendTextMessage();
}
if (uploadMusicActive) {
handleSendAudio.sendAudioMessage();
}
}
</script>
<svelte:window on:keydown={onKeyDown}/>
<div class="main-console nes-container is-rounded">
<!-- <div class="console nes-container is-rounded">
<img class="btn-close" src="resources/logos/send-yellow.svg" alt="Close">
</div>-->
<div class="main-global-message">
<h2> Global Message </h2>
<div class="global-message">
<div class="menu">
<button class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
<button class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
</div>
<div class="main-input">
{#if inputSendTextActive}
<InputTextGlobalMessage game={game} gameManager={gameManager}></InputTextGlobalMessage>
{/if}
{#if uploadMusicActive}
<UploadAudioGlobalMessage game={game} gameManager={gameManager}></UploadAudioGlobalMessage>
{/if}
</div>
<div class="console-global-message">
<div class="menu-console-global-message nes-container is-rounded" transition:fly="{{ x: -1000, duration: 500 }}">
<button type="button" class="nes-btn {inputSendTextActive ? 'is-disabled' : ''}" on:click|preventDefault={inputSendTextActivate}>Message</button>
<button type="button" class="nes-btn {uploadMusicActive ? 'is-disabled' : ''}" on:click|preventDefault={inputUploadMusicActivate}>Audio</button>
</div>
<div class="main-console-global-message nes-container is-rounded" transition:fly="{{ y: -1000, duration: 500 }}">
<div class="title-console-global-message">
<h2>Global Message</h2>
<button type="button" class="nes-btn is-error" on:click|preventDefault={closeConsoleGlobalMessage}><i class="nes-icon close is-small"></i></button>
</div>
<div class="content-console-global-message">
{#if inputSendTextActive}
<InputTextGlobalMessage game={game} gameManager={gameManager} bind:handleSending={handleSendText}/>
{/if}
{#if uploadMusicActive}
<UploadAudioGlobalMessage game={game} gameManager={gameManager} bind:handleSending={handleSendAudio}/>
{/if}
</div>
<div class="footer-console-global-message">
<label>
<input type="checkbox" class="nes-checkbox is-dark nes-pointer" bind:checked={broadcastToWorld}>
<span>Broadcast to all rooms of the world</span>
</label>
<button class="nes-btn is-primary" on:click|preventDefault={send}>Send</button>
</div>
</div>
</div>
</div>
<style lang="scss">
.nes-container {
padding: 0 5px;
}
div.console-global-message {
top: 20vh;
width: 50vw;
height: 50vh;
position: relative;
display: flex;
flex-direction: row;
margin-left: auto;
margin-right: auto;
padding: 0;
pointer-events: auto;
div.menu-console-global-message {
flex: 1 1 auto;
max-width: 180px;
text-align: center;
background-color: #333333;
button {
width: 136px;
margin-bottom: 10px;
}
}
div.main-console-global-message {
flex: 1 1 auto;
display: flex;
flex-direction: column;
background-color: #333333;
div.title-console-global-message {
flex: 0 0 auto;
height: 50px;
margin-bottom: 10px;
text-align: center;
color: whitesmoke;
.nes-btn {
position: absolute;
top: 0;
right: 0;
}
}
div.content-console-global-message {
flex: 1 1 auto;
max-height: calc(100% - 120px);
}
div.footer-console-global-message {
height: 50px;
margin-top: 10px;
text-align: center;
label {
margin: 0;
position: absolute;
left: 0;
max-width: 30%;
}
}
}
}
</style>

View File

@ -1,15 +1,14 @@
<script lang="ts">
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
import {onMount} from "svelte";
import type {Game} from "../../Phaser/Game/Game";
import type {GameManager} from "../../Phaser/Game/GameManager";
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
import type {Quill} from "quill";
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
import { onMount } from "svelte";
import type { Game } from "../../Phaser/Game/Game";
import type { GameManager } from "../../Phaser/Game/GameManager";
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
import type { Quill } from "quill";
//toolbar
export const toolbarOptions = [
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
@ -35,12 +34,31 @@
export let game: Game;
export let gameManager: GameManager;
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
let gameScene = gameManager.getCurrentGameScene(game.findAnyScene());
let quill: Quill;
let INPUT_CONSOLE_MESSAGE: HTMLDivElement;
const MESSAGE_TYPE = AdminMessageEventTypes.admin;
export const handleSending = {
sendTextMessage() {
if (gameScene == undefined) {
return;
}
const text = quill.getText(0, quill.getLength());
const GlobalMessage: PlayGlobalMessageInterface = {
id: "1", // FIXME: use another ID?
message: text,
type: MESSAGE_TYPE
};
quill.deleteText(0, quill.getLength());
gameScene.connection?.emitGlobalMessage(GlobalMessage);
disableConsole();
}
}
//Quill
onMount(async () => {
@ -48,6 +66,7 @@
const {default: Quill} = await import("quill"); // eslint-disable-line @typescript-eslint/no-explicit-any
quill = new Quill(INPUT_CONSOLE_MESSAGE, {
placeholder: 'Enter your message here...',
theme: 'snow',
modules: {
toolbar: toolbarOptions
@ -66,31 +85,10 @@
consoleGlobalMessageManagerVisibleStore.set(false);
consoleGlobalMessageManagerFocusStore.set(false);
}
function SendTextMessage() {
if (gameScene == undefined) {
return;
}
const text = quill.getText(0, quill.getLength());
const GlobalMessage: PlayGlobalMessageInterface = {
id: "1", // FIXME: use another ID?
message: text,
type: MESSAGE_TYPE
};
quill.deleteText(0, quill.getLength());
gameScene.connection?.emitGlobalMessage(GlobalMessage);
disableConsole();
}
</script>
<section class="section-input-send-text">
<div class="input-send-text" bind:this={INPUT_CONSOLE_MESSAGE}></div>
<div class="btn-action">
<button class="nes-btn is-primary" on:click|preventDefault={SendTextMessage}>Send</button>
</div>
</section>

View File

@ -1,12 +1,11 @@
<script lang="ts">
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
import type {Game} from "../../Phaser/Game/Game";
import type {GameManager} from "../../Phaser/Game/GameManager";
import {consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore} from "../../Stores/ConsoleGlobalMessageManagerStore";
import {AdminMessageEventTypes} from "../../Connexion/AdminMessagesService";
import type {PlayGlobalMessageInterface} from "../../Connexion/ConnexionModels";
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import type { Game } from "../../Phaser/Game/Game";
import type { GameManager } from "../../Phaser/Game/GameManager";
import { consoleGlobalMessageManagerFocusStore, consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
import { AdminMessageEventTypes } from "../../Connexion/AdminMessagesService";
import type { PlayGlobalMessageInterface } from "../../Connexion/ConnexionModels";
import uploadFile from "../images/music-file.svg";
import {LoginSceneName} from "../../Phaser/Login/LoginScene";
interface EventTargetFiles extends EventTarget {
files: Array<File>;
@ -15,38 +14,39 @@
export let game: Game;
export let gameManager: GameManager;
let gameScene = gameManager.getCurrentGameScene(game.scene.getScene(LoginSceneName));
let fileinput: HTMLInputElement;
let filename: string;
let filesize: string;
let errorfile: boolean;
let gameScene = gameManager.getCurrentGameScene(game.findAnyScene());
let fileInput: HTMLInputElement;
let fileName: string;
let fileSize: string;
let errorFile: boolean;
const AUDIO_TYPE = AdminMessageEventTypes.audio;
export const handleSending = {
async sendAudioMessage() {
if (gameScene == undefined) {
return;
}
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>("input-send-audio");
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
if (!selectedFile) {
errorFile = true;
throw 'no file selected';
}
async function SendAudioMessage() {
if (gameScene == undefined) {
return;
}
const inputAudio = HtmlUtils.getElementByIdOrFail<HTMLInputElement>("input-send-audio");
const selectedFile = inputAudio.files ? inputAudio.files[0] : null;
if (!selectedFile) {
errorfile = true;
throw 'no file selected';
}
const fd = new FormData();
fd.append('file', selectedFile);
const res = await gameScene.connection?.uploadAudio(fd);
const fd = new FormData();
fd.append('file', selectedFile);
const res = await gameScene.connection?.uploadAudio(fd);
const GlobalMessage: PlayGlobalMessageInterface = {
id: (res as { id: string }).id,
message: (res as { path: string }).path,
type: AUDIO_TYPE
const GlobalMessage: PlayGlobalMessageInterface = {
id: (res as { id: string }).id,
message: (res as { path: string }).path,
type: AUDIO_TYPE
}
inputAudio.value = '';
gameScene.connection?.emitGlobalMessage(GlobalMessage);
disableConsole();
}
inputAudio.value = '';
gameScene.connection?.emitGlobalMessage(GlobalMessage);
disableConsole();
}
function inputAudioFile(event: Event) {
@ -60,9 +60,9 @@
return;
}
filename = file.name;
filesize = getFileSize(file.size);
errorfile = false;
fileName = file.name;
fileSize = getFileSize(file.size);
errorFile = false;
}
function getFileSize(number: number) {
@ -85,46 +85,46 @@
<section class="section-input-send-audio">
<div class="input-send-audio">
<img src="{uploadFile}" alt="Upload a file" on:click|preventDefault={ () => {fileinput.click();}}>
{#if filename != undefined}
<label for="input-send-audio">{filename} : {filesize}</label>
{/if}
{#if errorfile}
<p class="err">No file selected. You need to upload a file before sending it.</p>
{/if}
<input type="file" id="input-send-audio" bind:this={fileinput} on:change={(e) => {inputAudioFile(e)}}>
</div>
<div class="btn-action">
<button class="nes-btn is-primary" on:click|preventDefault={SendAudioMessage}>Send</button>
</div>
<img class="nes-pointer" src="{uploadFile}" alt="Upload a file" on:click|preventDefault={ () => {fileInput.click();}}>
{#if fileName !== undefined}
<p>{fileName} : {fileSize}</p>
{/if}
{#if errorFile}
<p class="err">No file selected. You need to upload a file before sending it.</p>
{/if}
<input type="file" id="input-send-audio" bind:this={fileInput} on:change={(e) => {inputAudioFile(e)}}>
</section>
<style lang="scss">
//UploadAudioGlobalMessage
.section-input-send-audio {
margin: 10px;
}
section.section-input-send-audio {
display: flex;
flex-direction: column;
.section-input-send-audio .input-send-audio {
height: 100%;
text-align: center;
}
.section-input-send-audio #input-send-audio{
display: none;
}
img {
flex: 1 1 auto;
.section-input-send-audio div.input-send-audio label{
color: white;
}
max-height: 80%;
margin-bottom: 20px;
}
.section-input-send-audio div.input-send-audio p.err {
color: #ce372b;
text-align: center;
}
p {
flex: 1 1 auto;
.section-input-send-audio div.input-send-audio img{
height: 150px;
cursor: url('../../../style/images/cursor_pointer.png'), pointer;
margin-bottom: 5px;
color: whitesmoke;
font-size: 1rem;
&.err {
color: #ce372b;
}
}
input {
display: none;
}
}
</style>

View File

@ -1,35 +1,43 @@
import type {ITiledMapObject} from "../Map/ITiledMap";
import type {GameScene} from "../Game/GameScene";
import type { ITiledMapObject } from "../Map/ITiledMap";
import type { GameScene } from "../Game/GameScene";
import { type } from "os";
export class TextUtils {
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
if (object.text === undefined) {
throw new Error('This object has not textual representation.');
throw new Error("This object has not textual representation.");
}
const options: {
fontStyle?: string,
fontSize?: string,
fontFamily?: string,
color?: string,
align?: string,
fontStyle?: string;
fontSize?: string;
fontFamily?: string;
color?: string;
align?: string;
wordWrap?: {
width: number,
useAdvancedWrap?: boolean
}
width: number;
useAdvancedWrap?: boolean;
};
} = {};
if (object.text.italic) {
options.fontStyle = 'italic';
options.fontStyle = "italic";
}
// Note: there is no support for "strikeout" and "underline"
let fontSize: number = 16;
if (object.text.pixelsize) {
fontSize = object.text.pixelsize;
}
options.fontSize = fontSize + 'px';
options.fontSize = fontSize + "px";
if (object.text.fontfamily) {
options.fontFamily = '"'+object.text.fontfamily+'"';
options.fontFamily = '"' + object.text.fontfamily + '"';
}
let color = '#000000';
if (object.properties !== undefined) {
for (const property of object.properties) {
if (property.name === "font-family" && typeof property.value === "string") {
options.fontFamily = property.value;
}
}
}
let color = "#000000";
if (object.text.color !== undefined) {
color = object.text.color;
}
@ -38,7 +46,7 @@ export class TextUtils {
options.wordWrap = {
width: object.width,
//useAdvancedWrap: true
}
};
}
if (object.text.halign !== undefined) {
options.align = object.text.halign;

View File

@ -1,7 +1,7 @@
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
import {waScaleManager} from "../Services/WaScaleManager";
import {ResizableScene} from "../Login/ResizableScene";
import { SKIP_RENDER_OPTIMIZATIONS } from "../../Enum/EnvironmentVariable";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { waScaleManager } from "../Services/WaScaleManager";
import { ResizableScene } from "../Login/ResizableScene";
const Events = Phaser.Core.Events;
@ -14,10 +14,8 @@ const Events = Phaser.Core.Events;
* It also automatically calls "onResize" on any scenes extending ResizableScene.
*/
export class Game extends Phaser.Game {
private _isDirty = false;
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
super(GameConfig);
@ -27,7 +25,7 @@ export class Game extends Phaser.Game {
scene.onResize();
}
}
})
});
/*window.addEventListener('resize', (event) => {
// Let's trigger the onResize method of any active scene that is a ResizableScene
@ -39,11 +37,9 @@ export class Game extends Phaser.Game {
});*/
}
public step(time: number, delta: number)
{
public step(time: number, delta: number) {
// @ts-ignore
if (this.pendingDestroy)
{
if (this.pendingDestroy) {
// @ts-ignore
return this.runDestroy();
}
@ -100,15 +96,17 @@ export class Game extends Phaser.Game {
}
// Loop through the scenes in forward order
for (let i = 0; i < this.scene.scenes.length; i++)
{
for (let i = 0; i < this.scene.scenes.length; i++) {
const scene = this.scene.scenes[i];
const sys = scene.sys;
if (sys.settings.visible && sys.settings.status >= Phaser.Scenes.LOADING && sys.settings.status < Phaser.Scenes.SLEEPING)
{
if (
sys.settings.visible &&
sys.settings.status >= Phaser.Scenes.LOADING &&
sys.settings.status < Phaser.Scenes.SLEEPING
) {
// @ts-ignore
if(typeof scene.isDirty === 'function') {
if (typeof scene.isDirty === "function") {
// @ts-ignore
const isDirty = scene.isDirty() || scene.tweens.getAllTweens().length > 0;
if (isDirty) {
@ -129,4 +127,11 @@ export class Game extends Phaser.Game {
public markDirty(): void {
this._isDirty = true;
}
/**
* Return the first scene found in the game
*/
public findAnyScene(): Phaser.Scene {
return this.scene.getScenes()[0];
}
}

View File

@ -5,4 +5,4 @@
@import "style";
@import "mobile-style.scss";
@import "fonts.scss";
@import "svelte-style.scss";
@import "inputTextGlobalMessageSvelte-Style.scss";

View File

@ -0,0 +1,24 @@
//InputTextGlobalMessage
section.section-input-send-text {
height: 100%;
.ql-toolbar{
max-height: 100px;
background: whitesmoke;
}
div.input-send-text{
height: auto;
max-height: calc(100% - 100px);
overflow: auto;
color: whitesmoke;
font-size: 1rem;
.ql-editor.ql-blank::before {
color: whitesmoke;
font-size: 1rem;
}
}
}

View File

@ -1,60 +0,0 @@
//Contains all styles not unique to a svelte component.
//ConsoleGlobalMessage
div.main-console.nes-container {
pointer-events: auto;
margin-left: auto;
margin-right: auto;
top: 20vh;
width: 50vw;
height: 50vh;
padding: 0;
background-color: #333333;
.btn-action{
margin: 10px;
text-align: center;
}
.main-global-message {
width: 100%;
max-height: 100%;
}
.main-global-message h2 {
text-align: center;
color: white;
}
div.global-message {
display: flex;
max-height: 100%;
width: 100%;
}
div.menu {
flex: auto;
}
div.menu button {
margin: 7px;
}
.main-input {
width: 95%;
}
//InputTextGlobalMessage
.section-input-send-text {
margin: 10px;
}
.section-input-send-text .input-send-text .ql-editor{
color: white;
min-height: 200px;
}
.section-input-send-text .ql-toolbar{
background: white;
}
}

View File

@ -58,11 +58,17 @@
"height":94.6489098314831,
"id":1,
"name":"",
"properties":[
{
"name":"font-family",
"type":"string",
"value":"\"Press Start 2P\""
}],
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":11,
"pixelsize":8,
"text":"Test:\nWalk on the carpet and press space\nResult:\nJitsi opens on meet.jit.si (check this in the network tab). Note: this test only makes sense if the default configured Jitsi instance is NOT meet.jit.si (check your .env file)",
"wrap":true
},