Twemoji Svelte

This commit is contained in:
_Bastler 2021-09-10 17:06:46 +02:00
parent 5497253e38
commit bb3b58d8bf
7 changed files with 156 additions and 174 deletions

View File

@ -26,6 +26,7 @@
import {soundPlayingStore} from "../Stores/SoundPlayingStore"; import {soundPlayingStore} from "../Stores/SoundPlayingStore";
import ErrorDialog from "./UI/ErrorDialog.svelte"; import ErrorDialog from "./UI/ErrorDialog.svelte";
import Menu from "./Menu/Menu.svelte"; import Menu from "./Menu/Menu.svelte";
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
import VideoOverlay from "./Video/VideoOverlay.svelte"; import VideoOverlay from "./Video/VideoOverlay.svelte";
import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility"; import {gameOverlayVisibilityStore} from "../Stores/GameOverlayStoreVisibility";
import AdminMessage from "./TypeMessage/BanMessage.svelte"; import AdminMessage from "./TypeMessage/BanMessage.svelte";
@ -111,6 +112,9 @@
<Menu></Menu> <Menu></Menu>
</div> </div>
{/if} {/if}
{#if $gameOverlayVisibilityStore}
<EmoteMenu></EmoteMenu>
{/if}
{#if $gameOverlayVisibilityStore} {#if $gameOverlayVisibilityStore}
<div> <div>
<VideoOverlay></VideoOverlay> <VideoOverlay></VideoOverlay>

View File

@ -0,0 +1,65 @@
<script lang="typescript">
import { get } from "svelte/store";
import type { Unsubscriber } from "svelte/store";
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
import { onDestroy, onMount } from "svelte";
import { EmojiButton } from '@joeattardi/emoji-button';
let emojiContainer: HTMLElement;
let picker: EmojiButton;
let unsubscriber: Unsubscriber | null = null;
onMount(() => {
picker = new EmojiButton({
rootElement: emojiContainer,
style : 'twemoji',
styleProperties: {
'--font': 'Press Start 2P'
},
showSearch : false
});
picker.on("emoji", (selection) => {
emoteStore.set(selection.emoji);
});
picker.on("hidden", () => {
emoteMenuStore.set(false);
});
unsubscriber = emoteMenuStore.subscribe(() => {
if (get(emoteMenuStore)) {
picker.showPicker(emojiContainer);
} else {
picker.hidePicker();
}
})
})
onDestroy(() => {
if (unsubscriber) {
unsubscriber();
}
})
</script>
<div class="emote-menu-container">
<div class="emote-menu" bind:this={emojiContainer}></div>
</div>
<style lang="scss">
.emote-menu-container {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
.emote-menu {
pointer-events: all;
}
</style>

View File

@ -1,68 +0,0 @@
import DOMElement = Phaser.GameObjects.DOMElement;
import { DEPTH_UI_INDEX } from "../Game/DepthIndexes";
import { waScaleManager } from "../Services/WaScaleManager";
import type { UserInputManager } from "../UserInput/UserInputManager";
import { EmojiButton } from '@joeattardi/emoji-button';
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
export const EmoteMenuClickEvent = "emoteClick";
export class EmoteMenu extends Phaser.GameObjects.Container {
private resizeCallback: OmitThisParameter<() => void>;
private container: DOMElement;
private picker: EmojiButton;
constructor(scene: Phaser.Scene, x: number, y: number, private userInputManager: UserInputManager) {
super(scene, x, y);
this.setDepth(DEPTH_UI_INDEX);
this.scene.add.existing(this);
this.container = new DOMElement(this.scene, 0, 0, "div", "", "");
this.container.setClassName("emoji-container");
const scalingFactor = waScaleManager.uiScalingFactor * 0.5;
this.container.setScale(scalingFactor);
this.add(this.container);
const emojiContainer = HtmlUtils.querySelectorOrFail(".emoji-container");
this.picker = new EmojiButton({
rootElement: emojiContainer,
styleProperties: {
'--font': 'Press Start 2P'
}
});
this.picker.on("emoji", (selection) => {
this.emit(EmoteMenuClickEvent, selection.emoji);
});
this.picker.on("hidden", () => {
this.userInputManager.restoreControls();
});
this.resize();
this.resizeCallback = this.resize.bind(this);
this.scene.scale.on(Phaser.Scale.Events.RESIZE, this.resizeCallback);
}
public isOpen(): boolean {
return this.picker.isPickerVisible();
}
public openPicker() {
this.userInputManager.disableControls();
const emojiContainer = HtmlUtils.querySelectorOrFail(".emoji-container");
this.picker.showPicker(emojiContainer);
}
public closePicker() {
this.picker.hidePicker();
}
private resize() {
this.setScale(waScaleManager.uiScalingFactor);
}
public destroy() {
this.scene.scale.removeListener(Phaser.Scale.Events.RESIZE, this.resizeCallback);
super.destroy();
}
}

View File

@ -291,48 +291,48 @@ export abstract class Character extends Container {
playEmote(emote: string) { playEmote(emote: string) {
this.cancelPreviousEmote(); this.cancelPreviousEmote();
const emoteY = -45;
const scalingFactor = waScaleManager.uiScalingFactor;
const emoteY = -60;
this.playerName.setVisible(false); this.playerName.setVisible(false);
this.emote = new Text(this.scene, -12, 0, emote, { fontFamily: '"twemoji"', fontSize: '24px' }); this.emote = new Text(this.scene, -10, 0, emote, { fontFamily: '"twemoji"', fontSize: '20px' });
this.emote.setAlpha(0); this.emote.setAlpha(0);
this.add(this.emote); this.add(this.emote);
this.createStartTransition(scalingFactor, emoteY); this.createStartTransition(emoteY);
} }
private createStartTransition(scalingFactor: number, emoteY: number) { private createStartTransition(emoteY: number) {
this.emoteTween = this.scene?.tweens.add({ if (this.emote) {
targets: this.emote, this.emoteTween = this.scene?.tweens.add({
props: { targets: this.emote,
scale: scalingFactor, props: {
alpha: 1, alpha: 1,
y: emoteY, y: emoteY,
}, },
ease: "Power2", ease: "Power2",
duration: 500, duration: 500,
onComplete: () => { onComplete: () => {
this.startPulseTransition(emoteY, scalingFactor); this.startPulseTransition(emoteY);
}, },
}); });
}
} }
private startPulseTransition(emoteY: number, scalingFactor: number) { private startPulseTransition(emoteY: number) {
this.emoteTween = this.scene?.tweens.add({ if (this.emote) {
targets: this.emote, this.emoteTween = this.scene?.tweens.add({
props: { targets: this.emote,
y: emoteY * 1.3, props: {
scale: scalingFactor * 1.1, y: emoteY * 1.3,
}, scale: this.emote.scale * 1.1,
duration: 250, },
yoyo: true, duration: 250,
repeat: 1, yoyo: true,
completeDelay: 200, repeat: 1,
onComplete: () => { completeDelay: 200,
this.startExitTransition(emoteY); onComplete: () => {
}, this.startExitTransition(emoteY);
}); },
});
}
} }
private startExitTransition(emoteY: number) { private startExitTransition(emoteY: number) {

View File

@ -82,6 +82,7 @@ import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStor
import { SharedVariablesManager } from "./SharedVariablesManager"; import { SharedVariablesManager } from "./SharedVariablesManager";
import { playersStore } from "../../Stores/PlayersStore"; import { playersStore } from "../../Stores/PlayersStore";
import { chatVisibilityStore } from "../../Stores/ChatStore"; import { chatVisibilityStore } from "../../Stores/ChatStore";
import { emoteStore } from "../../Stores/EmoteStore";
import { import {
audioManagerFileStore, audioManagerFileStore,
audioManagerVisibilityStore, audioManagerVisibilityStore,
@ -94,6 +95,7 @@ import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { GameMapPropertiesListener } from "./GameMapPropertiesListener"; import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
import type { RadialMenuItem } from "../Components/RadialMenu"; import type { RadialMenuItem } from "../Components/RadialMenu";
import { get } from "svelte/store";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
@ -171,6 +173,7 @@ export class GameScene extends DirtyScene {
private iframeSubscriptionList!: Array<Subscription>; private iframeSubscriptionList!: Array<Subscription>;
private peerStoreUnsubscribe!: () => void; private peerStoreUnsubscribe!: () => void;
private chatVisibilityUnsubscribe!: () => void; private chatVisibilityUnsubscribe!: () => void;
private emoteUnsubscribe!: () => void;
private biggestAvailableAreaStoreUnsubscribe!: () => void; private biggestAvailableAreaStoreUnsubscribe!: () => void;
MapUrlFile: string; MapUrlFile: string;
roomUrl: string; roomUrl: string;
@ -272,7 +275,7 @@ export class GameScene extends DirtyScene {
// So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes) // So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes)
// See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure // See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure
const url = new URL(file.src); const url = new URL(file.src);
const host = url.host.split(":")[0]; const host = url.host.split(":")[ 0 ];
if ( if (
window.location.protocol === "https:" && window.location.protocol === "https:" &&
file.src === this.MapUrlFile && file.src === this.MapUrlFile &&
@ -321,8 +324,8 @@ export class GameScene extends DirtyScene {
//eslint-disable-next-line @typescript-eslint/no-explicit-any //eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.load as any).rexWebFont({ (this.load as any).rexWebFont({
custom: { custom: {
families: ["Press Start 2P"], families: [ "Press Start 2P" ],
urls: ["/resources/fonts/fonts.css"], urls: [ "/resources/fonts/fonts.css" ],
testString: "abcdefg", testString: "abcdefg",
}, },
}); });
@ -368,7 +371,7 @@ export class GameScene extends DirtyScene {
} }
} }
for (const [itemType, objectsOfType] of this.objectsByType) { for (const [ itemType, objectsOfType ] of this.objectsByType) {
// FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin. // FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin.
let itemFactory: ItemFactoryInterface; let itemFactory: ItemFactoryInterface;
@ -399,7 +402,7 @@ export class GameScene extends DirtyScene {
// TODO: we should pass here a factory to create sprites (maybe?) // TODO: we should pass here a factory to create sprites (maybe?)
// Do we have a state for this object? // Do we have a state for this object?
const state = roomJoinedAnswer.items[object.id]; const state = roomJoinedAnswer.items[ object.id ];
const actionableItem = itemFactory.factory(this, object, state); const actionableItem = itemFactory.factory(this, object, state);
this.actionableItems.set(actionableItem.getId(), actionableItem); this.actionableItems.set(actionableItem.getId(), actionableItem);
@ -614,7 +617,16 @@ export class GameScene extends DirtyScene {
this.openChatIcon.setVisible(!v); this.openChatIcon.setVisible(!v);
}); });
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises]).then(() => { this.emoteUnsubscribe = emoteStore.subscribe(() => {
const emoteKey = get(emoteStore);
if (emoteKey) {
this.CurrentPlayer?.playEmote(emoteKey)
this.connection?.emitEmoteEvent(emoteKey);
emoteStore.set(null);
}
});
Promise.all([ this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises ]).then(() => {
this.scene.wake(); this.scene.wake();
}); });
} }
@ -702,8 +714,8 @@ export class GameScene extends DirtyScene {
if (item === undefined) { if (item === undefined) {
console.warn( console.warn(
'Received an event about object "' + 'Received an event about object "' +
message.itemId + message.itemId +
'" but cannot find this item on the map.' '" but cannot find this item on the map.'
); );
return; return;
} }
@ -897,15 +909,15 @@ export class GameScene extends DirtyScene {
} else { } else {
console.error( console.error(
"Error while opening a popup. Cannot find an object on the map with name '" + "Error while opening a popup. Cannot find an object on the map with name '" +
openPopupEvent.targetObject + openPopupEvent.targetObject +
"'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map." "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map."
); );
return; return;
} }
const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message); const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message);
let html = `<div id="container" hidden><div class="nes-container with-title is-centered">`; let html = `<div id="container" hidden><div class="nes-container with-title is-centered">`;
html += escapedMessage; html += escapedMessage;
if(openPopupEvent.input) { if (openPopupEvent.input) {
html += `<input id="popupinput-${openPopupEvent.popupId}" class="nes-input" />` html += `<input id="popupinput-${openPopupEvent.popupId}" class="nes-input" />`
} }
html += `</div>`; html += `</div>`;
@ -938,7 +950,7 @@ export class GameScene extends DirtyScene {
const btnId = id; const btnId = id;
button.onclick = () => { button.onclick = () => {
let inputValue = ''; let inputValue = '';
if(openPopupEvent.input) { if (openPopupEvent.input) {
inputValue = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(`popupinput-${openPopupEvent.popupId}`).value; inputValue = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(`popupinput-${openPopupEvent.popupId}`).value;
} }
iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId, openPopupEvent.input, inputValue); iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId, openPopupEvent.input, inputValue);
@ -1095,7 +1107,7 @@ export class GameScene extends DirtyScene {
this.iframeSubscriptionList.push(iframeListener.unregisterIFrameStream.subscribe(() => { this.iframeSubscriptionList.push(iframeListener.unregisterIFrameStream.subscribe(() => {
const allProps = this.gameMap.getCurrentProperties(); const allProps = this.gameMap.getCurrentProperties();
if(allProps.get("openWebsite") == null) { if (allProps.get("openWebsite") == null) {
layoutManagerActionStore.removeAction("openWebsite"); layoutManagerActionStore.removeAction("openWebsite");
} else { } else {
const openWebsiteFunction = () => { const openWebsiteFunction = () => {
@ -1105,7 +1117,7 @@ export class GameScene extends DirtyScene {
}; };
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if(message === undefined) { if (message === undefined) {
message = 'Press SPACE or touch here to open web site'; message = 'Press SPACE or touch here to open web site';
} }
@ -1130,7 +1142,7 @@ export class GameScene extends DirtyScene {
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/")); const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1 //Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
let newFirstgid = 1; let newFirstgid = 1;
const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1]; const lastTileset = this.mapFile.tilesets[ this.mapFile.tilesets.length - 1 ];
if (lastTileset) { if (lastTileset) {
//If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset //If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset
newFirstgid = lastTileset.firstgid + lastTileset.tilecount; newFirstgid = lastTileset.firstgid + lastTileset.tilecount;
@ -1251,14 +1263,14 @@ export class GameScene extends DirtyScene {
if (phaserLayers === []) { if (phaserLayers === []) {
console.warn( console.warn(
'Could not find layer with name that contains "' + 'Could not find layer with name that contains "' +
layerName + layerName +
'" when calling WA.hideLayer / WA.showLayer' '" when calling WA.hideLayer / WA.showLayer'
); );
return; return;
} }
for (let i = 0; i < phaserLayers.length; i++) { for (let i = 0; i < phaserLayers.length; i++) {
phaserLayers[i].setVisible(visible); phaserLayers[ i ].setVisible(visible);
phaserLayers[i].setCollisionByProperty({ collides: true }, visible); phaserLayers[ i ].setCollisionByProperty({ collides: true }, visible);
} }
} }
this.markDirty(); this.markDirty();
@ -1338,6 +1350,7 @@ export class GameScene extends DirtyScene {
this.emoteManager.destroy(); this.emoteManager.destroy();
this.peerStoreUnsubscribe(); this.peerStoreUnsubscribe();
this.chatVisibilityUnsubscribe(); this.chatVisibilityUnsubscribe();
this.emoteUnsubscribe();
this.biggestAvailableAreaStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe();
iframeListener.unregisterAnswerer("getState"); iframeListener.unregisterAnswerer("getState");
iframeListener.unregisterAnswerer("loadTileset"); iframeListener.unregisterAnswerer("loadTileset");
@ -1856,13 +1869,13 @@ export class GameScene extends DirtyScene {
const allProps = this.gameMap.getCurrentProperties(); const allProps = this.gameMap.getCurrentProperties();
if(allProps.get("jitsiRoom") === undefined) { if (allProps.get("jitsiRoom") === undefined) {
layoutManagerActionStore.removeAction("jitsi"); layoutManagerActionStore.removeAction("jitsi");
} else { } else {
const openJitsiRoomFunction = () => { const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(allProps.get("jitsiRoom") as string, this.instance); const roomName = jitsiFactory.getRoomName(allProps.get("jitsiRoom") as string, this.instance);
const jitsiUrl = allProps.get("jitsiUrl") as string | undefined; const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
if(JITSI_PRIVATE_MODE && !jitsiUrl) { if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string | undefined; const adminTag = allProps.get("jitsiRoomAdminTag") as string | undefined;
this.connection && this.connection.emitQueryJitsiJwtMessage(roomName, adminTag); this.connection && this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
@ -1873,7 +1886,7 @@ export class GameScene extends DirtyScene {
} }
let message = allProps.get(JITSI_MESSAGE_PROPERTIES); let message = allProps.get(JITSI_MESSAGE_PROPERTIES);
if(message === undefined) { if (message === undefined) {
message = 'Press SPACE or touch here to enter Jitsi Meet room'; message = 'Press SPACE or touch here to enter Jitsi Meet room';
} }
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({

View File

@ -3,7 +3,8 @@ import type { GameScene } from "../Game/GameScene";
import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager"; import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager";
import { Character } from "../Entity/Character"; import { Character } from "../Entity/Character";
import { userMovingStore } from "../../Stores/GameStore"; import { userMovingStore } from "../../Stores/GameStore";
import { EmoteMenu, EmoteMenuClickEvent } from "../Components/EmoteMenu"; import { get } from "svelte/store";
import { emoteMenuStore } from "../../Stores/EmoteStore";
export const hasMovedEventName = "hasMoved"; export const hasMovedEventName = "hasMoved";
export const requestEmoteEventName = "requestEmote"; export const requestEmoteEventName = "requestEmote";
@ -11,8 +12,6 @@ export const requestEmoteEventName = "requestEmote";
export class Player extends Character { export class Player extends Character {
private previousDirection: string = PlayerAnimationDirections.Down; private previousDirection: string = PlayerAnimationDirections.Down;
private wasMoving: boolean = false; private wasMoving: boolean = false;
private emoteMenu: EmoteMenu | null = null;
private updateListener: () => void;
constructor( constructor(
Scene: GameScene, Scene: GameScene,
@ -30,14 +29,6 @@ export class Player extends Character {
//the current player model should be push away by other players to prevent conflict //the current player model should be push away by other players to prevent conflict
this.getBody().setImmovable(false); this.getBody().setImmovable(false);
this.updateListener = () => {
if (this.emoteMenu) {
this.emoteMenu.x = this.x;
this.emoteMenu.y = this.y;
}
};
this.scene.events.addListener("postupdate", this.updateListener);
} }
moveUser(delta: number): void { moveUser(delta: number): void {
@ -94,43 +85,16 @@ export class Player extends Character {
return this.wasMoving; return this.wasMoving;
} }
playEmote(emote: string) {
super.playEmote(emote);
emoteMenuStore.set(false);
}
openOrCloseEmoteMenu() { openOrCloseEmoteMenu() {
if (!this.emoteMenu) { if (get(emoteMenuStore)) {
this.emoteMenu = new EmoteMenu(this.scene, this.x, this.y, this.userInputManager) emoteMenuStore.set(false);
}
if (this.emoteMenu.isOpen()) {
this.closeEmoteMenu();
} else { } else {
this.openEmoteMenu(); emoteMenuStore.set(true);
} }
} }
openEmoteMenu(): void {
this.cancelPreviousEmote();
if (!this.emoteMenu) return;
this.emoteMenu.openPicker();
this.emoteMenu.on(EmoteMenuClickEvent, (emote: string) => {
this.closeEmoteMenu();
this.emit(requestEmoteEventName, emote);
this.playEmote(emote);
});
}
isSilent() {
super.isSilent();
}
noSilent() {
super.noSilent();
}
closeEmoteMenu(): void {
if (!this.emoteMenu) return;
this.emoteMenu.closePicker();
}
destroy() {
this.scene.events.removeListener("postupdate", this.updateListener);
super.destroy();
}
} }

View File

@ -0,0 +1,4 @@
import { writable } from "svelte/store";
export const emoteStore = writable<string | null>(null);
export const emoteMenuStore = writable(false);