Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop

This commit is contained in:
_Bastler 2022-03-14 10:33:34 +01:00
commit 9eec503215
19 changed files with 629 additions and 59 deletions

View File

@ -162,3 +162,34 @@ class ActionMessage {
remove() {}; 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:
<div class="col">
<img src="images/actions-menu-1.png" class="figure-img img-fluid rounded" alt="" />
</div>
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,
);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -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<typeof isActionsMenuActionClickedEvent>;
export type ActionsMenuActionClickedEventCallback = (event: ActionsMenuActionClickedEvent) => void;

View File

@ -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<typeof isAddActionsMenuKeyToRemotePlayerEvent>;
export type AddActionsMenuKeyToRemotePlayerEventCallback = (event: AddActionsMenuKeyToRemotePlayerEvent) => void;

View File

@ -36,6 +36,10 @@ import type { CameraFollowPlayerEvent } from "./CameraFollowPlayerEvent";
import { isColorEvent } from "./ColorEvent"; import { isColorEvent } from "./ColorEvent";
import { isMovePlayerToEventConfig } from "./MovePlayerToEvent"; import { isMovePlayerToEventConfig } from "./MovePlayerToEvent";
import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer"; import { isMovePlayerToEventAnswer } from "./MovePlayerToEventAnswer";
import type { RemotePlayerClickedEvent } from "./RemotePlayerClickedEvent";
import type { AddActionsMenuKeyToRemotePlayerEvent } from "./AddActionsMenuKeyToRemotePlayerEvent";
import type { ActionsMenuActionClickedEvent } from "./ActionsMenuActionClickedEvent";
import type { RemoveActionsMenuKeyFromRemotePlayerEvent } from "./RemoveActionsMenuKeyFromRemotePlayerEvent";
import { isGetPropertyEvent } from "./GetPropertyEvent"; import { isGetPropertyEvent } from "./GetPropertyEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
@ -46,6 +50,8 @@ export interface TypedMessageEvent<T> extends MessageEvent {
* List event types sent from an iFrame to WorkAdventure * List event types sent from an iFrame to WorkAdventure
*/ */
export type IframeEventMap = { export type IframeEventMap = {
addActionsMenuKeyToRemotePlayer: AddActionsMenuKeyToRemotePlayerEvent;
removeActionsMenuKeyFromRemotePlayer: RemoveActionsMenuKeyFromRemotePlayerEvent;
loadPage: LoadPageEvent; loadPage: LoadPageEvent;
chat: ChatEvent; chat: ChatEvent;
cameraFollowPlayer: CameraFollowPlayerEvent; cameraFollowPlayer: CameraFollowPlayerEvent;
@ -59,6 +65,7 @@ export type IframeEventMap = {
displayBubble: null; displayBubble: null;
removeBubble: null; removeBubble: null;
onPlayerMove: undefined; onPlayerMove: undefined;
onOpenActionMenu: undefined;
onCameraUpdate: undefined; onCameraUpdate: undefined;
showLayer: LayerEvent; showLayer: LayerEvent;
hideLayer: LayerEvent; hideLayer: LayerEvent;
@ -91,6 +98,8 @@ export interface IframeResponseEventMap {
enterZoneEvent: ChangeZoneEvent; enterZoneEvent: ChangeZoneEvent;
leaveZoneEvent: ChangeZoneEvent; leaveZoneEvent: ChangeZoneEvent;
buttonClickedEvent: ButtonClickedEvent; buttonClickedEvent: ButtonClickedEvent;
remotePlayerClickedEvent: RemotePlayerClickedEvent;
actionsMenuActionClickedEvent: ActionsMenuActionClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent; hasPlayerMoved: HasPlayerMovedEvent;
wasCameraUpdated: WasCameraUpdatedEvent; wasCameraUpdated: WasCameraUpdatedEvent;
menuItemClicked: MenuItemClickedEvent; menuItemClicked: MenuItemClickedEvent;

View File

@ -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<typeof isRemotePlayerClickedEvent>;
export type RemotePlayerClickedEventCallback = (event: RemotePlayerClickedEvent) => void;

View File

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

View File

@ -34,6 +34,16 @@ import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent"; import { CameraSetEvent, isCameraSetEvent } from "./Events/CameraSetEvent";
import { CameraFollowPlayerEvent, isCameraFollowPlayerEvent } from "./Events/CameraFollowPlayerEvent"; 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<T extends keyof IframeQueryMap> = ( type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"], query: IframeQueryMap[T]["query"],
@ -63,6 +73,15 @@ class IframeListener {
private readonly _cameraFollowPlayerStream: Subject<CameraFollowPlayerEvent> = new Subject(); private readonly _cameraFollowPlayerStream: Subject<CameraFollowPlayerEvent> = new Subject();
public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable(); public readonly cameraFollowPlayerStream = this._cameraFollowPlayerStream.asObservable();
private readonly _addActionsMenuKeyToRemotePlayerStream: Subject<AddActionsMenuKeyToRemotePlayerEvent> =
new Subject();
public readonly addActionsMenuKeyToRemotePlayerStream = this._addActionsMenuKeyToRemotePlayerStream.asObservable();
private readonly _removeActionsMenuKeyFromRemotePlayerEvent: Subject<RemoveActionsMenuKeyFromRemotePlayerEvent> =
new Subject();
public readonly removeActionsMenuKeyFromRemotePlayerEvent =
this._removeActionsMenuKeyFromRemotePlayerEvent.asObservable();
private readonly _enablePlayerControlStream: Subject<void> = new Subject(); private readonly _enablePlayerControlStream: Subject<void> = new Subject();
public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable(); public readonly enablePlayerControlStream = this._enablePlayerControlStream.asObservable();
@ -244,6 +263,16 @@ class IframeListener {
this._removeBubbleStream.next(); this._removeBubbleStream.next();
} else if (payload.type == "onPlayerMove") { } else if (payload.type == "onPlayerMove") {
this.sendPlayerMove = true; 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") { } else if (payload.type == "onCameraUpdate") {
this._trackCameraUpdateStream.next(); this._trackCameraUpdateStream.next();
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
@ -444,6 +473,20 @@ class IframeListener {
} }
} }
sendRemotePlayerClickedEvent(event: RemotePlayerClickedEvent) {
this.postMessage({
type: "remotePlayerClickedEvent",
data: event,
});
}
sendActionsMenuActionClickedEvent(event: ActionsMenuActionClickedEvent) {
this.postMessage({
type: "actionsMenuActionClickedEvent",
data: event,
});
}
sendCameraUpdated(event: WasCameraUpdatedEvent) { sendCameraUpdated(event: WasCameraUpdatedEvent) {
this.postMessage({ this.postMessage({
type: "wasCameraUpdated", type: "wasCameraUpdated",

View File

@ -8,6 +8,12 @@ import { ActionMessage } from "./Ui/ActionMessage";
import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent } from "../Events/ui/TriggerActionMessageEvent";
import { Menu } from "./Ui/Menu"; import { Menu } from "./Ui/Menu";
import type { RequireOnlyOne } from "../types"; 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; let popupId = 0;
const popups: Map<number, Popup> = new Map<number, Popup>(); const popups: Map<number, Popup> = new Map<number, Popup>();
@ -42,7 +48,77 @@ export interface ActionMessageOptions {
callback: () => void; callback: () => void;
} }
export interface RemotePlayerInterface {
addAction(key: string, callback: Function): void;
}
export class RemotePlayer implements RemotePlayerInterface {
private id: number;
private actions: Map<string, ActionsMenuAction> = new Map<string, ActionsMenuAction>();
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<WorkAdventureUiCommands> { export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventureUiCommands> {
public readonly _onRemotePlayerClicked: Subject<RemotePlayerInterface>;
public readonly onRemotePlayerClicked: Observable<RemotePlayerInterface>;
private currentlyClickedRemotePlayer?: RemotePlayer;
constructor() {
super();
this._onRemotePlayerClicked = new Subject<RemotePlayerInterface>();
this.onRemotePlayerClicked = this._onRemotePlayerClicked.asObservable();
}
callbacks = [ callbacks = [
apiCallback({ apiCallback({
type: "buttonClickedEvent", type: "buttonClickedEvent",
@ -83,15 +159,42 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
} }
}, },
}), }),
apiCallback({
type: "remotePlayerClickedEvent",
typeChecker: isRemotePlayerClickedEvent,
callback: (payloadData: RemotePlayerClickedEvent) => {
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( public addActionsMenuKeyToRemotePlayer(id: number, actionKey: string): void {
targetObject: string, 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, message: string,
buttons: ButtonDescriptor[], buttons: ButtonDescriptor[],
popupClass: string = "", popupClass: string = "",
input: boolean = false input: boolean = false): Popup {
): Popup {
popupId++; popupId++;
const popup = new Popup(popupId); const popup = new Popup(popupId);
const btnMap = new Map<number, () => void>(); const btnMap = new Map<number, () => void>();
@ -128,7 +231,10 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
return popup; return popup;
} }
registerMenuCommand(commandDescriptor: string, options: MenuOptions | ((commandDescriptor: string) => void)): Menu { public registerMenuCommand(
commandDescriptor: string,
options: MenuOptions | ((commandDescriptor: string) => void)
): Menu {
const menu = new Menu(commandDescriptor); const menu = new Menu(commandDescriptor);
if (typeof options === "function") { if (typeof options === "function") {
@ -177,15 +283,15 @@ export class WorkAdventureUiCommands extends IframeApiContribution<WorkAdventure
return menu; return menu;
} }
displayBubble(): void { public displayBubble(): void {
sendToWorkadventure({ type: "displayBubble", data: null }); sendToWorkadventure({ type: "displayBubble", data: null });
} }
removeBubble(): void { public removeBubble(): void {
sendToWorkadventure({ type: "removeBubble", data: null }); sendToWorkadventure({ type: "removeBubble", data: null });
} }
displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage { public displayActionMessage(actionMessageOptions: ActionMessageOptions): ActionMessage {
const actionMessage = new ActionMessage(actionMessageOptions, () => { const actionMessage = new ActionMessage(actionMessageOptions, () => {
actionMessages.delete(actionMessage.uuid); actionMessages.delete(actionMessage.uuid);
}); });

View File

@ -2,10 +2,12 @@
import { actionsMenuStore } from "../../Stores/ActionsMenuStore"; import { actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { onDestroy } from "svelte"; import { onDestroy } from "svelte";
import type { ActionsMenuAction } from "../../Stores/ActionsMenuStore";
import type { Unsubscriber } from "svelte/store"; import type { Unsubscriber } from "svelte/store";
import type { ActionsMenuData } from "../../Stores/ActionsMenuStore"; import type { ActionsMenuData } from "../../Stores/ActionsMenuStore";
let actionsMenuData: ActionsMenuData | undefined; let actionsMenuData: ActionsMenuData | undefined;
let sortedActions: ActionsMenuAction[] | undefined;
let actionsMenuStoreUnsubscriber: Unsubscriber | null; let actionsMenuStoreUnsubscriber: Unsubscriber | null;
@ -21,6 +23,20 @@
actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => { actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value) => {
actionsMenuData = 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(() => { onDestroy(() => {
@ -37,15 +53,15 @@
<button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>&times</button> <button type="button" class="nes-btn is-error close" on:click={closeActionsMenu}>&times</button>
<h2>{actionsMenuData.playerName}</h2> <h2>{actionsMenuData.playerName}</h2>
<div class="actions"> <div class="actions">
{#each [...actionsMenuData.actions] as { actionName, callback }} {#each sortedActions ?? [] as action}
<button <button
type="button" type="button"
class="nes-btn" class="nes-btn {action.style ?? ''}"
on:click|preventDefault={() => { on:click|preventDefault={() => {
callback(); action.callback();
}} }}
> >
{actionName} {action.actionName}
</button> </button>
{/each} {/each}
</div> </div>
@ -68,7 +84,7 @@
color: whitesmoke; color: whitesmoke;
.actions { .actions {
max-height: calc(100% - 50px); max-height: 30vh;
width: 100%; width: 100%;
display: block; display: block;
overflow-x: hidden; overflow-x: hidden;

View File

@ -331,9 +331,12 @@ class ConnectionManager {
throw new Error("No Auth code provided"); throw new Error("No Auth code provided");
} }
} }
const { authToken, userUuid, email, username, locale, textures } = await Axios.get(`${PUSHER_URL}/login-callback`, { const { authToken, userUuid, email, username, locale, textures } = await Axios.get(
params: { code, nonce, token, playUri: this.currentRoom?.key }, `${PUSHER_URL}/login-callback`,
}).then((res) => { {
params: { code, nonce, token, playUri: this.currentRoom?.key },
}
).then((res) => {
return res.data; return res.data;
}); });
localUserStore.setAuthToken(authToken); localUserStore.setAuthToken(authToken);

View File

@ -1,5 +1,5 @@
import { requestVisitCardsStore } from "../../Stores/GameStore"; import { requestVisitCardsStore } from "../../Stores/GameStore";
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore"; import { ActionsMenuAction, ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { Character } from "../Entity/Character"; import { Character } from "../Entity/Character";
import type { GameScene } from "../Game/GameScene"; import type { GameScene } from "../Game/GameScene";
import type { PointInterface } from "../../Connexion/ConnexionModels"; import type { PointInterface } from "../../Connexion/ConnexionModels";
@ -8,21 +8,28 @@ import type { Unsubscriber } from "svelte/store";
import type { ActivatableInterface } from "../Game/ActivatableInterface"; import type { ActivatableInterface } from "../Game/ActivatableInterface";
import type CancelablePromise from "cancelable-promise"; import type CancelablePromise from "cancelable-promise";
import LL from "../../i18n/i18n-svelte"; 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) * Class representing the sprite of a remote player (a player that plays on another computer)
*/ */
export class RemotePlayer extends Character implements ActivatableInterface { export class RemotePlayer extends Character implements ActivatableInterface {
public userId: number; public readonly userId: number;
public readonly userUuid: string;
public readonly activationRadius: number; public readonly activationRadius: number;
private registeredActions: { actionName: string; callback: Function }[];
private visitCardUrl: string | null; private visitCardUrl: string | null;
private isActionsMenuInitialized: boolean = false; private isActionsMenuInitialized: boolean = false;
private actionsMenuStoreUnsubscriber: Unsubscriber; private actionsMenuStoreUnsubscriber: Unsubscriber;
constructor( constructor(
userId: number, userId: number,
userUuid: string,
Scene: GameScene, Scene: GameScene,
x: number, x: number,
y: number, y: number,
@ -39,10 +46,9 @@ export class RemotePlayer extends Character implements ActivatableInterface {
//set data //set data
this.userId = userId; this.userId = userId;
this.userUuid = userUuid;
this.visitCardUrl = visitCardUrl; this.visitCardUrl = visitCardUrl;
this.registeredActions = []; this.setClickable(this.getDefaultActionsMenuActions().length > 0);
this.registerDefaultActionsMenuActions();
this.setClickable(this.registeredActions.length > 0);
this.activationRadius = activationRadius ?? 96; this.activationRadius = activationRadius ?? 96;
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => { this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
this.isActionsMenuInitialized = value ? true : false; this.isActionsMenuInitialized = value ? true : false;
@ -63,17 +69,19 @@ export class RemotePlayer extends Character implements ActivatableInterface {
} }
} }
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void { public registerActionsMenuAction(action: ActionsMenuAction): void {
this.registeredActions.push(action); actionsMenuStore.addAction({
this.updateIsClickable(); ...action,
priority: action.priority ?? 0,
callback: () => {
action.callback();
actionsMenuStore.clear();
},
});
} }
public unregisterActionsMenuAction(actionName: string) { public unregisterActionsMenuAction(actionName: string) {
const index = this.registeredActions.findIndex((action) => action.actionName === actionName); actionsMenuStore.removeAction(actionName);
if (index !== -1) {
this.registeredActions.splice(index, 1);
}
this.updateIsClickable();
} }
public activate(): void { public activate(): void {
@ -90,37 +98,52 @@ export class RemotePlayer extends Character implements ActivatableInterface {
return this.isClickable(); return this.isClickable();
} }
private updateIsClickable(): void {
this.setClickable(this.registeredActions.length > 0);
}
private toggleActionsMenu(): void { private toggleActionsMenu(): void {
if (this.isActionsMenuInitialized) { if (this.isActionsMenuInitialized) {
actionsMenuStore.clear(); actionsMenuStore.clear();
return; return;
} }
actionsMenuStore.initialize(this.playerName); actionsMenuStore.initialize(this.playerName);
for (const action of this.registeredActions) { for (const action of this.getDefaultActionsMenuActions()) {
actionsMenuStore.addAction(action.actionName, action.callback); actionsMenuStore.addAction(action);
} }
} }
private registerDefaultActionsMenuActions(): void { private getDefaultActionsMenuActions(): ActionsMenuAction[] {
const actions: ActionsMenuAction[] = [];
if (this.visitCardUrl) { if (this.visitCardUrl) {
this.registeredActions.push({ actions.push({
actionName: LL.woka.menu.businessCard(), actionName: LL.woka.menu.businessCard(),
protected: true,
priority: 1,
callback: () => { callback: () => {
requestVisitCardsStore.set(this.visitCardUrl); requestVisitCardsStore.set(this.visitCardUrl);
actionsMenuStore.clear(); 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 { private bindEventHandlers(): void {
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => { this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) { if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) {
this.toggleActionsMenu(); this.toggleActionsMenu();
this.emit(RemotePlayerEvent.Clicked);
} }
}); });
} }

View File

@ -30,7 +30,7 @@ import { localUserStore } from "../../Connexion/LocalUserStore";
import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import { SimplePeer } from "../../WebRtc/SimplePeer"; import { SimplePeer } from "../../WebRtc/SimplePeer";
import { Loader } from "../Components/Loader"; import { Loader } from "../Components/Loader";
import { RemotePlayer } from "../Entity/RemotePlayer"; import { RemotePlayer, RemotePlayerEvent } from "../Entity/RemotePlayer";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
import { PlayerAnimationDirections } from "../Player/Animation"; import { PlayerAnimationDirections } from "../Player/Animation";
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player"; import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
@ -1137,6 +1137,23 @@ export class GameScene extends DirtyScene {
}) })
); );
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( this.iframeSubscriptionList.push(
iframeListener.trackCameraUpdateStream.subscribe(() => { iframeListener.trackCameraUpdateStream.subscribe(() => {
if (!this.firstCameraUpdateSent) { if (!this.firstCameraUpdateSent) {
@ -1928,6 +1945,7 @@ export class GameScene extends DirtyScene {
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers); const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
const player = new RemotePlayer( const player = new RemotePlayer(
addPlayerData.userId, addPlayerData.userId,
addPlayerData.userUuid,
this, this,
addPlayerData.position.x, addPlayerData.position.x,
addPlayerData.position.y, addPlayerData.position.y,
@ -1955,6 +1973,10 @@ export class GameScene extends DirtyScene {
this.activatablesManager.handlePointerOutActivatableObject(); this.activatablesManager.handlePointerOutActivatableObject();
this.markDirty(); this.markDirty();
}); });
player.on(RemotePlayerEvent.Clicked, () => {
iframeListener.sendRemotePlayerClickedEvent({ id: player.userId });
});
} }
/** /**

View File

@ -1,8 +1,15 @@
import { writable } from "svelte/store"; 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 { export interface ActionsMenuData {
playerName: string; playerName: string;
actions: { actionName: string; callback: Function }[]; actions: Map<string, ActionsMenuAction>;
} }
function createActionsMenuStore() { function createActionsMenuStore() {
@ -13,21 +20,18 @@ function createActionsMenuStore() {
initialize: (playerName: string) => { initialize: (playerName: string) => {
set({ set({
playerName, playerName,
actions: [], actions: new Map<string, ActionsMenuAction>(),
}); });
}, },
addAction: (actionName: string, callback: Function) => { addAction: (action: ActionsMenuAction) => {
update((data) => { update((data) => {
data?.actions.push({ actionName, callback }); data?.actions.set(action.actionName, action);
return data; return data;
}); });
}, },
removeAction: (actionName: string) => { removeAction: (actionName: string) => {
update((data) => { update((data) => {
const actionIndex = data?.actions.findIndex((action) => action.actionName === actionName); data?.actions.delete(actionName);
if (actionIndex !== undefined && actionIndex != -1) {
data?.actions.splice(actionIndex, 1);
}
return data; return data;
}); });
}, },

View File

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

View File

@ -0,0 +1,43 @@
<!doctype html>
<html lang="en">
<head>
<script src="<?php echo $_SERVER["FRONT_URL"] ?>/iframe_api.js"></script>
<script>
window.addEventListener('load', () => {
//@ts-ignore
WA.onInit().then(() => {
const addActionButton = document.getElementById('addActionButton');
let lastRemotePlayerClicked;
WA.ui.onRemotePlayerClicked.subscribe((remotePlayer) => {
lastRemotePlayerClicked = remotePlayer;
const action = remotePlayer.addAction('This will disappear!', () => {
console.log('You managed to click me, young hero');
});
window.setTimeout(
() => {
action.remove();
},
1000,
);
remotePlayer.addAction('Ask to tell a joke', () => {
console.log('I am NOT telling you a joke!');
});
});
addActionButton.addEventListener('click', () => {
const randomActionName = (Math.floor(Math.random() * 100)).toString();
action = lastRemotePlayerClicked?.addAction(randomActionName, () => {
console.log(`called action ${randomActionName}`);
});
});
});
})
</script>
</head>
<body>
<button id="addActionButton">Add Action</button>
</body>
</html>

View File

@ -259,6 +259,14 @@
<a href="#" class="testLink" data-testmap="CameraApi/camera_api_test.json" target="_blank">Test camera API</a> <a href="#" class="testLink" data-testmap="CameraApi/camera_api_test.json" target="_blank">Test camera API</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-alter-action-menu"> Success <input type="radio" name="test-alter-action-menu"> Failure <input type="radio" name="test-alter-action-menu" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="AlterActionMenuApi/map.json" target="_blank">Test Alter ActionMenu API</a>
</td>
</tr>
<tr> <tr>
<td> <td>
<input type="radio" name="test-set-tiles"> Success <input type="radio" name="test-set-tiles"> Failure <input type="radio" name="test-set-tiles" checked> Pending <input type="radio" name="test-set-tiles"> Success <input type="radio" name="test-set-tiles"> Failure <input type="radio" name="test-set-tiles" checked> Pending

View File

@ -84,30 +84,75 @@ export class AuthenticateController extends BaseHttpController {
* get: * get:
* description: TODO * description: TODO
* parameters: * parameters:
* - name: "code"
* in: "query"
* description: "todo"
* required: false
* type: "string"
* - name: "nonce" * - name: "nonce"
* in: "query" * in: "query"
* description: "todo" * description: "todo"
* required: true * required: false
* type: "string" * type: "string"
* - name: "state" * - name: "token"
* in: "query" * in: "query"
* description: "todo" * description: "todo"
* required: true * required: false
* type: "string" * type: "string"
* - name: "playUri" * - name: "playUri"
* in: "query" * in: "query"
* description: "todo" * description: "todo"
* required: false * required: true
* type: "string"
* - name: "redirect"
* in: "query"
* description: "todo"
* required: false
* type: "string" * type: "string"
* responses: * responses:
* 200: * 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 //eslint-disable-next-line @typescript-eslint/no-misused-promises
this.app.get("/login-callback", async (req, res) => { this.app.get("/login-callback", async (req, res) => {

View File

@ -32,7 +32,7 @@ export class WokaListController extends BaseHttpController {
let { roomUrl } = parse(req.path_query); let { roomUrl } = parse(req.path_query);
if (typeof roomUrl !== "string") { if (typeof roomUrl !== "string") {
throw new Error("missing roomUrl URL parameter"); return res.status(400).send("missing roomUrl URL parameter");
} }
roomUrl = decodeURIComponent(roomUrl); roomUrl = decodeURIComponent(roomUrl);