diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index 53f16a16b2..aa26eb2c6b 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -188,6 +188,7 @@ const createEntityRegistryEntries = ( device_id: "mock-device-id", area_id: null, disabled_by: null, + hidden_by: null, entity_category: null, entity_id: "binary_sensor.updater", name: null, diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 83fcc601b5..eaefcee6e3 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -14,6 +14,7 @@ export interface EntityRegistryEntry { device_id: string | null; area_id: string | null; disabled_by: string | null; + hidden_by: string | null; entity_category: "config" | "diagnostic" | null; } @@ -38,6 +39,7 @@ export interface EntityRegistryEntryUpdateParams { device_class?: string | null; area_id?: string | null; disabled_by?: string | null; + hidden_by: string | null; new_entity_id?: string; } 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 ebfbd159dd..19e60da9ee 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 @@ -40,7 +40,7 @@ export class HaDeviceEntitiesCard extends LitElement { @property() public entities!: EntityRegistryStateEntry[]; - @property() public showDisabled = false; + @property() public showHidden = false; @state() private _extDisabledEntityEntries?: Record< string, @@ -60,77 +60,77 @@ export class HaDeviceEntitiesCard extends LitElement { } protected render(): TemplateResult { - const disabledEntities: EntityRegistryStateEntry[] = []; + if (!this.entities.length) { + return html` + +
+ ${this.hass.localize("ui.panel.config.devices.entities.none")} +
+
+ `; + } + + const shownEntities: EntityRegistryStateEntry[] = []; + const hiddenEntities: EntityRegistryStateEntry[] = []; this._entityRows = []; + + this.entities.forEach((entry) => { + if (entry.disabled_by || entry.hidden_by) { + if (this._extDisabledEntityEntries) { + hiddenEntities.push( + this._extDisabledEntityEntries[entry.entity_id] || entry + ); + } else { + hiddenEntities.push(entry); + } + } else { + shownEntities.push(entry); + } + }); + return html` - ${this.entities.length - ? html` -
- ${this.entities.map((entry: EntityRegistryStateEntry) => { - if (entry.disabled_by) { - if (this._extDisabledEntityEntries) { - disabledEntities.push( - this._extDisabledEntityEntries[entry.entity_id] || entry - ); - } else { - disabledEntities.push(entry); - } - return ""; - } - return this.hass.states[entry.entity_id] - ? this._renderEntity(entry) - : this._renderEntry(entry); - })} -
- ${disabledEntities.length - ? !this.showDisabled - ? html` - - ` - : html` - ${disabledEntities.map((entry) => - this._renderEntry(entry) - )} - - ` - : ""} -
- +
+ ${shownEntities.map((entry) => + this.hass.states[entry.entity_id] + ? this._renderEntity(entry) + : this._renderEntry(entry) + )} +
+ ${hiddenEntities.length + ? !this.showHidden + ? html` +
- ` - : html` -
- ${this.hass.localize("ui.panel.config.devices.entities.none")} -
- `} + + ` + : html` + ${hiddenEntities.map((entry) => this._renderEntry(entry))} + + ` + : ""} +
+ + ${this.hass.localize( + "ui.panel.config.devices.entities.add_entities_lovelace" + )} + +
`; } - private _toggleShowDisabled() { - this.showDisabled = !this.showDisabled; - if (!this.showDisabled || this._extDisabledEntityEntries !== undefined) { + private _toggleShowHidden() { + this.showHidden = !this.showHidden; + if (!this.showHidden || this._extDisabledEntityEntries !== undefined) { return; } this._extDisabledEntityEntries = {}; diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 7512b3067f..54d926b978 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -557,7 +557,7 @@ export class HaConfigDevicePage extends LitElement { )} .deviceName=${deviceName} .entities=${entitiesByCategory[category]} - .showDisabled=${device.disabled_by !== null} + .showHidden=${device.disabled_by !== null} > ` diff --git a/src/panels/config/entities/entity-registry-basic-editor.ts b/src/panels/config/entities/entity-registry-basic-editor.ts index 52240794b3..10d3bdf994 100644 --- a/src/panels/config/entities/entity-registry-basic-editor.ts +++ b/src/panels/config/entities/entity-registry-basic-editor.ts @@ -1,3 +1,5 @@ +import "../../../components/ha-expansion-panel"; +import "@material/mwc-formfield/mwc-formfield"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -5,7 +7,7 @@ import { computeDomain } from "../../../common/entity/compute_domain"; import "../../../components/ha-area-picker"; import "../../../components/ha-switch"; import "../../../components/ha-textfield"; -import type { HaSwitch } from "../../../components/ha-switch"; +import "../../../components/ha-radio"; import { DeviceRegistryEntry, subscribeDeviceRegistry, @@ -33,6 +35,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { @state() private _disabledBy!: string | null; + @state() private _hiddenBy!: string | null; + private _deviceLookup?: Record; @state() private _device?: DeviceRegistryEntry; @@ -51,6 +55,12 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { ) { params.disabled_by = this._disabledBy; } + if ( + this.entry.hidden_by !== this._hiddenBy && + (this._hiddenBy === null || this._hiddenBy === "user") + ) { + params.hidden_by = this._hiddenBy; + } try { const result = await updateEntityRegistryEntry( this.hass!, @@ -101,6 +111,7 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { this._origEntityId = this.entry.entity_id; this._entityId = this.entry.entity_id; this._disabledBy = this.entry.disabled_by; + this._hiddenBy = this.entry.hidden_by; this._areaId = this.entry.area_id; this._device = this.entry.device_id && this._deviceLookup @@ -138,37 +149,95 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { .placeholder=${this._device?.area_id} @value-changed=${this._areaPicked} > -
- - -
-
- ${this.hass.localize( + + +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.entity_status" + )}: +
+
+ ${this._disabledBy && this._disabledBy !== "user" + ? this.hass.localize( + "ui.dialogs.entity_registry.editor.enabled_cause", + "cause", + this.hass.localize( + `config_entry.disabled_by.${this._disabledBy}` + ) + ) + : ""} +
+
+ -
- ${this._disabledBy && this._disabledBy !== "user" - ? this.hass.localize( - "ui.dialogs.entity_registry.editor.enabled_cause", - "cause", - this.hass.localize( - `config_entry.disabled_by.${this._disabledBy}` - ) - ) - : ""} - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.enabled_description" + > + + + ${this.hass.localize( - "ui.dialogs.entity_registry.editor.note" + > + + + + > + +
-
+ + ${this._disabledBy !== null + ? html` +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.enabled_description" + )} +
+ ` + : this._hiddenBy !== null + ? html` +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.hidden_description" + )} +
+ ` + : ""} +
`; } @@ -180,8 +249,21 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { this._entityId = ev.target.value; } - private _disabledByChanged(ev: Event): void { - this._disabledBy = (ev.target as HaSwitch).checked ? null : "user"; + private _viewStatusChanged(ev: CustomEvent): void { + switch ((ev.target as any).value) { + case "enabled": + this._disabledBy = null; + this._hiddenBy = null; + break; + case "disabled": + this._disabledBy = "user"; + this._hiddenBy = null; + break; + case "hidden": + this._hiddenBy = "user"; + this._disabledBy = null; + break; + } } static get styles() { @@ -202,6 +284,12 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { display: block; margin-bottom: 8px; } + ha-expansion-panel { + margin-top: 8px; + } + .label { + margin-top: 16px; + } `; } } diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 542a730d32..c4278a7936 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1,3 +1,5 @@ +import "@material/mwc-formfield/mwc-formfield"; +import "../../../components/ha-radio"; import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; @@ -20,7 +22,6 @@ import "../../../components/ha-expansion-panel"; import "../../../components/ha-icon-picker"; import "../../../components/ha-select"; import "../../../components/ha-switch"; -import type { HaSwitch } from "../../../components/ha-switch"; import "../../../components/ha-textfield"; import { DeviceRegistryEntry, @@ -76,6 +77,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { @state() private _disabledBy!: string | null; + @state() private _hiddenBy!: string | null; + private _deviceLookup?: Record; @state() private _device?: DeviceRegistryEntry; @@ -112,6 +115,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { this._areaId = this.entry.area_id; this._entityId = this.entry.entity_id; this._disabledBy = this.entry.disabled_by; + this._hiddenBy = this.entry.hidden_by; this._device = this.entry.device_id && this._deviceLookup ? this._deviceLookup[this.entry.device_id] @@ -211,75 +215,126 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { @value-changed=${this._areaPicked} >` : ""} -
- - -
-
- ${this.hass.localize( + +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.entity_status" + )}: +
+
+ ${this._disabledBy && this._disabledBy !== "user" + ? this.hass.localize( + "ui.dialogs.entity_registry.editor.enabled_cause", + "cause", + this.hass.localize( + `config_entry.disabled_by.${this._disabledBy}` + ) + ) + : ""} +
+
+ -
- ${this._disabledBy && this._disabledBy !== "user" - ? this.hass.localize( - "ui.dialogs.entity_registry.editor.enabled_cause", - "cause", - this.hass.localize( - `config_entry.disabled_by.${this._disabledBy}` - ) - ) - : ""} - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.enabled_description" - )} -
${this.hass.localize( - "ui.dialogs.entity_registry.editor.note" - )} -
-
-
- - ${this.entry.device_id - ? html` -

- ${this.hass.localize( - "ui.dialogs.entity_registry.editor.area_note" - )} -

- ${this._areaId - ? html`${this.hass.localize( - "ui.dialogs.entity_registry.editor.follow_device_area" - )}` - : this._device - ? html`${this.hass.localize( - "ui.dialogs.entity_registry.editor.change_device_area" - )}` - : ""} -
` - : ""} + + + + + + + + +
+ + ${this._disabledBy !== null + ? html` +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.enabled_description" + )} +
+ ` + : this._hiddenBy !== null + ? html` +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.hidden_description" + )} +
+ ` + : ""} + ${this.entry.device_id + ? html` +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.change_area" + )}: +
+ +
+ ${this.hass.localize( + "ui.dialogs.entity_registry.editor.area_note" + )} + ${this._device + ? html` + + ` + : ""} +
+ ` + : ""} +
- entity.unavailable || entity.disabled_by || entity.readonly + entity.unavailable || + entity.disabled_by || + entity.hidden_by || + entity.readonly ? html`
@@ -280,6 +288,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ? this.hass.localize( "ui.panel.config.entities.picker.status.disabled" ) + : entity.hidden_by + ? this.hass.localize( + "ui.panel.config.entities.picker.status.hidden" + ) : this.hass.localize( "ui.panel.config.entities.picker.status.readonly" )} @@ -301,6 +313,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { showDisabled: boolean, showUnavailable: boolean, showReadOnly: boolean, + showHidden: boolean, entries?: ConfigEntry[] ) => { const result: EntityRow[] = []; @@ -362,6 +375,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ); } + if (!showHidden) { + filteredEntities = filteredEntities.filter( + (entity) => !entity.hidden_by + ); + } + for (const entry of filteredEntities) { const entity = this.hass.states[entry.entity_id]; const unavailable = entity?.state === UNAVAILABLE; @@ -465,6 +484,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { this._showDisabled, this._showUnavailable, this._showReadOnly, + this._showHidden, this._entries ); @@ -533,6 +553,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { "ui.panel.config.entities.picker.disable_selected.button" )} + ${this.hass.localize( + "ui.panel.config.entities.picker.hide_selected.button" + )} ${this.hass.localize( "ui.panel.config.entities.picker.remove_selected.button" @@ -562,6 +587,17 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { "ui.panel.config.entities.picker.disable_selected.button" )} + + + ${this.hass.localize( + "ui.panel.config.entities.picker.hide_selected.button" + )} + + + ${this.hass!.localize( + "ui.panel.config.entities.picker.filter.show_hidden" + )} + ) { + if (ev.detail.source !== "property") { + return; + } + this._showHidden = ev.detail.selected; + } + private _showRestoredChanged(ev: CustomEvent) { if (ev.detail.source !== "property") { return; @@ -791,6 +844,29 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { }); } + private _hideSelected() { + showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.config.entities.picker.hide_selected.confirm_title", + "number", + this._selectedEntities.length + ), + text: this.hass.localize( + "ui.panel.config.entities.picker.hide_selected.confirm_text" + ), + confirmText: this.hass.localize("ui.common.hide"), + dismissText: this.hass.localize("ui.common.cancel"), + confirm: () => { + this._selectedEntities.forEach((entity) => + updateEntityRegistryEntry(this.hass, entity, { + hidden_by: "user", + }) + ); + this._clearSelection(); + }, + }); + } + private _removeSelected() { const removeableEntities = this._selectedEntities.filter((entity) => { const stateObj = this.hass.states[entity]; diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 0d3fd0cb9a..8ffdd43709 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -238,7 +238,10 @@ const computeDefaultViewStates = ( const hiddenEntities = new Set( entityEntries .filter( - (entry) => entry.entity_category || HIDE_PLATFORM.has(entry.platform) + (entry) => + entry.entity_category || + HIDE_PLATFORM.has(entry.platform) || + entry.hidden_by ) .map((entry) => entry.entity_id) ); diff --git a/src/translations/en.json b/src/translations/en.json index 45661e7f8e..7b597b64c7 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -102,6 +102,11 @@ "integration": "Integration", "config_entry": "Config entry", "device": "Device" + }, + "hidden_by": { + "user": "User", + "integration": "Integration", + "device": "Device" } }, "ui": { @@ -292,6 +297,7 @@ "remove": "Remove", "enable": "Enable", "disable": "Disable", + "hide": "Hide", "close": "Close", "clear": "Clear", "leave": "Leave", @@ -783,13 +789,19 @@ } }, "unavailable": "This entity is unavailable.", - "enabled_label": "Enable entity", - "enabled_cause": "Disabled by {cause}.", + "entity_status": "Entity status", + "change_area": "Change Area", + "enabled_label": "Enabled", + "disabled_label": "Disabled", + "enabled_cause": "Cannot change status. Disabled by {cause}.", + "hidden_label": "Hidden", + "hidden_cause": "Hidden by {cause}.", "device_disabled": "The device of this entity is disabled.", "open_device_settings": "Open device settings", "enabled_description": "Disabled entities will not be added to Home Assistant.", "enabled_delay_confirm": "The enabled entities will be added to Home Assistant in {delay} seconds", "enabled_restart_confirm": "Restart Home Assistant to finish enabling the entities", + "hidden_description": "Hidden entities will not be shown on your dashboard. Their history is still tracked and you can still interact with them with services.", "delete": "Delete", "confirm_delete": "Are you sure you want to delete this entity?", "update": "Update", @@ -2378,8 +2390,8 @@ "config": "Configuration", "add_entities_lovelace": "Add to dashboard", "none": "This device has no entities", - "hide_disabled": "Hide disabled", - "disabled_entities": "+{count} {count, plural,\n one {disabled entity}\n other {disabled entities}\n}" + "show_less": "Show less", + "hidden_entities": "+{count} {count, plural,\n one {hidden entity}\n other {hidden entities}\n}" }, "confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?", "confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to update them yourself to use the new entity IDs!", @@ -2419,6 +2431,7 @@ "search": "Search entities", "filter": { "filter": "Filter", + "show_hidden": "Show hidden entities", "show_disabled": "Show disabled entities", "show_unavailable": "Show unavailable entities", "show_readonly": "Show read-only entities", @@ -2430,6 +2443,7 @@ "unavailable": "Unavailable", "disabled": "Disabled", "readonly": "Read-only", + "hidden": "Hidden", "ok": "Ok" }, "headers": { @@ -2458,6 +2472,11 @@ "confirm_partly_title": "Only {number} {number, plural,\n one {selected entity}\n other {selected entities}\n} can be removed.", "confirm_text": "You should remove them from your dashboard config and automations if they contain these entities.", "confirm_partly_text": "You can only remove {removable} of the selected {selected} entities. Entities can only be removed when the integration is no longer providing the entities. Sometimes you have to restart Home Assistant before you can remove the entities of a removed integration. Are you sure you want to remove the removable entities?" + }, + "hide_selected": { + "button": "Hide selected", + "confirm_title": "Do you want to hide {number} {number, plural,\n one {entity}\n other {entities}\n}?", + "confirm_text": "Hidden entities will not be shown on your dashboard. Their history is still tracked and you can still interact with them with services." } } },