Migrating variables functions to the "state" namespace.

This commit is contained in:
David Négrier 2021-07-07 22:14:59 +02:00
parent e65e8b2097
commit b1cb12861f
7 changed files with 117 additions and 69 deletions

View File

@ -8,14 +8,24 @@
- Migrated the admin console to Svelte, and redesigned the console #1211 - Migrated the admin console to Svelte, and redesigned the console #1211
- Layer properties (like `exitUrl`, `silent`, etc...) can now also used in tile properties #1210 (@jonnytest1) - Layer properties (like `exitUrl`, `silent`, etc...) can now also used in tile properties #1210 (@jonnytest1)
- New scripting API features : - New scripting API features :
- Use `WA.onInit(): Promise<void>` to wait for scripting API initialization
- Use `WA.room.showLayer(): void` to show a layer - Use `WA.room.showLayer(): void` to show a layer
- Use `WA.room.hideLayer(): void` to hide a layer - Use `WA.room.hideLayer(): void` to hide a layer
- Use `WA.room.setProperty() : void` to add or change existing property of a layer - Use `WA.room.setProperty() : void` to add or change existing property of a layer
- Use `WA.player.onPlayerMove(): void` to track the movement of the current player - Use `WA.player.onPlayerMove(): void` to track the movement of the current player
- Use `WA.room.getCurrentUser(): Promise<User>` to get the ID, name and tags of the current player - Use `WA.player.id: string|undefined` to get the ID of the current player
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - Use `WA.player.name: string` to get the name of the current player
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu - Use `WA.player.tags: string[]` to get the tags of the current player
- Use `WA.room.id: string` to get the ID of the room
- Use `WA.room.mapURL: string` to get the URL of the map
- Use `WA.room.mapURL: string` to get the URL of the map
- Use `WA.room.getMap(): Promise<ITiledMap>` to get the JSON map file
- Use `WA.room.setTiles(): void` to change an array of tiles - Use `WA.room.setTiles(): void` to change an array of tiles
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
- Use `WA.state.loadVariable(key: string): unknown` to retrieve a variable
- Use `WA.state.saveVariable(key: string, value: unknown): Promise<void>` to set a variable (across the room, for all users)
- Use `WA.state.onVariableChange(key: string): Subscription<unknown>` to track a variable
- Use `WA.state.[any variable]: unknown` to access directly any variable (this is a shortcut to using `WA.state.loadVariable` and `WA.state.saveVariable`)
- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked. - Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked.
## Version 1.4.3 - 1.4.4 - 1.4.5 ## Version 1.4.3 - 1.4.4 - 1.4.5

View File

