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 @@ - +