Skip to content
Snippets Groups Projects
Select Git revision
  • 785a3ce7f59f7c79e36fdfccdd09ab0f0bf44302
  • master default protected
  • 1.31
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
  • 4.31.0
  • 4.30.1
  • 4.30.0
  • 4.29.1
  • 4.29.0
23 results

config-manager.mjs

Blame
  • config-manager.mjs 6.90 KiB
    /**
     * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
     * Node module: @schukai/monster
     *
     * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
     * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
     *
     * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
     * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
     * For more information about purchasing a commercial license, please contact schukai GmbH.
     *
     * SPDX-License-Identifier: AGPL-3.0
     */
    
    import {
    	assembleMethodSymbol,
    	CustomElement,
    	registerCustomElement,
    } from "../../dom/customelement.mjs";
    import { ConfigManagerStyleSheet } from "./stylesheet/config-manager.mjs";
    import { getWindow } from "../../dom/util.mjs";
    import { instanceSymbol } from "../../constants.mjs";
    
    export { ConfigManager };
    
    /**
     * @private
     * @type {symbol}
     */
    const indexDBInstanceSymbol = Symbol("indexDBInstance");
    
    /**
     * @private
     * @type {symbol}
     */
    const initializedPromiseSymbol = Symbol("initializedPromiseSymbol");
    
    /**
     * @private
     * @type {string}
     */
    const MODE_READONLY = "readonly";
    
    /**
     * @private
     * @type {string}
     */
    const MODE_READ_WRITE = "readwrite";
    
    /**
     * The Config Manager component is used to encapsulate the configuration of the application.
     *
     * @copyright schukai GmbH
     * @summary A config manager component
     */
    class ConfigManager extends CustomElement {
    	/**
    	 * This method is called by the `instanceof` operator.
    	 * @return {symbol}
    	 */
    	static get [instanceSymbol]() {
    		return Symbol.for("@schukai/component-host/config-manager@@instance");
    	}
    
    	constructor() {
    		super();
    
    		/**
    		 * @private
    		 * @type {symbol}
    		 */
    		this[initializedPromiseSymbol] = [];
    		this[indexDBInstanceSymbol] = null;
    		this[initializedPromiseSymbol].push(openDatabase.call(this));
    	}
    
    	/**
    	 * @return {Promise}
    	 */
    	ready() {
    		return Promise.all(this[initializedPromiseSymbol]);
    	}
    
    	/**
    	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
    	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
    	 *
    	 * The individual configuration values can be found in the table.
    	 *
    	 * @property {Object} templates Template definitions
    	 * @property {string} templates.main Main template
    	 */
    	get defaults() {
    		return Object.assign({}, super.defaults, {
    			templates: {
    				main: getTemplate(),
    			},
    			indexDB: {
    				name: "monster",
    				version: 2,
    				objectStore: {
    					name: "config",
    					keyPath: "key",
    				},
    			},
    		});
    	}
    
    	/**
    	 * @param {string} key
    	 * @return {Promise<unknown>}
    	 */
    	getConfig(key) {
    		return this.ready().then(() => {
    			return getBlob.call(this, key);
    		});
    	}
    
    	/**
    	 * @param {string} key
    	 * @return {Promise<boolean>}
    	 */
    	hasConfig(key) {
    		return this.ready()
    			.then(() => {
    				return getBlob.call(this, key);
    			})
    			.then(() => {
    				return true;
    			})
    			.catch(() => {
    				return false;
    			});
    	}
    
    	/**
    	 * @param {string} key
    	 * @param {*} value
    	 * @return {Promise<unknown>}
    	 */
    	setConfig(key, value) {
    		return this.ready().then(() => {
    			return setBlob.call(this, key, value);
    		});
    	}
    
    	deleteConfig(key) {
    		return this.ready().then(() => {
    			return deleteBlob.call(this, key);
    		});
    	}
    
    	/**
    	 *
    	 * @return {string}
    	 */
    	static getTag() {
    		return "monster-config-manager";
    	}
    
    	/**
    	 * @return {CSSStyleSheet[]}
    	 */
    	static getCSSStyleSheet() {
    		return [ConfigManagerStyleSheet];
    	}
    
    	/**
    	 */
    	[assembleMethodSymbol]() {
    		super[assembleMethodSymbol]();
    	}
    }
    
    function openDatabase() {
    	const window = getWindow();
    
    	const name = this.getOption("indexDB.name");
    	const version = this.getOption("indexDB.version");
    	const storageName = this.getOption("indexDB.objectStore.name");
    	const KeyPath = this.getOption("indexDB.objectStore.keyPath");
    
    	if (!name || !version) {
    		throw new Error("The database name and version must be set.");
    	}
    
    	const request = window.indexedDB.open(name, version);
    
    	return new Promise((resolve, reject) => {
    		request.onerror = (event) => {
    			console.error("Error opening database", event);
    			reject(request.error);
    		};
    
    		request.onsuccess = (event) => {
    			this[indexDBInstanceSymbol] = event?.target?.result;
    			resolve(request.result);
    		};
    
    		request.onupgradeneeded = (event) => {
    			const db = event.target.result;
    
    			let objectStore;
    			if (!db.objectStoreNames.contains(storageName)) {
    				objectStore = db.createObjectStore(storageName, { keyPath: KeyPath });
    			}
    
    			objectStore.transaction.oncomplete = (event) => {
    				console.log("Database upgrade complete");
    				resolve();
    			};
    		};
    	});
    }
    
    /**
     * @param {string} mode either "readonly" or "readwrite"
     */
    function getObjectStore(mode) {
    	const storageName = this.getOption("indexDB.objectStore.name");
    
    	if (!this[indexDBInstanceSymbol]) {
    		throw new Error("The database is not open.");
    	}
    
    	// @see https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction
    	// transaction(storeNames, mode, options)
    	const tx = this[indexDBInstanceSymbol].transaction(storageName, mode);
    	return tx.objectStore(storageName);
    }
    
    /**
     * @return {Promise<unknown>}
     */
    function clearObjectStore() {
    	const store = getObjectStore.call(this, "readwrite");
    
    	return new Promise((resolve, reject) => {
    		const req = store.clear();
    		req.onsuccess = function (evt) {
    			console.log("clearObjectStore completed");
    			resolve();
    		};
    		req.onerror = function (evt) {
    			console.error("clearObjectStore:", evt.target.errorCode);
    			reject(evt.target.errorCode);
    		};
    	});
    }
    
    function getBlob(key) {
    	const store = getObjectStore.call(this, MODE_READONLY);
    
    	const req = store.get(key);
    
    	return new Promise((resolve, reject) => {
    		req.onsuccess = function (evt) {
    			const value = evt.target.result;
    			if (value) {
    				resolve(value.blob);
    				return;
    			}
    			reject(new Error("The value of the key '" + key + "' is not defined."));
    		};
    	});
    }
    
    function deleteBlob(key) {
    	const store = getObjectStore.call(this, MODE_READ_WRITE);
    
    	const req = store.delete(key);
    
    	return new Promise((resolve, reject) => {
    		req.onsuccess = function (evt) {
    			resolve();
    		};
    
    		req.onerror = function (evt) {
    			console.error("deleteBlob:", evt.target.errorCode);
    			reject(evt.target.errorCode);
    		};
    	});
    }
    
    function setBlob(key, blob) {
    	const store = getObjectStore.call(this, MODE_READ_WRITE);
    
    	const KeyPath = this.getOption("indexDB.objectStore.keyPath");
    	const obj = {};
    	obj[KeyPath] = key;
    	obj.blob = blob;
    	const req = store.put(obj);
    
    	return new Promise((resolve, reject) => {
    		req.onsuccess = function (evt) {
    			resolve();
    		};
    
    		req.onerror = function (evt) {
    			console.error("setBlob:", evt.target.errorCode);
    			reject(evt.target.errorCode);
    		};
    	});
    }
    
    function getTemplate() {
    	// language=HTML
    	return `
            <div data-monster-role="control" part="control">
                <slot></slot>
            </div>`;
    }
    
    registerCustomElement(ConfigManager);