Merge pull request #1227 from jonnytest1/trigger-message-refv3

trigger message api refactorv3
This commit is contained in:
David Négrier 2021-08-05 12:18:55 +02:00 committed by GitHub
commit a09f27b448
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 485 additions and 105 deletions

View File

@ -86,4 +86,51 @@ WA.ui.registerMenuCommand("test", () => {
<div class="col"> <div class="col">
<img src="https://workadventu.re/img/docs/menu-command.png" class="figure-img img-fluid rounded" alt="" /> <img src="https://workadventu.re/img/docs/menu-command.png" class="figure-img img-fluid rounded" alt="" />
</div> </div>
### Awaiting User Confirmation (with space bar)
```
WA.ui.displayActionMessage({
message: string,
callback: () => void,
type?: "message"|"warning",
}): ActionMessage
```
Displays a message at the bottom of the screen (that will disappear when space bar is pressed).
<div class="col">
<img src="https://workadventu.re/img/docs/trigger_message.png" class="figure-img img-fluid rounded" alt="" />
</div>
Example:
```javascript
const triggerMessage = WA.ui.displayActionMessage({
message: "press 'space' to confirm",
callback: () => {
WA.chat.sendChatMessage("confirmed", "trigger message logic")
}
});
setTimeout(() => {
// later
triggerMessage.remove();
}, 1000)
```
Please note that `displayActionMessage` returns an object of the `ActionMessage` class.
The `ActionMessage` class contains a single method: `remove(): Promise<void>`. This will obviously remove the message when called.
```javascript
class ActionMessage {
/**
* Hides the message
*/
remove() {};
}
```

View File

@ -26,7 +26,6 @@
"rules": { "rules": {
"no-unused-vars": "off", "no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
// TODO: remove those ignored rules and write a stronger code! // TODO: remove those ignored rules and write a stronger code!
"@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-call": "off",

View File

@ -9,6 +9,7 @@ import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent";
import type { OpenPopupEvent } from "./OpenPopupEvent"; import type { OpenPopupEvent } from "./OpenPopupEvent";
import type { OpenTabEvent } from "./OpenTabEvent"; import type { OpenTabEvent } from "./OpenTabEvent";
import type { UserInputChatEvent } from "./UserInputChatEvent"; import type { UserInputChatEvent } from "./UserInputChatEvent";
import type { MapDataEvent } from "./MapDataEvent";
import type { LayerEvent } from "./LayerEvent"; import type { LayerEvent } from "./LayerEvent";
import type { SetPropertyEvent } from "./setPropertyEvent"; import type { SetPropertyEvent } from "./setPropertyEvent";
import type { LoadSoundEvent } from "./LoadSoundEvent"; import type { LoadSoundEvent } from "./LoadSoundEvent";
@ -23,6 +24,13 @@ import { isMapDataEvent } from "./MapDataEvent";
import { isSetVariableEvent } from "./SetVariableEvent"; import { isSetVariableEvent } from "./SetVariableEvent";
import type { LoadTilesetEvent } from "./LoadTilesetEvent"; import type { LoadTilesetEvent } from "./LoadTilesetEvent";
import { isLoadTilesetEvent } from "./LoadTilesetEvent"; import { isLoadTilesetEvent } from "./LoadTilesetEvent";
import type {
MessageReferenceEvent,
removeActionMessage,
triggerActionMessage,
TriggerActionMessageEvent,
} from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T; data: T;
@ -73,6 +81,7 @@ export interface IframeResponseEventMap {
hasPlayerMoved: HasPlayerMovedEvent; hasPlayerMoved: HasPlayerMovedEvent;
menuItemClicked: MenuItemClickedEvent; menuItemClicked: MenuItemClickedEvent;
setVariable: SetVariableEvent; setVariable: SetVariableEvent;
messageTriggered: MessageReferenceEvent;
} }
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> { export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
type: T; type: T;
@ -105,6 +114,14 @@ export const iframeQueryMapTypeGuards = {
query: isLoadTilesetEvent, query: isLoadTilesetEvent,
answer: tg.isNumber, answer: tg.isNumber,
}, },
triggerActionMessage: {
query: isTriggerActionMessageEvent,
answer: tg.isUndefined,
},
removeActionMessage: {
query: isMessageReferenceEvent,
answer: tg.isUndefined,
},
}; };
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never; type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;

View File

@ -0,0 +1,26 @@
import * as tg from "generic-type-guard";
export const triggerActionMessage = "triggerActionMessage";
export const removeActionMessage = "removeActionMessage";
export const isActionMessageType = tg.isSingletonStringUnion("message", "warning");
export type ActionMessageType = tg.GuardedType<typeof isActionMessageType>;
export const isTriggerActionMessageEvent = new tg.IsInterface()
.withProperties({
message: tg.isString,
uuid: tg.isString,
type: isActionMessageType,
})
.get();
export type TriggerActionMessageEvent = tg.GuardedType<typeof isTriggerActionMessageEvent>;
export const isMessageReferenceEvent = new tg.IsInterface()
.withProperties({
uuid: tg.isString,
})
.get();
export type MessageReferenceEvent = tg.GuardedType<typeof isMessageReferenceEvent>;

View File

@ -0,0 +1,24 @@
import {
isMessageReferenceEvent,
isTriggerActionMessageEvent,
removeActionMessage,
triggerActionMessage,
} from './TriggerActionMessageEvent';
import * as tg from 'generic-type-guard';
const isTriggerMessageEventObject = new tg.IsInterface()
.withProperties({
type: tg.isSingletonString(triggerActionMessage),
data: isTriggerActionMessageEvent,
})
.get();
const isTriggerMessageRemoveEventObject = new tg.IsInterface()
.withProperties({
type: tg.isSingletonString(removeActionMessage),
data: isMessageReferenceEvent,
})
.get();
export const isTriggerMessageHandlerEvent = tg.isUnion(isTriggerMessageEventObject, isTriggerMessageRemoveEventObject);

View File

@ -1,4 +1,5 @@
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import type * as tg from "generic-type-guard";
import { ChatEvent, isChatEvent } from "./Events/ChatEvent"; import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
import { HtmlUtils } from "../WebRtc/HtmlUtils"; import { HtmlUtils } from "../WebRtc/HtmlUtils";
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent"; import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
@ -121,7 +122,7 @@ class IframeListener {
init() { init() {
window.addEventListener( window.addEventListener(
"message", "message",
(message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => { (message: MessageEvent<unknown>) => {
// Do we trust the sender of this message? // Do we trust the sender of this message?
// Let's only accept messages from the iframe that are allowed. // Let's only accept messages from the iframe that are allowed.
// Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain). // Note: maybe we could restrict on the domain too for additional security (in case the iframe goes to another domain).
@ -416,6 +417,15 @@ class IframeListener {
}); });
} }
sendActionMessageTriggered(uuid: string): void {
this.postMessage({
type: "messageTriggered",
data: {
uuid,
},
});
}
/** /**
* Sends the message... to all allowed iframes. * Sends the message... to all allowed iframes.
*/ */

View File

@ -1,51 +1,66 @@
import type * as tg from "generic-type-guard"; import type * as tg from "generic-type-guard";
import type { import type {
IframeEvent, IframeEvent,
IframeEventMap, IframeQuery, IframeEventMap,
IframeQuery,
IframeQueryMap, IframeQueryMap,
IframeResponseEventMap IframeResponseEventMap,
} from '../Events/IframeEvent'; } from "../Events/IframeEvent";
import type {IframeQueryWrapper} from "../Events/IframeEvent"; import type { IframeQueryWrapper } from "../Events/IframeEvent";
export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) { export function sendToWorkadventure(content: IframeEvent<keyof IframeEventMap>) {
window.parent.postMessage(content, "*") window.parent.postMessage(content, "*");
} }
let queryNumber = 0; let queryNumber = 0;
export const answerPromises = new Map<number, { export const answerPromises = new Map<
resolve: (value: (IframeQueryMap[keyof IframeQueryMap]['answer'] | PromiseLike<IframeQueryMap[keyof IframeQueryMap]['answer']>)) => void, number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any {
reject: (reason?: any) => void resolve: (
}>(); value:
| IframeQueryMap[keyof IframeQueryMap]["answer"]
| PromiseLike<IframeQueryMap[keyof IframeQueryMap]["answer"]>
) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (reason?: any) => void;
}
>();
export function queryWorkadventure<T extends keyof IframeQueryMap>(content: IframeQuery<T>): Promise<IframeQueryMap[T]['answer']> { export function queryWorkadventure<T extends keyof IframeQueryMap>(
return new Promise<IframeQueryMap[T]['answer']>((resolve, reject) => { content: IframeQuery<T>
window.parent.postMessage({ ): Promise<IframeQueryMap[T]["answer"]> {
id: queryNumber, return new Promise<IframeQueryMap[T]["answer"]>((resolve, reject) => {
query: content window.parent.postMessage(
} as IframeQueryWrapper<T>, "*"); {
id: queryNumber,
query: content,
} as IframeQueryWrapper<T>,
"*"
);
answerPromises.set(queryNumber, { answerPromises.set(queryNumber, {
resolve, resolve,
reject reject,
}); });
queryNumber++; queryNumber++;
}); });
} }
type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never type GuardedType<Guard extends tg.TypeGuard<unknown>> = Guard extends tg.TypeGuard<infer T> ? T : never;
export interface IframeCallback<Key extends keyof IframeResponseEventMap, T = IframeResponseEventMap[Key], Guard = tg.TypeGuard<T>> { export interface IframeCallback<
Key extends keyof IframeResponseEventMap,
typeChecker: Guard, T = IframeResponseEventMap[Key],
callback: (payloadData: T) => void Guard = tg.TypeGuard<T>
> {
typeChecker: Guard;
callback: (payloadData: T) => void;
} }
export interface IframeCallbackContribution<Key extends keyof IframeResponseEventMap> extends IframeCallback<Key> { export interface IframeCallbackContribution<Key extends keyof IframeResponseEventMap> extends IframeCallback<Key> {
type: Key;
type: Key
} }
/** /**
@ -54,9 +69,10 @@ export interface IframeCallbackContribution<Key extends keyof IframeResponseEven
* *
*/ */
export abstract class IframeApiContribution<T extends { export abstract class IframeApiContribution<
callbacks: Array<IframeCallbackContribution<keyof IframeResponseEventMap>>, T extends {
}> { callbacks: Array<IframeCallbackContribution<keyof IframeResponseEventMap>>;
}
abstract callbacks: T["callbacks"] > {
abstract callbacks: T["callbacks"];
} }

View File

@ -0,0 +1,56 @@
import {
ActionMessageType,
MessageReferenceEvent,
removeActionMessage,
triggerActionMessage,
TriggerActionMessageEvent,
} from "../../Events/ui/TriggerActionMessageEvent";
import { queryWorkadventure } from "../IframeApiContribution";
import type { ActionMessageOptions } from "../ui";
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0,
v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export class ActionMessage {
public readonly uuid: string;
private readonly type: ActionMessageType;
private readonly message: string;
private readonly callback: () => void;
constructor(actionMessageOptions: ActionMessageOptions, private onRemove: () => void) {
this.uuid = uuidv4();
this.message = actionMessageOptions.message;
this.type = actionMessageOptions.type ?? "message";
this.callback = actionMessageOptions.callback;
this.create();
}
private async create() {
await queryWorkadventure({
type: triggerActionMessage,
data: {
message: this.message,
type: this.type,
uuid: this.uuid,
} as TriggerActionMessageEvent,
});
}
async remove() {
await queryWorkadventure({
type: removeActionMessage,
data: {
uuid: this.uuid,
} as MessageReferenceEvent,
});
this.onRemove();
}
triggerCallback() {
this.callback();
}
}

View File

@ -1,10 +1,11 @@
import { isButtonClickedEvent } from "../Events/ButtonClickedEvent"; import { isButtonClickedEvent } from "../Events/ButtonClickedEvent";
import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent"; import { isMenuItemClickedEvent } from "../Events/ui/MenuItemClickedEvent";
import type { MenuItemRegisterEvent } from "../Events/ui/MenuItemRegisterEvent";
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor"; import type { ButtonClickedCallback, ButtonDescriptor } from "./Ui/ButtonDescriptor";
import { Popup } from "./Ui/Popup"; import { Popup } from "./Ui/Popup";
import { ActionMessage } from "./Ui/ActionMessage";
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
let popupId = 0; let popupId = 0;
const popups: Map<number, Popup> = new Map<number, Popup>(); const popups: Map<number, Popup> = new Map<number, Popup>();
@ -14,6 +15,7 @@ const popupCallbacks: Map<number, Map<number, ButtonClickedCallback>> = new Map<
>(); >();
const menuCallbacks: Map<string, (command: string) => void> = new Map(); const menuCallbacks: Map<string, (command: string) => void> = new Map();
const actionMessages = new Map<string, ActionMessage>();
interface ZonedPopupOptions { interface ZonedPopupOptions {
zone: string; zone: string;
@ -23,6 +25,12 @@ interface ZonedPopupOptions {
popupOptions: Array<ButtonDescriptor>; popupOptions: Array<ButtonDescriptor>;
} }
export interface ActionMessageOptions {
message: string;
type?: "message" | "warning";
callback: () => void;
}
export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> { export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
callbacks = [ callbacks = [
apiCallback({ apiCallback({
@ -49,6 +57,16 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
} }
}, },
}), }),
apiCallback({
type: "messageTriggered",
typeChecker: isMessageReferenceEvent,
callback: (event) => {
const actionMessage = actionMessages.get(event.uuid);
if (actionMessage) {
actionMessage.triggerCallback();
}
},
}),
]; ];
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup {
@ -103,6 +121,14 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
removeBubble(): void { removeBubble(): void {
sendToWorkadventure({ type: "removeBubble", data: null }); sendToWorkadventure({ type: "removeBubble", data: null });
} }
displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {
const actionMessage = new ActionMessage(actionMessageOptions, () => {
actionMessages.delete(actionMessage.uuid);
});
actionMessages.set(actionMessage.uuid, actionMessage);
return actionMessage;
}
} }
export default new WorkAdventureUiCommands(); export default new WorkAdventureUiCommands();

