import { Utils } from "../common/Utils";
import { BaseComponent } from "../components/BaseComponent";
const __ = {
private: Symbol('private'),
}
// Ignored attribute changes.
let ignoreChangingAttributes = ['_object', '_active', 'app'];
/**
* @class MonitorDataComponent
* The monitor data component.
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class MonitorDataComponent extends BaseComponent {
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
_private.anyPendingChanges = {};
_private.pendingChangeCount = 0;
_private.pendingChanges = {};
_private.pendingSubscribers = [];
_private.changedCount = 0;
_private.monitorAttributesNames = [];
_private.monitorAttributes = {};
// subscription list
_private._subscribers = new Map();
// Callback when changing attributes.
const changePropValue = (object, prop, value, receiver) => {
// If it is an attribute that needs to ignore changes, do not proxy.
if (ignoreChangingAttributes.includes(prop)) {
return true;
}
_private.monitorAttributes[prop] = value;
if (_private.monitorAttributesNames.includes('*')) {
if (!_private.pendingChangeCount) {
// If it is a single attribute change and subscribed to a callback for any attribute change.
_private.anyPendingChanges = { [prop]: value };
}
else {
if (object[prop] == value) {
delete _private.anyPendingChanges[prop];
}
else {
_private.anyPendingChanges[prop] = value;
}
}
if (!_private.monitorAttributesNames.includes(prop)) {
_private.monitorAttributesNames.push(prop);
}
}
object[prop] = value;
if (_private.monitorAttributesNames.includes(prop)) {
// If there are multiple attribute changes.
if (_private.pendingChangeCount) {
_private.pendingChanges[prop] = value;
_private.changedCount++;
if (_private.changedCount == _private.pendingChangeCount) {
this._publish(prop, value);
}
}
else {
this._publish(prop, value);
}
}
}
// Proxy the set and deleteProperty interfaces of the class.
let proxy = new Proxy(this, {
set: function (object, prop, value, receiver) {
changePropValue(object, prop, value, receiver);
return true;
},
deleteProperty: function (object, prop) {
changePropValue(object, prop);
delete object[prop];
return true;
}
});
// Proxy monitorAttributes to this.
Object.defineProperty(proxy, 'customFormatters', {
enumerable: false,
configurable: false,
get: function () {
return ['object', { object: _private.monitorAttributes }];
}
});
return proxy;
}
setAttributes(name, value) {
let _private = this[__.private];
if (Utils.isString(name)) {
this[name] = value;
}
else if (Utils.isObject(name)) {
if (!_private.monitorAttributesNames.includes('*')) {
const intersection = Object.keys(name).filter(value => _private.monitorAttributesNames.includes(value));
_private.pendingChangeCount = intersection.length;
}
else {
_private.pendingChangeCount = Object.keys(name).length;
}
_private.changedCount = 0;
for (let prop in name) {
this[prop] = name[prop];
}
}
}
subscribe(key, fn) {
let _private = this[__.private];
// Monitor all data changes.
if (Utils.isFunction(key)) {
fn = key;
key = '*';
}
// Monitoring partial data changes.
else if (Utils.isArray(key)) {
_private.pendingSubscribers = [];
for (let i = 0; i < key.length; i++) {
_private.pendingSubscribers.push(this.subscribe(key[i], fn));
}
return _private.pendingSubscribers;
}
// Add to monitoring attribute array.
if (!_private.monitorAttributesNames.includes(key)) {
_private.monitorAttributesNames.push(key);
}
return this._addSubscriber(key, fn);
}
_addSubscriber(key, fn) {
let _private = this[__.private];
if (Utils.isFunction(fn)) {
let arr = _private._subscribers.get(key);
if (arr) {
arr.push(fn);
}
else {
_private._subscribers.set(key, [fn])
}
return { key, listener: fn };
}
else {
console.warn('Subscription callback should be a function.');
}
}
unsubscribe(subscriber) {
if (Utils.isArray(subscriber)) {
subscriber.forEach(element =>
this._unsubscribe(element.key, element.listener)
);
}
else {
this._unsubscribe(subscriber.key, subscriber.listener)
}
}
_unsubscribe(key, fn) {
let _private = this[__.private];
let fns = _private._subscribers.get(key);
if (fns && fns.length > 0) {
for (let i = fns.length - 1; i >= 0; i--) {
if (fns[i] == fn) {
fns.splice(i, 1);
}
}
}
}
unsubscribeAll() {
let _private = this[__.private];
_private._subscribers.clear();
}
clearAttributes() {
let _private = this[__.private];
for (let prop in _private.monitorAttributes) {
delete this[prop];
}
_private.monitorAttributes = {};
}
_publish(prop, value) {
let _private = this[__.private];
// If the callback contains any attribute changes.
let anyFns = _private._subscribers.get('*');
if (anyFns) {
anyFns.forEach((fn) => {
fn({ data: _private.anyPendingChanges });
})
}
// If the number of pending changes is greater than 0, only messages that are subscribed to together with these attributes will be published.
if (_private.pendingChangeCount > 0) {
let commonFns = this._findCommonValues(_private._subscribers, Object.keys(_private.pendingChanges));
if (commonFns.length > 0) {
commonFns.forEach(function (fn) {
fn({ data: _private.pendingChanges });
})
}
}
else {
// Get the function by prop and trigger these functions.
let fns = _private._subscribers.get(prop);
if (!fns || fns.length === 0) {
return;
}
for (let i = 0; i < fns.length; i++) {
fns[i]({ data: { [prop]: value }});
}
}
this._clearPendingChanges();
}
_clearPendingChanges() {
let _private = this[__.private];
// Clear pending attribute changes.
_private.anyPendingChanges = {};
_private.pendingChanges = {};
_private.pendingChangeCount = 0;
_private.changedCount = 0;
}
_findCommonValues(map, keys) {
if (!Array.isArray(keys) || keys.length === 0) {
return undefined;
}
// Find the callback corresponding to the monitored attribute.
const valueArrays = keys.map(
key => map.get(key)
);
// Using the reduce () method to find the same value.
const commonValues = valueArrays.reduce((intersection, currentArray) => {
if (!intersection || intersection.length === 0) {
return currentArray;
}
return intersection.filter(value => currentArray.includes(value));
}, []);
// return commonValues;
return [...new Set(commonValues)];
}
}
export { MonitorDataComponent }