Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
@@ -22,6 +22,8 @@ export const isEmbeddedWebsiteEvent = new tg.IsInterface()
|
||||
y: tg.isNumber,
|
||||
width: tg.isNumber,
|
||||
height: tg.isNumber,
|
||||
origin: tg.isSingletonStringUnion("player", "map"),
|
||||
scale: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
@@ -35,6 +37,8 @@ export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface()
|
||||
visible: tg.isBoolean,
|
||||
allowApi: tg.isBoolean,
|
||||
allow: tg.isString,
|
||||
origin: tg.isSingletonStringUnion("player", "map"),
|
||||
scale: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ export const isGameStateEvent = new tg.IsInterface()
|
||||
tags: tg.isArray(tg.isString),
|
||||
variables: tg.isObject,
|
||||
userRoomToken: tg.isUnion(tg.isString, tg.isUndefined),
|
||||
playerVariables: tg.isObject,
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,8 @@ import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEv
|
||||
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
|
||||
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
||||
import { isColorEvent } from "./ColorEvent";
|
||||
import { isPlayerPosition } from "./PlayerPosition";
|
||||
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
|
||||
import { isGetPropertyEvent } from "./GetPropertyEvent";
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
@@ -51,6 +53,7 @@ export type IframeEventMap = {
|
||||
displayBubble: null;
|
||||
removeBubble: null;
|
||||
onPlayerMove: undefined;
|
||||
onCameraUpdate: undefined;
|
||||
showLayer: LayerEvent;
|
||||
hideLayer: LayerEvent;
|
||||
setProperty: SetPropertyEvent;
|
||||
@@ -83,6 +86,7 @@ export interface IframeResponseEventMap {
|
||||
leaveZoneEvent: ChangeZoneEvent;
|
||||
buttonClickedEvent: ButtonClickedEvent;
|
||||
hasPlayerMoved: HasPlayerMovedEvent;
|
||||
wasCameraUpdated: WasCameraUpdatedEvent;
|
||||
menuItemClicked: MenuItemClickedEvent;
|
||||
setVariable: SetVariableEvent;
|
||||
messageTriggered: MessageReferenceEvent;
|
||||
@@ -166,6 +170,10 @@ export const iframeQueryMapTypeGuards = {
|
||||
query: tg.isUndefined,
|
||||
answer: tg.isUndefined,
|
||||
},
|
||||
getPlayerPosition: {
|
||||
query: tg.isUndefined,
|
||||
answer: isPlayerPosition,
|
||||
},
|
||||
};
|
||||
|
||||
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isPlayerPosition = new tg.IsInterface()
|
||||
.withProperties({
|
||||
x: tg.isNumber,
|
||||
y: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
export type PlayerPosition = tg.GuardedType<typeof isPlayerPosition>;
|
||||
@@ -4,6 +4,7 @@ export const isSetVariableEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
key: tg.isString,
|
||||
value: tg.isUnknown,
|
||||
target: tg.isSingletonStringUnion("global", "player"),
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isWasCameraUpdatedEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
x: tg.isNumber,
|
||||
y: tg.isNumber,
|
||||
width: tg.isNumber,
|
||||
height: tg.isNumber,
|
||||
zoom: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the game to the iFrame to notify a movement from the camera.
|
||||
*/
|
||||
|
||||
export type WasCameraUpdatedEvent = tg.GuardedType<typeof isWasCameraUpdatedEvent>;
|
||||
|
||||
export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void;
|
||||
@@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent";
|
||||
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
|
||||
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
|
||||
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
|
||||
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
|
||||
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
||||
|
||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||
@@ -88,6 +89,9 @@ class IframeListener {
|
||||
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
||||
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
||||
|
||||
private readonly _trackCameraUpdateStream: Subject<LoadSoundEvent> = new Subject();
|
||||
public readonly trackCameraUpdateStream = this._trackCameraUpdateStream.asObservable();
|
||||
|
||||
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
||||
public readonly setTilesStream = this._setTilesStream.asObservable();
|
||||
|
||||
@@ -229,6 +233,8 @@ class IframeListener {
|
||||
this._removeBubbleStream.next();
|
||||
} else if (payload.type == "onPlayerMove") {
|
||||
this.sendPlayerMove = true;
|
||||
} else if (payload.type == "onCameraUpdate") {
|
||||
this._trackCameraUpdateStream.next();
|
||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||
this._setTilesStream.next(payload.data);
|
||||
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) {
|
||||
@@ -448,6 +454,13 @@ class IframeListener {
|
||||
}
|
||||
}
|
||||
|
||||
sendCameraUpdated(event: WasCameraUpdatedEvent) {
|
||||
this.postMessage({
|
||||
type: "wasCameraUpdated",
|
||||
data: event,
|
||||
});
|
||||
}
|
||||
|
||||
sendButtonClickedEvent(popupId: number, buttonId: number, input : boolean, inputValue : string | null): void {
|
||||
this.postMessage({
|
||||
type: "buttonClickedEvent",
|
||||
|
||||
@@ -12,6 +12,8 @@ export class EmbeddedWebsite {
|
||||
private _allow: string;
|
||||
private _allowApi: boolean;
|
||||
private _position: Rectangle;
|
||||
private readonly origin: "map" | "player" | undefined;
|
||||
private _scale: number;
|
||||
|
||||
constructor(private config: CreateEmbeddedWebsiteEvent) {
|
||||
this.name = config.name;
|
||||
@@ -20,6 +22,12 @@ export class EmbeddedWebsite {
|
||||
this._allow = config.allow ?? "";
|
||||
this._allowApi = config.allowApi ?? false;
|
||||
this._position = config.position;
|
||||
this.origin = config.origin;
|
||||
this._scale = config.scale ?? 1;
|
||||
}
|
||||
|
||||
public get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public set url(url: string) {
|
||||
@@ -33,6 +41,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get visible() {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
public set visible(visible: boolean) {
|
||||
this._visible = visible;
|
||||
sendToWorkadventure({
|
||||
@@ -44,6 +56,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get x() {
|
||||
return this._position.x;
|
||||
}
|
||||
|
||||
public set x(x: number) {
|
||||
this._position.x = x;
|
||||
sendToWorkadventure({
|
||||
@@ -55,6 +71,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get y() {
|
||||
return this._position.y;
|
||||
}
|
||||
|
||||
public set y(y: number) {
|
||||
this._position.y = y;
|
||||
sendToWorkadventure({
|
||||
@@ -66,6 +86,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get width() {
|
||||
return this._position.width;
|
||||
}
|
||||
|
||||
public set width(width: number) {
|
||||
this._position.width = width;
|
||||
sendToWorkadventure({
|
||||
@@ -77,6 +101,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get height() {
|
||||
return this._position.height;
|
||||
}
|
||||
|
||||
public set height(height: number) {
|
||||
this._position.height = height;
|
||||
sendToWorkadventure({
|
||||
@@ -87,4 +115,19 @@ export class EmbeddedWebsite {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public get scale(): number {
|
||||
return this._scale;
|
||||
}
|
||||
|
||||
public set scale(scale: number) {
|
||||
this._scale = scale;
|
||||
sendToWorkadventure({
|
||||
type: "modifyEmbeddedWebsite",
|
||||
data: {
|
||||
name: this.name,
|
||||
scale: this._scale,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||
import { Subject } from "rxjs";
|
||||
import type { WasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
||||
|
||||
const moveStream = new Subject<WasCameraUpdatedEvent>();
|
||||
|
||||
export class WorkAdventureCameraCommands extends IframeApiContribution<WorkAdventureCameraCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: "wasCameraUpdated",
|
||||
typeChecker: isWasCameraUpdatedEvent,
|
||||
callback: (payloadData) => {
|
||||
moveStream.next(payloadData);
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
||||
sendToWorkadventure({
|
||||
type: "onCameraUpdate",
|
||||
data: null,
|
||||
});
|
||||
return moveStream;
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkAdventureCameraCommands();
|
||||
@@ -3,6 +3,7 @@ import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events
|
||||
import { Subject } from "rxjs";
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent";
|
||||
import { createState } from "./state";
|
||||
|
||||
const moveStream = new Subject<HasPlayerMovedEvent>();
|
||||
|
||||
@@ -31,6 +32,8 @@ export const setUuid = (_uuid: string | undefined) => {
|
||||
};
|
||||
|
||||
export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
||||
readonly state = createState("player");
|
||||
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: "hasPlayerMoved",
|
||||
@@ -74,6 +77,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
||||
return uuid;
|
||||
}
|
||||
|
||||
async getPosition(): Promise<Position> {
|
||||
return await queryWorkadventure({
|
||||
type: "getPlayerPosition",
|
||||
data: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
get userRoomToken(): string | undefined {
|
||||
if (userRoomToken === undefined) {
|
||||
throw new Error(
|
||||
@@ -102,4 +112,9 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
||||
}
|
||||
}
|
||||
|
||||
export type Position = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export default new WorkadventurePlayerCommands();
|
||||
|
||||
@@ -8,93 +8,101 @@ import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent
|
||||
|
||||
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||
|
||||
const setVariableResolvers = new Subject<SetVariableEvent>();
|
||||
const variables = new Map<string, unknown>();
|
||||
const variableSubscribers = new Map<string, Subject<unknown>>();
|
||||
|
||||
export const initVariables = (_variables: Map<string, unknown>): void => {
|
||||
for (const [name, value] of _variables.entries()) {
|
||||
// In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this.
|
||||
if (!variables.has(name)) {
|
||||
variables.set(name, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setVariableResolvers.subscribe((event) => {
|
||||
const oldValue = variables.get(event.key);
|
||||
// If we are setting the same value, no need to do anything.
|
||||
// No need to do this check since it is already performed in SharedVariablesManager
|
||||
/*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) {
|
||||
return;
|
||||
}*/
|
||||
|
||||
variables.set(event.key, event.value);
|
||||
const subject = variableSubscribers.get(event.key);
|
||||
if (subject !== undefined) {
|
||||
subject.next(event.value);
|
||||
}
|
||||
});
|
||||
|
||||
export class WorkadventureStateCommands extends IframeApiContribution<WorkadventureStateCommands> {
|
||||
private setVariableResolvers = new Subject<SetVariableEvent>();
|
||||
private variables = new Map<string, unknown>();
|
||||
private variableSubscribers = new Map<string, Subject<unknown>>();
|
||||
|
||||
constructor(private target: "global" | "player") {
|
||||
super();
|
||||
|
||||
this.setVariableResolvers.subscribe((event) => {
|
||||
const oldValue = this.variables.get(event.key);
|
||||
// If we are setting the same value, no need to do anything.
|
||||
// No need to do this check since it is already performed in SharedVariablesManager
|
||||
/*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) {
|
||||
return;
|
||||
}*/
|
||||
|
||||
this.variables.set(event.key, event.value);
|
||||
const subject = this.variableSubscribers.get(event.key);
|
||||
if (subject !== undefined) {
|
||||
subject.next(event.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: "setVariable",
|
||||
typeChecker: isSetVariableEvent,
|
||||
callback: (payloadData) => {
|
||||
setVariableResolvers.next(payloadData);
|
||||
if (payloadData.target === this.target) {
|
||||
this.setVariableResolvers.next(payloadData);
|
||||
}
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
// TODO: see how we can remove this method from types exposed to WA.state object
|
||||
initVariables(_variables: Map<string, unknown>): void {
|
||||
for (const [name, value] of _variables.entries()) {
|
||||
// In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this.
|
||||
if (!this.variables.has(name)) {
|
||||
this.variables.set(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveVariable(key: string, value: unknown): Promise<void> {
|
||||
variables.set(key, value);
|
||||
this.variables.set(key, value);
|
||||
return queryWorkadventure({
|
||||
type: "setVariable",
|
||||
data: {
|
||||
key,
|
||||
value,
|
||||
target: this.target,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
loadVariable(key: string): unknown {
|
||||
return variables.get(key);
|
||||
return this.variables.get(key);
|
||||
}
|
||||
|
||||
hasVariable(key: string): boolean {
|
||||
return variables.has(key);
|
||||
return this.variables.has(key);
|
||||
}
|
||||
|
||||
onVariableChange(key: string): Observable<unknown> {
|
||||
let subject = variableSubscribers.get(key);
|
||||
let subject = this.variableSubscribers.get(key);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<unknown>();
|
||||
variableSubscribers.set(key, subject);
|
||||
this.variableSubscribers.set(key, subject);
|
||||
}
|
||||
return subject.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
const proxyCommand = new Proxy(new WorkadventureStateCommands(), {
|
||||
get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown {
|
||||
if (p in target) {
|
||||
return Reflect.get(target, p, receiver);
|
||||
}
|
||||
return target.loadVariable(p.toString());
|
||||
},
|
||||
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
|
||||
// Note: when using "set", there is no way to wait, so we ignore the return of the promise.
|
||||
// User must use WA.state.saveVariable to have error message.
|
||||
target.saveVariable(p.toString(), value);
|
||||
return true;
|
||||
},
|
||||
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
|
||||
if (p in target) {
|
||||
export function createState(target: "global" | "player"): WorkadventureStateCommands & { [key: string]: unknown } {
|
||||
return new Proxy(new WorkadventureStateCommands(target), {
|
||||
get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown {
|
||||
if (p in target) {
|
||||
return Reflect.get(target, p, receiver);
|
||||
}
|
||||
return target.loadVariable(p.toString());
|
||||
},
|
||||
set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
|
||||
// Note: when using "set", there is no way to wait, so we ignore the return of the promise.
|
||||
// User must use WA.state.saveVariable to have error message.
|
||||
target.saveVariable(p.toString(), value);
|
||||
return true;
|
||||
}
|
||||
return target.hasVariable(p.toString());
|
||||
},
|
||||
}) as WorkadventureStateCommands & { [key: string]: unknown };
|
||||
|
||||
export default proxyCommand;
|
||||
},
|
||||
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
|
||||
if (p in target) {
|
||||
return true;
|
||||
}
|
||||
return target.hasVariable(p.toString());
|
||||
},
|
||||
}) as WorkadventureStateCommands & { [key: string]: unknown };
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
|
||||
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
|
||||
import type { StopSoundEvent } from "../Events/StopSoundEvent";
|
||||
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
|
||||
import { Sound } from "./Sound/Sound";
|
||||
import { EmbeddedWebsite } from "./Room/EmbeddedWebsite";
|
||||
import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user