diff --git a/package.json b/package.json index 4bc5f94..7e6860d 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "postversion": "git push && git push --tags" }, "dependencies": { - "home-assistant-javascript-templates": "^4.0.0", + "home-assistant-javascript-templates": "^5.0.0", "home-assistant-query-selector": "^4.2.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64515b4..ebb46db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: home-assistant-javascript-templates: - specifier: ^4.0.0 - version: 4.0.0 + specifier: ^5.0.0 + version: 5.0.0 home-assistant-query-selector: specifier: ^4.2.0 version: 4.2.0 @@ -858,8 +858,8 @@ packages: resolution: {integrity: sha512-J00e55zffgi3yVnUp0UdbMztNkr2PnizEkOe9URNohnrNhW5X0QpegkuLpOmFQInpi93Nb8MCjQRHAiCDF42NQ==} engines: {node: '>=10.0.0'} - home-assistant-javascript-templates@4.0.0: - resolution: {integrity: sha512-Ng63AO56T0scmYWe/j+i+UILVKEhdJGUI6gBGr+GfACuXPkmM9GUIPzCGXhoRSa85u3Od+EuMMUlkE+Bk+8QCA==} + home-assistant-javascript-templates@5.0.0: + resolution: {integrity: sha512-vUdqyfBi7bCGecCcUj1Keon6UynfVCJuab6zxshYO1wVTL/UMvTR6s+AFnizK3VVxY92y5hnV12P7mcqh93E+Q==} home-assistant-query-selector@4.2.0: resolution: {integrity: sha512-8VwrqTunsE3sgakCirBdQgrZTvWgT1SIaVthpgVCeaA/8dUK0qQfn3JFUcwaa8xm0NUa8yDgjRkxKe9lGHA65Q==} @@ -2292,7 +2292,7 @@ snapshots: helpertypes@0.0.19: {} - home-assistant-javascript-templates@4.0.0: {} + home-assistant-javascript-templates@5.0.0: {} home-assistant-query-selector@4.2.0: dependencies: diff --git a/src/constants/index.ts b/src/constants/index.ts index cfef51e..37a2223 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -126,19 +126,13 @@ export enum ELEMENT { HA_DIALOG_ATTRIBUTES = 'ha-attributes' } -export enum SUBSCRIBE_TYPE { - SUBSCRIBE_EVENTS = 'subscribe_events', - RENDER_TEMPLATE = 'render_template' -} - export const TRUE = 'true'; export const JS_TEMPLATE_REG = /^\s*\[\[\[([\s\S]+)\]\]\]\s*$/; export const JINJA_TEMPLATE_REG = /\{\{[\s\S]*\}\}|\{%[\s\S]*%\}/; -export const DOMAIN_REGEXP = /^([a-z_]+)[\w.]*$/; export const CUSTOM_MOBILE_WIDTH_DEFAULT = 812; export const TOGGLE_MENU_EVENT = 'hass-toggle-menu'; export const CONTEXT_MENU_EVENT = 'contextmenu'; -export const STATE_CHANGE_EVENT = 'state_changed'; +export const RENDER_TEMPLATE_EVENT = 'render_template'; export const RESIZE_EVENT = 'resize'; export const MAX_ATTEMPTS = 500; export const RETRY_DELAY = 50; diff --git a/src/kiosk-mode.ts b/src/kiosk-mode.ts index 9545f57..0cd715f 100644 --- a/src/kiosk-mode.ts +++ b/src/kiosk-mode.ts @@ -5,7 +5,10 @@ import { OnMoreInfoDialogOpenDetail, OnHistoryAndLogBookDialogOpenDetail } from 'home-assistant-query-selector'; -import HomeAssistantJavaScriptTemplates from 'home-assistant-javascript-templates'; +import HomeAssistantJavaScriptTemplates, { + HomeAssistantJavaScriptTemplatesRenderer, + HassConnection +} from 'home-assistant-javascript-templates'; import { KioskModeRunner, HomeAsssistantExtended, @@ -13,9 +16,7 @@ import { KioskConfig, ConditionalKioskConfig, Options, - HassConnection, SubscriberTemplate, - SubscriberEvent, MoreInfoDialog, Version } from '@types'; @@ -29,13 +30,12 @@ import { TOGGLE_MENU_EVENT, CONTEXT_MENU_EVENT, RESIZE_EVENT, - STATE_CHANGE_EVENT, + RENDER_TEMPLATE_EVENT, JS_TEMPLATE_REG, JINJA_TEMPLATE_REG, - DOMAIN_REGEXP, - SUBSCRIBE_TYPE, WINDOW_RESIZE_DELAY, NAMESPACE, + RETRY_DELAY, NON_CRITICAL_WARNING } from '@constants'; import { @@ -92,17 +92,11 @@ class KioskMode implements KioskModeRunner { `${ELEMENT.HOME_ASSISTANT} > hass > user` ); - this.version = parseVersion(this.ha.hass?.config?.version); - - this._renderer = new HomeAssistantJavaScriptTemplates(this.ha); - - this._jsSuscriptions = new Map void>>(); + this._renderer = await new HomeAssistantJavaScriptTemplates(this.ha).getRenderer(); - // Start kiosk-mode - this.run(); + this.version = parseVersion(this.ha.hass?.config?.version); - // Watch for entities changes - this._watchForEntitiesChange(); + this.run(); }); @@ -134,8 +128,8 @@ class KioskMode implements KioskModeRunner { private menuTranslations: Record; private resizeDelay: number; private resizeWindowBinded: () => void; - private _renderer: HomeAssistantJavaScriptTemplates; - private _jsSuscriptions: Map void>>; + private _renderer: HomeAssistantJavaScriptTemplatesRenderer; + private _runTimeout: number; private version: Version | null; // Kiosk Mode options @@ -160,37 +154,12 @@ class KioskMode implements KioskModeRunner { this.panelOptions.set(panelUrl, options); } - private _watchForEntitiesChange() { - window.hassConnection.then((hassConnection: HassConnection): void => { - hassConnection.conn.subscribeMessage( - (event) => this._entityWatchCallback(event), - { - type: SUBSCRIBE_TYPE.SUBSCRIBE_EVENTS, - event_type: STATE_CHANGE_EVENT - } - ); - }); - } - - private _entityWatchCallback(event: SubscriberEvent) { - if (this._jsSuscriptions?.size) { - const entityId = event.data.entity_id; - const domain = entityId.replace(DOMAIN_REGEXP, '$1'); - if (this._jsSuscriptions.has(entityId)) { - const subscribedOptions = this._jsSuscriptions.get(entityId); - const callbacks = subscribedOptions.values(); - Array.from(callbacks).forEach((callback) => { - callback(); - }); - } - if (this._jsSuscriptions.has(domain)) { - const subscribedOptions = this._jsSuscriptions.get(domain); - const callbacks = subscribedOptions.values(); - Array.from(callbacks).forEach((callback) => { - callback(); - }); - } - } + private runThrottle() { + window.clearTimeout(this._runTimeout); + this._runTimeout = window.setTimeout(() => { + this.run(); + this.runDialogs(); + }, RETRY_DELAY); } public async run() { @@ -755,46 +724,23 @@ class KioskMode implements KioskModeRunner { } else if (JS_TEMPLATE_REG.test(value)) { - const callback = () => { - - this._renderer.cleanTracked(); - const compiled = this._renderer.renderTemplate( - value.replace(JS_TEMPLATE_REG, '$1') - ); - const tracked = this._renderer.tracked; - const {entities, domains} = tracked; - - // Store all tracked entities ids and domains ids - [...entities, ...domains].forEach((id: string): void => { - if (this._jsSuscriptions.has(id)) { - const subscribedOptions = this._jsSuscriptions.get(id); - if (!subscribedOptions.has(options)) { - subscribedOptions.set(options, callback); - } - } else { - this._jsSuscriptions.set( - id, - new Map([ - [options, callback] - ]) - ); - } - }); - + const renderingFunction = (result: unknown): void => { // Set the compiled option - if (typeof compiled === 'boolean') { - options[option] = compiled; + if (typeof result === 'boolean') { + options[option] = result; } else { options[option] = false; console.warn(`${NAMESPACE}: the JavaScript template "${value}" of the option "${option}" doesn't return a boolean value. The option has been set as false`); } if (this._getPanelUrl() === panelUrl) { - this.run(); - this.runDialogs(); + this.runThrottle(); } }; - callback(); + this._renderer.trackTemplate( + value.replace(JS_TEMPLATE_REG, '$1'), + renderingFunction + ); } else if (JINJA_TEMPLATE_REG.test(value)) { @@ -817,12 +763,11 @@ class KioskMode implements KioskModeRunner { console.warn(`${NAMESPACE}: the Jinja template "${value}" of the option "${option}" doesn't return a boolean value. The option has been set as false`); } if (this._getPanelUrl() === panelUrl) { - this.run(); - this.runDialogs(); + this.runThrottle(); } }, { - type: SUBSCRIBE_TYPE.RENDER_TEMPLATE, + type: RENDER_TEMPLATE_EVENT, template: value, variables: { user_name: this.ha.hass.user.name, diff --git a/src/types/index.ts b/src/types/index.ts index 2b08be0..071b4ed 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -64,30 +64,8 @@ export class Lovelace extends HTMLElement { }; } -export type SubscriberEvent = { - event_type: string; - data: { - entity_id: string; - old_state?: { - state: string; - }; - new_state: { - state: string; - }; - } -}; - -export type SubscriberTemplate = { +export interface SubscriberTemplate { result: string; -}; - -export interface HassConnection { - conn: { - subscribeMessage: ( - callback: (response: T) => void, - options: Record> - ) => void; - } } export type StyleElement = Element | ShadowRoot | Element[] | ShadowRoot[]; @@ -99,7 +77,6 @@ export interface MoreInfoDialog extends HTMLElement { declare global { interface Window { KioskMode: KioskModeRunner; - hassConnection: Promise; } } diff --git a/tests/main-options.spec.ts b/tests/main-options.spec.ts index 5e6f8fa..aa399a8 100644 --- a/tests/main-options.spec.ts +++ b/tests/main-options.spec.ts @@ -256,6 +256,8 @@ test.describe('Option: block_context_menu', () => { await haRequest(ENTITIES.BLOCK_CONTEXT_MENU, true); + await page.waitForTimeout(100); + await page.locator(SELECTORS.HEADER).click({ button: 'right' }); @@ -266,6 +268,8 @@ test.describe('Option: block_context_menu', () => { await haRequest(ENTITIES.BLOCK_CONTEXT_MENU, false); + await page.waitForTimeout(100); + await page.locator(SELECTORS.HEADER).click({ button: 'right' });