@croquet/react
Version:
React bindings for Croquet
107 lines (106 loc) • 4.73 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { Model } from '@croquet/croquet';
export class ReactModel extends Model {
constructor() {
super(...arguments);
this.__reactEvents = [];
this.__views = null;
}
init(options) {
const { trackViews } = options, opts = __rest(options, ["trackViews"]);
super.init(opts);
this.__reactEvents = [];
// We don't want to track the joined views in every model.
// the trackViews option enables this behavior
if (trackViews === true)
this.__views = new Set();
// Subscribe directly via super since we don't need to publish react-updated
super.subscribe(this.sessionId, 'view-join', this.__viewJoin);
super.subscribe(this.sessionId, 'view-exit', this.__viewExit);
}
__viewJoin(viewId) {
if (this.__views) {
this.__views.add(viewId);
this.publish(this.sessionId, 'views-updated');
}
this.handleViewJoin(viewId);
}
__viewExit(viewId) {
if (this.__views) {
this.__views.delete(viewId);
this.publish(this.sessionId, 'views-updated');
}
this.handleViewExit(viewId);
}
// Override these methods to add custom handling
handleViewJoin(viewId) { } // eslint-disable-line @typescript-eslint/no-unused-vars
handleViewExit(viewId) { } // eslint-disable-line @typescript-eslint/no-unused-vars
// Public function to assert that users do not subscribe directly to view-join/exit events
subscribe(scope, event, handler) {
if (event === 'view-join' || event === 'view-exit') {
throw new Error(`In /react you cannot directly subscribe to ${event}.\n` +
`Override ${event === 'view-join' ? 'handleViewJoin(viewId)' : 'handleViewExit(viewId)'} instead\n`);
}
this.__subscribe(scope, event, handler);
}
// This function is the one that performs the subscription logic, without performing any checks.
// It's separate from the one above since we need to directly subscribe to view-join/exit events
__subscribe(scope, event, handler) {
this.__reactEvents.push({ scope, event });
// normalize handler into string or QFunc
if (typeof handler === 'function') {
const model = this;
// if the handler is a method of the model, use the method name
if (model[handler.name] === handler)
handler = handler.name;
// otherwise, we assume that the handler is a QFunc
}
// we call the original handler, and then publish a react-updated event
// We pass as a javascript string to survive minification
const reactHandler = this.createQFunc({ handler }, `(data) => {
if (typeof handler === 'function') handler(data)
else this[handler](data)
this.publish(this.sessionId, 'react-updated')
}`);
super.subscribe(scope, event, reactHandler);
}
// Function that helps ReactModel publish a react-updated event
// every time a future message is executed
__future_wrapper(methodName, ...args) {
const value = this[methodName];
if (typeof value === 'function') {
value.apply(this, args);
this.publish(this.sessionId, 'react-updated');
}
else {
console.error(`${methodName} is not a function of ${this}`);
}
}
future(tOffset, method, ...args) {
// non-proxy case: we schedule the call to a __future_wrapper function
// that will call method, and then publish a react-updated event
if (method !== undefined) {
return super.future(tOffset, '__future_wrapper', method, ...args);
}
// We want to allow for the `this.future(tOffset).methodName(...args) syntax
// To do so, we return a Proxy that invokes this function
// with all the arguments for the non-proxy case
return new Proxy(this, {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
get(target, prop, _) {
return (...args) => target.future(tOffset, prop, ...args);
},
});
}
}
ReactModel.register('ReactModel');