iobroker.nspanel-lovelace-ui
Version:
NsPanel Lovelace UI is a Firmware for the nextion screen inside of NSPanel in the Design of Lovelace UI Design.
783 lines (782 loc) • 26.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var library_exports = {};
__export(library_exports, {
BaseClass: () => BaseClass,
Library: () => Library
});
module.exports = __toCommonJS(library_exports);
var import_fs = __toESM(require("fs"));
var import_definition = require("../const/definition");
var LocalTranslations = __toESM(require("../../../templates/translations.json"));
var _adapter, _prefix;
class BaseClass {
unload = false;
log;
adapter;
library;
name = ``;
friendlyName = ``;
constructor(adapter, name = "", logName = "") {
this.name = name;
this.friendlyName = logName ? logName : this.name;
this.log = new CustomLog(adapter, this.friendlyName);
this.adapter = adapter;
this.library = adapter.library;
}
async delete() {
this.unload = true;
}
}
class CustomLog {
constructor(adapter, text = "") {
__privateAdd(this, _adapter);
__privateAdd(this, _prefix);
__privateSet(this, _adapter, adapter);
__privateSet(this, _prefix, text);
}
getName() {
return __privateGet(this, _prefix);
}
debug(log, log2 = "") {
__privateGet(this, _adapter).log.debug(log2 ? `[${log}] ${log2}` : `[${__privateGet(this, _prefix)}] ${log}`);
}
info(log, log2 = "") {
__privateGet(this, _adapter).log.info(log2 ? `[${log}] ${log2}` : `[${__privateGet(this, _prefix)}] ${log}`);
}
warn(log, log2 = "") {
__privateGet(this, _adapter).log.warn(log2 ? `[${log}] ${log2}` : `[${__privateGet(this, _prefix)}] ${log}`);
}
error(log, log2 = "") {
__privateGet(this, _adapter).log.error(log2 ? `[${log}] ${log2}` : `[${__privateGet(this, _prefix)}] ${log}`);
if (__privateGet(this, _adapter).config.testCase) {
throw new Error(log2 ? `[${log}] ${log2}` : `[${__privateGet(this, _prefix)}] No Erros while testing - ${log}`);
}
}
setLogPrefix(text) {
__privateSet(this, _prefix, text);
}
}
_adapter = new WeakMap();
_prefix = new WeakMap();
class Library extends BaseClass {
stateDataBase = {};
forbiddenDirs = [];
translation = { custom: {}, standard: {} };
unknownTokens = {};
unknownTokensInterval;
defaults = {
updateStateOnChangeOnly: false
};
constructor(adapter, _options = null) {
super(adapter, "library");
this.stateDataBase = {};
}
async init() {
await this.checkLanguage();
if (this.adapter.config.logUnknownTokens) {
this.unknownTokensInterval = this.adapter.setInterval(() => {
this.log.info(`Unknown tokens: ${JSON.stringify(this.unknownTokens)}`);
}, 6e4);
}
}
/**
/**
* Write/create states and channels from a JSON subtree using a definition.
*
* Parallelization:
* - Parent channel is created first (sequential).
* - Children (array items or object keys) are processed in parallel with a concurrency limit.
*
* @param prefix ioBroker datapoint prefix to write into (e.g., "adapter.0.foo")
* @param objNode JSON-path into the definition (used by getObjectDefFromJson)
* @param def Definition JSON (mapping of nodes to Channel/State object definitions)
* @param data Source JSON subtree to materialize under `prefix`
* @param expandTree If true, arrays below a state are expanded into channels instead of being stringified
* @param concurrency Max number of parallel child writes (default: 8)
* @returns Promise<void>
*/
async writeFromJson(prefix, objNode, def, data, expandTree = false, concurrency = 8) {
if (!def || typeof def !== "object") {
return;
}
const t = typeof data;
if (data === void 0 || t !== "string" && t !== "number" && t !== "boolean" && t !== "object") {
return;
}
const objectDefinition = objNode ? await this.getObjectDefFromJson(`${objNode}`, def, data) : null;
if (objectDefinition) {
objectDefinition.native = {
...objectDefinition.native || {},
objectDefinitionReference: objNode
};
}
const queue = [];
let active = 0;
const runLimited = async (task) => {
if (active >= concurrency) {
await new Promise((resolve) => queue.push(async () => resolve()));
}
active++;
try {
return await task();
} finally {
active--;
const next = queue.shift();
if (next) {
next().catch(() => void 0);
}
}
};
const processArrayItem = (idx, item) => runLimited(async () => {
if (!objectDefinition) {
return;
}
const defChannel = this.getChannelObject(objectDefinition);
const dp = `${prefix}${`00${idx}`.slice(-2)}`;
await this.writedp(dp, null, defChannel);
await this.writeFromJson(dp, `${objNode}`, def, item, expandTree, concurrency);
});
const processObjectKey = (key, value) => runLimited(async () => {
await this.writeFromJson(`${prefix}.${key}`, `${objNode}.${key}`, def, value, expandTree, concurrency);
});
if (typeof data === "object" && data !== null) {
if (Array.isArray(data)) {
if (!objectDefinition) {
return;
}
if (objectDefinition.type === "state" && !expandTree) {
const serialized = JSON.stringify(data) || "[]";
await this.writeFromJson(prefix, objNode, def, serialized, expandTree, concurrency);
return;
}
const tasks2 = data.map((item, idx) => processArrayItem(idx, item));
await Promise.all(tasks2);
return;
}
if (objectDefinition) {
const defChannel = this.getChannelObject(objectDefinition);
await this.writedp(prefix, null, defChannel);
}
if (data === null) {
return;
}
const entries = Object.entries(data);
const tasks = entries.map(([k, v]) => processObjectKey(k, v));
await Promise.all(tasks);
return;
}
if (!objectDefinition) {
return;
}
await this.writedp(prefix, data, objectDefinition);
}
/**
* Get the ioBroker.Object out of stateDefinition
*
* @param key is the deep linking key to the definition
* @param def is the definition object
* @param data is the definition dataset
* @returns ioBroker.ChannelObject | ioBroker.DeviceObject | ioBroker.StateObject
*/
async getObjectDefFromJson(key, def, data) {
let result = this.deepJsonValue(key, def);
if (result === null || result === void 0) {
const k = key.split(".");
if (k && k[k.length - 1].startsWith("_")) {
result = import_definition.genericStateObjects.customString;
result = structuredClone(result);
} else {
this.log.debug(`No definition for ${key}!`);
result = import_definition.genericStateObjects.default;
result = structuredClone(result);
switch (typeof data) {
case "number":
case "bigint":
{
result.common.type = "number";
result.common.role = "value";
}
break;
case "boolean":
{
result.common.type = "boolean";
result.common.role = "indicator";
}
break;
case "string":
{
result.common.type = "string";
result.common.role = "text";
}
break;
case "symbol":
case "undefined":
case "object":
case "function":
{
result.common.type = "string";
result.common.role = "json";
}
break;
}
}
} else {
result = structuredClone(result);
}
return result;
}
deepJsonValue(key, data) {
if (!key || !data || typeof data !== "object" || typeof key !== "string") {
throw new Error(`Error(222) data or key are missing/wrong type!`);
}
const k = key.split(`.`);
let c = 0, s = data;
while (c < k.length) {
s = s[k[c++]];
if (s === void 0) {
return null;
}
}
return s;
}
/**
* Get a channel/device definition from property _channel out of a getObjectDefFromJson() result or a default definition.
*
* @param definition the definition object
* @returns ioBroker.ChannelObject | ioBroker.DeviceObject or a default channel obj
*/
getChannelObject(definition = null) {
const def = definition && definition._channel || null;
const result = {
_id: def ? def._id : "",
type: def ? def.type == "channel" ? "channel" : def.type === "device" ? "device" : "folder" : "folder",
common: {
name: def && def.common && def.common.name || "no definition"
},
native: def && def.native || {}
};
return result;
}
/**
* Write/create the specified datapoint with a value.
*
* Behavior:
* - Creates/extends the ioBroker object when it does not exist in the in-memory DB.
* - Channels/Devices are created/updated but never written as states.
* - For states, writes only when:
* - `val !== undefined` and
* - (`defaults.updateStateOnChangeOnly` is true) OR (old value differs) OR (`forceWrite` is true) OR (`!node.ack`)
* - Values are converted to the target ioBroker common.type (if available).
* - Skips write operations for disallowed directories (as per `isDirAllowed`).
*
* @param dp Datapoint id (will be normalized via `cleandp`).
* @param val New value (channels/devices use `undefined` and will not be written).
* @param obj Object definition for creation/extension (Channel/Device/State); required if the node is new.
* @param ack Acknowledged flag for state write.
* @param forceWrite Force write even if `val` equals old value.
* @returns Promise<void>
* @throws Error if a new state must be created but `obj` is missing.
*/
async writedp(dp, val, obj = null, ack = true, forceWrite = false) {
var _a, _b, _c;
dp = this.cleandp(dp);
let node = this.readdb(dp);
const disallowed = !this.isDirAllowed(dp);
if (node === void 0) {
if (!obj) {
throw new Error("writedp: trying to create a state without object information.");
}
obj._id = `${this.adapter.name}.${this.adapter.instance}.${dp}`;
if (typeof obj.common.name === "string") {
obj.common.name = await this.getTranslationObj(obj.common.name);
}
if (!disallowed) {
if (obj.type === "state" && obj.common.states) {
const existing = await this.adapter.getObjectAsync(dp);
if (existing) {
existing.common.states = obj.common.states;
await this.adapter.setObject(dp, existing);
}
}
await this.adapter.extendObject(dp, obj);
}
const stateType = obj.type !== "state" ? void 0 : (_a = obj == null ? void 0 : obj.common) == null ? void 0 : _a.type;
node = this.setdb(dp, obj.type, void 0, stateType, true, Date.now(), obj);
} else if (node.init && obj) {
if (typeof obj.common.name === "string") {
obj.common.name = await this.getTranslationObj(obj.common.name);
}
if (!disallowed) {
if (obj.type === "state" && obj.common.states) {
const existing = await this.adapter.getObjectAsync(dp);
if (existing) {
existing.common.states = obj.common.states;
await this.adapter.setObject(dp, existing);
}
}
await this.adapter.extendObject(dp, obj);
node.init = false;
}
}
if (obj && obj.type !== "state") {
return;
}
if (node && !(node.type === "state" && val === void 0)) {
this.setdb(dp, node.type, val, node.stateTyp, false, void 0, void 0, node.init);
}
if (node && val !== void 0 && (!this.defaults.updateStateOnChangeOnly || node.val != val || forceWrite || !node.ack)) {
const targetType = (_c = (_b = obj == null ? void 0 : obj.common) == null ? void 0 : _b.type) != null ? _c : node.stateTyp;
if (targetType && typeof val !== targetType) {
val = this.convertToType(val, targetType);
}
if (!disallowed) {
await this.adapter.setState(dp, {
val,
ts: Date.now(),
ack
});
}
}
}
setForbiddenDirs(dirs) {
this.forbiddenDirs = this.forbiddenDirs.concat(dirs);
}
isDirAllowed(dp) {
if (dp && dp.split(".").length <= 2) {
return true;
}
for (const a of this.forbiddenDirs) {
if (dp.search(new RegExp(a, "g")) != -1) {
return false;
}
}
return true;
}
getStates(str) {
const result = {};
for (const dp in this.stateDataBase) {
if (dp.search(new RegExp(str, "g")) != -1) {
result[dp] = this.stateDataBase[dp];
}
}
return result;
}
async cleanUpTree(hold, filter, deep) {
let del = [];
for (const dp in this.stateDataBase) {
if (filter && filter.filter((a) => dp.startsWith(a) || a.startsWith(dp)).length == 0) {
continue;
}
if (hold.filter((a) => dp.startsWith(a) || a.startsWith(dp)).length > 0) {
continue;
}
delete this.stateDataBase[dp];
del.push(dp.split(".").slice(0, deep).join("."));
}
del = del.filter((item, pos, arr) => {
return arr.indexOf(item) == pos;
});
for (const a of del) {
await this.adapter.delObjectAsync(a, { recursive: true });
this.log.debug(`Clean up tree delete: ${a}`);
}
}
/**
* Remove forbidden chars from datapoint string.
*
* @param string Datapoint string to clean
* @param lowerCase lowerCase() first param.
* @param removePoints remove . from dp
* @returns void
*/
cleandp(string, lowerCase = false, removePoints = false) {
if (!string && typeof string != "string") {
return string;
}
string = string.replace(this.adapter.FORBIDDEN_CHARS, "_");
if (removePoints) {
string = string.replace(/[^0-9A-Za-z_-]/gu, "_");
} else {
string = string.replace(/[^0-9A-Za-z._-]/gu, "_");
}
return lowerCase ? string.toLowerCase() : string;
}
/**
* Convert an arbitrary value to the requested target type and return an ioBroker.StateValue.
*
* Rules:
* - 'string': primitives -> String(value); arrays/objects -> JSON string.
* - 'number': numbers stay; booleans -> 1/0; strings parsed with comma support ("1,23" -> 1.23); NaN -> 0.
* - 'boolean': booleans stay; numbers -> value !== 0; strings -> common truthy/falsey keywords; otherwise Boolean(value).
* - 'array' | 'json': always JSON string of the input.
*
* @param value Input value to convert (may be primitive, array, or object)
* @param type Target type: 'string' | 'number' | 'boolean' | 'array' | 'json'
* @returns Converted value as ioBroker.StateValue (string | number | boolean | null)
* @throws Error if `type` is 'undefined'
*/
convertToType(value, type) {
if (value === null) {
return null;
}
if (type === "undefined") {
throw new Error('convertToType: type "undefined" not allowed');
}
if (value === void 0) {
value = "";
}
if (type === "mixed") {
if (["string", "number", "boolean"].includes(typeof value)) {
return value;
}
type = Array.isArray(value) ? "array" : "json";
}
if (type === "object") {
type = "json";
}
const toJsonString = (v) => JSON.stringify(v);
switch (type) {
case "string": {
if (typeof value === "string") {
return value;
}
if (typeof value === "object") {
return toJsonString(value);
}
return String(value);
}
case "number": {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "boolean") {
return value ? 1 : 0;
}
if (typeof value === "string") {
const n = Number(value.trim().replace(",", "."));
return Number.isFinite(n) ? n : 0;
}
return 0;
}
case "boolean": {
if (typeof value === "boolean") {
return value;
}
if (typeof value === "number") {
return value !== 0;
}
if (typeof value === "string") {
const s = value.trim().toLowerCase();
if (["true", "1", "on", "yes", "y"].includes(s)) {
return true;
}
if (["false", "0", "off", "no", "n", ""].includes(s)) {
return false;
}
return Boolean(s);
}
return Boolean(value);
}
case "array":
case "json": {
return toJsonString(value);
}
}
}
readdb(dp) {
return this.stateDataBase[this.cleandp(dp)];
}
setdb(dp, type, val = void 0, stateType = void 0, ack = true, ts = Date.now(), obj = void 0, init = false) {
if (typeof type == "object") {
type = type;
this.stateDataBase[dp] = type;
} else {
type = type;
this.stateDataBase[dp] = {
type,
stateTyp: stateType !== void 0 ? stateType : this.stateDataBase[dp] !== void 0 && this.stateDataBase[dp].stateTyp !== void 0 ? this.stateDataBase[dp].stateTyp : void 0,
val,
ack,
ts: ts ? ts : Date.now(),
obj: obj !== void 0 ? obj : this.stateDataBase[dp] !== void 0 && this.stateDataBase[dp].obj !== void 0 ? this.stateDataBase[dp].obj : void 0,
init
};
}
return this.stateDataBase[dp];
}
async memberDeleteAsync(data) {
if (this.unknownTokensInterval) {
this.adapter.clearInterval(this.unknownTokensInterval);
}
for (const d of data) {
await d.delete();
}
}
async fileExistAsync(file) {
if (import_fs.default.existsSync(`./admin/${file}`)) {
return true;
}
return false;
}
/**
* Initialise the database with the states to prevent unnecessary creation and writing.
*
* @param states States that are to be read into the database during initialisation.
* @returns void
*/
async initStates(states) {
if (!states) {
return;
}
this.stateDataBase = {};
const removedChannels = [];
for (const state in states) {
const dp = state.replace(`${this.adapter.name}.${this.adapter.instance}.`, "");
const del = !this.isDirAllowed(dp);
if (!del) {
const obj = await this.adapter.getObjectAsync(dp);
this.setdb(
dp,
"state",
states[state] ? states[state].val : void 0,
obj && obj.common && obj.common.type ? obj.common.type : void 0,
states[state] && states[state].ack,
states[state] && states[state].ts ? states[state].ts : Date.now(),
obj == null ? void 0 : obj,
true
);
} else {
if (!removedChannels.every((a) => !dp.startsWith(a))) {
continue;
}
const channel = dp.split(".").slice(0, 4).join(".");
removedChannels.push(channel);
await this.adapter.delObjectAsync(channel, { recursive: true });
this.log.debug(`Delete channel with dp:${channel}`);
}
}
}
/**
* Resets states that have not been updated in the database in offset time.
*
* @param prefix String with which states begin that are reset.
* @param offset Time in ms since last update.
* @param del Delete the state if it is not updated.
* @returns void
*/
async garbageColleting(prefix, offset = 2e3, del = false) {
if (!prefix) {
return;
}
if (this.stateDataBase) {
for (const id in this.stateDataBase) {
if (id.startsWith(prefix)) {
const state = this.stateDataBase[id];
if (!state || state.val == void 0) {
continue;
}
if (state.ts < Date.now() - offset) {
if (del) {
await this.cleanUpTree([], [id], -1);
continue;
}
let newVal;
switch (state.stateTyp) {
case "string":
if (typeof state.val == "string") {
if (state.val.startsWith("{") && state.val.endsWith("}")) {
newVal = "{}";
} else if (state.val.startsWith("[") && state.val.endsWith("]")) {
newVal = "[]";
} else {
newVal = "";
}
} else {
newVal = "";
}
break;
case "bigint":
case "number":
newVal = -1;
break;
case "boolean":
newVal = false;
break;
case "symbol":
case "object":
case "function":
newVal = null;
break;
case "undefined":
newVal = void 0;
break;
}
await this.writedp(id, newVal);
}
}
}
}
}
getLocalLanguage() {
if (this.adapter.language) {
return this.adapter.language;
}
return "en";
}
getTranslation(key) {
if (!key) {
return "";
}
if (this.translation.standard[key] !== void 0) {
return this.translation.standard[key];
}
if (this.translation.custom[key] !== void 0) {
return this.translation.custom[key];
}
if (this.adapter.config.logUnknownTokens) {
this.unknownTokens[key] = "";
}
return key;
}
existTranslation(key) {
if (this.translation.standard[key] !== void 0) {
return true;
}
if (this.translation.custom[key] !== void 0) {
return true;
}
return false;
}
async getTranslationObj(key) {
const language = ["en", "de", "ru", "pt", "nl", "fr", "it", "es", "pl", "uk", "zh-cn"];
const result = {};
for (const l of language) {
try {
const i = await Promise.resolve().then(() => __toESM(require(`../../../admin/i18n/${l}/translations.json`)));
if (i[key] !== void 0) {
result[l] = i[key];
}
} catch {
if (this.adapter.config.logUnknownTokens) {
this.unknownTokens[key] = "";
}
return key;
}
}
if (result.en == void 0) {
if (this.adapter.config.logUnknownTokens) {
this.unknownTokens[key] = "";
}
return key;
}
return result;
}
async checkLanguage() {
try {
this.log.debug(`Load language ${this.adapter.language}`);
this.translation.standard = await Promise.resolve().then(() => __toESM(require(`../../../admin/i18n/${this.adapter.language}/translations.json`)));
} catch {
this.log.warn(`Standard: Language ${this.adapter.language} not exist!`);
}
try {
this.log.debug(`Load language ${this.adapter.language} from custom`);
this.translation.custom = await Promise.resolve().then(() => __toESM(require(`../../../admin/custom/i18n/${this.adapter.language}.json`)));
} catch {
this.log.warn(`Custom: Language ${this.adapter.language} not exist!`);
}
}
sortText(text) {
text.sort((a, b) => {
const nameA = a.toUpperCase();
const nameB = b.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
});
return text;
}
/**
*
* @param text string to replace a Date
* @param noti appendix to translation key
* @param day true = Mo, 12.05 - false = 12.05
* @returns Monday first March
*/
convertSpeakDate(text, noti = "", day = false) {
if (!text || typeof text !== `string`) {
return ``;
}
const b = text.split(`.`);
if (day) {
b[0] = b[0].split(" ")[2];
}
return ` ${`${(/* @__PURE__ */ new Date(`${b[1]}/${b[0]}/${(/* @__PURE__ */ new Date()).getFullYear()}`)).toLocaleString(this.getLocalLanguage(), {
weekday: day ? "long" : void 0,
day: "numeric",
month: `long`
})} `.replace(/([0-9]+\.)/gu, (x) => {
const result = this.getTranslation(x + noti);
if (result != x + noti) {
return result;
}
return this.getTranslation(x);
})}`;
}
getLocalTranslation(group, key) {
try {
if (group in LocalTranslations) {
const result = LocalTranslations[group];
if (key in result) {
return result[key]["de-DE"];
}
}
} catch {
return key;
}
return key;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BaseClass,
Library
});
//# sourceMappingURL=library.js.map