From b81b1ff68bf7c482f34ca31a22306db1235e4fca Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Tue, 12 Oct 2021 10:38:49 +0200 Subject: [PATCH] Implement new cowbesite system on API --- docs/maps/api-nav.md | 29 +- front/dist/index.tmpl.html | 3 +- front/src/Api/Events/CloseCoWebsiteEvent.ts | 12 + front/src/Api/Events/IframeEvent.ts | 24 +- front/src/Api/Events/OpenCoWebSiteEvent.ts | 14 - front/src/Api/Events/OpenCoWebsiteEvent.ts | 22 + front/src/Api/IframeListener.ts | 44 +- front/src/Api/ScriptUtils.ts | 10 +- front/src/Api/iframe/nav.ts | 60 +- front/src/Phaser/Game/GameScene.ts | 57 +- front/src/WebRtc/CoWebsiteManager.ts | 14 +- front/style/cowebsite-mobile.scss | 7 +- front/style/cowebsite.scss | 2 +- maps/tests/CoWebsite/cowebsite_api.json | 629 +++++++++++++++++++ maps/tests/CoWebsite/cowebsite_property.json | 118 ++++ maps/tests/CoWebsite/script.js | 108 ++++ maps/tests/CoWebsite/tileset1.png | Bin 0 -> 29402 bytes maps/tests/index.html | 20 + 18 files changed, 1089 insertions(+), 84 deletions(-) create mode 100644 front/src/Api/Events/CloseCoWebsiteEvent.ts delete mode 100644 front/src/Api/Events/OpenCoWebSiteEvent.ts create mode 100644 front/src/Api/Events/OpenCoWebsiteEvent.ts create mode 100644 maps/tests/CoWebsite/cowebsite_api.json create mode 100644 maps/tests/CoWebsite/cowebsite_property.json create mode 100644 maps/tests/CoWebsite/script.js create mode 100644 maps/tests/CoWebsite/tileset1.png diff --git a/docs/maps/api-nav.md b/docs/maps/api-nav.md index f5721063..386edf9e 100644 --- a/docs/maps/api-nav.md +++ b/docs/maps/api-nav.md @@ -49,19 +49,36 @@ WA.nav.goToRoom('../otherMap/map.json'); WA.nav.goToRoom("/_/global/.json#start-layer-2") ``` -### Opening/closing a web page in an iFrame +### Opening/closing web page in Co-Websites ``` -WA.nav.openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void -WA.nav.closeCoWebSite(): void +WA.nav.openCoWebsite(url: string, allowApi: boolean = false, allowPolicy: string = "", position: number = 0): Promise +WA.nav.closeCoWebsite(coWebsiteId: string): Promise +WA.nav.closeCoWebsites(): Promise ``` -Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow). +Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame. `allowApi` allows the webpage to use the "IFrame API" and execute script (it is equivalent to putting the `openWebsiteAllowApi` property in the map). `allowPolicy` grants additional access rights to the iFrame. The `allowPolicy` parameter is turned into an [`allow` feature policy in the iFrame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-allow), position in whitch slot the web page will be open. Example: ```javascript -WA.nav.openCoWebSite('https://www.wikipedia.org/'); +const coWebsite = await WA.nav.openCoWebsite('https://www.wikipedia.org/'); +const coWebsiteWorkAdventure = await WA.nav.openCoWebsite('https://workadventu.re/', true, "", 1); // ... -WA.nav.closeCoWebSite(); +WA.nav.closeCoWebsite(coWebsite.id); +WA.nav.closeCoWebsites(); +``` + +### Opening/closing web page in Co-Websites + +``` +WA.nav.getCoWebsites(): Promise +``` + +Get all opened co-websites withe their ids and positions. + +Example: + +```javascript +const coWebsites = await WA.nav.getCowebsites(); ``` diff --git a/front/dist/index.tmpl.html b/front/dist/index.tmpl.html index dc21765b..3b43a5ef 100644 --- a/front/dist/index.tmpl.html +++ b/front/dist/index.tmpl.html @@ -111,8 +111,7 @@
-
-
+
diff --git a/front/src/Api/Events/CloseCoWebsiteEvent.ts b/front/src/Api/Events/CloseCoWebsiteEvent.ts new file mode 100644 index 00000000..94167d5e --- /dev/null +++ b/front/src/Api/Events/CloseCoWebsiteEvent.ts @@ -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; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 861acc22..554e4545 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -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, diff --git a/front/src/Api/Events/OpenCoWebSiteEvent.ts b/front/src/Api/Events/OpenCoWebSiteEvent.ts deleted file mode 100644 index 7b5e6070..00000000 --- a/front/src/Api/Events/OpenCoWebSiteEvent.ts +++ /dev/null @@ -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; diff --git a/front/src/Api/Events/OpenCoWebsiteEvent.ts b/front/src/Api/Events/OpenCoWebsiteEvent.ts new file mode 100644 index 00000000..cc70b2e5 --- /dev/null +++ b/front/src/Api/Events/OpenCoWebsiteEvent.ts @@ -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; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 5a9aca85..caa59420 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -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 = ( @@ -53,10 +48,7 @@ class IframeListener { public readonly openTabStream = this._openTabStream.asObservable(); private readonly _loadPageStream: Subject = new Subject(); - public readonly loadPageStream = this._loadPageStream.asObservable(); - - private readonly _openCoWebSiteStream: Subject = new Subject(); - public readonly openCoWebSiteStream = this._openCoWebSiteStream.asObservable(); + public readonly loadPageStream = this._loadPageStream.asObservable() private readonly _disablePlayerControlStream: Subject = 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); } diff --git a/front/src/Api/ScriptUtils.ts b/front/src/Api/ScriptUtils.ts index ad6dcc0f..10a80c92 100644 --- a/front/src/Api/ScriptUtils.ts +++ b/front/src/Api/ScriptUtils.ts @@ -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); diff --git a/front/src/Api/iframe/nav.ts b/front/src/Api/iframe/nav.ts index f051a7c0..24b04e62 100644 --- a/front/src/Api/iframe/nav.ts +++ b/front/src/Api/iframe/nav.ts @@ -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 { callbacks = []; @@ -34,21 +30,61 @@ export class WorkadventureNavigationCommands extends IframeApiContribution { + 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(); diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index b60578df..06b29d18 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -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() diff --git a/front/style/cowebsite-mobile.scss b/front/style/cowebsite-mobile.scss index b336a29a..3a3482a9 100644 --- a/front/style/cowebsite-mobile.scss +++ b/front/style/cowebsite-mobile.scss @@ -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; diff --git a/front/style/cowebsite.scss b/front/style/cowebsite.scss index c2e0b2d2..223ed85a 100644 --- a/front/style/cowebsite.scss +++ b/front/style/cowebsite.scss @@ -4,7 +4,7 @@ position: fixed; z-index: 200; transition: transform 0.5s; - background-color: white; + background-color: whitesmoke; display: none; &.loading { diff --git a/maps/tests/CoWebsite/cowebsite_api.json b/maps/tests/CoWebsite/cowebsite_api.json new file mode 100644 index 00000000..7f5537ff --- /dev/null +++ b/maps/tests/CoWebsite/cowebsite_api.json @@ -0,0 +1,629 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23], + "height":10, + "id":5, + "name":"first_cowebsite", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12], + "height":10, + "id":7, + "name":"second_cowebsite", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":157.696496283938, + "id":1, + "name":"Tests", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":8, + "text":"Test 1:\nEnter \/cowebsite open https:\/\/wikipedia.com on the chat\nResult:\nA cowebsite must have been opened\n\nDo the first test 4 more times\n\nTest 2:\nEnter \/cowebsite close 0 on the chat\nResult:\nThe main co-website has been closed\n\nTest 3:\nEnter \/cowebsite close all on the chat\nResult:\nAll co-websites has been closed", + "wrap":true + }, + "type":"", + "visible":true, + "width":316.770833333333, + "x":1.64026713939023, + "y":160.264699934273 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 27, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"objects", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }], + "nextlayerid":9, + "nextobjectid":3, + "orientation":"orthogonal", + "properties":[ + { + "name":"script", + "type":"string", + "value":"script.js" + }], + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":1, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":2, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":3, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":4, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":5, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":6, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":7, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":8, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":9, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":10, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":12, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":20, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":21, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":23, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":24, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":25, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":26, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":27, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":28, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":29, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":30, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":31, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":32, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":34, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":35, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":42, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":43, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":45, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":46, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":59, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":60, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":70, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":71, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":80, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":81, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":89, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":91, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":93, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":94, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":95, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":96, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":97, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":100, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":102, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":103, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":104, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":105, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":106, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":107, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":108, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":114, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":115, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/maps/tests/CoWebsite/cowebsite_property.json b/maps/tests/CoWebsite/cowebsite_property.json new file mode 100644 index 00000000..d01c1753 --- /dev/null +++ b/maps/tests/CoWebsite/cowebsite_property.json @@ -0,0 +1,118 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23], + "height":10, + "id":5, + "name":"first_cowebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/wikipedia.org" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12], + "height":10, + "id":7, + "name":"second_cowebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/workadventu.re\/" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":141, + "id":1, + "name":"Tests", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":11, + "text":"Test:\nWalk on the blue carpet, an iframe open, walk on the white carpet another one to open another one\nResult:\n2 co-websites must be opened\n\nTest:\nGo outside of carpets\nResult:\nAll co-websites must disapeared", + "wrap":true + }, + "type":"", + "visible":true, + "width":316.770833333333, + "x":0.28125, + "y":187.833333333333 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":8, + "nextobjectid":3, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.1", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/maps/tests/CoWebsite/script.js b/maps/tests/CoWebsite/script.js new file mode 100644 index 00000000..ae1f94d2 --- /dev/null +++ b/maps/tests/CoWebsite/script.js @@ -0,0 +1,108 @@ +WA.onInit().then(() => { + WA.chat.onChatMessage((message) => { + + if (!message.startsWith('/')) { + return; + } + + const splitedMessage = message.trim().split(' '); + const command = splitedMessage.shift().substr(1); + + executeCommand(command, splitedMessage); + }); +}); + +function wookaSendMessage(message) { + WA.chat.sendChatMessage(message, 'Wooka'); +} + +function unknownCommand() { + wookaSendMessage('Unknown command'); +} + +function executeCommand(command, args) { + switch(command) { + case 'cowebsite': + coWebsiteCommand(args); + break; + default: + unknownCommand(); + } +} + +function coWebsiteCommand(args) { + if (args.length < 1) { + wookaSendMessage('Too few arguments'); + return; + } + + const subCommand = args.shift(); + + switch(subCommand) { + case 'open': + openCoWebsite(args); + break; + case 'close': + closeCoWebsite(args); + break; + default: + unknownCommand(); + } +} + +async function openCoWebsite(args) { + if (args.length < 1) { + wookaSendMessage('Too few arguments'); + return; + } + + try { + const url = new URL(args[0]); + } catch (exception) { + wookaSendMessage('Parameter is not a valid URL !'); + return; + } + + await WA.nav.openCoWebsite(args[0]).then(() => { + wookaSendMessage('Co-website has been opened !'); + }).catch((error) => { + wookaSendMessage(`Something wrong happen during co-website opening: ${error.message}`); + }); +} + +async function closeCoWebsite(args) { + if (args.length < 1) { + wookaSendMessage('Too few arguments'); + return; + } + + // All + if (args[0] === "all" || args[0] === "*") { + WA.nav.closeCoWebsites().then(() => { + wookaSendMessage('All co-websites has been closed !'); + }).catch((error) => { + wookaSendMessage(`Something wrong happen during co-websites closing: ${error.message}`); + }); + return; + } + + const coWebsites = await WA.nav.getCoWebsites(); + const position = parseInt(args[0]); + + // By ID or Position + const coWebsite = + isNaN(position) ? + coWebsites.find((coWebsite) => coWebsite.id === args[0]) : + coWebsites.find((coWebsite) => coWebsite.position === position); + + if (!coWebsite) { + wookaSendMessage('Unknown co-website'); + return; + } + + await WA.nav.closeCoWebsite(coWebsite.id).then(() => { + wookaSendMessage('This co-websites has been closed !'); + }).catch((error) => { + wookaSendMessage(`Something wrong happen during co-website closing: ${error.message}`); + }); +} diff --git a/maps/tests/CoWebsite/tileset1.png b/maps/tests/CoWebsite/tileset1.png new file mode 100644 index 0000000000000000000000000000000000000000..6e7dafb47dfbf1d7b225b7094ef7bfde8699f93c GIT binary patch literal 29402 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 literal 0 HcmV?d00001 diff --git a/maps/tests/index.html b/maps/tests/index.html index c30b54a5..692783af 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -3,6 +3,7 @@ + Map Tests
@@ -235,6 +236,25 @@ +

CoWebsite

+ + + + + + + + + +
+ Success Failure Pending + + Open co-websites by map property +
+ Success Failure Pending + + Open co-websites by scripting api +

Mobile