@ -4,15 +4,11 @@ import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution"; import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import {isSetVariableEvent, SetVariableEvent} from "../Events/SetVariableEvent";
import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>(); const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>(); const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
const setVariableResolvers = new Subject<SetVariableEvent>();
const variables = new Map<string, unknown>();
const variableSubscribers = new Map<string, Subject<unknown>>();
interface TileDescriptor { interface TileDescriptor {
x: number; x: number;
@ -33,24 +29,6 @@ export const setMapURL = (url: string) => {
mapURL = url; mapURL = url;
} }
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) => {
variables.set(event.key, event.value);
const subject = variableSubscribers.get(event.key);
if (subject !== undefined) {
subject.next(event.value);
}
});
export class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> { export class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
callbacks = [ callbacks = [
apiCallback({ apiCallback({
@ -67,13 +45,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
leaveStreams.get(payloadData.name)?.next(); leaveStreams.get(payloadData.name)?.next();
}, },
}), }),
apiCallback({
type: "setVariable",
typeChecker: isSetVariableEvent,
callback: (payloadData) => {
setVariableResolvers.next(payloadData);
}
}),
]; ];
onEnterZone(name: string, callback: () => void): void { onEnterZone(name: string, callback: () => void): void {
@ -119,31 +90,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
}); });
} }
saveVariable(key : string, value : unknown): Promise<void> {
variables.set(key, value);
return queryWorkadventure({
type: 'setVariable',
data: {
key,
value
}
})
}
loadVariable(key: string): unknown {
return variables.get(key);
}
onVariableChange(key: string): Observable<unknown> {
let subject = variableSubscribers.get(key);
if (subject === undefined) {
subject = new Subject<unknown>();
variableSubscribers.set(key, subject);
}
return subject.asObservable();
}
get id() : string { get id() : string {
if (roomId === undefined) { if (roomId === undefined) {
throw new Error('Room id not initialized yet. You should call WA.room.id within a WA.onInit callback.'); throw new Error('Room id not initialized yet. You should call WA.room.id within a WA.onInit callback.');

View File

@ -0,0 +1,85 @@
import {Observable, Subject} from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks";
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) => {
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> {
callbacks = [
apiCallback({
type: "setVariable",
typeChecker: isSetVariableEvent,
callback: (payloadData) => {
setVariableResolvers.next(payloadData);
}
}),
];
saveVariable(key : string, value : unknown): Promise<void> {
variables.set(key, value);
return queryWorkadventure({
type: 'setVariable',
data: {
key,
value
}
})
}
loadVariable(key: string): unknown {
return variables.get(key);
}
onVariableChange(key: string): Observable<unknown> {
let subject = variableSubscribers.get(key);
if (subject === undefined) {
subject = new Subject<unknown>();
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;
}
});
export default proxyCommand;

View File

@ -177,7 +177,6 @@ export class RoomConnection implements RoomConnection {
} }
} }
} else if (message.hasRoomjoinedmessage()) { } else if (message.hasRoomjoinedmessage()) {
console.error('COUCOU')
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage; const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
const items: { [itemId: number]: unknown } = {}; const items: { [itemId: number]: unknown } = {};

View File

@ -11,7 +11,8 @@ import nav from "./Api/iframe/nav";
import controls from "./Api/iframe/controls"; import controls from "./Api/iframe/controls";
import ui from "./Api/iframe/ui"; import ui from "./Api/iframe/ui";
import sound from "./Api/iframe/sound"; import sound from "./Api/iframe/sound";
import room, {initVariables, setMapURL, setRoomId} from "./Api/iframe/room"; import room, {setMapURL, setRoomId} from "./Api/iframe/room";
import state, {initVariables} from "./Api/iframe/state";
import player, {setPlayerName, setTags, setUuid} from "./Api/iframe/player"; import player, {setPlayerName, setTags, setUuid} from "./Api/iframe/player";
import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor";
import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Popup } from "./Api/iframe/Ui/Popup";
@ -42,6 +43,7 @@ const wa = {
sound, sound,
room, room,
player, player,
state,
onInit(): Promise<void> { onInit(): Promise<void> {
return initPromise; return initPromise;

View File

@ -1,20 +1,26 @@
WA.onInit().then(() => { WA.onInit().then(() => {
console.log('Trying to read variable "doorOpened" whose default property is true. This should display "true".'); console.log('Trying to read variable "doorOpened" whose default property is true. This should display "true".');
console.log('doorOpened', WA.room.loadVariable('doorOpened')); console.log('doorOpened', WA.state.loadVariable('doorOpened'));
console.log('Trying to set variable "not_exists". This should display an error in the console, followed by a log saying the error was caught.') console.log('Trying to set variable "not_exists". This should display an error in the console, followed by a log saying the error was caught.')
WA.room.saveVariable('not_exists', 'foo').catch((e) => { WA.state.saveVariable('not_exists', 'foo').catch((e) => {
console.log('Successfully caught error: ', e); console.log('Successfully caught error: ', e);
}); });
console.log('Trying to set variable "myvar". This should work.'); console.log('Trying to set variable "myvar". This should work.');
WA.room.saveVariable('myvar', {'foo': 'bar'}); WA.state.saveVariable('myvar', {'foo': 'bar'});
console.log('Trying to read variable "myvar". This should display a {"foo": "bar"} object.'); console.log('Trying to read variable "myvar". This should display a {"foo": "bar"} object.');
console.log(WA.room.loadVariable('myvar')); console.log(WA.state.loadVariable('myvar'));
console.log('Trying to set variable "myvar" using proxy. This should work.');
WA.state.myvar = {'baz': 42};
console.log('Trying to read variable "myvar" using proxy. This should display a {"baz": 42} object.');
console.log(WA.state.myvar);
console.log('Trying to set variable "config". This should not work because we are not logged as admin.'); console.log('Trying to set variable "config". This should not work because we are not logged as admin.');
WA.room.saveVariable('config', {'foo': 'bar'}).catch(e => { WA.state.saveVariable('config', {'foo': 'bar'}).catch(e => {
console.log('Successfully caught error because variable "config" is not writable: ', e); console.log('Successfully caught error because variable "config" is not writable: ', e);
}); });
}); });

View File

@ -12,21 +12,21 @@
WA.onInit().then(() => { WA.onInit().then(() => {
console.log('After WA init'); console.log('After WA init');
const textField = document.getElementById('textField'); const textField = document.getElementById('textField');
textField.value = WA.room.loadVariable('textField'); textField.value = WA.state.loadVariable('textField');
textField.addEventListener('change', function (evt) { textField.addEventListener('change', function (evt) {
console.log('saving variable') console.log('saving variable')
WA.room.saveVariable('textField', this.value); WA.state.saveVariable('textField', this.value);
}); });
WA.room.onVariableChange('textField').subscribe((value) => { WA.state.onVariableChange('textField').subscribe((value) => {
console.log('variable changed received') console.log('variable changed received')
textField.value = value; textField.value = value;
}); });
document.getElementById('btn').addEventListener('click', () => { document.getElementById('btn').addEventListener('click', () => {
console.log(WA.room.loadVariable('textField')); console.log(WA.state.loadVariable('textField'));
document.getElementById('placeholder').innerText = WA.room.loadVariable('textField'); document.getElementById('placeholder').innerText = WA.state.loadVariable('textField');
}); });
}); });
}) })