Merge branch 'develop' into changeRegisterAccess
Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> # Conflicts: # pusher/src/Services/AdminApi.ts
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isActionsMenuActionClickedEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
id: tg.isNumber,
|
||||
actionName: tg.isString,
|
||||
})
|
||||
.get();
|
||||
|
||||
export type ActionsMenuActionClickedEvent = tg.GuardedType<typeof isActionsMenuActionClickedEvent>;
|
||||
|
||||
export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void;
|
||||
@@ -0,0 +1,12 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isAddActionsMenuKeyToRemotePlayerEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
id: tg.isNumber,
|
||||
actionKey: tg.isString,
|
||||
})
|
||||
.get();
|
||||
|
||||
export type AddActionsMenuKeyToRemotePlayerEvent = tg.GuardedType<typeof isAddActionsMenuKeyToRemotePlayerEvent>;
|
||||
|
||||
export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void;
|
||||
@@ -36,6 +36,10 @@ import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
|
||||
import { isColorEvent } from "./ColorEvent";
|
||||
import { isMovePlayerToEventConfig } from "./MovePlayerToEvent";
|
||||
import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer";
|
||||
import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent";
|
||||
import type { AddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent";
|
||||
import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent";
|
||||
import type { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent";
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T;
|
||||
@@ -45,6 +49,8 @@ export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
* List event types sent from an iFrame to WorkAdventure
|
||||
*/
|
||||
export type IframeEventMap = {
|
||||
addActionsMenuKeyToRemotePlayer: AddActionsMenuKeyToRemotePlayerEvent;
|
||||
removeActionsMenuKeyFromRemotePlayer: RemoveActionsMenuKeyFromRemotePlayerEvent;
|
||||
loadPage: LoadPageEvent;
|
||||
chat: ChatEvent;
|
||||
cameraFollowPlayer: CameraFollowPlayerEvent;
|
||||
@@ -58,6 +64,7 @@ export type IframeEventMap = {
|
||||
displayBubble: null;
|
||||
removeBubble: null;
|
||||
onPlayerMove: undefined;
|
||||
onOpenActionMenu: undefined;
|
||||
onCameraUpdate: undefined;
|
||||
showLayer: LayerEvent;
|
||||
hideLayer: LayerEvent;
|
||||
@@ -90,6 +97,8 @@ export interface IframeResponseEventMap {
|
||||
enterZoneEvent: ChangeZoneEvent;
|
||||
leaveZoneEvent: ChangeZoneEvent;
|
||||
buttonClickedEvent: ButtonClickedEvent;
|
||||
remotePlayerClickedEvent: RemotePlayerClickedEvent;
|
||||
actionsMenuActionClickedEvent: ActionsMenuActionClickedEvent;
|
||||
hasPlayerMoved: HasPlayerMovedEvent;
|
||||
wasCameraUpdated: WasCameraUpdatedEvent;
|
||||
menuItemClicked: MenuItemClickedEvent;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
// TODO: Change for player Clicked, add all neccessary data
|
||||
export const isRemotePlayerClickedEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
id: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the game to the iFrame when RemotePlayer is clicked.
|
||||
*/
|
||||
export type RemotePlayerClickedEvent = tg.GuardedType<typeof isRemotePlayerClickedEvent>;
|
||||
|
||||
export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void;
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isRemoveActionsMenuKeyFromRemotePlayerEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
id: tg.isNumber,
|
||||
actionKey: tg.isString,
|
||||
})
|
||||
.get();
|
||||
|
||||
export type RemoveActionsMenuKeyFromRemotePlayerEvent = tg.GuardedType<
|
||||
typeof isRemoveActionsMenuKeyFromRemotePlayerEvent
|
||||
>;
|
||||
|
||||
export type RemoveActionsMenuKeyFromRemotePlayerEventCallback = (
|
||||
event: RemoveActionsMenuKeyFromRemotePlayerEvent
|
||||
) => void;
|
||||
@@ -34,6 +34,16 @@ import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
|
||||
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
||||
import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent";
|
||||
import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent";
|
||||
import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent";
|
||||
import {
|
||||
AddActionsMenuKeyToRemotePlayerEvent,
|
||||
isAddActionsMenuKeyToRemotePlayerEvent,
|
||||
} from "./Events/AddActionsMenuKeyToRemotePlayerEvent";
|
||||
import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent";
|
||||
import {
|
||||
isRemoveActionsMenuKeyFromRemotePlayerEvent,
|
||||
RemoveActionsMenuKeyFromRemotePlayerEvent,
|
||||
} from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent";
|
||||
|
||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||
query: IframeQueryMap[T]["query"],
|
||||
@@ -63,6 +73,15 @@ class IframeListener {
|
||||
private readonly _cameraFollowPlayerStream: Subject<CameraFollowPlayerEvent> = new Subject();
|
||||
public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable();
|
||||
|
||||
private readonly _addActionsMenuKeyToRemotePlayerStream: Subject<AddActionsMenuKeyToRemotePlayerEvent> =
|
||||
new Subject();
|
||||
public readonly addActionsMenuKeyToRemotePlayerStream = this._addActionsMenuKeyToRemotePlayerStream.asObservable();
|
||||
|
||||
private readonly _removeActionsMenuKeyFromRemotePlayerEvent: Subject<RemoveActionsMenuKeyFromRemotePlayerEvent> =
|
||||
new Subject();
|
||||
public readonly removeActionsMenuKeyFromRemotePlayerEvent =
|
||||
this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable();
|
||||
|
||||
private readonly _enablePlayerControlStream: Subject<void> = new Subject();
|
||||
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
|
||||
|
||||
@@ -241,6 +260,16 @@ class IframeListener {
|
||||
this._removeBubbleStream.next();
|
||||
} else if (payload.type == "onPlayerMove") {
|
||||
this.sendPlayerMove = true;
|
||||
} else if (
|
||||
payload.type == "addActionsMenuKeyToRemotePlayer" &&
|
||||
isAddActionsMenuKeyToRemotePlayerEvent(payload.data)
|
||||
) {
|
||||
this._addActionsMenuKeyToRemotePlayerStream.next(payload.data);
|
||||
} else if (
|
||||
payload.type == "removeActionsMenuKeyFromRemotePlayer" &&
|
||||
isRemoveActionsMenuKeyFromRemotePlayerEvent(payload.data)
|
||||
) {
|
||||
this._removeActionsMenuKeyFromRemotePlayerEvent.next(payload.data);
|
||||
} else if (payload.type == "onCameraUpdate") {
|
||||
this._trackCameraUpdateStream.next();
|
||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||
@@ -289,68 +318,42 @@ class IframeListener {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.info("Loading map related script at ", scriptUrl);
|
||||
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
|
||||
// Using external iframe mode (
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = "none";
|
||||
iframe.src =
|
||||
"/iframe.html?script=" +
|
||||
encodeURIComponent(scriptUrl) +
|
||||
"&moduleMode=" +
|
||||
(enableModuleMode ? "true" : "false");
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = "none";
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add("allow-scripts");
|
||||
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add("allow-scripts");
|
||||
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||
|
||||
iframe.addEventListener("load", () => {
|
||||
resolve();
|
||||
});
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc =
|
||||
"<!doctype html>\n" +
|
||||
"\n" +
|
||||
'<html lang="en">\n' +
|
||||
"<head>\n" +
|
||||
'<script src="' +
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
window.location.host +
|
||||
'/iframe_api.js" ></script>\n' +
|
||||
"<script " +
|
||||
(enableModuleMode ? 'type="module" ' : "") +
|
||||
'src="' +
|
||||
scriptUrl +
|
||||
'" ></script>\n' +
|
||||
"<title></title>\n" +
|
||||
"</head>\n" +
|
||||
"</html>\n";
|
||||
|
||||
document.body.prepend(iframe);
|
||||
iframe.addEventListener("load", () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
this.registerIframe(iframe);
|
||||
} else {
|
||||
// production code
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.id = IframeListener.getIFrameId(scriptUrl);
|
||||
iframe.style.display = "none";
|
||||
document.body.prepend(iframe);
|
||||
|
||||
// We are putting a sandbox on this script because it will run in the same domain as the main website.
|
||||
iframe.sandbox.add("allow-scripts");
|
||||
iframe.sandbox.add("allow-top-navigation-by-user-activation");
|
||||
|
||||
//iframe.src = "data:text/html;charset=utf-8," + escape(html);
|
||||
iframe.srcdoc =
|
||||
"<!doctype html>\n" +
|
||||
"\n" +
|
||||
'<html lang="en">\n' +
|
||||
"<head>\n" +
|
||||
'<script src="' +
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
window.location.host +
|
||||
'/iframe_api.js" ></script>\n' +
|
||||
"<script " +
|
||||
(enableModuleMode ? 'type="module" ' : "") +
|
||||
'src="' +
|
||||
scriptUrl +
|
||||
'" ></script>\n' +
|
||||
"<title></title>\n" +
|
||||
"</head>\n" +
|
||||
"</html>\n";
|
||||
|
||||
iframe.addEventListener("load", () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
document.body.prepend(iframe);
|
||||
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
this.registerIframe(iframe);
|
||||
}
|
||||
this.scripts.set(scriptUrl, iframe);
|
||||
this.registerIframe(iframe);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -465,6 +468,20 @@ class IframeListener {
|
||||
}
|
||||
}
|
||||
|
||||
sendRemotePlayerClickedEvent(event: RemotePlayerClickedEvent) {
|
||||
this.postMessage({
|
||||
type: "remotePlayerClickedEvent",
|
||||
data: event,
|
||||
});
|
||||
}
|
||||
|
||||
sendActionsMenuActionClickedEvent(event: ActionsMenuActionClickedEvent) {
|
||||
this.postMessage({
|
||||
type: "actionsMenuActionClickedEvent",
|
||||
data: event,
|
||||
});
|
||||
}
|
||||
|
||||
sendCameraUpdated(event: WasCameraUpdatedEvent) {
|
||||
this.postMessage({
|
||||
type: "wasCameraUpdated",
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../../Stores/MediaStore";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
class DesktopApi {
|
||||
isSilent: boolean = false;
|
||||
|
||||
init() {
|
||||
if (!window?.WAD?.desktop) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Yipee you are using the desktop app ;)");
|
||||
|
||||
window.WAD.onMuteToggle(() => {
|
||||
if (this.isSilent) return;
|
||||
if (get(requestedMicrophoneState) === true) {
|
||||
requestedMicrophoneState.disableMicrophone();
|
||||
} else {
|
||||
requestedMicrophoneState.enableMicrophone();
|
||||
}
|
||||
});
|
||||
|
||||
window.WAD.onCameraToggle(() => {
|
||||
if (this.isSilent) return;
|
||||
if (get(requestedCameraState) === true) {
|
||||
requestedCameraState.disableWebcam();
|
||||
} else {
|
||||
requestedCameraState.enableWebcam();
|
||||
}
|
||||
});
|
||||
|
||||
isSilentStore.subscribe((value) => {
|
||||
this.isSilent = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const desktopApi = new DesktopApi();
|
||||
+113
-5
@@ -8,6 +8,12 @@ import { ActionMessage } from "./Ui/ActionMessage";
|
||||
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
|
||||
import { Menu } from "./Ui/Menu";
|
||||
import type { RequireOnlyOne } from "../types";
|
||||
import { isRemotePlayerClickedEvent, RemotePlayerClickedEvent } from "../Events/RemotePlayerClickedEvent";
|
||||
import {
|
||||
ActionsMenuActionClickedEvent,
|
||||
isActionsMenuActionClickedEvent,
|
||||
} from "../Events/ActionsMenuActionClickedEvent";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
|
||||
let popupId = 0;
|
||||
const popups: Map<number, Popup> = new Map<number, Popup>();
|
||||
@@ -42,7 +48,77 @@ export interface ActionMessageOptions {
|
||||
callback: () => void;
|
||||
}
|
||||
|
||||
export interface RemotePlayerInterface {
|
||||
addAction(key: string, callback: Function): void;
|
||||
}
|
||||
|
||||
export class RemotePlayer implements RemotePlayerInterface {
|
||||
private id: number;
|
||||
|
||||
private actions: Map<string, ActionsMenuAction> = new Map<string, ActionsMenuAction>();
|
||||
|
||||
constructor(id: number) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public addAction(key: string, callback: Function): ActionsMenuAction {
|
||||
const newAction = new ActionsMenuAction(this, key, callback);
|
||||
this.actions.set(key, newAction);
|
||||
sendToWorkadventure({
|
||||
type: "addActionsMenuKeyToRemotePlayer",
|
||||
data: { id: this.id, actionKey: key },
|
||||
});
|
||||
return newAction;
|
||||
}
|
||||
|
||||
public callAction(key: string): void {
|
||||
const action = this.actions.get(key);
|
||||
if (action) {
|
||||
action.call();
|
||||
}
|
||||
}
|
||||
|
||||
public removeAction(key: string): void {
|
||||
this.actions.delete(key);
|
||||
sendToWorkadventure({
|
||||
type: "removeActionsMenuKeyFromRemotePlayer",
|
||||
data: { id: this.id, actionKey: key },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ActionsMenuAction {
|
||||
private remotePlayer: RemotePlayer;
|
||||
private key: string;
|
||||
private callback: Function;
|
||||
|
||||
constructor(remotePlayer: RemotePlayer, key: string, callback: Function) {
|
||||
this.remotePlayer = remotePlayer;
|
||||
this.key = key;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public call(): void {
|
||||
this.callback();
|
||||
}
|
||||
|
||||
public remove(): void {
|
||||
this.remotePlayer.removeAction(this.key);
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
|
||||
public readonly _onRemotePlayerClicked: Subject<RemotePlayerInterface>;
|
||||
public readonly onRemotePlayerClicked: Observable<RemotePlayerInterface>;
|
||||
|
||||
private currentlyClickedRemotePlayer?: RemotePlayer;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._onRemotePlayerClicked = new Subject<RemotePlayerInterface>();
|
||||
this.onRemotePlayerClicked = this._onRemotePlayerClicked.asObservable();
|
||||
}
|
||||
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: "buttonClickedEvent",
|
||||
@@ -82,9 +158,38 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
||||
}
|
||||
},
|
||||
}),
|
||||
apiCallback({
|
||||
type: "remotePlayerClickedEvent",
|
||||
typeChecker: isRemotePlayerClickedEvent,
|
||||
callback: (payloadData: RemotePlayerClickedEvent) => {
|
||||
this.currentlyClickedRemotePlayer = new RemotePlayer(payloadData.id);
|
||||
this._onRemotePlayerClicked.next(this.currentlyClickedRemotePlayer);
|
||||
},
|
||||
}),
|
||||
apiCallback({
|
||||
type: "actionsMenuActionClickedEvent",
|
||||
typeChecker: isActionsMenuActionClickedEvent,
|
||||
callback: (payloadData: ActionsMenuActionClickedEvent) => {
|
||||
this.currentlyClickedRemotePlayer?.callAction(payloadData.actionName);
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||
public addActionsMenuKeyToRemotePlayer(id: number, actionKey: string): void {
|
||||
sendToWorkadventure({
|
||||
type: "addActionsMenuKeyToRemotePlayer",
|
||||
data: { id, actionKey },
|
||||
});
|
||||
}
|
||||
|
||||
public removeActionsMenuKeyFromRemotePlayer(id: number, actionKey: string): void {
|
||||
sendToWorkadventure({
|
||||
type: "removeActionsMenuKeyFromRemotePlayer",
|
||||
data: { id, actionKey },
|
||||
});
|
||||
}
|
||||
|
||||
public openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
|
||||
popupId++;
|
||||
const popup = new Popup(popupId);
|
||||
const btnMap = new Map<number, () => void>();
|
||||
@@ -119,7 +224,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
||||
return popup;
|
||||
}
|
||||
|
||||
registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu {
|
||||
public registerMenuCommand(
|
||||
commandDescriptor: string,
|
||||
options: MenuOptions | ((commandDescriptor: string) => void)
|
||||
): Menu {
|
||||
const menu = new Menu(commandDescriptor);
|
||||
|
||||
if (typeof options === "function") {
|
||||
@@ -168,15 +276,15 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
||||
return menu;
|
||||
}
|
||||
|
||||
displayBubble(): void {
|
||||
public displayBubble(): void {
|
||||
sendToWorkadventure({ type: "displayBubble", data: null });
|
||||
}
|
||||
|
||||
removeBubble(): void {
|
||||
public removeBubble(): void {
|
||||
sendToWorkadventure({ type: "removeBubble", data: null });
|
||||
}
|
||||
|
||||
displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {
|
||||
public displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {
|
||||
const actionMessage = new ActionMessage(actionMessageOptions, () => {
|
||||
actionMessages.delete(actionMessage.uuid);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
import type { ActionsMenuAction } from "../../Stores/ActionsMenuStore";
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import type { ActionsMenuData } from "../../Stores/ActionsMenuStore";
|
||||
|
||||
let actionsMenuData: ActionsMenuData | undefined;
|
||||
let sortedActions: ActionsMenuAction[] | undefined;
|
||||
|
||||
let actionsMenuStoreUnsubscriber: Unsubscriber | null;
|
||||
|
||||
@@ -21,6 +23,20 @@
|
||||
|
||||
actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => {
|
||||
actionsMenuData = value;
|
||||
if (actionsMenuData) {
|
||||
sortedActions = [...actionsMenuData.actions.values()].sort((a, b) => {
|
||||
const ap = a.priority ?? 0;
|
||||
const bp = b.priority ?? 0;
|
||||
if (ap > bp) {
|
||||
return -1;
|
||||
}
|
||||
if (ap < bp) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
@@ -37,15 +53,15 @@
|
||||
<button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>×</button>
|
||||
<h2>{actionsMenuData.playerName}</h2>
|
||||
<div class="actions">
|
||||
{#each [...actionsMenuData.actions] as { actionName, callback }}
|
||||
{#each sortedActions ?? [] as action}
|
||||
<button
|
||||
type="button"
|
||||
class="nes-btn"
|
||||
class="nes-btn {action.style ?? ''}"
|
||||
on:click|preventDefault={() => {
|
||||
callback();
|
||||
action.callback();
|
||||
}}
|
||||
>
|
||||
{actionName}
|
||||
{action.actionName}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -68,7 +84,7 @@
|
||||
color: whitesmoke;
|
||||
|
||||
.actions {
|
||||
max-height: calc(100% - 50px);
|
||||
max-height: 30vh;
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { Game } from "../Phaser/Game/Game";
|
||||
import { chatVisibilityStore } from "../Stores/ChatStore";
|
||||
import { customCharacterSceneVisibleStore } from "../Stores/CustomCharacterStore";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { requestedScreenSharingState, screenSharingAvailableStore } from "../Stores/ScreenSharingStore";
|
||||
import { isSilentStore, requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
|
||||
import monitorImg from "./images/monitor.svg";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import type { PictureStore } from "../../Stores/PictureStore";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import { CustomizeScene, CustomizeSceneName } from "../../Phaser/Login/CustomizeScene";
|
||||
import { activeRowStore } from "../../Stores/CustomCharacterStore";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import { streamableCollectionStore } from "../../Stores/StreamableCollectionStore";
|
||||
import MediaBox from "../Video/MediaBox.svelte";
|
||||
|
||||
export let highlightedEmbedScreen: EmbedScreen | null;
|
||||
export let highlightedEmbedScreen: EmbedScreen | undefined;
|
||||
export let full = false;
|
||||
$: clickable = !full;
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { ICON_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { coWebsitesNotAsleep, mainCoWebsite } from "../../Stores/CoWebsiteStore";
|
||||
import { mainCoWebsite } from "../../Stores/CoWebsiteStore";
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { iframeStates } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
|
||||
@@ -14,16 +15,15 @@
|
||||
|
||||
let icon: HTMLImageElement;
|
||||
let iconLoaded = false;
|
||||
let state = coWebsite.state;
|
||||
|
||||
const coWebsiteUrl = coWebsite.iframe.src;
|
||||
const urlObject = new URL(coWebsiteUrl);
|
||||
let state = coWebsite.getStateSubscriber();
|
||||
let isJitsi: boolean = coWebsite instanceof JitsiCoWebsite;
|
||||
const mainState = coWebsiteManager.getMainStateSubscriber();
|
||||
|
||||
onMount(() => {
|
||||
icon.src = coWebsite.jitsi
|
||||
? "/resources/logos/meet.svg"
|
||||
: `${ICON_URL}/icon?url=${urlObject.hostname}&size=64..96..256&fallback_icon_color=14304c`;
|
||||
icon.alt = coWebsite.altMessage ?? urlObject.hostname;
|
||||
icon.src = isJitsi
|
||||
? "/resources/logos/jitsi.png"
|
||||
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c`;
|
||||
icon.alt = coWebsite.getUrl().hostname;
|
||||
icon.onload = () => {
|
||||
iconLoaded = true;
|
||||
};
|
||||
@@ -33,21 +33,24 @@
|
||||
if (vertical) {
|
||||
coWebsiteManager.goToMain(coWebsite);
|
||||
} else if ($mainCoWebsite) {
|
||||
if ($mainCoWebsite.iframe.id === coWebsite.iframe.id) {
|
||||
const coWebsites = $coWebsitesNotAsleep;
|
||||
const newMain = $highlightedEmbedScreen ?? coWebsites.length > 1 ? coWebsites[1] : undefined;
|
||||
if (newMain && newMain.iframe.id !== $mainCoWebsite.iframe.id) {
|
||||
coWebsiteManager.goToMain(newMain);
|
||||
} else if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||
if ($mainCoWebsite.getId() === coWebsite.getId()) {
|
||||
if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||
coWebsiteManager.displayMain();
|
||||
} else if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||
coWebsiteManager.goToMain($highlightedEmbedScreen.embed);
|
||||
} else {
|
||||
coWebsiteManager.hideMain();
|
||||
}
|
||||
} else {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: coWebsite,
|
||||
});
|
||||
if (coWebsiteManager.getMainState() === iframeStates.closed) {
|
||||
coWebsiteManager.goToMain(coWebsite);
|
||||
coWebsiteManager.displayMain();
|
||||
} else {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: coWebsite,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +68,14 @@
|
||||
let isHighlight: boolean = false;
|
||||
let isMain: boolean = false;
|
||||
$: {
|
||||
isMain = $mainCoWebsite !== undefined && $mainCoWebsite.iframe === coWebsite.iframe;
|
||||
isMain =
|
||||
$mainState === iframeStates.opened &&
|
||||
$mainCoWebsite !== undefined &&
|
||||
$mainCoWebsite.getId() === coWebsite.getId();
|
||||
isHighlight =
|
||||
$highlightedEmbedScreen !== null &&
|
||||
$highlightedEmbedScreen.type === "cowebsite" &&
|
||||
$highlightedEmbedScreen.embed.iframe === coWebsite.iframe;
|
||||
$highlightedEmbedScreen !== undefined &&
|
||||
$highlightedEmbedScreen?.type === "cowebsite" &&
|
||||
$highlightedEmbedScreen?.embed.getId() === coWebsite.getId();
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -86,7 +92,7 @@
|
||||
<img
|
||||
class="cowebsite-icon noselect nes-pointer"
|
||||
class:hide={!iconLoaded}
|
||||
class:jitsi={coWebsite.jitsi}
|
||||
class:jitsi={isJitsi}
|
||||
bind:this={icon}
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
alt=""
|
||||
@@ -182,10 +188,16 @@
|
||||
/>
|
||||
</rect>
|
||||
</svg>
|
||||
|
||||
<!-- TODO use trigger message property -->
|
||||
<div class="cowebsite-hover" class:hide={!isJitsi} style="width: max-content;">
|
||||
<p>Open / Close Jitsi meeting!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.cowebsite-thumbnail {
|
||||
cursor: url("../../../style/images/cursor_pointer.png"), pointer;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
background-color: rgba(#000000, 0.6);
|
||||
@@ -213,7 +225,8 @@
|
||||
}
|
||||
|
||||
&:not(.vertical) {
|
||||
animation: bounce 0.35s ease 6 alternate;
|
||||
transition: all 300ms;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
@@ -229,12 +242,17 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.cowebsite-hover {
|
||||
top: -4px;
|
||||
left: 55px;
|
||||
}
|
||||
|
||||
animation: shake 0.35s ease-in-out;
|
||||
}
|
||||
|
||||
&.displayed {
|
||||
&:not(.vertical) {
|
||||
animation: activeThumbnail 300ms ease-in 0s forwards;
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,16 +281,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes activeThumbnail {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
@@ -318,10 +326,33 @@
|
||||
}
|
||||
|
||||
&.jitsi {
|
||||
filter: invert(100%);
|
||||
-webkit-filter: invert(100%);
|
||||
padding: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.cowebsite-hover {
|
||||
display: block;
|
||||
width: max-content !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cowebsite-hover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
top: -40px;
|
||||
left: -4px;
|
||||
width: 0 !important;
|
||||
min-height: 20px;
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { coWebsites } from "../../Stores/CoWebsiteStore";
|
||||
import CoWebsiteThumbnail from "./CoWebsiteThumbnailSlot.svelte";
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
{#if $coWebsites.length > 0}
|
||||
<div id="cowebsite-thumbnail-container" class:vertical>
|
||||
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.iframe.id)}
|
||||
{#each [...$coWebsites.values()] as coWebsite, index (coWebsite.getId())}
|
||||
<CoWebsiteThumbnail {index} {coWebsite} {vertical} />
|
||||
{/each}
|
||||
</div>
|
||||
@@ -24,6 +24,7 @@
|
||||
left: 2%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overflow: visible;
|
||||
|
||||
&.vertical {
|
||||
height: auto !important;
|
||||
@@ -31,8 +32,6 @@
|
||||
bottom: auto !important;
|
||||
left: auto !important;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 4px;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import PresentationLayout from "./Layouts/PresentationLayout.svelte";
|
||||
import MozaicLayout from "./Layouts/MozaicLayout.svelte";
|
||||
import { LayoutMode } from "../../WebRtc/LayoutManager";
|
||||
|
||||
@@ -9,13 +9,11 @@
|
||||
|
||||
function closeCoWebsite() {
|
||||
if ($highlightedEmbedScreen?.type === "cowebsite") {
|
||||
if ($highlightedEmbedScreen.embed.closable) {
|
||||
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed).catch(() => {
|
||||
console.error("Error during co-website highlighted closing");
|
||||
});
|
||||
if ($highlightedEmbedScreen.embed.isClosable()) {
|
||||
coWebsiteManager.closeCoWebsite($highlightedEmbedScreen.embed);
|
||||
} else {
|
||||
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch(() => {
|
||||
console.error("Error during co-website highlighted unloading");
|
||||
coWebsiteManager.unloadCoWebsite($highlightedEmbedScreen.embed).catch((err) => {
|
||||
console.error("Cannot unload co-website", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -68,9 +66,9 @@
|
||||
/>
|
||||
{/key}
|
||||
{:else if $highlightedEmbedScreen.type === "cowebsite"}
|
||||
{#key $highlightedEmbedScreen.embed.iframe.id}
|
||||
{#key $highlightedEmbedScreen.embed.getId()}
|
||||
<div
|
||||
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.iframe.id}
|
||||
id={"cowebsite-slot-" + $highlightedEmbedScreen.embed.getId()}
|
||||
class="highlighted-cowebsite nes-container is-rounded"
|
||||
>
|
||||
<div class="actions">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene";
|
||||
import {
|
||||
audioConstraintStore,
|
||||
cameraListStore,
|
||||
localStreamStore,
|
||||
localVolumeStore,
|
||||
microphoneListStore,
|
||||
videoConstraintStore,
|
||||
} from "../../Stores/MediaStore";
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
let stream: MediaStream | null;
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe((value) => {
|
||||
const unsubscribeLocalStreamStore = localStreamStore.subscribe((value) => {
|
||||
if (value.type === "success") {
|
||||
stream = value.stream;
|
||||
|
||||
@@ -59,7 +60,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
onDestroy(() => {
|
||||
unsubscribeLocalStreamStore();
|
||||
});
|
||||
|
||||
function normalizeDeviceName(label: string): string {
|
||||
// remove IDs (that can appear in Chrome, like: "HD Pro Webcam (4df7:4eda)"
|
||||
@@ -86,7 +89,7 @@
|
||||
<img class="background-img" src={cinemaCloseImg} alt="" />
|
||||
</div>
|
||||
{/if}
|
||||
<HorizontalSoundMeterWidget {stream} />
|
||||
<HorizontalSoundMeterWidget volume={$localVolumeStore} />
|
||||
|
||||
<section class="selectWebcamForm">
|
||||
{#if $cameraListStore.length > 1}
|
||||
|
||||
@@ -1,50 +1,8 @@
|
||||
<script lang="typescript">
|
||||
import { AudioContext } from "standardized-audio-context";
|
||||
import { SoundMeter } from "../../Phaser/Components/SoundMeter";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
export let stream: MediaStream | null;
|
||||
let volume = 0;
|
||||
<script lang="ts">
|
||||
export let volume = 0;
|
||||
|
||||
const NB_BARS = 20;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
let error = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
error = false;
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try {
|
||||
volume = parseInt(((soundMeter.getVolume() / 100) * NB_BARS).toFixed(0));
|
||||
} catch (err) {
|
||||
if (!error) {
|
||||
console.error(err);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
});
|
||||
|
||||
function color(i: number, volume: number) {
|
||||
const red = (255 * i) / NB_BARS;
|
||||
const green = 255 * (1 - i / NB_BARS);
|
||||
@@ -58,7 +16,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="horizontal-sound-meter" class:active={display}>
|
||||
<div class="horizontal-sound-meter" class:active={volume !== undefined}>
|
||||
{#each [...Array(NB_BARS).keys()] as i (i)}
|
||||
<div style={color(i, volume)} />
|
||||
{/each}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import followImg from "../images/follow.svg";
|
||||
|
||||
export let hidden: Boolean;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
|
||||
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<!-- https://lihautan.com/notes/svelte-lazy-load/ -->
|
||||
<script>
|
||||
export let when = false;
|
||||
export let component;
|
||||
|
||||
let loading;
|
||||
|
||||
$: if (when) {
|
||||
load();
|
||||
}
|
||||
|
||||
function load() {
|
||||
loading = component();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if when}
|
||||
{#await loading then { default: Component }}
|
||||
<Component />
|
||||
{/await}
|
||||
{/if}
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene";
|
||||
import { DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { audioManagerVisibilityStore } from "../Stores/AudioManagerStore";
|
||||
import { embedScreenLayout, hasEmbedScreen } from "../Stores/EmbedScreensStore";
|
||||
@@ -12,7 +12,6 @@
|
||||
import AudioManager from "./AudioManager/AudioManager.svelte";
|
||||
import CameraControls from "./CameraControls.svelte";
|
||||
import EmbedScreensContainer from "./EmbedScreens/EmbedScreensContainer.svelte";
|
||||
import EmoteMenu from "./EmoteMenu/EmoteMenu.svelte";
|
||||
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
|
||||
import LayoutActionManager from "./LayoutActionManager/LayoutActionManager.svelte";
|
||||
import Menu from "./Menu/Menu.svelte";
|
||||
@@ -38,6 +37,7 @@
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { actionsMenuStore } from "../Stores/ActionsMenuStore";
|
||||
import ActionsMenu from "./ActionsMenu/ActionsMenu.svelte";
|
||||
import Lazy from "./Lazy.svelte";
|
||||
|
||||
let mainLayout: HTMLDivElement;
|
||||
|
||||
@@ -116,9 +116,7 @@
|
||||
<VisitCard visitCardUrl={$requestVisitCardsStore} />
|
||||
{/if}
|
||||
|
||||
{#if $emoteMenuStore}
|
||||
<EmoteMenu />
|
||||
{/if}
|
||||
<Lazy when={$emoteMenuStore} component={() => import("./EmoteMenu/EmoteMenu.svelte")} />
|
||||
|
||||
{#if hasEmbedScreen}
|
||||
<EmbedScreensContainer />
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<script lang="ts">
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
||||
|
||||
let entryPoint: string = $startLayerNamesStore[0];
|
||||
let walkAutomatically: boolean = false;
|
||||
const currentPlayer = gameManager.getCurrentGameScene().CurrentPlayer;
|
||||
const playerPos = { x: Math.floor(currentPlayer.x), y: Math.floor(currentPlayer.y) };
|
||||
|
||||
function copyLink() {
|
||||
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
|
||||
@@ -8,8 +15,23 @@
|
||||
document.execCommand("copy");
|
||||
}
|
||||
|
||||
function getLink() {
|
||||
return `${location.origin}${location.pathname}#${entryPoint}${
|
||||
walkAutomatically ? `&moveTo=${playerPos.x},${playerPos.y}` : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
function updateInputFieldValue() {
|
||||
const input = document.getElementById("input-share-link");
|
||||
if (input) {
|
||||
(input as HTMLInputElement).value = getLink();
|
||||
}
|
||||
}
|
||||
|
||||
let canShare = navigator.share !== undefined;
|
||||
|
||||
async function shareLink() {
|
||||
const shareData = { url: location.toString() };
|
||||
const shareData = { url: getLink() };
|
||||
|
||||
try {
|
||||
await navigator.share(shareData);
|
||||
@@ -22,16 +44,43 @@
|
||||
|
||||
<div class="guest-main">
|
||||
<section class="container-overflow">
|
||||
<section class="share-url not-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="text" readonly id="input-share-link" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
|
||||
</section>
|
||||
<section class="is-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
|
||||
{#if !canShare}
|
||||
<section class="share-url not-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="text" readonly id="input-share-link" class="link-url" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={copyLink}>{$LL.menu.invite.copy()}</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section class="is-mobile">
|
||||
<h3>{$LL.menu.invite.description()}</h3>
|
||||
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
|
||||
<button type="button" class="nes-btn is-primary" on:click={shareLink}>{$LL.menu.invite.share()}</button>
|
||||
</section>
|
||||
{/if}
|
||||
<h3>Select an entry point</h3>
|
||||
<section class="nes-select is-dark starting-points">
|
||||
<select
|
||||
bind:value={entryPoint}
|
||||
on:blur={() => {
|
||||
updateInputFieldValue();
|
||||
}}
|
||||
>
|
||||
{#each $startLayerNamesStore as entryPointName}
|
||||
<option value={entryPointName}>{entryPointName}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</section>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="nes-checkbox is-dark"
|
||||
bind:checked={walkAutomatically}
|
||||
on:change={() => {
|
||||
updateInputFieldValue();
|
||||
}}
|
||||
/>
|
||||
<span>{$LL.menu.invite.walk_automatically_to_position()}</span>
|
||||
</label>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -39,14 +88,27 @@
|
||||
@import "../../../style/breakpoints.scss";
|
||||
|
||||
div.guest-main {
|
||||
width: 50%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: calc(100% - 56px);
|
||||
|
||||
text-align: center;
|
||||
input.link-url {
|
||||
width: calc(100% - 200px);
|
||||
}
|
||||
|
||||
.starting-points {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
section.nes-select select:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
@@ -55,25 +117,23 @@
|
||||
}
|
||||
|
||||
section.is-mobile {
|
||||
display: none;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
div.guest-main {
|
||||
section.share-url.not-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section.is-mobile {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
section.container-overflow {
|
||||
height: calc(100% - 120px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
div.guest-main {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
||||
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import logoTalk from "../images/logo-message-pixel.png";
|
||||
import logoWA from "../images/logo-WA-pixel.png";
|
||||
import logoInvite from "../images/logo-invite-pixel.png";
|
||||
@@ -61,15 +61,15 @@
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={showMenu}
|
||||
/>
|
||||
<img
|
||||
src={logoTalk}
|
||||
alt={$LL.menu.icon.open.chat()}
|
||||
class="nes-pointer"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={showChat}
|
||||
/>
|
||||
{/if}
|
||||
<img
|
||||
src={logoTalk}
|
||||
alt={$LL.menu.icon.open.chat()}
|
||||
class="nes-pointer"
|
||||
draggable="false"
|
||||
on:dragstart|preventDefault={noDrag}
|
||||
on:click|preventDefault={showChat}
|
||||
/>
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
|
||||
import { menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected } from "../../Stores/MenuStore";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { videoConstraintStore } from "../../Stores/MediaStore";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
|
||||
@@ -76,7 +76,3 @@
|
||||
<section class="section-input-send-text">
|
||||
<div class="input-send-text" bind:this={QUILL_EDITOR} />
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@import "https://cdn.quilljs.com/1.3.7/quill.snow.css";
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="typescript">
|
||||
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||
<script lang="ts">
|
||||
import { localVolumeStore, obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||
import { localStreamStore, isSilentStore } from "../Stores/MediaStore";
|
||||
import SoundMeterWidget from "./SoundMeterWidget.svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
let stream: MediaStream | null;
|
||||
|
||||
const unsubscribe = localStreamStore.subscribe((value) => {
|
||||
const unsubscribeLocalStreamStore = localStreamStore.subscribe((value) => {
|
||||
if (value.type === "success") {
|
||||
stream = value.stream;
|
||||
} else {
|
||||
@@ -16,7 +16,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(unsubscribe);
|
||||
onDestroy(() => {
|
||||
unsubscribeLocalStreamStore();
|
||||
});
|
||||
|
||||
let isSilent: boolean;
|
||||
const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
|
||||
@@ -51,7 +53,7 @@
|
||||
<div class="is-silent">{$LL.camera.my.silentZone()}</div>
|
||||
{:else if $localStreamStore.type === "success" && $localStreamStore.stream}
|
||||
<video class="my-cam-video" use:srcObject={stream} autoplay muted playsinline />
|
||||
<SoundMeterWidget {stream} />
|
||||
<SoundMeterWidget volume={$localVolumeStore} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
|
||||
|
||||
@@ -1,47 +1,6 @@
|
||||
<script lang="typescript">
|
||||
import { AudioContext } from "standardized-audio-context";
|
||||
import { SoundMeter } from "../Phaser/Components/SoundMeter";
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
export let stream: MediaStream | null;
|
||||
let volume = 0;
|
||||
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const soundMeter = new SoundMeter();
|
||||
let display = false;
|
||||
let error = false;
|
||||
|
||||
$: {
|
||||
if (stream && stream.getAudioTracks().length > 0) {
|
||||
display = true;
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
error = false;
|
||||
}
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try {
|
||||
volume = soundMeter.getVolume();
|
||||
} catch (err) {
|
||||
if (!error) {
|
||||
console.error(err);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
display = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
soundMeter.stop();
|
||||
if (timeout) {
|
||||
clearInterval(timeout);
|
||||
}
|
||||
});
|
||||
<script lang="ts">
|
||||
export let volume = 0;
|
||||
let display = true;
|
||||
</script>
|
||||
|
||||
<div class="sound-progress" class:active={display}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { ScreenSharingLocalMedia } from "../../Stores/ScreenSharingStore";
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||
|
||||
import type { ScreenSharingPeer } from "../../WebRtc/ScreenSharingPeer";
|
||||
import { getColorByString, srcObject } from "./utils";
|
||||
import { getColorByString, srcObject, getTextColorByBackgroundColor } from "./utils";
|
||||
|
||||
export let clickable = false;
|
||||
|
||||
export let peer: ScreenSharingPeer;
|
||||
let streamStore = peer.streamStore;
|
||||
let name = peer.userName;
|
||||
let backGroundColor = getColorByString(peer.userName);
|
||||
let textColor = getTextColorByBackgroundColor(backGroundColor);
|
||||
let statusStore = peer.statusStore;
|
||||
|
||||
let embedScreen: EmbedScreen;
|
||||
@@ -32,7 +34,7 @@
|
||||
{/if}
|
||||
{#if $streamStore !== null}
|
||||
<i class="container">
|
||||
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||
<span style="background-color: {backGroundColor}; color: {textColor};">{name}</span>
|
||||
</i>
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import reportImg from "./images/report.svg";
|
||||
import blockSignImg from "./images/blockSign.svg";
|
||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||
import { getColorByString, srcObject } from "./utils";
|
||||
import { getColorByString, getTextColorByBackgroundColor, srcObject } from "./utils";
|
||||
import { highlightedEmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { EmbedScreen } from "../../Stores/EmbedScreensStore";
|
||||
import type { Streamable } from "../../Stores/StreamableCollectionStore";
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
export let peer: VideoPeer;
|
||||
let streamStore = peer.streamStore;
|
||||
let volumeStore = peer.volumeStore;
|
||||
let name = peer.userName;
|
||||
let backGroundColor = getColorByString(peer.userName);
|
||||
let textColor = getTextColorByBackgroundColor(backGroundColor);
|
||||
let statusStore = peer.statusStore;
|
||||
let constraintStore = peer.constraintsStore;
|
||||
|
||||
@@ -64,7 +67,7 @@
|
||||
{/if}
|
||||
<!-- {#if !$constraintStore || $constraintStore.video === false} -->
|
||||
<i class="container">
|
||||
<span style="background-color: {getColorByString(name)};">{name}</span>
|
||||
<span style="background-color: {backGroundColor}; color: {textColor};">{name}</span>
|
||||
</i>
|
||||
<div class="woka-icon {($constraintStore && $constraintStore.video !== false) || minimized ? '' : 'no-video'}">
|
||||
<Woka userId={peer.userId} placeholderSrc={""} />
|
||||
@@ -93,7 +96,7 @@
|
||||
/>
|
||||
<img src={blockSignImg} draggable="false" on:dragstart|preventDefault={noDrag} class="block-logo" alt="Block" />
|
||||
{#if $constraintStore && $constraintStore.audio !== false}
|
||||
<SoundMeterWidget stream={$streamStore} />
|
||||
<SoundMeterWidget volume={$volumeStore} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,6 +18,24 @@ export function getColorByString(str: string): string | null {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param color: string
|
||||
* @return string
|
||||
*/
|
||||
export function getTextColorByBackgroundColor(color: string | null): string {
|
||||
if (!color) {
|
||||
return "white";
|
||||
}
|
||||
const rgb = color.slice(1);
|
||||
const brightness = Math.round(
|
||||
(parseInt(rgb[0] + rgb[1], 16) * 299 +
|
||||
parseInt(rgb[2] + rgb[3], 16) * 587 +
|
||||
parseInt(rgb[4] + rgb[5], 16) * 114) /
|
||||
1000
|
||||
);
|
||||
return brightness > 125 ? "black" : "white";
|
||||
}
|
||||
|
||||
export function srcObject(node: HTMLVideoElement, stream: MediaStream | null) {
|
||||
node.srcObject = stream;
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
@@ -1,39 +1,36 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { userIsAdminStore, limitMapStore } from "../../Stores/GameStore";
|
||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
|
||||
const upgradeLink = ADMIN_URL + "/pricing";
|
||||
const registerLink = ADMIN_URL + "/second-step-register";
|
||||
</script>
|
||||
|
||||
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
|
||||
{#if $userIsAdminStore}
|
||||
<h2>{$LL.warning.title()}</h2>
|
||||
<p>
|
||||
{$LL.warning.content({ upgradeLink })}
|
||||
</p>
|
||||
<p>{@html $LL.warning.content()}</p>
|
||||
{:else if $limitMapStore}
|
||||
<p>
|
||||
This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>!
|
||||
</p>
|
||||
{:else}
|
||||
<h2>{$LL.warning.title()}</h2>
|
||||
<p>{$LL.warning.limit()}</p>
|
||||
<p>{@html $LL.warning.content()}</p>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
main.warningMain {
|
||||
pointer-events: auto;
|
||||
width: 80%;
|
||||
width: 100%;
|
||||
background-color: #f9e81e;
|
||||
color: #14304c;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
|
||||
top: 4%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import { onDestroy } from "svelte";
|
||||
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="typescript">
|
||||
<script lang="ts">
|
||||
import type { Game } from "../../Phaser/Game/Game";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
|
||||
|
||||
export let game: Game;
|
||||
|
||||
@@ -40,11 +41,13 @@
|
||||
class="selectCharacterSceneFormSubmit nes-btn is-primary"
|
||||
on:click|preventDefault={cameraScene}>{$LL.woka.selectWoka.continue()}</button
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
||||
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
||||
>
|
||||
{#if $customizeAvailableStore}
|
||||
<button
|
||||
type="submit"
|
||||
class="selectCharacterSceneFormCustomYourOwnSubmit nes-btn"
|
||||
on:click|preventDefault={customizeScene}>{$LL.woka.selectWoka.customize()}</button
|
||||
>
|
||||
{/if}
|
||||
</section>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ import { isRegisterData } from "../Messages/JsonMessages/RegisterData";
|
||||
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
|
||||
import { limitMapStore } from "../Stores/GameStore";
|
||||
import { showLimitRoomModalStore } from "../Stores/ModalStore";
|
||||
import { gameManager } from "../Phaser/Game/GameManager";
|
||||
import { locales } from "../i18n/i18n-util";
|
||||
import type { Locales } from "../i18n/i18n-types";
|
||||
import { setCurrentLocale } from "../i18n/locales";
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!: LocalUser;
|
||||
@@ -171,7 +175,7 @@ class ConnectionManager {
|
||||
console.error("Invalid data received from /register route. Data: ", data);
|
||||
throw new Error("Invalid data received from /register route.");
|
||||
}
|
||||
this.localUser = new LocalUser(data.userUuid, data.textures, data.email);
|
||||
this.localUser = new LocalUser(data.userUuid, data.email);
|
||||
this.authToken = data.authToken;
|
||||
localUserStore.saveUser(this.localUser);
|
||||
localUserStore.setAuthToken(this.authToken);
|
||||
@@ -251,22 +255,6 @@ class ConnectionManager {
|
||||
}
|
||||
}
|
||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||
|
||||
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if (this.localUser.textures.length === 0) {
|
||||
this.localUser.textures = this._currentRoom.textures;
|
||||
} else {
|
||||
this._currentRoom.textures.forEach((newTexture) => {
|
||||
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
||||
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||
return;
|
||||
}
|
||||
this.localUser.textures.push(newTexture);
|
||||
});
|
||||
}
|
||||
localUserStore.saveUser(this.localUser);
|
||||
}
|
||||
}
|
||||
if (this._currentRoom == undefined) {
|
||||
return Promise.reject(new Error("Invalid URL"));
|
||||
@@ -292,7 +280,7 @@ class ConnectionManager {
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
const data = await axiosWithRetry.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data);
|
||||
this.localUser = new LocalUser(data.userUuid, [], data.email);
|
||||
this.localUser = new LocalUser(data.userUuid, data.email);
|
||||
this.authToken = data.authToken;
|
||||
if (!isBenchmark) {
|
||||
// In benchmark, we don't have a local storage.
|
||||
@@ -302,7 +290,7 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
public initBenchmark(): void {
|
||||
this.localUser = new LocalUser("", []);
|
||||
this.localUser = new LocalUser("");
|
||||
}
|
||||
|
||||
public connectToRoomSocket(
|
||||
@@ -379,16 +367,52 @@ class ConnectionManager {
|
||||
throw new Error("No Auth code provided");
|
||||
}
|
||||
}
|
||||
const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, {
|
||||
params: { code, nonce, token, playUri: this.currentRoom?.key },
|
||||
}).then((res) => {
|
||||
const { authToken, userUuid, email, username, locale, textures } = await Axios.get(
|
||||
`${PUSHER_URL}/login-callback`,
|
||||
{
|
||||
params: { code, nonce, token, playUri: this.currentRoom?.key },
|
||||
}
|
||||
).then((res) => {
|
||||
return res.data;
|
||||
});
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.localUser = new LocalUser(userUuid, textures, email);
|
||||
this.localUser = new LocalUser(userUuid, email);
|
||||
localUserStore.saveUser(this.localUser);
|
||||
this.authToken = authToken;
|
||||
|
||||
if (username) {
|
||||
gameManager.setPlayerName(username);
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
try {
|
||||
if (locales.indexOf(locale) == -1) {
|
||||
locales.forEach((l) => {
|
||||
if (l.startsWith(locale.split("-")[0])) {
|
||||
setCurrentLocale(l);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setCurrentLocale(locale as Locales);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Could not set locale", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (textures) {
|
||||
const layers: string[] = [];
|
||||
for (const texture of textures) {
|
||||
if (texture !== undefined) {
|
||||
layers.push(texture.id);
|
||||
}
|
||||
}
|
||||
if (layers.length > 0) {
|
||||
gameManager.setCharacterLayers(layers);
|
||||
}
|
||||
}
|
||||
|
||||
//user connected, set connected store for menu at true
|
||||
userIsConnected.set(true);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { SignalData } from "simple-peer";
|
||||
import type { RoomConnection } from "./RoomConnection";
|
||||
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
|
||||
import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
|
||||
|
||||
export interface PointInterface {
|
||||
x: number;
|
||||
@@ -83,6 +82,7 @@ export interface RoomJoinedMessageInterface {
|
||||
//groups: GroupCreatedUpdatedMessageInterface[],
|
||||
items: { [itemId: number]: unknown };
|
||||
variables: Map<string, unknown>;
|
||||
characterLayers: BodyResourceDescriptionInterface[];
|
||||
}
|
||||
|
||||
export interface PlayGlobalMessageInterface {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable";
|
||||
|
||||
export type LayerNames = "woka" | "body" | "eyes" | "hair" | "clothes" | "hat" | "accessory";
|
||||
|
||||
export interface CharacterTexture {
|
||||
id: number;
|
||||
level: number;
|
||||
id: string;
|
||||
layer: LayerNames;
|
||||
url: string;
|
||||
rights: string;
|
||||
}
|
||||
|
||||
export const maxUserNameLength: number = MAX_USERNAME_LENGTH;
|
||||
@@ -14,9 +15,11 @@ export function isUserNameValid(value: unknown): boolean {
|
||||
}
|
||||
|
||||
export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||
if (!value || !value.length) return false;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (/^\w+$/.exec(value[i]) === null) {
|
||||
if (!value || !value.length) {
|
||||
return false;
|
||||
}
|
||||
for (const layerName of value) {
|
||||
if (layerName.length === 0 || layerName === " ") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -24,9 +27,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean {
|
||||
}
|
||||
|
||||
export class LocalUser {
|
||||
constructor(
|
||||
public readonly uuid: string,
|
||||
public textures: CharacterTexture[],
|
||||
public email: string | null = null
|
||||
) {}
|
||||
constructor(public readonly uuid: string, public email: string | null = null) {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
|
||||
import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect";
|
||||
|
||||
export class MapDetail {
|
||||
constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {}
|
||||
constructor(public readonly mapUrl: string) {}
|
||||
}
|
||||
|
||||
export interface RoomRedirect {
|
||||
@@ -25,7 +25,6 @@ export class Room {
|
||||
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
|
||||
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
private instance: string | undefined;
|
||||
private readonly _search: URLSearchParams;
|
||||
private _contactPage: string | undefined;
|
||||
@@ -118,7 +117,6 @@ export class Room {
|
||||
} else if (isMapDetailsData(data)) {
|
||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._group = data.group;
|
||||
this._authenticationMandatory =
|
||||
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
|
||||
@@ -128,7 +126,7 @@ export class Room {
|
||||
this._expireOn = new Date(data.expireOn);
|
||||
}
|
||||
this._canReport = data.canReport ?? false;
|
||||
return new MapDetail(data.mapUrl, data.textures);
|
||||
return new MapDetail(data.mapUrl);
|
||||
} else {
|
||||
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
|
||||
}
|
||||
@@ -205,10 +203,6 @@ export class Room {
|
||||
return this.roomUrl.toString();
|
||||
}
|
||||
|
||||
get textures(): CharacterTexture[] | undefined {
|
||||
return this._textures;
|
||||
}
|
||||
|
||||
get mapUrl(): string {
|
||||
if (!this._mapUrl) {
|
||||
throw new Error("Map URL not fetched yet");
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe
|
||||
import { adminMessagesService } from "./AdminMessagesService";
|
||||
import { connectionManager } from "./ConnectionManager";
|
||||
import { get } from "svelte/store";
|
||||
import { warningContainerStore } from "../Stores/MenuStore";
|
||||
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
|
||||
import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore";
|
||||
import { localUserStore } from "./LocalUserStore";
|
||||
import {
|
||||
@@ -52,10 +52,14 @@ import {
|
||||
PositionMessage_Direction,
|
||||
SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto,
|
||||
PingMessage as PingMessageTsProto,
|
||||
CharacterLayerMessage,
|
||||
} from "../Messages/ts-proto-generated/messages";
|
||||
import { Subject } from "rxjs";
|
||||
import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent";
|
||||
import { match } from "assert";
|
||||
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
|
||||
import { gameManager } from "../Phaser/Game/GameManager";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
|
||||
|
||||
const manualPingDelay = 20000;
|
||||
|
||||
@@ -337,11 +341,28 @@ export class RoomConnection implements RoomConnection {
|
||||
this.tags = roomJoinedMessage.tag;
|
||||
this._userRoomToken = roomJoinedMessage.userRoomToken;
|
||||
|
||||
// If one of the URLs sent to us does not exist, let's go to the Woka selection screen.
|
||||
for (const characterLayer of roomJoinedMessage.characterLayer) {
|
||||
if (!characterLayer.url) {
|
||||
this.goToSelectYourWokaScene();
|
||||
this.closed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.closed) {
|
||||
break;
|
||||
}
|
||||
|
||||
const characterLayers = roomJoinedMessage.characterLayer.map(
|
||||
this.mapCharacterLayerToBodyResourceDescription.bind(this)
|
||||
);
|
||||
|
||||
this._roomJoinedMessageStream.next({
|
||||
connection: this,
|
||||
room: {
|
||||
items,
|
||||
variables,
|
||||
characterLayers,
|
||||
} as RoomJoinedMessageInterface,
|
||||
});
|
||||
break;
|
||||
@@ -351,6 +372,12 @@ export class RoomConnection implements RoomConnection {
|
||||
this.closed = true;
|
||||
break;
|
||||
}
|
||||
case "invalidTextureMessage": {
|
||||
this.goToSelectYourWokaScene();
|
||||
|
||||
this.closed = true;
|
||||
break;
|
||||
}
|
||||
case "tokenExpiredMessage": {
|
||||
connectionManager.logout().catch((e) => console.error(e));
|
||||
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
|
||||
@@ -591,6 +618,15 @@ export class RoomConnection implements RoomConnection {
|
||||
});
|
||||
}*/
|
||||
|
||||
private mapCharacterLayerToBodyResourceDescription(
|
||||
characterLayer: CharacterLayerMessage
|
||||
): BodyResourceDescriptionInterface {
|
||||
return {
|
||||
id: characterLayer.name,
|
||||
img: characterLayer.url,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: move this to protobuf utils
|
||||
private toMessageUserJoined(message: UserJoinedMessageTsProto): MessageUserJoined {
|
||||
const position = message.position;
|
||||
@@ -598,12 +634,7 @@ export class RoomConnection implements RoomConnection {
|
||||
throw new Error("Invalid JOIN_ROOM message");
|
||||
}
|
||||
|
||||
const characterLayers = message.characterLayers.map((characterLayer): BodyResourceDescriptionInterface => {
|
||||
return {
|
||||
name: characterLayer.name,
|
||||
img: characterLayer.url,
|
||||
};
|
||||
});
|
||||
const characterLayers = message.characterLayers.map(this.mapCharacterLayerToBodyResourceDescription.bind(this));
|
||||
|
||||
const companion = message.companion;
|
||||
|
||||
@@ -863,4 +894,11 @@ export class RoomConnection implements RoomConnection {
|
||||
public get userRoomToken(): string | undefined {
|
||||
return this._userRoomToken;
|
||||
}
|
||||
|
||||
private goToSelectYourWokaScene(): void {
|
||||
menuVisiblilityStore.set(false);
|
||||
menuIconVisiblilityStore.set(false);
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,33 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
}
|
||||
import { getEnvConfig } from "@geprog/vite-plugin-env-config/getEnvConfig";
|
||||
|
||||
const getEnv = (key: string): string | undefined => {
|
||||
if (global.window?.env) {
|
||||
return global.window.env[key];
|
||||
}
|
||||
if (global.process?.env) {
|
||||
return global.process.env[key];
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
const DEBUG_MODE: boolean = getEnv("DEBUG_MODE") == "true";
|
||||
const START_ROOM_URL: string = getEnv("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||
const PUSHER_URL = getEnv("PUSHER_URL") || "//pusher.workadventure.localhost";
|
||||
export const ADMIN_URL = getEnv("ADMIN_URL") || "//workadventu.re";
|
||||
const UPLOADER_URL = getEnv("UPLOADER_URL") || "//uploader.workadventure.localhost";
|
||||
const ICON_URL = getEnv("ICON_URL") || "//icon.workadventure.localhost";
|
||||
const STUN_SERVER: string = getEnv("STUN_SERVER") || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = getEnv("TURN_SERVER") || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnv("SKIP_RENDER_OPTIMIZATIONS") == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = getEnv("DISABLE_NOTIFICATIONS") == "true";
|
||||
const TURN_USER: string = getEnv("TURN_USER") || "";
|
||||
const TURN_PASSWORD: string = getEnv("TURN_PASSWORD") || "";
|
||||
const JITSI_URL: string | undefined = getEnv("JITSI_URL") === "" ? undefined : getEnv("JITSI_URL");
|
||||
const JITSI_PRIVATE_MODE: boolean = getEnv("JITSI_PRIVATE_MODE") == "true";
|
||||
const DEBUG_MODE: boolean = getEnvConfig("DEBUG_MODE") == "true";
|
||||
const START_ROOM_URL: string =
|
||||
getEnvConfig("START_ROOM_URL") || "/_/global/maps.workadventure.localhost/Floor1/floor1.json";
|
||||
const PUSHER_URL = getEnvConfig("PUSHER_URL") || "//pusher.workadventure.localhost";
|
||||
export const ADMIN_URL = getEnvConfig("ADMIN_URL") || "//workadventu.re";
|
||||
const UPLOADER_URL = getEnvConfig("UPLOADER_URL") || "//uploader.workadventure.localhost";
|
||||
const ICON_URL = getEnvConfig("ICON_URL") || "//icon.workadventure.localhost";
|
||||
const STUN_SERVER: string = getEnvConfig("STUN_SERVER") || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = getEnvConfig("TURN_SERVER") || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = getEnvConfig("SKIP_RENDER_OPTIMIZATIONS") == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = getEnvConfig("DISABLE_NOTIFICATIONS") == "true";
|
||||
const TURN_USER: string = getEnvConfig("TURN_USER") || "";
|
||||
const TURN_PASSWORD: string = getEnvConfig("TURN_PASSWORD") || "";
|
||||
const JITSI_URL: string | undefined = getEnvConfig("JITSI_URL") === "" ? undefined : getEnvConfig("JITSI_URL");
|
||||
const JITSI_PRIVATE_MODE: boolean = getEnvConfig("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
|
||||
export const MAX_USERNAME_LENGTH = parseInt(getEnv("MAX_USERNAME_LENGTH") || "") || 8;
|
||||
export const MAX_PER_GROUP = parseInt(getEnv("MAX_PER_GROUP") || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = getEnv("DISPLAY_TERMS_OF_USE") == "true";
|
||||
export const NODE_ENV = getEnv("NODE_ENV") || "development";
|
||||
export const CONTACT_URL = getEnv("CONTACT_URL") || undefined;
|
||||
export const PROFILE_URL = getEnv("PROFILE_URL") || undefined;
|
||||
export const POSTHOG_API_KEY: string = (getEnv("POSTHOG_API_KEY") as string) || "";
|
||||
export const POSTHOG_URL = getEnv("POSTHOG_URL") || undefined;
|
||||
export const DISABLE_ANONYMOUS: boolean = getEnv("DISABLE_ANONYMOUS") === "true";
|
||||
export const OPID_LOGIN_SCREEN_PROVIDER = getEnv("OPID_LOGIN_SCREEN_PROVIDER");
|
||||
const FALLBACK_LOCALE = getEnv("FALLBACK_LOCALE") || undefined;
|
||||
export const MAX_USERNAME_LENGTH = parseInt(getEnvConfig("MAX_USERNAME_LENGTH") || "") || 8;
|
||||
export const MAX_PER_GROUP = parseInt(getEnvConfig("MAX_PER_GROUP") || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = getEnvConfig("DISPLAY_TERMS_OF_USE") == "true";
|
||||
export const NODE_ENV = getEnvConfig("NODE_ENV") || "development";
|
||||
export const CONTACT_URL = getEnvConfig("CONTACT_URL") || undefined;
|
||||
export const PROFILE_URL = getEnvConfig("PROFILE_URL") || undefined;
|
||||
export const POSTHOG_API_KEY: string = (getEnvConfig("POSTHOG_API_KEY") as string) || "";
|
||||
export const POSTHOG_URL = getEnvConfig("POSTHOG_URL") || undefined;
|
||||
export const DISABLE_ANONYMOUS: boolean = getEnvConfig("DISABLE_ANONYMOUS") === "true";
|
||||
export const OPID_LOGIN_SCREEN_PROVIDER = getEnvConfig("OPID_LOGIN_SCREEN_PROVIDER");
|
||||
const FALLBACK_LOCALE = getEnvConfig("FALLBACK_LOCALE") || undefined;
|
||||
|
||||
export {
|
||||
DEBUG_MODE,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
|
||||
import { AudioContext, IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "standardized-audio-context";
|
||||
|
||||
/**
|
||||
* Class to measure the sound volume of a media stream
|
||||
@@ -6,41 +6,15 @@ import type { IAnalyserNode, IAudioContext, IMediaStreamAudioSourceNode } from "
|
||||
export class SoundMeter {
|
||||
private instant: number;
|
||||
private clip: number;
|
||||
//private script: ScriptProcessorNode;
|
||||
private analyser: IAnalyserNode<IAudioContext> | undefined;
|
||||
private dataArray: Uint8Array | undefined;
|
||||
private context: IAudioContext | undefined;
|
||||
private source: IMediaStreamAudioSourceNode<IAudioContext> | undefined;
|
||||
|
||||
constructor() {
|
||||
constructor(mediaStream: MediaStream) {
|
||||
this.instant = 0.0;
|
||||
this.clip = 0.0;
|
||||
//this.script = context.createScriptProcessor(2048, 1, 1);
|
||||
}
|
||||
|
||||
private init(context: IAudioContext) {
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
|
||||
this.analyser.fftSize = 2048;
|
||||
const bufferLength = this.analyser.fftSize;
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
public connectToSource(stream: MediaStream, context: IAudioContext): void {
|
||||
if (this.source !== undefined) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
this.init(context);
|
||||
|
||||
this.source = this.context?.createMediaStreamSource(stream);
|
||||
if (this.analyser !== undefined) {
|
||||
this.source?.connect(this.analyser);
|
||||
}
|
||||
//analyser.connect(distortion);
|
||||
//distortion.connect(this.context.destination);
|
||||
//this.analyser.connect(this.context.destination);
|
||||
this.connectToSource(mediaStream, new AudioContext());
|
||||
}
|
||||
|
||||
public getVolume(): number {
|
||||
@@ -78,4 +52,29 @@ export class SoundMeter {
|
||||
this.dataArray = undefined;
|
||||
this.source = undefined;
|
||||
}
|
||||
|
||||
private init(context: IAudioContext) {
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
|
||||
this.analyser.fftSize = 2048;
|
||||
const bufferLength = this.analyser.fftSize;
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
|
||||
private connectToSource(stream: MediaStream, context: IAudioContext): void {
|
||||
if (this.source !== undefined) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
this.init(context);
|
||||
|
||||
this.source = this.context?.createMediaStreamSource(stream);
|
||||
if (this.analyser !== undefined) {
|
||||
this.source?.connect(this.analyser);
|
||||
}
|
||||
//analyser.connect(distortion);
|
||||
//distortion.connect(this.context.destination);
|
||||
//this.analyser.connect(this.context.destination);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Easing } from "../../types";
|
||||
|
||||
export class TalkIcon extends Phaser.GameObjects.Image {
|
||||
private shown: boolean;
|
||||
private showAnimationTween?: Phaser.Tweens.Tween;
|
||||
|
||||
private defaultPosition: { x: number; y: number };
|
||||
private defaultScale: number;
|
||||
|
||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||
super(scene, x, y, "iconTalk");
|
||||
|
||||
this.defaultPosition = { x, y };
|
||||
this.defaultScale = 0.3;
|
||||
|
||||
this.shown = false;
|
||||
this.setAlpha(0);
|
||||
this.setScale(this.defaultScale);
|
||||
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
public show(show: boolean = true, forceClose: boolean = false): void {
|
||||
if (this.shown === show && !forceClose) {
|
||||
return;
|
||||
}
|
||||
this.showAnimation(show, forceClose);
|
||||
}
|
||||
|
||||
private showAnimation(show: boolean = true, forceClose: boolean = false) {
|
||||
if (forceClose && !show) {
|
||||
this.showAnimationTween?.stop();
|
||||
} else if (this.showAnimationTween?.isPlaying()) {
|
||||
return;
|
||||
}
|
||||
this.shown = show;
|
||||
if (show) {
|
||||
this.y += 50;
|
||||
this.scale = 0.05;
|
||||
this.alpha = 0;
|
||||
}
|
||||
this.showAnimationTween = this.scene.tweens.add({
|
||||
targets: [this],
|
||||
duration: 350,
|
||||
alpha: show ? 1 : 0,
|
||||
y: this.defaultPosition.y,
|
||||
scale: this.defaultScale,
|
||||
ease: Easing.BackEaseOut,
|
||||
onComplete: () => {
|
||||
this.showAnimationTween = undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public isShown(): boolean {
|
||||
return this.shown;
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,14 @@ import type { GameScene } from "../Game/GameScene";
|
||||
import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes";
|
||||
import type OutlinePipelinePlugin from "phaser3-rex-plugins/plugins/outlinepipeline-plugin.js";
|
||||
import { isSilentStore } from "../../Stores/MediaStore";
|
||||
import { lazyLoadPlayerCharacterTextures, loadAllDefaultModels } from "./PlayerTexturesLoadingManager";
|
||||
import { lazyLoadPlayerCharacterTextures } from "./PlayerTexturesLoadingManager";
|
||||
import { TexturesHelper } from "../Helpers/TexturesHelper";
|
||||
import type { PictureStore } from "../../Stores/PictureStore";
|
||||
import { Unsubscriber, Writable, writable } from "svelte/store";
|
||||
import { createColorStore } from "../../Stores/OutlineColorStore";
|
||||
import type { OutlineableInterface } from "../Game/OutlineableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import { TalkIcon } from "../Components/TalkIcon";
|
||||
|
||||
const playerNameY = -25;
|
||||
|
||||
@@ -33,6 +34,7 @@ const interactiveRadius = 35;
|
||||
export abstract class Character extends Container implements OutlineableInterface {
|
||||
private bubble: SpeechBubble | null = null;
|
||||
private readonly playerNameText: Text;
|
||||
private readonly talkIcon: TalkIcon;
|
||||
public playerName: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||
@@ -81,7 +83,16 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
return lazyLoadPlayerCharacterTextures(scene.load, ["color_22", "eyes_23"]).then((textures) => {
|
||||
return lazyLoadPlayerCharacterTextures(scene.load, [
|
||||
{
|
||||
id: "color_22",
|
||||
img: "resources/customisation/character_color/character_color21.png",
|
||||
},
|
||||
{
|
||||
id: "eyes_23",
|
||||
img: "resources/customisation/character_eyes/character_eyes23.png",
|
||||
},
|
||||
]).then((textures) => {
|
||||
this.addTextures(textures, frame);
|
||||
this.invisible = false;
|
||||
this.playAnimation(direction, moving);
|
||||
@@ -95,13 +106,24 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
fontFamily: '"Press Start 2P"',
|
||||
fontSize: "8px",
|
||||
strokeThickness: 2,
|
||||
stroke: "gray",
|
||||
stroke: "#14304C",
|
||||
metrics: {
|
||||
ascent: 20,
|
||||
descent: 10,
|
||||
fontSize: 35,
|
||||
},
|
||||
});
|
||||
|
||||
this.talkIcon = new TalkIcon(scene, 0, -45);
|
||||
this.add(this.talkIcon);
|
||||
|
||||
if (isClickable) {
|
||||
this.setInteractive({
|
||||
hitArea: new Phaser.Geom.Circle(0, 0, interactiveRadius),
|
||||
hitAreaCallback: Phaser.Geom.Circle.Contains, //eslint-disable-line @typescript-eslint/unbound-method
|
||||
useHandCursor: true,
|
||||
});
|
||||
}
|
||||
this.playerNameText.setOrigin(0.5).setDepth(DEPTH_INGAME_TEXT_INDEX);
|
||||
this.add(this.playerNameText);
|
||||
|
||||
@@ -200,6 +222,10 @@ export abstract class Character extends Container implements OutlineableInterfac
|
||||
});
|
||||
}
|
||||
|
||||
public showTalkIcon(show: boolean = true, forceClose: boolean = false): void {
|
||||
this.talkIcon.show(show, forceClose);
|
||||
}
|
||||
|
||||
public addCompanion(name: string, texturePromise?: CancelablePromise<string>): void {
|
||||
if (typeof texturePromise !== "undefined") {
|
||||
this.companion = new Companion(this.scene, this.x, this.y, name, texturePromise);
|
||||
|
||||
@@ -5,446 +5,122 @@ export interface BodyResourceDescriptionListInterface {
|
||||
}
|
||||
|
||||
export interface BodyResourceDescriptionInterface {
|
||||
name: string;
|
||||
id: string;
|
||||
img: string;
|
||||
level?: number;
|
||||
}
|
||||
|
||||
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
male1: { name: "male1", img: "resources/characters/pipoya/Male 01-1.png" },
|
||||
male2: { name: "male2", img: "resources/characters/pipoya/Male 02-2.png" },
|
||||
male3: { name: "male3", img: "resources/characters/pipoya/Male 03-4.png" },
|
||||
male4: { name: "male4", img: "resources/characters/pipoya/Male 09-1.png" },
|
||||
male5: { name: "male5", img: "resources/characters/pipoya/Male 10-3.png" },
|
||||
male6: { name: "male6", img: "resources/characters/pipoya/Male 17-2.png" },
|
||||
male7: { name: "male7", img: "resources/characters/pipoya/Male 18-1.png" },
|
||||
male8: { name: "male8", img: "resources/characters/pipoya/Male 16-4.png" },
|
||||
male9: { name: "male9", img: "resources/characters/pipoya/Male 07-2.png" },
|
||||
male10: { name: "male10", img: "resources/characters/pipoya/Male 05-3.png" },
|
||||
male11: { name: "male11", img: "resources/characters/pipoya/Teacher male 02.png" },
|
||||
male12: { name: "male12", img: "resources/characters/pipoya/su4 Student male 12.png" },
|
||||
|
||||
Female1: { name: "Female1", img: "resources/characters/pipoya/Female 01-1.png" },
|
||||
Female2: { name: "Female2", img: "resources/characters/pipoya/Female 02-2.png" },
|
||||
Female3: { name: "Female3", img: "resources/characters/pipoya/Female 03-4.png" },
|
||||
Female4: { name: "Female4", img: "resources/characters/pipoya/Female 09-1.png" },
|
||||
Female5: { name: "Female5", img: "resources/characters/pipoya/Female 10-3.png" },
|
||||
Female6: { name: "Female6", img: "resources/characters/pipoya/Female 17-2.png" },
|
||||
Female7: { name: "Female7", img: "resources/characters/pipoya/Female 18-1.png" },
|
||||
Female8: { name: "Female8", img: "resources/characters/pipoya/Female 16-4.png" },
|
||||
Female9: { name: "Female9", img: "resources/characters/pipoya/Female 07-2.png" },
|
||||
Female10: { name: "Female10", img: "resources/characters/pipoya/Female 05-3.png" },
|
||||
Female11: { name: "Female11", img: "resources/characters/pipoya/Teacher fmale 02.png" },
|
||||
Female12: { name: "Female12", img: "resources/characters/pipoya/su4 Student fmale 12.png" },
|
||||
/**
|
||||
* Temporary object to map layers to the old "level" concept.
|
||||
*/
|
||||
export const mapLayerToLevel = {
|
||||
woka: -1,
|
||||
body: 0,
|
||||
eyes: 1,
|
||||
hair: 2,
|
||||
clothes: 3,
|
||||
hat: 4,
|
||||
accessory: 5,
|
||||
};
|
||||
|
||||
export const COLOR_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
color_1: { name: "color_1", img: "resources/customisation/character_color/character_color0.png" },
|
||||
color_2: { name: "color_2", img: "resources/customisation/character_color/character_color1.png" },
|
||||
color_3: { name: "color_3", img: "resources/customisation/character_color/character_color2.png" },
|
||||
color_4: { name: "color_4", img: "resources/customisation/character_color/character_color3.png" },
|
||||
color_5: { name: "color_5", img: "resources/customisation/character_color/character_color4.png" },
|
||||
color_6: { name: "color_6", img: "resources/customisation/character_color/character_color5.png" },
|
||||
color_7: { name: "color_7", img: "resources/customisation/character_color/character_color6.png" },
|
||||
color_8: { name: "color_8", img: "resources/customisation/character_color/character_color7.png" },
|
||||
color_9: { name: "color_9", img: "resources/customisation/character_color/character_color8.png" },
|
||||
color_10: { name: "color_10", img: "resources/customisation/character_color/character_color9.png" },
|
||||
color_11: { name: "color_11", img: "resources/customisation/character_color/character_color10.png" },
|
||||
color_12: { name: "color_12", img: "resources/customisation/character_color/character_color11.png" },
|
||||
color_13: { name: "color_13", img: "resources/customisation/character_color/character_color12.png" },
|
||||
color_14: { name: "color_14", img: "resources/customisation/character_color/character_color13.png" },
|
||||
color_15: { name: "color_15", img: "resources/customisation/character_color/character_color14.png" },
|
||||
color_16: { name: "color_16", img: "resources/customisation/character_color/character_color15.png" },
|
||||
color_17: { name: "color_17", img: "resources/customisation/character_color/character_color16.png" },
|
||||
color_18: { name: "color_18", img: "resources/customisation/character_color/character_color17.png" },
|
||||
color_19: { name: "color_19", img: "resources/customisation/character_color/character_color18.png" },
|
||||
color_20: { name: "color_20", img: "resources/customisation/character_color/character_color19.png" },
|
||||
color_21: { name: "color_21", img: "resources/customisation/character_color/character_color20.png" },
|
||||
color_22: { name: "color_22", img: "resources/customisation/character_color/character_color21.png" },
|
||||
color_23: { name: "color_23", img: "resources/customisation/character_color/character_color22.png" },
|
||||
color_24: { name: "color_24", img: "resources/customisation/character_color/character_color23.png" },
|
||||
color_25: { name: "color_25", img: "resources/customisation/character_color/character_color24.png" },
|
||||
color_26: { name: "color_26", img: "resources/customisation/character_color/character_color25.png" },
|
||||
color_27: { name: "color_27", img: "resources/customisation/character_color/character_color26.png" },
|
||||
color_28: { name: "color_28", img: "resources/customisation/character_color/character_color27.png" },
|
||||
color_29: { name: "color_29", img: "resources/customisation/character_color/character_color28.png" },
|
||||
color_30: { name: "color_30", img: "resources/customisation/character_color/character_color29.png" },
|
||||
color_31: { name: "color_31", img: "resources/customisation/character_color/character_color30.png" },
|
||||
color_32: { name: "color_32", img: "resources/customisation/character_color/character_color31.png" },
|
||||
color_33: { name: "color_33", img: "resources/customisation/character_color/character_color32.png" },
|
||||
};
|
||||
export enum PlayerTexturesKey {
|
||||
Accessory = "accessory",
|
||||
Body = "body",
|
||||
Clothes = "clothes",
|
||||
Eyes = "eyes",
|
||||
Hair = "hair",
|
||||
Hat = "hat",
|
||||
Woka = "woka",
|
||||
}
|
||||
|
||||
export const EYES_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
eyes_1: { name: "eyes_1", img: "resources/customisation/character_eyes/character_eyes1.png" },
|
||||
eyes_2: { name: "eyes_2", img: "resources/customisation/character_eyes/character_eyes2.png" },
|
||||
eyes_3: { name: "eyes_3", img: "resources/customisation/character_eyes/character_eyes3.png" },
|
||||
eyes_4: { name: "eyes_4", img: "resources/customisation/character_eyes/character_eyes4.png" },
|
||||
eyes_5: { name: "eyes_5", img: "resources/customisation/character_eyes/character_eyes5.png" },
|
||||
eyes_6: { name: "eyes_6", img: "resources/customisation/character_eyes/character_eyes6.png" },
|
||||
eyes_7: { name: "eyes_7", img: "resources/customisation/character_eyes/character_eyes7.png" },
|
||||
eyes_8: { name: "eyes_8", img: "resources/customisation/character_eyes/character_eyes8.png" },
|
||||
eyes_9: { name: "eyes_9", img: "resources/customisation/character_eyes/character_eyes9.png" },
|
||||
eyes_10: { name: "eyes_10", img: "resources/customisation/character_eyes/character_eyes10.png" },
|
||||
eyes_11: { name: "eyes_11", img: "resources/customisation/character_eyes/character_eyes11.png" },
|
||||
eyes_12: { name: "eyes_12", img: "resources/customisation/character_eyes/character_eyes12.png" },
|
||||
eyes_13: { name: "eyes_13", img: "resources/customisation/character_eyes/character_eyes13.png" },
|
||||
eyes_14: { name: "eyes_14", img: "resources/customisation/character_eyes/character_eyes14.png" },
|
||||
eyes_15: { name: "eyes_15", img: "resources/customisation/character_eyes/character_eyes15.png" },
|
||||
eyes_16: { name: "eyes_16", img: "resources/customisation/character_eyes/character_eyes16.png" },
|
||||
eyes_17: { name: "eyes_17", img: "resources/customisation/character_eyes/character_eyes17.png" },
|
||||
eyes_18: { name: "eyes_18", img: "resources/customisation/character_eyes/character_eyes18.png" },
|
||||
eyes_19: { name: "eyes_19", img: "resources/customisation/character_eyes/character_eyes19.png" },
|
||||
eyes_20: { name: "eyes_20", img: "resources/customisation/character_eyes/character_eyes20.png" },
|
||||
eyes_21: { name: "eyes_21", img: "resources/customisation/character_eyes/character_eyes21.png" },
|
||||
eyes_22: { name: "eyes_22", img: "resources/customisation/character_eyes/character_eyes22.png" },
|
||||
eyes_23: { name: "eyes_23", img: "resources/customisation/character_eyes/character_eyes23.png" },
|
||||
eyes_24: { name: "eyes_24", img: "resources/customisation/character_eyes/character_eyes24.png" },
|
||||
eyes_25: { name: "eyes_25", img: "resources/customisation/character_eyes/character_eyes25.png" },
|
||||
eyes_26: { name: "eyes_26", img: "resources/customisation/character_eyes/character_eyes26.png" },
|
||||
eyes_27: { name: "eyes_27", img: "resources/customisation/character_eyes/character_eyes27.png" },
|
||||
eyes_28: { name: "eyes_28", img: "resources/customisation/character_eyes/character_eyes28.png" },
|
||||
eyes_29: { name: "eyes_29", img: "resources/customisation/character_eyes/character_eyes29.png" },
|
||||
eyes_30: { name: "eyes_30", img: "resources/customisation/character_eyes/character_eyes30.png" },
|
||||
};
|
||||
type PlayerTexturesMetadata = Record<PlayerTexturesKey, PlayerTexturesCategory>;
|
||||
|
||||
export const HAIR_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
hair_1: { name: "hair_1", img: "resources/customisation/character_hairs/character_hairs0.png" },
|
||||
hair_2: { name: "hair_2", img: "resources/customisation/character_hairs/character_hairs1.png" },
|
||||
hair_3: { name: "hair_3", img: "resources/customisation/character_hairs/character_hairs2.png" },
|
||||
hair_4: { name: "hair_4", img: "resources/customisation/character_hairs/character_hairs3.png" },
|
||||
hair_5: { name: "hair_5", img: "resources/customisation/character_hairs/character_hairs4.png" },
|
||||
hair_6: { name: "hair_6", img: "resources/customisation/character_hairs/character_hairs5.png" },
|
||||
hair_7: { name: "hair_7", img: "resources/customisation/character_hairs/character_hairs6.png" },
|
||||
hair_8: { name: "hair_8", img: "resources/customisation/character_hairs/character_hairs7.png" },
|
||||
hair_9: { name: "hair_9", img: "resources/customisation/character_hairs/character_hairs8.png" },
|
||||
hair_10: { name: "hair_10", img: "resources/customisation/character_hairs/character_hairs9.png" },
|
||||
hair_11: { name: "hair_11", img: "resources/customisation/character_hairs/character_hairs10.png" },
|
||||
hair_12: { name: "hair_12", img: "resources/customisation/character_hairs/character_hairs11.png" },
|
||||
hair_13: { name: "hair_13", img: "resources/customisation/character_hairs/character_hairs12.png" },
|
||||
hair_14: { name: "hair_14", img: "resources/customisation/character_hairs/character_hairs13.png" },
|
||||
hair_15: { name: "hair_15", img: "resources/customisation/character_hairs/character_hairs14.png" },
|
||||
hair_16: { name: "hair_16", img: "resources/customisation/character_hairs/character_hairs15.png" },
|
||||
hair_17: { name: "hair_17", img: "resources/customisation/character_hairs/character_hairs16.png" },
|
||||
hair_18: { name: "hair_18", img: "resources/customisation/character_hairs/character_hairs17.png" },
|
||||
hair_19: { name: "hair_19", img: "resources/customisation/character_hairs/character_hairs18.png" },
|
||||
hair_20: { name: "hair_20", img: "resources/customisation/character_hairs/character_hairs19.png" },
|
||||
hair_21: { name: "hair_21", img: "resources/customisation/character_hairs/character_hairs20.png" },
|
||||
hair_22: { name: "hair_22", img: "resources/customisation/character_hairs/character_hairs21.png" },
|
||||
hair_23: { name: "hair_23", img: "resources/customisation/character_hairs/character_hairs22.png" },
|
||||
hair_24: { name: "hair_24", img: "resources/customisation/character_hairs/character_hairs23.png" },
|
||||
hair_25: { name: "hair_25", img: "resources/customisation/character_hairs/character_hairs24.png" },
|
||||
hair_26: { name: "hair_26", img: "resources/customisation/character_hairs/character_hairs25.png" },
|
||||
hair_27: { name: "hair_27", img: "resources/customisation/character_hairs/character_hairs26.png" },
|
||||
hair_28: { name: "hair_28", img: "resources/customisation/character_hairs/character_hairs27.png" },
|
||||
hair_29: { name: "hair_29", img: "resources/customisation/character_hairs/character_hairs28.png" },
|
||||
hair_30: { name: "hair_30", img: "resources/customisation/character_hairs/character_hairs29.png" },
|
||||
hair_31: { name: "hair_31", img: "resources/customisation/character_hairs/character_hairs30.png" },
|
||||
hair_32: { name: "hair_32", img: "resources/customisation/character_hairs/character_hairs31.png" },
|
||||
hair_33: { name: "hair_33", img: "resources/customisation/character_hairs/character_hairs32.png" },
|
||||
hair_34: { name: "hair_34", img: "resources/customisation/character_hairs/character_hairs33.png" },
|
||||
hair_35: { name: "hair_35", img: "resources/customisation/character_hairs/character_hairs34.png" },
|
||||
hair_36: { name: "hair_36", img: "resources/customisation/character_hairs/character_hairs35.png" },
|
||||
hair_37: { name: "hair_37", img: "resources/customisation/character_hairs/character_hairs36.png" },
|
||||
hair_38: { name: "hair_38", img: "resources/customisation/character_hairs/character_hairs37.png" },
|
||||
hair_39: { name: "hair_39", img: "resources/customisation/character_hairs/character_hairs38.png" },
|
||||
hair_40: { name: "hair_40", img: "resources/customisation/character_hairs/character_hairs39.png" },
|
||||
hair_41: { name: "hair_41", img: "resources/customisation/character_hairs/character_hairs40.png" },
|
||||
hair_42: { name: "hair_42", img: "resources/customisation/character_hairs/character_hairs41.png" },
|
||||
hair_43: { name: "hair_43", img: "resources/customisation/character_hairs/character_hairs42.png" },
|
||||
hair_44: { name: "hair_44", img: "resources/customisation/character_hairs/character_hairs43.png" },
|
||||
hair_45: { name: "hair_45", img: "resources/customisation/character_hairs/character_hairs44.png" },
|
||||
hair_46: { name: "hair_46", img: "resources/customisation/character_hairs/character_hairs45.png" },
|
||||
hair_47: { name: "hair_47", img: "resources/customisation/character_hairs/character_hairs46.png" },
|
||||
hair_48: { name: "hair_48", img: "resources/customisation/character_hairs/character_hairs47.png" },
|
||||
hair_49: { name: "hair_49", img: "resources/customisation/character_hairs/character_hairs48.png" },
|
||||
hair_50: { name: "hair_50", img: "resources/customisation/character_hairs/character_hairs49.png" },
|
||||
hair_51: { name: "hair_51", img: "resources/customisation/character_hairs/character_hairs50.png" },
|
||||
hair_52: { name: "hair_52", img: "resources/customisation/character_hairs/character_hairs51.png" },
|
||||
hair_53: { name: "hair_53", img: "resources/customisation/character_hairs/character_hairs52.png" },
|
||||
hair_54: { name: "hair_54", img: "resources/customisation/character_hairs/character_hairs53.png" },
|
||||
hair_55: { name: "hair_55", img: "resources/customisation/character_hairs/character_hairs54.png" },
|
||||
hair_56: { name: "hair_56", img: "resources/customisation/character_hairs/character_hairs55.png" },
|
||||
hair_57: { name: "hair_57", img: "resources/customisation/character_hairs/character_hairs56.png" },
|
||||
hair_58: { name: "hair_58", img: "resources/customisation/character_hairs/character_hairs57.png" },
|
||||
hair_59: { name: "hair_59", img: "resources/customisation/character_hairs/character_hairs58.png" },
|
||||
hair_60: { name: "hair_60", img: "resources/customisation/character_hairs/character_hairs59.png" },
|
||||
hair_61: { name: "hair_61", img: "resources/customisation/character_hairs/character_hairs60.png" },
|
||||
hair_62: { name: "hair_62", img: "resources/customisation/character_hairs/character_hairs61.png" },
|
||||
hair_63: { name: "hair_63", img: "resources/customisation/character_hairs/character_hairs62.png" },
|
||||
hair_64: { name: "hair_64", img: "resources/customisation/character_hairs/character_hairs63.png" },
|
||||
hair_65: { name: "hair_65", img: "resources/customisation/character_hairs/character_hairs64.png" },
|
||||
hair_66: { name: "hair_66", img: "resources/customisation/character_hairs/character_hairs65.png" },
|
||||
hair_67: { name: "hair_67", img: "resources/customisation/character_hairs/character_hairs66.png" },
|
||||
hair_68: { name: "hair_68", img: "resources/customisation/character_hairs/character_hairs67.png" },
|
||||
hair_69: { name: "hair_69", img: "resources/customisation/character_hairs/character_hairs68.png" },
|
||||
hair_70: { name: "hair_70", img: "resources/customisation/character_hairs/character_hairs69.png" },
|
||||
hair_71: { name: "hair_71", img: "resources/customisation/character_hairs/character_hairs70.png" },
|
||||
hair_72: { name: "hair_72", img: "resources/customisation/character_hairs/character_hairs71.png" },
|
||||
hair_73: { name: "hair_73", img: "resources/customisation/character_hairs/character_hairs72.png" },
|
||||
hair_74: { name: "hair_74", img: "resources/customisation/character_hairs/character_hairs73.png" },
|
||||
};
|
||||
interface PlayerTexturesCategory {
|
||||
collections: PlayerTexturesCollection[];
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export const CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
clothes_1: { name: "clothes_1", img: "resources/customisation/character_clothes/character_clothes0.png" },
|
||||
clothes_2: { name: "clothes_2", img: "resources/customisation/character_clothes/character_clothes1.png" },
|
||||
clothes_3: { name: "clothes_3", img: "resources/customisation/character_clothes/character_clothes2.png" },
|
||||
clothes_4: { name: "clothes_4", img: "resources/customisation/character_clothes/character_clothes3.png" },
|
||||
clothes_5: { name: "clothes_5", img: "resources/customisation/character_clothes/character_clothes4.png" },
|
||||
clothes_6: { name: "clothes_6", img: "resources/customisation/character_clothes/character_clothes5.png" },
|
||||
clothes_7: { name: "clothes_7", img: "resources/customisation/character_clothes/character_clothes6.png" },
|
||||
clothes_8: { name: "clothes_8", img: "resources/customisation/character_clothes/character_clothes7.png" },
|
||||
clothes_9: { name: "clothes_9", img: "resources/customisation/character_clothes/character_clothes8.png" },
|
||||
clothes_10: { name: "clothes_10", img: "resources/customisation/character_clothes/character_clothes9.png" },
|
||||
clothes_11: { name: "clothes_11", img: "resources/customisation/character_clothes/character_clothes10.png" },
|
||||
clothes_12: { name: "clothes_12", img: "resources/customisation/character_clothes/character_clothes11.png" },
|
||||
clothes_13: { name: "clothes_13", img: "resources/customisation/character_clothes/character_clothes12.png" },
|
||||
clothes_14: { name: "clothes_14", img: "resources/customisation/character_clothes/character_clothes13.png" },
|
||||
clothes_15: { name: "clothes_15", img: "resources/customisation/character_clothes/character_clothes14.png" },
|
||||
clothes_16: { name: "clothes_16", img: "resources/customisation/character_clothes/character_clothes15.png" },
|
||||
clothes_17: { name: "clothes_17", img: "resources/customisation/character_clothes/character_clothes16.png" },
|
||||
clothes_18: { name: "clothes_18", img: "resources/customisation/character_clothes/character_clothes17.png" },
|
||||
clothes_19: { name: "clothes_19", img: "resources/customisation/character_clothes/character_clothes18.png" },
|
||||
clothes_20: { name: "clothes_20", img: "resources/customisation/character_clothes/character_clothes19.png" },
|
||||
clothes_21: { name: "clothes_21", img: "resources/customisation/character_clothes/character_clothes20.png" },
|
||||
clothes_22: { name: "clothes_22", img: "resources/customisation/character_clothes/character_clothes21.png" },
|
||||
clothes_23: { name: "clothes_23", img: "resources/customisation/character_clothes/character_clothes22.png" },
|
||||
clothes_24: { name: "clothes_24", img: "resources/customisation/character_clothes/character_clothes23.png" },
|
||||
clothes_25: { name: "clothes_25", img: "resources/customisation/character_clothes/character_clothes24.png" },
|
||||
clothes_26: { name: "clothes_26", img: "resources/customisation/character_clothes/character_clothes25.png" },
|
||||
clothes_27: { name: "clothes_27", img: "resources/customisation/character_clothes/character_clothes26.png" },
|
||||
clothes_28: { name: "clothes_28", img: "resources/customisation/character_clothes/character_clothes27.png" },
|
||||
clothes_29: { name: "clothes_29", img: "resources/customisation/character_clothes/character_clothes28.png" },
|
||||
clothes_30: { name: "clothes_30", img: "resources/customisation/character_clothes/character_clothes29.png" },
|
||||
clothes_31: { name: "clothes_31", img: "resources/customisation/character_clothes/character_clothes30.png" },
|
||||
clothes_32: { name: "clothes_32", img: "resources/customisation/character_clothes/character_clothes31.png" },
|
||||
clothes_33: { name: "clothes_33", img: "resources/customisation/character_clothes/character_clothes32.png" },
|
||||
clothes_34: { name: "clothes_34", img: "resources/customisation/character_clothes/character_clothes33.png" },
|
||||
clothes_35: { name: "clothes_35", img: "resources/customisation/character_clothes/character_clothes34.png" },
|
||||
clothes_36: { name: "clothes_36", img: "resources/customisation/character_clothes/character_clothes35.png" },
|
||||
clothes_37: { name: "clothes_37", img: "resources/customisation/character_clothes/character_clothes36.png" },
|
||||
clothes_38: { name: "clothes_38", img: "resources/customisation/character_clothes/character_clothes37.png" },
|
||||
clothes_39: { name: "clothes_39", img: "resources/customisation/character_clothes/character_clothes38.png" },
|
||||
clothes_40: { name: "clothes_40", img: "resources/customisation/character_clothes/character_clothes39.png" },
|
||||
clothes_41: { name: "clothes_41", img: "resources/customisation/character_clothes/character_clothes40.png" },
|
||||
clothes_42: { name: "clothes_42", img: "resources/customisation/character_clothes/character_clothes41.png" },
|
||||
clothes_43: { name: "clothes_43", img: "resources/customisation/character_clothes/character_clothes42.png" },
|
||||
clothes_44: { name: "clothes_44", img: "resources/customisation/character_clothes/character_clothes43.png" },
|
||||
clothes_45: { name: "clothes_45", img: "resources/customisation/character_clothes/character_clothes44.png" },
|
||||
clothes_46: { name: "clothes_46", img: "resources/customisation/character_clothes/character_clothes45.png" },
|
||||
clothes_47: { name: "clothes_47", img: "resources/customisation/character_clothes/character_clothes46.png" },
|
||||
clothes_48: { name: "clothes_48", img: "resources/customisation/character_clothes/character_clothes47.png" },
|
||||
clothes_49: { name: "clothes_49", img: "resources/customisation/character_clothes/character_clothes48.png" },
|
||||
clothes_50: { name: "clothes_50", img: "resources/customisation/character_clothes/character_clothes49.png" },
|
||||
clothes_51: { name: "clothes_51", img: "resources/customisation/character_clothes/character_clothes50.png" },
|
||||
clothes_52: { name: "clothes_52", img: "resources/customisation/character_clothes/character_clothes51.png" },
|
||||
clothes_53: { name: "clothes_53", img: "resources/customisation/character_clothes/character_clothes52.png" },
|
||||
clothes_54: { name: "clothes_54", img: "resources/customisation/character_clothes/character_clothes53.png" },
|
||||
clothes_55: { name: "clothes_55", img: "resources/customisation/character_clothes/character_clothes54.png" },
|
||||
clothes_56: { name: "clothes_56", img: "resources/customisation/character_clothes/character_clothes55.png" },
|
||||
clothes_57: { name: "clothes_57", img: "resources/customisation/character_clothes/character_clothes56.png" },
|
||||
clothes_58: { name: "clothes_58", img: "resources/customisation/character_clothes/character_clothes57.png" },
|
||||
clothes_59: { name: "clothes_59", img: "resources/customisation/character_clothes/character_clothes58.png" },
|
||||
clothes_60: { name: "clothes_60", img: "resources/customisation/character_clothes/character_clothes59.png" },
|
||||
clothes_61: { name: "clothes_61", img: "resources/customisation/character_clothes/character_clothes60.png" },
|
||||
clothes_62: { name: "clothes_62", img: "resources/customisation/character_clothes/character_clothes61.png" },
|
||||
clothes_63: { name: "clothes_63", img: "resources/customisation/character_clothes/character_clothes62.png" },
|
||||
clothes_64: { name: "clothes_64", img: "resources/customisation/character_clothes/character_clothes63.png" },
|
||||
clothes_65: { name: "clothes_65", img: "resources/customisation/character_clothes/character_clothes64.png" },
|
||||
clothes_66: { name: "clothes_66", img: "resources/customisation/character_clothes/character_clothes65.png" },
|
||||
clothes_67: { name: "clothes_67", img: "resources/customisation/character_clothes/character_clothes66.png" },
|
||||
clothes_68: { name: "clothes_68", img: "resources/customisation/character_clothes/character_clothes67.png" },
|
||||
clothes_69: { name: "clothes_69", img: "resources/customisation/character_clothes/character_clothes68.png" },
|
||||
clothes_70: { name: "clothes_70", img: "resources/customisation/character_clothes/character_clothes69.png" },
|
||||
clothes_pride_shirt: {
|
||||
name: "clothes_pride_shirt",
|
||||
img: "resources/customisation/character_clothes/pride_shirt.png",
|
||||
},
|
||||
clothes_black_hoodie: {
|
||||
name: "clothes_black_hoodie",
|
||||
img: "resources/customisation/character_clothes/black_hoodie.png",
|
||||
},
|
||||
clothes_white_hoodie: {
|
||||
name: "clothes_white_hoodie",
|
||||
img: "resources/customisation/character_clothes/white_hoodie.png",
|
||||
},
|
||||
clothes_engelbert: { name: "clothes_engelbert", img: "resources/customisation/character_clothes/engelbert.png" },
|
||||
};
|
||||
interface PlayerTexturesCollection {
|
||||
name: string;
|
||||
textures: PlayerTexturesRecord[];
|
||||
}
|
||||
|
||||
export const HATS_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
hats_1: { name: "hats_1", img: "resources/customisation/character_hats/character_hats1.png" },
|
||||
hats_2: { name: "hats_2", img: "resources/customisation/character_hats/character_hats2.png" },
|
||||
hats_3: { name: "hats_3", img: "resources/customisation/character_hats/character_hats3.png" },
|
||||
hats_4: { name: "hats_4", img: "resources/customisation/character_hats/character_hats4.png" },
|
||||
hats_5: { name: "hats_5", img: "resources/customisation/character_hats/character_hats5.png" },
|
||||
hats_6: { name: "hats_6", img: "resources/customisation/character_hats/character_hats6.png" },
|
||||
hats_7: { name: "hats_7", img: "resources/customisation/character_hats/character_hats7.png" },
|
||||
hats_8: { name: "hats_8", img: "resources/customisation/character_hats/character_hats8.png" },
|
||||
hats_9: { name: "hats_9", img: "resources/customisation/character_hats/character_hats9.png" },
|
||||
hats_10: { name: "hats_10", img: "resources/customisation/character_hats/character_hats10.png" },
|
||||
hats_11: { name: "hats_11", img: "resources/customisation/character_hats/character_hats11.png" },
|
||||
hats_12: { name: "hats_12", img: "resources/customisation/character_hats/character_hats12.png" },
|
||||
hats_13: { name: "hats_13", img: "resources/customisation/character_hats/character_hats13.png" },
|
||||
hats_14: { name: "hats_14", img: "resources/customisation/character_hats/character_hats14.png" },
|
||||
hats_15: { name: "hats_15", img: "resources/customisation/character_hats/character_hats15.png" },
|
||||
hats_16: { name: "hats_16", img: "resources/customisation/character_hats/character_hats16.png" },
|
||||
hats_17: { name: "hats_17", img: "resources/customisation/character_hats/character_hats17.png" },
|
||||
hats_18: { name: "hats_18", img: "resources/customisation/character_hats/character_hats18.png" },
|
||||
hats_19: { name: "hats_19", img: "resources/customisation/character_hats/character_hats19.png" },
|
||||
hats_20: { name: "hats_20", img: "resources/customisation/character_hats/character_hats20.png" },
|
||||
hats_21: { name: "hats_21", img: "resources/customisation/character_hats/character_hats21.png" },
|
||||
hats_22: { name: "hats_22", img: "resources/customisation/character_hats/character_hats22.png" },
|
||||
hats_23: { name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png" },
|
||||
hats_24: { name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png" },
|
||||
hats_25: { name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png" },
|
||||
hats_26: { name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png" },
|
||||
tinfoil_hat1: { name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png" },
|
||||
};
|
||||
interface PlayerTexturesRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
accessory_1: {
|
||||
name: "accessory_1",
|
||||
img: "resources/customisation/character_accessories/character_accessories1.png",
|
||||
},
|
||||
accessory_2: {
|
||||
name: "accessory_2",
|
||||
img: "resources/customisation/character_accessories/character_accessories2.png",
|
||||
},
|
||||
accessory_3: {
|
||||
name: "accessory_3",
|
||||
img: "resources/customisation/character_accessories/character_accessories3.png",
|
||||
},
|
||||
accessory_4: {
|
||||
name: "accessory_4",
|
||||
img: "resources/customisation/character_accessories/character_accessories4.png",
|
||||
},
|
||||
accessory_5: {
|
||||
name: "accessory_5",
|
||||
img: "resources/customisation/character_accessories/character_accessories5.png",
|
||||
},
|
||||
accessory_6: {
|
||||
name: "accessory_6",
|
||||
img: "resources/customisation/character_accessories/character_accessories6.png",
|
||||
},
|
||||
accessory_7: {
|
||||
name: "accessory_7",
|
||||
img: "resources/customisation/character_accessories/character_accessories7.png",
|
||||
},
|
||||
accessory_8: {
|
||||
name: "accessory_8",
|
||||
img: "resources/customisation/character_accessories/character_accessories8.png",
|
||||
},
|
||||
accessory_9: {
|
||||
name: "accessory_9",
|
||||
img: "resources/customisation/character_accessories/character_accessories9.png",
|
||||
},
|
||||
accessory_10: {
|
||||
name: "accessory_10",
|
||||
img: "resources/customisation/character_accessories/character_accessories10.png",
|
||||
},
|
||||
accessory_11: {
|
||||
name: "accessory_11",
|
||||
img: "resources/customisation/character_accessories/character_accessories11.png",
|
||||
},
|
||||
accessory_12: {
|
||||
name: "accessory_12",
|
||||
img: "resources/customisation/character_accessories/character_accessories12.png",
|
||||
},
|
||||
accessory_13: {
|
||||
name: "accessory_13",
|
||||
img: "resources/customisation/character_accessories/character_accessories13.png",
|
||||
},
|
||||
accessory_14: {
|
||||
name: "accessory_14",
|
||||
img: "resources/customisation/character_accessories/character_accessories14.png",
|
||||
},
|
||||
accessory_15: {
|
||||
name: "accessory_15",
|
||||
img: "resources/customisation/character_accessories/character_accessories15.png",
|
||||
},
|
||||
accessory_16: {
|
||||
name: "accessory_16",
|
||||
img: "resources/customisation/character_accessories/character_accessories16.png",
|
||||
},
|
||||
accessory_17: {
|
||||
name: "accessory_17",
|
||||
img: "resources/customisation/character_accessories/character_accessories17.png",
|
||||
},
|
||||
accessory_18: {
|
||||
name: "accessory_18",
|
||||
img: "resources/customisation/character_accessories/character_accessories18.png",
|
||||
},
|
||||
accessory_19: {
|
||||
name: "accessory_19",
|
||||
img: "resources/customisation/character_accessories/character_accessories19.png",
|
||||
},
|
||||
accessory_20: {
|
||||
name: "accessory_20",
|
||||
img: "resources/customisation/character_accessories/character_accessories20.png",
|
||||
},
|
||||
accessory_21: {
|
||||
name: "accessory_21",
|
||||
img: "resources/customisation/character_accessories/character_accessories21.png",
|
||||
},
|
||||
accessory_22: {
|
||||
name: "accessory_22",
|
||||
img: "resources/customisation/character_accessories/character_accessories22.png",
|
||||
},
|
||||
accessory_23: {
|
||||
name: "accessory_23",
|
||||
img: "resources/customisation/character_accessories/character_accessories23.png",
|
||||
},
|
||||
accessory_24: {
|
||||
name: "accessory_24",
|
||||
img: "resources/customisation/character_accessories/character_accessories24.png",
|
||||
},
|
||||
accessory_25: {
|
||||
name: "accessory_25",
|
||||
img: "resources/customisation/character_accessories/character_accessories25.png",
|
||||
},
|
||||
accessory_26: {
|
||||
name: "accessory_26",
|
||||
img: "resources/customisation/character_accessories/character_accessories26.png",
|
||||
},
|
||||
accessory_27: {
|
||||
name: "accessory_27",
|
||||
img: "resources/customisation/character_accessories/character_accessories27.png",
|
||||
},
|
||||
accessory_28: {
|
||||
name: "accessory_28",
|
||||
img: "resources/customisation/character_accessories/character_accessories28.png",
|
||||
},
|
||||
accessory_29: {
|
||||
name: "accessory_29",
|
||||
img: "resources/customisation/character_accessories/character_accessories29.png",
|
||||
},
|
||||
accessory_30: {
|
||||
name: "accessory_30",
|
||||
img: "resources/customisation/character_accessories/character_accessories30.png",
|
||||
},
|
||||
accessory_31: {
|
||||
name: "accessory_31",
|
||||
img: "resources/customisation/character_accessories/character_accessories31.png",
|
||||
},
|
||||
accessory_32: {
|
||||
name: "accessory_32",
|
||||
img: "resources/customisation/character_accessories/character_accessories32.png",
|
||||
},
|
||||
accessory_mate_bottle: {
|
||||
name: "accessory_mate_bottle",
|
||||
img: "resources/customisation/character_accessories/mate_bottle1.png",
|
||||
},
|
||||
accessory_mask: { name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png" },
|
||||
};
|
||||
export class PlayerTextures {
|
||||
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private EYES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private HAIR_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private CLOTHES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private HATS_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
|
||||
private LAYERS: BodyResourceDescriptionListInterface[] = [];
|
||||
|
||||
export const LAYERS: BodyResourceDescriptionListInterface[] = [
|
||||
COLOR_RESOURCES,
|
||||
EYES_RESOURCES,
|
||||
HAIR_RESOURCES,
|
||||
CLOTHES_RESOURCES,
|
||||
HATS_RESOURCES,
|
||||
ACCESSORIES_RESOURCES,
|
||||
];
|
||||
public loadPlayerTexturesMetadata(metadata: PlayerTexturesMetadata): void {
|
||||
this.mapTexturesMetadataIntoResources(metadata);
|
||||
}
|
||||
|
||||
public getTexturesResources(key: PlayerTexturesKey): BodyResourceDescriptionListInterface {
|
||||
switch (key) {
|
||||
case PlayerTexturesKey.Accessory:
|
||||
return this.ACCESSORIES_RESOURCES;
|
||||
case PlayerTexturesKey.Body:
|
||||
return this.COLOR_RESOURCES;
|
||||
case PlayerTexturesKey.Clothes:
|
||||
return this.CLOTHES_RESOURCES;
|
||||
case PlayerTexturesKey.Eyes:
|
||||
return this.EYES_RESOURCES;
|
||||
case PlayerTexturesKey.Hair:
|
||||
return this.HAIR_RESOURCES;
|
||||
case PlayerTexturesKey.Hat:
|
||||
return this.HATS_RESOURCES;
|
||||
case PlayerTexturesKey.Woka:
|
||||
return this.PLAYER_RESOURCES;
|
||||
}
|
||||
}
|
||||
|
||||
public getLayers(): BodyResourceDescriptionListInterface[] {
|
||||
return this.LAYERS;
|
||||
}
|
||||
|
||||
private mapTexturesMetadataIntoResources(metadata: PlayerTexturesMetadata): void {
|
||||
this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka);
|
||||
this.COLOR_RESOURCES = this.getMappedResources(metadata.body);
|
||||
this.EYES_RESOURCES = this.getMappedResources(metadata.eyes);
|
||||
this.HAIR_RESOURCES = this.getMappedResources(metadata.hair);
|
||||
this.CLOTHES_RESOURCES = this.getMappedResources(metadata.clothes);
|
||||
this.HATS_RESOURCES = this.getMappedResources(metadata.hat);
|
||||
this.ACCESSORIES_RESOURCES = this.getMappedResources(metadata.accessory);
|
||||
|
||||
this.LAYERS = [
|
||||
this.COLOR_RESOURCES,
|
||||
this.EYES_RESOURCES,
|
||||
this.HAIR_RESOURCES,
|
||||
this.CLOTHES_RESOURCES,
|
||||
this.HATS_RESOURCES,
|
||||
this.ACCESSORIES_RESOURCES,
|
||||
];
|
||||
}
|
||||
|
||||
private getMappedResources(category: PlayerTexturesCategory): BodyResourceDescriptionListInterface {
|
||||
const resources: BodyResourceDescriptionListInterface = {};
|
||||
if (!category) {
|
||||
return {};
|
||||
}
|
||||
for (const collection of category.collections) {
|
||||
for (const texture of collection.textures) {
|
||||
resources[texture.id] = { id: texture.id, img: texture.url };
|
||||
}
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
|
||||
export const OBJECTS: BodyResourceDescriptionInterface[] = [
|
||||
{ name: "teleportation", img: "resources/objects/teleportation.png" },
|
||||
{ id: "teleportation", img: "resources/objects/teleportation.png" },
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import { BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES } from "./PlayerTextures";
|
||||
import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
|
||||
export interface FrameConfig {
|
||||
@@ -8,33 +8,37 @@ export interface FrameConfig {
|
||||
frameHeight: number;
|
||||
}
|
||||
|
||||
export const loadAllLayers = (load: LoaderPlugin): BodyResourceDescriptionInterface[][] => {
|
||||
export const loadAllLayers = (
|
||||
load: LoaderPlugin,
|
||||
playerTextures: PlayerTextures
|
||||
): BodyResourceDescriptionInterface[][] => {
|
||||
const returnArray: BodyResourceDescriptionInterface[][] = [];
|
||||
LAYERS.forEach((layer) => {
|
||||
playerTextures.getLayers().forEach((layer) => {
|
||||
const layerArray: BodyResourceDescriptionInterface[] = [];
|
||||
Object.values(layer).forEach((textureDescriptor) => {
|
||||
layerArray.push(textureDescriptor);
|
||||
load.spritesheet(textureDescriptor.name, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
|
||||
load.spritesheet(textureDescriptor.id, textureDescriptor.img, { frameWidth: 32, frameHeight: 32 });
|
||||
});
|
||||
returnArray.push(layerArray);
|
||||
});
|
||||
return returnArray;
|
||||
};
|
||||
export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptionInterface[] => {
|
||||
const returnArray = Object.values(PLAYER_RESOURCES);
|
||||
export const loadAllDefaultModels = (
|
||||
load: LoaderPlugin,
|
||||
playerTextures: PlayerTextures
|
||||
): BodyResourceDescriptionInterface[] => {
|
||||
const returnArray = Object.values(playerTextures.getTexturesResources(PlayerTexturesKey.Woka));
|
||||
returnArray.forEach((playerResource: BodyResourceDescriptionInterface) => {
|
||||
load.spritesheet(playerResource.name, playerResource.img, { frameWidth: 32, frameHeight: 32 });
|
||||
load.spritesheet(playerResource.id, playerResource.img, { frameWidth: 32, frameHeight: 32 });
|
||||
});
|
||||
return returnArray;
|
||||
};
|
||||
|
||||
export const loadCustomTexture = (
|
||||
export const loadWokaTexture = (
|
||||
loaderPlugin: LoaderPlugin,
|
||||
texture: CharacterTexture
|
||||
texture: BodyResourceDescriptionInterface
|
||||
): CancelablePromise<BodyResourceDescriptionInterface> => {
|
||||
const name = "customCharacterTexture" + texture.id;
|
||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level };
|
||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor, {
|
||||
return createLoadingPromise(loaderPlugin, texture, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
});
|
||||
@@ -42,16 +46,15 @@ export const loadCustomTexture = (
|
||||
|
||||
export const lazyLoadPlayerCharacterTextures = (
|
||||
loadPlugin: LoaderPlugin,
|
||||
texturekeys: Array<string | BodyResourceDescriptionInterface>
|
||||
textures: BodyResourceDescriptionInterface[]
|
||||
): CancelablePromise<string[]> => {
|
||||
const promisesList: CancelablePromise<unknown>[] = [];
|
||||
texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => {
|
||||
textures.forEach((texture) => {
|
||||
try {
|
||||
//TODO refactor
|
||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
if (!loadPlugin.textureManager.exists(texture.id)) {
|
||||
promisesList.push(
|
||||
createLoadingPromise(loadPlugin, playerResourceDescriptor, {
|
||||
createLoadingPromise(loadPlugin, texture, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32,
|
||||
})
|
||||
@@ -64,58 +67,41 @@ export const lazyLoadPlayerCharacterTextures = (
|
||||
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
|
||||
if (promisesList.length > 0) {
|
||||
loadPlugin.start();
|
||||
returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys);
|
||||
returnPromise = CancelablePromise.all(promisesList).then(() => textures);
|
||||
} else {
|
||||
returnPromise = CancelablePromise.resolve(texturekeys);
|
||||
returnPromise = CancelablePromise.resolve(textures);
|
||||
}
|
||||
|
||||
//If the loading fail, we render the default model instead.
|
||||
return returnPromise.then((keys) =>
|
||||
keys.map((key) => {
|
||||
return typeof key !== "string" ? key.name : key;
|
||||
return typeof key !== "string" ? key.id : key;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const getRessourceDescriptor = (
|
||||
textureKey: string | BodyResourceDescriptionInterface
|
||||
): BodyResourceDescriptionInterface => {
|
||||
if (typeof textureKey !== "string" && textureKey.img) {
|
||||
return textureKey;
|
||||
}
|
||||
const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name;
|
||||
const playerResource = PLAYER_RESOURCES[textureName];
|
||||
if (playerResource !== undefined) return playerResource;
|
||||
|
||||
for (let i = 0; i < LAYERS.length; i++) {
|
||||
const playerResource = LAYERS[i][textureName];
|
||||
if (playerResource !== undefined) return playerResource;
|
||||
}
|
||||
throw new Error("Could not find a data for texture " + textureName);
|
||||
};
|
||||
|
||||
export const createLoadingPromise = (
|
||||
loadPlugin: LoaderPlugin,
|
||||
playerResourceDescriptor: BodyResourceDescriptionInterface,
|
||||
frameConfig: FrameConfig
|
||||
) => {
|
||||
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.id)) {
|
||||
return res(playerResourceDescriptor);
|
||||
}
|
||||
|
||||
cancel(() => {
|
||||
loadPlugin.off("loaderror");
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name);
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id);
|
||||
return;
|
||||
});
|
||||
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, frameConfig);
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.id, playerResourceDescriptor.img, frameConfig);
|
||||
const errorCallback = (file: { src: string }) => {
|
||||
if (file.src !== playerResourceDescriptor.img) return;
|
||||
console.error("failed loading player resource: ", playerResourceDescriptor);
|
||||
rej(playerResourceDescriptor);
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
|
||||
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
|
||||
loadPlugin.off("loaderror", errorCallback);
|
||||
};
|
||||
const successCallback = () => {
|
||||
@@ -123,7 +109,7 @@ export const createLoadingPromise = (
|
||||
res(playerResourceDescriptor);
|
||||
};
|
||||
|
||||
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.name, successCallback);
|
||||
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
|
||||
loadPlugin.on("loaderror", errorCallback);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { requestVisitCardsStore } from "../../Stores/GameStore";
|
||||
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||
import { ActionsMenuAction, ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
|
||||
import { Character } from "../Entity/Character";
|
||||
import type { GameScene } from "../Game/GameScene";
|
||||
import type { PointInterface } from "../../Connexion/ConnexionModels";
|
||||
@@ -7,21 +7,29 @@ import type { PlayerAnimationDirections } from "../Player/Animation";
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
import type { ActivatableInterface } from "../Game/ActivatableInterface";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { blackListManager } from "../../WebRtc/BlackListManager";
|
||||
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
|
||||
|
||||
export enum RemotePlayerEvent {
|
||||
Clicked = "Clicked",
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
*/
|
||||
export class RemotePlayer extends Character implements ActivatableInterface {
|
||||
public userId: number;
|
||||
public readonly userId: number;
|
||||
public readonly userUuid: string;
|
||||
public readonly activationRadius: number;
|
||||
|
||||
private registeredActions: { actionName: string; callback: Function }[];
|
||||
private visitCardUrl: string | null;
|
||||
private isActionsMenuInitialized: boolean = false;
|
||||
private actionsMenuStoreUnsubscriber: Unsubscriber;
|
||||
|
||||
constructor(
|
||||
userId: number,
|
||||
userUuid: string,
|
||||
Scene: GameScene,
|
||||
x: number,
|
||||
y: number,
|
||||
@@ -38,10 +46,9 @@ export class RemotePlayer extends Character implements ActivatableInterface {
|
||||
|
||||
//set data
|
||||
this.userId = userId;
|
||||
this.userUuid = userUuid;
|
||||
this.visitCardUrl = visitCardUrl;
|
||||
this.registeredActions = [];
|
||||
this.registerDefaultActionsMenuActions();
|
||||
this.setClickable(this.registeredActions.length > 0);
|
||||
this.setClickable(this.getDefaultActionsMenuActions().length > 0);
|
||||
this.activationRadius = activationRadius ?? 96;
|
||||
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
|
||||
this.isActionsMenuInitialized = value ? true : false;
|
||||
@@ -62,17 +69,19 @@ export class RemotePlayer extends Character implements ActivatableInterface {
|
||||
}
|
||||
}
|
||||
|
||||
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void {
|
||||
this.registeredActions.push(action);
|
||||
this.updateIsClickable();
|
||||
public registerActionsMenuAction(action: ActionsMenuAction): void {
|
||||
actionsMenuStore.addAction({
|
||||
...action,
|
||||
priority: action.priority ?? 0,
|
||||
callback: () => {
|
||||
action.callback();
|
||||
actionsMenuStore.clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public unregisterActionsMenuAction(actionName: string) {
|
||||
const index = this.registeredActions.findIndex((action) => action.actionName === actionName);
|
||||
if (index !== -1) {
|
||||
this.registeredActions.splice(index, 1);
|
||||
}
|
||||
this.updateIsClickable();
|
||||
actionsMenuStore.removeAction(actionName);
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
@@ -89,37 +98,52 @@ export class RemotePlayer extends Character implements ActivatableInterface {
|
||||
return this.isClickable();
|
||||
}
|
||||
|
||||
private updateIsClickable(): void {
|
||||
this.setClickable(this.registeredActions.length > 0);
|
||||
}
|
||||
|
||||
private toggleActionsMenu(): void {
|
||||
if (this.isActionsMenuInitialized) {
|
||||
actionsMenuStore.clear();
|
||||
return;
|
||||
}
|
||||
actionsMenuStore.initialize(this.playerName);
|
||||
for (const action of this.registeredActions) {
|
||||
actionsMenuStore.addAction(action.actionName, action.callback);
|
||||
for (const action of this.getDefaultActionsMenuActions()) {
|
||||
actionsMenuStore.addAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
private registerDefaultActionsMenuActions(): void {
|
||||
private getDefaultActionsMenuActions(): ActionsMenuAction[] {
|
||||
const actions: ActionsMenuAction[] = [];
|
||||
if (this.visitCardUrl) {
|
||||
this.registeredActions.push({
|
||||
actionName: "Visiting Card",
|
||||
actions.push({
|
||||
actionName: LL.woka.menu.businessCard(),
|
||||
protected: true,
|
||||
priority: 1,
|
||||
callback: () => {
|
||||
requestVisitCardsStore.set(this.visitCardUrl);
|
||||
actionsMenuStore.clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
actions.push({
|
||||
actionName: blackListManager.isBlackListed(this.userUuid)
|
||||
? LL.report.block.unblock()
|
||||
: LL.report.block.block(),
|
||||
protected: true,
|
||||
priority: -1,
|
||||
style: "is-error",
|
||||
callback: () => {
|
||||
showReportScreenStore.set({ userId: this.userId, userName: this.name });
|
||||
actionsMenuStore.clear();
|
||||
},
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private bindEventHandlers(): void {
|
||||
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
|
||||
if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) {
|
||||
this.toggleActionsMenu();
|
||||
this.emit(RemotePlayerEvent.Clicked);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class ActivatablesManager {
|
||||
|
||||
private canSelectByDistance: boolean = true;
|
||||
|
||||
private readonly outlineColor = 0xffff00;
|
||||
private readonly outlineColor = 0xf9e81e;
|
||||
private readonly directionalActivationPositionShift = 50;
|
||||
|
||||
constructor(currentPlayer: Player) {
|
||||
|
||||
@@ -85,6 +85,7 @@ export class GameMap {
|
||||
phaserMap
|
||||
.createLayer(layer.name, terrains, (layer.x || 0) * 32, (layer.y || 0) * 32)
|
||||
.setDepth(depth)
|
||||
.setScrollFactor(layer.parallaxx ?? 1, layer.parallaxy ?? 1)
|
||||
.setAlpha(layer.opacity)
|
||||
.setVisible(layer.visible)
|
||||
.setSize(layer.width, layer.height)
|
||||
@@ -120,12 +121,12 @@ export class GameMap {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getCollisionsGrid(): number[][] {
|
||||
public getCollisionGrid(): number[][] {
|
||||
const grid: number[][] = [];
|
||||
for (let y = 0; y < this.map.height; y += 1) {
|
||||
const row: number[] = [];
|
||||
for (let x = 0; x < this.map.width; x += 1) {
|
||||
row.push(this.isCollidingAt(x, y) ? 1 : 0);
|
||||
row.push(this.isCollidingAt(x, y) ? 1 : this.isExitTile(x, y) ? 2 : 0);
|
||||
}
|
||||
grid.push(row);
|
||||
}
|
||||
@@ -322,19 +323,51 @@ export class GameMap {
|
||||
throw new Error("No possible position found");
|
||||
}
|
||||
|
||||
public getObjectWithName(name: string): ITiledMapObject | undefined {
|
||||
return this.tiledObjects.find((object) => object.name === name);
|
||||
}
|
||||
|
||||
private getLayersByKey(key: number): Array<ITiledMapLayer> {
|
||||
return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0);
|
||||
}
|
||||
|
||||
private isCollidingAt(x: number, y: number): boolean {
|
||||
for (const layer of this.phaserLayers) {
|
||||
if (layer.getTileAt(x, y)?.properties[GameMapProperties.COLLIDES]) {
|
||||
if (!layer.visible) {
|
||||
continue;
|
||||
}
|
||||
if (layer.getTileAt(x, y)?.properties?.[GameMapProperties.COLLIDES]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private isExitTile(x: number, y: number): boolean {
|
||||
for (const layer of this.phaserLayers) {
|
||||
if (!layer.visible) {
|
||||
continue;
|
||||
}
|
||||
const tile = layer.getTileAt(x, y);
|
||||
if (!tile) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
tile &&
|
||||
(tile.properties[GameMapProperties.EXIT_URL] || tile.properties[GameMapProperties.EXIT_SCENE_URL])
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
for (const property of layer.layer.properties) {
|
||||
//@ts-ignore
|
||||
if (property.name && property.name === "exitUrl") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private triggerAllProperties(): void {
|
||||
const newProps = this.getProperties(this.key ?? 0);
|
||||
const oldProps = this.lastProperties;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { GameScene } from "./GameScene";
|
||||
import type { GameMap } from "./GameMap";
|
||||
import { scriptUtils } from "../../Api/ScriptUtils";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
@@ -9,17 +8,19 @@ import { get } from "svelte/store";
|
||||
import { ON_ACTION_TRIGGER_BUTTON, ON_ICON_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import type { ITiledMapLayer } from "../Map/ITiledMap";
|
||||
import { GameMapProperties } from "./GameMapProperties";
|
||||
|
||||
enum OpenCoWebsiteState {
|
||||
ASLEEP,
|
||||
OPENED,
|
||||
MUST_BE_CLOSE,
|
||||
}
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
|
||||
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||
import { JITSI_PRIVATE_MODE, JITSI_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { Room } from "../../Connexion/Room";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
|
||||
interface OpenCoWebsite {
|
||||
actionId: string;
|
||||
coWebsite?: CoWebsite;
|
||||
state: OpenCoWebsiteState;
|
||||
}
|
||||
|
||||
export class GameMapPropertiesListener {
|
||||
@@ -29,6 +30,7 @@ export class GameMapPropertiesListener {
|
||||
constructor(private scene: GameScene, private gameMap: GameMap) {}
|
||||
|
||||
register() {
|
||||
// Website on new tab
|
||||
this.gameMap.onPropertyChange(GameMapProperties.OPEN_TAB, (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("openTab");
|
||||
@@ -39,7 +41,7 @@ export class GameMapPropertiesListener {
|
||||
if (forceTrigger || openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(GameMapProperties.OPEN_WEBSITE_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to open web site in new tab";
|
||||
message = get(LL).trigger.newTab();
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "openTab",
|
||||
@@ -54,6 +56,129 @@ export class GameMapPropertiesListener {
|
||||
}
|
||||
});
|
||||
|
||||
// Jitsi room
|
||||
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
coWebsiteManager.getCoWebsites().forEach((coWebsite) => {
|
||||
if (coWebsite instanceof JitsiCoWebsite) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.scene.instance);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
|
||||
|
||||
this.scene.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
let domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
|
||||
}
|
||||
|
||||
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
|
||||
domain = `${location.protocol}//${domain}`;
|
||||
}
|
||||
|
||||
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
|
||||
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
|
||||
this.scene.initialiseJitsi(coWebsite, roomName, undefined);
|
||||
}
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
};
|
||||
|
||||
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
|
||||
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
|
||||
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = get(LL).trigger.jitsiRoom();
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "jitsi",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openJitsiRoomFunction(),
|
||||
userInputManager: this.scene.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openJitsiRoomFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.scene
|
||||
.onMapExit(
|
||||
Room.getRoomPathFromExitSceneUrl(
|
||||
newValue as string,
|
||||
window.location.toString(),
|
||||
this.scene.MapUrlFile
|
||||
)
|
||||
)
|
||||
.catch((e) => console.error(e));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.scene
|
||||
.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString()))
|
||||
.catch((e) => console.error(e));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === "") {
|
||||
this.scene.connection?.setSilent(false);
|
||||
this.scene.CurrentPlayer.noSilent();
|
||||
} else {
|
||||
this.scene.connection?.setSilent(true);
|
||||
this.scene.CurrentPlayer.isSilent();
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
|
||||
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), volume, loop);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.scene.getMapDirUrl(), undefined, true);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
|
||||
// TODO: Legacy functionnality replace by layer change
|
||||
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
|
||||
if (oldValue) {
|
||||
iframeListener.sendLeaveEvent(oldValue as string);
|
||||
}
|
||||
if (newValue) {
|
||||
iframeListener.sendEnterEvent(newValue as string);
|
||||
}
|
||||
});
|
||||
|
||||
// Open a new co-website by the property.
|
||||
this.gameMap.onEnterLayer((newLayers) => {
|
||||
const handler = () => {
|
||||
@@ -106,49 +231,33 @@ export class GameMapPropertiesListener {
|
||||
return;
|
||||
}
|
||||
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
const coWebsiteOpen: OpenCoWebsite = {
|
||||
actionId: actionId,
|
||||
coWebsite: undefined,
|
||||
state: OpenCoWebsiteState.ASLEEP,
|
||||
});
|
||||
};
|
||||
|
||||
this.coWebsitesOpenByLayer.set(layer, coWebsiteOpen);
|
||||
|
||||
const loadCoWebsiteFunction = (coWebsite: CoWebsite) => {
|
||||
coWebsiteManager
|
||||
.loadCoWebsite(coWebsite)
|
||||
.then((coWebsite) => {
|
||||
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
|
||||
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during a co-website closing");
|
||||
});
|
||||
this.coWebsitesOpenByLayer.delete(layer);
|
||||
this.coWebsitesActionTriggerByLayer.delete(layer);
|
||||
} else {
|
||||
this.coWebsitesOpenByLayer.set(layer, {
|
||||
actionId,
|
||||
coWebsite,
|
||||
state: OpenCoWebsiteState.OPENED,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("Error during loading a co-website: " + coWebsite.url);
|
||||
});
|
||||
coWebsiteManager.loadCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during loading a co-website: " + coWebsite.getUrl());
|
||||
});
|
||||
|
||||
layoutManagerActionStore.removeAction(actionId);
|
||||
};
|
||||
|
||||
const openCoWebsiteFunction = () => {
|
||||
const coWebsite = coWebsiteManager.addCoWebsite(
|
||||
openWebsiteProperty ?? "",
|
||||
this.scene.MapUrlFile,
|
||||
const coWebsite = new SimpleCoWebsite(
|
||||
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
|
||||
allowApiProperty,
|
||||
websitePolicyProperty,
|
||||
websiteWidthProperty,
|
||||
websitePositionProperty,
|
||||
false
|
||||
);
|
||||
|
||||
coWebsiteOpen.coWebsite = coWebsite;
|
||||
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
|
||||
|
||||
loadCoWebsiteFunction(coWebsite);
|
||||
};
|
||||
|
||||
@@ -157,7 +266,7 @@ export class GameMapPropertiesListener {
|
||||
websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON
|
||||
) {
|
||||
if (!websiteTriggerMessageProperty) {
|
||||
websiteTriggerMessageProperty = "Press SPACE or touch here to open web site";
|
||||
websiteTriggerMessageProperty = get(LL).trigger.cowebsite();
|
||||
}
|
||||
|
||||
this.coWebsitesActionTriggerByLayer.set(layer, actionId);
|
||||
@@ -170,21 +279,17 @@ export class GameMapPropertiesListener {
|
||||
userInputManager: this.scene.userInputManager,
|
||||
});
|
||||
} else if (websiteTriggerProperty === ON_ICON_TRIGGER_BUTTON) {
|
||||
const coWebsite = coWebsiteManager.addCoWebsite(
|
||||
openWebsiteProperty,
|
||||
this.scene.MapUrlFile,
|
||||
const coWebsite = new SimpleCoWebsite(
|
||||
new URL(openWebsiteProperty ?? "", this.scene.MapUrlFile),
|
||||
allowApiProperty,
|
||||
websitePolicyProperty,
|
||||
websiteWidthProperty,
|
||||
websitePositionProperty,
|
||||
false
|
||||
);
|
||||
|
||||
const ObjectByLayer = this.coWebsitesOpenByLayer.get(layer);
|
||||
coWebsiteOpen.coWebsite = coWebsite;
|
||||
|
||||
if (ObjectByLayer) {
|
||||
ObjectByLayer.coWebsite = coWebsite;
|
||||
}
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, websitePositionProperty);
|
||||
}
|
||||
|
||||
if (!websiteTriggerProperty) {
|
||||
@@ -228,12 +333,10 @@ export class GameMapPropertiesListener {
|
||||
return;
|
||||
}
|
||||
|
||||
if (coWebsiteOpen.state === OpenCoWebsiteState.ASLEEP) {
|
||||
coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
|
||||
}
|
||||
const coWebsite = coWebsiteOpen.coWebsite;
|
||||
|
||||
if (coWebsiteOpen.coWebsite !== undefined) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e));
|
||||
if (coWebsite) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
this.coWebsitesOpenByLayer.delete(layer);
|
||||
|
||||
+167
-177
@@ -5,7 +5,7 @@ import { get, Unsubscriber } from "svelte/store";
|
||||
|
||||
import { userMessageManager } from "../../Administration/UserMessageManager";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
||||
import { urlManager } from "../../Url/UrlManager";
|
||||
import { mediaManager } from "../../WebRtc/MediaManager";
|
||||
import { UserInputManager } from "../UserInput/UserInputManager";
|
||||
@@ -18,11 +18,10 @@ import { soundManager } from "./SoundManager";
|
||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||
|
||||
import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { lazyLoadPlayerCharacterTextures, loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import { ON_ACTION_TRIGGER_BUTTON } from "../../WebRtc/LayoutManager";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
||||
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
||||
import { Room } from "../../Connexion/Room";
|
||||
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
||||
@@ -31,7 +30,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
||||
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
||||
import { Loader } from "../Components/Loader";
|
||||
import { RemotePlayer } from "../Entity/RemotePlayer";
|
||||
import { RemotePlayer, RemotePlayerEvent } from "../Entity/RemotePlayer";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
|
||||
import { PlayerAnimationDirections } from "../Player/Animation";
|
||||
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
|
||||
@@ -76,7 +75,7 @@ import { emoteStore, emoteMenuStore } from "../../Stores/EmoteStore";
|
||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
import { contactPageStore } from "../../Stores/MenuStore";
|
||||
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
|
||||
import { audioManagerFileStore, audioManagerVisibilityStore } from "../../Stores/AudioManagerStore";
|
||||
import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
|
||||
|
||||
import EVENT_TYPE = Phaser.Scenes.Events;
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
@@ -92,6 +91,14 @@ import { MapStore } from "../../Stores/Utils/MapStore";
|
||||
import { followUsersColorStore } from "../../Stores/FollowStore";
|
||||
import { GameSceneUserInputHandler } from "../UserInput/GameSceneUserInputHandler";
|
||||
import { locale } from "../../i18n/i18n-svelte";
|
||||
import { localVolumeStore } from "../../Stores/MediaStore";
|
||||
import { StringUtils } from "../../Utils/StringUtils";
|
||||
import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore";
|
||||
import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite";
|
||||
import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
|
||||
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
|
||||
import { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
reconnecting: boolean;
|
||||
@@ -168,6 +175,9 @@ export class GameScene extends DirtyScene {
|
||||
private peerStoreUnsubscribe!: Unsubscriber;
|
||||
private emoteUnsubscribe!: Unsubscriber;
|
||||
private emoteMenuUnsubscribe!: Unsubscriber;
|
||||
|
||||
private volumeStoreUnsubscribers: Map<number, Unsubscriber> = new Map<number, Unsubscriber>();
|
||||
private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
|
||||
private followUsersColorStoreUnsubscribe!: Unsubscriber;
|
||||
|
||||
private biggestAvailableAreaStoreUnsubscribe!: () => void;
|
||||
@@ -236,13 +246,7 @@ export class GameScene extends DirtyScene {
|
||||
//initialize frame event of scripting API
|
||||
this.listenToIframeEvents();
|
||||
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
loadCustomTexture(this.load, texture).catch((e) => console.error(e));
|
||||
}
|
||||
}
|
||||
this.load.image("iconTalk", "/resources/icons/icon_talking.png");
|
||||
|
||||
if (touchScreenManager.supportTouchScreen) {
|
||||
this.load.image(joystickBaseKey, joystickBaseImg);
|
||||
@@ -334,7 +338,6 @@ export class GameScene extends DirtyScene {
|
||||
(this.load as any).rexWebFont({
|
||||
custom: {
|
||||
families: ["Press Start 2P"],
|
||||
urls: ["/resources/fonts/fonts.css"],
|
||||
testString: "abcdefg",
|
||||
},
|
||||
});
|
||||
@@ -541,6 +544,8 @@ export class GameScene extends DirtyScene {
|
||||
urlManager.getStartLayerNameFromUrl()
|
||||
);
|
||||
|
||||
startLayerNamesStore.set(this.startPositionCalculator.getStartPositionNames());
|
||||
|
||||
//add entities
|
||||
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
|
||||
|
||||
@@ -560,7 +565,7 @@ export class GameScene extends DirtyScene {
|
||||
|
||||
this.pathfindingManager = new PathfindingManager(
|
||||
this,
|
||||
this.gameMap.getCollisionsGrid(),
|
||||
this.gameMap.getCollisionGrid(),
|
||||
this.gameMap.getTileDimensions()
|
||||
);
|
||||
|
||||
@@ -568,18 +573,14 @@ export class GameScene extends DirtyScene {
|
||||
this.createCurrentPlayer();
|
||||
this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted
|
||||
|
||||
this.tryMovePlayerWithMoveToParameter();
|
||||
|
||||
this.cameraManager = new CameraManager(
|
||||
this,
|
||||
{ x: this.Map.widthInPixels, y: this.Map.heightInPixels },
|
||||
waScaleManager
|
||||
);
|
||||
|
||||
this.pathfindingManager = new PathfindingManager(
|
||||
this,
|
||||
this.gameMap.getCollisionsGrid(),
|
||||
this.gameMap.getTileDimensions()
|
||||
);
|
||||
|
||||
this.activatablesManager = new ActivatablesManager(this.CurrentPlayer);
|
||||
|
||||
biggestAvailableAreaStore.recompute();
|
||||
@@ -627,21 +628,51 @@ export class GameScene extends DirtyScene {
|
||||
);
|
||||
|
||||
new GameMapPropertiesListener(this, this.gameMap).register();
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
|
||||
if (!this.room.isDisconnected()) {
|
||||
this.scene.sleep();
|
||||
this.connect();
|
||||
}
|
||||
|
||||
const talkIconVolumeTreshold = 10;
|
||||
let oldPeerNumber = 0;
|
||||
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
|
||||
this.volumeStoreUnsubscribers.forEach((unsubscribe) => unsubscribe());
|
||||
this.volumeStoreUnsubscribers.clear();
|
||||
|
||||
for (const [key, videoStream] of peers) {
|
||||
this.volumeStoreUnsubscribers.set(
|
||||
key,
|
||||
videoStream.volumeStore.subscribe((volume) => {
|
||||
if (volume) {
|
||||
this.MapPlayersByKey.get(key)?.showTalkIcon(volume > talkIconVolumeTreshold);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const newPeerNumber = peers.size;
|
||||
if (newPeerNumber > oldPeerNumber) {
|
||||
this.playSound("audio-webrtc-in");
|
||||
} else if (newPeerNumber < oldPeerNumber) {
|
||||
this.playSound("audio-webrtc-out");
|
||||
}
|
||||
if (newPeerNumber > 0) {
|
||||
if (!this.localVolumeStoreUnsubscriber) {
|
||||
this.localVolumeStoreUnsubscriber = localVolumeStore.subscribe((volume) => {
|
||||
if (volume) {
|
||||
this.CurrentPlayer.showTalkIcon(volume > talkIconVolumeTreshold);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.CurrentPlayer.showTalkIcon(false, true);
|
||||
this.MapPlayersByKey.forEach((remotePlayer) => remotePlayer.showTalkIcon(false, true));
|
||||
if (this.localVolumeStoreUnsubscriber) {
|
||||
this.localVolumeStoreUnsubscriber();
|
||||
this.localVolumeStoreUnsubscriber = undefined;
|
||||
}
|
||||
}
|
||||
oldPeerNumber = newPeerNumber;
|
||||
});
|
||||
|
||||
@@ -708,6 +739,14 @@ export class GameScene extends DirtyScene {
|
||||
.then((onConnect: OnConnectInterface) => {
|
||||
this.connection = onConnect.connection;
|
||||
|
||||
lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers)
|
||||
.then((layers) => {
|
||||
this.currentPlayerTexturesResolve(layers);
|
||||
})
|
||||
.catch((e) => {
|
||||
this.currentPlayerTexturesReject(e);
|
||||
});
|
||||
|
||||
playersStore.connectToRoomConnection(this.connection);
|
||||
userIsAdminStore.set(this.connection.hasTag("admin"));
|
||||
|
||||
@@ -794,7 +833,19 @@ export class GameScene extends DirtyScene {
|
||||
* Triggered when we receive the JWT token to connect to Jitsi
|
||||
*/
|
||||
this.connection.sendJitsiJwtMessageStream.subscribe((message) => {
|
||||
this.startJitsi(message.jitsiRoom, message.jwt);
|
||||
if (!JITSI_URL) {
|
||||
throw new Error("Missing JITSI_URL environment variable.");
|
||||
}
|
||||
|
||||
let domain = JITSI_URL;
|
||||
|
||||
if (domain.substring(0, 7) !== "http://" && domain.substring(0, 8) !== "https://") {
|
||||
domain = `${location.protocol}//${domain}`;
|
||||
}
|
||||
|
||||
const coWebsite = new JitsiCoWebsite(new URL(domain), false, undefined, undefined, false);
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, 0);
|
||||
this.initialiseJitsi(coWebsite, message.jitsiRoom, message.jwt);
|
||||
});
|
||||
|
||||
this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => {
|
||||
@@ -931,103 +982,6 @@ export class GameScene extends DirtyScene {
|
||||
}
|
||||
}
|
||||
|
||||
private triggerOnMapLayerPropertyChange() {
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_SCENE_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.onMapExit(
|
||||
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
|
||||
).catch((e) => console.error(e));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) =>
|
||||
console.error(e)
|
||||
);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
layoutManagerActionStore.removeAction("roomAccessDenied");
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
this.gameMap.onPropertyChange(GameMapProperties.JITSI_ROOM, (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
this.stopJitsi();
|
||||
} else {
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get(GameMapProperties.JITSI_ADMIN_ROOM_TAG) as string | undefined;
|
||||
|
||||
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
};
|
||||
|
||||
const jitsiTriggerValue = allProps.get(GameMapProperties.JITSI_TRIGGER);
|
||||
const forceTrigger = localUserStore.getForceCowebsiteTrigger();
|
||||
if (forceTrigger || jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
let message = allProps.get(GameMapProperties.JITSI_TRIGGER_MESSAGE);
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to enter Jitsi Meet room";
|
||||
}
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "jitsi",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openJitsiRoomFunction(),
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openJitsiRoomFunction();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange(GameMapProperties.SILENT, (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === "") {
|
||||
this.connection?.setSilent(false);
|
||||
this.CurrentPlayer.noSilent();
|
||||
} else {
|
||||
this.connection?.setSilent(true);
|
||||
this.CurrentPlayer.isSilent();
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO, (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(GameMapProperties.AUDIO_VOLUME) as number | undefined;
|
||||
const loop = allProps.get(GameMapProperties.AUDIO_LOOP) as boolean | undefined;
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), volume, loop);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange(GameMapProperties.PLAY_AUDIO_LOOP, (newValue, oldValue) => {
|
||||
newValue === undefined
|
||||
? audioManagerFileStore.unloadAudio()
|
||||
: audioManagerFileStore.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||
audioManagerVisibilityStore.set(!(newValue === undefined));
|
||||
});
|
||||
|
||||
// TODO: Legacy functionnality replace by layer change
|
||||
this.gameMap.onPropertyChange(GameMapProperties.ZONE, (newValue, oldValue) => {
|
||||
if (oldValue) {
|
||||
iframeListener.sendLeaveEvent(oldValue as string);
|
||||
}
|
||||
if (newValue) {
|
||||
iframeListener.sendEnterEvent(newValue as string);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private listenToIframeEvents(): void {
|
||||
this.iframeSubscriptionList = [];
|
||||
this.iframeSubscriptionList.push(
|
||||
@@ -1154,6 +1108,23 @@ ${escapedMessage}
|
||||
})
|
||||
);
|
||||
|
||||
this.iframeSubscriptionList.push(
|
||||
iframeListener.addActionsMenuKeyToRemotePlayerStream.subscribe((data) => {
|
||||
this.MapPlayersByKey.get(data.id)?.registerActionsMenuAction({
|
||||
actionName: data.actionKey,
|
||||
callback: () => {
|
||||
iframeListener.sendActionsMenuActionClickedEvent({ actionName: data.actionKey, id: data.id });
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
this.iframeSubscriptionList.push(
|
||||
iframeListener.removeActionsMenuKeyFromRemotePlayerEvent.subscribe((data) => {
|
||||
this.MapPlayersByKey.get(data.id)?.unregisterActionsMenuAction(data.actionKey);
|
||||
})
|
||||
);
|
||||
|
||||
this.iframeSubscriptionList.push(
|
||||
iframeListener.trackCameraUpdateStream.subscribe(() => {
|
||||
if (!this.firstCameraUpdateSent) {
|
||||
@@ -1260,22 +1231,22 @@ ${escapedMessage}
|
||||
throw new Error("Unknown query source");
|
||||
}
|
||||
|
||||
const coWebsite = coWebsiteManager.addCoWebsite(
|
||||
openCoWebsite.url,
|
||||
iframeListener.getBaseUrlFromSource(source),
|
||||
const coWebsite: SimpleCoWebsite = new SimpleCoWebsite(
|
||||
new URL(openCoWebsite.url, iframeListener.getBaseUrlFromSource(source)),
|
||||
openCoWebsite.allowApi,
|
||||
openCoWebsite.allowPolicy,
|
||||
openCoWebsite.widthPercent,
|
||||
openCoWebsite.position,
|
||||
openCoWebsite.closable ?? true
|
||||
);
|
||||
|
||||
coWebsiteManager.addCoWebsiteToStore(coWebsite, openCoWebsite.position);
|
||||
|
||||
if (openCoWebsite.lazy === undefined || !openCoWebsite.lazy) {
|
||||
await coWebsiteManager.loadCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
return {
|
||||
id: coWebsite.iframe.id,
|
||||
id: coWebsite.getId(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1284,27 +1255,23 @@ ${escapedMessage}
|
||||
|
||||
return coWebsites.map((coWebsite: CoWebsite) => {
|
||||
return {
|
||||
id: coWebsite.iframe.id,
|
||||
id: coWebsite.getId(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("closeCoWebsite", async (coWebsiteId) => {
|
||||
iframeListener.registerAnswerer("closeCoWebsite", (coWebsiteId) => {
|
||||
const coWebsite = coWebsiteManager.getCoWebsiteById(coWebsiteId);
|
||||
|
||||
if (!coWebsite) {
|
||||
throw new Error("Unknown co-website");
|
||||
}
|
||||
|
||||
return coWebsiteManager.closeCoWebsite(coWebsite).catch((error) => {
|
||||
throw new Error("Error on closing co-website");
|
||||
});
|
||||
return coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("closeCoWebsites", async () => {
|
||||
return await coWebsiteManager.closeCoWebsites().catch((error) => {
|
||||
throw new Error("Error on closing all co-websites");
|
||||
});
|
||||
iframeListener.registerAnswerer("closeCoWebsites", () => {
|
||||
return coWebsiteManager.closeCoWebsites();
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("getMapData", () => {
|
||||
@@ -1389,7 +1356,7 @@ ${escapedMessage}
|
||||
//Create new colliders with the new GameMap
|
||||
this.createCollisionWithPlayer();
|
||||
//Create new trigger with the new GameMap
|
||||
this.triggerOnMapLayerPropertyChange();
|
||||
new GameMapPropertiesListener(this, this.gameMap).register();
|
||||
resolve(newFirstgid);
|
||||
});
|
||||
});
|
||||
@@ -1460,9 +1427,9 @@ ${escapedMessage}
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("movePlayerTo", async (message) => {
|
||||
const index = this.getGameMap().getTileIndexAt(message.x, message.y);
|
||||
const startTile = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
const path = await this.getPathfindingManager().findPath(startTile, index, true, true);
|
||||
const startTileIndex = this.getGameMap().getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
const destinationTileIndex = this.getGameMap().getTileIndexAt(message.x, message.y);
|
||||
const path = await this.getPathfindingManager().findPath(startTileIndex, destinationTileIndex, true, true);
|
||||
path.shift();
|
||||
if (path.length === 0) {
|
||||
throw new Error("no path available");
|
||||
@@ -1489,7 +1456,7 @@ ${escapedMessage}
|
||||
phaserLayer.setCollisionByProperty({ collides: true }, visible);
|
||||
} else {
|
||||
const phaserLayers = this.gameMap.findPhaserLayers(layerName + "/");
|
||||
if (phaserLayers === []) {
|
||||
if (phaserLayers.length === 0) {
|
||||
console.warn(
|
||||
'Could not find layer with name that contains "' +
|
||||
layerName +
|
||||
@@ -1502,14 +1469,15 @@ ${escapedMessage}
|
||||
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
|
||||
}
|
||||
}
|
||||
this.pathfindingManager.setCollisionGrid(this.gameMap.getCollisionGrid());
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
private getMapDirUrl(): string {
|
||||
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
|
||||
public getMapDirUrl(): string {
|
||||
return this.MapUrlFile.substring(0, this.MapUrlFile.lastIndexOf("/"));
|
||||
}
|
||||
|
||||
private async onMapExit(roomUrl: URL) {
|
||||
public async onMapExit(roomUrl: URL) {
|
||||
if (this.mapTransitioning) return;
|
||||
this.mapTransitioning = true;
|
||||
|
||||
@@ -1568,14 +1536,13 @@ ${escapedMessage}
|
||||
|
||||
public cleanupClosingScene(): void {
|
||||
// stop playing audio, close any open website, stop any open Jitsi
|
||||
coWebsiteManager.closeCoWebsites().catch((e) => console.error(e));
|
||||
coWebsiteManager.closeCoWebsites();
|
||||
// Stop the script, if any
|
||||
const scripts = this.getScriptUrls(this.mapFile);
|
||||
for (const script of scripts) {
|
||||
iframeListener.unregisterScript(script);
|
||||
}
|
||||
|
||||
this.stopJitsi();
|
||||
audioManagerFileStore.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
this.connection?.closeConnection();
|
||||
@@ -1584,7 +1551,7 @@ ${escapedMessage}
|
||||
this.messageSubscription?.unsubscribe();
|
||||
this.userInputManager.destroy();
|
||||
this.pinchManager?.destroy();
|
||||
this.emoteManager.destroy();
|
||||
this.emoteManager?.destroy();
|
||||
this.cameraManager.destroy();
|
||||
this.peerStoreUnsubscribe();
|
||||
this.emoteUnsubscribe();
|
||||
@@ -1626,6 +1593,38 @@ ${escapedMessage}
|
||||
this.MapPlayersByKey.clear();
|
||||
}
|
||||
|
||||
private tryMovePlayerWithMoveToParameter(): void {
|
||||
const moveToParam = urlManager.getHashParameter("moveTo");
|
||||
if (moveToParam) {
|
||||
try {
|
||||
let endPos;
|
||||
const posFromParam = StringUtils.parsePointFromParam(moveToParam);
|
||||
if (posFromParam) {
|
||||
endPos = this.gameMap.getTileIndexAt(posFromParam.x, posFromParam.y);
|
||||
} else {
|
||||
const destinationObject = this.gameMap.getObjectWithName(moveToParam);
|
||||
if (destinationObject) {
|
||||
endPos = this.gameMap.getTileIndexAt(destinationObject.x, destinationObject.y);
|
||||
} else {
|
||||
endPos = this.gameMap.getRandomPositionFromLayer(moveToParam);
|
||||
}
|
||||
}
|
||||
this.pathfindingManager
|
||||
.findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos)
|
||||
.then((path) => {
|
||||
if (path && path.length > 0) {
|
||||
this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason));
|
||||
}
|
||||
})
|
||||
.catch((reason) => console.warn(reason));
|
||||
|
||||
urlManager.clearHashParameter();
|
||||
} catch (err) {
|
||||
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getExitUrl(layer: ITiledMapLayer): string | undefined {
|
||||
return this.getProperty(layer, GameMapProperties.EXIT_URL) as string | undefined;
|
||||
}
|
||||
@@ -1711,16 +1710,23 @@ ${escapedMessage}
|
||||
}
|
||||
}
|
||||
|
||||
// The promise that will resolve to the current player texture. This will be available only after connection is established.
|
||||
private currentPlayerTexturesResolve!: (value: string[]) => void;
|
||||
private currentPlayerTexturesReject!: (reason: unknown) => void;
|
||||
private currentPlayerTexturesPromise: CancelablePromise<string[]> = new CancelablePromise((resolve, reject) => {
|
||||
this.currentPlayerTexturesResolve = resolve;
|
||||
this.currentPlayerTexturesReject = reject;
|
||||
});
|
||||
|
||||
private createCurrentPlayer() {
|
||||
//TODO create animation moving between exit and start
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
||||
try {
|
||||
this.CurrentPlayer = new Player(
|
||||
this,
|
||||
this.startPositionCalculator.startPosition.x,
|
||||
this.startPositionCalculator.startPosition.y,
|
||||
this.playerName,
|
||||
texturesPromise,
|
||||
this.currentPlayerTexturesPromise,
|
||||
PlayerAnimationDirections.Down,
|
||||
false,
|
||||
this.companion,
|
||||
@@ -1739,7 +1745,7 @@ ${escapedMessage}
|
||||
}
|
||||
});
|
||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OVER, (pointer: Phaser.Input.Pointer) => {
|
||||
this.CurrentPlayer.pointerOverOutline(0x00ffff);
|
||||
this.CurrentPlayer.pointerOverOutline(0x365dff);
|
||||
});
|
||||
this.CurrentPlayer.on(Phaser.Input.Events.POINTER_OUT, (pointer: Phaser.Input.Pointer) => {
|
||||
this.CurrentPlayer.pointerOutOutline();
|
||||
@@ -1748,22 +1754,6 @@ ${escapedMessage}
|
||||
this.connection?.emitEmoteEvent(emoteKey);
|
||||
analyticsClient.launchEmote(emoteKey);
|
||||
});
|
||||
const moveToParam = urlManager.getHashParameter("moveTo");
|
||||
if (moveToParam) {
|
||||
try {
|
||||
const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam);
|
||||
this.pathfindingManager
|
||||
.findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos)
|
||||
.then((path) => {
|
||||
if (path && path.length > 0) {
|
||||
this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason));
|
||||
}
|
||||
})
|
||||
.catch((reason) => console.warn(reason));
|
||||
} catch (err) {
|
||||
console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof TextureError) {
|
||||
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
|
||||
@@ -1920,6 +1910,7 @@ ${escapedMessage}
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
|
||||
const player = new RemotePlayer(
|
||||
addPlayerData.userId,
|
||||
addPlayerData.userUuid,
|
||||
this,
|
||||
addPlayerData.position.x,
|
||||
addPlayerData.position.y,
|
||||
@@ -1947,6 +1938,10 @@ ${escapedMessage}
|
||||
this.activatablesManager.handlePointerOutActivatableObject();
|
||||
this.markDirty();
|
||||
});
|
||||
|
||||
player.on(RemotePlayerEvent.Clicked, () => {
|
||||
iframeListener.sendRemotePlayerClickedEvent({ id: player.userId });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2116,7 +2111,7 @@ ${escapedMessage}
|
||||
mediaManager.hideMyCamera();
|
||||
}
|
||||
|
||||
public startJitsi(roomName: string, jwt?: string): void {
|
||||
public initialiseJitsi(coWebsite: JitsiCoWebsite, roomName: string, jwt?: string): void {
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(
|
||||
allProps.get(GameMapProperties.JITSI_CONFIG) as string | undefined,
|
||||
@@ -2128,20 +2123,15 @@ ${escapedMessage}
|
||||
);
|
||||
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
|
||||
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl).catch(() => {
|
||||
console.error("Cannot start a Jitsi co-website");
|
||||
coWebsite.setJitsiLoadPromise(() => {
|
||||
return jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||
});
|
||||
this.disableMediaBehaviors();
|
||||
analyticsClient.enteredJitsi(roomName, this.room.id);
|
||||
}
|
||||
|
||||
public stopJitsi(): void {
|
||||
const coWebsite = coWebsiteManager.searchJitsi();
|
||||
if (coWebsite) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => {
|
||||
console.error("Error during Jitsi co-website closing", e);
|
||||
});
|
||||
}
|
||||
coWebsiteManager.loadCoWebsite(coWebsite).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
analyticsClient.enteredJitsi(roomName, this.room.id);
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
|
||||
@@ -16,32 +16,6 @@ export class StartPositionCalculator {
|
||||
) {
|
||||
this.initStartXAndStartY();
|
||||
}
|
||||
private initStartXAndStartY() {
|
||||
// If there is an init position passed
|
||||
if (this.initPosition !== null) {
|
||||
this.startPosition = this.initPosition;
|
||||
} else {
|
||||
// Now, let's find the start layer
|
||||
if (this.startLayerName) {
|
||||
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
|
||||
}
|
||||
if (this.startPosition === undefined) {
|
||||
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
||||
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
|
||||
}
|
||||
}
|
||||
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||
if (this.startPosition === undefined) {
|
||||
console.warn(
|
||||
'This map is missing a layer named "start" that contains the available default start positions.'
|
||||
);
|
||||
// Let's start in the middle of the map
|
||||
this.startPosition = {
|
||||
x: this.mapFile.width * 16,
|
||||
y: this.mapFile.height * 16,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -76,6 +50,47 @@ export class StartPositionCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
public getStartPositionNames(): string[] {
|
||||
const names: string[] = [];
|
||||
for (const layer of this.gameMap.flatLayers) {
|
||||
if (layer.name === "start") {
|
||||
names.push(layer.name);
|
||||
continue;
|
||||
}
|
||||
if (this.isStartLayer(layer)) {
|
||||
names.push(layer.name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private initStartXAndStartY() {
|
||||
// If there is an init position passed
|
||||
if (this.initPosition !== null) {
|
||||
this.startPosition = this.initPosition;
|
||||
} else {
|
||||
// Now, let's find the start layer
|
||||
if (this.startLayerName) {
|
||||
this.initPositionFromLayerName(this.startLayerName, this.startLayerName);
|
||||
}
|
||||
if (this.startPosition === undefined) {
|
||||
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
|
||||
this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName);
|
||||
}
|
||||
}
|
||||
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||
if (this.startPosition === undefined) {
|
||||
console.warn(
|
||||
'This map is missing a layer named "start" that contains the available default start positions.'
|
||||
);
|
||||
// Let's start in the middle of the map
|
||||
this.startPosition = {
|
||||
x: this.mapFile.width * 16,
|
||||
y: this.mapFile.height * 16,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private isStartLayer(layer: ITiledMapLayer): boolean {
|
||||
return this.getProperty(layer, GameMapProperties.START_LAYER) == true;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class ActionableItem implements ActivatableInterface {
|
||||
|
||||
this.getOutlinePlugin()?.add(this.sprite, {
|
||||
thickness: 2,
|
||||
outlineColor: 0xffff00,
|
||||
outlineColor: 0xf9e81e,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ export default {
|
||||
state = initState;
|
||||
}
|
||||
|
||||
// Idée: ESSAYER WebPack? https://paultavares.wordpress.com/2018/07/02/webpack-how-to-generate-an-es-module-bundle/
|
||||
const computer = new Sprite(scene, object.x, object.y, "computer");
|
||||
scene.add.existing(computer);
|
||||
if (state.status === "on") {
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import type { CharacterTexture } from "../../Connexion/LocalUser";
|
||||
import { BodyResourceDescriptionInterface, PlayerTexturesKey } from "../Entity/PlayerTextures";
|
||||
import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import { PlayerTextures } from "../Entity/PlayerTextures";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
protected playerTextures: PlayerTextures;
|
||||
|
||||
constructor(params: { key: string }) {
|
||||
super(params);
|
||||
this.playerTextures = new PlayerTextures();
|
||||
}
|
||||
|
||||
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const textures = this.playerTextures.getTexturesResources(PlayerTexturesKey.Woka);
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
for (const texture of Object.values(textures)) {
|
||||
if (texture.level === -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
promises.push(loadWokaTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
for (const textures of this.playerTextures.getLayers()) {
|
||||
for (const texture of Object.values(textures)) {
|
||||
if (texture.level !== -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
promises.push(loadWokaTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private getTextures(): CharacterTexture[] | undefined {
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
return localUser?.textures;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { CustomizedCharacter } from "../Entity/CustomizedCharacter";
|
||||
import { get } from "svelte/store";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
@@ -40,27 +41,45 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.loadCustomSceneSelectCharacters()
|
||||
.then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if (
|
||||
bodyResourceDescription.level == undefined ||
|
||||
bodyResourceDescription.level < 0 ||
|
||||
bodyResourceDescription.level > 5
|
||||
) {
|
||||
throw new Error("Texture level is null");
|
||||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
const wokaMetadataKey = "woka-list";
|
||||
this.cache.json.remove(wokaMetadataKey);
|
||||
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
|
||||
this.load.json(
|
||||
wokaMetadataKey,
|
||||
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
|
||||
undefined,
|
||||
{
|
||||
responseType: "text",
|
||||
headers: {
|
||||
Authorization: localUserStore.getAuthToken() ?? "",
|
||||
},
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
|
||||
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
|
||||
this.loadCustomSceneSelectCharacters()
|
||||
.then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if (
|
||||
bodyResourceDescription.level == undefined ||
|
||||
bodyResourceDescription.level < 0 ||
|
||||
bodyResourceDescription.level > 5
|
||||
) {
|
||||
throw new Error("Texture level is null");
|
||||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
this.lazyloadingAttempt = false;
|
||||
this.layers = loadAllLayers(this.load, this.playerTextures);
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
@@ -192,13 +211,13 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
const children: Array<string> = new Array<string>();
|
||||
for (let j = 0; j <= layerNumber; j++) {
|
||||
if (j === layerNumber) {
|
||||
children.push(this.layers[j][selectedItem].name);
|
||||
children.push(this.layers[j][selectedItem].id);
|
||||
} else {
|
||||
const layer = this.selectedLayers[j];
|
||||
if (layer === undefined) {
|
||||
continue;
|
||||
}
|
||||
children.push(this.layers[j][layer].name);
|
||||
children.push(this.layers[j][layer].id);
|
||||
}
|
||||
}
|
||||
return children;
|
||||
@@ -276,7 +295,7 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
let i = 0;
|
||||
for (const layerItem of this.selectedLayers) {
|
||||
if (layerItem !== undefined) {
|
||||
layers.push(this.layers[i][layerItem].name);
|
||||
layers.push(this.layers[i][layerItem].id);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -287,14 +306,14 @@ export class CustomizeScene extends AbstractCharacterScene {
|
||||
analyticsClient.validationWoka("CustomizeWoka");
|
||||
|
||||
gameManager.setCharacterLayers(layers);
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
this.scene.stop(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
gameManager.tryResumingGame(EnableCameraSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
}
|
||||
|
||||
public backToPreviousScene() {
|
||||
this.scene.sleep(CustomizeSceneName);
|
||||
this.scene.stop(CustomizeSceneName);
|
||||
waScaleManager.restoreZoom();
|
||||
this.scene.run(SelectCharacterSceneName);
|
||||
customCharacterSceneVisibleStore.set(false);
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ReconnectingTextures } from "../Reconnecting/ReconnectingScene";
|
||||
import LL from "../../i18n/i18n-svelte";
|
||||
import { get } from "svelte/store";
|
||||
import { localeDetector } from "../../i18n/locales";
|
||||
import { PlayerTextures } from "../Entity/PlayerTextures";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
export const EntrySceneName = "EntryScene";
|
||||
|
||||
@@ -15,6 +17,8 @@ export const EntrySceneName = "EntryScene";
|
||||
* and to route to the next correct scene.
|
||||
*/
|
||||
export class EntryScene extends Scene {
|
||||
private localeLoaded: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
key: EntrySceneName,
|
||||
@@ -30,6 +34,10 @@ export class EntryScene extends Scene {
|
||||
}
|
||||
|
||||
create() {
|
||||
this.loadLocale();
|
||||
}
|
||||
|
||||
private loadLocale(): void {
|
||||
localeDetector()
|
||||
.then(() => {
|
||||
gameManager
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CustomizeSceneName } from "./CustomizeScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { loadAllDefaultModels } from "../Entity/PlayerTexturesLoadingManager";
|
||||
import { Loader } from "../Components/Loader";
|
||||
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||
import { BodyResourceDescriptionInterface, PlayerTextures } from "../Entity/PlayerTextures";
|
||||
import { AbstractCharacterScene } from "./AbstractCharacterScene";
|
||||
import { areCharacterLayersValid } from "../../Connexion/LocalUser";
|
||||
import { touchScreenManager } from "../../Touch/TouchScreenManager";
|
||||
@@ -14,6 +14,8 @@ import { selectCharacterSceneVisibleStore } from "../../Stores/SelectCharacterSt
|
||||
import { waScaleManager } from "../Services/WaScaleManager";
|
||||
import { analyticsClient } from "../../Administration/AnalyticsClient";
|
||||
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
|
||||
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
|
||||
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
@@ -38,25 +40,46 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
key: SelectCharacterSceneName,
|
||||
});
|
||||
this.loader = new Loader(this);
|
||||
this.playerTextures = new PlayerTextures();
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.loadSelectSceneCharacters()
|
||||
.then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
this.playerModels = loadAllDefaultModels(this.load);
|
||||
this.lazyloadingAttempt = false;
|
||||
const wokaMetadataKey = "woka-list";
|
||||
this.cache.json.remove(wokaMetadataKey);
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
|
||||
this.load.json(
|
||||
wokaMetadataKey,
|
||||
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
|
||||
undefined,
|
||||
{
|
||||
responseType: "text",
|
||||
headers: {
|
||||
Authorization: localUserStore.getAuthToken() ?? "",
|
||||
},
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
|
||||
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
|
||||
this.loadSelectSceneCharacters()
|
||||
.then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
this.lazyloadingAttempt = true;
|
||||
})
|
||||
.catch((e) => console.error(e));
|
||||
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
|
||||
this.lazyloadingAttempt = false;
|
||||
|
||||
//this function must stay at the end of preload function
|
||||
this.loader.addLoader();
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
customizeAvailableStore.set(this.isCustomizationAvailable());
|
||||
selectCharacterSceneVisibleStore.set(true);
|
||||
this.events.addListener("wake", () => {
|
||||
waScaleManager.saveZoom();
|
||||
@@ -130,16 +153,16 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
const playerResource = this.playerModels[i];
|
||||
|
||||
//check already exist texture
|
||||
if (this.players.find((c) => c.texture.key === playerResource.name)) {
|
||||
if (this.players.find((c) => c.texture.key === playerResource.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const [middleX, middleY] = this.getCharacterPosition();
|
||||
const player = this.physics.add.sprite(middleX, middleY, playerResource.name, 0);
|
||||
const player = this.physics.add.sprite(middleX, middleY, playerResource.id, 0);
|
||||
this.setUpPlayer(player, i);
|
||||
this.anims.create({
|
||||
key: playerResource.name,
|
||||
frames: this.anims.generateFrameNumbers(playerResource.name, { start: 0, end: 11 }),
|
||||
key: playerResource.id,
|
||||
frames: this.anims.generateFrameNumbers(playerResource.id, { start: 0, end: 11 }),
|
||||
frameRate: 8,
|
||||
repeat: -1,
|
||||
});
|
||||
@@ -164,7 +187,7 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
this.currentSelectUser = 0;
|
||||
}
|
||||
this.selectedPlayer = this.players[this.currentSelectUser];
|
||||
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].name);
|
||||
this.selectedPlayer.play(this.playerModels[this.currentSelectUser].id);
|
||||
}
|
||||
|
||||
protected moveUser() {
|
||||
@@ -247,9 +270,9 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
}
|
||||
|
||||
protected updateSelectedPlayer(): void {
|
||||
this.selectedPlayer?.anims.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
|
||||
this.selectedPlayer?.anims?.pause(this.selectedPlayer?.anims.currentAnim.frames[0]);
|
||||
const player = this.players[this.currentSelectUser];
|
||||
player.play(this.playerModels[this.currentSelectUser].name);
|
||||
player?.play(this.playerModels[this.currentSelectUser].id);
|
||||
this.selectedPlayer = player;
|
||||
localUserStore.setPlayerCharacterIndex(this.currentSelectUser);
|
||||
}
|
||||
@@ -274,4 +297,13 @@ export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
//move position of user
|
||||
this.moveUser();
|
||||
}
|
||||
|
||||
private isCustomizationAvailable(): boolean {
|
||||
for (const layer of this.playerTextures.getLayers()) {
|
||||
if (Object.keys(layer).length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ export interface ITiledMapTileLayer {
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
parallaxx?: number;
|
||||
parallaxy?: number;
|
||||
|
||||
/**
|
||||
* Draw order (topdown (default), index)
|
||||
|
||||
@@ -76,7 +76,7 @@ export class Player extends Character {
|
||||
speed?: number
|
||||
): Promise<{ x: number; y: number; cancelled: boolean }> {
|
||||
const isPreviousPathInProgress = this.pathToFollow !== undefined && this.pathToFollow.length > 0;
|
||||
// take collider offset into consideraton
|
||||
// take collider offset into consideration
|
||||
this.pathToFollow = this.adjustPathToFollowToColliderBounds(path);
|
||||
this.pathWalkingSpeed = speed;
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export type ActionsMenuAction = {
|
||||
actionName: string;
|
||||
callback: Function;
|
||||
protected?: boolean;
|
||||
priority?: number;
|
||||
style?: "is-success" | "is-error" | "is-primary";
|
||||
};
|
||||
export interface ActionsMenuData {
|
||||
playerName: string;
|
||||
actions: { actionName: string; callback: Function }[];
|
||||
actions: Map<string, ActionsMenuAction>;
|
||||
}
|
||||
|
||||
function createActionsMenuStore() {
|
||||
@@ -13,21 +20,18 @@ function createActionsMenuStore() {
|
||||
initialize: (playerName: string) => {
|
||||
set({
|
||||
playerName,
|
||||
actions: [],
|
||||
actions: new Map<string, ActionsMenuAction>(),
|
||||
});
|
||||
},
|
||||
addAction: (actionName: string, callback: Function) => {
|
||||
addAction: (action: ActionsMenuAction) => {
|
||||
update((data) => {
|
||||
data?.actions.push({ actionName, callback });
|
||||
data?.actions.set(action.actionName, action);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
removeAction: (actionName: string) => {
|
||||
update((data) => {
|
||||
const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName);
|
||||
if (actionIndex !== undefined && actionIndex != -1) {
|
||||
data?.actions.splice(actionIndex, 1);
|
||||
}
|
||||
data?.actions.delete(actionName);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { derived, get, writable } from "svelte/store";
|
||||
import type { CoWebsite } from "../WebRtc/CoWebsiteManager";
|
||||
import { derived, writable } from "svelte/store";
|
||||
import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
|
||||
|
||||
function createCoWebsiteStore() {
|
||||
const { subscribe, set, update } = writable(Array<CoWebsite>());
|
||||
@@ -9,7 +9,7 @@ function createCoWebsiteStore() {
|
||||
return {
|
||||
subscribe,
|
||||
add: (coWebsite: CoWebsite, position?: number) => {
|
||||
coWebsite.state.subscribe((value) => {
|
||||
coWebsite.getStateSubscriber().subscribe((value) => {
|
||||
update((currentArray) => currentArray);
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ function createCoWebsiteStore() {
|
||||
},
|
||||
remove: (coWebsite: CoWebsite) => {
|
||||
update((currentArray) => [
|
||||
...currentArray.filter((currentCoWebsite) => currentCoWebsite.iframe.id !== coWebsite.iframe.id),
|
||||
...currentArray.filter((currentCoWebsite) => currentCoWebsite.getId() !== coWebsite.getId()),
|
||||
]);
|
||||
},
|
||||
empty: () => {
|
||||
@@ -43,9 +43,9 @@ function createCoWebsiteStore() {
|
||||
export const coWebsites = createCoWebsiteStore();
|
||||
|
||||
export const coWebsitesNotAsleep = derived([coWebsites], ([$coWebsites]) =>
|
||||
$coWebsites.filter((coWebsite) => get(coWebsite.state) !== "asleep")
|
||||
$coWebsites.filter((coWebsite) => coWebsite.getState() !== "asleep")
|
||||
);
|
||||
|
||||
export const mainCoWebsite = derived([coWebsites], ([$coWebsites]) =>
|
||||
$coWebsites.find((coWebsite) => get(coWebsite.state) !== "asleep")
|
||||
$coWebsites.find((coWebsite) => coWebsite.getState() !== "asleep")
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { derived, get, writable } from "svelte/store";
|
||||
import type { CoWebsite } from "../WebRtc/CoWebsiteManager";
|
||||
import type { CoWebsite } from "../WebRtc/CoWebsite/CoWesbite";
|
||||
import { LayoutMode } from "../WebRtc/LayoutManager";
|
||||
import { coWebsites } from "./CoWebsiteStore";
|
||||
import { Streamable, streamableCollectionStore } from "./StreamableCollectionStore";
|
||||
@@ -15,7 +15,7 @@ export type EmbedScreen =
|
||||
};
|
||||
|
||||
function createHighlightedEmbedScreenStore() {
|
||||
const { subscribe, set, update } = writable<EmbedScreen | null>(null);
|
||||
const { subscribe, set, update } = writable<EmbedScreen | undefined>(undefined);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
@@ -23,7 +23,7 @@ function createHighlightedEmbedScreenStore() {
|
||||
set(embedScreen);
|
||||
},
|
||||
removeHighlight: () => {
|
||||
set(null);
|
||||
set(undefined);
|
||||
},
|
||||
toggleHighlight: (embedScreen: EmbedScreen) => {
|
||||
update((currentEmbedScreen) =>
|
||||
@@ -31,12 +31,12 @@ function createHighlightedEmbedScreenStore() {
|
||||
embedScreen.type !== currentEmbedScreen.type ||
|
||||
(embedScreen.type === "cowebsite" &&
|
||||
currentEmbedScreen.type === "cowebsite" &&
|
||||
embedScreen.embed.iframe.id !== currentEmbedScreen.embed.iframe.id) ||
|
||||
embedScreen.embed.getId() !== currentEmbedScreen.embed.getId()) ||
|
||||
(embedScreen.type === "streamable" &&
|
||||
currentEmbedScreen.type === "streamable" &&
|
||||
embedScreen.embed.uniqueId !== currentEmbedScreen.embed.uniqueId)
|
||||
? embedScreen
|
||||
: null
|
||||
: undefined
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -10,6 +10,8 @@ import { myCameraVisibilityStore } from "./MyCameraStoreVisibility";
|
||||
import { peerStore } from "./PeerStore";
|
||||
import { privacyShutdownStore } from "./PrivacyShutdownStore";
|
||||
import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError";
|
||||
import { SoundMeter } from "../Phaser/Components/SoundMeter";
|
||||
import { AudioContext } from "standardized-audio-context";
|
||||
|
||||
/**
|
||||
* A store that contains the camera state requested by the user (on or off).
|
||||
@@ -541,6 +543,41 @@ export const obtainedMediaConstraintStore = derived<Readable<MediaStreamConstrai
|
||||
}
|
||||
);
|
||||
|
||||
export const localVolumeStore = readable<number | undefined>(undefined, (set) => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => {
|
||||
clearInterval(timeout);
|
||||
if (localStreamStoreValue.type === "error") {
|
||||
set(undefined);
|
||||
return;
|
||||
}
|
||||
const mediaStream = localStreamStoreValue.stream;
|
||||
|
||||
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
|
||||
set(undefined);
|
||||
return;
|
||||
}
|
||||
const soundMeter = new SoundMeter(mediaStream);
|
||||
let error = false;
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try {
|
||||
set(soundMeter.getVolume());
|
||||
} catch (err) {
|
||||
if (!error) {
|
||||
console.error(err);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearInterval(timeout);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Device list
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const customizeAvailableStore = writable(false);
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Readable, writable } from "svelte/store";
|
||||
|
||||
/**
|
||||
* A store that contains the map starting layers names
|
||||
*/
|
||||
export const startLayerNamesStore = writable<string[]>([]);
|
||||
@@ -89,7 +89,7 @@ export class MapStore<K, V> extends Map<K, V> implements Readable<Map<K, V>> {
|
||||
}
|
||||
}
|
||||
|
||||
return readable(initStoreValue, (set) => {
|
||||
return readable<T | undefined>(initStoreValue, (set) => {
|
||||
const storeByKey = this.getStore(key);
|
||||
|
||||
let unsubscribeDeepStore: Unsubscriber | undefined;
|
||||
|
||||
@@ -77,6 +77,10 @@ class UrlManager {
|
||||
return this.getHashParameters()[name];
|
||||
}
|
||||
|
||||
public clearHashParameter(): void {
|
||||
window.location.hash = "";
|
||||
}
|
||||
|
||||
private getHashParameters(): Record<string, string> {
|
||||
return window.location.hash
|
||||
.substring(1)
|
||||
|
||||
@@ -13,12 +13,18 @@ export class PathfindingManager {
|
||||
|
||||
this.easyStar = new EasyStar.js();
|
||||
this.easyStar.enableDiagonals();
|
||||
this.easyStar.disableCornerCutting();
|
||||
this.easyStar.setTileCost(2, 100);
|
||||
|
||||
this.grid = collisionsGrid;
|
||||
this.tileDimensions = tileDimensions;
|
||||
this.setEasyStarGrid(collisionsGrid);
|
||||
}
|
||||
|
||||
public setCollisionGrid(collisionGrid: number[][]): void {
|
||||
this.setEasyStarGrid(collisionGrid);
|
||||
}
|
||||
|
||||
public async findPath(
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number },
|
||||
@@ -108,7 +114,7 @@ export class PathfindingManager {
|
||||
|
||||
private setEasyStarGrid(grid: number[][]): void {
|
||||
this.easyStar.setGrid(grid);
|
||||
this.easyStar.setAcceptableTiles([0]); // zeroes are walkable
|
||||
this.easyStar.setAcceptableTiles([0, 2]); // zeroes are walkable, 2 are exits, also walkable
|
||||
}
|
||||
|
||||
private logGridToTheConsole(grid: number[][]): void {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export class StringUtils {
|
||||
public static parsePointFromParam(param: string, separator: string = ","): { x: number; y: number } | undefined {
|
||||
const values = param.split(separator).map((val) => parseInt(val));
|
||||
if (values.length !== 2) {
|
||||
return;
|
||||
}
|
||||
if (isNaN(values[0]) || isNaN(values[1])) {
|
||||
return;
|
||||
}
|
||||
return { x: values[0], y: values[1] };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
import type { Readable, Writable } from "svelte/store";
|
||||
|
||||
export type CoWebsiteState = "asleep" | "loading" | "ready";
|
||||
|
||||
export interface CoWebsite {
|
||||
getId(): string;
|
||||
getUrl(): URL;
|
||||
getState(): CoWebsiteState;
|
||||
getStateSubscriber(): Readable<CoWebsiteState>;
|
||||
getIframe(): HTMLIFrameElement | undefined;
|
||||
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined;
|
||||
getWidthPercent(): number | undefined;
|
||||
isClosable(): boolean;
|
||||
load(): CancelablePromise<HTMLIFrameElement>;
|
||||
unload(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { gameManager } from "../../Phaser/Game/GameManager";
|
||||
import { jitsiFactory } from "../JitsiFactory";
|
||||
import { SimpleCoWebsite } from "./SimpleCoWebsite";
|
||||
|
||||
export class JitsiCoWebsite extends SimpleCoWebsite {
|
||||
private jitsiLoadPromise?: () => CancelablePromise<HTMLIFrameElement>;
|
||||
|
||||
setJitsiLoadPromise(promise: () => CancelablePromise<HTMLIFrameElement>): void {
|
||||
this.jitsiLoadPromise = promise;
|
||||
}
|
||||
|
||||
load(): CancelablePromise<HTMLIFrameElement> {
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
this.state.set("loading");
|
||||
|
||||
gameManager.getCurrentGameScene().disableMediaBehaviors();
|
||||
|
||||
if (!this.jitsiLoadPromise) {
|
||||
return reject("Undefined Jitsi start callback");
|
||||
}
|
||||
|
||||
const jitsiLoading = this.jitsiLoadPromise()
|
||||
.then((iframe) => {
|
||||
this.iframe = iframe;
|
||||
this.iframe.classList.add("pixel");
|
||||
this.state.set("ready");
|
||||
return resolve(iframe);
|
||||
})
|
||||
.catch((err) => {
|
||||
return reject(err);
|
||||
});
|
||||
|
||||
cancel(() => {
|
||||
jitsiLoading.cancel();
|
||||
this.unload().catch((err) => {
|
||||
console.error("Cannot unload Jitsi co-website while cancel loading", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
unload(): Promise<void> {
|
||||
jitsiFactory.destroy();
|
||||
gameManager.getCurrentGameScene().enableMediaBehaviors();
|
||||
|
||||
return super.unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
import { get, Readable, writable, Writable } from "svelte/store";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import { coWebsiteManager } from "../CoWebsiteManager";
|
||||
import type { CoWebsite, CoWebsiteState } from "./CoWesbite";
|
||||
|
||||
export class SimpleCoWebsite implements CoWebsite {
|
||||
protected id: string;
|
||||
protected url: URL;
|
||||
protected state: Writable<CoWebsiteState>;
|
||||
protected iframe?: HTMLIFrameElement;
|
||||
protected loadIframe?: CancelablePromise<HTMLIFrameElement>;
|
||||
protected allowApi?: boolean;
|
||||
protected allowPolicy?: string;
|
||||
protected widthPercent?: number;
|
||||
protected closable: boolean;
|
||||
|
||||
constructor(url: URL, allowApi?: boolean, allowPolicy?: string, widthPercent?: number, closable?: boolean) {
|
||||
this.id = coWebsiteManager.generateUniqueId();
|
||||
this.url = url;
|
||||
this.state = writable("asleep" as CoWebsiteState);
|
||||
this.allowApi = allowApi;
|
||||
this.allowPolicy = allowPolicy;
|
||||
this.widthPercent = widthPercent;
|
||||
this.closable = closable ?? false;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
getUrl(): URL {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
getState(): CoWebsiteState {
|
||||
return get(this.state);
|
||||
}
|
||||
|
||||
getStateSubscriber(): Readable<CoWebsiteState> {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
getIframe(): HTMLIFrameElement | undefined {
|
||||
return this.iframe;
|
||||
}
|
||||
|
||||
getLoadIframe(): CancelablePromise<HTMLIFrameElement> | undefined {
|
||||
return this.loadIframe;
|
||||
}
|
||||
|
||||
getWidthPercent(): number | undefined {
|
||||
return this.widthPercent;
|
||||
}
|
||||
|
||||
isClosable(): boolean {
|
||||
return this.closable;
|
||||
}
|
||||
|
||||
load(): CancelablePromise<HTMLIFrameElement> {
|
||||
this.loadIframe = new CancelablePromise((resolve, reject, cancel) => {
|
||||
this.state.set("loading");
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
this.iframe = iframe;
|
||||
this.iframe.src = this.url.toString();
|
||||
this.iframe.id = this.id;
|
||||
|
||||
if (this.allowPolicy) {
|
||||
this.iframe.allow = this.allowPolicy;
|
||||
}
|
||||
|
||||
if (this.allowApi) {
|
||||
iframeListener.registerIframe(this.iframe);
|
||||
}
|
||||
|
||||
this.iframe.classList.add("pixel");
|
||||
|
||||
const onloadPromise = new Promise<void>((resolve) => {
|
||||
if (this.iframe) {
|
||||
this.iframe.onload = () => {
|
||||
this.state.set("ready");
|
||||
resolve();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const onTimeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
|
||||
coWebsiteManager.getCoWebsiteBuffer().appendChild(this.iframe);
|
||||
|
||||
const race = CancelablePromise.race([onloadPromise, onTimeoutPromise])
|
||||
.then(() => {
|
||||
return resolve(iframe);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error on co-website loading => ", err);
|
||||
return reject();
|
||||
});
|
||||
|
||||
cancel(() => {
|
||||
race.cancel();
|
||||
this.unload().catch((err) => {
|
||||
console.error("Cannot unload co-website while cancel loading", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return this.loadIframe;
|
||||
}
|
||||
|
||||
unload(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (this.iframe) {
|
||||
if (this.allowApi) {
|
||||
iframeListener.unregisterIframe(this.iframe);
|
||||
}
|
||||
this.iframe.parentNode?.removeChild(this.iframe);
|
||||
}
|
||||
|
||||
if (this.loadIframe) {
|
||||
this.loadIframe.cancel();
|
||||
this.loadIframe = undefined;
|
||||
}
|
||||
|
||||
this.state.set("asleep");
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
import { HtmlUtils } from "./HtmlUtils";
|
||||
import { Subject } from "rxjs";
|
||||
import { iframeListener } from "../Api/IframeListener";
|
||||
import { waScaleManager } from "../Phaser/Services/WaScaleManager";
|
||||
import { coWebsites, coWebsitesNotAsleep, mainCoWebsite } from "../Stores/CoWebsiteStore";
|
||||
import { get, Writable, writable } from "svelte/store";
|
||||
import { get, Readable, Writable, writable } from "svelte/store";
|
||||
import { embedScreenLayout, highlightedEmbedScreen } from "../Stores/EmbedScreensStore";
|
||||
import { isMediaBreakpointDown } from "../Utils/BreakpointsUtils";
|
||||
import { jitsiFactory } from "./JitsiFactory";
|
||||
import { gameManager } from "../Phaser/Game/GameManager";
|
||||
import { LayoutMode } from "./LayoutManager";
|
||||
import type { CoWebsite } from "./CoWebsite/CoWesbite";
|
||||
import type CancelablePromise from "cancelable-promise";
|
||||
|
||||
export enum iframeStates {
|
||||
closed = 1,
|
||||
@@ -34,30 +33,12 @@ interface TouchMoveCoordinates {
|
||||
y: number;
|
||||
}
|
||||
|
||||
export type CoWebsiteState = "asleep" | "loading" | "ready";
|
||||
|
||||
export type CoWebsite = {
|
||||
iframe: HTMLIFrameElement;
|
||||
url: URL;
|
||||
state: Writable<CoWebsiteState>;
|
||||
closable: boolean;
|
||||
allowPolicy: string | undefined;
|
||||
allowApi: boolean | undefined;
|
||||
widthPercent?: number | undefined;
|
||||
jitsi?: boolean;
|
||||
altMessage?: string;
|
||||
};
|
||||
|
||||
class CoWebsiteManager {
|
||||
private openedMain: iframeStates = iframeStates.closed;
|
||||
private openedMain: Writable<iframeStates> = writable(iframeStates.closed);
|
||||
|
||||
private _onResize: Subject<void> = new Subject();
|
||||
public onResize = this._onResize.asObservable();
|
||||
/**
|
||||
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
|
||||
* So we use this promise to queue up every cowebsite state transition
|
||||
*/
|
||||
private currentOperationPromise: Promise<void> = Promise.resolve();
|
||||
|
||||
private cowebsiteDom: HTMLDivElement;
|
||||
private resizing: boolean = false;
|
||||
private gameOverlayDom: HTMLDivElement;
|
||||
@@ -76,6 +57,10 @@ class CoWebsiteManager {
|
||||
});
|
||||
|
||||
public getMainState() {
|
||||
return get(this.openedMain);
|
||||
}
|
||||
|
||||
public getMainStateSubscriber(): Readable<iframeStates> {
|
||||
return this.openedMain;
|
||||
}
|
||||
|
||||
@@ -147,13 +132,11 @@ class CoWebsiteManager {
|
||||
throw new Error("Undefined main co-website on closing");
|
||||
}
|
||||
|
||||
if (coWebsite.closable) {
|
||||
this.closeCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during closing a co-website by a button");
|
||||
});
|
||||
if (coWebsite.isClosable()) {
|
||||
this.closeCoWebsite(coWebsite);
|
||||
} else {
|
||||
this.unloadCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during unloading a co-website by a button");
|
||||
this.unloadCoWebsite(coWebsite).catch((err) => {
|
||||
console.error("Cannot unload co-website on click on close button", err);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -176,9 +159,17 @@ class CoWebsiteManager {
|
||||
});
|
||||
|
||||
buttonSwipe.addEventListener("click", () => {
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
const highlightedEmbed = get(highlightedEmbedScreen);
|
||||
if (highlightedEmbed?.type === "cowebsite") {
|
||||
this.goToMain(highlightedEmbed.embed);
|
||||
|
||||
if (mainCoWebsite) {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: mainCoWebsite,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -241,7 +232,10 @@ class CoWebsiteManager {
|
||||
return;
|
||||
}
|
||||
|
||||
coWebsite.iframe.style.display = "none";
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "none";
|
||||
}
|
||||
this.resizing = true;
|
||||
document.addEventListener("mousemove", movecallback);
|
||||
});
|
||||
@@ -257,7 +251,10 @@ class CoWebsiteManager {
|
||||
return;
|
||||
}
|
||||
|
||||
coWebsite.iframe.style.display = "flex";
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "flex";
|
||||
}
|
||||
this.resizing = false;
|
||||
});
|
||||
|
||||
@@ -270,7 +267,10 @@ class CoWebsiteManager {
|
||||
return;
|
||||
}
|
||||
|
||||
coWebsite.iframe.style.display = "none";
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "none";
|
||||
}
|
||||
this.resizing = true;
|
||||
const touchEvent = event.touches[0];
|
||||
this.previousTouchMoveCoordinates = { x: touchEvent.pageX, y: touchEvent.pageY };
|
||||
@@ -289,7 +289,10 @@ class CoWebsiteManager {
|
||||
return;
|
||||
}
|
||||
|
||||
coWebsite.iframe.style.display = "flex";
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "flex";
|
||||
}
|
||||
this.resizing = false;
|
||||
});
|
||||
}
|
||||
@@ -313,9 +316,12 @@ class CoWebsiteManager {
|
||||
public displayMain() {
|
||||
const coWebsite = this.getMainCoWebsite();
|
||||
if (coWebsite) {
|
||||
coWebsite.iframe.style.display = "block";
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "block";
|
||||
}
|
||||
}
|
||||
this.loadMain();
|
||||
this.loadMain(coWebsite?.getWidthPercent());
|
||||
this.openMain();
|
||||
this.fire();
|
||||
}
|
||||
@@ -323,11 +329,14 @@ class CoWebsiteManager {
|
||||
public hideMain() {
|
||||
const coWebsite = this.getMainCoWebsite();
|
||||
if (coWebsite) {
|
||||
coWebsite.iframe.style.display = "none";
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "none";
|
||||
}
|
||||
}
|
||||
this.cowebsiteDom.classList.add("closing");
|
||||
this.cowebsiteDom.classList.remove("opened");
|
||||
this.openedMain = iframeStates.closed;
|
||||
this.openedMain.set(iframeStates.closed);
|
||||
this.fire();
|
||||
}
|
||||
|
||||
@@ -335,7 +344,7 @@ class CoWebsiteManager {
|
||||
this.toggleFullScreenIcon(true);
|
||||
this.cowebsiteDom.classList.add("closing");
|
||||
this.cowebsiteDom.classList.remove("opened");
|
||||
this.openedMain = iframeStates.closed;
|
||||
this.openedMain.set(iframeStates.closed);
|
||||
this.resetStyleMain();
|
||||
this.fire();
|
||||
}
|
||||
@@ -389,14 +398,14 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
this.cowebsiteDom.classList.add("opened");
|
||||
this.openedMain = iframeStates.loading;
|
||||
this.openedMain.set(iframeStates.loading);
|
||||
}
|
||||
|
||||
private openMain(): void {
|
||||
this.cowebsiteDom.addEventListener("transitionend", () => {
|
||||
this.resizeAllIframes();
|
||||
});
|
||||
this.openedMain = iframeStates.opened;
|
||||
this.openedMain.set(iframeStates.opened);
|
||||
}
|
||||
|
||||
public resetStyleMain() {
|
||||
@@ -409,7 +418,9 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
public getCoWebsiteById(coWebsiteId: string): CoWebsite | undefined {
|
||||
return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId);
|
||||
return get(coWebsites).find((coWebsite: CoWebsite) => {
|
||||
return coWebsite.getId() === coWebsiteId;
|
||||
});
|
||||
}
|
||||
|
||||
private getCoWebsiteByPosition(position: number): CoWebsite | undefined {
|
||||
@@ -429,7 +440,9 @@ class CoWebsiteManager {
|
||||
}
|
||||
|
||||
private getPositionByCoWebsite(coWebsite: CoWebsite): number {
|
||||
return get(coWebsites).findIndex((currentCoWebsite) => currentCoWebsite.iframe.id === coWebsite.iframe.id);
|
||||
return get(coWebsites).findIndex((currentCoWebsite) => {
|
||||
return currentCoWebsite.getId() === coWebsite.getId();
|
||||
});
|
||||
}
|
||||
|
||||
private getSlotByCowebsite(coWebsite: CoWebsite): HTMLDivElement | undefined {
|
||||
@@ -443,7 +456,7 @@ class CoWebsiteManager {
|
||||
if (index === 0) {
|
||||
id += "main";
|
||||
} else {
|
||||
id += coWebsite.iframe.id;
|
||||
id += coWebsite.getId();
|
||||
}
|
||||
|
||||
const slot = HtmlUtils.getElementById<HTMLDivElement>(id);
|
||||
@@ -460,60 +473,72 @@ class CoWebsiteManager {
|
||||
|
||||
const bounding = coWebsiteSlot.getBoundingClientRect();
|
||||
|
||||
coWebsite.iframe.style.top = bounding.top + "px";
|
||||
coWebsite.iframe.style.left = bounding.left + "px";
|
||||
coWebsite.iframe.style.width = bounding.right - bounding.left + "px";
|
||||
coWebsite.iframe.style.height = bounding.bottom - bounding.top + "px";
|
||||
const iframe = coWebsite.getIframe();
|
||||
|
||||
if (iframe) {
|
||||
iframe.style.top = bounding.top + "px";
|
||||
iframe.style.left = bounding.left + "px";
|
||||
iframe.style.width = bounding.right - bounding.left + "px";
|
||||
iframe.style.height = bounding.bottom - bounding.top + "px";
|
||||
}
|
||||
}
|
||||
|
||||
public resizeAllIframes() {
|
||||
const mainCoWebsite = this.getCoWebsiteByPosition(0);
|
||||
const mainIframe = mainCoWebsite?.getIframe();
|
||||
const highlightEmbed = get(highlightedEmbedScreen);
|
||||
|
||||
get(coWebsites).forEach((coWebsite) => {
|
||||
const notMain = !mainCoWebsite || (mainCoWebsite && mainCoWebsite.iframe.id !== coWebsite.iframe.id);
|
||||
get(coWebsites).forEach((coWebsite: CoWebsite) => {
|
||||
const iframe = coWebsite.getIframe();
|
||||
if (!iframe) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notMain = !mainCoWebsite || (mainCoWebsite && mainIframe && mainIframe.id !== iframe.id);
|
||||
const notHighlighEmbed =
|
||||
!highlightEmbed ||
|
||||
(highlightEmbed &&
|
||||
(highlightEmbed.type !== "cowebsite" ||
|
||||
(highlightEmbed.type === "cowebsite" &&
|
||||
highlightEmbed.embed.iframe.id !== coWebsite.iframe.id)));
|
||||
(highlightEmbed.type === "cowebsite" && highlightEmbed.embed.getId() !== coWebsite.getId())));
|
||||
|
||||
if (coWebsite.iframe.classList.contains("main") && notMain) {
|
||||
coWebsite.iframe.classList.remove("main");
|
||||
if (iframe.classList.contains("main") && notMain) {
|
||||
iframe.classList.remove("main");
|
||||
}
|
||||
|
||||
if (coWebsite.iframe.classList.contains("highlighted") && notHighlighEmbed) {
|
||||
coWebsite.iframe.classList.remove("highlighted");
|
||||
coWebsite.iframe.classList.add("pixel");
|
||||
coWebsite.iframe.style.top = "-1px";
|
||||
coWebsite.iframe.style.left = "-1px";
|
||||
if (iframe.classList.contains("highlighted") && notHighlighEmbed) {
|
||||
iframe.classList.remove("highlighted");
|
||||
iframe.classList.add("pixel");
|
||||
iframe.style.top = "-1px";
|
||||
iframe.style.left = "-1px";
|
||||
}
|
||||
|
||||
if (notMain && notHighlighEmbed) {
|
||||
coWebsite.iframe.classList.add("pixel");
|
||||
coWebsite.iframe.style.top = "-1px";
|
||||
coWebsite.iframe.style.left = "-1px";
|
||||
iframe.classList.add("pixel");
|
||||
iframe.style.top = "-1px";
|
||||
iframe.style.left = "-1px";
|
||||
}
|
||||
|
||||
this.setIframeOffset(coWebsite);
|
||||
});
|
||||
|
||||
if (mainCoWebsite) {
|
||||
mainCoWebsite.iframe.classList.add("main");
|
||||
mainCoWebsite.iframe.classList.remove("pixel");
|
||||
if (mainIframe) {
|
||||
mainIframe.classList.add("main");
|
||||
mainIframe.classList.remove("pixel");
|
||||
}
|
||||
|
||||
if (highlightEmbed && highlightEmbed.type === "cowebsite") {
|
||||
highlightEmbed.embed.iframe.classList.add("highlighted");
|
||||
highlightEmbed.embed.iframe.classList.remove("pixel");
|
||||
const highlightEmbedIframe = highlightEmbed.embed.getIframe();
|
||||
if (highlightEmbedIframe) {
|
||||
highlightEmbedIframe.classList.add("highlighted");
|
||||
highlightEmbedIframe.classList.remove("pixel");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeHighlightCoWebsite(coWebsite: CoWebsite) {
|
||||
const highlighted = get(highlightedEmbedScreen);
|
||||
|
||||
if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.iframe.id === coWebsite.iframe.id) {
|
||||
if (highlighted && highlighted.type === "cowebsite" && highlighted.embed.getId() === coWebsite.getId()) {
|
||||
highlightedEmbedScreen.removeHighlight();
|
||||
}
|
||||
}
|
||||
@@ -526,7 +551,9 @@ class CoWebsiteManager {
|
||||
this.closeMain();
|
||||
}
|
||||
|
||||
coWebsite.iframe.remove();
|
||||
coWebsite.unload().catch((err) => {
|
||||
console.error("Cannot unload cowebsite on remove from stack");
|
||||
});
|
||||
}
|
||||
|
||||
public goToMain(coWebsite: CoWebsite) {
|
||||
@@ -534,42 +561,32 @@ class CoWebsiteManager {
|
||||
coWebsites.remove(coWebsite);
|
||||
coWebsites.add(coWebsite, 0);
|
||||
|
||||
if (mainCoWebsite) {
|
||||
const iframe = mainCoWebsite.getIframe();
|
||||
if (iframe) {
|
||||
iframe.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
isMediaBreakpointDown("lg") &&
|
||||
get(embedScreenLayout) === LayoutMode.Presentation &&
|
||||
mainCoWebsite &&
|
||||
mainCoWebsite.iframe.id !== coWebsite.iframe.id &&
|
||||
get(mainCoWebsite.state) !== "asleep"
|
||||
mainCoWebsite.getId() !== coWebsite.getId() &&
|
||||
mainCoWebsite.getState() !== "asleep"
|
||||
) {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: mainCoWebsite,
|
||||
});
|
||||
highlightedEmbedScreen.removeHighlight();
|
||||
}
|
||||
|
||||
this.resizeAllIframes();
|
||||
}
|
||||
|
||||
public searchJitsi(): CoWebsite | undefined {
|
||||
return get(coWebsites).find((coWebsite: CoWebsite) => coWebsite.jitsi);
|
||||
}
|
||||
|
||||
private initialiseCowebsite(coWebsite: CoWebsite, position: number | undefined) {
|
||||
if (coWebsite.allowPolicy) {
|
||||
coWebsite.iframe.allow = coWebsite.allowPolicy;
|
||||
}
|
||||
|
||||
if (coWebsite.allowApi) {
|
||||
iframeListener.registerIframe(coWebsite.iframe);
|
||||
}
|
||||
|
||||
coWebsite.iframe.classList.add("pixel");
|
||||
|
||||
public addCoWebsiteToStore(coWebsite: CoWebsite, position: number | undefined) {
|
||||
const coWebsitePosition = position === undefined ? get(coWebsites).length : position;
|
||||
coWebsites.add(coWebsite, coWebsitePosition);
|
||||
}
|
||||
|
||||
private generateUniqueId() {
|
||||
public generateUniqueId() {
|
||||
let id = undefined;
|
||||
do {
|
||||
id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7);
|
||||
@@ -578,210 +595,97 @@ class CoWebsiteManager {
|
||||
return id;
|
||||
}
|
||||
|
||||
public addCoWebsite(
|
||||
url: string,
|
||||
base: string,
|
||||
allowApi?: boolean,
|
||||
allowPolicy?: string,
|
||||
widthPercent?: number,
|
||||
position?: number,
|
||||
closable?: boolean,
|
||||
altMessage?: string
|
||||
): CoWebsite {
|
||||
const iframe = document.createElement("iframe");
|
||||
const fullUrl = new URL(url, base);
|
||||
iframe.src = fullUrl.toString();
|
||||
iframe.id = this.generateUniqueId();
|
||||
|
||||
const newCoWebsite: CoWebsite = {
|
||||
iframe,
|
||||
url: fullUrl,
|
||||
state: writable("asleep" as CoWebsiteState),
|
||||
closable: closable ?? false,
|
||||
allowPolicy,
|
||||
allowApi,
|
||||
widthPercent,
|
||||
altMessage,
|
||||
};
|
||||
|
||||
this.initialiseCowebsite(newCoWebsite, position);
|
||||
|
||||
return newCoWebsite;
|
||||
}
|
||||
|
||||
public addCoWebsiteFromIframe(
|
||||
iframe: HTMLIFrameElement,
|
||||
allowApi?: boolean,
|
||||
allowPolicy?: string,
|
||||
widthPercent?: number,
|
||||
position?: number,
|
||||
closable?: boolean,
|
||||
jitsi?: boolean
|
||||
): CoWebsite {
|
||||
if (get(coWebsitesNotAsleep).length < 1) {
|
||||
this.loadMain(widthPercent);
|
||||
}
|
||||
|
||||
iframe.id = this.generateUniqueId();
|
||||
|
||||
const newCoWebsite: CoWebsite = {
|
||||
iframe,
|
||||
url: new URL(iframe.src),
|
||||
state: writable("ready" as CoWebsiteState),
|
||||
closable: closable ?? false,
|
||||
allowPolicy,
|
||||
allowApi,
|
||||
widthPercent,
|
||||
jitsi,
|
||||
};
|
||||
|
||||
if (position === 0) {
|
||||
this.openMain();
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
}, animationTime);
|
||||
}
|
||||
|
||||
this.initialiseCowebsite(newCoWebsite, position);
|
||||
|
||||
return newCoWebsite;
|
||||
}
|
||||
|
||||
public loadCoWebsite(coWebsite: CoWebsite): Promise<CoWebsite> {
|
||||
public loadCoWebsite(coWebsite: CoWebsite): CancelablePromise<void> {
|
||||
if (get(coWebsitesNotAsleep).length < 1) {
|
||||
coWebsites.remove(coWebsite);
|
||||
coWebsites.add(coWebsite, 0);
|
||||
this.loadMain(coWebsite.widthPercent);
|
||||
this.loadMain(coWebsite.getWidthPercent());
|
||||
}
|
||||
|
||||
// Check if the main is hide
|
||||
if (this.getMainCoWebsite() && this.openedMain === iframeStates.closed) {
|
||||
if (this.getMainCoWebsite() && this.getMainState() === iframeStates.closed) {
|
||||
this.displayMain();
|
||||
}
|
||||
|
||||
coWebsite.state.set("loading");
|
||||
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const onloadPromise = new Promise<void>((resolve) => {
|
||||
coWebsite.iframe.onload = () => {
|
||||
coWebsite.state.set("ready");
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
const onTimeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
|
||||
this.cowebsiteBufferDom.appendChild(coWebsite.iframe);
|
||||
|
||||
if (coWebsite.jitsi) {
|
||||
const gameScene = gameManager.getCurrentGameScene();
|
||||
gameScene.disableMediaBehaviors();
|
||||
jitsiFactory.restart();
|
||||
}
|
||||
|
||||
this.currentOperationPromise = this.currentOperationPromise
|
||||
.then(() => Promise.race([onloadPromise, onTimeoutPromise]))
|
||||
.then(() => {
|
||||
if (mainCoWebsite && mainCoWebsite.iframe.id === coWebsite.iframe.id) {
|
||||
const coWebsiteLloading = coWebsite
|
||||
.load()
|
||||
.then(() => {
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
const highlightedEmbed = get(highlightedEmbedScreen);
|
||||
if (mainCoWebsite) {
|
||||
if (mainCoWebsite.getId() === coWebsite.getId()) {
|
||||
this.openMain();
|
||||
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
}, animationTime);
|
||||
} else if (!highlightedEmbed) {
|
||||
highlightedEmbedScreen.toggleHighlight({
|
||||
type: "cowebsite",
|
||||
embed: coWebsite,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.resizeAllIframes();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error on co-website loading => ", err);
|
||||
this.removeCoWebsiteFromStack(coWebsite);
|
||||
});
|
||||
|
||||
return resolve(coWebsite);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error on co-website loading => ", err);
|
||||
this.removeCoWebsiteFromStack(coWebsite);
|
||||
return reject();
|
||||
});
|
||||
});
|
||||
return coWebsiteLloading;
|
||||
}
|
||||
|
||||
public unloadCoWebsite(coWebsite: CoWebsite): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.removeHighlightCoWebsite(coWebsite);
|
||||
this.removeHighlightCoWebsite(coWebsite);
|
||||
|
||||
coWebsite.iframe.parentNode?.removeChild(coWebsite.iframe);
|
||||
coWebsite.state.set("asleep");
|
||||
coWebsites.remove(coWebsite);
|
||||
return coWebsite
|
||||
.unload()
|
||||
.then(() => {
|
||||
coWebsites.remove(coWebsite);
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
|
||||
if (coWebsite.jitsi) {
|
||||
jitsiFactory.stop();
|
||||
const gameScene = gameManager.getCurrentGameScene();
|
||||
gameScene.enableMediaBehaviors();
|
||||
}
|
||||
if (mainCoWebsite) {
|
||||
this.removeHighlightCoWebsite(mainCoWebsite);
|
||||
this.goToMain(mainCoWebsite);
|
||||
this.resizeAllIframes();
|
||||
} else {
|
||||
this.closeMain();
|
||||
}
|
||||
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
coWebsites.add(coWebsite, get(coWebsites).length);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error();
|
||||
});
|
||||
}
|
||||
|
||||
if (mainCoWebsite) {
|
||||
this.removeHighlightCoWebsite(mainCoWebsite);
|
||||
this.goToMain(mainCoWebsite);
|
||||
this.resizeAllIframes();
|
||||
} else {
|
||||
this.closeMain();
|
||||
}
|
||||
public closeCoWebsite(coWebsite: CoWebsite): void {
|
||||
if (get(coWebsites).length === 1) {
|
||||
this.fire();
|
||||
}
|
||||
|
||||
coWebsites.add(coWebsite, get(coWebsites).length);
|
||||
this.removeCoWebsiteFromStack(coWebsite);
|
||||
|
||||
resolve();
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
|
||||
if (mainCoWebsite) {
|
||||
this.removeHighlightCoWebsite(mainCoWebsite);
|
||||
this.goToMain(mainCoWebsite);
|
||||
this.resizeAllIframes();
|
||||
} else {
|
||||
this.closeMain();
|
||||
}
|
||||
}
|
||||
|
||||
public closeCoWebsites(): void {
|
||||
get(coWebsites).forEach((coWebsite: CoWebsite) => {
|
||||
this.closeCoWebsite(coWebsite);
|
||||
});
|
||||
}
|
||||
|
||||
public closeCoWebsite(coWebsite: CoWebsite): Promise<void> {
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
if (coWebsite.jitsi) {
|
||||
jitsiFactory.destroy();
|
||||
const gameScene = gameManager.getCurrentGameScene();
|
||||
gameScene.enableMediaBehaviors();
|
||||
}
|
||||
|
||||
if (get(coWebsites).length === 1) {
|
||||
this.fire();
|
||||
}
|
||||
|
||||
if (coWebsite.allowApi) {
|
||||
iframeListener.unregisterIframe(coWebsite.iframe);
|
||||
}
|
||||
|
||||
this.removeCoWebsiteFromStack(coWebsite);
|
||||
|
||||
const mainCoWebsite = this.getMainCoWebsite();
|
||||
|
||||
if (mainCoWebsite) {
|
||||
this.removeHighlightCoWebsite(mainCoWebsite);
|
||||
this.goToMain(mainCoWebsite);
|
||||
this.resizeAllIframes();
|
||||
} else {
|
||||
this.closeMain();
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
);
|
||||
return this.currentOperationPromise;
|
||||
}
|
||||
|
||||
public closeCoWebsites(): Promise<void> {
|
||||
return (this.currentOperationPromise = this.currentOperationPromise.then(() => {
|
||||
get(coWebsites).forEach((coWebsite: CoWebsite) => {
|
||||
this.closeCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during closing a co-website");
|
||||
});
|
||||
});
|
||||
}));
|
||||
return this.currentOperationPromise;
|
||||
}
|
||||
|
||||
public getGameSize(): { width: number; height: number } {
|
||||
if (this.openedMain === iframeStates.closed) {
|
||||
if (this.getMainState() === iframeStates.closed) {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { JITSI_URL } from "../Enum/EnvironmentVariable";
|
||||
import { CoWebsite, coWebsiteManager } from "./CoWebsiteManager";
|
||||
import { coWebsiteManager } from "./CoWebsiteManager";
|
||||
import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
|
||||
import { get } from "svelte/store";
|
||||
import CancelablePromise from "cancelable-promise";
|
||||
|
||||
interface jitsiConfigInterface {
|
||||
startWithAudioMuted: boolean;
|
||||
@@ -134,122 +135,96 @@ class JitsiFactory {
|
||||
return slugify(instance.replace("/", "-") + "-" + roomName);
|
||||
}
|
||||
|
||||
public async start(
|
||||
public start(
|
||||
roomName: string,
|
||||
playerName: string,
|
||||
jwt?: string,
|
||||
config?: object,
|
||||
interfaceConfig?: object,
|
||||
jitsiUrl?: string
|
||||
) {
|
||||
const coWebsite = coWebsiteManager.searchJitsi();
|
||||
): CancelablePromise<HTMLIFrameElement> {
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
// after some time the URLs get so huge that loading the iframe
|
||||
// becomes slow and eventually breaks completely. Thus lets just
|
||||
// clear jitsi local storage before starting a new conference.
|
||||
window.localStorage.removeItem("jitsiLocalStorage");
|
||||
|
||||
if (coWebsite) {
|
||||
await coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
}
|
||||
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
// after some time the URLs get so huge that loading the iframe
|
||||
// becomes slow and eventually breaks completely. Thus lets just
|
||||
// clear jitsi local storage before starting a new conference.
|
||||
window.localStorage.removeItem("jitsiLocalStorage");
|
||||
|
||||
const domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
|
||||
}
|
||||
await this.loadJitsiScript(domain);
|
||||
|
||||
const options: JitsiOptions = {
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: coWebsiteManager.getCoWebsiteBuffer(),
|
||||
configOverwrite: mergeConfig(config),
|
||||
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
|
||||
};
|
||||
|
||||
if (!options.jwt) {
|
||||
delete options.jwt;
|
||||
}
|
||||
|
||||
const doResolve = (): void => {
|
||||
const iframe = coWebsiteManager.getCoWebsiteBuffer().querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
|
||||
if (iframe && this.jitsiApi) {
|
||||
const coWebsite = coWebsiteManager.addCoWebsiteFromIframe(
|
||||
iframe,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
0,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
this.jitsiApi.addListener("videoConferenceLeft", () => {
|
||||
this.closeOrUnload(coWebsite);
|
||||
});
|
||||
|
||||
this.jitsiApi.addListener("readyToClose", () => {
|
||||
this.closeOrUnload(coWebsite);
|
||||
});
|
||||
const domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error("Missing JITSI_URL environment variable or jitsiUrl parameter in the map.");
|
||||
}
|
||||
|
||||
coWebsiteManager.resizeAllIframes();
|
||||
};
|
||||
const loadScript = this.loadJitsiScript(domain).then(() => {
|
||||
const options: JitsiOptions = {
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: coWebsiteManager.getCoWebsiteBuffer(),
|
||||
configOverwrite: mergeConfig(config),
|
||||
interfaceConfigOverwrite: { ...defaultInterfaceConfig, ...interfaceConfig },
|
||||
};
|
||||
|
||||
this.jitsiApi = undefined;
|
||||
if (!options.jwt) {
|
||||
delete options.jwt;
|
||||
}
|
||||
|
||||
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
|
||||
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
|
||||
this.jitsiApi.executeCommand("displayName", playerName);
|
||||
const timemout = setTimeout(() => doResolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
|
||||
|
||||
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
|
||||
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
|
||||
const doResolve = (): void => {
|
||||
clearTimeout(timemout);
|
||||
const iframe = coWebsiteManager
|
||||
.getCoWebsiteBuffer()
|
||||
.querySelector<HTMLIFrameElement>('[id*="jitsi" i]');
|
||||
|
||||
if (iframe && this.jitsiApi) {
|
||||
this.jitsiApi.addListener("videoConferenceLeft", () => {
|
||||
this.closeOrUnload(iframe);
|
||||
});
|
||||
|
||||
this.jitsiApi.addListener("readyToClose", () => {
|
||||
this.closeOrUnload(iframe);
|
||||
});
|
||||
|
||||
return resolve(iframe);
|
||||
}
|
||||
};
|
||||
|
||||
this.jitsiApi = undefined;
|
||||
|
||||
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
|
||||
this.jitsiApi.executeCommand("displayName", playerName);
|
||||
|
||||
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
|
||||
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
|
||||
});
|
||||
|
||||
cancel(() => {
|
||||
loadScript.cancel();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private closeOrUnload = function (coWebsite: CoWebsite) {
|
||||
if (coWebsite.closable) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during closing a Jitsi Meet");
|
||||
});
|
||||
private closeOrUnload = function (iframe: HTMLIFrameElement) {
|
||||
const coWebsite = coWebsiteManager.getCoWebsites().find((coWebsite) => coWebsite.getIframe() === iframe);
|
||||
|
||||
if (!coWebsite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (coWebsite.isClosable()) {
|
||||
coWebsiteManager.closeCoWebsite(coWebsite);
|
||||
} else {
|
||||
coWebsiteManager.unloadCoWebsite(coWebsite).catch(() => {
|
||||
console.error("Error during unloading a Jitsi Meet");
|
||||
coWebsiteManager.unloadCoWebsite(coWebsite).catch((err) => {
|
||||
console.error("Cannot unload co-website from the Jitsi factory", err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public restart() {
|
||||
if (!this.jitsiApi) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
|
||||
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
|
||||
|
||||
const coWebsite = coWebsiteManager.searchJitsi();
|
||||
console.log("jitsi api ", this.jitsiApi);
|
||||
console.log("iframe cowebsite", coWebsite?.iframe);
|
||||
|
||||
if (!coWebsite) {
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.jitsiApi.addListener("videoConferenceLeft", () => {
|
||||
this.closeOrUnload(coWebsite);
|
||||
});
|
||||
|
||||
this.jitsiApi.addListener("readyToClose", () => {
|
||||
this.closeOrUnload(coWebsite);
|
||||
});
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (!this.jitsiApi) {
|
||||
return;
|
||||
@@ -284,8 +259,8 @@ class JitsiFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadJitsiScript(domain: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
private loadJitsiScript(domain: string): CancelablePromise<void> {
|
||||
return new CancelablePromise<void>((resolve, reject, cancel) => {
|
||||
if (this.jitsiScriptLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
@@ -304,6 +279,10 @@ class JitsiFactory {
|
||||
};
|
||||
|
||||
document.head.appendChild(jitsiScript);
|
||||
|
||||
cancel(() => {
|
||||
jitsiScript.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type * as SimplePeerNamespace from "simple-peer";
|
||||
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||
import { MESSAGE_TYPE_CONSTRAINT, PeerStatus } from "./VideoPeer";
|
||||
import type { UserSimplePeerInterface } from "./SimplePeer";
|
||||
@@ -6,8 +5,8 @@ import { Readable, readable, writable, Writable } from "svelte/store";
|
||||
import { getIceServersConfig } from "../Components/Video/utils";
|
||||
import { highlightedEmbedScreen } from "../Stores/EmbedScreensStore";
|
||||
import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
|
||||
import Peer from "simple-peer/simplepeer.min.js";
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
/**
|
||||
* A peer connection used to transmit video / audio signals between 2 peers.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type * as SimplePeerNamespace from "simple-peer";
|
||||
import { mediaManager } from "./MediaManager";
|
||||
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||
import { blackListManager } from "./BlackListManager";
|
||||
@@ -10,8 +9,11 @@ import { playersStore } from "../Stores/PlayersStore";
|
||||
import { chatMessagesStore, newChatMessageSubject } from "../Stores/ChatStore";
|
||||
import { getIceServersConfig } from "../Components/Video/utils";
|
||||
import { isMediaBreakpointUp } from "../Utils/BreakpointsUtils";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
|
||||
import { SoundMeter } from "../Phaser/Components/SoundMeter";
|
||||
import { AudioContext } from "standardized-audio-context";
|
||||
import { Console } from "console";
|
||||
import Peer from "simple-peer/simplepeer.min.js";
|
||||
import { Buffer } from "buffer";
|
||||
|
||||
export type PeerStatus = "connecting" | "connected" | "error" | "closed";
|
||||
|
||||
@@ -33,6 +35,7 @@ export class VideoPeer extends Peer {
|
||||
private onBlockSubscribe: Subscription;
|
||||
private onUnBlockSubscribe: Subscription;
|
||||
public readonly streamStore: Readable<MediaStream | null>;
|
||||
public readonly volumeStore: Readable<number | undefined>;
|
||||
public readonly statusStore: Readable<PeerStatus>;
|
||||
public readonly constraintsStore: Readable<ObtainedMediaStreamConstraints | null>;
|
||||
private newMessageSubscribtion: Subscription | undefined;
|
||||
@@ -69,6 +72,34 @@ export class VideoPeer extends Peer {
|
||||
};
|
||||
});
|
||||
|
||||
this.volumeStore = readable<number | undefined>(undefined, (set) => {
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const unsubscribe = this.streamStore.subscribe((mediaStream) => {
|
||||
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
|
||||
set(undefined);
|
||||
return;
|
||||
}
|
||||
const soundMeter = new SoundMeter(mediaStream);
|
||||
let error = false;
|
||||
|
||||
timeout = setInterval(() => {
|
||||
try {
|
||||
set(soundMeter.getVolume());
|
||||
} catch (err) {
|
||||
if (!error) {
|
||||
console.error(err);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
clearInterval(timeout);
|
||||
};
|
||||
});
|
||||
|
||||
this.constraintsStore = readable<ObtainedMediaStreamConstraints | null>(null, (set) => {
|
||||
const onData = (chunk: Buffer) => {
|
||||
const message = JSON.parse(chunk.toString("utf8"));
|
||||
|
||||
@@ -12,9 +12,10 @@ import menu from "./menu";
|
||||
import report from "./report";
|
||||
import warning from "./warning";
|
||||
import woka from "./woka";
|
||||
import trigger from "./trigger";
|
||||
|
||||
const de_DE: Translation = {
|
||||
...en_US,
|
||||
...(en_US as Translation),
|
||||
language: "Deutsch",
|
||||
country: "Deutschland",
|
||||
audio,
|
||||
@@ -29,6 +30,7 @@ const de_DE: Translation = {
|
||||
report,
|
||||
warning,
|
||||
emoji,
|
||||
trigger,
|
||||
};
|
||||
|
||||
export default de_DE;
|
||||
|
||||
@@ -70,6 +70,7 @@ const menu: NonNullable<Translation["menu"]> = {
|
||||
description: "Link zu diesem Raum teilen!",
|
||||
copy: "Kopieren",
|
||||
share: "Teilen",
|
||||
walk_automatically_to_position: "Automatisch zu meiner Position gehen",
|
||||
},
|
||||
globalMessage: {
|
||||
text: "Text",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Translation } from "../i18n-types";
|
||||
|
||||
const trigger: NonNullable<Translation["trigger"]> = {
|
||||
cowebsite: "Drücke LEERTASTE oder tippe hier um die Webseite zu öffnen",
|
||||
newTab: "Drücke LEERTASTE oder tippe hier um die Webseite in einem neuen Tab zu öffnen",
|
||||
jitsiRoom: "Drücke LEERTASTE oder tippe hier um dem Jitsi Meet Raum beizutreten",
|
||||
};
|
||||
|
||||
export default trigger;
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Translation } from "../i18n-types";
|
||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
const upgradeLink = ADMIN_URL + "/pricing";
|
||||
|
||||
const warning: NonNullable<Translation["warning"]> = {
|
||||
title: "Warnung!",
|
||||
content:
|
||||
'Diese Welt erreicht bald die maximale Kapazität. Du kannst die Kapazität <a href={upgradeLink} target="_blank">hier</a> erhöhen',
|
||||
content: `Diese Welt erreicht bald die maximale Kapazität. Du kannst die Kapazität <a href="${upgradeLink}" target="_blank">hier</a> erhöhen`,
|
||||
limit: "Diese Welt erreicht bald die maximale Kapazität!",
|
||||
accessDenied: {
|
||||
camera: "Zugriff auf die Kamera verweigert. Hier klicken um deine Browser Berechtigungen zu prüfen.",
|
||||
|
||||
@@ -15,6 +15,9 @@ const woka: NonNullable<Translation["woka"]> = {
|
||||
continue: "Auswählen",
|
||||
customize: "Bearbeite dein WOKA",
|
||||
},
|
||||
menu: {
|
||||
businessCard: "Visitenkarte",
|
||||
},
|
||||
};
|
||||
|
||||
export default woka;
|
||||
|
||||
@@ -11,6 +11,7 @@ import menu from "./menu";
|
||||
import report from "./report";
|
||||
import warning from "./warning";
|
||||
import emoji from "./emoji";
|
||||
import trigger from "./trigger";
|
||||
|
||||
const en_US: BaseTranslation = {
|
||||
language: "English",
|
||||
@@ -27,6 +28,7 @@ const en_US: BaseTranslation = {
|
||||
report,
|
||||
warning,
|
||||
emoji,
|
||||
trigger,
|
||||
};
|
||||
|
||||
export default en_US;
|
||||
|
||||
@@ -70,6 +70,7 @@ const menu: BaseTranslation = {
|
||||
description: "Share the link of the room!",
|
||||
copy: "Copy",
|
||||
share: "Share",
|
||||
walk_automatically_to_position: "Walk automatically to my position",
|
||||
},
|
||||
globalMessage: {
|
||||
text: "Text",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { BaseTranslation } from "../i18n-types";
|
||||
|
||||
const trigger: BaseTranslation = {
|
||||
cowebsite: "Press SPACE or touch here to open web site",
|
||||
jitsiRoom: "Press SPACE or touch here to enter Jitsi Meet room",
|
||||
newTab: "Press SPACE or touch here to open web site in new tab",
|
||||
};
|
||||
|
||||
export default trigger;
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { BaseTranslation } from "../i18n-types";
|
||||
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
|
||||
|
||||
const upgradeLink = ADMIN_URL + "/pricing";
|
||||
|
||||
const warning: BaseTranslation = {
|
||||
title: "Warning!",
|
||||
content:
|
||||
'This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank">here</a>',
|
||||
content: `This world is close to its limit!. You can upgrade its capacity <a href="${upgradeLink}" target="_blank">here</a>`,
|
||||
limit: "This world is close to its limit!",
|
||||
accessDenied: {
|
||||
camera: "Camera access denied. Click here and check your browser permissions.",
|
||||
|
||||
@@ -15,6 +15,9 @@ const woka: BaseTranslation = {
|
||||
continue: "Continue",
|
||||
customize: "Customize your WOKA",
|
||||
},
|
||||
menu: {
|
||||
businessCard: "Business Card",
|
||||
},
|
||||
};
|
||||
|
||||
export default woka;
|
||||
|
||||
@@ -12,9 +12,10 @@ import menu from "./menu";
|
||||
import report from "./report";
|
||||
import warning from "./warning";
|
||||
import woka from "./woka";
|
||||
import trigger from "./trigger";
|
||||
|
||||
const fr_FR: Translation = {
|
||||
...en_US,
|
||||
...(en_US as Translation),
|
||||
language: "Français",
|
||||
country: "France",
|
||||
audio,
|
||||
@@ -29,6 +30,7 @@ const fr_FR: Translation = {
|
||||
report,
|
||||
warning,
|
||||
emoji,
|
||||
trigger,
|
||||
};
|
||||
|
||||
export default fr_FR;
|
||||
|
||||
@@ -63,13 +63,14 @@ const menu: NonNullable<Translation["menu"]> = {
|
||||
},
|
||||
fullscreen: "Plein écran",
|
||||
notifications: "Notifications",
|
||||
cowebsiteTrigger: "Demander toujours avant d'ouvrir des sites web et des salles de réunion Jitsi",
|
||||
cowebsiteTrigger: "Demander toujours avant d'ouvrir des sites web et des salles de conférence Jitsi",
|
||||
ignoreFollowRequest: "Ignorer les demandes de suivi des autres utilisateurs",
|
||||
},
|
||||
invite: {
|
||||
description: "Partager le lien de la salle!",
|
||||
copy: "Copier",
|
||||
share: "Partager",
|
||||
walk_automatically_to_position: "Marcher automatiquement jusqu'à ma position",
|
||||
},
|
||||
globalMessage: {
|
||||
text: "Texte",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { Translation } from "../i18n-types";
|
||||
|
||||
const trigger: NonNullable<Translation["trigger"]> = {
|
||||
cowebsite: "Appuyez sur ESPACE ou ici pour ouvrir le site Web",
|
||||
jitsiRoom: "Appuyez sur ESPACE ou ici pour entrer dans la salle conférence Jitsi",
|
||||
newTab: "Appuyez sur ESPACE ou ici pour ouvrir le site Web dans un nouvel onglet",
|
||||
};
|
||||
|
||||
export default trigger;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user