UNPKG

@geogirafe/lib-geoportal

Version:

GeoGirafe is a flexible application to build online geoportals.

251 lines (250 loc) 11.9 kB
/* 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;