From e8df70c2f8080fd507f7db0d83b2d773bc8cb87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Pereira=20Mu=C3=B1oz?= Date: Sat, 14 Sep 2024 22:53:16 +0200 Subject: [PATCH] Add support for Jinja templates in the kiosk-mode options --- .hass/config/ui-lovelace.yaml | 109 ++------ README.md | 165 ++++++------- src/constants/index.ts | 7 +- src/kiosk-mode.ts | 450 +++++++++++++++++++--------------- src/types/index.ts | 51 ++-- 5 files changed, 379 insertions(+), 403 deletions(-) diff --git a/.hass/config/ui-lovelace.yaml b/.hass/config/ui-lovelace.yaml index 3562f62..3e5d751 100644 --- a/.hass/config/ui-lovelace.yaml +++ b/.hass/config/ui-lovelace.yaml @@ -1,86 +1,31 @@ kiosk_mode: - entity_settings: - - entity: - input_boolean.kiosk: 'on' - kiosk: true - - entity: - input_boolean.kiosk_hide_header: 'on' - hide_header: true - - entity: - input_boolean.kiosk_hide_sidebar: 'on' - hide_sidebar: true - - entity: - input_boolean.kiosk_hide_menubutton: 'on' - hide_menubutton: true - - entity: - input_boolean.kiosk_hide_overflow: 'on' - hide_overflow: true - - entity: - input_boolean.kiosk_hide_notifications: 'on' - hide_notifications: true - - entity: - input_boolean.kiosk_hide_account: 'on' - hide_account: true - - entity: - input_boolean.kiosk_hide_search: 'on' - hide_search: true - - entity: - input_boolean.kiosk_hide_assistant: 'on' - hide_assistant: true - - entity: - input_boolean.kiosk_hide_edit_dashboard: 'on' - hide_edit_dashboard: true - - entity: - input_boolean.kiosk_hide_refresh: 'on' - hide_refresh: true - - entity: - input_boolean.kiosk_hide_unused_entities: 'on' - hide_unused_entities: true - - entity: - input_boolean.kiosk_hide_reload_resources: 'on' - hide_reload_resources: true - - entity: - input_boolean.kiosk_hide_dialog_header_history: 'on' - hide_dialog_header_history: true - - entity: - input_boolean.kiosk_hide_dialog_header_settings: 'on' - hide_dialog_header_settings: true - - entity: - input_boolean.kiosk_hide_dialog_header_action_items: 'on' - hide_dialog_header_action_items: true - - entity: - input_boolean.kiosk_hide_dialog_header_overflow: 'on' - hide_dialog_header_overflow: true - - entity: - input_boolean.kiosk_hide_dialog_history: 'on' - hide_dialog_history: true - - entity: - input_boolean.kiosk_hide_dialog_logbook: 'on' - hide_dialog_logbook: true - - entity: - input_boolean.kiosk_hide_dialog_attributes: 'on' - hide_dialog_attributes: true - - entity: - input_boolean.kiosk_hide_dialog_update_actions: 'on' - hide_dialog_update_actions: true - - entity: - input_boolean.kiosk_hide_dialog_timer_actions: 'on' - hide_dialog_timer_actions: true - - entity: - input_boolean.kiosk_hide_dialog_history_show_more: 'on' - hide_dialog_history_show_more: true - - entity: - input_boolean.kiosk_hide_dialog_logbook_show_more: 'on' - hide_dialog_logbook_show_more: true - - entity: - input_boolean.kiosk_block_overflow: 'on' - block_overflow: true - - entity: - input_boolean.kiosk_block_mouse: 'on' - block_mouse: true - - entity: - input_boolean.kiosk_block_context_menu: 'on' - block_context_menu: true + kiosk: "{{ is_state('input_boolean.kiosk', 'on') }}" + hide_header: "{{ is_state('input_boolean.kiosk_hide_header', 'on') }}" + hide_sidebar: "{{ is_state('input_boolean.kiosk_hide_sidebar', 'on') }}" + hide_menubutton: "{{ is_state('input_boolean.kiosk_hide_menubutton', 'on') }}" + hide_overflow: "{{ is_state('input_boolean.kiosk_hide_overflow', 'on') }}" + hide_notifications: "{{ is_state('input_boolean.kiosk_hide_notifications', 'on') }}" + hide_account: "{{ is_state('input_boolean.kiosk_hide_account', 'on') }}" + hide_search: "{{ is_state('input_boolean.kiosk_hide_search', 'on') }}" + hide_assistant: "{{ is_state('input_boolean.kiosk_hide_assistant', 'on') }}" + hide_edit_dashboard: "{{ is_state('input_boolean.kiosk_hide_edit_dashboard', 'on') }}" + hide_refresh: "{{ is_state('input_boolean.kiosk_hide_refresh', 'on') }}" + hide_unused_entities: "{{ is_state('input_boolean.kiosk_hide_unused_entities', 'on') }}" + hide_reload_resources: "{{ is_state('input_boolean.kiosk_hide_reload_resources', 'on') }}" + hide_dialog_header_history: "{{ is_state('input_boolean.kiosk_hide_dialog_header_history', 'on') }}" + hide_dialog_header_settings: "{{ is_state('input_boolean.kiosk_hide_dialog_header_settings', 'on') }}" + hide_dialog_header_action_items: "{{ is_state('input_boolean.kiosk_hide_dialog_header_action_items', 'on') }}" + hide_dialog_header_overflow: "{{ is_state('input_boolean.kiosk_hide_dialog_header_overflow', 'on') }}" + hide_dialog_history: "{{ is_state('input_boolean.kiosk_hide_dialog_history', 'on') }}" + hide_dialog_logbook: "{{ is_state('input_boolean.kiosk_hide_dialog_logbook', 'on') }}" + hide_dialog_attributes: "{{ is_state('input_boolean.kiosk_hide_dialog_attributes', 'on') }}" + hide_dialog_update_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_update_actions', 'on') }}" + hide_dialog_timer_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_timer_actions', 'on') }}" + hide_dialog_history_show_more: "{{ is_state('input_boolean.kiosk_hide_dialog_history_show_more', 'on') }}" + hide_dialog_logbook_show_more: "{{ is_state('input_boolean.kiosk_hide_dialog_logbook_show_more', 'on') }}" + block_overflow: "{{ is_state('input_boolean.kiosk_block_overflow', 'on') }}" + block_mouse: "{{ is_state('input_boolean.kiosk_block_mouse', 'on') }}" + block_context_menu: "{{ is_state('input_boolean.kiosk_block_context_menu', 'on') }}" title: Kiosk-mode Overview views: - theme: Backend-selected diff --git a/README.md b/README.md index 2205211..8241efa 100644 --- a/README.md +++ b/README.md @@ -81,56 +81,60 @@ frontend: YAML mode users need to add the configuration manually to the lovelace dashboard file in which they want to enable `kiosk-mode`. Non-YAML users (Storage Mode) need to add the configuration to each lovelace panel going to `Edit Dashboard` option (located in the overflow menu that appears when one clicks on the top-right three-dots button). Once in `Edit Dashboard` mode, click again on the top-right three-dots button and select `Raw configuration editor`. -``` +```yaml kiosk_mode: hide_header: true - + hide_sidebar: '{{ is_state("input_boolean.hide_sidebar", "on") }}' views: ``` *Note: `views:` is added in the example above to show where `kiosk_mode:` should be placed in your Lovelace config*

