@geogirafe/lib-geoportal
Version:
GeoGirafe is a flexible application to build online geoportals.
251 lines (250 loc) • 11.9 kB
JavaScript
/* eslint @typescript-eslint/no-explicit-any: 0 */
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _StateManager_girafeState, _StateManager_stateProxy, _StateManager_callbacks;
import GirafeSingleton from '../../base/GirafeSingleton';
import State from './state';
import ConfigManager from '../configuration/configmanager';
import onChange from 'on-change';
import { getPropertyByPath } from '../utils/pathUtils';
class StateManager extends GirafeSingleton {
get state() {
return __classPrivateFieldGet(this, _StateManager_stateProxy, "f");
}
constructor(type) {
super(type);
_StateManager_girafeState.set(this, null);
_StateManager_stateProxy.set(this, void 0);
_StateManager_callbacks.set(this, {});
Object.defineProperty(this, "configManager", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.configManager = ConfigManager.getInstance();
__classPrivateFieldSet(this, _StateManager_girafeState, new State(), "f");
__classPrivateFieldSet(this, _StateManager_stateProxy, onChange(__classPrivateFieldGet(this, _StateManager_girafeState, "f"), (path, value, oldValue, _applyData) => {
if (!this.areEqual(oldValue, value)) {
console.debug(`${path} has changed.`);
this.onChange(path, oldValue, value);
}
}, {
// Adding object in the state with a name starting by a symbol will avoid to Proxy this object
// (The Proxy API changes the class!) and prevent to listen changes on this object.
// NOTE: the method areEqual() will also ignore underscores when deeply comparing objects
ignoreUnderscores: true,
ignoreSymbols: true,
ignoreDetached: true
}), "f");
// Prevent extensions of the State Object.
Object.preventExtensions(__classPrivateFieldGet(this, _StateManager_girafeState, "f"));
this.setDefaultValues();
}
setDefaultValues() {
// Set default values
this.configManager?.loadConfig().then(() => {
const config = this.configManager?.Config;
if (this.state && config) {
this.state.projection = config.map.srid;
this.state.language = config.languages.defaultLanguage;
this.state.interface.selectionComponent = config.interface.defaultSelectionComponent;
}
});
}
onChange(property, oldValue, value) {
const path = property.trim();
for (const key in __classPrivateFieldGet(this, _StateManager_callbacks, "f")) {
const regex = new RegExp('^' + key + '$');
if (path.match(regex)) {
// We find the parent object and send it in the callback
const indexOfLastPoint = path.lastIndexOf('.');
const parentPath = path.substring(0, indexOfLastPoint);
const childPathFromParent = path.substring(indexOfLastPoint + 1);
const parentObject = getPropertyByPath(this.state, parentPath);
if (!parentObject.found) {
console.warn('Parent object could not be found in the state');
}
else {
// At this point, the "value" is not the proxy, but the initial object.
// But we want to get the proxy and to return it, because it can be used in the calling methods
// Otherwise, the modifications made to the object won't go through the proxy, and the events won't be fired
value = parentObject.object[childPathFromParent];
}
const callbacks = __classPrivateFieldGet(this, _StateManager_callbacks, "f")[key];
for (const callback of callbacks) {
callback(oldValue, value, parentObject.object);
}
}
}
}
subscribe(path, callback) {
const pathAsString = typeof path === 'string' ? path : path.source;
if (!(pathAsString in __classPrivateFieldGet(this, _StateManager_callbacks, "f"))) {
__classPrivateFieldGet(this, _StateManager_callbacks, "f")[pathAsString] = [];
}
__classPrivateFieldGet(this, _StateManager_callbacks, "f")[pathAsString].push(callback);
console.debug(`Subscribing to ${path}. ${__classPrivateFieldGet(this, _StateManager_callbacks, "f")[pathAsString].length} are currently subscribing ${pathAsString}.`);
// At the application start, perhaps the value in state was initialized before the subscribe method was called
// Therefore, if the subscribed value os not null, undefined or an empty object or array
// We immediately call the callback.
const obj = getPropertyByPath(this.state, pathAsString);
if (obj.found) {
if (obj.object === null ||
obj.object === undefined ||
(Array.isArray(obj.object) && obj.object.length === 0) ||
(obj.object instanceof Object && Object.keys(obj.object).length === 0)) {
// Empty object => nothing to do
}
else {
// Object is not null during the subscribe. => we call the callback
const parentPath = pathAsString.substring(0, pathAsString.lastIndexOf('.'));
const parentObject = getPropertyByPath(this.state, parentPath);
callback(null, obj.object, parentObject.object);
}
}
return callback;
}
unsubscribe(callbacks) {
(Array.isArray(callbacks) ? callbacks : [callbacks]).forEach((callback) => {
let found = false;
for (const path in __classPrivateFieldGet(this, _StateManager_callbacks, "f")) {
const callbacks = __classPrivateFieldGet(this, _StateManager_callbacks, "f")[path];
const index = callbacks.indexOf(callback);
if (index !== -1) {
found = true;
callbacks.splice(index, 1);
console.debug(`Unsubscribing to ${path}. ${__classPrivateFieldGet(this, _StateManager_callbacks, "f")[path].length} subscribtions remaining.`);
}
}
if (!found) {
throw Error(`Cannot unsubscribe this callback : it does not exist`);
}
});
}
/**
* Returns true if to object are deeply equal, false otherwise
* Circular references are ignored
*/
areEqual(obj1, obj2, visitedObjects = new WeakSet()) {
let areEqual;
areEqual = this.areNumbersEqual(obj1, obj2);
if (areEqual !== undefined) {
return areEqual;
}
areEqual = this.areNullOrUndefinedEqual(obj1, obj2);
if (areEqual !== undefined) {
return areEqual;
}
areEqual = this.areSimpleValuesEqual(obj1, obj2);
if (areEqual !== undefined) {
return areEqual;
}
areEqual = this.areCircularReferencesEqual(obj1, obj2, visitedObjects);
if (areEqual !== undefined) {
return areEqual;
}
// Mark the objects as visited
visitedObjects.add(obj1);
visitedObjects.add(obj2);
areEqual = this.areArraysEqual(obj1, obj2, visitedObjects);
if (areEqual !== undefined) {
return areEqual;
}
areEqual = this.areObjectsEqual(obj1, obj2, visitedObjects);
if (areEqual !== undefined) {
return areEqual;
}
// Unmanaged case
throw Error('Unmanaged case for equality check');
}
areNumbersEqual(obj1, obj2) {
if (typeof obj1 === 'number' && typeof obj2 === 'number') {
// Special case for numbers : check NaN
if (Number.isNaN(obj1) && Number.isNaN(obj2)) {
return true;
}
return obj1 === obj2;
}
// Not numbers
return undefined;
}
areNullOrUndefinedEqual(obj1, obj2) {
if (obj1 === null || obj2 === null || obj1 === undefined || obj2 === undefined) {
return obj1 === obj2;
}
// Not null or undefined
return undefined;
}
areSimpleValuesEqual(obj1, obj2) {
if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
return obj1 === obj2;
}
// Not a simple object
return undefined;
}
areCircularReferencesEqual(obj1, obj2, visitedObjects) {
if (visitedObjects.has(obj1) || visitedObjects.has(obj2)) {
// This object was already checked. It should be the same object
return obj1 === obj2;
}
// Not a circular reference
return undefined;
}
areArraysEqual(obj1, obj2, visitedObjects) {
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) {
// Different length for both arrays
return false;
}
for (let i = 0; i < obj1.length; i++) {
if (!this.areEqual(obj1[i], obj2[i], visitedObjects)) {
// Not equal
return false;
}
}
// Arrays are equal
return true;
}
// Not arrays
return undefined;
}
areObjectsEqual(obj1, obj2, visitedObjects) {
if (typeof obj1 === 'object' && typeof obj2 === 'object') {
// Ignore properties that begins with underscore
// This is coherent with the configuration of on-change
// Otherwise, all objects will be compared (including openlayers ones), and we do not want this
const keys1 = Object.keys(obj1).filter((key) => !key.startsWith('_'));
const keys2 = Object.keys(obj2).filter((key) => !key.startsWith('_'));
if (keys1.length !== keys2.length) {
// Not the same number of properties
return false;
}
for (const key of keys1) {
if (!obj2.hasOwnProperty(key)) {
// Key is not present in the second object
return false;
}
if (!this.areEqual(obj1[key], obj2[key], visitedObjects)) {
// Properties have different values
return false;
}
}
// Everything is equal
return true;
}
// Not an object
return undefined;
}
}
_StateManager_girafeState = new WeakMap(), _StateManager_stateProxy = new WeakMap(), _StateManager_callbacks = new WeakMap();
export default StateManager;