mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
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
This commit is contained in:
parent
12d8a04c15
commit
9ad7f0dbac
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<DeviceAction> {
|
||||
protected headerKey = "ui.panel.config.devices.automation.actions.caption";
|
||||
|
||||
constructor() {
|
||||
super(localizeDeviceAutomationAction, fetchDeviceActions);
|
||||
super(localizeDeviceAutomationAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<T[]>;
|
||||
|
||||
constructor(
|
||||
localizeDeviceAutomation: HaDeviceAutomationCard<
|
||||
T
|
||||
>["_localizeDeviceAutomation"],
|
||||
fetchDeviceAutomations: HaDeviceAutomationCard<T>["_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<void> {
|
||||
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<
|
||||
<div class="card-content">
|
||||
<ha-chips
|
||||
@chip-clicked=${this._handleAutomationClicked}
|
||||
.items=${this._automations.map((automation) =>
|
||||
.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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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`
|
||||
<ha-card>
|
||||
<paper-item>
|
||||
@ -67,8 +48,8 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
)}
|
||||
</ha-switch>
|
||||
</paper-item>
|
||||
${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 {
|
||||
></ha-icon>
|
||||
`}
|
||||
<paper-item-body two-line>
|
||||
<div class="name">${this._computeEntityName(entry)}</div>
|
||||
<div class="name">${entry.stateName}</div>
|
||||
<div class="secondary entity-id">${entry.entity_id}</div>
|
||||
</paper-item-body>
|
||||
<div class="buttons">
|
||||
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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`
|
||||
<hass-subpage .header=${device.name_by_user || device.name}>
|
||||
<paper-icon-button
|
||||
@ -84,37 +149,114 @@ export class HaConfigDevicePage extends LitElement {
|
||||
hide-entities
|
||||
></ha-device-card>
|
||||
|
||||
<div class="header">Entities</div>
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
.entities=${this.entities}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
|
||||
<div class="header">Automations</div>
|
||||
<ha-device-triggers-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
></ha-device-triggers-card>
|
||||
<ha-device-conditions-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
></ha-device-conditions-card>
|
||||
<ha-device-actions-card
|
||||
.hass=${this.hass}
|
||||
.deviceId=${this.deviceId}
|
||||
></ha-device-actions-card>
|
||||
${entities.length
|
||||
? html`
|
||||
<div class="header">Entities</div>
|
||||
<ha-device-entities-card
|
||||
.hass=${this.hass}
|
||||
.entities=${entities}
|
||||
>
|
||||
</ha-device-entities-card>
|
||||
`
|
||||
: html``}
|
||||
${this._triggers.length ||
|
||||
this._conditions.length ||
|
||||
this._actions.length
|
||||
? html`
|
||||
<div class="header">Automations</div>
|
||||
${this._triggers.length
|
||||
? html`
|
||||
<ha-device-triggers-card
|
||||
.hass=${this.hass}
|
||||
.automations=${this._triggers}
|
||||
></ha-device-triggers-card>
|
||||
`
|
||||
: ""}
|
||||
${this._conditions.length
|
||||
? html`
|
||||
<ha-device-conditions-card
|
||||
.hass=${this.hass}
|
||||
.automations=${this._conditions}
|
||||
></ha-device-conditions-card>
|
||||
`
|
||||
: ""}
|
||||
${this._actions.length
|
||||
? html`
|
||||
<ha-device-actions-card
|
||||
.hass=${this.hass}
|
||||
.automations=${this._actions}
|
||||
></ha-device-actions-card>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html``}
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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 = <T extends EntityConfig>(
|
||||
|
Loading…
x
Reference in New Issue
Block a user