diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d23f4b9..710b85fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ ## Version develop +### Updates +- New scripting API features : + - Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu` to add a custom menu or an iframe to the menu. + +## Version 1.4.14 + ### Updates - New scripting API features : - Use `WA.room.loadTileset(url: string) : Promise` to load a tileset from a JSON file. diff --git a/docs/maps/api-deprecated.md b/docs/maps/api-deprecated.md index 930caebe..f2b582a5 100644 --- a/docs/maps/api-deprecated.md +++ b/docs/maps/api-deprecated.md @@ -18,3 +18,4 @@ The list of functions below is **deprecated**. You should not use those but. use - Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`. - Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`. - Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`. +- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`. \ No newline at end of file diff --git a/docs/maps/api-state.md b/docs/maps/api-state.md index 634b47e1..1cc4f7fb 100644 --- a/docs/maps/api-state.md +++ b/docs/maps/api-state.md @@ -9,6 +9,7 @@ Moreover, `WA.state` functions can be used to persist this state across reloads. ``` WA.state.saveVariable(key : string, data : unknown): void WA.state.loadVariable(key : string) : unknown +WA.state.hasVariable(key : string) : boolean WA.state.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription WA.state.[any property]: unknown ``` diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md index e4b9425d..dc701500 100644 --- a/docs/maps/api-ui.md +++ b/docs/maps/api-ui.md @@ -68,25 +68,53 @@ WA.room.onLeaveZone('myZone', () => { ### Add custom menu -```typescript -WA.ui.registerMenuCommand(menuCommand: string, callback: (menuCommand: string) => void): void ``` -Add a custom menu item containing the text `commandDescriptor` in the main menu. A click on the menu will trigger the `callback`. +WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions): Menu +``` +Add a custom menu item containing the text `commandDescriptor` in the navbar of the menu. +`options` attribute accepts an object with three properties : +- `callback : (commandDescriptor: string) => void` : A click on the custom menu will trigger the `callback`. +- `iframe: string` : A click on the custom menu will open the `iframe` inside the menu. +- `allowApi?: boolean` : Allow the iframe of the custom menu to use the Scripting API. + +Important : `options` accepts only `callback` or `iframe` not both. + Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script. -Example: +
+
+ +
+
+ +
+
+Example: ```javascript +const menu = WA.ui.registerMenuCommand('menu test', + { + callback: () => { + WA.chat.sendChatMessage('test'); + } + }) -WA.ui.registerMenuCommand("test", () => { - WA.chat.sendChatMessage("test clicked", "menu cmd") -}) - +// Some time later, if you want to remove the menu: +menu.remove(); ``` -
- -
+Please note that `registerMenuCommand` returns an object of the `Menu` class. + +The `Menu` class contains a single method: `remove(): void`. This will obviously remove the menu when called. + +```javascript +class Menu { + /** + * Remove the menu + */ + remove() {}; +} +``` diff --git a/docs/maps/images/custom-menu-iframe.png b/docs/maps/images/custom-menu-iframe.png new file mode 100644 index 00000000..9df2aa5f Binary files /dev/null and b/docs/maps/images/custom-menu-iframe.png differ diff --git a/docs/maps/images/custom-menu-navbar.png b/docs/maps/images/custom-menu-navbar.png new file mode 100644 index 00000000..c2440956 Binary files /dev/null and b/docs/maps/images/custom-menu-navbar.png differ diff --git a/front/dist/static/images/logo-WA-min.png b/front/dist/static/images/logo-WA-min.png new file mode 100644 index 00000000..fe213151 Binary files /dev/null and b/front/dist/static/images/logo-WA-min.png differ diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index ed723241..861acc22 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -15,7 +15,6 @@ import type { SetPropertyEvent } from "./setPropertyEvent"; import type { LoadSoundEvent } from "./LoadSoundEvent"; import type { PlaySoundEvent } from "./PlaySoundEvent"; import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent"; -import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent"; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; import type { SetTilesEvent } from "./SetTilesEvent"; import type { SetVariableEvent } from "./SetVariableEvent"; @@ -33,6 +32,7 @@ import type { TriggerActionMessageEvent, } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; +import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -63,7 +63,8 @@ export type IframeEventMap = { stopSound: null; getState: undefined; loadTileset: LoadTilesetEvent; - registerMenuCommand: MenuItemRegisterEvent; + registerMenu: MenuRegisterEvent; + unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact }; diff --git a/front/src/Api/Events/SetVariableEvent.ts b/front/src/Api/Events/SetVariableEvent.ts index 3b4e9c85..3e2303b3 100644 --- a/front/src/Api/Events/SetVariableEvent.ts +++ b/front/src/Api/Events/SetVariableEvent.ts @@ -1,5 +1,4 @@ import * as tg from "generic-type-guard"; -import { isMenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent"; export const isSetVariableEvent = new tg.IsInterface() .withProperties({ diff --git a/front/src/Api/Events/ui/MenuItemRegisterEvent.ts b/front/src/Api/Events/ui/MenuItemRegisterEvent.ts deleted file mode 100644 index abc8974d..00000000 --- a/front/src/Api/Events/ui/MenuItemRegisterEvent.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as tg from "generic-type-guard"; -import { Subject } from "rxjs"; -import { subMenusStore } from "../../../Stores/MenuStore"; - -export const isMenuItemRegisterEvent = new tg.IsInterface() - .withProperties({ - menutItem: tg.isString, - }) - .get(); -/** - * A message sent from the iFrame to the game to add a new menu item. - */ -export type MenuItemRegisterEvent = tg.GuardedType; - -export const isMenuItemRegisterIframeEvent = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonString("registerMenuCommand"), - data: isMenuItemRegisterEvent, - }) - .get(); - -export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) { - subMenusStore.addMenu(event.menutItem); -} diff --git a/front/src/Api/Events/ui/MenuRegisterEvent.ts b/front/src/Api/Events/ui/MenuRegisterEvent.ts new file mode 100644 index 00000000..f620745f --- /dev/null +++ b/front/src/Api/Events/ui/MenuRegisterEvent.ts @@ -0,0 +1,31 @@ +import * as tg from "generic-type-guard"; + +/** + * A message sent from a script to the game to remove a custom menu from the menu + */ +export const isUnregisterMenuEvent = new tg.IsInterface() + .withProperties({ + name: tg.isString, + }) + .get(); + +export type UnregisterMenuEvent = tg.GuardedType; + +export const isMenuRegisterOptions = new tg.IsInterface() + .withProperties({ + allowApi: tg.isBoolean, + }) + .get(); + +/** + * A message sent from a script to the game to add a custom menu from the menu + */ +export const isMenuRegisterEvent = new tg.IsInterface() + .withProperties({ + name: tg.isString, + iframe: tg.isUnion(tg.isString, tg.isUndefined), + options: isMenuRegisterOptions, + }) + .get(); + +export type MenuRegisterEvent = tg.GuardedType; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index f798d310..940c3c0c 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -29,12 +29,12 @@ import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent" import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; import { isLoadPageEvent } from "./Events/LoadPageEvent"; -import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; +import { isMenuRegisterEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegisterEvent"; 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 { subMenusStore } from "../Stores/MenuStore"; +import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -258,17 +258,23 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; - } else if (isMenuItemRegisterIframeEvent(payload)) { - const data = payload.data.menutItem; - // @ts-ignore - this.iframeCloseCallbacks.get(iframe).push(() => { - subMenusStore.removeMenu(data); - }); - handleMenuItemRegistrationEvent(payload.data); } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { this._modifyEmbeddedWebsiteStream.next(payload.data); + } else if (payload.type == "registerMenu" && isMenuRegisterEvent(payload.data)) { + const dataName = payload.data.name; + this.iframeCloseCallbacks.get(iframe)?.push(() => { + handleMenuUnregisterEvent(dataName); + }); + handleMenuRegistrationEvent( + payload.data.name, + payload.data.iframe, + foundSrc, + payload.data.options + ); + } else if (payload.type == "unregisterMenu" && isUnregisterMenuEvent(payload.data)) { + handleMenuUnregisterEvent(payload.data.name); } } }, diff --git a/front/src/Api/iframe/Ui/Menu.ts b/front/src/Api/iframe/Ui/Menu.ts new file mode 100644 index 00000000..c0fe772e --- /dev/null +++ b/front/src/Api/iframe/Ui/Menu.ts @@ -0,0 +1,17 @@ +import { sendToWorkadventure } from "../IframeApiContribution"; + +export class Menu { + constructor(private menuName: string) {} + + /** + * remove the menu + */ + public remove() { + sendToWorkadventure({ + type: "unregisterMenu", + data: { + name: this.menuName, + }, + }); + } +} diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index 3b551864..a875f3e0 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -62,6 +62,10 @@ export class WorkadventureStateCommands extends IframeApiContribution { let subject = variableSubscribers.get(key); if (subject === undefined) { @@ -85,6 +89,12 @@ const proxyCommand = new Proxy(new WorkadventureStateCommands(), { target.saveVariable(p.toString(), value); return true; }, + has(target: WorkadventureStateCommands, p: PropertyKey): boolean { + if (p in target) { + return true; + } + return target.hasVariable(p.toString()); + }, }) as WorkadventureStateCommands & { [key: string]: unknown }; export default proxyCommand; diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts index 87114cb5..ce39bebe 100644 --- a/front/src/Api/iframe/ui.ts +++ b/front/src/Api/iframe/ui.ts @@ -6,6 +6,8 @@ import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescrip import { Popup } from "./Ui/Popup"; import { ActionMessage } from "./Ui/ActionMessage"; import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent"; +import { Menu } from "./Ui/Menu"; +import type { RequireOnlyOne } from "../types"; let popupId = 0; const popups: Map = new Map(); @@ -14,9 +16,18 @@ const popupCallbacks: Map> = new Map< Map >(); +const menus: Map = new Map(); const menuCallbacks: Map void> = new Map(); const actionMessages = new Map(); +interface MenuDescriptor { + callback?: (commandDescriptor: string) => void; + iframe?: string; + allowApi?: boolean; +} + +export type MenuOptions = RequireOnlyOne; + interface ZonedPopupOptions { zone: string; objectLayerName?: string; @@ -53,6 +64,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution { const callback = menuCallbacks.get(event.menuItem); + const menu = menus.get(event.menuItem); + if (menu === undefined) { + throw new Error('Could not find menu named "' + event.menuItem + '"'); + } if (callback) { callback(event.menuItem); } @@ -106,14 +121,53 @@ export class WorkAdventureUiCommands extends IframeApiContribution void) { - menuCallbacks.set(commandDescriptor, callback); - sendToWorkadventure({ - type: "registerMenuCommand", - data: { - menutItem: commandDescriptor, - }, - }); + registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu { + const menu = new Menu(commandDescriptor); + + if (typeof options === "function") { + menuCallbacks.set(commandDescriptor, options); + sendToWorkadventure({ + type: "registerMenu", + data: { + name: commandDescriptor, + options: { + allowApi: false, + }, + }, + }); + } else { + options.allowApi = options.allowApi === undefined ? options.iframe !== undefined : options.allowApi; + + if (options.iframe !== undefined) { + sendToWorkadventure({ + type: "registerMenu", + data: { + name: commandDescriptor, + iframe: options.iframe, + options: { + allowApi: options.allowApi, + }, + }, + }); + } else if (options.callback !== undefined) { + menuCallbacks.set(commandDescriptor, options.callback); + sendToWorkadventure({ + type: "registerMenu", + data: { + name: commandDescriptor, + options: { + allowApi: options.allowApi, + }, + }, + }); + } else { + throw new Error( + "When adding a menu with WA.ui.registerMenuCommand, you must pass either an iframe or a callback" + ); + } + } + menus.set(commandDescriptor, menu); + return menu; } displayBubble(): void { diff --git a/front/src/Api/types.ts b/front/src/Api/types.ts new file mode 100644 index 00000000..7d1a2107 --- /dev/null +++ b/front/src/Api/types.ts @@ -0,0 +1,4 @@ +export type RequireOnlyOne = Pick> & + { + [K in keys]-?: Required> & Partial, undefined>>; + }[keys]; diff --git a/front/src/Components/Menu/CustomSubMenu.svelte b/front/src/Components/Menu/CustomSubMenu.svelte new file mode 100644 index 00000000..f85499c3 --- /dev/null +++ b/front/src/Components/Menu/CustomSubMenu.svelte @@ -0,0 +1,33 @@ + + + + + + \ No newline at end of file diff --git a/front/src/Components/Menu/Menu.svelte b/front/src/Components/Menu/Menu.svelte index afbcd065..4086a9ae 100644 --- a/front/src/Components/Menu/Menu.svelte +++ b/front/src/Components/Menu/Menu.svelte @@ -6,18 +6,34 @@ import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte"; import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte"; import ContactSubMenu from "./ContactSubMenu.svelte"; - import {menuVisiblilityStore, SubMenusInterface, subMenusStore} from "../../Stores/MenuStore"; - import {onMount} from "svelte"; + import CustomSubMenu from "./CustomSubMenu.svelte" + import {customMenuIframe, menuVisiblilityStore, SubMenusInterface, subMenusStore} from "../../Stores/MenuStore"; + import {onDestroy, onMount} from "svelte"; import {get} from "svelte/store"; + import type {Unsubscriber} from "svelte/store"; import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem"; let activeSubMenu: string = SubMenusInterface.settings; - let activeComponent: typeof SettingsSubMenu = SettingsSubMenu; + let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu; + let props: { url: string, allowApi: boolean }; + let unsubscriberSubMenuStore: Unsubscriber; onMount(() => { + unsubscriberSubMenuStore = subMenusStore.subscribe(() => { + if(!get(subMenusStore).includes(activeSubMenu)) { + switchMenu(SubMenusInterface.settings); + } + }) + switchMenu(SubMenusInterface.settings); }) + onDestroy(() => { + if(unsubscriberSubMenuStore) { + unsubscriberSubMenuStore(); + } + }) + function switchMenu(menu: string) { if (get(subMenusStore).find((subMenu) => subMenu === menu)) { activeSubMenu = menu; @@ -41,8 +57,14 @@ activeComponent = ContactSubMenu; break; default: - sendMenuClickedEvent(menu); - menuVisiblilityStore.set(false); + const customMenu = customMenuIframe.get(menu); + if (customMenu !== undefined) { + props = { url: customMenu.url, allowApi: customMenu.allowApi }; + activeComponent = CustomSubMenu; + } else { + sendMenuClickedEvent(menu); + menuVisiblilityStore.set(false); + } break; } } else throw ("There is no menu called " + menu); @@ -74,7 +96,7 @@ diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 98583cba..23e5c4e9 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -80,8 +80,11 @@ export class GameMap { return; } this.key = key; + this.triggerAll(); + } - const newProps = this.getProperties(key); + private triggerAll(): void { + const newProps = this.getProperties(this.key ?? 0); const oldProps = this.lastProperties; this.lastProperties = newProps; @@ -253,4 +256,34 @@ export class GameMap { } return this.tileNameMap.get(tile); } + + public setLayerProperty( + layerName: string, + propertyName: string, + propertyValue: string | number | undefined | boolean + ) { + const layer = this.findLayer(layerName); + if (layer === undefined) { + console.warn('Could not find layer "' + layerName + '" when calling setProperty'); + return; + } + if (layer.properties === undefined) { + layer.properties = []; + } + const property = layer.properties.find((property) => property.name === propertyName); + if (property === undefined) { + if (propertyValue === undefined) { + return; + } + layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue }); + return; + } + if (propertyValue === undefined) { + const index = layer.properties.indexOf(property); + layer.properties.splice(index, 1); + } + property.value = propertyValue; + + this.triggerAll(); + } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2ec989ab..8d912f1e 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1276,30 +1276,10 @@ export class GameScene extends DirtyScene { propertyName: string, propertyValue: string | number | boolean | undefined ): void { - const layer = this.gameMap.findLayer(layerName); - if (layer === undefined) { - console.warn('Could not find layer "' + layerName + '" when calling setProperty'); - return; - } if (propertyName === "exitUrl" && typeof propertyValue === "string") { this.loadNextGameFromExitUrl(propertyValue); } - if (layer.properties === undefined) { - layer.properties = []; - } - const property = layer.properties.find((property) => property.name === propertyName); - if (property === undefined) { - if (propertyValue === undefined) { - return; - } - layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue }); - return; - } - if (propertyValue === undefined) { - const index = layer.properties.indexOf(property); - layer.properties.splice(index, 1); - } - property.value = propertyValue; + this.gameMap.setLayerProperty(layerName, propertyName, propertyValue); } private setLayerVisibility(layerName: string, visible: boolean): void { diff --git a/front/src/Stores/MenuStore.ts b/front/src/Stores/MenuStore.ts index ff4a6287..91ec0648 100644 --- a/front/src/Stores/MenuStore.ts +++ b/front/src/Stores/MenuStore.ts @@ -82,3 +82,35 @@ function checkSubMenuToShow() { } checkSubMenuToShow(); + +export const customMenuIframe = new Map(); + +export function handleMenuRegistrationEvent( + menuName: string, + iframeUrl: string | undefined = undefined, + source: string | undefined = undefined, + options: { allowApi: boolean } +) { + if (get(subMenusStore).includes(menuName)) { + console.warn("The menu " + menuName + " already exist."); + return; + } + + subMenusStore.addMenu(menuName); + + if (iframeUrl !== undefined) { + const url = new URL(iframeUrl, source); + customMenuIframe.set(menuName, { url: url.toString(), allowApi: options.allowApi }); + } +} + +export function handleMenuUnregisterEvent(menuName: string) { + const subMenuGeneral: string[] = Object.values(SubMenusInterface); + if (subMenuGeneral.includes(menuName)) { + console.warn("The menu " + menuName + " is a mandatory menu. It can't be remove"); + return; + } + + subMenusStore.removeMenu(menuName); + customMenuIframe.delete(menuName); +} diff --git a/front/src/types.ts b/front/src/types.ts index 6b9e97e6..d957a2c2 100644 --- a/front/src/types.ts +++ b/front/src/types.ts @@ -1,10 +1,10 @@ import type Phaser from "phaser"; export type CursorKey = { - isDown: boolean -} + isDown: boolean; +}; -export type Direction = 'left' | 'right' | 'up' | 'down' +export type Direction = "left" | "right" | "up" | "down"; export interface CursorKeys extends Record { left: CursorKey; @@ -21,4 +21,3 @@ export interface IVirtualJoystick extends Phaser.GameObjects.GameObject { visible: boolean; createCursorKeys: () => CursorKeys; } - diff --git a/maps/tests/Metadata/customIframeMenu.html b/maps/tests/Metadata/customIframeMenu.html new file mode 100644 index 00000000..a17457ac --- /dev/null +++ b/maps/tests/Metadata/customIframeMenu.html @@ -0,0 +1,9 @@ + + + + Custom Iframe Menu + + +

This is an iframe in a custom menu.

+ + \ No newline at end of file diff --git a/maps/tests/Metadata/customIframeMenuApi.html b/maps/tests/Metadata/customIframeMenuApi.html new file mode 100644 index 00000000..afe840cc --- /dev/null +++ b/maps/tests/Metadata/customIframeMenuApi.html @@ -0,0 +1,20 @@ + + + + + API in iframe menu + + + +

This is an iframe in a custom menu.

+ + \ No newline at end of file diff --git a/maps/tests/Metadata/customMenu.html b/maps/tests/Metadata/customMenu.html index 404673f3..4b38f0b9 100644 --- a/maps/tests/Metadata/customMenu.html +++ b/maps/tests/Metadata/customMenu.html @@ -1,20 +1,18 @@ - - - - -

Add a custom menu

- + + + + +

Add a custom menu

+ \ No newline at end of file diff --git a/maps/tests/Metadata/customMenu.js b/maps/tests/Metadata/customMenu.js new file mode 100644 index 00000000..ba66ee0d --- /dev/null +++ b/maps/tests/Metadata/customMenu.js @@ -0,0 +1,15 @@ +let menuIframeApi = undefined; + +WA.ui.registerMenuCommand('custom callback menu', () => { + WA.nav.openTab("https://workadventu.re/"); +}) + +WA.ui.registerMenuCommand('custom iframe menu', {iframe: 'customIframeMenu.html'}); + +WA.room.onEnterZone('iframeMenu', () => { + menuIframeApi = WA.ui.registerMenuCommand('IFRAME USE API', {iframe: 'customIframeMenuApi.html', allowApi: true}); +}) + +WA.room.onLeaveZone('iframeMenu', () => { + menuIframeApi.remove(); +}) \ No newline at end of file diff --git a/maps/tests/Metadata/customMenu.json b/maps/tests/Metadata/customMenu.json index 49840d0b..c107aaf2 100644 --- a/maps/tests/Metadata/customMenu.json +++ b/maps/tests/Metadata/customMenu.json @@ -1,279 +1,100 @@ { "compressionlevel":-1, - "editorsettings": + "height":10, + "infinite":false, + "layers":[ { - "export": - { - "target":"." - } + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 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, 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":1, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 }, - "height":10, - "infinite":false, - "layers":[ + { + "data":[33, 34, 34, 34, 34, 34, 35, 0, 0, 0, 41, 42, 42, 42, 42, 42, 43, 0, 0, 0, 41, 42, 42, 42, 42, 42, 43, 0, 0, 0, 41, 42, 42, 42, 42, 42, 59, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 0, 0, 0, 42, 42, 42, 42, 42, 42, 43, 0, 0, 0, 42, 42, 42, 42, 42, 42, 43, 0, 0, 0, 50, 50, 50, 50, 50, 50, 51], + "height":10, + "id":2, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 41, 42, 43, 0, 0, 0, 0, 0, 0, 0, 49, 50, 51, 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, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":3, + "name":"ZoneIframeMenu", + "opacity":1, + "properties":[ { - "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 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, 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":1, - "name":"start", - "opacity":1, - "type":"tilelayer", - "visible":true, - "width":10, - "x":0, - "y":0 - }, - { - "data":[33, 34, 34, 34, 34, 34, 34, 34, 34, 35, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 41, 42, 42, 42, 42, 42, 42, 42, 42, 43, 49, 50, 50, 50, 50, 50, 50, 50, 50, 51], - "height":10, - "id":2, - "name":"bottom", - "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, 101, 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":6, - "name":"exit", - "opacity":1, - "properties":[ - { - "name":"exitUrl", - "type":"string", - "value":"showHideLayer.json" - }], - "type":"tilelayer", - "visible":true, - "width":10, - "x":0, - "y":0 - }, - { - "data":[0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 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":4, - "name":"metadata", - "opacity":1, - "properties":[ - { - "name":"openWebsite", - "type":"string", - "value":"customMenu.html" - }, - { - "name":"openWebsiteAllowApi", - "type":"bool", - "value":true - }], - "type":"tilelayer", - "visible":true, - "width":10, - "x":0, - "y":0 - }, - { - "draworder":"topdown", - "id":5, - "name":"floorLayer", - "objects":[ - { - "height":217.142414860681, - "id":1, - "name":"", - "rotation":0, - "text": - { - "fontfamily":"Sans Serif", - "pixelsize":9, - "text":"Test : \nWalk on the grass, an iframe open.\nResult : \nOpen the menu, a new sub-menu is displayed.\n\nTest : \nExit the grass\nResult : \nOpen the menu, the submenu has disappeared.\n\nTest : \nClick on the 'HELP' menu.\nResult : \nChat open and a 'HELP' message is displayed.\n\nTest : \nWalk on the red tile then open the menu.\nResult : \nYou have exit the room to another room, the submenu has disappeared.\n", - "wrap":true - }, - "type":"", - "visible":true, - "width":305.097705765524, - "x":15.1244925229277, - "y":103.029937496349 - }], - "opacity":1, - "type":"objectgroup", - "visible":true, - "x":0, - "y":0 + "name":"zone", + "type":"string", + "value":"iframeMenu" }], - "nextlayerid":7, - "nextobjectid":2, - "orientation":"orthogonal", - "renderorder":"right-down", - "tiledversion":"1.4.3", - "tileheight":32, - "tilesets":[ + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 34, 35, 0, 0, 0, 0, 0, 0, 0, 41, 42, 43, 0, 0, 0, 0, 0, 0, 0, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":4, + "name":"CoWebSiteIframeMenu", + "opacity":1, + "properties":[ { - "columns":8, - "firstgid":1, - "image":"tileset_dungeon.png", - "imageheight":256, - "imagewidth":256, - "margin":0, - "name":"TDungeon", - "spacing":0, - "tilecount":64, - "tileheight":32, - "tiles":[ - { - "id":0, - "properties":[ - { - "name":"collides", - "type":"bool", - "value":true - }] - }, - { - "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":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":11, - "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 - }] - }], - "tilewidth":32 - }, + "name":"openWebsite", + "type":"string", + "value":"customMenu.html" + }, { - "columns":8, - "firstgid":65, - "image":"floortileset.png", - "imageheight":288, - "imagewidth":256, - "margin":0, - "name":"Floor", - "spacing":0, - "tilecount":72, - "tileheight":32, - "tilewidth":32 + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true }], - "tilewidth":32, - "type":"map", - "version":1.4, - "width":10 + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }], + "nextlayerid":5, + "nextobjectid":1, + "orientation":"orthogonal", + "properties":[ + { + "name":"script", + "type":"string", + "value":"customMenu.js" + }], + "renderorder":"right-down", + "tiledversion":"1.7.1", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"tileset_dungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 } \ No newline at end of file