From b1cb12861fc10c2b8e2d2e91692bc4139b02deac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 7 Jul 2021 22:14:59 +0200 Subject: [PATCH] Migrating variables functions to the "state" namespace. --- CHANGELOG.md | 16 +++- front/src/Api/iframe/room.ts | 54 -------------- front/src/Api/iframe/state.ts | 85 ++++++++++++++++++++++ front/src/Connexion/RoomConnection.ts | 1 - front/src/iframe_api.ts | 4 +- maps/tests/Variables/script.js | 16 ++-- maps/tests/Variables/shared_variables.html | 10 +-- 7 files changed, 117 insertions(+), 69 deletions(-) create mode 100644 front/src/Api/iframe/state.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a83e8213..e8070634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,24 @@ - 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) - New scripting API features : + - Use `WA.onInit(): Promise` to wait for scripting API initialization - Use `WA.room.showLayer(): void` to show 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.player.onPlayerMove(): void` to track the movement of the current player - - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player - - Use `WA.room.getCurrentRoom(): Promise` 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.ui.registerMenuCommand(): void` to add a custom menu + - Use `WA.player.id: string|undefined` to get the ID of the current player + - Use `WA.player.name: string` to get the name of the current player + - 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` to get the JSON map file - 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` to set a variable (across the room, for all users) + - Use `WA.state.onVariableChange(key: string): Subscription` 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. ## Version 1.4.3 - 1.4.4 - 1.4.5 diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index db639cd9..9954cb7c 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -4,15 +4,11 @@ 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 enterStreams: Map> = new Map>(); const leaveStreams: Map> = new Map>(); -const setVariableResolvers = new Subject(); -const variables = new Map(); -const variableSubscribers = new Map>(); interface TileDescriptor { x: number; @@ -33,24 +29,6 @@ export const setMapURL = (url: string) => { mapURL = url; } -export const initVariables = (_variables: Map): 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 { callbacks = [ apiCallback({ @@ -67,13 +45,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - setVariableResolvers.next(payloadData); - } - }), ]; onEnterZone(name: string, callback: () => void): void { @@ -119,31 +90,6 @@ export class WorkadventureRoomCommands extends IframeApiContribution { - variables.set(key, value); - return queryWorkadventure({ - type: 'setVariable', - data: { - key, - value - } - }) - } - - loadVariable(key: string): unknown { - return variables.get(key); - } - - onVariableChange(key: string): Observable { - let subject = variableSubscribers.get(key); - if (subject === undefined) { - subject = new Subject(); - variableSubscribers.set(key, subject); - } - return subject.asObservable(); - } - - get id() : string { if (roomId === undefined) { throw new Error('Room id not initialized yet. You should call WA.room.id within a WA.onInit callback.'); diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts new file mode 100644 index 00000000..c894e09e --- /dev/null +++ b/front/src/Api/iframe/state.ts @@ -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(); +const variables = new Map(); +const variableSubscribers = new Map>(); + +export const initVariables = (_variables: Map): 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 { + callbacks = [ + apiCallback({ + type: "setVariable", + typeChecker: isSetVariableEvent, + callback: (payloadData) => { + setVariableResolvers.next(payloadData); + } + }), + ]; + + saveVariable(key : string, value : unknown): Promise { + variables.set(key, value); + return queryWorkadventure({ + type: 'setVariable', + data: { + key, + value + } + }) + } + + loadVariable(key: string): unknown { + return variables.get(key); + } + + onVariableChange(key: string): Observable { + let subject = variableSubscribers.get(key); + if (subject === undefined) { + subject = new Subject(); + 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; diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 53eff010..33122caa 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -177,7 +177,6 @@ export class RoomConnection implements RoomConnection { } } } else if (message.hasRoomjoinedmessage()) { - console.error('COUCOU') const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage; const items: { [itemId: number]: unknown } = {}; diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index cd610ab0..2bf1185b 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -11,7 +11,8 @@ import nav from "./Api/iframe/nav"; import controls from "./Api/iframe/controls"; import ui from "./Api/iframe/ui"; 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 type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; @@ -42,6 +43,7 @@ const wa = { sound, room, player, + state, onInit(): Promise { return initPromise; diff --git a/maps/tests/Variables/script.js b/maps/tests/Variables/script.js index 120a4425..ae663cc9 100644 --- a/maps/tests/Variables/script.js +++ b/maps/tests/Variables/script.js @@ -1,20 +1,26 @@ WA.onInit().then(() => { 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.') - WA.room.saveVariable('not_exists', 'foo').catch((e) => { + WA.state.saveVariable('not_exists', 'foo').catch((e) => { console.log('Successfully caught error: ', e); }); 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(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.'); - 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); }); }); diff --git a/maps/tests/Variables/shared_variables.html b/maps/tests/Variables/shared_variables.html index ae282b1c..80fdbdd4 100644 --- a/maps/tests/Variables/shared_variables.html +++ b/maps/tests/Variables/shared_variables.html @@ -12,21 +12,21 @@ WA.onInit().then(() => { console.log('After WA init'); const textField = document.getElementById('textField'); - textField.value = WA.room.loadVariable('textField'); + textField.value = WA.state.loadVariable('textField'); textField.addEventListener('change', function (evt) { 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') textField.value = value; }); document.getElementById('btn').addEventListener('click', () => { - console.log(WA.room.loadVariable('textField')); - document.getElementById('placeholder').innerText = WA.room.loadVariable('textField'); + console.log(WA.state.loadVariable('textField')); + document.getElementById('placeholder').innerText = WA.state.loadVariable('textField'); }); }); })