View File

@ -1,26 +1,5 @@
<script lang="ts"> <script lang="ts">
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { onDestroy, onMount } from "svelte";
import { get } from "svelte/store";
onMount(() => {
for (const action of get(layoutManagerActionStore)) {
action.userInputManager?.addSpaceEventListner(action.callback);
if ( action.type === 'warning') {
//remove it after 10 sec
setTimeout(() => {
layoutManagerActionStore.removeAction(action);
}, 10000)
}
}
})
onDestroy(() => {
for (const action of get(layoutManagerActionStore)) {
action.userInputManager?.removeSpaceEventListner(action.callback);
}
layoutManagerActionStore.clearActions();
})
function onClick(callback: () => void) { function onClick(callback: () => void) {
callback(); callback();
@ -75,4 +54,4 @@
50% {bottom: 30px;} 50% {bottom: 30px;}
100% {bottom: 40px;} 100% {bottom: 40px;}
} }
</style> </style>

View File

@ -86,7 +86,7 @@ import { playersStore } from "../../Stores/PlayersStore";
import { chatVisibilityStore } from "../../Stores/ChatStore"; import { chatVisibilityStore } from "../../Stores/ChatStore";
import Tileset = Phaser.Tilemaps.Tileset; import Tileset = Phaser.Tilemaps.Tileset;
import { userIsAdminStore } from "../../Stores/GameStore"; import { userIsAdminStore } from "../../Stores/GameStore";
import { layoutManagerActionStore, layoutManagerVisibilityStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
@ -792,7 +792,7 @@ export class GameScene extends DirtyScene {
}); });
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
if (newValue === undefined) { if (newValue === undefined) {
layoutManagerVisibilityStore.set(false); layoutManagerActionStore.removeAction("openWebsite");
coWebsiteManager.closeCoWebsite(); coWebsiteManager.closeCoWebsite();
} else { } else {
const openWebsiteFunction = () => { const openWebsiteFunction = () => {
@ -802,7 +802,7 @@ export class GameScene extends DirtyScene {
allProps.get("openWebsiteAllowApi") as boolean | undefined, allProps.get("openWebsiteAllowApi") as boolean | undefined,
allProps.get("openWebsitePolicy") as string | undefined allProps.get("openWebsitePolicy") as string | undefined
); );
layoutManagerVisibilityStore.set(false); layoutManagerActionStore.removeAction("openWebsite");
}; };
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES); const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
@ -812,12 +812,12 @@ export class GameScene extends DirtyScene {
message = "Press SPACE or touch here to open web site"; message = "Press SPACE or touch here to open web site";
} }
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({
type: "openWebsite", uuid: "openWebsite",
type: "message",
message: message, message: message,
callback: () => openWebsiteFunction(), callback: () => openWebsiteFunction(),
userInputManager: this.userInputManager, userInputManager: this.userInputManager,
}); });
layoutManagerVisibilityStore.set(true);
} else { } else {
openWebsiteFunction(); openWebsiteFunction();
} }
@ -825,7 +825,7 @@ export class GameScene extends DirtyScene {
}); });
this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => {
if (newValue === undefined) { if (newValue === undefined) {
layoutManagerVisibilityStore.set(false); layoutManagerActionStore.removeAction("jitsi");
this.stopJitsi(); this.stopJitsi();
} else { } else {
const openJitsiRoomFunction = () => { const openJitsiRoomFunction = () => {
@ -838,7 +838,7 @@ export class GameScene extends DirtyScene {
} else { } else {
this.startJitsi(roomName, undefined); this.startJitsi(roomName, undefined);
} }
layoutManagerVisibilityStore.set(false); layoutManagerActionStore.removeAction("jitsi");
}; };
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES); const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
@ -848,12 +848,12 @@ export class GameScene extends DirtyScene {
message = "Press SPACE or touch here to enter Jitsi Meet room"; message = "Press SPACE or touch here to enter Jitsi Meet room";
} }
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({
type: "jitsiRoom", uuid: "jitsi",
type: "message",
message: message, message: message,
callback: () => openJitsiRoomFunction(), callback: () => openJitsiRoomFunction(),
userInputManager: this.userInputManager, userInputManager: this.userInputManager,
}); });
layoutManagerVisibilityStore.set(true);
} else { } else {
openJitsiRoomFunction(); openJitsiRoomFunction();
} }
@ -909,7 +909,7 @@ export class GameScene extends DirtyScene {
let html = `<div id="container" hidden><div class="nes-container with-title is-centered"> let html = `<div id="container" hidden><div class="nes-container with-title is-centered">
${escapedMessage} ${escapedMessage}
</div> `; </div> `;
const buttonContainer = `<div class="buttonContainer"</div>`; const buttonContainer = '<div class="buttonContainer"</div>';
html += buttonContainer; html += buttonContainer;
let id = 0; let id = 0;
for (const button of openPopupEvent.buttons) { for (const button of openPopupEvent.buttons) {
@ -1149,6 +1149,23 @@ ${escapedMessage}
}); });
}); });
}); });
iframeListener.registerAnswerer("triggerActionMessage", (message) =>
layoutManagerActionStore.addAction({
uuid: message.uuid,
type: "message",
message: message.message,
callback: () => {
layoutManagerActionStore.removeAction(message.uuid);
iframeListener.sendActionMessageTriggered(message.uuid);
},
userInputManager: this.userInputManager,
})
);
iframeListener.registerAnswerer("removeActionMessage", (message) => {
layoutManagerActionStore.removeAction(message.uuid);
});
} }
private setPropertyLayer( private setPropertyLayer(
@ -1271,6 +1288,10 @@ ${escapedMessage}
this.biggestAvailableAreaStoreUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe();
iframeListener.unregisterAnswerer("getState"); iframeListener.unregisterAnswerer("getState");
iframeListener.unregisterAnswerer("loadTileset"); iframeListener.unregisterAnswerer("loadTileset");
iframeListener.unregisterAnswerer("getMapData");
iframeListener.unregisterAnswerer("getState");
iframeListener.unregisterAnswerer("triggerActionMessage");
iframeListener.unregisterAnswerer("removeActionMessage");
this.sharedVariablesManager?.close(); this.sharedVariablesManager?.close();
mediaManager.hideGameOverlay(); mediaManager.hideGameOverlay();

View File

@ -1,15 +1,14 @@
import { writable } from "svelte/store"; import { derived, writable } from "svelte/store";
import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager";
export interface LayoutManagerAction { export interface LayoutManagerAction {
type: string; uuid: string;
type: "warning" | "message";
message: string | number | boolean | undefined; message: string | number | boolean | undefined;
callback: () => void; callback: () => void;
userInputManager: UserInputManager | undefined; userInputManager: UserInputManager | undefined;
} }
export const layoutManagerVisibilityStore = writable(false);
function createLayoutManagerAction() { function createLayoutManagerAction() {
const { subscribe, set, update } = writable<LayoutManagerAction[]>([]); const { subscribe, set, update } = writable<LayoutManagerAction[]>([]);
@ -18,26 +17,26 @@ function createLayoutManagerAction() {
addAction: (newAction: LayoutManagerAction): void => { addAction: (newAction: LayoutManagerAction): void => {
update((list: LayoutManagerAction[]) => { update((list: LayoutManagerAction[]) => {
let found = false; let found = false;
for (const actions of list) { for (const action of list) {
if (actions.type === newAction.type && actions.message === newAction.message) { if (action.uuid === newAction.uuid) {
found = true; found = true;
} }
} }
if (!found) { if (!found) {
list.push(newAction); list.push(newAction);
newAction.userInputManager?.addSpaceEventListner(newAction.callback);
} }
return list; return list;
}); });
}, },
removeAction: (oldAction: LayoutManagerAction): void => { removeAction: (uuid: string): void => {
update((list: LayoutManagerAction[]) => { update((list: LayoutManagerAction[]) => {
const index = list.findIndex( const index = list.findIndex((action) => action.uuid === uuid);
(actions) => actions.type === oldAction.type && actions.message === oldAction.message
);
if (index !== -1) { if (index !== -1) {
list[index].userInputManager?.removeSpaceEventListner(list[index].callback);
list.splice(index, 1); list.splice(index, 1);
} }
@ -51,3 +50,7 @@ function createLayoutManagerAction() {
} }
export const layoutManagerActionStore = createLayoutManagerAction(); export const layoutManagerActionStore = createLayoutManagerAction();
export const layoutManagerVisibilityStore = derived(layoutManagerActionStore, ($layoutManagerActionStore) => {
return !!$layoutManagerActionStore.length;
});

View File

@ -25,15 +25,18 @@ export class MediaManager {
if (result.type === "error") { if (result.type === "error") {
console.error(result.error); console.error(result.error);
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({
uuid: "cameraAccessDenied",
type: "warning", type: "warning",
message: "Camera access denied. Click here and check your browser permissions.", message: "Camera access denied. Click here and check your browser permissions.",
callback: () => { callback: () => {
helpCameraSettingsVisibleStore.set(true); helpCameraSettingsVisibleStore.set(true);
layoutManagerVisibilityStore.set(false);
}, },
userInputManager: this.userInputManager, userInputManager: this.userInputManager,
}); });
layoutManagerVisibilityStore.set(true); //remove it after 10 sec
setTimeout(() => {
layoutManagerActionStore.removeAction("cameraAccessDenied");
}, 10000);
return; return;
} }
}); });
@ -42,15 +45,18 @@ export class MediaManager {
if (result.type === "error") { if (result.type === "error") {
console.error(result.error); console.error(result.error);
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({
uuid: "screenSharingAccessDenied",
type: "warning", type: "warning",
message: "Screen sharing denied. Click here and check your browser permissions.", message: "Screen sharing denied. Click here and check your browser permissions.",
callback: () => { callback: () => {
helpCameraSettingsVisibleStore.set(true); helpCameraSettingsVisibleStore.set(true);
layoutManagerVisibilityStore.set(false);
}, },
userInputManager: this.userInputManager, userInputManager: this.userInputManager,
}); });
layoutManagerVisibilityStore.set(true); //remove it after 10 sec
setTimeout(() => {
layoutManagerActionStore.removeAction("screenSharingAccessDenied");
}, 10000);
return; return;
} }
}); });

