Implement new cowbesite system on API

This commit is contained in:
Alexis Faizeau 2021-10-12 10:38:49 +02:00
parent c29c4bfaa4
commit b81b1ff68b
18 changed files with 1089 additions and 84 deletions

View file

@ -111,8 +111,7 @@
</div>
<div id="cowebsite-sub-icons"></div>
</aside>
<main id="cowebsite-slot-0">
</main>
<main id="cowebsite-slot-0"></main>
</div>
<div id="cowebsite-buffer"></div>
</div>

View file

@ -0,0 +1,12 @@
import * as tg from "generic-type-guard";
export const isCloseCoWebsite = new tg.IsInterface()
.withProperties({
id: tg.isOptional(tg.isString)
})
.get();
/**
* A message sent from the iFrame to the game to add a message in the chat.
*/
export type CloseCoWebsiteEvent = tg.GuardedType<typeof isCloseCoWebsite>;

View file

@ -5,11 +5,10 @@ import type { ClosePopupEvent } from "./ClosePopupEvent";
import type { EnterLeaveEvent } from "./EnterLeaveEvent";
import type { GoToPageEvent } from "./GoToPageEvent";
import type { LoadPageEvent } from "./LoadPageEvent";
import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent";
import { coWebsite, isOpenCoWebsiteEvent } from "./OpenCoWebsiteEvent";
import type { OpenPopupEvent } from "./OpenPopupEvent";
import type { OpenTabEvent } from "./OpenTabEvent";
import type { UserInputChatEvent } from "./UserInputChatEvent";
import type { MapDataEvent } from "./MapDataEvent";
import type { LayerEvent } from "./LayerEvent";
import type { SetPropertyEvent } from "./setPropertyEvent";
import type { LoadSoundEvent } from "./LoadSoundEvent";
@ -27,9 +26,6 @@ import type { LoadTilesetEvent } from "./LoadTilesetEvent";
import { isLoadTilesetEvent } from "./LoadTilesetEvent";
import type {
MessageReferenceEvent,
removeActionMessage,
triggerActionMessage,
TriggerActionMessageEvent,
} from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
@ -48,8 +44,6 @@ export type IframeEventMap = {
closePopup: ClosePopupEvent;
openTab: OpenTabEvent;
goToPage: GoToPageEvent;
openCoWebSite: OpenCoWebSiteEvent;
closeCoWebSite: null;
disablePlayerControls: null;
restorePlayerControls: null;
displayBubble: null;
@ -118,6 +112,22 @@ export const iframeQueryMapTypeGuards = {
query: isLoadTilesetEvent,
answer: tg.isNumber,
},
openCoWebsite: {
query: isOpenCoWebsiteEvent,
answer: coWebsite
},
getCoWebsites: {
query: tg.isUndefined,
answer: tg.isArray(coWebsite)
},
closeCoWebsite: {
query: tg.isString,
answer: tg.isUndefined
},
closeCoWebsites: {
query: tg.isUndefined,
answer: tg.isUndefined
},
triggerActionMessage: {
query: isTriggerActionMessageEvent,
answer: tg.isUndefined,

View file

@ -1,14 +0,0 @@
import * as tg from "generic-type-guard";
export const isOpenCoWebsite = new tg.IsInterface()
.withProperties({
url: tg.isString,
allowApi: tg.isBoolean,
allowPolicy: tg.isString,
})
.get();
/**
* A message sent from the iFrame to the game to add a message in the chat.
*/
export type OpenCoWebSiteEvent = tg.GuardedType<typeof isOpenCoWebsite>;

View file

@ -0,0 +1,22 @@
import * as tg from "generic-type-guard";
export const isOpenCoWebsiteEvent = new tg.IsInterface()
.withProperties({
url: tg.isString,
allowApi: tg.isOptional(tg.isBoolean),
allowPolicy: tg.isOptional(tg.isString),
position: tg.isOptional(tg.isNumber)
})
.get();
export const coWebsite = new tg.IsInterface()
.withProperties({
id: tg.isString,
position: tg.isNumber,
})
.get();
/**
* A message sent from the iFrame to the game to add a message in the chat.
*/
export type OpenCoWebsiteEvent = tg.GuardedType<typeof isOpenCoWebsiteEvent>;

View file

@ -1,6 +1,5 @@
import { Subject } from "rxjs";
import type * as tg from "generic-type-guard";
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
import { isChatEvent } from "./Events/ChatEvent";
import { HtmlUtils } from "../WebRtc/HtmlUtils";
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
@ -8,18 +7,15 @@ import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
import { scriptUtils } from "./ScriptUtils";
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
import { isGoToPageEvent } from "./Events/GoToPageEvent";
import { isCloseCoWebsite, CloseCoWebsiteEvent } from "./Events/CloseCoWebsiteEvent";
import {
IframeErrorAnswerEvent,
IframeEvent,
IframeEventMap,
IframeQueryMap,
IframeResponseEvent,
IframeResponseEventMap,
isIframeEventWrapper,
isIframeQueryWrapper,
TypedMessageEvent,
} from "./Events/IframeEvent";
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
import { isPlaySoundEvent, PlaySoundEvent } from "./Events/PlaySoundEvent";
@ -33,7 +29,6 @@ import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegi
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
import type { SetVariableEvent } from "./Events/SetVariableEvent";
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
import { EmbeddedWebsite } from "./iframe/Room/EmbeddedWebsite";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
type AnswererCallback<T extends keyof IframeQueryMap> = (
@ -53,10 +48,7 @@ class IframeListener {
public readonly openTabStream = this._openTabStream.asObservable();
private readonly _loadPageStream: Subject<string> = new Subject();
public readonly loadPageStream = this._loadPageStream.asObservable();
private readonly _openCoWebSiteStream: Subject<OpenCoWebSiteEvent> = new Subject();
public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable();
public readonly loadPageStream = this._loadPageStream.asObservable()
private readonly _disablePlayerControlStream: Subject<void> = new Subject();
public readonly disablePlayerControlStream = this._disablePlayerControlStream.asObservable();
@ -138,8 +130,6 @@ class IframeListener {
return;
}
foundSrc = this.getBaseUrl(foundSrc, message.source);
if (isIframeQueryWrapper(payload)) {
const queryId = payload.id;
const query = payload.query;
@ -224,15 +214,6 @@ class IframeListener {
this._stopSoundStream.next(payload.data);
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
this._loadSoundStream.next(payload.data);
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
scriptUtils.openCoWebsite(
payload.data.url,
foundSrc,
payload.data.allowApi,
payload.data.allowPolicy
);
} else if (payload.type === "closeCoWebSite") {
scriptUtils.closeCoWebSite();
} else if (payload.type === "disablePlayerControls") {
this._disablePlayerControlStream.next();
} else if (payload.type === "restorePlayerControls") {
@ -252,6 +233,9 @@ class IframeListener {
this.iframeCloseCallbacks.get(iframe)?.push(() => {
handleMenuUnregisterEvent(dataName);
});
foundSrc = this.getBaseUrl(foundSrc, message.source);
handleMenuRegistrationEvent(
payload.data.name,
payload.data.iframe,
@ -354,6 +338,20 @@ class IframeListener {
return src;
}
public getBaseUrlFromSource(source: MessageEventSource): string {
let foundSrc: string | undefined;
let iframe: HTMLIFrameElement | undefined;
for (iframe of this.iframes) {
if (iframe.contentWindow === source) {
foundSrc = iframe.src;
break;
}
}
return this.getBaseUrl(foundSrc ?? "", source);
}
private static getIFrameId(scriptUrl: string): string {
return "script" + btoa(scriptUrl);
}

View file

@ -1,4 +1,4 @@
import { coWebsiteManager } from "../WebRtc/CoWebsiteManager";
import { coWebsiteManager, CoWebsite } from "../WebRtc/CoWebsiteManager";
import { playersStore } from "../Stores/PlayersStore";
import { chatMessagesStore } from "../Stores/ChatStore";
import type { ChatEvent } from "./Events/ChatEvent";
@ -12,14 +12,6 @@ class ScriptUtils {
window.location.href = url;
}
public openCoWebsite(url: string, base: string, api: boolean, policy: string) {
coWebsiteManager.loadCoWebsite(url, base, api, policy);
}
public closeCoWebSite() {
coWebsiteManager.closeCoWebsite();
}
public sendAnonymousChat(chatEvent: ChatEvent) {
const userId = playersStore.addFacticePlayer(chatEvent.author);
chatMessagesStore.addExternalMessage(userId, chatEvent.message);

View file

@ -1,8 +1,4 @@
import type { GoToPageEvent } from "../Events/GoToPageEvent";
import type { OpenTabEvent } from "../Events/OpenTabEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import type { OpenCoWebSiteEvent } from "../Events/OpenCoWebSiteEvent";
import type { LoadPageEvent } from "../Events/LoadPageEvent";
import { IframeApiContribution, sendToWorkadventure, queryWorkadventure } from "./IframeApiContribution";
export class WorkadventureNavigationCommands extends IframeApiContribution<WorkadventureNavigationCommands> {
callbacks = [];
@ -34,21 +30,61 @@ export class WorkadventureNavigationCommands extends IframeApiContribution<Worka
});
}
openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void {
sendToWorkadventure({
type: "openCoWebSite",
/**
* @deprecated Use openCoWebsite instead
*/
openCoWebSite(url: string, allowApi?: boolean, allowPolicy?: string) {
return queryWorkadventure({
type: "openCoWebsite",
data: {
url,
allowApi,
allowPolicy,
position: undefined,
},
});
}
closeCoWebSite(): void {
sendToWorkadventure({
type: "closeCoWebSite",
data: null,
openCoWebsite(url: string, allowApi?: boolean, allowPolicy?: string, position?: number) {
return queryWorkadventure({
type: "openCoWebsite",
data: {
url,
allowApi,
allowPolicy,
position,
},
});
}
getCoWebsites() {
return queryWorkadventure({
type: "getCoWebsites",
data: undefined
});
}
/**
* @deprecated Use closeCoWebsites instead to close all co-websites
*/
closeCoWebSite() {
return queryWorkadventure({
type: "closeCoWebsites",
data: undefined,
});
}
closeCoWebsite(coWebsiteId: string) {
return queryWorkadventure({
type: "closeCoWebsite",
data: coWebsiteId,
});
}
closeCoWebsites() {
return queryWorkadventure({
type: "closeCoWebsites",
data: undefined,
});
}
}

View file

@ -25,7 +25,7 @@ import {
TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES,
} from "../../WebRtc/LayoutManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { CoWebsite, coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
import type { RoomConnection } from "../../Connexion/RoomConnection";
@ -95,7 +95,6 @@ import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { GameMapPropertiesListener } from "./GameMapPropertiesListener";
import { analyticsClient } from "../../Administration/AnalyticsClient";
import { get } from "svelte/store";
import type { RadialMenuItem } from "../Components/RadialMenu";
import { contactPageStore } from "../../Stores/MenuStore";
export interface GameSceneInitInterface {
@ -1081,6 +1080,58 @@ ${escapedMessage}
})
);
iframeListener.registerAnswerer("openCoWebsite", async (openCoWebsite, source) => {
if (!source) {
throw new Error("Unknown query source");
}
const coWebsite = await coWebsiteManager.loadCoWebsite(
openCoWebsite.url,
iframeListener.getBaseUrlFromSource(source),
openCoWebsite.allowApi,
openCoWebsite.allowPolicy,
openCoWebsite.position
);
if (!coWebsite) {
throw new Error("Error on opening co-website");
}
return {
id: coWebsite.iframe.id,
position: coWebsite.position,
};
});
iframeListener.registerAnswerer("getCoWebsites", () => {
const coWebsites = coWebsiteManager.getCoWebsites();
return coWebsites.map((coWebsite: CoWebsite) => {
return {
id: coWebsite.iframe.id,
position: coWebsite.position,
};
});
});
iframeListener.registerAnswerer("closeCoWebsite", async (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");
});
});
iframeListener.registerAnswerer("closeCoWebsites", async () => {
return await coWebsiteManager.closeCoWebsites().catch((error) => {
throw new Error("Error on closing all co-websites");
});
});
iframeListener.registerAnswerer("getMapData", () => {
return {
data: this.gameMap.getMap(),
@ -1310,6 +1361,8 @@ ${escapedMessage}
iframeListener.unregisterAnswerer("getMapData");
iframeListener.unregisterAnswerer("triggerActionMessage");
iframeListener.unregisterAnswerer("removeActionMessage");
iframeListener.unregisterAnswerer("openCoWebsite");
iframeListener.unregisterAnswerer("getCoWebsites");
this.sharedVariablesManager?.close();
this.embeddedWebsiteManager?.close();

View file

@ -27,13 +27,13 @@ interface TouchMoveCoordinates {
y: number;
}
export interface CoWebsite {
export type CoWebsite = {
iframe: HTMLIFrameElement,
icon: HTMLDivElement,
position: number
}
interface CoWebsiteSlot {
type CoWebsiteSlot = {
container: HTMLElement,
position: number
}
@ -186,6 +186,7 @@ class CoWebsiteManager {
};
this.cowebsiteAsideDom.addEventListener(touchMode ? "touchstart" : "mousedown", (event) => {
this.cowebsiteMainDom.style.display = "none";
this.resizing = true;
if (touchMode) {
const touchEvent = (event as TouchEvent).touches[0];
@ -203,6 +204,7 @@ class CoWebsiteManager {
document.removeEventListener(touchMode ? "touchmove" : "mousemove", movecallback);
this.cowebsiteMainDom.style.display = "block";
this.resizing = false;
this.cowebsiteMainDom.style.display = "flex";
});
}
@ -277,7 +279,11 @@ class CoWebsiteManager {
});
}
private searchCoWebsiteById(coWebsiteId: string): CoWebsite|undefined {
public getCoWebsites(): CoWebsite[] {
return this.coWebsites;
}
public getCoWebsiteById(coWebsiteId: string): CoWebsite|undefined {
return this.coWebsites.find((coWebsite: CoWebsite) => coWebsite.iframe.id === coWebsiteId);
}
@ -449,7 +455,7 @@ class CoWebsiteManager {
do {
iframe.id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7);
} while (iframe.id.toLowerCase().includes('jitsi') || this.searchCoWebsiteById(iframe.id));
} while (iframe.id.toLowerCase().includes('jitsi') || this.getCoWebsiteById(iframe.id));
iframe.src = new URL(url, base).toString()

View file

@ -31,8 +31,7 @@
aside {
height: 30px;
min-height: 30px;
height: 50px;
cursor: ns-resize;
flex-direction: row-reverse;
align-items: center;
@ -58,8 +57,8 @@
visibility: visible;
img {
height: 20px;
width: 20px;
height: 30px;
width: 30px;
cursor: pointer !important;
border-radius: 50%;
background-color: whitesmoke;

View file

@ -4,7 +4,7 @@
position: fixed;
z-index: 200;
transition: transform 0.5s;
background-color: white;
background-color: whitesmoke;
display: none;
&.loading {