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.
594 lines (593 loc) • 23.2 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 __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 Page_exports = {};
__export(Page_exports, {
Page: () => Page,
isMediaButtonActionType: () => isMediaButtonActionType
});
module.exports = __toCommonJS(Page_exports);
var convertColorScaleBest = __toESM(require("../types/function-and-const"));
var import_baseClassPage = require("./baseClassPage");
var import_pageItem = require("../pages/pageItem");
var import_tools = require("../const/tools");
var import_templateArray = require("../templates/templateArray");
var import_icon_mapping = require("../const/icon_mapping");
var import_Color = require("../const/Color");
class Page extends import_baseClassPage.BaseClassPage {
card;
id;
lastCardCounter = 0;
//protected overridePageItemsDpInit: string | RegExp = '';
isScreensaver;
hidden = false;
/**
* Direct reference to the parent page,
* bypassing navigation logic.
*/
directParentPage;
/**
* Direct reference to the child page,
* bypassing navigation logic.
*/
directChildPage;
//readonly enums: string | string[];
config;
//config: Card['config'];
/**
* Constructs a new Page instance.
* Initializes page properties, processes device and dpInit patterns, and sets up the configuration.
* This is the base constructor for all page types (Grid, Media, Entities, etc.).
*
* @param card - Page interface containing card type, ID, and data point initialization pattern
* @param pageItemsConfig - Optional page configuration including items, enums, device references
* @param isScreensaver - Whether this page is a screensaver (default: false)
*/
constructor(card, pageItemsConfig, isScreensaver = false) {
var _a;
super(card, pageItemsConfig && pageItemsConfig.alwaysOn, pageItemsConfig && pageItemsConfig.pageItems);
this.isScreensaver = isScreensaver;
this.card = card.card;
this.id = card.id;
this.hidden = pageItemsConfig && "hidden" in pageItemsConfig ? !!pageItemsConfig.hidden : false;
this.enums = pageItemsConfig && "enums" in pageItemsConfig && pageItemsConfig.enums ? pageItemsConfig.enums : "";
this.device = pageItemsConfig && "device" in pageItemsConfig && pageItemsConfig.device ? pageItemsConfig.device : "";
if (this.device) {
card.dpInit = typeof card.dpInit === "string" ? card.dpInit.replace("#\xB0^\xB0#", this.device) : card.dpInit;
}
if (card.dpInit && typeof card.dpInit === "string") {
const reg = (0, import_tools.getRegExp)(card.dpInit);
if (reg) {
card.dpInit = reg;
}
}
this.dpInit = (_a = card.dpInit) != null ? _a : "";
this.config = pageItemsConfig && pageItemsConfig.config;
}
/**
* Initializes the page and all its PageItems.
* Processes templates, resolves data point patterns (dpInit/enums), and creates PageItem instances.
* Must be called after construction and before the page is displayed.
* Derived classes may override this to add page-specific initialization logic.
*
* @returns Promise that resolves when all PageItems are initialized
*/
async init() {
var _a;
if (this.pageItemConfig) {
for (let a = 0; a < this.pageItemConfig.length; a++) {
let options = this.pageItemConfig[a];
if (options === void 0) {
continue;
}
if (options.type === "text" && this && this.card === "cardThermo") {
options.type = "button";
options.role = "indicator";
}
options = await this.getItemFromTemplate(options);
if (!options) {
this.log.error(`Dont get a template for ${a} in ${this.name}`);
continue;
}
options.dpInit = typeof options.dpInit === "string" && options.device ? options.dpInit.replace("#\xB0^\xB0#", options.device) : options.dpInit;
if (options.dpInit && typeof options.dpInit === "string") {
const reg = (0, import_tools.getRegExp)(options.dpInit, { startsWith: true });
if (reg) {
options.dpInit = reg;
}
}
const dpInit = (_a = this.dpInit ? this.dpInit : options.dpInit) != null ? _a : "";
const enums = this.enums ? this.enums : options.enums;
if (!dpInit && !enums) {
}
options.data = dpInit || enums ? await this.basePanel.statesControler.getDataItemsFromAuto(
dpInit,
options.data,
"appendix" in options ? options.appendix : void 0,
this.enums ? this.enums : options.enums
) : options.data;
options = structuredClone(options);
if (options) {
options.dpInit = dpInit;
}
this.pageItemConfig[a] = await this.initPageItems(options);
}
}
}
/**
* Initializes a single PageItem configuration.
* Resolves data items from auto-discovery (dpInit patterns or enums) and clones the configuration.
* Used during page initialization and when dynamically creating PageItems.
*
* @param item - PageItem configuration to initialize
* @param overrideDpInit - Optional override for data point initialization pattern
* @returns Promise resolving to the initialized PageItem configuration, or undefined if invalid
*/
async initPageItems(item, overrideDpInit = "") {
var _a;
let options = item;
if (options === void 0) {
return void 0;
}
const dpInit = (_a = overrideDpInit || (this.dpInit ? this.dpInit : options.dpInit)) != null ? _a : "";
const enums = this.enums ? this.enums : options.enums;
options.data = dpInit || enums ? await this.basePanel.statesControler.getDataItemsFromAuto(
dpInit,
options.data,
"appendix" in options ? options.appendix : void 0,
this.enums ? this.enums : options.enums
) : options.data;
options = JSON.parse(JSON.stringify(options));
if (options) {
options.dpInit = dpInit;
}
return options;
}
/**
* Resolves a PageItem configuration from its template definition.
* Templates provide pre-configured settings for common device types (lights, shutters, etc.).
* Supports template inheritance and deep merging of color/icon overrides.
* Validates adapter compatibility and type consistency.
*
* @param options - PageItem configuration with template reference
* @param subtemplate - Optional sub-template identifier for recursive resolution
* @param loop - Recursion depth counter to prevent infinite loops (max 10)
* @returns Promise resolving to the merged configuration, or undefined if template not found/invalid
*/
async getItemFromTemplate(options, subtemplate = "", loop = 0) {
var _a, _b, _c, _d, _e;
if ("template" in options && options.template) {
const template = subtemplate ? import_templateArray.pageItemTemplates[subtemplate] : import_templateArray.pageItemTemplates[options.template];
const name = options.template;
if (!template) {
this.log.error(`Dont find template ${options.template}`);
return void 0;
}
if (template.adapter && typeof options.dpInit === "string" && !options.dpInit.includes(template.adapter) && typeof this.dpInit === "string" && !this.dpInit.includes(template.adapter)) {
this.log.error(
`Missing dbInit or dbInit not starts with${template.adapter} for template ${options.template}`
);
return void 0;
}
const newTemplate = structuredClone(template);
delete newTemplate.adapter;
if (options.type && options.type !== template.type && !(options.type == "button" && template.type == "text")) {
this.log.error(`Type: ${String(options.type)} is not equal with ${template.type}`);
return void 0;
}
const colorTrue = (options.color || {}).true;
const colorFalse = (options.color || {}).false;
const colorScale = (options.color || {}).scale;
const iconTrue = (options.icon || {}).true;
const iconFalse = (options.icon || {}).false;
options.type = options.type || template.type;
options.role = options.role || template.role;
options = (0, import_tools.deepAssign)(newTemplate, options);
if (template.template !== void 0) {
if (loop > 10) {
throw new Error(
`Endless loop in getItemFromTemplate() detected! From ${template.template} for ${name}. Bye Bye`
);
}
const o = await this.getItemFromTemplate(options, template.template, ++loop);
if (o !== void 0) {
options = o;
} else {
this.log.warn(`Dont get a template from ${template.template} for ${name}`);
}
}
if (options.data) {
options.data.icon = (_a = options.data.icon) != null ? _a : {};
if (colorTrue) {
options.data.icon.true = (_b = options.data.icon.true) != null ? _b : {};
options.data.icon.true.color = colorTrue;
}
if (iconTrue) {
options.data.icon.true = (_c = options.data.icon.true) != null ? _c : {};
options.data.icon.true.value = iconTrue;
}
if (colorFalse) {
options.data.icon.false = (_d = options.data.icon.false) != null ? _d : {};
options.data.icon.false.color = colorFalse;
}
if (iconFalse) {
options.data.icon.false = (_e = options.data.icon.false) != null ? _e : {};
options.data.icon.false.value = iconFalse;
}
if (colorScale) {
options.data.icon.scale = { type: "const", constVal: colorScale };
}
}
}
return options;
}
/**
* Handles incoming button events from the NSPanel.
* Base implementation logs a warning; derived classes should override this to handle
* page-specific button interactions (navigation, media controls, alarm actions, etc.).
*
* @param event - The incoming event from the panel containing button action and value
*/
async onButtonEvent(event) {
this.log.warn(`Event received but no handler! ${JSON.stringify(event)}`);
}
/**
* Sends the page type command to the NSPanel to prepare the display.
* Determines whether to force-send based on card type and panel state.
* Some card types (Chart, Thermo) always force-send to ensure correct rendering.
* Implements throttling to avoid redundant type commands (sends every 15th call if unchanged).
*
* @param force - Optional flag to force sending the pageType command regardless of cache
*/
sendType(force) {
let forceSend = force || false;
let renderCurrentPage = false;
switch (this.card) {
//case 'cardBurnRec':
case "cardChart":
case "cardLChart":
case "cardThermo":
forceSend = true;
//@disable-next-line no-fallthrough
case "cardEntities":
case "cardGrid":
case "cardGrid2":
case "cardGrid3":
case "cardMedia":
case "cardQR":
case "cardAlarm":
case "cardPower":
case "screensaver":
case "screensaver2":
case "screensaver3":
case "cardItemSpecial":
case "cardSchedule":
case "cardThermo2":
renderCurrentPage = true;
break;
case "popupNotify":
case "popupNotify2":
renderCurrentPage = false;
break;
default:
convertColorScaleBest.exhaustiveCheck(this.card);
break;
}
if (forceSend || this.basePanel.lastCard !== this.card) {
this.basePanel.lastSendTypeDate = Date.now();
this.log.debug(
`Register last send type ${this.basePanel.name}-${this.card} block for ${this.basePanel.blockTouchEventsForMs}ms`
);
this.sendToPanel(`pageType~${this.card}`, renderCurrentPage);
} else {
if (this.lastCardCounter++ > 31) {
this.lastCardCounter = 0;
this.basePanel.lastSendTypeDate = Date.now();
this.log.debug(
`Register last send type ${this.card} block for ${this.basePanel.blockTouchEventsForMs}ms`
);
this.sendToPanel(`pageType~${this.card}`, renderCurrentPage);
}
}
this.basePanel.lastCard = this.card;
}
/**
* Creates PageItem instances from configuration.
* Constructs PageItem objects for interactive elements (lights, buttons, shutters, etc.).
* Used during page initialization and when dynamically adding items to a page.
*
* @param pageItemsConfig - Single or array of PageItem configurations
* @param ident - Optional identifier prefix for the PageItems (used in naming)
* @returns Promise resolving to array of created PageItem instances (may contain undefined for invalid configs)
*/
async createPageItems(pageItemsConfig, ident = "") {
const result = [];
if (pageItemsConfig) {
if (!Array.isArray(pageItemsConfig)) {
pageItemsConfig = [pageItemsConfig];
}
for (let a = 0; a < pageItemsConfig.length; a++) {
const config = {
name: ident ? ident : `${this.name}|PI`,
adapter: this.adapter,
panel: this.basePanel,
card: "cardItemSpecial",
id: `${this.id}?${ident ? ident : a}`,
parent: this
};
result[a] = await import_pageItem.PageItem.getPageItem(config, pageItemsConfig[a]);
}
}
return result;
}
/**
* Generates the navigation payload string for the NSPanel.
* If a direct parent page exists, shows an "up" arrow for back navigation.
* Otherwise delegates to the panel's navigation controller for normal nav behavior.
*
* @param side - Optional side to generate navigation for ('left' or 'right'); if omitted, returns both
* @returns Formatted navigation payload string for MQTT transmission
*/
getNavigation(side) {
if (this.directParentPage) {
let left = "";
let right = "";
if (!side || side === "left") {
left = (0, import_tools.getPayloadRemoveTilde)(
"button",
"bUp",
import_icon_mapping.Icons.GetIcon("arrow-up-bold"),
String(import_Color.Color.rgb_dec565(import_Color.Color.navParent)),
"",
""
);
}
if (!side || side === "right") {
right = (0, import_tools.getPayload)("", "", "", "", "", "");
}
if (!side) {
return (0, import_tools.getPayload)(left, right);
}
return side === "left" ? left : right;
}
return this.basePanel.navigation.buildNavigationString(side);
}
/**
* Handles left navigation button press.
* If a direct parent page exists, navigates to it (for popup/child pages).
* Otherwise delegates to the panel's navigation controller (history-based navigation).
*
* @param short - Whether the navigation is a short press (true) or long press (false)
*/
goLeft(short) {
if (this.directParentPage) {
void this.basePanel.setActivePage(this.directParentPage, false);
return;
}
this.basePanel.navigation.goLeft(short);
}
/**
* Handles right navigation button press.
* If a direct parent page exists, does nothing (right nav disabled for child pages).
* Otherwise delegates to the panel's navigation controller (forward navigation).
*
* @param short - Whether the navigation is a short press (true) or long press (false)
*/
goRight(short) {
if (this.directParentPage) {
return;
}
this.basePanel.navigation.goRight(short);
}
/**
* Called when the page becomes visible or hidden.
* When visible: creates PageItems, sends page type, and triggers initial update.
* When hidden: deletes all PageItems to free resources and unsubscribe from states.
* Derived classes should call super.onVisibilityChange() if overriding.
*
* @param val - true if page is becoming visible, false if being hidden
*/
async onVisibilityChange(val) {
if (val) {
if (!this.pageItems || this.pageItems.length === 0) {
this.pageItems = await this.createPageItems(this.pageItemConfig);
}
this.sendType();
await this.update();
} else {
if (this.pageItems) {
for (const item of this.pageItems) {
item && await item.delete();
}
this.pageItems = [];
}
}
}
/**
* Returns the data point initialization pattern for child pages.
* Base implementation returns empty string; derived classes (like PageMenu)
* override this to pass dpInit patterns to dynamically created child pages.
*
* @returns Data point pattern (string or RegExp) for child page initialization
*/
getdpInitForChild() {
return "";
}
/**
* Registers a page as the last active page.
* Base implementation does nothing; derived classes (like PageMenu with pagination)
* override this to track navigation history or maintain parent-child relationships.
*
* @param _p - The page to register as last active (may be undefined)
*/
setLastPage(_p) {
}
/**
* Removes a page from the last active page tracking.
* Base implementation does nothing; derived classes (like PageMenu)
* override this to clean up navigation history or parent-child relationships.
*
* @param _p - The page to remove from tracking (may be undefined)
*/
removeLastPage(_p) {
}
getLastPage() {
return void 0;
}
/**
* Updates the page content and sends data to the NSPanel.
* Base implementation logs a warning; all derived page classes MUST override this
* to implement page-specific rendering logic (e.g., grid items, media player state, chart data).
* Called when the page becomes visible or when subscribed states change.
*/
async update() {
this.adapter.log.warn(
`<- instance of [${Object.getPrototypeOf(this)}] update() is not defined or call super.onStateTrigger()`
);
}
/**
* Handles popup requests from the NSPanel (e.g., light color picker, shutter position).
* Routes the request to the appropriate PageItem, executes commands, or generates popup payloads.
* Called when user interacts with a PageItem that triggers a popup dialog.
*
* @param id - The PageItem index or identifier
* @param popup - The type of popup to display (e.g., 'popupLight', 'popupShutter')
* @param action - The button action performed in the popup (e.g., 'OnOff', 'brightnessSlider')
* @param value - The value from the action (e.g., slider position, color RGB)
* @param _event - The raw incoming event from the panel (optional)
* @returns Promise that resolves when popup is handled and sent to panel
*/
async onPopupRequest(id, popup, action, value, _event = null) {
if (!this.pageItems || id == "") {
this.log.debug(
`onPopupRequest: No pageItems or id this is only a warning if u used a pageitem except: 'arrow': ${id}`
);
return;
}
let item;
if (isNaN(Number(id)) && typeof id === "string") {
this.log.error(
`onPopupRequest: id should be a number but is a string: ${id}. Page name: ${this.name}, Page id: ${this.id}, Page card: ${this.card}`
);
} else {
const i = typeof id === "number" ? id : parseInt(id);
item = this.pageItems[i];
}
if (!item) {
return;
}
let msg = null;
if (action && value !== void 0 && await item.onCommand(action, value)) {
return;
} else if (convertColorScaleBest.isPopupType(popup) && action !== "bExit") {
this.basePanel.lastCard = "";
msg = await item.GeneratePopup(popup);
}
if (msg !== null) {
this.sleep = true;
this.sendToPanel(msg, false);
}
}
async onButtonPress3(id, _popup, action, value, _event = null) {
if (!this.pageItems || id == "") {
this.log.debug(
`onPopupRequest: No pageItems or id this is only a warning if u used a pageitem except: 'arrow': ${id}`
);
return false;
}
let item;
if (isNaN(Number(id)) && typeof id === "string") {
this.log.error(
`onPopupRequest: id should be a number but is a string: ${id}. Page name: ${this.name}, Page id: ${this.id}, Page card: ${this.card}`
);
} else {
const i = typeof id === "number" ? id : parseInt(id);
item = this.pageItems[i];
}
if (!item) {
return false;
}
return !!action && value !== void 0 && await item.onCommandLongPress(action, value);
}
/**
* Cleans up the page and all its resources.
* Recursively deletes child/parent page references, destroys all PageItems,
* and calls the base class cleanup (unsubscribes states, clears timers).
* Must be called when a page is removed from navigation or the adapter unloads.
*
* @returns Promise that resolves when cleanup is complete
*/
async delete() {
this.unload = true;
if (this.directChildPage) {
await this.directChildPage.delete();
this.directChildPage = void 0;
}
if (this.directParentPage) {
this.directParentPage.directChildPage = void 0;
this.directParentPage = void 0;
}
if (this.pageItems) {
for (const item of this.pageItems) {
item && await item.delete();
}
}
this.pageItems = [];
this.pageItemConfig = [];
await super.delete();
}
}
function isMediaButtonActionType(F) {
switch (F) {
case "media-back":
case "media-pause":
case "media-next":
case "media-shuffle":
case "volumeSlider":
case "mode-speakerlist":
case "mode-playlist":
case "mode-tracklist":
case "mode-repeat":
case "mode-equalizer":
case "mode-seek":
case "mode-crossfade":
case "mode-favorites":
case "mode-insel":
case "media-OnOff":
case "button":
return true;
}
console.error(`${F} isMediaButtonActionType === false`);
return false;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Page,
isMediaButtonActionType
});
//# sourceMappingURL=Page.js.map