From 9ad7f0dbac7d174fbe1aaebc0acbd53d0a1e8a00 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 8 Oct 2019 17:53:31 +0200 Subject: [PATCH] Hide empty blocks on device page (#3950) * Hide empty blocks on device page * lint * Rename entities on device rename * check if entity_id is valid * clarify var name * Review comments * Use regex to replace not allowed chars * Align with backend --- src/common/entity/valid_entity_id.ts | 12 +- src/components/entity/ha-entities-picker.ts | 2 +- .../device-detail/ha-device-actions-card.ts | 3 +- .../ha-device-automation-card.ts | 29 +-- .../ha-device-conditions-card.ts | 3 +- .../device-detail/ha-device-entities-card.ts | 37 +--- .../device-detail/ha-device-triggers-card.ts | 3 +- .../config/devices/ha-config-device-page.ts | 190 +++++++++++++++--- .../config/devices/ha-config-devices.ts | 2 + .../lovelace/cards/hui-entity-button-card.ts | 2 +- src/panels/lovelace/cards/hui-gauge-card.ts | 2 +- .../cards/hui-weather-forecast-card.ts | 2 +- .../common/process-config-entities.ts | 2 +- 13 files changed, 198 insertions(+), 91 deletions(-) diff --git a/src/common/entity/valid_entity_id.ts b/src/common/entity/valid_entity_id.ts index 45888dab44..df89f88e54 100644 --- a/src/common/entity/valid_entity_id.ts +++ b/src/common/entity/valid_entity_id.ts @@ -1,2 +1,12 @@ const validEntityId = /^(\w+)\.(\w+)$/; -export default (entityId: string) => validEntityId.test(entityId); + +export const isValidEntityId = (entityId: string) => + validEntityId.test(entityId); + +export const createValidEntityId = (input: string) => + input + .toLowerCase() + .replace(/\s|\'/g, "_") // replace spaces and quotes with underscore + .replace(/\W/g, "") // remove not allowed chars + .replace(/_{2,}/g, "_") // replace multiple underscores with 1 + .replace(/_$/, ""); // remove underscores at the end diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index 684ef67dc1..0ac70b9223 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -10,7 +10,7 @@ import "@polymer/paper-icon-button/paper-icon-button-light"; import { HomeAssistant } from "../../types"; import { PolymerChangedEvent } from "../../polymer-types"; import { fireEvent } from "../../common/dom/fire_event"; -import isValidEntityId from "../../common/entity/valid_entity_id"; +import { isValidEntityId } from "../../common/entity/valid_entity_id"; import "./ha-entity-picker"; // Not a duplicate, type import diff --git a/src/panels/config/devices/device-detail/ha-device-actions-card.ts b/src/panels/config/devices/device-detail/ha-device-actions-card.ts index a799b96593..45b933b343 100644 --- a/src/panels/config/devices/device-detail/ha-device-actions-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-actions-card.ts @@ -1,7 +1,6 @@ import { customElement } from "lit-element"; import { DeviceAction, - fetchDeviceActions, localizeDeviceAutomationAction, } from "../../../../data/device_automation"; @@ -15,7 +14,7 @@ export class HaDeviceActionsCard extends HaDeviceAutomationCard { protected headerKey = "ui.panel.config.devices.automation.actions.caption"; constructor() { - super(localizeDeviceAutomationAction, fetchDeviceActions); + super(localizeDeviceAutomationAction); } } diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts index 3f7cb43b7b..1cdbe5c6b9 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-card.ts @@ -11,34 +11,27 @@ export abstract class HaDeviceAutomationCard< > extends LitElement { @property() public hass!: HomeAssistant; @property() public deviceId?: string; + @property() public automations: T[] = []; protected headerKey = ""; protected type = ""; - @property() private _automations: T[] = []; - private _localizeDeviceAutomation: ( hass: HomeAssistant, automation: T ) => string; - private _fetchDeviceAutomations: ( - hass: HomeAssistant, - deviceId: string - ) => Promise; constructor( localizeDeviceAutomation: HaDeviceAutomationCard< T - >["_localizeDeviceAutomation"], - fetchDeviceAutomations: HaDeviceAutomationCard["_fetchDeviceAutomations"] + >["_localizeDeviceAutomation"] ) { super(); this._localizeDeviceAutomation = localizeDeviceAutomation; - this._fetchDeviceAutomations = fetchDeviceAutomations; } protected shouldUpdate(changedProps): boolean { - if (changedProps.has("deviceId") || changedProps.has("_automations")) { + if (changedProps.has("deviceId") || changedProps.has("automations")) { return true; } const oldHass = changedProps.get("hass"); @@ -48,18 +41,8 @@ export abstract class HaDeviceAutomationCard< return false; } - protected async updated(changedProps): Promise { - super.updated(changedProps); - - if (changedProps.has("deviceId")) { - this._automations = this.deviceId - ? await this._fetchDeviceAutomations(this.hass, this.deviceId) - : []; - } - } - protected render(): TemplateResult { - if (this._automations.length === 0) { + if (this.automations.length === 0) { return html``; } return html` @@ -70,7 +53,7 @@ export abstract class HaDeviceAutomationCard<
+ .items=${this.automations.map((automation) => this._localizeDeviceAutomation(this.hass, automation) )} > @@ -81,7 +64,7 @@ export abstract class HaDeviceAutomationCard< } private _handleAutomationClicked(ev: CustomEvent) { - const automation = this._automations[ev.detail.index]; + const automation = this.automations[ev.detail.index]; if (!automation) { return; } diff --git a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts index b98e1a67c4..a56119d480 100644 --- a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts @@ -1,7 +1,6 @@ import { customElement } from "lit-element"; import { DeviceCondition, - fetchDeviceConditions, localizeDeviceAutomationCondition, } from "../../../../data/device_automation"; @@ -17,7 +16,7 @@ export class HaDeviceConditionsCard extends HaDeviceAutomationCard< protected headerKey = "ui.panel.config.devices.automation.conditions.caption"; constructor() { - super(localizeDeviceAutomationCondition, fetchDeviceConditions); + super(localizeDeviceAutomationCondition); } } diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 03129e5a1c..17f260064b 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -10,9 +10,7 @@ import { import { classMap } from "lit-html/directives/class-map"; import { HomeAssistant } from "../../../../types"; -import memoizeOne from "memoize-one"; -import { compare } from "../../../../common/string/compare"; import "../../../../components/entity/state-badge"; import "@polymer/paper-item/paper-item"; @@ -22,40 +20,23 @@ import "@polymer/paper-item/paper-item-body"; import "../../../../components/ha-card"; import "../../../../components/ha-icon"; import "../../../../components/ha-switch"; -import { computeStateName } from "../../../../common/entity/compute_state_name"; -import { EntityRegistryEntry } from "../../../../data/entity_registry"; import { showEntityRegistryDetailDialog } from "../../entity_registry/show-dialog-entity-registry-detail"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; // tslint:disable-next-line import { HaSwitch } from "../../../../components/ha-switch"; +import { EntityRegistryStateEntry } from "../ha-config-device-page"; @customElement("ha-device-entities-card") export class HaDeviceEntitiesCard extends LitElement { @property() public hass!: HomeAssistant; @property() public deviceId!: string; - @property() public entities!: EntityRegistryEntry[]; + @property() public entities!: EntityRegistryStateEntry[]; @property() public narrow!: boolean; @property() private _showDisabled = false; - private _entities = memoizeOne( - ( - deviceId: string, - entities: EntityRegistryEntry[] - ): EntityRegistryEntry[] => - entities - .filter((entity) => entity.device_id === deviceId) - .sort((ent1, ent2) => - compare( - this._computeEntityName(ent1) || `zzz${ent1.entity_id}`, - this._computeEntityName(ent2) || `zzz${ent2.entity_id}` - ) - ) - ); - protected render(): TemplateResult { - const entities = this._entities(this.deviceId, this.entities); return html` @@ -67,8 +48,8 @@ export class HaDeviceEntitiesCard extends LitElement { )} - ${entities.length - ? entities.map((entry: EntityRegistryEntry) => { + ${this.entities.length + ? this.entities.map((entry: EntityRegistryStateEntry) => { if (!this._showDisabled && entry.disabled_by) { return ""; } @@ -92,7 +73,7 @@ export class HaDeviceEntitiesCard extends LitElement { > `} -
${this._computeEntityName(entry)}
+
${entry.stateName}
${entry.entity_id}
@@ -143,14 +124,6 @@ export class HaDeviceEntitiesCard extends LitElement { fireEvent(this, "hass-more-info", { entityId: entry.entity_id }); } - private _computeEntityName(entity) { - if (entity.name) { - return entity.name; - } - const state = this.hass.states[entity.entity_id]; - return state ? computeStateName(state) : null; - } - static get styles(): CSSResult { return css` ha-icon { diff --git a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts index 76dba47577..7920681605 100644 --- a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts @@ -1,7 +1,6 @@ import { customElement } from "lit-element"; import { DeviceTrigger, - fetchDeviceTriggers, localizeDeviceAutomationTrigger, } from "../../../../data/device_automation"; @@ -15,7 +14,7 @@ export class HaDeviceTriggersCard extends HaDeviceAutomationCard< protected headerKey = "ui.panel.config.devices.automation.triggers.caption"; constructor() { - super(localizeDeviceAutomationTrigger, fetchDeviceTriggers); + super(localizeDeviceAutomationTrigger); } } diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index c47cf2610d..07a53b6d8d 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -19,7 +19,10 @@ import "./device-detail/ha-device-actions-card"; import "./device-detail/ha-device-entities-card"; import { HomeAssistant } from "../../../types"; import { ConfigEntry } from "../../../data/config_entries"; -import { EntityRegistryEntry } from "../../../data/entity_registry"; +import { + EntityRegistryEntry, + updateEntityRegistryEntry, +} from "../../../data/entity_registry"; import { DeviceRegistryEntry, updateDeviceRegistryEntry, @@ -30,6 +33,22 @@ import { showDeviceRegistryDetailDialog, } from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; +import { + DeviceTrigger, + DeviceAction, + DeviceCondition, + fetchDeviceTriggers, + fetchDeviceConditions, + fetchDeviceActions, +} from "../../../data/device_automation"; +import { compare } from "../../../common/string/compare"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { createValidEntityId } from "../../../common/entity/valid_entity_id"; + +export interface EntityRegistryStateEntry extends EntityRegistryEntry { + stateName?: string; +} + @customElement("ha-config-device-page") export class HaConfigDevicePage extends LitElement { @property() public hass!: HomeAssistant; @@ -39,6 +58,10 @@ export class HaConfigDevicePage extends LitElement { @property() public areas!: AreaRegistryEntry[]; @property() public deviceId!: string; @property() public narrow!: boolean; + @property() public showAdvanced!: boolean; + @property() private _triggers: DeviceTrigger[] = []; + @property() private _conditions: DeviceCondition[] = []; + @property() private _actions: DeviceAction[] = []; private _device = memoizeOne( ( @@ -48,11 +71,51 @@ export class HaConfigDevicePage extends LitElement { devices ? devices.find((device) => device.id === deviceId) : undefined ); + private _entities = memoizeOne( + ( + deviceId: string, + entities: EntityRegistryEntry[] + ): EntityRegistryStateEntry[] => + entities + .filter((entity) => entity.device_id === deviceId) + .map((entity) => { + return { ...entity, stateName: this._computeEntityName(entity) }; + }) + .sort((ent1, ent2) => + compare( + ent1.stateName || `zzz${ent1.entity_id}`, + ent2.stateName || `zzz${ent2.entity_id}` + ) + ) + ); + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); loadDeviceRegistryDetailDialog(); } + protected updated(changedProps): void { + super.updated(changedProps); + + if (changedProps.has("deviceId")) { + if (this.deviceId) { + fetchDeviceTriggers(this.hass, this.deviceId).then( + (triggers) => (this._triggers = triggers) + ); + fetchDeviceConditions(this.hass, this.deviceId).then( + (conditions) => (this._conditions = conditions) + ); + fetchDeviceActions(this.hass, this.deviceId).then( + (actions) => (this._actions = actions) + ); + } else { + this._triggers = []; + this._conditions = []; + this._actions = []; + } + } + } + protected render() { const device = this._device(this.deviceId, this.devices); @@ -62,6 +125,8 @@ export class HaConfigDevicePage extends LitElement { `; } + const entities = this._entities(this.deviceId, this.entities); + return html` -
Entities
- - - -
Automations
- - - + ${entities.length + ? html` +
Entities
+ + + ` + : html``} + ${this._triggers.length || + this._conditions.length || + this._actions.length + ? html` +
Automations
+ ${this._triggers.length + ? html` + + ` + : ""} + ${this._conditions.length + ? html` + + ` + : ""} + ${this._actions.length + ? html` + + ` + : ""} + ` + : html``}
`; } - private _showSettings() { + private _computeEntityName(entity) { + if (entity.name) { + return entity.name; + } + const state = this.hass.states[entity.entity_id]; + return state ? computeStateName(state) : null; + } + + private async _showSettings() { + const device = this._device(this.deviceId, this.devices)!; showDeviceRegistryDetailDialog(this, { - device: this._device(this.deviceId, this.devices)!, + device, updateEntry: async (updates) => { + const oldDeviceName = device.name_by_user || device.name; + const newDeviceName = updates.name_by_user; await updateDeviceRegistryEntry(this.hass, this.deviceId, updates); + + if ( + !oldDeviceName || + !newDeviceName || + oldDeviceName === newDeviceName + ) { + return; + } + const entities = this._entities(this.deviceId, this.entities); + + const renameEntityid = + this.showAdvanced && + confirm( + "Do you also want to rename the entity id's of your entities?" + ); + + const updateProms = entities.map((entity) => { + const name = entity.name || entity.stateName; + let newEntityId: string | null = null; + let newName: string | null = null; + + if (name && name.includes(oldDeviceName)) { + newName = name.replace(oldDeviceName, newDeviceName); + } + + if (renameEntityid) { + const oldSearch = createValidEntityId(oldDeviceName); + if (entity.entity_id.includes(oldSearch)) { + newEntityId = entity.entity_id.replace( + oldSearch, + createValidEntityId(newDeviceName) + ); + } + } + + if (!newName && !newEntityId) { + return new Promise((resolve) => resolve()); + } + + return updateEntityRegistryEntry(this.hass!, entity.entity_id, { + name: newName || name, + disabled_by: entity.disabled_by, + new_entity_id: newEntityId || entity.entity_id, + }); + }); + await Promise.all(updateProms); }, }); } diff --git a/src/panels/config/devices/ha-config-devices.ts b/src/panels/config/devices/ha-config-devices.ts index 1e7c696abe..f0de595083 100644 --- a/src/panels/config/devices/ha-config-devices.ts +++ b/src/panels/config/devices/ha-config-devices.ts @@ -28,6 +28,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; class HaConfigDevices extends HassRouterPage { @property() public hass!: HomeAssistant; @property() public narrow!: boolean; + @property() public showAdvanced!: boolean; protected routerOptions: RouterOptions = { defaultPage: "dashboard", @@ -96,6 +97,7 @@ class HaConfigDevices extends HassRouterPage { pageEl.devices = this._deviceRegistryEntries; pageEl.areas = this._areas; pageEl.narrow = this.narrow; + pageEl.showAdvanced = this.showAdvanced; } private _loadData() { diff --git a/src/panels/lovelace/cards/hui-entity-button-card.ts b/src/panels/lovelace/cards/hui-entity-button-card.ts index 1bcc93b11f..3d91d68816 100644 --- a/src/panels/lovelace/cards/hui-entity-button-card.ts +++ b/src/panels/lovelace/cards/hui-entity-button-card.ts @@ -15,7 +15,7 @@ import "@material/mwc-ripple"; import "../../../components/ha-card"; import "../components/hui-warning"; -import isValidEntityId from "../../../common/entity/valid_entity_id"; +import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { stateIcon } from "../../../common/entity/state_icon"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index cf00c29b04..8264931b39 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -13,7 +13,7 @@ import { styleMap } from "lit-html/directives/style-map"; import "../../../components/ha-card"; import "../components/hui-warning"; -import isValidEntityId from "../../../common/entity/valid_entity_id"; +import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import { computeStateName } from "../../../common/entity/compute_state_name"; diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 93f43609e1..e17b06d311 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -12,7 +12,7 @@ import { import "../../../components/ha-card"; import "../components/hui-warning"; -import isValidEntityId from "../../../common/entity/valid_entity_id"; +import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { HomeAssistant } from "../../../types"; diff --git a/src/panels/lovelace/common/process-config-entities.ts b/src/panels/lovelace/common/process-config-entities.ts index adb91cd09c..d20b03f907 100644 --- a/src/panels/lovelace/common/process-config-entities.ts +++ b/src/panels/lovelace/common/process-config-entities.ts @@ -1,5 +1,5 @@ // Parse array of entity objects from config -import isValidEntityId from "../../../common/entity/valid_entity_id"; +import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { EntityConfig } from "../entity-rows/types"; export const processConfigEntities = (