Merge pull request #1392 from thecodingmachine/iframeMenuScript
Iframe menu script
This commit is contained in:
commit
7576fbbe6d
23 changed files with 424 additions and 352 deletions
|
@ -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<T> 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<EmbeddedWebsite>; // Note: name should be compulsory in fact
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as tg from "generic-type-guard";
|
||||
import { isMenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
||||
|
||||
export const isSetVariableEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
|
|
|
@ -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<typeof isMenuItemRegisterEvent>;
|
||||
|
||||
export const isMenuItemRegisterIframeEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
type: tg.isSingletonString("registerMenuCommand"),
|
||||
data: isMenuItemRegisterEvent,
|
||||
})
|
||||
.get();
|
||||
|
||||
export function handleMenuItemRegistrationEvent(event: MenuItemRegisterEvent) {
|
||||
subMenusStore.addMenu(event.menutItem);
|
||||
}
|
31
front/src/Api/Events/ui/MenuRegisterEvent.ts
Normal file
31
front/src/Api/Events/ui/MenuRegisterEvent.ts
Normal file
|
@ -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<typeof isUnregisterMenuEvent>;
|
||||
|
||||
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<typeof isMenuRegisterEvent>;
|
|
@ -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<T extends keyof IframeQueryMap> = (
|
||||
query: IframeQueryMap[T]["query"],
|
||||
|
@ -255,17 +255,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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
17
front/src/Api/iframe/Ui/Menu.ts
Normal file
17
front/src/Api/iframe/Ui/Menu.ts
Normal file
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<number, Popup> = new Map<number, Popup>();
|
||||
|
@ -14,9 +16,18 @@ const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
|
|||
Map<number, ButtonClickedCallback>
|
||||
>();
|
||||
|
||||
const menus: Map<string, Menu> = new Map<string, Menu>();
|
||||
const menuCallbacks: Map<string, (command: string) => void> = new Map();
|
||||
const actionMessages = new Map<string, ActionMessage>();
|
||||
|
||||
interface MenuDescriptor {
|
||||
callback?: (commandDescriptor: string) => void;
|
||||
iframe?: string;
|
||||
allowApi?: boolean;
|
||||
}
|
||||
|
||||
export type MenuOptions = RequireOnlyOne<MenuDescriptor, "callback" | "iframe">;
|
||||
|
||||
interface ZonedPopupOptions {
|
||||
zone: string;
|
||||
objectLayerName?: string;
|
||||
|
@ -52,6 +63,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
|||
typeChecker: isMenuItemClickedEvent,
|
||||
callback: (event) => {
|
||||
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,14 +119,53 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
|
|||
return popup;
|
||||
}
|
||||
|
||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => 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 {
|
||||
|
|
4
front/src/Api/types.ts
Normal file
4
front/src/Api/types.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export type RequireOnlyOne<T, keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, keys>> &
|
||||
{
|
||||
[K in keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<keys, K>, undefined>>;
|
||||
}[keys];
|
Loading…
Add table
Add a link
Reference in a new issue