Merge branch 'develop' of github.com:thecodingmachine/workadventure into main
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import type { GameScene } from "./GameScene";
|
||||
import { iframeListener } from "../../Api/IframeListener";
|
||||
import type { Subscription } from "rxjs";
|
||||
import type { CreateEmbeddedWebsiteEvent, ModifyEmbeddedWebsiteEvent } from "../../Api/Events/EmbeddedWebsiteEvent";
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
|
||||
type EmbeddedWebsite = CreateEmbeddedWebsiteEvent & { iframe: HTMLIFrameElement; phaserObject: DOMElement };
|
||||
|
||||
export class EmbeddedWebsiteManager {
|
||||
private readonly embeddedWebsites = new Map<string, EmbeddedWebsite>();
|
||||
private readonly subscription: Subscription;
|
||||
|
||||
constructor(private gameScene: GameScene) {
|
||||
iframeListener.registerAnswerer("getEmbeddedWebsite", (name: string) => {
|
||||
const website = this.embeddedWebsites.get(name);
|
||||
if (website === undefined) {
|
||||
throw new Error('Cannot find embedded website with name "' + name + '"');
|
||||
}
|
||||
const rect = website.iframe.getBoundingClientRect();
|
||||
return {
|
||||
url: website.url,
|
||||
name: website.name,
|
||||
visible: website.visible,
|
||||
allowApi: website.allowApi,
|
||||
allow: website.allow,
|
||||
position: {
|
||||
x: website.phaserObject.x,
|
||||
y: website.phaserObject.y,
|
||||
width: rect["width"],
|
||||
height: rect["height"],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("deleteEmbeddedWebsite", (name: string) => {
|
||||
const website = this.embeddedWebsites.get(name);
|
||||
if (!website) {
|
||||
throw new Error('Could not find website to delete with the name "' + name + '" in your map');
|
||||
}
|
||||
|
||||
website.iframe.remove();
|
||||
website.phaserObject.destroy();
|
||||
this.embeddedWebsites.delete(name);
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer(
|
||||
"createEmbeddedWebsite",
|
||||
(createEmbeddedWebsiteEvent: CreateEmbeddedWebsiteEvent) => {
|
||||
if (this.embeddedWebsites.has(createEmbeddedWebsiteEvent.name)) {
|
||||
throw new Error('An embedded website with the name "' + name + '" already exists in your map');
|
||||
}
|
||||
|
||||
this.createEmbeddedWebsite(
|
||||
createEmbeddedWebsiteEvent.name,
|
||||
createEmbeddedWebsiteEvent.url,
|
||||
createEmbeddedWebsiteEvent.position.x,
|
||||
createEmbeddedWebsiteEvent.position.y,
|
||||
createEmbeddedWebsiteEvent.position.width,
|
||||
createEmbeddedWebsiteEvent.position.height,
|
||||
createEmbeddedWebsiteEvent.visible ?? true,
|
||||
createEmbeddedWebsiteEvent.allowApi ?? false,
|
||||
createEmbeddedWebsiteEvent.allow ?? ""
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
this.subscription = iframeListener.modifyEmbeddedWebsiteStream.subscribe(
|
||||
(embeddedWebsiteEvent: ModifyEmbeddedWebsiteEvent) => {
|
||||
const website = this.embeddedWebsites.get(embeddedWebsiteEvent.name);
|
||||
if (!website) {
|
||||
throw new Error(
|
||||
'Could not find website with the name "' + embeddedWebsiteEvent.name + '" in your map'
|
||||
);
|
||||
}
|
||||
|
||||
gameScene.markDirty();
|
||||
|
||||
if (embeddedWebsiteEvent.url !== undefined) {
|
||||
website.url = embeddedWebsiteEvent.url;
|
||||
const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString();
|
||||
website.iframe.src = absoluteUrl;
|
||||
}
|
||||
|
||||
if (embeddedWebsiteEvent.visible !== undefined) {
|
||||
website.visible = embeddedWebsiteEvent.visible;
|
||||
website.phaserObject.visible = embeddedWebsiteEvent.visible;
|
||||
}
|
||||
|
||||
if (embeddedWebsiteEvent.allowApi !== undefined) {
|
||||
website.allowApi = embeddedWebsiteEvent.allowApi;
|
||||
if (embeddedWebsiteEvent.allowApi) {
|
||||
iframeListener.registerIframe(website.iframe);
|
||||
} else {
|
||||
iframeListener.unregisterIframe(website.iframe);
|
||||
}
|
||||
}
|
||||
|
||||
if (embeddedWebsiteEvent.allow !== undefined) {
|
||||
website.allow = embeddedWebsiteEvent.allow;
|
||||
website.iframe.allow = embeddedWebsiteEvent.allow;
|
||||
}
|
||||
|
||||
if (embeddedWebsiteEvent?.x !== undefined) {
|
||||
website.phaserObject.x = embeddedWebsiteEvent.x;
|
||||
}
|
||||
if (embeddedWebsiteEvent?.y !== undefined) {
|
||||
website.phaserObject.y = embeddedWebsiteEvent.y;
|
||||
}
|
||||
if (embeddedWebsiteEvent?.width !== undefined) {
|
||||
website.iframe.style.width = embeddedWebsiteEvent.width + "px";
|
||||
}
|
||||
if (embeddedWebsiteEvent?.height !== undefined) {
|
||||
website.iframe.style.height = embeddedWebsiteEvent.height + "px";
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public createEmbeddedWebsite(
|
||||
name: string,
|
||||
url: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
visible: boolean,
|
||||
allowApi: boolean,
|
||||
allow: string
|
||||
): void {
|
||||
if (this.embeddedWebsites.has(name)) {
|
||||
throw new Error('An embedded website with the name "' + name + '" already exists in your map');
|
||||
}
|
||||
|
||||
const embeddedWebsiteEvent: CreateEmbeddedWebsiteEvent = {
|
||||
name,
|
||||
url,
|
||||
/*x,
|
||||
y,
|
||||
width,
|
||||
height,*/
|
||||
allow,
|
||||
allowApi,
|
||||
visible,
|
||||
position: {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
};
|
||||
|
||||
const embeddedWebsite = this.doCreateEmbeddedWebsite(embeddedWebsiteEvent, visible);
|
||||
|
||||
this.embeddedWebsites.set(name, embeddedWebsite);
|
||||
}
|
||||
|
||||
private doCreateEmbeddedWebsite(
|
||||
embeddedWebsiteEvent: CreateEmbeddedWebsiteEvent,
|
||||
visible: boolean
|
||||
): EmbeddedWebsite {
|
||||
const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString();
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.src = absoluteUrl;
|
||||
iframe.style.width = embeddedWebsiteEvent.position.width + "px";
|
||||
iframe.style.height = embeddedWebsiteEvent.position.height + "px";
|
||||
iframe.style.margin = "0";
|
||||
iframe.style.padding = "0";
|
||||
iframe.style.border = "none";
|
||||
|
||||
const embeddedWebsite = {
|
||||
...embeddedWebsiteEvent,
|
||||
phaserObject: this.gameScene.add
|
||||
.dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe)
|
||||
.setVisible(visible)
|
||||
.setOrigin(0, 0),
|
||||
iframe: iframe,
|
||||
};
|
||||
if (embeddedWebsiteEvent.allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
|
||||
return embeddedWebsite;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
for (const [key, website] of this.embeddedWebsites) {
|
||||
if (website.allowApi) {
|
||||
iframeListener.unregisterIframe(website.iframe);
|
||||
}
|
||||
}
|
||||
|
||||
this.subscription.unsubscribe();
|
||||
iframeListener.unregisterAnswerer("getEmbeddedWebsite");
|
||||
iframeListener.unregisterAnswerer("deleteEmbeddedWebsite");
|
||||
iframeListener.unregisterAnswerer("createEmbeddedWebsite");
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
AUDIO_VOLUME_PROPERTY,
|
||||
Box,
|
||||
JITSI_MESSAGE_PROPERTIES,
|
||||
layoutManager,
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
TRIGGER_JITSI_PROPERTIES,
|
||||
TRIGGER_WEBSITE_PROPERTIES,
|
||||
@@ -85,8 +84,12 @@ import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStor
|
||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
import { playersStore } from "../../Stores/PlayersStore";
|
||||
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
||||
import { PropertyUtils } from "../Map/PropertyUtils";
|
||||
import Tileset = Phaser.Tilemaps.Tileset;
|
||||
import { userIsAdminStore } from "../../Stores/GameStore";
|
||||
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
||||
import { get } from "svelte/store";
|
||||
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
@@ -197,6 +200,8 @@ export class GameScene extends DirtyScene {
|
||||
private preloading: boolean = true;
|
||||
private startPositionCalculator!: StartPositionCalculator;
|
||||
private sharedVariablesManager!: SharedVariablesManager;
|
||||
private objectsByType = new Map<string, ITiledMapObject[]>();
|
||||
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||
super({
|
||||
@@ -336,27 +341,27 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
|
||||
// Scan the object layers for objects to load and load them.
|
||||
const objects = new Map<string, ITiledMapObject[]>();
|
||||
this.objectsByType = new Map<string, ITiledMapObject[]>();
|
||||
|
||||
for (const layer of this.mapFile.layers) {
|
||||
if (layer.type === "objectgroup") {
|
||||
for (const object of layer.objects) {
|
||||
let objectsOfType: ITiledMapObject[] | undefined;
|
||||
if (!objects.has(object.type)) {
|
||||
if (!this.objectsByType.has(object.type)) {
|
||||
objectsOfType = new Array<ITiledMapObject>();
|
||||
} else {
|
||||
objectsOfType = objects.get(object.type);
|
||||
objectsOfType = this.objectsByType.get(object.type);
|
||||
if (objectsOfType === undefined) {
|
||||
throw new Error("Unexpected object type not found");
|
||||
}
|
||||
}
|
||||
objectsOfType.push(object);
|
||||
objects.set(object.type, objectsOfType);
|
||||
this.objectsByType.set(object.type, objectsOfType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [itemType, objectsOfType] of objects) {
|
||||
for (const [itemType, objectsOfType] of this.objectsByType) {
|
||||
// FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin.
|
||||
|
||||
let itemFactory: ItemFactoryInterface;
|
||||
@@ -456,6 +461,8 @@ export class GameScene extends DirtyScene {
|
||||
//permit to set bound collision
|
||||
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||
|
||||
this.embeddedWebsiteManager = new EmbeddedWebsiteManager(this);
|
||||
|
||||
//add layer on map
|
||||
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
||||
for (const layer of this.gameMap.flatLayers) {
|
||||
@@ -476,6 +483,28 @@ export class GameScene extends DirtyScene {
|
||||
if (object.text) {
|
||||
TextUtils.createTextFromITiledMapObject(this, object);
|
||||
}
|
||||
if (object.type === "website") {
|
||||
// Let's load iframes in the map
|
||||
const url = PropertyUtils.mustFindStringProperty(
|
||||
"url",
|
||||
object.properties,
|
||||
'in the "' + object.name + '" object of type "website"'
|
||||
);
|
||||
const allowApi = PropertyUtils.findBooleanProperty("allowApi", object.properties);
|
||||
|
||||
// TODO: add a "allow" property to iframe
|
||||
this.embeddedWebsiteManager.createEmbeddedWebsite(
|
||||
object.name,
|
||||
url,
|
||||
object.x,
|
||||
object.y,
|
||||
object.width,
|
||||
object.height,
|
||||
object.visible,
|
||||
allowApi ?? false,
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -791,7 +820,7 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManager.removeActionButton("openWebsite", this.userInputManager);
|
||||
layoutManagerActionStore.removeAction("openWebsite");
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
} else {
|
||||
const openWebsiteFunction = () => {
|
||||
@@ -801,7 +830,7 @@ export class GameScene extends DirtyScene {
|
||||
allProps.get("openWebsiteAllowApi") as boolean | undefined,
|
||||
allProps.get("openWebsitePolicy") as string | undefined
|
||||
);
|
||||
layoutManager.removeActionButton("openWebsite", this.userInputManager);
|
||||
layoutManagerActionStore.removeAction("openWebsite");
|
||||
};
|
||||
|
||||
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
|
||||
@@ -810,14 +839,13 @@ export class GameScene extends DirtyScene {
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to open web site";
|
||||
}
|
||||
layoutManager.addActionButton(
|
||||
"openWebsite",
|
||||
message.toString(),
|
||||
() => {
|
||||
openWebsiteFunction();
|
||||
},
|
||||
this.userInputManager
|
||||
);
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "openWebsite",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openWebsiteFunction(),
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openWebsiteFunction();
|
||||
}
|
||||
@@ -825,7 +853,7 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => {
|
||||
if (newValue === undefined) {
|
||||
layoutManager.removeActionButton("jitsiRoom", this.userInputManager);
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
this.stopJitsi();
|
||||
} else {
|
||||
const openJitsiRoomFunction = () => {
|
||||
@@ -838,7 +866,7 @@ export class GameScene extends DirtyScene {
|
||||
} else {
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
layoutManager.removeActionButton("jitsiRoom", this.userInputManager);
|
||||
layoutManagerActionStore.removeAction("jitsi");
|
||||
};
|
||||
|
||||
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
|
||||
@@ -847,14 +875,13 @@ export class GameScene extends DirtyScene {
|
||||
if (message === undefined) {
|
||||
message = "Press SPACE or touch here to enter Jitsi Meet room";
|
||||
}
|
||||
layoutManager.addActionButton(
|
||||
"jitsiRoom",
|
||||
message.toString(),
|
||||
() => {
|
||||
openJitsiRoomFunction();
|
||||
},
|
||||
this.userInputManager
|
||||
);
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: "jitsi",
|
||||
type: "message",
|
||||
message: message,
|
||||
callback: () => openJitsiRoomFunction(),
|
||||
userInputManager: this.userInputManager,
|
||||
});
|
||||
} else {
|
||||
openJitsiRoomFunction();
|
||||
}
|
||||
@@ -1183,6 +1210,44 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("triggerActionMessage", (message) =>
|
||||
layoutManagerActionStore.addAction({
|
||||
uuid: message.uuid,
|
||||
type: "message",
|
||||
message: message.message,
|
||||
callback: () => {
|
||||
layoutManagerActionStore.removeAction(message.uuid);
|
||||
iframeListener.sendActionMessageTriggered(message.uuid);
|
||||
},
|
||||
userInputManager: this.userInputManager,
|
||||
})
|
||||
);
|
||||
|
||||
iframeListener.registerAnswerer("removeActionMessage", (message) => {
|
||||
layoutManagerActionStore.removeAction(message.uuid);
|
||||
});
|
||||
|
||||
this.iframeSubscriptionList.push(
|
||||
iframeListener.modifyEmbeddedWebsiteStream.subscribe((embeddedWebsite) => {
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
// TODO
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private setPropertyLayer(
|
||||
@@ -1250,7 +1315,7 @@ export class GameScene extends DirtyScene {
|
||||
let targetRoom: Room;
|
||||
try {
|
||||
targetRoom = await Room.createRoom(roomUrl);
|
||||
} catch (e) {
|
||||
} catch (e /*: unknown*/) {
|
||||
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
||||
this.mapTransitioning = false;
|
||||
return;
|
||||
@@ -1305,7 +1370,12 @@ export class GameScene extends DirtyScene {
|
||||
this.biggestAvailableAreaStoreUnsubscribe();
|
||||
iframeListener.unregisterAnswerer("getState");
|
||||
iframeListener.unregisterAnswerer("loadTileset");
|
||||
iframeListener.unregisterAnswerer("getMapData");
|
||||
iframeListener.unregisterAnswerer("getState");
|
||||
iframeListener.unregisterAnswerer("triggerActionMessage");
|
||||
iframeListener.unregisterAnswerer("removeActionMessage");
|
||||
this.sharedVariablesManager?.close();
|
||||
this.embeddedWebsiteManager?.close();
|
||||
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
@@ -1377,7 +1447,7 @@ export class GameScene extends DirtyScene {
|
||||
try {
|
||||
const room = await Room.createRoom(exitRoomPath);
|
||||
return gameManager.loadMap(room, this.scene);
|
||||
} catch (e) {
|
||||
} catch (e /*: unknown*/) {
|
||||
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { ITiledMapProperty } from "./ITiledMap";
|
||||
|
||||
export class PropertyUtils {
|
||||
public static findProperty(
|
||||
name: string,
|
||||
properties: ITiledMapProperty[] | undefined
|
||||
): string | boolean | number | undefined {
|
||||
return properties?.find((property) => property.name === name)?.value;
|
||||
}
|
||||
|
||||
public static findBooleanProperty(
|
||||
name: string,
|
||||
properties: ITiledMapProperty[] | undefined,
|
||||
context?: string
|
||||
): boolean | undefined {
|
||||
const property = PropertyUtils.findProperty(name, properties);
|
||||
if (property === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof property !== "boolean") {
|
||||
throw new Error(
|
||||
'Expected property "' + name + '" to be a boolean. ' + (context ? " (" + context + ")" : "")
|
||||
);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
public static mustFindProperty(
|
||||
name: string,
|
||||
properties: ITiledMapProperty[] | undefined,
|
||||
context?: string
|
||||
): string | boolean | number {
|
||||
const property = PropertyUtils.findProperty(name, properties);
|
||||
if (property === undefined) {
|
||||
throw new Error('Could not find property "' + name + '"' + (context ? " (" + context + ")" : ""));
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
public static mustFindStringProperty(
|
||||
name: string,
|
||||
properties: ITiledMapProperty[] | undefined,
|
||||
context?: string
|
||||
): string {
|
||||
const property = PropertyUtils.mustFindProperty(name, properties, context);
|
||||
if (typeof property !== "string") {
|
||||
throw new Error(
|
||||
'Expected property "' + name + '" to be a string. ' + (context ? " (" + context + ")" : "")
|
||||
);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user