View File

@ -0,0 +1,16 @@
WA.onInit().then(() => {
let message;
WA.room.onEnterZone("carpet", () => {
message = WA.ui.displayActionMessage({
message: "This is a test message. Press space to display a chat message. Walk out to hide the message.",
callback: () => {
WA.chat.sendChatMessage("Hello world!", "The bot");
}
});
});
WA.room.onLeaveZone("carpet", () => {
message && message.remove();
});
});

View File

@ -0,0 +1,106 @@
{ "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":"carpet",
"opacity":1,
"properties":[
{
"name":"zone",
"type":"string",
"value":"carpet"
}],
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":3,
"name":"floorLayer",
"objects":[
{
"height":304.037037037037,
"id":3,
"name":"",
"rotation":0,
"text":
{
"fontfamily":"Sans Serif",
"pixelsize":11,
"text":"Test:\nWalk on the carpet\n\nResult:\nA message is displayed at the bottom of the screen\n\nTest:\nPress space\n\nResult:\nA chat message is displayed\n\n\nTest:\nWalk out of the carpet\n\nResult:\nThe message is hidden\n",
"wrap":true
},
"type":"",
"visible":true,
"width":252.4375,
"x":2.78125,
"y":2.5
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":9,
"nextobjectid":11,
"orientation":"orthogonal",
"properties":[
{
"name":"script",
"type":"string",
"value":"script.js"
}],
"renderorder":"right-down",
"tiledversion":"2021.03.23",
"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.5,
"width":10
}

View File

@ -202,6 +202,14 @@
<a href="#" class="testLink" data-testmap="Variables/shared_variables.json" target="_blank">Testing shared scripting variables</a> <a href="#" class="testLink" data-testmap="Variables/shared_variables.json" target="_blank">Testing shared scripting variables</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-trigger-message-api"> Success <input type="radio" name="test-trigger-message-api"> Failure <input type="radio" name="test-trigger-message-api" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="TriggerMessageApi/triggerMessage.json" target="_blank">Testing trigger message API</a>
</td>
</tr>
</table> </table>
<script> <script>

View File

@ -1,40 +1,41 @@
///<reference path="../../front/src/iframe_api.ts" />
console.log('SCRIPT LAUNCHED'); console.log('SCRIPT LAUNCHED');
//WA.sendChatMessage('Hi, my name is Poly and I repeat what you say!', 'Poly Parrot'); //WA.sendChatMessage('Hi, my name is Poly and I repeat what you say!', 'Poly Parrot');
var isFirstTimeTuto = false; var isFirstTimeTuto = false;
var textFirstPopup = 'Hey ! This is how to open start a discussion with someone ! You can be 4 max in a booble'; var textFirstPopup = 'Hey ! This is how to open start a discussion with someone ! You can be 4 max in a booble';
var textSecondPopup = 'You can also use the chat to communicate ! '; var textSecondPopup = 'You can also use the chat to communicate ! ';
var targetObjectTutoBubble ='myPopup1'; var targetObjectTutoBubble = 'myPopup1';
var targetObjectTutoChat ='myPopup2'; var targetObjectTutoChat = 'myPopup2';
var popUpExplanation = undefined; var popUpExplanation = undefined;
function launchTuto (){ function launchTuto() {
WA.ui.openPopup(targetObjectTutoBubble, textFirstPopup, [ WA.ui.openPopup(targetObjectTutoBubble, textFirstPopup, [
{ {
label: "Next", label: "Next",
className: "popUpElement", className: "popUpElement",
callback: (popup) => { callback: (popup) => {
popup.close(); popup.close();
WA.ui.openPopup(targetObjectTutoChat, textSecondPopup, [ WA.ui.openPopup(targetObjectTutoChat, textSecondPopup, [
{ {
label: "Open Chat", label: "Open Chat",
className: "popUpElement", className: "popUpElement",
callback: (popup1) => { callback: (popup1) => {
WA.chat.sendChatMessage("Hey you can talk here too ! ", 'WA Guide'); WA.chat.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
popup1.close(); popup1.close();
WA.controls.restorePlayerControls(); WA.controls.restorePlayerControls();
}
} }
}
]) ])
}
} }
]); }
WA.controls.disablePlayerControls(); ]);
WA.controls.disablePlayerControls();
} }
WA.chat.onChatMessage((message => { WA.chat.onChatMessage((message => {
console.log('CHAT MESSAGE RECEIVED BY SCRIPT'); console.log('CHAT MESSAGE RECEIVED BY SCRIPT');
WA.chat.sendChatMessage('Poly Parrot says: "'+message+'"', 'Poly Parrot'); WA.chat.sendChatMessage('Poly Parrot says: "' + message + '"', 'Poly Parrot');
})); }));
WA.room.onEnterZone('myTrigger', () => { WA.room.onEnterZone('myTrigger', () => {
@ -50,11 +51,11 @@ WA.room.onEnterZone('notExist', () => {
WA.room.onEnterZone('popupZone', () => { WA.room.onEnterZone('popupZone', () => {
WA.ui.displayBubble(); WA.ui.displayBubble();
if (!isFirstTimeTuto) { if(!isFirstTimeTuto) {
isFirstTimeTuto = true; isFirstTimeTuto = true;
launchTuto(); launchTuto();
} }
else popUpExplanation = WA.ui.openPopup(targetObjectTutoChat,'Do you want to review the explanation ? ', [ else popUpExplanation = WA.ui.openPopup(targetObjectTutoChat, 'Do you want to review the explanation ? ', [
{ {
label: "No", label: "No",
className: "popUpElementReviewexplanation", className: "popUpElementReviewexplanation",
@ -74,6 +75,13 @@ WA.room.onEnterZone('popupZone', () => {
}); });
WA.room.onLeaveZone('popupZone', () => { WA.room.onLeaveZone('popupZone', () => {
if (popUpExplanation !== undefined) popUpExplanation.close(); if(popUpExplanation !== undefined) popUpExplanation.close();
WA.ui.removeBubble(); WA.ui.removeBubble();
}) })
const message = WA.ui.displayActionMessage("testMessage", () => {
WA.chat.sendChatMessage("triggered", "triggerbot");
})
setTimeout(() => {
message.remove();
}, 5000)

12
package-lock.json generated Normal file
View File

@ -0,0 +1,12 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"husky": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz",
"integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==",
"dev": true
}
}
}