diff --git a/src/elements/lc-config-card.js b/src/elements/lc-config-card.js deleted file mode 100644 index 7e444cc..0000000 --- a/src/elements/lc-config-card.js +++ /dev/null @@ -1,427 +0,0 @@ -import { LitElement, html, css, nothing } from 'lit'; - -class LandroidConfigCard extends LitElement { - static properties = { - hass: { type: Object }, - config: { type: Object }, - deviceName: { type: String }, - }; - - /** - * Lifecycle method to update the component when it is connected to the DOM. - * - * Calls the LitElement `connectedCallback` method. - * - * @return {void} This function does not return anything. - * @see https://lit.dev/docs/components/lifecycle/#connectedcallback - */ - connectedCallback() { - super.connectedCallback(); - this.addEventListener('hass-updated', () => this.requestUpdate()); - } - - /** - * Lifecycle method to clean up when the component is disconnected from the DOM. - * - * Calls the LitElement `disconnectedCallback` method. - * - * @return {void} This function does not return anything. - * @see https://lit.dev/docs/components/lifecycle/#disconnectedcallback - */ - disconnectedCallback() { - super.disconnectedCallback(); - this.removeEventListener('hass-updated', () => this.requestUpdate()); - } - - /** - * Lifecycle method to update the component after the first render. - * - * This function is called automatically by LitElement after the component - * has been rendered for the first time. It sets the `_firstRendered` property - * of the component to `true`, indicating that the component has been rendered - * at least once. - * - * @return {void} This function does not return a value. - * @see https://lit.dev/docs/components/lifecycle/#firstupdated - */ - firstUpdated() { - this._firstRendered = true; - } - - /** - * Lifecycle method to indicate if the component should update. - * - * This method is called every time a property of the component changes. - * It is used to determine if the component should re-render when a property - * value changes. - * - * @param {Map} changedProps - Map of changed properties. - * - * @return {void} This function does not return a value. - * @see https://lit.dev/docs/components/lifecycle/#updated - */ - updated(changedProps) { - if (changedProps.has('hass')) { - console.log('Объект hass обновлен:', this.hass); - this.requestUpdate(); // Обновляем компонент при каждом изменении `hass` - } - } - - /** - * Lifecycle method to indicate if the component should update. - * - * This method is called by LitElement whenever a property of the component - * changes. It returns `true` if the component should update, and `false` - * otherwise. - * - * We update the component if the `hass`, `config`, or `deviceName` properties - * change. - * - * @param {Map} changedProps - Map of changed properties. - * @return {boolean} True if the component should update, false otherwise. - * @see https://lit.dev/docs/components/lifecycle/#shouldupdate - */ - shouldUpdate(changedProps) { - return ( - changedProps.has('hass') || - changedProps.has('config') || - changedProps.has('deviceName') - ); - } - - /** - * Styles for the component. - * - * The styles are scoped to the component and are used to style the - * component's host element. The styles are defined using LitElement's - * `css` tag function. - * - * @return {CSSResult} The styles for the component. - */ - static get styles() { - return css` - :host { - height: 100%; - display: flex; - flex-direction: column; - padding: var(--lc-spacing); - border-top: 1px solid var(--lc-divider-color); - } - - #states { - flex: 1 1 0%; - } - - #states > * { - margin: 8px 0px; - } - - #states > :first-child { - margin-top: 0px; - } - - #states > *:last-child { - margin-bottom: 0; - } - - #states > div > * { - overflow: clip visible; - } - - #states > div { - position: relative; - } - - .icon { - padding: 0px 18px 0px 8px; - } - - /* hui-input-number-entity-row */ - .flex { - display: flex; - align-items: center; - justify-content: flex-end; - flex-grow: 2; - } - - .state { - min-width: 45px; - text-align: end; - } - - .slider { - flex-grow: 2; - width: 100px; - max-width: 200px; - } - - ha-textfield { - text-align: end; - } - - ha-slider { - width: 100%; - max-width: 200px; - } - - ha-select { - width: 100%; - } - `; - } - - /** - * Renders the UI for the LandroidConfigCard component based on the current configuration. - * - * This function checks if the configuration and its entities are present. - * It then maps over the entities and renders the appropriate elements - * depending on the domain of each entity (button, number, select, switch). - * The rendered elements are wrapped inside a div with the id "states". - * - * @return {TemplateResult|nothing} The rendered HTML template or `nothing` if no config is available. - */ - render() { - if (!this.config || !this.config.entities) return nothing; - - const entities = this.config.entities.map((entityId) => { - const domain = entityId.split('.')[0]; - switch (domain) { - case 'button': - return this.renderButtonEntity(entityId); - case 'number': - return this.renderNumberEntity(entityId); - case 'select': - return this.renderSelectEntity(entityId); - case 'switch': - return this.renderToggleSwitchEntity(entityId); - default: - return nothing; - } - }); - - return html` -
- ${entities} -
- `; - } - - /** - * Renders a button for a given button entity in the LandroidConfigCard UI. - * - * @param {string} entityId - The ID of the button entity to be rendered. - * @return {TemplateResult|nothing} The rendered HTML template for the button or `nothing` if the entity is unavailable. - */ - renderButtonEntity(entityId) { - const stateObj = this.hass?.states[entityId]; - if (!stateObj || stateObj.state === 'unavailable') return nothing; - - return html` - - this.pressButton(entityId)} - .disabled=${stateObj.state === 'unavailable'} - > - ${this.hass.localize('ui.card.button.press')} - - - `; - } - - /** - * Renders a number input for a given number entity in the LandroidConfigCard UI. - * - * @param {string} entityId - The ID of the number entity to be rendered. - * @return {TemplateResult|nothing} The rendered HTML template for the number input - * or `nothing` if the entity is unavailable. - */ - renderNumberEntity(entityId) { - const stateObj = this.hass.states[entityId]; - if (!stateObj || stateObj.state === 'unavailable') return nothing; - - return html` - - ${stateObj.attributes.mode === 'slider' || - (stateObj.attributes.mode === 'auto' && - (Number(stateObj.attributes.max) - Number(stateObj.attributes.min)) / - Number(stateObj.attributes.step) <= - 256) - ? html` - this.numberValueChanged(e, stateObj)} - > - - ${this.hass.formatEntityState(stateObj)} - - ` - : html` -
- this.numberValueChanged(e, stateObj)} - > -
- `} -
- `; - } - - /** - * Renders a select for a given entity in the UI. - * - * @param {string} entityId - The entity to render a select for. - * @return {TemplateResult} The rendered select as a TemplateResult. - */ - renderSelectEntity(entityId) { - const stateObj = this.hass.states[entityId]; - if (!stateObj || stateObj.state === 'unavailable') return nothing; - const config = this.configForEntity(entityId); - - return html` - - this.selectedChanged(e, stateObj)} - @closed=${(e) => e.stopPropagation()} - @click=${(e) => e.stopPropagation()} - > - ${stateObj.attributes.options - ? stateObj.attributes.options.map( - (option) => html` - - ${this.hass.formatEntityState(stateObj, option)} - - `, - ) - : ''} - - - `; - } - - /** - * Renders a toggle switch for a given entity in the UI. - * - * @param {string} entityId - The entity to render a toggle switch for. - * @return {TemplateResult} The rendered toggle switch as a TemplateResult. - */ - renderToggleSwitchEntity(entityId) { - const stateObj = this.hass.states[entityId]; - if (!stateObj || stateObj.state === 'unavailable') return nothing; - - return html` - - this.toggleChanged(e, stateObj)} - > - - `; - } - - /** - * Generates a configuration object for a given entity. - * - * @param {string} entityId - The ID of the entity to generate the configuration for. - * @return {Object} A configuration object containing the entity ID, - * the entity's name without the device name, and the entity's icon. - */ - configForEntity(entityId) { - const stateObj = this.hass.states[entityId]; - return { - entity: entityId, - name: stateObj.attributes.friendly_name.replace(`${this.deviceName} `, ''), - icon: stateObj.attributes.icon, - }; - } - - /** - * Invokes the 'press' service call for a button entity. - * - * @param {string} entityId - The ID of the button entity to be pressed. - * @return {void} This function does not return anything. - */ - pressButton(entityId) { - this.hass.callService("button", "press", { entity_id: entityId }); - this.requestUpdate(); - } - - /** - * Invokes the 'set_value' service call for a number entity - * if the value of the input element is different from the current state of the entity. - * - * @param {Event} e - The event that triggered this function. - * @param {Object} stateObj - The state object of the entity. - * @return {void} This function does not return anything. - */ - numberValueChanged(e, stateObj) { - const value = e.target.value; - if (value !== stateObj.state) { - this.hass.callService("number", "set_value", { - entity_id: stateObj.entity_id, - value, - }); - this.requestUpdate(); - } - } - - /** - * Handles the change event for a select element in the UI, updating the entity state if a new option is selected. - * - * @param {Event} e - The event triggered by the select element. - * @param {Object} stateObj - The state object of the entity. - * @return {void} This function does not return anything. - */ - selectedChanged(e, stateObj) { - const option = e.target.value; - if ( - option === stateObj.state || - !stateObj.attributes.options.includes(option) - ) { - return; - } - - this.hass.callService('select', 'select_option', { - entity_id: [stateObj.entity_id], - option, - }); - this.requestUpdate(); - } - - /** - * Handles the change event for a switch element in the UI, updating the entity state if the switch is toggled. - * - * @param {Event} e - The event triggered by the switch element. - * @param {Object} stateObj - The state object of the entity. - * @return {void} This function does not return anything. - */ - toggleChanged(e, stateObj) { - const newState = e.target.checked ? "on" : "off"; - if (newState !== stateObj.state) { - this.hass.callService("switch", newState === "on" ? "turn_on" : "turn_off", { - entity_id: stateObj.entity_id, - }); - this.requestUpdate(); - } - } - -} - -customElements.define('lc-config-card', LandroidConfigCard); diff --git a/src/landroid-card.js b/src/landroid-card.js index 25ab1ab..6d97076 100644 --- a/src/landroid-card.js +++ b/src/landroid-card.js @@ -16,10 +16,8 @@ import * as consts from './constants'; import { DEFAULT_LANG, defaultConfig } from './defaults'; import LandroidCardEditor from './landroid-card-editor'; import './elements/lc-linear-progress'; -import './elements/lc-config-card'; const editorName = 'landroid-card-editor'; -const SENSOR_DEVICE_CLASS_TIMESTAMP = 'timestamp'; customElements.define(editorName, LandroidCardEditor); @@ -95,7 +93,7 @@ class LandroidCard extends LitElement { * @return {object|undefined} The entity object from the Home Assistant state, or undefined if the entity does not exist. */ get entity() { - return this.hass.states[this.config.entity] || undefined; + return this.hass?.states[this.config.entity] || undefined; } /** @@ -254,6 +252,17 @@ class LandroidCard extends LitElement { return this.config?.show_toolbar ?? true; } + /** + * Returns the list of entities to be displayed as settings in the card. + * If the user has not specified the 'settings' option in the config, + * this function returns an empty array. + * + * @return {string[]} The list of entities to be displayed as settings in the card. + */ + get settingsEntity() { + return this.config?.settings ?? []; + } + /** * Sets the configuration for the component. * @@ -354,10 +363,10 @@ class LandroidCard extends LitElement { * @return {void} This function does not return anything. */ disconnectedCallback() { - super.disconnectedCallback(); if (this.camera) { clearInterval(this.thumbUpdater); } + super.disconnectedCallback(); } /** @@ -413,14 +422,14 @@ class LandroidCard extends LitElement { handleAction(e, action, params = {}) { const actions = this.config.actions || {}; const {defaultService = action, ...service_data} = params; - // return () => { + return () => { if (!actions[action]) { this.callService(e, defaultService, service_data); return; } this.callAction(actions[action]); - // }; + }; } /** @@ -439,13 +448,15 @@ class LandroidCard extends LitElement { } /** - * Retrieves the friendly name of an entity without the device name. + * Returns the friendly name of the given entity without the device name prefix. * - * @param {Object} entity - The entity object for which to retrieve the name. - * @return {string} The friendly name of the entity without the device name. + * @param {string} entityId - The ID of the entity to get the friendly name for. + * @return {string} The friendly name of the entity. */ - getEntityName(entity) { - if (!isObject(entity)) return ''; + getEntityName(entityId) { + const entity = this.hass.states[entityId]; + if (!isObject(entity)) + return ''; const { friendly_name: deviceName } = this.getAttributes(); const { friendly_name: entityName } = entity.attributes; @@ -492,6 +503,23 @@ class LandroidCard extends LitElement { }, []); } + /** + * Retrieves an object with entity objects from the associatedEntities object by matching the given suffixes + * against the entity IDs. The resulting object will only contain entities that have a state other than 'unavailable'. + * + * @param {string[]} suffixes - The suffixes to match against the entity IDs. + * @return {string[]} An array of matching entity IDs. + */ + findEntitiesIdBySuffixes(suffixes) { + return suffixes.reduce((entityIds, suffix) => { + const filteredEntities = Object.values(this.associatedEntities).filter( + (entity) => entity && entity.state !== 'unavailable' && entity.entity_id.endsWith(suffix) + ); + return entityIds.concat(filteredEntities.map(entity => entity.entity_id)); + }, []); + // return this.findEntitiesBySuffixes(suffixes).map(entity => entity.entity_id); + } + /** * Toggles the visibility of the given card type and updates the component to reflect the change. * @@ -544,7 +572,7 @@ class LandroidCard extends LitElement { return nothing; } - const title = this.getEntityName(entity); + const title = this.getEntityName(entity.entity_id); const state = entity.entity_id.includes('rssi') ? wifiStrenghtToQuality(entity.state) : this.hass.formatEntityState(entity); @@ -565,53 +593,6 @@ class LandroidCard extends LitElement { `; } - /** - * Renders the Info Card for a given card type. - * - * @param {string} card - The type of card to render. - * @return {TemplateResult|nothing} The rendered Info Card or nothing if the card is not visible. - */ - renderInfoCard(card) { - if (!consts.CARD_MAP[card].visibility) return nothing; - - try { - const entities = this.findEntitiesBySuffixes(consts.CARD_MAP[card].entities); - - return html` -
-
- ${Object.values(entities).map((stateObj) => { - if (!stateObj || stateObj.state === consts.UNAVAILABLE) return nothing; - - const entity_id = stateObj.entity_id; - const title = this.getEntityName(stateObj); - const config = { entity: entity_id, name: title }; - - return html` - -
this.handleMore(entity_id)}> - ${stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP && !stateObj.state.includes('unknown') - ? html` - - ` - : this.hass.formatEntityState(stateObj)} -
-
- `; - })} -
-
- `; - } catch (e) { - console.warn(e); - return nothing; - } - } - /** * Renders the camera or image based on the provided state. * @@ -965,21 +946,38 @@ return html` } /** - * Renders a configuration card to edit the settings. + * Renders a HUI entities card based on the given entities and configuration. * - * The `lc-config-card` element is created and its properties are set based on the - * component's configuration and device name. + * @param {Array} entities - The entities to be rendered. + * @param {boolean} [showCard=false] - Whether to show the card or not. + * @return {TemplateResult} The rendered HUI entities card component. + */ + renderEntitiesCard(entities, showCard = false) { + if (!this.config || !entities || !showCard) return nothing; + + const entitiesCardConfig = { + type: 'entities', + entities: entities.map(entity => ({ + entity: entity, + name: this.getEntityName(entity), + })) + }; + + return this.createHuiCardElement(entitiesCardConfig); + } + + /** + * Creates a HUI entities card element with the given configuration. * - * @return {HTMLElement} The rendered configuration card element. + * @param {Object} config - The configuration for the HUI entities card. + * @return {HuiEntitiesCardElement} The created HUI entities card element. */ - renderConfigCard() { - if (!this.config || !this.config.settings || !this.showConfigCard) return nothing; - - const configCard = document.createElement('lc-config-card'); - configCard.hass = this.hass; - configCard.config = { entities: this.config.settings, }; - configCard.deviceName = this.getAttributes().friendly_name; - return configCard; + createHuiCardElement(config) { + const element = document.createElement('hui-entities-card'); + element.setConfig(config); + element.hass = this.hass; + this.entitiesCard = element; // Store reference + return element; } /** @@ -1006,28 +1004,25 @@ return html` return html` +
+ ${this.renderTipButton(consts.INFOCARD)} + ${this.renderTipButton(consts.STATISTICSCARD)} + ${this.renderTipButton(consts.BATTERYCARD)} +
-
- ${this.renderTipButton(consts.INFOCARD)} - ${this.renderTipButton(consts.STATISTICSCARD)} - ${this.renderTipButton(consts.BATTERYCARD)} -
- ${this.renderInfoCard(consts.INFOCARD)} - ${this.renderInfoCard(consts.STATISTICSCARD)} - ${this.renderInfoCard(consts.BATTERYCARD)} + ${Object.values(consts.CARD_MAP).map((card) => this.renderEntitiesCard(this.findEntitiesIdBySuffixes(card.entities), card.visibility))}
${this.renderCameraOrImage(state)} -
${this.renderStats(state)}
${this.renderToolbar(state)}
- ${this.renderConfigCard()} + ${this.renderEntitiesCard(this.settingsEntity, this.showConfigCard)}
- `; + `; } }