From 6f4c3a89cd6f2efc6741091af04ab8a37295d497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 14 Mar 2022 09:57:56 +0100 Subject: [PATCH 1/3] Improving openapi doc --- .../src/Controller/AuthenticateController.ts | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 82760e62..689addbb 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -84,30 +84,75 @@ export class AuthenticateController extends BaseHttpController { * get: * description: TODO * parameters: + * - name: "code" + * in: "query" + * description: "todo" + * required: false + * type: "string" * - name: "nonce" * in: "query" * description: "todo" - * required: true + * required: false * type: "string" - * - name: "state" + * - name: "token" * in: "query" * description: "todo" - * required: true + * required: false * type: "string" * - name: "playUri" * in: "query" * description: "todo" - * required: false - * type: "string" - * - name: "redirect" - * in: "query" - * description: "todo" - * required: false + * required: true * type: "string" * responses: * 200: - * description: TODO - * + * description: NOTE - THERE ARE ADDITIONAL PROPERTIES NOT DISPLAYED HERE. THEY COME FROM THE CALL TO openIDClient.checkTokenAuth + * content: + * application/json: + * schema: + * type: object + * properties: + * authToken: + * type: string + * description: A new JWT token (if no token was passed in parameter), or returns the token that was passed in parameter if one was supplied + * username: + * type: string|undefined + * description: Contains the username stored in the JWT token passed in parameter. If no token was passed, contains the data from OpenID. + * example: John Doe + * locale: + * type: string|undefined + * description: Contains the locale stored in the JWT token passed in parameter. If no token was passed, contains the data from OpenID. + * example: fr_FR + * email: + * type: string + * description: TODO + * example: TODO + * userUuid: + * type: string + * description: TODO + * example: TODO + * visitCardUrl: + * type: string|null + * description: TODO + * example: TODO + * tags: + * type: array + * description: The list of tags of the user + * items: + * type: string + * example: speaker + * textures: + * type: array + * description: The list of textures of the user + * items: + * type: TODO + * example: TODO + * messages: + * type: array + * description: The list of messages to be displayed to the user + * items: + * type: TODO + * example: TODO */ //eslint-disable-next-line @typescript-eslint/no-misused-promises this.app.get("/login-callback", async (req, res) => { From 55db6a9b126f7053343e3bb34fcb064c4ff7c53d Mon Sep 17 00:00:00 2001 From: Lurkars Date: Mon, 14 Mar 2022 10:14:35 +0100 Subject: [PATCH 2/3] apply textures on openid login, fix pusher errors on woka list (#1961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * apply textures on openid login, fix pusher errors on woka list * remove logging * Returning a HTTP 400 id roomUrl parameter not set Co-authored-by: David NĂ©grier --- front/src/Connexion/ConnectionManager.ts | 21 ++++++++++++++++--- front/src/Phaser/Login/CustomizeScene.ts | 2 +- .../src/Phaser/Login/SelectCharacterScene.ts | 2 +- pusher/src/Controller/WokaListController.ts | 18 +++++++--------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 004f9039..7578a942 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -330,9 +330,12 @@ class ConnectionManager { throw new Error("No Auth code provided"); } } - const { authToken, userUuid, email, username, locale } = await Axios.get(`${PUSHER_URL}/login-callback`, { - params: { code, nonce, token, playUri: this.currentRoom?.key }, - }).then((res) => { + const { authToken, userUuid, email, username, locale, textures } = await Axios.get( + `${PUSHER_URL}/login-callback`, + { + params: { code, nonce, token, playUri: this.currentRoom?.key }, + } + ).then((res) => { return res.data; }); localUserStore.setAuthToken(authToken); @@ -361,6 +364,18 @@ class ConnectionManager { } } + if (textures) { + const layers: string[] = []; + for (const texture of textures) { + if (texture !== undefined) { + layers.push(texture.id); + } + } + if (layers.length > 0) { + gameManager.setCharacterLayers(layers); + } + } + //user connected, set connected store for menu at true userIsConnected.set(true); } diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 918cf9cf..30fbd77d 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -46,7 +46,7 @@ export class CustomizeScene extends AbstractCharacterScene { // FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!) this.load.json( wokaMetadataKey, - `${PUSHER_URL}/woka/list/` + encodeURIComponent(window.location.href), + `${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href), undefined, { responseType: "text", diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index f7fd3c8a..064739f5 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -50,7 +50,7 @@ export class SelectCharacterScene extends AbstractCharacterScene { // FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!) this.load.json( wokaMetadataKey, - `${PUSHER_URL}/woka/list/` + encodeURIComponent(window.location.href), + `${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href), undefined, { responseType: "text", diff --git a/pusher/src/Controller/WokaListController.ts b/pusher/src/Controller/WokaListController.ts index 56300e90..42d3a4c5 100644 --- a/pusher/src/Controller/WokaListController.ts +++ b/pusher/src/Controller/WokaListController.ts @@ -1,17 +1,17 @@ import { BaseHttpController } from "./BaseHttpController"; +import { parse } from "query-string"; import { wokaService } from "../Services/WokaService"; -import * as tg from "generic-type-guard"; import { jwtTokenManager } from "../Services/JWTTokenManager"; export class WokaListController extends BaseHttpController { routes() { - this.app.options("/woka/list/:roomUrl", {}, (req, res) => { + this.app.options("/woka/list", {}, (req, res) => { res.status(200).send(""); return; }); // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.app.get("/woka/list/:roomUrl", {}, async (req, res) => { + this.app.get("/woka/list", {}, async (req, res) => { const token = req.header("Authorization"); if (!token) { @@ -29,17 +29,13 @@ export class WokaListController extends BaseHttpController { return; } - const isParameters = new tg.IsInterface() - .withProperties({ - roomUrl: tg.isString, - }) - .get(); + let { roomUrl } = parse(req.path_query); - if (!isParameters(req.path_parameters)) { - return res.status(400).send("Unknown parameters"); + if (typeof roomUrl !== "string") { + return res.status(400).send("missing roomUrl URL parameter"); } - const roomUrl = decodeURIComponent(req.path_parameters.roomUrl); + roomUrl = decodeURIComponent(roomUrl); const wokaList = await wokaService.getWokaList(roomUrl, req.params["uuid"]); if (!wokaList) { From d4dcd0d5ce35cbbe2982daee60b57b6b8950625e Mon Sep 17 00:00:00 2001 From: Piotr Hanusiak Date: Mon, 14 Mar 2022 10:15:10 +0100 Subject: [PATCH 3/3] Actions menu api (#1862) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * random action on click * removing actions * register single key per command use * change removeActionsMenu action name * fixed actions menu not hiding content properly: * actions menu fix * added mock Block Player action * ActionsMenu buttons styling * added displaying priority for menu actions * moved utils actionMenu features to the UI * import as a type: * more object oriented style for API * removed registered actions from RemotePlayer instance * readme update * Fixing typos / Improving wording * added instructions on AlterActionsMenu test map Co-authored-by: Hanusiak Piotr Co-authored-by: David NĂ©grier --- docs/maps/api-ui.md | 31 ++++ docs/maps/images/actions-menu-1.png | Bin 0 -> 7040 bytes .../Events/ActionsMenuActionClickedEvent.ts | 12 ++ .../AddActionsMenuKeyToRemotePlayerEvent.ts | 12 ++ front/src/Api/Events/IframeEvent.ts | 9 + .../Api/Events/RemotePlayerClickedEvent.ts | 15 ++ ...moveActionsMenuKeyFromRemotePlayerEvent.ts | 16 ++ front/src/Api/IframeListener.ts | 43 +++++ front/src/Api/iframe/ui.ts | 118 ++++++++++++- .../Components/ActionsMenu/ActionsMenu.svelte | 26 ++- front/src/Phaser/Entity/RemotePlayer.ts | 67 +++++--- front/src/Phaser/Game/GameScene.ts | 24 ++- front/src/Stores/ActionsMenuStore.ts | 20 ++- maps/tests/AlterActionMenuApi/map.json | 162 ++++++++++++++++++ maps/tests/AlterActionMenuApi/script.php | 43 +++++ maps/tests/index.html | 8 + 16 files changed, 565 insertions(+), 41 deletions(-) create mode 100644 docs/maps/images/actions-menu-1.png create mode 100644 front/src/Api/Events/ActionsMenuActionClickedEvent.ts create mode 100644 front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts create mode 100644 front/src/Api/Events/RemotePlayerClickedEvent.ts create mode 100644 front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts create mode 100644 maps/tests/AlterActionMenuApi/map.json create mode 100644 maps/tests/AlterActionMenuApi/script.php diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md index 8583c061..2fb97e0a 100644 --- a/docs/maps/api-ui.md +++ b/docs/maps/api-ui.md @@ -162,3 +162,34 @@ class ActionMessage { remove() {}; } ``` + +### Adding custom ActionsMenu Action + +When clicking on other player's WOKA, the contextual menu (we call it ActionsMenu) is displayed with some default Actions. It is possible to add custom actions right when player is clicked: + +
+ +
+ +To do that, we need to listen for the `onRemotePlayerClicked` stream and make use of the `remotePlayer` object that is passed by as a payload. + +```javascript +WA.ui.onRemotePlayerClicked.subscribe((remotePlayer) => { + remotePlayer.addAction('Ask to tell a joke', () => { + console.log('I am NOT telling you a joke!'); + }); +} +``` + +`remotePlayer.addAction(actionName, callback)` returns an Action object, which can remove itself from ActionsMenu: +```javascript +const action = remotePlayer.addAction('This will disappear!', () => { + console.log('You managed to click me!'); +}); +setTimeout( + () => { + action.remove(); + }, + 1000, +); +``` diff --git a/docs/maps/images/actions-menu-1.png b/docs/maps/images/actions-menu-1.png new file mode 100644 index 0000000000000000000000000000000000000000..e592ead04913d83e1d1251e349dc093c53479cc1 GIT binary patch literal 7040 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 literal 0 HcmV?d00001 diff --git a/front/src/Api/Events/ActionsMenuActionClickedEvent.ts b/front/src/Api/Events/ActionsMenuActionClickedEvent.ts new file mode 100644 index 00000000..4ff5485a --- /dev/null +++ b/front/src/Api/Events/ActionsMenuActionClickedEvent.ts @@ -0,0 +1,12 @@ +import * as tg from "generic-type-guard"; + +export const isActionsMenuActionClickedEvent = new tg.IsInterface() + .withProperties({ + id: tg.isNumber, + actionName: tg.isString, + }) + .get(); + +export type ActionsMenuActionClickedEvent = tg.GuardedType; + +export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void; diff --git a/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts b/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts new file mode 100644 index 00000000..6741d730 --- /dev/null +++ b/front/src/Api/Events/AddActionsMenuKeyToRemotePlayerEvent.ts @@ -0,0 +1,12 @@ +import * as tg from "generic-type-guard"; + +export const isAddActionsMenuKeyToRemotePlayerEvent = new tg.IsInterface() + .withProperties({ + id: tg.isNumber, + actionKey: tg.isString, + }) + .get(); + +export type AddActionsMenuKeyToRemotePlayerEvent = tg.GuardedType; + +export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index e56699a7..fc48b040 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -36,6 +36,10 @@ import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent"; import { isColorEvent } from "./ColorEvent"; import { isMovePlayerToEventConfig } from "./MovePlayerToEvent"; import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer"; +import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent"; +import type { AddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent"; +import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent"; +import type { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -45,6 +49,8 @@ export interface TypedMessageEvent extends MessageEvent { * List event types sent from an iFrame to WorkAdventure */ export type IframeEventMap = { + addActionsMenuKeyToRemotePlayer: AddActionsMenuKeyToRemotePlayerEvent; + removeActionsMenuKeyFromRemotePlayer: RemoveActionsMenuKeyFromRemotePlayerEvent; loadPage: LoadPageEvent; chat: ChatEvent; cameraFollowPlayer: CameraFollowPlayerEvent; @@ -58,6 +64,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; + onOpenActionMenu: undefined; onCameraUpdate: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; @@ -90,6 +97,8 @@ export interface IframeResponseEventMap { enterZoneEvent: ChangeZoneEvent; leaveZoneEvent: ChangeZoneEvent; buttonClickedEvent: ButtonClickedEvent; + remotePlayerClickedEvent: RemotePlayerClickedEvent; + actionsMenuActionClickedEvent: ActionsMenuActionClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; wasCameraUpdated: WasCameraUpdatedEvent; menuItemClicked: MenuItemClickedEvent; diff --git a/front/src/Api/Events/RemotePlayerClickedEvent.ts b/front/src/Api/Events/RemotePlayerClickedEvent.ts new file mode 100644 index 00000000..bf217adc --- /dev/null +++ b/front/src/Api/Events/RemotePlayerClickedEvent.ts @@ -0,0 +1,15 @@ +import * as tg from "generic-type-guard"; + +// TODO: Change for player Clicked, add all neccessary data +export const isRemotePlayerClickedEvent = new tg.IsInterface() + .withProperties({ + id: tg.isNumber, + }) + .get(); + +/** + * A message sent from the game to the iFrame when RemotePlayer is clicked. + */ +export type RemotePlayerClickedEvent = tg.GuardedType; + +export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void; diff --git a/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts b/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts new file mode 100644 index 00000000..745a07df --- /dev/null +++ b/front/src/Api/Events/RemoveActionsMenuKeyFromRemotePlayerEvent.ts @@ -0,0 +1,16 @@ +import * as tg from "generic-type-guard"; + +export const isRemoveActionsMenuKeyFromRemotePlayerEvent = new tg.IsInterface() + .withProperties({ + id: tg.isNumber, + actionKey: tg.isString, + }) + .get(); + +export type RemoveActionsMenuKeyFromRemotePlayerEvent = tg.GuardedType< + typeof isRemoveActionsMenuKeyFromRemotePlayerEvent +>; + +export type RemoveActionsMenuKeyFromRemotePlayerEventCallback = ( + event: RemoveActionsMenuKeyFromRemotePlayerEvent +) => void; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 0f850360..e3609b9f 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -34,6 +34,16 @@ import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent"; import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; +import type { RemotePlayerClickedEvent } from "./Events/RemotePlayerClickedEvent"; +import { + AddActionsMenuKeyToRemotePlayerEvent, + isAddActionsMenuKeyToRemotePlayerEvent, +} from "./Events/AddActionsMenuKeyToRemotePlayerEvent"; +import type { ActionsMenuActionClickedEvent } from "./Events/ActionsMenuActionClickedEvent"; +import { + isRemoveActionsMenuKeyFromRemotePlayerEvent, + RemoveActionsMenuKeyFromRemotePlayerEvent, +} from "./Events/RemoveActionsMenuKeyFromRemotePlayerEvent"; type AnswererCallback = ( query: IframeQueryMap[T]["query"], @@ -63,6 +73,15 @@ class IframeListener { private readonly _cameraFollowPlayerStream: Subject = new Subject(); public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable(); + private readonly _addActionsMenuKeyToRemotePlayerStream: Subject = + new Subject(); + public readonly addActionsMenuKeyToRemotePlayerStream = this._addActionsMenuKeyToRemotePlayerStream.asObservable(); + + private readonly _removeActionsMenuKeyFromRemotePlayerEvent: Subject = + new Subject(); + public readonly removeActionsMenuKeyFromRemotePlayerEvent = + this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable(); + private readonly _enablePlayerControlStream: Subject = new Subject(); public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable(); @@ -241,6 +260,16 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; + } else if ( + payload.type == "addActionsMenuKeyToRemotePlayer" && + isAddActionsMenuKeyToRemotePlayerEvent(payload.data) + ) { + this._addActionsMenuKeyToRemotePlayerStream.next(payload.data); + } else if ( + payload.type == "removeActionsMenuKeyFromRemotePlayer" && + isRemoveActionsMenuKeyFromRemotePlayerEvent(payload.data) + ) { + this._removeActionsMenuKeyFromRemotePlayerEvent.next(payload.data); } else if (payload.type == "onCameraUpdate") { this._trackCameraUpdateStream.next(); } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { @@ -439,6 +468,20 @@ class IframeListener { } } + sendRemotePlayerClickedEvent(event: RemotePlayerClickedEvent) { + this.postMessage({ + type: "remotePlayerClickedEvent", + data: event, + }); + } + + sendActionsMenuActionClickedEvent(event: ActionsMenuActionClickedEvent) { + this.postMessage({ + type: "actionsMenuActionClickedEvent", + data: event, + }); + } + sendCameraUpdated(event: WasCameraUpdatedEvent) { this.postMessage({ type: "wasCameraUpdated", diff --git a/front/src/Api/iframe/ui.ts b/front/src/Api/iframe/ui.ts index c4d40d16..9b109654 100644 --- a/front/src/Api/iframe/ui.ts +++ b/front/src/Api/iframe/ui.ts @@ -8,6 +8,12 @@ import { ActionMessage } from "./Ui/ActionMessage"; import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent"; import { Menu } from "./Ui/Menu"; import type { RequireOnlyOne } from "../types"; +import { isRemotePlayerClickedEvent, RemotePlayerClickedEvent } from "../Events/RemotePlayerClickedEvent"; +import { + ActionsMenuActionClickedEvent, + isActionsMenuActionClickedEvent, +} from "../Events/ActionsMenuActionClickedEvent"; +import { Observable, Subject } from "rxjs"; let popupId = 0; const popups: Map = new Map(); @@ -42,7 +48,77 @@ export interface ActionMessageOptions { callback: () => void; } +export interface RemotePlayerInterface { + addAction(key: string, callback: Function): void; +} + +export class RemotePlayer implements RemotePlayerInterface { + private id: number; + + private actions: Map = new Map(); + + constructor(id: number) { + this.id = id; + } + + public addAction(key: string, callback: Function): ActionsMenuAction { + const newAction = new ActionsMenuAction(this, key, callback); + this.actions.set(key, newAction); + sendToWorkadventure({ + type: "addActionsMenuKeyToRemotePlayer", + data: { id: this.id, actionKey: key }, + }); + return newAction; + } + + public callAction(key: string): void { + const action = this.actions.get(key); + if (action) { + action.call(); + } + } + + public removeAction(key: string): void { + this.actions.delete(key); + sendToWorkadventure({ + type: "removeActionsMenuKeyFromRemotePlayer", + data: { id: this.id, actionKey: key }, + }); + } +} + +export class ActionsMenuAction { + private remotePlayer: RemotePlayer; + private key: string; + private callback: Function; + + constructor(remotePlayer: RemotePlayer, key: string, callback: Function) { + this.remotePlayer = remotePlayer; + this.key = key; + this.callback = callback; + } + + public call(): void { + this.callback(); + } + + public remove(): void { + this.remotePlayer.removeAction(this.key); + } +} + export class WorkAdventureUiCommands extends IframeApiContribution { + public readonly _onRemotePlayerClicked: Subject; + public readonly onRemotePlayerClicked: Observable; + + private currentlyClickedRemotePlayer?: RemotePlayer; + + constructor() { + super(); + this._onRemotePlayerClicked = new Subject(); + this.onRemotePlayerClicked = this._onRemotePlayerClicked.asObservable(); + } + callbacks = [ apiCallback({ type: "buttonClickedEvent", @@ -82,9 +158,38 @@ export class WorkAdventureUiCommands extends IframeApiContribution { + this.currentlyClickedRemotePlayer = new RemotePlayer(payloadData.id); + this._onRemotePlayerClicked.next(this.currentlyClickedRemotePlayer); + }, + }), + apiCallback({ + type: "actionsMenuActionClickedEvent", + typeChecker: isActionsMenuActionClickedEvent, + callback: (payloadData: ActionsMenuActionClickedEvent) => { + this.currentlyClickedRemotePlayer?.callAction(payloadData.actionName); + }, + }), ]; - openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { + public addActionsMenuKeyToRemotePlayer(id: number, actionKey: string): void { + sendToWorkadventure({ + type: "addActionsMenuKeyToRemotePlayer", + data: { id, actionKey }, + }); + } + + public removeActionsMenuKeyFromRemotePlayer(id: number, actionKey: string): void { + sendToWorkadventure({ + type: "removeActionsMenuKeyFromRemotePlayer", + data: { id, actionKey }, + }); + } + + public openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup { popupId++; const popup = new Popup(popupId); const btnMap = new Map void>(); @@ -119,7 +224,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution void)): Menu { + public registerMenuCommand( + commandDescriptor: string, + options: MenuOptions | ((commandDescriptor: string) => void) + ): Menu { const menu = new Menu(commandDescriptor); if (typeof options === "function") { @@ -168,15 +276,15 @@ export class WorkAdventureUiCommands extends IframeApiContribution { actionMessages.delete(actionMessage.uuid); }); diff --git a/front/src/Components/ActionsMenu/ActionsMenu.svelte b/front/src/Components/ActionsMenu/ActionsMenu.svelte index d6f4e7e5..c1eb317a 100644 --- a/front/src/Components/ActionsMenu/ActionsMenu.svelte +++ b/front/src/Components/ActionsMenu/ActionsMenu.svelte @@ -2,10 +2,12 @@ import { actionsMenuStore } from "../../Stores/ActionsMenuStore"; import { onDestroy } from "svelte"; + import type { ActionsMenuAction } from "../../Stores/ActionsMenuStore"; import type { Unsubscriber } from "svelte/store"; import type { ActionsMenuData } from "../../Stores/ActionsMenuStore"; let actionsMenuData: ActionsMenuData | undefined; + let sortedActions: ActionsMenuAction[] | undefined; let actionsMenuStoreUnsubscriber: Unsubscriber | null; @@ -21,6 +23,20 @@ actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => { actionsMenuData = value; + if (actionsMenuData) { + sortedActions = [...actionsMenuData.actions.values()].sort((a, b) => { + const ap = a.priority ?? 0; + const bp = b.priority ?? 0; + if (ap > bp) { + return -1; + } + if (ap < bp) { + return 1; + } else { + return 0; + } + }); + } }); onDestroy(() => { @@ -37,15 +53,15 @@

{actionsMenuData.playerName}

- {#each [...actionsMenuData.actions] as { actionName, callback }} + {#each sortedActions ?? [] as action} {/each}
@@ -68,7 +84,7 @@ color: whitesmoke; .actions { - max-height: calc(100% - 50px); + max-height: 30vh; width: 100%; display: block; overflow-x: hidden; diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index a2ca63ea..f820b578 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -1,5 +1,5 @@ import { requestVisitCardsStore } from "../../Stores/GameStore"; -import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore"; +import { ActionsMenuAction, ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore"; import { Character } from "../Entity/Character"; import type { GameScene } from "../Game/GameScene"; import type { PointInterface } from "../../Connexion/ConnexionModels"; @@ -8,21 +8,28 @@ import type { Unsubscriber } from "svelte/store"; import type { ActivatableInterface } from "../Game/ActivatableInterface"; import type CancelablePromise from "cancelable-promise"; import LL from "../../i18n/i18n-svelte"; +import { blackListManager } from "../../WebRtc/BlackListManager"; +import { showReportScreenStore } from "../../Stores/ShowReportScreenStore"; + +export enum RemotePlayerEvent { + Clicked = "Clicked", +} /** * Class representing the sprite of a remote player (a player that plays on another computer) */ export class RemotePlayer extends Character implements ActivatableInterface { - public userId: number; + public readonly userId: number; + public readonly userUuid: string; public readonly activationRadius: number; - private registeredActions: { actionName: string; callback: Function }[]; private visitCardUrl: string | null; private isActionsMenuInitialized: boolean = false; private actionsMenuStoreUnsubscriber: Unsubscriber; constructor( userId: number, + userUuid: string, Scene: GameScene, x: number, y: number, @@ -39,10 +46,9 @@ export class RemotePlayer extends Character implements ActivatableInterface { //set data this.userId = userId; + this.userUuid = userUuid; this.visitCardUrl = visitCardUrl; - this.registeredActions = []; - this.registerDefaultActionsMenuActions(); - this.setClickable(this.registeredActions.length > 0); + this.setClickable(this.getDefaultActionsMenuActions().length > 0); this.activationRadius = activationRadius ?? 96; this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => { this.isActionsMenuInitialized = value ? true : false; @@ -63,17 +69,19 @@ export class RemotePlayer extends Character implements ActivatableInterface { } } - public registerActionsMenuAction(action: { actionName: string; callback: Function }): void { - this.registeredActions.push(action); - this.updateIsClickable(); + public registerActionsMenuAction(action: ActionsMenuAction): void { + actionsMenuStore.addAction({ + ...action, + priority: action.priority ?? 0, + callback: () => { + action.callback(); + actionsMenuStore.clear(); + }, + }); } public unregisterActionsMenuAction(actionName: string) { - const index = this.registeredActions.findIndex((action) => action.actionName === actionName); - if (index !== -1) { - this.registeredActions.splice(index, 1); - } - this.updateIsClickable(); + actionsMenuStore.removeAction(actionName); } public activate(): void { @@ -90,37 +98,52 @@ export class RemotePlayer extends Character implements ActivatableInterface { return this.isClickable(); } - private updateIsClickable(): void { - this.setClickable(this.registeredActions.length > 0); - } - private toggleActionsMenu(): void { if (this.isActionsMenuInitialized) { actionsMenuStore.clear(); return; } actionsMenuStore.initialize(this.playerName); - for (const action of this.registeredActions) { - actionsMenuStore.addAction(action.actionName, action.callback); + for (const action of this.getDefaultActionsMenuActions()) { + actionsMenuStore.addAction(action); } } - private registerDefaultActionsMenuActions(): void { + private getDefaultActionsMenuActions(): ActionsMenuAction[] { + const actions: ActionsMenuAction[] = []; if (this.visitCardUrl) { - this.registeredActions.push({ + actions.push({ actionName: LL.woka.menu.businessCard(), + protected: true, + priority: 1, callback: () => { requestVisitCardsStore.set(this.visitCardUrl); actionsMenuStore.clear(); }, }); } + + actions.push({ + actionName: blackListManager.isBlackListed(this.userUuid) + ? LL.report.block.unblock() + : LL.report.block.block(), + protected: true, + priority: -1, + style: "is-error", + callback: () => { + showReportScreenStore.set({ userId: this.userId, userName: this.name }); + actionsMenuStore.clear(); + }, + }); + + return actions; } private bindEventHandlers(): void { this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => { if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) { this.toggleActionsMenu(); + this.emit(RemotePlayerEvent.Clicked); } }); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 58fe73e8..4d4c84f5 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -30,7 +30,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore"; import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import { SimplePeer } from "../../WebRtc/SimplePeer"; import { Loader } from "../Components/Loader"; -import { RemotePlayer } from "../Entity/RemotePlayer"; +import { RemotePlayer, RemotePlayerEvent } from "../Entity/RemotePlayer"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; import { PlayerAnimationDirections } from "../Player/Animation"; import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player"; @@ -1108,6 +1108,23 @@ ${escapedMessage} }) ); + this.iframeSubscriptionList.push( + iframeListener.addActionsMenuKeyToRemotePlayerStream.subscribe((data) => { + this.MapPlayersByKey.get(data.id)?.registerActionsMenuAction({ + actionName: data.actionKey, + callback: () => { + iframeListener.sendActionsMenuActionClickedEvent({ actionName: data.actionKey, id: data.id }); + }, + }); + }) + ); + + this.iframeSubscriptionList.push( + iframeListener.removeActionsMenuKeyFromRemotePlayerEvent.subscribe((data) => { + this.MapPlayersByKey.get(data.id)?.unregisterActionsMenuAction(data.actionKey); + }) + ); + this.iframeSubscriptionList.push( iframeListener.trackCameraUpdateStream.subscribe(() => { if (!this.firstCameraUpdateSent) { @@ -1893,6 +1910,7 @@ ${escapedMessage} const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers); const player = new RemotePlayer( addPlayerData.userId, + addPlayerData.userUuid, this, addPlayerData.position.x, addPlayerData.position.y, @@ -1920,6 +1938,10 @@ ${escapedMessage} this.activatablesManager.handlePointerOutActivatableObject(); this.markDirty(); }); + + player.on(RemotePlayerEvent.Clicked, () => { + iframeListener.sendRemotePlayerClickedEvent({ id: player.userId }); + }); } /** diff --git a/front/src/Stores/ActionsMenuStore.ts b/front/src/Stores/ActionsMenuStore.ts index f891dad8..d7bae70c 100644 --- a/front/src/Stores/ActionsMenuStore.ts +++ b/front/src/Stores/ActionsMenuStore.ts @@ -1,8 +1,15 @@ import { writable } from "svelte/store"; +export type ActionsMenuAction = { + actionName: string; + callback: Function; + protected?: boolean; + priority?: number; + style?: "is-success" | "is-error" | "is-primary"; +}; export interface ActionsMenuData { playerName: string; - actions: { actionName: string; callback: Function }[]; + actions: Map; } function createActionsMenuStore() { @@ -13,21 +20,18 @@ function createActionsMenuStore() { initialize: (playerName: string) => { set({ playerName, - actions: [], + actions: new Map(), }); }, - addAction: (actionName: string, callback: Function) => { + addAction: (action: ActionsMenuAction) => { update((data) => { - data?.actions.push({ actionName, callback }); + data?.actions.set(action.actionName, action); return data; }); }, removeAction: (actionName: string) => { update((data) => { - const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName); - if (actionIndex !== undefined && actionIndex != -1) { - data?.actions.splice(actionIndex, 1); - } + data?.actions.delete(actionName); return data; }); }, diff --git a/maps/tests/AlterActionMenuApi/map.json b/maps/tests/AlterActionMenuApi/map.json new file mode 100644 index 00000000..1af059f9 --- /dev/null +++ b/maps/tests/AlterActionMenuApi/map.json @@ -0,0 +1,162 @@ +{ "compressionlevel":-1, + "height":30, + "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, 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, 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, 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, 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, 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, 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, 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, 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":30, + "id":1, + "name":"floor", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":30, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":6, + "name":"furnitures", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":30, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":30, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":19, + "id":11, + "name":"", + "rotation":0, + "text": + { + "text":"1. Log in with at least 2 users", + "wrap":true + }, + "type":"", + "visible":true, + "width":315.510479739171, + "x":320, + "y":256 + }, + { + "height":19, + "id":12, + "name":"", + "rotation":0, + "text": + { + "text":"2. Click on the second user to open ActionsMenu", + "wrap":true + }, + "type":"", + "visible":true, + "width":388.01, + "x":320, + "y":288 + }, + { + "height":19, + "id":13, + "name":"", + "rotation":0, + "text": + { + "text":"3. Try to add new action when menu is opened", + "wrap":true + }, + "type":"", + "visible":true, + "width":376.51, + "x":320, + "y":320 + }, + { + "height":19, + "id":14, + "name":"", + "rotation":0, + "text": + { + "text":"4. Click on actions to call them", + "wrap":true + }, + "type":"", + "visible":true, + "width":376.51, + "x":320, + "y":352 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":7, + "nextobjectid":15, + "orientation":"orthogonal", + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"script.php" + }, + { + "name":"openWebsiteAllowApi", + "type":"bool", + "value":true + }], + "renderorder":"right-down", + "tiledversion":"1.7.2", + "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.6", + "width":30 +} \ No newline at end of file diff --git a/maps/tests/AlterActionMenuApi/script.php b/maps/tests/AlterActionMenuApi/script.php new file mode 100644 index 00000000..0b246995 --- /dev/null +++ b/maps/tests/AlterActionMenuApi/script.php @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/maps/tests/index.html b/maps/tests/index.html index 3b3c700a..26a1795f 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -259,6 +259,14 @@ Test camera API + + + Success Failure Pending + + + Test Alter ActionMenu API + + Success Failure Pending