From cf7bfe79caa71e64870dfb81558944a96515499b Mon Sep 17 00:00:00 2001 From: GRL Date: Fri, 27 Aug 2021 10:34:03 +0200 Subject: [PATCH] Refactor to only have one function registerMenuCommand When selected custom menu is removed, go to settings menu Allow iframe in custom menu to use Scripting API Return menu object when it is registered, can call remove function on it --- front/src/Api/Events/IframeEvent.ts | 5 +- front/src/Api/Events/SetVariableEvent.ts | 1 - front/src/Api/Events/ui/MenuRegisterEvent.ts | 49 ++++------ front/src/Api/IframeListener.ts | 21 +++-- front/src/Api/iframe/Ui/Menu.ts | 17 ++++ front/src/Api/iframe/ui.ts | 89 +++++++++++++------ .../src/Components/Menu/CustomSubMenu.svelte | 21 ++++- front/src/Components/Menu/Menu.svelte | 23 +++-- front/src/Stores/MenuStore.ts | 8 +- front/src/types.ts | 10 ++- maps/tests/Metadata/customIframeMenu.html | 9 ++ maps/tests/Metadata/customIframeMenuApi.html | 20 +++++ maps/tests/Metadata/customMenu.html | 2 +- maps/tests/Metadata/customMenu.js | 15 ++++ 14 files changed, 203 insertions(+), 87 deletions(-) create mode 100644 front/src/Api/iframe/Ui/Menu.ts create mode 100644 maps/tests/Metadata/customIframeMenu.html create mode 100644 maps/tests/Metadata/customIframeMenuApi.html create mode 100644 maps/tests/Metadata/customMenu.js diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index d561f9f7..861acc22 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -32,7 +32,7 @@ import type { TriggerActionMessageEvent, } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; -import type { MenuIframeRegisterEvent, MenuItemRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; +import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -63,8 +63,7 @@ export type IframeEventMap = { stopSound: null; getState: undefined; loadTileset: LoadTilesetEvent; - registerMenuCommand: MenuItemRegisterEvent; - registerMenuIframe: MenuIframeRegisterEvent; + 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 77740683..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/MenuRegisterEvent"; export const isSetVariableEvent = new tg.IsInterface() .withProperties({ diff --git a/front/src/Api/Events/ui/MenuRegisterEvent.ts b/front/src/Api/Events/ui/MenuRegisterEvent.ts index 92bca580..f620745f 100644 --- a/front/src/Api/Events/ui/MenuRegisterEvent.ts +++ b/front/src/Api/Events/ui/MenuRegisterEvent.ts @@ -1,35 +1,5 @@ import * as tg from "generic-type-guard"; -/** - * A message sent from a script to the game to add a new button in the menu. - */ -export const isMenuItemRegisterEvent = new tg.IsInterface() - .withProperties({ - menuItem: tg.isString, - }) - .get(); - -export type MenuItemRegisterEvent = tg.GuardedType; - -export const isMenuItemRegisterIframeEvent = new tg.IsInterface() - .withProperties({ - type: tg.isSingletonString("registerMenuCommand"), - data: isMenuItemRegisterEvent, - }) - .get(); - -/** - * A message sent from a script to the game to add an iframe submenu in the menu. - */ -export const isMenuIframeEvent = new tg.IsInterface() - .withProperties({ - name: tg.isString, - url: tg.isString, - }) - .get(); - -export type MenuIframeRegisterEvent = tg.GuardedType; - /** * A message sent from a script to the game to remove a custom menu from the menu */ @@ -40,3 +10,22 @@ export const isUnregisterMenuEvent = new tg.IsInterface() .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 75cbdd68..140d2f34 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -29,7 +29,7 @@ import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent" import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; import { isLoadPageEvent } from "./Events/LoadPageEvent"; -import { isMenuItemRegisterIframeEvent, isMenuIframeEvent, isUnregisterMenuEvent } from "./Events/ui/MenuRegisterEvent"; +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"; @@ -255,22 +255,21 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; - } else if (isMenuItemRegisterIframeEvent(payload)) { - const data = payload.data.menuItem; - this.iframeCloseCallbacks.get(iframe)?.push(() => { - handleMenuUnregisterEvent(data); - }); - handleMenuRegistrationEvent(payload.data.menuItem); } 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 == "registerMenuIframe" && isMenuIframeEvent(payload.data)) { - const data = payload.data.name; + } else if (payload.type == "registerMenu" && isMenuRegisterEvent(payload.data)) { + const dataName = payload.data.name; this.iframeCloseCallbacks.get(iframe)?.push(() => { - handleMenuUnregisterEvent(data); + handleMenuUnregisterEvent(dataName); }); - handleMenuRegistrationEvent(payload.data.name, payload.data.url, foundSrc); + 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/ui.ts b/front/src/Api/iframe/ui.ts index e093835a..cd0ec05a 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; +} + +type CallbackOrIframe = RequireOnlyOne; + interface ZonedPopupOptions { zone: string; objectLayerName?: string; @@ -52,6 +63,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); } @@ -104,36 +119,56 @@ export class WorkAdventureUiCommands extends IframeApiContribution void) { - menuCallbacks.set(commandDescriptor, callback); - sendToWorkadventure({ - type: "registerMenuCommand", - data: { - menuItem: commandDescriptor, - }, - }); - } + registerMenuCommand( + commandDescriptor: string, + options: CallbackOrIframe | ((commandDescriptor: string) => void) + ): Menu { + const menu = new Menu(commandDescriptor); - registerMenuIframe(menuName: string, iframeUrl: string) { - sendToWorkadventure({ - type: "registerMenuIframe", - data: { - name: menuName, - url: iframeUrl, - }, - }); - } + 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; - unregisterMenu(menuName: string) { - sendToWorkadventure({ - type: "unregisterMenu", - data: { - name: menuName, - }, - }); - if (menuCallbacks.get(menuName)) { - menuCallbacks.delete(menuName); + 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/Components/Menu/CustomSubMenu.svelte b/front/src/Components/Menu/CustomSubMenu.svelte index fd583084..f85499c3 100644 --- a/front/src/Components/Menu/CustomSubMenu.svelte +++ b/front/src/Components/Menu/CustomSubMenu.svelte @@ -1,10 +1,27 @@ - +