UNPKG

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.

654 lines (653 loc) 25.1 kB
"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 __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 admin_exports = {}; __export(admin_exports, { AdminConfiguration: () => AdminConfiguration }); module.exports = __toCommonJS(admin_exports); var import_library = require("../controller/library"); var ShareConfig = __toESM(require("../types/adminShareConfig")); var import_function_and_const = require("../types/function-and-const"); function shallowDescribe(value) { if (value === null) { return "null"; } if (Array.isArray(value)) { return `Array(${value.length})`; } if (typeof value === "object") { const obj = value; const keys = Object.keys(obj); const entries = keys.map((k) => { const v = obj[k]; if (Array.isArray(v)) { return `${k}: Array(${v.length})`; } if (v !== null && typeof v === "object") { return `${k}: {${Object.keys(v).join(", ")}}`; } return `${k}: ${JSON.stringify(v)}`; }); return `{ ${entries.join(", ")} }`; } return JSON.stringify(value); } class AdminConfiguration extends import_library.BaseClass { pageConfig = []; constructor(adapter) { super(adapter, "AdminConfiguration"); this.adapter = adapter; this.pageConfig = this.adapter.config.pageConfig || []; } /** * Process configurable pages from adapter config and build navigation entries. * Orchestrates page creation (phase 1) and deferred navigation chain resolution (phase 2). * * @param option - Panel configuration partial containing pages and navigation arrays */ async processentrys(option) { var _a, _b; try { const pendingNavs = await this.createPagesFromConfig(option); this.applyPendingNavigations(option, pendingNavs); } catch (error) { const msg = error instanceof Error ? error.message : String(error); const stack = error instanceof Error ? (_a = error.stack) != null ? _a : "no stack" : "no stack"; this.log.error( `[processentrys] Failed to process panel "${(_b = option.name) != null ? _b : shallowDescribe(option)}": ${msg} Option: ${shallowDescribe(option)} Stack: ${stack}` ); } return option; } /** * Phase 1: create all pages from admin config, push stub navigation entries, and collect * pending prev/next chain assignments for deferred resolution. * home/parent links are applied immediately since they carry no chain dependency. * * @param option - Panel configuration partial */ async createPagesFromConfig(option) { var _a, _b; const entries = this.pageConfig; const pendingNavs = []; for (const entry of entries) { if (!entry.navigationAssignment || !entry.card) { continue; } const allPanelsAssignment = entry.navigationAssignment.find( (a) => a.topic === ShareConfig.ALL_PANELS_SPECIAL_ID ); const panelAssignment = entry.navigationAssignment.find((a) => a.topic === option.topic); let navAssign; if (panelAssignment) { if (!panelAssignment.navigation && allPanelsAssignment) { continue; } navAssign = panelAssignment; } else if (allPanelsAssignment) { navAssign = allPanelsAssignment; } else { continue; } let newPage; switch (entry.card) { case "cardAlarm": { if (!isAlwaysOnMode(entry.alwaysOn)) { entry.alwaysOn = "none"; } newPage = { uniqueID: entry.uniqueName, hidden: !!entry.hidden, alwaysOn: entry.alwaysOn, dpInit: "", config: { card: "cardAlarm", data: { alarmType: { type: "const", constVal: entry.alarmType || "unlock" }, headline: { type: "const", constVal: entry.headline || "Unlock" }, button1: entry.button1 ? { type: "const", constVal: entry.button1 } : void 0, button2: entry.button2 ? { type: "const", constVal: entry.button2 } : void 0, button3: entry.button3 ? { type: "const", constVal: entry.button3 } : void 0, button4: entry.button4 ? { type: "const", constVal: entry.button4 } : void 0, button5: entry.button1 ? { type: "const", constVal: entry.button5 } : void 0, button6: entry.button2 ? { type: "const", constVal: entry.button6 } : void 0, button7: entry.button3 ? { type: "const", constVal: entry.button7 } : void 0, button8: entry.button4 ? { type: "const", constVal: entry.button8 } : void 0, pin: entry.pin != null ? { type: "const", constVal: String(entry.pin) } : void 0, approved: { type: "const", constVal: !!entry.approved }, global: { type: "const", constVal: !!entry.global }, setNavi: entry.setNavi ? { type: "const", constVal: entry.setNavi } : void 0 } }, pageItems: [] }; break; } case "cardQR": { if (!isAlwaysOnMode(entry.alwaysOn)) { entry.alwaysOn = "none"; } newPage = { uniqueID: entry.uniqueName, hidden: !!entry.hidden, alwaysOn: entry.alwaysOn, dpInit: "", config: { card: "cardQR", data: { headline: { type: "const", constVal: entry.headline || "Page QR" }, selType: { type: "const", constVal: entry.selType || 0 }, ssidUrlTel: { type: "const", constVal: entry.ssidUrlTel || "" }, wlantype: { type: "const", constVal: entry.wlantype || "WPA" }, wlanhidden: { type: "const", constVal: !!entry.wlanhidden || false }, password: { type: "const", constVal: entry.qrPass || "" }, pwdhidden: { type: "const", constVal: !!entry.pwdhidden || false }, setState: entry.setState ? { type: "triggered", dp: entry.setState } : void 0 } }, pageItems: [] }; break; } case "cardTrash": { if (!isAlwaysOnMode(entry.alwaysOn)) { entry.alwaysOn = "none"; } newPage = dataForcardTrash(entry); this.log.debug(`Generated trash 1page for '${entry.uniqueName}'`); break; } case "cardGrid": case "cardGrid2": case "cardGrid3": { if (!isAlwaysOnMode(entry.alwaysOn)) { entry.alwaysOn = "none"; } newPage = { uniqueID: entry.uniqueName, hidden: !!entry.hidden, alwaysOn: entry.alwaysOn, dpInit: "", config: { card: entry.card, scrollPresentation: entry.scrollPresentation || "classic", data: { headline: { type: "const", constVal: entry.headline || entry.uniqueName } } }, pageItems: [] }; if (entry.pageItems) { let start = false; for (let index = entry.pageItems.length - 1; index >= 0; index--) { let item = entry.pageItems[index]; if (!item && !start) { continue; } start = true; if (!item) { item = { channelId: ShareConfig.emptyChannelValueConfig("empty") }; } else { item = { ...item, channelId: ShareConfig.normalizeChannelId(item.channelId) }; } const result = await this.adapter.convertAdminPageItemToPageItemConfig( item, { card: entry.card, uniqueName: entry.uniqueName }, [] ); if (!result.error && result.pageItem) { newPage.pageItems = (_a = newPage.pageItems) != null ? _a : []; newPage.pageItems.unshift(result.pageItem); } else if (result.error) { this.log.warn( `Error processing1 page item ${index} for page '${entry.uniqueName}': ${result.error}` ); } } } break; } case "cardEntities": case "cardSchedule": { if (!isAlwaysOnMode(entry.alwaysOn)) { entry.alwaysOn = "none"; } newPage = { uniqueID: entry.uniqueName, hidden: !!entry.hidden, alwaysOn: entry.alwaysOn, dpInit: "", config: { card: entry.card, data: { headline: { type: "const", constVal: entry.headline || entry.uniqueName } } }, pageItems: [] }; if (entry.pageItems) { let start = false; for (let index = entry.pageItems.length - 1; index >= 0; index--) { let item = entry.pageItems[index]; if (!item && !start) { continue; } start = true; if (!item) { item = { channelId: ShareConfig.emptyChannelValueConfig("empty") }; } else { item = { ...item, channelId: ShareConfig.normalizeChannelId(item.channelId) }; } const result = await this.adapter.convertAdminPageItemToPageItemConfig( item, { card: entry.card, uniqueName: entry.uniqueName }, [] ); if (!result.error && result.pageItem) { newPage.pageItems = (_b = newPage.pageItems) != null ? _b : []; newPage.pageItems.unshift(result.pageItem); } else if (result.error) { this.log.warn( `Error processing page item ${index} for page '${entry.uniqueName}': ${result.error}` ); } } } break; } default: { this.log.warn(`Unsupported card type '${entry.card}' for page '${entry.uniqueName}', skipping!`); continue; } } if (!this.adapter.config.adminOverridesScriptPages) { if (option.pages.find((a) => a.uniqueID === newPage.uniqueID)) { this.log.warn(`Page with name ${newPage.uniqueID} already exists, skipping!`); continue; } } else { option.pages = option.pages.filter((a) => a.uniqueID !== newPage.uniqueID); option.navigation = option.navigation.filter( (b) => b && b.name !== newPage.uniqueID ); option.navigation.forEach((b) => { var _a2, _b2; if (b) { if (this.pageConfig.find((e) => e.uniqueName === b.name)) { } else { if (((_a2 = b.left) == null ? void 0 : _a2.single) === newPage.uniqueID) { b.left.single = void 0; } if (((_b2 = b.right) == null ? void 0 : _b2.single) === newPage.uniqueID) { b.right.single = void 0; } } } }); } option.pages.push(newPage); const navigationEntry = { name: newPage.uniqueID, page: newPage.uniqueID, right: { single: void 0, double: void 0 }, left: { single: void 0, double: void 0 } }; option.navigation.push(navigationEntry); const navigation = navAssign.navigation; if (!navigation) { continue; } const nav = { ...navigation }; if (!nav.prev && !nav.next && !nav.home && !nav.parent) { nav.home = "main"; } if (nav.home) { navigationEntry.right.double = nav.home; } if (nav.parent) { navigationEntry.left.double = nav.parent; } if (nav.prev !== void 0 || nav.next !== void 0) { pendingNavs.push({ pageId: newPage.uniqueID, prev: nav.prev, next: nav.next }); } } return pendingNavs; } /** * Phase 2: wire prev/next navigation by building full chains and splicing * them into the existing navigation, preserving existing connections. * * Algorithm: * 1. Apply each page's own left/right pointers from its explicit prev/next. * 2. Group all pages by their declared `prev` target. * 3. Find root insertion points (prevTarget is NOT a pending page) and build * full chains by recursively following sub-groups. * 4. Splice each full chain after its prevTarget: if prevTarget had an existing * right pointer, it is moved to the end of the chain (preserving the topology). * 5. Handle remaining groups (prevTarget is a pending page not reached from any root). * 6. For pages that only declare `next` (no prev), splice before next target, * inheriting the target's old left pointer. * * @param option - Panel configuration partial * @param pendingNavs - Pending navigation entries collected in phase 1 */ applyPendingNavigations(option, pendingNavs) { var _a, _b, _c, _d, _e, _f, _g, _h, _i; if (!pendingNavs.length) { return; } const pendingMap = /* @__PURE__ */ new Map(); for (const p of pendingNavs) { pendingMap.set(p.pageId, p); } for (const pending of pendingNavs) { const pageEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === pending.pageId); if (!pageEntry) { continue; } if (pending.prev !== void 0) { pageEntry.left = { ...(_a = pageEntry.left) != null ? _a : {}, single: pending.prev }; } if (pending.next !== void 0) { pageEntry.right = { ...(_b = pageEntry.right) != null ? _b : {}, single: pending.next }; } } const byPrev = /* @__PURE__ */ new Map(); for (const pending of pendingNavs) { if (pending.prev !== void 0) { const list = (_c = byPrev.get(pending.prev)) != null ? _c : []; list.push(pending.pageId); byPrev.set(pending.prev, list); } } const processedGroups = /* @__PURE__ */ new Set(); for (const [prevTarget] of byPrev) { if (pendingMap.has(prevTarget)) { continue; } const fullChain = this.buildFullChain(prevTarget, byPrev, pendingMap, processedGroups); if (fullChain.length) { this.spliceChainAfter(prevTarget, fullChain, pendingMap, option); } } for (const [prevTarget] of byPrev) { if (processedGroups.has(prevTarget)) { continue; } const fullChain = this.buildFullChain(prevTarget, byPrev, pendingMap, processedGroups); if (fullChain.length) { this.spliceChainAfter(prevTarget, fullChain, pendingMap, option); } } for (const pending of pendingNavs) { if (pending.next === void 0 || pending.prev !== void 0) { continue; } const nextEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === pending.next); if (!nextEntry) { continue; } const pageEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === pending.pageId); if (!pageEntry) { continue; } const oldLeft = (_d = nextEntry.left) == null ? void 0 : _d.single; nextEntry.left = { ...(_e = nextEntry.left) != null ? _e : {}, single: pending.pageId }; if (oldLeft !== void 0 && oldLeft !== pending.pageId && !((_f = pageEntry.left) == null ? void 0 : _f.single)) { pageEntry.left = { ...(_g = pageEntry.left) != null ? _g : {}, single: oldLeft }; const oldLeftEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === oldLeft); if (((_h = oldLeftEntry == null ? void 0 : oldLeftEntry.right) == null ? void 0 : _h.single) === pending.next) { oldLeftEntry.right = { ...(_i = oldLeftEntry.right) != null ? _i : {}, single: pending.pageId }; } } } for (const pending of pendingNavs) { if (pending.prev !== void 0 && !option.navigation.find((b) => (b == null ? void 0 : b.name) === pending.prev)) { this.log.warn(`Navigation unresolved for '${pending.pageId}': prev page '${pending.prev}' not found.`); } if (pending.next !== void 0 && !option.navigation.find((b) => (b == null ? void 0 : b.name) === pending.next)) { this.log.warn(`Navigation unresolved for '${pending.pageId}': next page '${pending.next}' not found.`); } } } /** * Splice a chain of pages after prevTarget, preserving existing connections. * If prevTarget.right was already set, the old target is moved to the end * of the chain (provided the last element has no explicit next declaration). * * @param prevTarget - Name of the page to insert after * @param fullChain - Ordered list of page IDs to insert * @param pendingMap - Lookup map from pageId to PendingNavEntry * @param option - Panel configuration partial */ spliceChainAfter(prevTarget, fullChain, pendingMap, option) { var _a, _b, _c, _d, _e, _f, _g; const prevEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === prevTarget); const oldRight = (_a = prevEntry == null ? void 0 : prevEntry.right) == null ? void 0 : _a.single; if (prevEntry) { prevEntry.right = { ...(_b = prevEntry.right) != null ? _b : {}, single: fullChain[0] }; } const firstEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === fullChain[0]); if (firstEntry) { firstEntry.left = { ...(_c = firstEntry.left) != null ? _c : {}, single: prevTarget }; } for (let i = 1; i < fullChain.length; i++) { const curr = option.navigation.find((b) => (b == null ? void 0 : b.name) === fullChain[i]); const prev = option.navigation.find((b) => (b == null ? void 0 : b.name) === fullChain[i - 1]); if (curr) { curr.left = { ...(_d = curr.left) != null ? _d : {}, single: fullChain[i - 1] }; } if (prev) { const prevPending = pendingMap.get(fullChain[i - 1]); const hasExternalNext = (prevPending == null ? void 0 : prevPending.next) !== void 0 && prevPending.next !== fullChain[i]; if (!hasExternalNext) { prev.right = { ...(_e = prev.right) != null ? _e : {}, single: fullChain[i] }; } } } if (oldRight !== void 0 && !fullChain.includes(oldRight)) { const lastId = fullChain[fullChain.length - 1]; const lastPending = pendingMap.get(lastId); const lastEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === lastId); if (lastEntry && !(lastPending == null ? void 0 : lastPending.next)) { lastEntry.right = { ...(_f = lastEntry.right) != null ? _f : {}, single: oldRight }; const oldRightEntry = option.navigation.find((b) => (b == null ? void 0 : b.name) === oldRight); if (oldRightEntry) { oldRightEntry.left = { ...(_g = oldRightEntry.left) != null ? _g : {}, single: lastId }; } } } } /** * Recursively build a complete chain starting from pages grouped by prevTarget, * following sub-groups where a chain member is itself the prevTarget of another group. * * @param prevTarget - The prevTarget whose group to process * @param byPrev - Mapping from prevTarget to list of page IDs * @param pendingMap - Lookup map from pageId to PendingNavEntry * @param processedGroups - Set of already processed prevTargets (prevents cycles) */ buildFullChain(prevTarget, byPrev, pendingMap, processedGroups) { const pageIds = byPrev.get(prevTarget); if (!pageIds || processedGroups.has(prevTarget)) { return []; } processedGroups.add(prevTarget); const groupChain = this.buildChainFromGroup(pageIds, pendingMap); const result = []; for (const pageId of groupChain) { result.push(pageId); if (byPrev.has(pageId) && !processedGroups.has(pageId)) { const subChain = this.buildFullChain(pageId, byPrev, pendingMap, processedGroups); result.push(...subChain); } } return result; } /** * Build an ordered chain from a group of pages that share the same `prev` target. * Pages connected via intra-group `next` pointers are ordered first; * remaining pages are appended in their original definition order. * * @param pageIds - Page IDs in the group (definition order) * @param pendingMap - Lookup map from pageId to PendingNavEntry */ buildChainFromGroup(pageIds, pendingMap) { const pageSet = new Set(pageIds); const nextInGroup = /* @__PURE__ */ new Map(); for (const id of pageIds) { const p = pendingMap.get(id); if ((p == null ? void 0 : p.next) !== void 0 && pageSet.has(p.next)) { nextInGroup.set(id, p.next); } } const hasIncoming = new Set(nextInGroup.values()); const starts = pageIds.filter((id) => !hasIncoming.has(id)); const result = []; const visited = /* @__PURE__ */ new Set(); for (const start of starts) { let curr = start; while (curr !== void 0 && pageSet.has(curr) && !visited.has(curr)) { visited.add(curr); result.push(curr); curr = nextInGroup.get(curr); } } for (const id of pageIds) { if (!visited.has(id)) { result.push(id); } } return result; } } function isAlwaysOnMode(F) { const R = F; switch (R) { case "always": case "none": case "ignore": case "action": return true; default: (0, import_function_and_const.exhaustiveCheck)(R); return false; } } function dataForcardTrash(entry) { let newPage; const pageItems = Array.from({ length: entry.countItems }, (_, i) => { return { id: `pageItem${i}`, role: "text.list", type: "text", data: { icon: { true: { value: { type: "internal", dp: `///pageTrash_${entry.uniqueName}`, read: `return val[${i}].icon;` }, color: { type: "internal", dp: `///pageTrash_${entry.uniqueName}`, read: `return val[${i}].color;` } } }, entity1: { value: { type: "const", constVal: true } }, text: { true: { type: "internal", dp: `///pageTrash_${entry.uniqueName}`, read: `return val[${i}].text;` }, false: void 0 }, text1: { true: { type: "internal", dp: `///pageTrash_${entry.uniqueName}`, read: `return val[${i}].text1;` }, false: void 0 } } }; }); if (entry.countItems < 1 || entry.countItems > 6) { entry.countItems = 6; } if (entry.countItems < 6) { newPage = { uniqueID: entry.uniqueName, hidden: !!entry.hidden, alwaysOn: entry.alwaysOn, dpInit: "", template: "entities.waste-calendar", config: { card: "cardEntities", data: { headline: { type: "const", constVal: entry.headline || "Trash" } } }, pageItems }; } else { newPage = { uniqueID: entry.uniqueName, hidden: !!entry.hidden, alwaysOn: entry.alwaysOn, dpInit: "", template: "entities.waste-calendar", config: { card: "cardSchedule", data: { headline: { type: "const", constVal: entry.headline || "Trash" } } }, pageItems }; } return newPage; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AdminConfiguration }); //# sourceMappingURL=admin.js.map