## Config Options -| Config Option | Type | Default | Description | -|:-----------------------------------------|:--------|:--------|:------------| -|`kiosk` | Boolean | false | Hides both the header and sidebar. | -|`hide_header`1 | Boolean | false | Hides only the header. | -|`hide_sidebar` | Boolean | false | Hides only the sidebar. | -|`hide_menubutton`1 | Boolean | false | Hides only the sidebar menu icon. | -|`hide_notifications` | Boolean | false | Hide the notifications entry-point. | -|`hide_account` | Boolean | false | Hides the account icon. | -|`hide_search` | Boolean | false | Hides the search icon. | -|`hide_assistant` | Boolean | false | Hides the assistant icon. | -|`hide_overflow` | Boolean | false | Hides the top right overflow menu. | -|`block_overflow` | Boolean | false | Blocks the top right overflow menu mouse interactions. | -|`hide_refresh` | Boolean | false | Hides the "Refresh" button inside the top right overflow menu in lovelace yaml mode. | -|`hide_unused_entities` | Boolean | false | Hides the "Unused entities" button inside the top right overflow menu in lovelace yaml mode. | -|`hide_reload_resources` | Boolean | false | Hides the "Reload resources" button inside the top right overflow menu in lovelace yaml mode. | -|`hide_edit_dashboard` | Boolean | false | Hides the "Edit dashboard" button inside the top right overflow menu. | -|`block_mouse` | Boolean | false | Blocks completely the mouse. No interaction is allowed and the mouse will not be visible. **Can only be disabled with `?disable_km` query parameter in the URL.**. | -|`block_context_menu` | Boolean | false | Prevents opening a right-click context menu (sometimes accessible via tap-and-hold on touchscreen devices). | -|`hide_dialog_header_history` | Boolean | false | Hides the "History" icon in the header of more-info dialogs. | -|`hide_dialog_header_settings`2 | Boolean | false | Hides the "Settings" icon in the header of more-info dialogs. | -|`hide_dialog_header_overflow`2 | Boolean | false | Hides the top right overflow menu in the header of more-info dialogs. | -|`hide_dialog_header_action_items` | Boolean | false | Hides all the action items from the header of more-info dialogs. | -|`hide_dialog_history` | Boolean | false | Hides the "History" section in the more-info dialogs. | -|`hide_dialog_history_show_more` | Boolean | false | Hides the "Show more" link in the "History" section of more-info dialogs. | -|`hide_dialog_logbook` | Boolean | false | Hides the "Logbook" section in the more-info dialogs. | -|`hide_dialog_logbook_show_more` | Boolean | false | Hides the "Show more" link in the "Logbook" section of more-info dialogs. | -|`hide_dialog_attributes` | Boolean | false | Hides the "Attributes" section in the more-info dialogs. | -|`hide_dialog_media_actions` | Boolean | false | Hides the actions block in the more-info dialogs of media-player entities. | -|`hide_dialog_update_actions` | Boolean | false | Hides the actions block in the more-info dialogs of update entities. | -|`hide_dialog_timer_actions` | Boolean | false | Hides the actions block in the more-info dialogs of timer entities. | -|`hide_dialog_climate_actions` | Boolean | false | Hides all the actions in the more-info dialogs of climate entities. | -|`hide_dialog_climate_temperature_actions` | Boolean | false | Hides the temperature cotrol actions in the more-info dialogs of climate entities. | -|`hide_dialog_climate_settings_actions` | Boolean | false | Hides the mode and preset actions in the more-info dialogs of climate entities. | -|`hide_dialog_light_actions` | Boolean | false | Hides all the actions in the more-info dialogs of light entities. | -|`hide_dialog_light_control_actions` | Boolean | false | Hides the control actions in the more-info dialogs of light entities. | -|`hide_dialog_light_color_actions` | Boolean | false | Hides the favorite colors actions in the more-info dialogs of light entities. | -|`hide_dialog_light_settings_actions` | Boolean | false | Hides the settings actions in the more-info dialogs of light entities. | -|`ignore_entity_settings`3 | Boolean | false | Useful for [conditional configs](#conditional-lovelace-config) and will cause `entity_settings` to be ignored. | -|`ignore_mobile_settings`4 | Boolean | false | Useful for [conditional configs](#conditional-lovelace-config) and will cause `mobile_settings` to be ignored. | -|`ignore_disable_km`3 | Boolean | false | Useful for [conditional configs](#conditional-lovelace-config) and will cause `disable_km` URL parameter to be ignored. | +All the options can be set as a boolean and all of them are `false` by default. Excluding `ignore_mobile_settings` and `ignore_disable_km`, all the options can be set as a Jinja template that returns a boolean. + +>**Note:** If you set the option as a string but it is not a valid Jinja template, the library will throw an error. If you set a Jinja template and it doesn't return a boolean, the option will be set as false and a warning will be thrown. + + +| Config Option | Description | +|:-----------------------------------------|:------------| +|`kiosk` | Hides both the header and sidebar. | +|`hide_header`1 | Hides only the header. | +|`hide_sidebar` | Hides only the sidebar. | +|`hide_menubutton`1 | Hides only the sidebar menu icon. | +|`hide_notifications` | Hide the notifications entry-point. | +|`hide_account` | Hides the account icon. | +|`hide_search` | Hides the search icon. | +|`hide_assistant` | Hides the assistant icon. | +|`hide_overflow` | Hides the top right overflow menu. | +|`block_overflow` | Blocks the top right overflow menu mouse interactions. | +|`hide_refresh` | Hides the "Refresh" button inside the top right overflow menu in lovelace yaml mode. | +|`hide_unused_entities` | Hides the "Unused entities" button inside the top right overflow menu in lovelace yaml mode. | +|`hide_reload_resources` | Hides the "Reload resources" button inside the top right overflow menu in lovelace yaml mode. | +|`hide_edit_dashboard` | Hides the "Edit dashboard" button inside the top right overflow menu. | +|`block_mouse` | Blocks completely the mouse. No interaction is allowed and the mouse will not be visible. **Can only be disabled with `?disable_km` query parameter in the URL.**. | +|`block_context_menu` | Prevents opening a right-click context menu (sometimes accessible via tap-and-hold on touchscreen devices).| +|`hide_dialog_header_history` | Hides the "History" icon in the header of more-info dialogs. | +|`hide_dialog_header_settings`2 | Hides the "Settings" icon in the header of more-info dialogs. | +|`hide_dialog_header_overflow`2 | Hides the top right overflow menu in the header of more-info dialogs. | +|`hide_dialog_header_action_items` | Hides all the action items from the header of more-info dialogs. | +|`hide_dialog_history` | Hides the "History" section in the more-info dialogs. | +|`hide_dialog_history_show_more` | Hides the "Show more" link in the "History" section of more-info dialogs. | +|`hide_dialog_logbook` | Hides the "Logbook" section in the more-info dialogs. | +|`hide_dialog_logbook_show_more` | Hides the "Show more" link in the "Logbook" section of more-info dialogs. | +|`hide_dialog_attributes` | Hides the "Attributes" section in the more-info dialogs. | +|`hide_dialog_media_actions` | Hides the actions block in the more-info dialogs of media-player entities. | +|`hide_dialog_update_actions` | Hides the actions block in the more-info dialogs of update entities. | +|`hide_dialog_timer_actions` | Hides the actions block in the more-info dialogs of timer entities. | +|`hide_dialog_climate_actions` | Hides all the actions in the more-info dialogs of climate entities. | +|`hide_dialog_climate_temperature_actions` | Hides the temperature cotrol actions in the more-info dialogs of climate entities. | +|`hide_dialog_climate_settings_actions` | Hides the mode and preset actions in the more-info dialogs of climate entities. | +|`hide_dialog_light_actions` | Hides all the actions in the more-info dialogs of light entities. | +|`hide_dialog_light_control_actions` | Hides the control actions in the more-info dialogs of light entities. | +|`hide_dialog_light_color_actions` | Hides the favorite colors actions in the more-info dialogs of light entities. | +|`hide_dialog_light_settings_actions` | Hides the settings actions in the more-info dialogs of light entities. | +|`ignore_disable_km` | Useful for [conditional configs](#conditional-lovelace-config) and will cause `disable_km` URL parameter to be ignored. | +|`ignore_mobile_settings`3 | Useful for [conditional configs](#conditional-lovelace-config) and will cause `mobile_settings` to be ignored. |
@@ -138,12 +142,37 @@ views: > >2 These elements are not visible by default if the account is not an admin account. > ->3 These options only work if they are placed inside [admin_settings](#admin_settings), [non_admin_settings](#non_admin_settings), [user_settings](#user_settings) or [mobile_settings](#mobile_settings). They will not have any effect if they are placed inside [entity_settings](#entity_settings) -> ->4 This option only works if it is placed inside [admin_settings](#admin_settings), [non_admin_settings](#non_admin_settings) or [user_settings](#user_settings). It will not have any effect if it is placed inside [mobile_settings](#mobile_settings) or [entity_settings](#entity_settings) +>3 This option only works if it is placed inside [admin_settings](#admin_settings), [non_admin_settings](#non_admin_settings) or [user_settings](#user_settings). It will not have any effect if it is placed inside [mobile_settings](#mobile_settings).
+If you us Jinja templates for the options, the next variables will be available: + +* `user_name`: String with the logged user's name +* `user_is_admin`: Bolean value than indicates if the logged user is admin or not +* `user_is_owner`: Bolean value than indicates if the logged user is the owner or not +* `user_agent`: User agent of the browser in which Home Assistant is being executed + +This gives a lot of flexibility and depending on your objectives you can omit the usage of the [conditional configs](#conditional-lovelace-config). For example, this configuration: + +```yaml +kiosk_mode: + hide_header: false + user_settings: + - users: + - "ryan meek" + - "maykar" + hide_header: true +``` + +Can be transformed in a simpler version: + +```yaml +kiosk_mode: + hide_header: '{{ user_name in ("maykar", "ryan meek") }}' +``` +
+ ## Options through screenshots
@@ -247,7 +276,7 @@ These use the same options as above, but placed under one of the following user/ ### admin_settings: Sets the config for every admin user.
-*Overwritten by user_settings, mobile_settings, and entity_settings ( unless one of the ignore options is used ).*
+*Overwritten by user_settings and mobile_settings ( unless `ignore_mobile_settings` is used ).*
``` kiosk_mode: @@ -258,19 +287,18 @@ kiosk_mode: ### non_admin_settings: Sets the config for every regular user.
-*Overwritten by user_settings, mobile_settings, and entity_settings ( unless one of the ignore options is used ).*
+*Overwritten by user_settings and mobile_settings ( unless `ignore_mobile_settings` is used ).*
``` kiosk_mode: non_admin_settings: hide_header: true - ignore_entity_settings: true ```
### user_settings: Sets the config for specific users. **This uses a user's name, not their username (if they're different)**.
-*Overwritten by mobile_settings, and entity_settings ( unless one of the ignore options is used ).*
+*Overwritten by mobile_settings ( unless `ignore_mobile_settings` is used ).*
``` kiosk_mode: @@ -282,55 +310,20 @@ kiosk_mode: - users: - "the wife" kiosk: true - ignore_entity_settings: true ```
### mobile_settings: -Sets the config for mobile devices. The default breakpoint is 812px, which can be changed by setting the `custom_width` option.
-*Overwritten by entity_settings, unless `ignore_entity_settings` is used, can be ignored with `ignore_mobile_settings`.*
+Sets the config for mobile devices. The default breakpoint is `812px`, which can be changed by setting the `custom_width` option.
``` kiosk_mode: mobile_settings: hide_header: true - ignore_entity_settings: true custom_width: 768 ```
-### entity_settings: -Dynamically change config on any entity's state. Under `entity:` list the entity followed by the state that will enable the config below. For more complex logic use this with a template sensor.
-*Takes priority over all other config settings unless they use `ignore_entity_settings`.*

- -*Any condition that doesn't match will then fall back to previous configurations if another "false" entity condition hasn't also been set (see the 2nd example).* -``` -kiosk_mode: - entity_settings: - - entity: - input_boolean.hide_sidebar: 'on' - hide_sidebar: true - - entity: - sensor.hide_header: 'on' - hide_header: true - - entity: - input_text.kiosk: 'true' - kiosk: true -``` - -``` -kiosk_mode: - entity_settings: - # hide_sidebar has both true and false conditions to be a true override. - - entity: - input_boolean.hide_sidebar: 'on' - hide_sidebar: true - - entity: - input_boolean.hide_sidebar: 'off' - hide_sidebar: false -``` -
- ## Query Strings Add a query string such as `?kiosk` to the end of your URL: @@ -391,6 +384,8 @@ You save settings in a devices cache by using the cache keyword once on the devi Example: `?hide_header&cache` makes all views & dashboards hide the header.
This works for all query strings except for the utility strings listed below. +>**Note:** Do not use the query string cache if you are using Jinja templates for the options or you will get weird results. + **Utility Query Strings** * `?clear_km_cache` will clear all cached preferences diff --git a/src/constants/index.ts b/src/constants/index.ts index 1e56c72..c087441 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -41,7 +41,6 @@ export enum OPTION { } export enum CONDITIONAL_OPTION { - IGNORE_ENTITY_SETTINGS = 'ignore_entity_settings', IGNORE_MOBILE_SETTINGS = 'ignore_mobile_settings', IGNORE_DISABLE_KM = 'ignore_disable_km' } @@ -128,10 +127,12 @@ export enum ELEMENT { } export const TRUE = 'true'; +export const JINJA_TEMPLATE_REG = /\{\{[\s\S]*\}\}|\{%[\s\S]*%\}/; export const CUSTOM_MOBILE_WIDTH_DEFAULT = 812; -export const SUSCRIBE_EVENTS_TYPE = 'subscribe_events'; -export const STATE_CHANGED_EVENT = 'state_changed'; export const TOGGLE_MENU_EVENT = 'hass-toggle-menu'; +export const CONTEXT_MENU_EVENT = 'contextmenu'; +export const RESIZE_EVENT = 'resize'; +export const SUBSCRIBE_RENDER_TEMPLATE = 'render_template'; export const MAX_ATTEMPTS = 500; export const RETRY_DELAY = 50; export const WINDOW_RESIZE_DELAY = 250; \ No newline at end of file diff --git a/src/kiosk-mode.ts b/src/kiosk-mode.ts index 856eae6..5ee22ca 100644 --- a/src/kiosk-mode.ts +++ b/src/kiosk-mode.ts @@ -12,8 +12,10 @@ import { Lovelace, KioskConfig, ConditionalKioskConfig, - SuscriberEvent, - EntitySetting, + Options, + HassConnection, + SubscriberTemplate, + MoreInfoDialog, Version } from '@types'; import { @@ -23,9 +25,11 @@ import { ELEMENT, TRUE, CUSTOM_MOBILE_WIDTH_DEFAULT, - SUSCRIBE_EVENTS_TYPE, - STATE_CHANGED_EVENT, TOGGLE_MENU_EVENT, + CONTEXT_MENU_EVENT, + RESIZE_EVENT, + JINJA_TEMPLATE_REG, + SUBSCRIBE_RENDER_TEMPLATE, WINDOW_RESIZE_DELAY, NAMESPACE, NON_CRITICAL_WARNING @@ -54,9 +58,7 @@ class KioskMode implements KioskModeRunner { resetCache(); } - window.kioskModeEntities = {}; - - this.options = {}; + this.panelOptions = new Map(); const selector = new HAQuerySelector(); @@ -104,7 +106,6 @@ class KioskMode implements KioskModeRunner { }); selector.listen(); - this.entityWatch(); this.resizeWindowBinded = this.resizeWindow.bind(this); } @@ -125,12 +126,26 @@ class KioskMode implements KioskModeRunner { private version: Version | null; // Kiosk Mode options - private options: Partial< - Record< - OPTION | CONDITIONAL_OPTION, - boolean - > - >; + private panelOptions: Map; + + private _getPanelUrl(): string { + return this.ha.hass.panelUrl; + } + + private _hasStoredOptions(): boolean { + const panelUrl = this._getPanelUrl(); + return this.panelOptions.has(panelUrl); + } + + private _getOptions(): Options { + const panelUrl = this._getPanelUrl(); + return this.panelOptions.get(panelUrl); + } + + private _storeOptions(options: Options): void { + const panelUrl = this._getPanelUrl(); + this.panelOptions.set(panelUrl, options); + } public async run() { @@ -140,38 +155,46 @@ class KioskMode implements KioskModeRunner { return; } - // Get the configuration and process it - return getPromisableElement( - () => lovelace?.lovelace?.config, - (config: Lovelace['lovelace']['config']) => !!config, - 'Lovelace config' - ) - .then((config: Lovelace['lovelace']['config']) => { - return this.processConfig( - config.kiosk_mode || {} - ); - }); + if (this._hasStoredOptions()) { + // Insert styles + this.insertStyles(); + } else { + // Get the configuration from the lovalace panel and process it + return getPromisableElement( + () => lovelace?.lovelace?.config, + (config: Lovelace['lovelace']['config']) => !!config, + 'Lovelace config' + ) + .then((config: Lovelace['lovelace']['config']) => { + return this.processConfig( + config.kiosk_mode || {} + ); + }); + } } - public runDialogs(dialog: Element = this.ha?.shadowRoot?.querySelector(ELEMENT.HA_MORE_INFO_DIALOG)) { - if (!dialog) return; + public runDialogs() { + const dialog = this.ha?.shadowRoot?.querySelector(ELEMENT.HA_MORE_INFO_DIALOG); + const haDialog = dialog?.shadowRoot.querySelector(ELEMENT.HA_DIALOG); + if ( + !haDialog || + !haDialog.__open + ) { + return; + } this.insertMoreInfoDialogStyles(); } protected async processConfig(config: KioskConfig) { - const dash = this.ha.hass.panelUrl; - - if (!window.kioskModeEntities[dash]) { - window.kioskModeEntities[dash] = []; - } + const options: Options = {}; // Set all the options to false Object.values(OPTION).forEach((option: OPTION) => { - this.options[option] = false; + options[option] = false; }); Object.values(CONDITIONAL_OPTION).forEach((option: CONDITIONAL_OPTION) => { - this.options[option] = false; + options[option] = false; }); // Get menu translations @@ -190,11 +213,11 @@ class KioskMode implements KioskModeRunner { queryString(Object.values(OPTION)) ) { Object.values(OPTION).forEach((option: OPTION): void => { - this.options[option] = cached(option) || queryString(option); + options[option] = cached(option) || queryString(option); }); } else { // Use config values only if config strings and cache aren't used. - this.setOptions(config, false); + this.setOptions(options, config, false); } // Admin non-admin config @@ -203,20 +226,20 @@ class KioskMode implements KioskModeRunner { : config.non_admin_settings; if (adminConfig) { - this.setOptions(adminConfig, true); + this.setOptions(options, adminConfig, true); } // User settings config if (config.user_settings) { toArray(config.user_settings).forEach((conf) => { - if (toArray(conf.users).some((x) => x.toLowerCase() === this.user.name.toLowerCase())) { - this.setOptions(conf, true); + if (toArray(conf.users).some((user) => user.toLowerCase() === this.user.name.toLowerCase())) { + this.setOptions(options, conf, true); } }); } // Mobile config - const mobileConfig = this.options[CONDITIONAL_OPTION.IGNORE_MOBILE_SETTINGS] + const mobileConfig = options[CONDITIONAL_OPTION.IGNORE_MOBILE_SETTINGS] ? null : config.mobile_settings; @@ -225,44 +248,34 @@ class KioskMode implements KioskModeRunner { ? mobileConfig.custom_width : CUSTOM_MOBILE_WIDTH_DEFAULT; if (window.innerWidth <= mobileWidth) { - this.setOptions(mobileConfig, true); + this.setOptions(options, mobileConfig, true); } } - // Entity config - const entityConfig = this.options[CONDITIONAL_OPTION.IGNORE_ENTITY_SETTINGS] - ? null - : config.entity_settings; + this._storeOptions(options); - if (entityConfig) { - entityConfig.forEach((conf: EntitySetting) => { - const entity = Object.keys(conf.entity)[0]; - if (!window.kioskModeEntities[dash].includes(entity)) { - window.kioskModeEntities[dash].push(entity); - } - if (this.ha.hass.states[entity].state == conf.entity[entity]) { - this.setOptions(conf, false); - } - }); - } + this.insertStyles(); + } + + // INSERT REGULAR STYLES + protected insertStyles() { + + const options = this._getOptions(); // Do not run kiosk-mode if it is disabled if ( - queryString(SPECIAL_QUERY_PARAMS.DISABLE_KIOSK_MODE) && - !this.options[CONDITIONAL_OPTION.IGNORE_DISABLE_KM] + !options || + ( + queryString(SPECIAL_QUERY_PARAMS.DISABLE_KIOSK_MODE) && + !options[CONDITIONAL_OPTION.IGNORE_DISABLE_KM] + ) ) { return; } - this.insertStyles(); - } - - // INSERT REGULAR STYLES - protected insertStyles() { - if ( - this.options[OPTION.KIOSK] || - this.options[OPTION.HIDE_HEADER] + options[OPTION.KIOSK] || + options[OPTION.HIDE_HEADER] ) { addStyle(STYLES.HEADER, this.huiRoot); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) setCache(OPTION.HIDE_HEADER, TRUE); @@ -274,8 +287,8 @@ class KioskMode implements KioskModeRunner { this.main?.host?.removeEventListener(TOGGLE_MENU_EVENT, this.blockEventHandler, true); if ( - this.options[OPTION.KIOSK] || - this.options[OPTION.HIDE_SIDEBAR] + options[OPTION.KIOSK] || + options[OPTION.HIDE_SIDEBAR] ) { this.main?.host?.addEventListener(TOGGLE_MENU_EVENT, this.blockEventHandler, true); addStyle(STYLES.SIDEBAR, this.drawerLayout); @@ -287,109 +300,122 @@ class KioskMode implements KioskModeRunner { } if ( - this.options[OPTION.HIDE_ACCOUNT] || - this.options[OPTION.HIDE_NOTIFICATIONS] || - this.options[OPTION.HIDE_MENU_BUTTON] + options[OPTION.HIDE_ACCOUNT] || + options[OPTION.HIDE_NOTIFICATIONS] || + options[OPTION.HIDE_MENU_BUTTON] ) { const styles = [ - this.options[OPTION.HIDE_ACCOUNT] + options[OPTION.HIDE_ACCOUNT] ? STYLES.ACCOUNT : '', - this.options[OPTION.HIDE_NOTIFICATIONS] + options[OPTION.HIDE_NOTIFICATIONS] ? STYLES.NOTIFICATIONS : '', - this.options[OPTION.HIDE_ACCOUNT] && this.options[OPTION.HIDE_NOTIFICATIONS] + options[OPTION.HIDE_ACCOUNT] && options[OPTION.HIDE_NOTIFICATIONS] ? STYLES.DIVIDER : '', - this.options[OPTION.HIDE_ACCOUNT] || this.options[OPTION.HIDE_NOTIFICATIONS] + options[OPTION.HIDE_ACCOUNT] || options[OPTION.HIDE_NOTIFICATIONS] ? STYLES.PAPER_LISTBOX( - this.options[OPTION.HIDE_ACCOUNT], - this.options[OPTION.HIDE_NOTIFICATIONS] + options[OPTION.HIDE_ACCOUNT], + options[OPTION.HIDE_NOTIFICATIONS] ) : '', - this.options[OPTION.HIDE_MENU_BUTTON] + options[OPTION.HIDE_MENU_BUTTON] ? STYLES.MENU_BUTTON : '', ]; addStyle(styles.join(''), this.sideBarRoot); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_ACCOUNT]) setCache(OPTION.HIDE_ACCOUNT, TRUE); - if (this.options[OPTION.HIDE_NOTIFICATIONS]) setCache(OPTION.HIDE_NOTIFICATIONS, TRUE); + if (options[OPTION.HIDE_ACCOUNT]) setCache(OPTION.HIDE_ACCOUNT, TRUE); + if (options[OPTION.HIDE_NOTIFICATIONS]) setCache(OPTION.HIDE_NOTIFICATIONS, TRUE); } } else { removeStyle(this.sideBarRoot); } if ( - this.options[OPTION.HIDE_SEARCH] || - this.options[OPTION.HIDE_ASSISTANT] || - this.options[OPTION.HIDE_REFRESH] || - this.options[OPTION.HIDE_UNUSED_ENTITIES] || - this.options[OPTION.HIDE_RELOAD_RESOURCES] || - this.options[OPTION.HIDE_EDIT_DASHBOARD] || - this.options[OPTION.HIDE_OVERFLOW] || - this.options[OPTION.BLOCK_OVERFLOW] || - this.options[OPTION.HIDE_SIDEBAR] || - this.options[OPTION.HIDE_MENU_BUTTON] + options[OPTION.HIDE_SEARCH] || + options[OPTION.HIDE_ASSISTANT] || + options[OPTION.HIDE_REFRESH] || + options[OPTION.HIDE_UNUSED_ENTITIES] || + options[OPTION.HIDE_RELOAD_RESOURCES] || + options[OPTION.HIDE_EDIT_DASHBOARD] || + options[OPTION.HIDE_OVERFLOW] || + options[OPTION.BLOCK_OVERFLOW] || + options[OPTION.HIDE_SIDEBAR] || + options[OPTION.HIDE_MENU_BUTTON] ) { const styles = [ - this.options[OPTION.HIDE_SEARCH] + options[OPTION.HIDE_SEARCH] ? STYLES.SEARCH : '', - this.options[OPTION.HIDE_ASSISTANT] + options[OPTION.HIDE_ASSISTANT] ? STYLES.ASSISTANT : '', - this.options[OPTION.HIDE_REFRESH] + options[OPTION.HIDE_REFRESH] ? STYLES.REFRESH : '', - this.options[OPTION.HIDE_UNUSED_ENTITIES] + options[OPTION.HIDE_UNUSED_ENTITIES] ? STYLES.UNUSED_ENTITIES : '', - this.options[OPTION.HIDE_RELOAD_RESOURCES] + options[OPTION.HIDE_RELOAD_RESOURCES] ? STYLES.RELOAD_RESOURCES : '', - this.options[OPTION.HIDE_EDIT_DASHBOARD] + options[OPTION.HIDE_EDIT_DASHBOARD] ? STYLES.EDIT_DASHBOARD : '', - this.options[OPTION.HIDE_OVERFLOW] + options[OPTION.HIDE_OVERFLOW] ? STYLES.OVERFLOW_MENU : '', - this.options[OPTION.BLOCK_OVERFLOW] + options[OPTION.BLOCK_OVERFLOW] ? STYLES.BLOCK_OVERFLOW : '', - this.options[OPTION.HIDE_MENU_BUTTON] || this.options[OPTION.HIDE_SIDEBAR] + options[OPTION.HIDE_MENU_BUTTON] || options[OPTION.HIDE_SIDEBAR] ? STYLES.MENU_BUTTON_BURGER : '', ]; addStyle(styles.join(''), this.appToolbar); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_SEARCH]) setCache(OPTION.HIDE_SEARCH, TRUE); - if (this.options[OPTION.HIDE_ASSISTANT]) setCache(OPTION.HIDE_ASSISTANT, TRUE); - if (this.options[OPTION.HIDE_REFRESH]) setCache(OPTION.HIDE_REFRESH, TRUE); - if (this.options[OPTION.HIDE_UNUSED_ENTITIES]) setCache(OPTION.HIDE_UNUSED_ENTITIES, TRUE); - if (this.options[OPTION.HIDE_RELOAD_RESOURCES]) setCache(OPTION.HIDE_RELOAD_RESOURCES, TRUE); - if (this.options[OPTION.HIDE_EDIT_DASHBOARD]) setCache(OPTION.HIDE_EDIT_DASHBOARD, TRUE); - if (this.options[OPTION.HIDE_OVERFLOW]) setCache(OPTION.HIDE_OVERFLOW, TRUE); - if (this.options[OPTION.BLOCK_OVERFLOW]) setCache(OPTION.BLOCK_OVERFLOW, TRUE); - if (this.options[OPTION.HIDE_MENU_BUTTON]) setCache(OPTION.HIDE_MENU_BUTTON, TRUE); + if (options[OPTION.HIDE_SEARCH]) setCache(OPTION.HIDE_SEARCH, TRUE); + if (options[OPTION.HIDE_ASSISTANT]) setCache(OPTION.HIDE_ASSISTANT, TRUE); + if (options[OPTION.HIDE_REFRESH]) setCache(OPTION.HIDE_REFRESH, TRUE); + if (options[OPTION.HIDE_UNUSED_ENTITIES]) setCache(OPTION.HIDE_UNUSED_ENTITIES, TRUE); + if (options[OPTION.HIDE_RELOAD_RESOURCES]) setCache(OPTION.HIDE_RELOAD_RESOURCES, TRUE); + if (options[OPTION.HIDE_EDIT_DASHBOARD]) setCache(OPTION.HIDE_EDIT_DASHBOARD, TRUE); + if (options[OPTION.HIDE_OVERFLOW]) setCache(OPTION.HIDE_OVERFLOW, TRUE); + if (options[OPTION.BLOCK_OVERFLOW]) setCache(OPTION.BLOCK_OVERFLOW, TRUE); + if (options[OPTION.HIDE_MENU_BUTTON]) setCache(OPTION.HIDE_MENU_BUTTON, TRUE); } } else { removeStyle(this.appToolbar); } - if (this.options[OPTION.BLOCK_MOUSE]) { + if (options[OPTION.BLOCK_MOUSE]) { addStyle(STYLES.MOUSE, document.body); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) setCache(OPTION.BLOCK_MOUSE, TRUE); } else { removeStyle(document.body); } - window.removeEventListener('contextmenu', this.blockEventHandler, true); + window.removeEventListener(CONTEXT_MENU_EVENT, this.blockEventHandler, true); - if (this.options[OPTION.BLOCK_CONTEXT_MENU]) { - window.addEventListener('contextmenu', this.blockEventHandler, true); + if (options[OPTION.BLOCK_CONTEXT_MENU]) { + window.addEventListener(CONTEXT_MENU_EVENT, this.blockEventHandler, true); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) setCache(OPTION.BLOCK_CONTEXT_MENU, TRUE); } // Resize event - window.removeEventListener('resize', this.resizeWindowBinded); - window.addEventListener('resize', this.resizeWindowBinded); + window.removeEventListener(RESIZE_EVENT, this.resizeWindowBinded); + window.addEventListener(RESIZE_EVENT, this.resizeWindowBinded); // Resize window to 'refresh' view. - window.dispatchEvent(new Event('resize')); + window.dispatchEvent(new Event(RESIZE_EVENT)); } // INSERT MORE INFO DIALOG STYLES protected async insertMoreInfoDialogStyles() { + const options = this._getOptions(); + + // Do not run kiosk-mode if it is disabled + if ( + !options || + ( + queryString(SPECIAL_QUERY_PARAMS.DISABLE_KIOSK_MODE) && + !options[CONDITIONAL_OPTION.IGNORE_DISABLE_KM] + ) + ) { + return; + } + this.HAMoreInfoDialogElements.HA_DIALOG .selector.query(`${ELEMENT.HA_DIALOG_HEADER} > ${ELEMENT.MENU_ITEM}`) .all @@ -407,25 +433,25 @@ class KioskMode implements KioskModeRunner { // General dialog elements if ( - this.options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || - this.options[OPTION.HIDE_DIALOG_HEADER_HISTORY] || - this.options[OPTION.HIDE_DIALOG_HEADER_SETTINGS] || - this.options[OPTION.HIDE_DIALOG_HEADER_OVERFLOW] + options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || + options[OPTION.HIDE_DIALOG_HEADER_HISTORY] || + options[OPTION.HIDE_DIALOG_HEADER_SETTINGS] || + options[OPTION.HIDE_DIALOG_HEADER_OVERFLOW] ) { const styles = [ - this.options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || this.options[OPTION.HIDE_DIALOG_HEADER_HISTORY] + options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || options[OPTION.HIDE_DIALOG_HEADER_HISTORY] ? STYLES.DIALOG_HEADER_HISTORY : '', - this.options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || this.options[OPTION.HIDE_DIALOG_HEADER_SETTINGS] + options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || options[OPTION.HIDE_DIALOG_HEADER_SETTINGS] ? STYLES.DIALOG_HEADER_SETTINGS : '', - this.options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || this.options[OPTION.HIDE_DIALOG_HEADER_OVERFLOW] + options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS] || options[OPTION.HIDE_DIALOG_HEADER_OVERFLOW] ? STYLES.DIALOG_HEADER_OVERFLOW : '' ]; addStyle(styles.join(''), dialog); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS]) setCache(OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_HEADER_HISTORY]) setCache(OPTION.HIDE_DIALOG_HEADER_HISTORY, TRUE); - if (this.options[OPTION.HIDE_DIALOG_HEADER_SETTINGS]) setCache(OPTION.HIDE_DIALOG_HEADER_SETTINGS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_HEADER_OVERFLOW]) setCache(OPTION.HIDE_DIALOG_HEADER_OVERFLOW, TRUE); + if (options[OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS]) setCache(OPTION.HIDE_DIALOG_HEADER_ACTION_ITEMS, TRUE); + if (options[OPTION.HIDE_DIALOG_HEADER_HISTORY]) setCache(OPTION.HIDE_DIALOG_HEADER_HISTORY, TRUE); + if (options[OPTION.HIDE_DIALOG_HEADER_SETTINGS]) setCache(OPTION.HIDE_DIALOG_HEADER_SETTINGS, TRUE); + if (options[OPTION.HIDE_DIALOG_HEADER_OVERFLOW]) setCache(OPTION.HIDE_DIALOG_HEADER_OVERFLOW, TRUE); } } else { removeStyle(dialog); @@ -448,13 +474,13 @@ class KioskMode implements KioskModeRunner { haDialogClimate.element.then((haDialogClimate: ShadowRoot): void => { if ( - this.options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS] + options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS] || + options[OPTION.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS] ) { addStyle(STYLES.DIALOG_CLIMATE_CONTROL_SELECT, haDialogClimate); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS]) setCache(OPTION.HIDE_DIALOG_CLIMATE_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS]) setCache(OPTION.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS]) setCache(OPTION.HIDE_DIALOG_CLIMATE_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS]) setCache(OPTION.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS, TRUE); } } else { removeStyle(haDialogClimate); @@ -463,12 +489,12 @@ class KioskMode implements KioskModeRunner { haDialogClimateTemperature.element.then((haDialogClimateTemperature: ShadowRoot): void => { if ( - this.options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS] + options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS] || + options[OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS] ) { addStyle(STYLES.DIALOG_CLIMATE_TEMPERATURE_BUTTONS, haDialogClimateTemperature); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS]) setCache(OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS]) setCache(OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS, TRUE); } } else { removeStyle(haDialogClimateTemperature); @@ -477,8 +503,8 @@ class KioskMode implements KioskModeRunner { haDialogClimateCircularSlider.element.then((haDialogClimateCircularSlider: ShadowRoot) => { if ( - this.options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS] + options[OPTION.HIDE_DIALOG_CLIMATE_ACTIONS] || + options[OPTION.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS] ) { addStyle(STYLES.DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION, haDialogClimateCircularSlider); } else { @@ -507,63 +533,63 @@ class KioskMode implements KioskModeRunner { .then((dialogChild: ShadowRoot) => { if ( - this.options[OPTION.HIDE_DIALOG_ATTRIBUTES] || - this.options[OPTION.HIDE_DIALOG_TIMER_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_MEDIA_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_UPDATE_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS] + options[OPTION.HIDE_DIALOG_ATTRIBUTES] || + options[OPTION.HIDE_DIALOG_TIMER_ACTIONS] || + options[OPTION.HIDE_DIALOG_MEDIA_ACTIONS] || + options[OPTION.HIDE_DIALOG_UPDATE_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS] ) { const styles = [ - this.options[OPTION.HIDE_DIALOG_ATTRIBUTES] ? STYLES.DIALOG_ATTRIBUTES : '', + options[OPTION.HIDE_DIALOG_ATTRIBUTES] ? STYLES.DIALOG_ATTRIBUTES : '', ( - this.options[OPTION.HIDE_DIALOG_TIMER_ACTIONS] && + options[OPTION.HIDE_DIALOG_TIMER_ACTIONS] && dialogChild.host.localName === ELEMENT.HA_DIALOG_TIMER ) ? STYLES.DIALOG_TIMER_ACTIONS : '', ( - this.options[OPTION.HIDE_DIALOG_MEDIA_ACTIONS] && + options[OPTION.HIDE_DIALOG_MEDIA_ACTIONS] && dialogChild.host.localName === ELEMENT.HA_DIALOG_MEDIA_PLAYER ) ? STYLES.DIALOG_MEDIA_ACTIONS : '', ( - this.options[OPTION.HIDE_DIALOG_UPDATE_ACTIONS] && + options[OPTION.HIDE_DIALOG_UPDATE_ACTIONS] && dialogChild.host.localName === ELEMENT.HA_DIALOG_UPDATE ) ? STYLES.DIALOG_UPDATE_ACTIONS : '', ( - this.options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS] + options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS] ) ? STYLES.DIALOG_LIGHT_CONTROL_ACTIONS : '', ( - this.options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS] + options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS] ) ? STYLES.DIALOG_LIGHT_COLOR_ACTIONS : '', ( - this.options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || - this.options[OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS] + options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS] || + options[OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS] ) ? STYLES.DIALOG_LIGHT_SETTINGS_ACTIONS : '' ]; addStyle(styles.join(''), dialogChild); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_DIALOG_ATTRIBUTES]) setCache(OPTION.HIDE_DIALOG_ATTRIBUTES, TRUE); - if (this.options[OPTION.HIDE_DIALOG_TIMER_ACTIONS]) setCache(OPTION.HIDE_DIALOG_TIMER_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_MEDIA_ACTIONS]) setCache(OPTION.HIDE_DIALOG_MEDIA_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_UPDATE_ACTIONS]) setCache(OPTION.HIDE_DIALOG_UPDATE_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS, TRUE); - if (this.options[OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_ATTRIBUTES]) setCache(OPTION.HIDE_DIALOG_ATTRIBUTES, TRUE); + if (options[OPTION.HIDE_DIALOG_TIMER_ACTIONS]) setCache(OPTION.HIDE_DIALOG_TIMER_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_MEDIA_ACTIONS]) setCache(OPTION.HIDE_DIALOG_MEDIA_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_UPDATE_ACTIONS]) setCache(OPTION.HIDE_DIALOG_UPDATE_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_LIGHT_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_COLOR_ACTIONS, TRUE); + if (options[OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS]) setCache(OPTION.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS, TRUE); } } else { removeStyle(dialogChild); @@ -573,19 +599,19 @@ class KioskMode implements KioskModeRunner { // History and Logbook if ( - this.options[OPTION.HIDE_DIALOG_HISTORY] || - this.options[OPTION.HIDE_DIALOG_LOGBOOK] + options[OPTION.HIDE_DIALOG_HISTORY] || + options[OPTION.HIDE_DIALOG_LOGBOOK] ) { const styles = [ - this.options[OPTION.HIDE_DIALOG_HISTORY] + options[OPTION.HIDE_DIALOG_HISTORY] ? STYLES.DIALOG_HISTORY : '', - this.options[OPTION.HIDE_DIALOG_LOGBOOK] + options[OPTION.HIDE_DIALOG_LOGBOOK] ? STYLES.DIALOG_LOGBOOK : '' ]; addStyle(styles.join(''), moreInfo); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) { - if (this.options[OPTION.HIDE_DIALOG_HISTORY]) setCache(OPTION.HIDE_DIALOG_HISTORY, TRUE); - if (this.options[OPTION.HIDE_DIALOG_LOGBOOK]) setCache(OPTION.HIDE_DIALOG_LOGBOOK, TRUE); + if (options[OPTION.HIDE_DIALOG_HISTORY]) setCache(OPTION.HIDE_DIALOG_HISTORY, TRUE); + if (options[OPTION.HIDE_DIALOG_LOGBOOK]) setCache(OPTION.HIDE_DIALOG_LOGBOOK, TRUE); } } else { removeStyle(moreInfo); @@ -596,7 +622,7 @@ class KioskMode implements KioskModeRunner { .$ .element .then((dialogHistory: ShadowRoot) => { - if (this.options[OPTION.HIDE_DIALOG_HISTORY_SHOW_MORE]) { + if (options[OPTION.HIDE_DIALOG_HISTORY_SHOW_MORE]) { addStyle(STYLES.DIALOG_SHOW_MORE, dialogHistory); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) setCache(OPTION.HIDE_DIALOG_HISTORY_SHOW_MORE, TRUE); } else { @@ -609,7 +635,7 @@ class KioskMode implements KioskModeRunner { .$ .element .then((dialogLogbook: ShadowRoot) => { - if (this.options[OPTION.HIDE_DIALOG_LOGBOOK_SHOW_MORE]) { + if (options[OPTION.HIDE_DIALOG_LOGBOOK_SHOW_MORE]) { addStyle(STYLES.DIALOG_SHOW_MORE, dialogLogbook); if (queryString(SPECIAL_QUERY_PARAMS.CACHE)) setCache(OPTION.HIDE_DIALOG_LOGBOOK_SHOW_MORE, TRUE); } else { @@ -641,11 +667,7 @@ class KioskMode implements KioskModeRunner { this.HAElements.HEADER.selector.query(`${ELEMENT.TOOLBAR} ${ELEMENT.OVERLAY_MENU_ITEM}`).all .then((overflowMenuItems: NodeListOf) => { overflowMenuItems.forEach((overflowMenuItem: HTMLElement): void => { - if ( - overflowMenuItem && - overflowMenuItem.dataset && - !overflowMenuItem.dataset.selector - ) { + if (!overflowMenuItem?.dataset?.selector) { const textContent = overflowMenuItem.textContent.trim(); overflowMenuItem.dataset.selector = this.menuTranslations[textContent]; } @@ -654,49 +676,75 @@ class KioskMode implements KioskModeRunner { } } - // Run on entity change - protected async entityWatch() { - (await window.hassConnection).conn.subscribeMessage((e) => this.entityWatchCallback(e), { - type: SUSCRIBE_EVENTS_TYPE, - event_type: STATE_CHANGED_EVENT, - }); - } - - protected async entityWatchCallback(event: SuscriberEvent) { - const entities = window.kioskModeEntities[this.ha?.hass?.panelUrl] || []; - if ( - entities.length && - event.event_type === STATE_CHANGED_EVENT && - entities.includes(event.data.entity_id) && - ( - !event.data.old_state || - event.data.new_state.state !== event.data.old_state.state - ) - ) { - await this.run(); - this.runDialogs(); - } - } - protected blockEventHandler(event: Event) { event.preventDefault(); event.stopImmediatePropagation(); } - protected setOptions(config: ConditionalKioskConfig, conditional: boolean) { + protected setOptions(options: Options, config: ConditionalKioskConfig, conditional: boolean) { Object.values(OPTION).forEach((option: OPTION): void => { if (option in config) { - this.options[option] = config[option]; + this.setOptionsOrSubscribe(options, config, option); } }); if (conditional) { Object.values(CONDITIONAL_OPTION).forEach((option: CONDITIONAL_OPTION): void => { if (option in config) { - this.options[option] = config[option]; + if (typeof config[option] === 'boolean') { + options[option] = config[option]; + } else { + throw SyntaxError(`${NAMESPACE}: the option "${option}" accepts only boolean values`); + } } }); } } + + protected setOptionsOrSubscribe(options: Options, config: ConditionalKioskConfig, option: OPTION | CONDITIONAL_OPTION) { + const panelUrl = this._getPanelUrl(); + const value = config[option]; + if (typeof value === 'boolean') { + options[option] = value; + } else if (JINJA_TEMPLATE_REG.test(value)) { + window.hassConnection.then((hassConnection: HassConnection): void => { + hassConnection.conn.subscribeMessage( + (message: SubscriberTemplate): void => { + const result = message.result; + if (typeof result === 'boolean') { + options[option] = result; + } else if ( + typeof result === 'string' + && ( + result === 'true' || + result === 'false' + ) + ) { + options[option] = result === 'true'; + } else { + options[option] = false; + 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.insertStyles(); + this.runDialogs(); + } + }, + { + type: SUBSCRIBE_RENDER_TEMPLATE, + template: value, + variables: { + user_name: this.ha.hass.user.name, + user_is_admin: this.ha.hass.user.is_admin, + user_is_owner: this.ha.hass.user.is_owner, + user_agent: window.navigator.userAgent + } + } + ); + }); + } else { + throw SyntaxError(`${NAMESPACE}: the value "${value}" of the option "${option}" is not a well formed Jinja template`); + } + } } // Console tag diff --git a/src/types/index.ts b/src/types/index.ts index 544e46a..a8ff298 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,10 +8,10 @@ export interface KioskModeRunner { export interface User { name: string; is_admin: boolean; + is_owner: boolean; } export interface MobileSettings extends Exclude { - hide_header: boolean; custom_width: number; } @@ -19,22 +19,23 @@ export interface UserSetting extends ConditionalKioskConfig { users: string[]; } -export interface EntitySetting extends KioskConfig { - entity: Record; -} - -export type EntitySettings = EntitySetting[]; - type BaseKioskConfig = Partial< Record< OPTION, - boolean + boolean | string > >; type BaseConditionalKioskConfig = Partial< Record< CONDITIONAL_OPTION, + boolean | string + > +>; + +export type Options = Partial< + Record< + OPTION | CONDITIONAL_OPTION, boolean > >; @@ -44,15 +45,10 @@ export interface KioskConfig extends BaseKioskConfig { non_admin_settings?: ConditionalKioskConfig; user_settings?: UserSetting[]; mobile_settings?: MobileSettings; - entity_settings?: EntitySettings; } export type ConditionalKioskConfig = KioskConfig & BaseConditionalKioskConfig; -export interface EntityState { - state: string; -} - export class HomeAssistant extends HTMLElement { hass: { user: User; @@ -62,7 +58,6 @@ export class HomeAssistant extends HTMLElement { language: string; resources: Record>; panelUrl: string; - states: Record; }; } @@ -75,35 +70,27 @@ export class Lovelace extends HTMLElement { }; } -export type SuscriberEvent = { - event_type: string; - data: { - entity_id: string; - old_state?: { - state: string; - }; - new_state: { - state: string; - }; - } -}; -export type SuscriberCallback = (event: SuscriberEvent) => void; -export type SuscriberOptions = { - type: string; - event_type: string; +export type SubscriberTemplate = { + result: string; }; export interface HassConnection { conn: { - subscribeMessage: (callback: SuscriberCallback, options: SuscriberOptions) => void; + subscribeMessage: ( + callback: (response: T) => void, + options: Record> + ) => void; } } export type StyleElement = Element | ShadowRoot | Element[] | ShadowRoot[]; +export interface MoreInfoDialog extends HTMLElement { + __open: boolean; +} + declare global { interface Window { - kioskModeEntities: Record; KioskMode: KioskModeRunner; hassConnection: Promise; }