Merge pull request #12049 from home-assistant/hide-hidden-entities

This commit is contained in:
Zack Barett 2022-03-16 17:49:20 -05:00 committed by GitHub
commit 4fbe9a7b10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 440 additions and 174 deletions

View File

@ -188,6 +188,7 @@ const createEntityRegistryEntries = (
device_id: "mock-device-id", device_id: "mock-device-id",
area_id: null, area_id: null,
disabled_by: null, disabled_by: null,
hidden_by: null,
entity_category: null, entity_category: null,
entity_id: "binary_sensor.updater", entity_id: "binary_sensor.updater",
name: null, name: null,

View File

@ -14,6 +14,7 @@ export interface EntityRegistryEntry {
device_id: string | null; device_id: string | null;
area_id: string | null; area_id: string | null;
disabled_by: string | null; disabled_by: string | null;
hidden_by: string | null;
entity_category: "config" | "diagnostic" | null; entity_category: "config" | "diagnostic" | null;
} }
@ -38,6 +39,7 @@ export interface EntityRegistryEntryUpdateParams {
device_class?: string | null; device_class?: string | null;
area_id?: string | null; area_id?: string | null;
disabled_by?: string | null; disabled_by?: string | null;
hidden_by: string | null;
new_entity_id?: string; new_entity_id?: string;
} }

View File

@ -40,7 +40,7 @@ export class HaDeviceEntitiesCard extends LitElement {
@property() public entities!: EntityRegistryStateEntry[]; @property() public entities!: EntityRegistryStateEntry[];
@property() public showDisabled = false; @property() public showHidden = false;
@state() private _extDisabledEntityEntries?: Record< @state() private _extDisabledEntityEntries?: Record<
string, string,
@ -60,77 +60,77 @@ export class HaDeviceEntitiesCard extends LitElement {
} }
protected render(): TemplateResult { protected render(): TemplateResult {
const disabledEntities: EntityRegistryStateEntry[] = []; if (!this.entities.length) {
return html`
<ha-card .header=${this.header}>
<div class="empty card-content">
${this.hass.localize("ui.panel.config.devices.entities.none")}
</div>
</ha-card>
`;
}
const shownEntities: EntityRegistryStateEntry[] = [];
const hiddenEntities: EntityRegistryStateEntry[] = [];
this._entityRows = []; 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` return html`
<ha-card .header=${this.header}> <ha-card .header=${this.header}>
${this.entities.length <div id="entities" @hass-more-info=${this._overrideMoreInfo}>
? html` ${shownEntities.map((entry) =>
<div id="entities" @hass-more-info=${this._overrideMoreInfo}> this.hass.states[entry.entity_id]
${this.entities.map((entry: EntityRegistryStateEntry) => { ? this._renderEntity(entry)
if (entry.disabled_by) { : this._renderEntry(entry)
if (this._extDisabledEntityEntries) { )}
disabledEntities.push( </div>
this._extDisabledEntityEntries[entry.entity_id] || entry ${hiddenEntities.length
); ? !this.showHidden
} else { ? html`
disabledEntities.push(entry); <button class="show-more" @click=${this._toggleShowHidden}>
}
return "";
}
return this.hass.states[entry.entity_id]
? this._renderEntity(entry)
: this._renderEntry(entry);
})}
</div>
${disabledEntities.length
? !this.showDisabled
? html`
<button
class="show-more"
@click=${this._toggleShowDisabled}
>
${this.hass.localize(
"ui.panel.config.devices.entities.disabled_entities",
"count",
disabledEntities.length
)}
</button>
`
: html`
${disabledEntities.map((entry) =>
this._renderEntry(entry)
)}
<button
class="show-more"
@click=${this._toggleShowDisabled}
>
${this.hass.localize(
"ui.panel.config.devices.entities.hide_disabled"
)}
</button>
`
: ""}
<div class="card-actions">
<mwc-button @click=${this._addToLovelaceView}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.devices.entities.add_entities_lovelace" "ui.panel.config.devices.entities.hidden_entities",
"count",
hiddenEntities.length
)} )}
</mwc-button> </button>
</div> `
` : html`
: html` ${hiddenEntities.map((entry) => this._renderEntry(entry))}
<div class="empty card-content"> <button class="show-more" @click=${this._toggleShowHidden}>
${this.hass.localize("ui.panel.config.devices.entities.none")} ${this.hass.localize(
</div> "ui.panel.config.devices.entities.hide_disabled"
`} )}
</button>
`
: ""}
<div class="card-actions">
<mwc-button @click=${this._addToLovelaceView}>
${this.hass.localize(
"ui.panel.config.devices.entities.add_entities_lovelace"
)}
</mwc-button>
</div>
</ha-card> </ha-card>
`; `;
} }
private _toggleShowDisabled() { private _toggleShowHidden() {
this.showDisabled = !this.showDisabled; this.showHidden = !this.showHidden;
if (!this.showDisabled || this._extDisabledEntityEntries !== undefined) { if (!this.showHidden || this._extDisabledEntityEntries !== undefined) {
return; return;
} }
this._extDisabledEntityEntries = {}; this._extDisabledEntityEntries = {};

View File

@ -557,7 +557,7 @@ export class HaConfigDevicePage extends LitElement {
)} )}
.deviceName=${deviceName} .deviceName=${deviceName}
.entities=${entitiesByCategory[category]} .entities=${entitiesByCategory[category]}
.showDisabled=${device.disabled_by !== null} .showHidden=${device.disabled_by !== null}
> >
</ha-device-entities-card> </ha-device-entities-card>
` `

View File

@ -1,3 +1,5 @@
import "../../../components/ha-expansion-panel";
import "@material/mwc-formfield/mwc-formfield";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; 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-area-picker";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import "../../../components/ha-textfield"; import "../../../components/ha-textfield";
import type { HaSwitch } from "../../../components/ha-switch"; import "../../../components/ha-radio";
import { import {
DeviceRegistryEntry, DeviceRegistryEntry,
subscribeDeviceRegistry, subscribeDeviceRegistry,
@ -33,6 +35,8 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
@state() private _disabledBy!: string | null; @state() private _disabledBy!: string | null;
@state() private _hiddenBy!: string | null;
private _deviceLookup?: Record<string, DeviceRegistryEntry>; private _deviceLookup?: Record<string, DeviceRegistryEntry>;
@state() private _device?: DeviceRegistryEntry; @state() private _device?: DeviceRegistryEntry;
@ -51,6 +55,12 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
) { ) {
params.disabled_by = this._disabledBy; params.disabled_by = this._disabledBy;
} }
if (
this.entry.hidden_by !== this._hiddenBy &&
(this._hiddenBy === null || this._hiddenBy === "user")
) {
params.hidden_by = this._hiddenBy;
}
try { try {
const result = await updateEntityRegistryEntry( const result = await updateEntityRegistryEntry(
this.hass!, this.hass!,
@ -101,6 +111,7 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
this._origEntityId = this.entry.entity_id; this._origEntityId = this.entry.entity_id;
this._entityId = this.entry.entity_id; this._entityId = this.entry.entity_id;
this._disabledBy = this.entry.disabled_by; this._disabledBy = this.entry.disabled_by;
this._hiddenBy = this.entry.hidden_by;
this._areaId = this.entry.area_id; this._areaId = this.entry.area_id;
this._device = this._device =
this.entry.device_id && this._deviceLookup this.entry.device_id && this._deviceLookup
@ -138,37 +149,95 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
.placeholder=${this._device?.area_id} .placeholder=${this._device?.area_id}
@value-changed=${this._areaPicked} @value-changed=${this._areaPicked}
></ha-area-picker> ></ha-area-picker>
<div class="row">
<ha-switch <ha-expansion-panel
.checked=${!this._disabledBy} .header=${this.hass.localize(
@change=${this._disabledByChanged} "ui.dialogs.entity_registry.editor.advanced"
> )}
</ha-switch> outlined
<div> >
<div> <div class="label">
${this.hass.localize( ${this.hass.localize(
"ui.dialogs.entity_registry.editor.entity_status"
)}:
</div>
<div class="secondary">
${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}`
)
)
: ""}
</div>
<div class="row">
<mwc-formfield
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.enabled_label" "ui.dialogs.entity_registry.editor.enabled_label"
)} )}
</div> >
<div class="secondary"> <ha-radio
${this._disabledBy && this._disabledBy !== "user" name="hiddendisabled"
? this.hass.localize( value="enabled"
"ui.dialogs.entity_registry.editor.enabled_cause", .checked=${!this._hiddenBy && !this._disabledBy}
"cause", .disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
this.hass.localize( this._device?.disabled_by ||
`config_entry.disabled_by.${this._disabledBy}` (this._disabledBy && this._disabledBy !== "user")}
) @change=${this._viewStatusChanged}
) ></ha-radio>
: ""} </mwc-formfield>
${this.hass.localize( <mwc-formfield
"ui.dialogs.entity_registry.editor.enabled_description" .label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.hidden_label"
)} )}
<br />${this.hass.localize( >
"ui.dialogs.entity_registry.editor.note" <ha-radio
name="hiddendisabled"
value="hidden"
.checked=${this._hiddenBy !== null}
.disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
Boolean(this._device?.disabled_by) ||
(this._disabledBy && this._disabledBy !== "user")}
@change=${this._viewStatusChanged}
></ha-radio>
</mwc-formfield>
<mwc-formfield
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.disabled_label"
)} )}
</div> >
<ha-radio
name="hiddendisabled"
value="disabled"
.checked=${this._disabledBy !== null}
.disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
Boolean(this._device?.disabled_by) ||
(this._disabledBy && this._disabledBy !== "user")}
@change=${this._viewStatusChanged}
></ha-radio>
</mwc-formfield>
</div> </div>
</div>
${this._disabledBy !== null
? html`
<div class="secondary">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.enabled_description"
)}
</div>
`
: this._hiddenBy !== null
? html`
<div class="secondary">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.hidden_description"
)}
</div>
`
: ""}
</ha-expansion-panel>
`; `;
} }
@ -180,8 +249,21 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
this._entityId = ev.target.value; this._entityId = ev.target.value;
} }
private _disabledByChanged(ev: Event): void { private _viewStatusChanged(ev: CustomEvent): void {
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user"; 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() { static get styles() {
@ -202,6 +284,12 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 8px;
} }
ha-expansion-panel {
margin-top: 8px;
}
.label {
margin-top: 16px;
}
`; `;
} }
} }

View File

@ -1,3 +1,5 @@
import "@material/mwc-formfield/mwc-formfield";
import "../../../components/ha-radio";
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; 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-icon-picker";
import "../../../components/ha-select"; import "../../../components/ha-select";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import type { HaSwitch } from "../../../components/ha-switch";
import "../../../components/ha-textfield"; import "../../../components/ha-textfield";
import { import {
DeviceRegistryEntry, DeviceRegistryEntry,
@ -76,6 +77,8 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
@state() private _disabledBy!: string | null; @state() private _disabledBy!: string | null;
@state() private _hiddenBy!: string | null;
private _deviceLookup?: Record<string, DeviceRegistryEntry>; private _deviceLookup?: Record<string, DeviceRegistryEntry>;
@state() private _device?: DeviceRegistryEntry; @state() private _device?: DeviceRegistryEntry;
@ -112,6 +115,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
this._areaId = this.entry.area_id; this._areaId = this.entry.area_id;
this._entityId = this.entry.entity_id; this._entityId = this.entry.entity_id;
this._disabledBy = this.entry.disabled_by; this._disabledBy = this.entry.disabled_by;
this._hiddenBy = this.entry.hidden_by;
this._device = this._device =
this.entry.device_id && this._deviceLookup this.entry.device_id && this._deviceLookup
? this._deviceLookup[this.entry.device_id] ? this._deviceLookup[this.entry.device_id]
@ -211,75 +215,126 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
@value-changed=${this._areaPicked} @value-changed=${this._areaPicked}
></ha-area-picker>` ></ha-area-picker>`
: ""} : ""}
<div class="row"> <ha-expansion-panel
<ha-switch .header=${this.hass.localize(
.checked=${!this._disabledBy} "ui.dialogs.entity_registry.editor.advanced"
.disabled=${this._device?.disabled_by} )}
@change=${this._disabledByChanged} outlined
> >
</ha-switch> <div class="label">
<div> ${this.hass.localize(
<div> "ui.dialogs.entity_registry.editor.entity_status"
${this.hass.localize( )}:
</div>
<div class="secondary">
${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}`
)
)
: ""}
</div>
<div class="row">
<mwc-formfield
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.enabled_label" "ui.dialogs.entity_registry.editor.enabled_label"
)} )}
</div>
<div class="secondary">
${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"
)}
<br />${this.hass.localize(
"ui.dialogs.entity_registry.editor.note"
)}
</div>
</div>
</div>
${this.entry.device_id
? html`<ha-expansion-panel
.header=${this.hass.localize(
"ui.dialogs.entity_registry.editor.advanced"
)}
outlined
> >
<p> <ha-radio
${this.hass.localize( name="hiddendisabled"
"ui.dialogs.entity_registry.editor.area_note" value="enabled"
)} .checked=${!this._hiddenBy && !this._disabledBy}
</p> .disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
${this._areaId this._device?.disabled_by ||
? html`<mwc-button @click=${this._clearArea} (this._disabledBy && this._disabledBy !== "user")}
>${this.hass.localize( @change=${this._viewStatusChanged}
"ui.dialogs.entity_registry.editor.follow_device_area" ></ha-radio>
)}</mwc-button </mwc-formfield>
>` <mwc-formfield
: this._device .label=${this.hass.localize(
? html`<mwc-button @click=${this._openDeviceSettings} "ui.dialogs.entity_registry.editor.hidden_label"
>${this.hass.localize( )}
"ui.dialogs.entity_registry.editor.change_device_area" >
)}</mwc-button <ha-radio
>` name="hiddendisabled"
: ""} value="hidden"
<ha-area-picker .checked=${this._hiddenBy !== null}
.hass=${this.hass} .disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
.value=${this._areaId} Boolean(this._device?.disabled_by) ||
.placeholder=${this._device?.area_id} (this._disabledBy && this._disabledBy !== "user")}
.label=${this.hass.localize( @change=${this._viewStatusChanged}
"ui.dialogs.entity_registry.editor.area" ></ha-radio>
)} </mwc-formfield>
@value-changed=${this._areaPicked} <mwc-formfield
></ha-area-picker .label=${this.hass.localize(
></ha-expansion-panel>` "ui.dialogs.entity_registry.editor.disabled_label"
: ""} )}
>
<ha-radio
name="hiddendisabled"
value="disabled"
.checked=${this._disabledBy !== null}
.disabled=${(this._hiddenBy && this._hiddenBy !== "user") ||
Boolean(this._device?.disabled_by) ||
(this._disabledBy && this._disabledBy !== "user")}
@change=${this._viewStatusChanged}
></ha-radio>
</mwc-formfield>
</div>
${this._disabledBy !== null
? html`
<div class="secondary">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.enabled_description"
)}
</div>
`
: this._hiddenBy !== null
? html`
<div class="secondary">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.hidden_description"
)}
</div>
`
: ""}
${this.entry.device_id
? html`
<div class="label">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.change_area"
)}:
</div>
<ha-area-picker
.hass=${this.hass}
.value=${this._areaId}
.placeholder=${this._device?.area_id}
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.area"
)}
@value-changed=${this._areaPicked}
></ha-area-picker>
<div class="secondary">
${this.hass.localize(
"ui.dialogs.entity_registry.editor.area_note"
)}
${this._device
? html`
<button class="link" @click=${this._openDeviceSettings}>
${this.hass.localize(
"ui.dialogs.entity_registry.editor.change_device_area"
)}
</button>
`
: ""}
</div>
`
: ""}
</ha-expansion-panel>
</div> </div>
<div class="buttons"> <div class="buttons">
<mwc-button <mwc-button
@ -325,9 +380,21 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
this._areaId = ev.detail.value; this._areaId = ev.detail.value;
} }
private _clearArea() { private _viewStatusChanged(ev: CustomEvent): void {
this._error = undefined; switch ((ev.target as any).value) {
this._areaId = null; 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;
}
} }
private _openDeviceSettings() { private _openDeviceSettings() {
@ -354,6 +421,12 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
) { ) {
params.disabled_by = this._disabledBy; params.disabled_by = this._disabledBy;
} }
if (
this.entry.hidden_by !== this._hiddenBy &&
(this._hiddenBy === null || this._hiddenBy === "user")
) {
params.hidden_by = this._hiddenBy;
}
try { try {
const result = await updateEntityRegistryEntry( const result = await updateEntityRegistryEntry(
this.hass!, this.hass!,
@ -405,10 +478,6 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
} }
} }
private _disabledByChanged(ev: Event): void {
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
@ -446,14 +515,22 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
display: block; display: block;
margin: 8px 0; margin: 8px 0;
} }
ha-area-picker {
margin: 8px 0;
display: block;
}
.row { .row {
margin: 8px 0; margin: 8px 0;
color: var(--primary-text-color); color: var(--primary-text-color);
display: flex; display: flex;
align-items: center; align-items: center;
} }
p { .label {
margin-top: 16px;
}
.secondary {
margin: 8px 0; margin: 8px 0;
width: 340px;
} }
`, `,
]; ];

View File

@ -3,6 +3,7 @@ import {
mdiAlertCircle, mdiAlertCircle,
mdiCancel, mdiCancel,
mdiDelete, mdiDelete,
mdiEyeOff,
mdiFilterVariant, mdiFilterVariant,
mdiPencilOff, mdiPencilOff,
mdiPlus, mdiPlus,
@ -101,6 +102,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
@state() private _showDisabled = false; @state() private _showDisabled = false;
@state() private _showHidden = false;
@state() private _showUnavailable = true; @state() private _showUnavailable = true;
@state() private _showReadOnly = true; @state() private _showReadOnly = true;
@ -249,7 +252,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
filterable: true, filterable: true,
width: "68px", width: "68px",
template: (_status, entity: EntityRow) => template: (_status, entity: EntityRow) =>
entity.unavailable || entity.disabled_by || entity.readonly entity.unavailable ||
entity.disabled_by ||
entity.hidden_by ||
entity.readonly
? html` ? html`
<div <div
tabindex="0" tabindex="0"
@ -265,6 +271,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
? mdiAlertCircle ? mdiAlertCircle
: entity.disabled_by : entity.disabled_by
? mdiCancel ? mdiCancel
: entity.hidden_by
? mdiEyeOff
: mdiPencilOff} : mdiPencilOff}
></ha-svg-icon> ></ha-svg-icon>
<paper-tooltip animation-delay="0" position="left"> <paper-tooltip animation-delay="0" position="left">
@ -280,6 +288,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.disabled" "ui.panel.config.entities.picker.status.disabled"
) )
: entity.hidden_by
? this.hass.localize(
"ui.panel.config.entities.picker.status.hidden"
)
: this.hass.localize( : this.hass.localize(
"ui.panel.config.entities.picker.status.readonly" "ui.panel.config.entities.picker.status.readonly"
)} )}
@ -301,6 +313,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
showDisabled: boolean, showDisabled: boolean,
showUnavailable: boolean, showUnavailable: boolean,
showReadOnly: boolean, showReadOnly: boolean,
showHidden: boolean,
entries?: ConfigEntry[] entries?: ConfigEntry[]
) => { ) => {
const result: EntityRow[] = []; 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) { for (const entry of filteredEntities) {
const entity = this.hass.states[entry.entity_id]; const entity = this.hass.states[entry.entity_id];
const unavailable = entity?.state === UNAVAILABLE; const unavailable = entity?.state === UNAVAILABLE;
@ -465,6 +484,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
this._showDisabled, this._showDisabled,
this._showUnavailable, this._showUnavailable,
this._showReadOnly, this._showReadOnly,
this._showHidden,
this._entries this._entries
); );
@ -533,6 +553,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.disable_selected.button" "ui.panel.config.entities.picker.disable_selected.button"
)}</mwc-button )}</mwc-button
> >
<mwc-button @click=${this._hideSelected}
>${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button"
)}</mwc-button
>
<mwc-button @click=${this._removeSelected} class="warning" <mwc-button @click=${this._removeSelected} class="warning"
>${this.hass.localize( >${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button" "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" "ui.panel.config.entities.picker.disable_selected.button"
)} )}
</paper-tooltip> </paper-tooltip>
<ha-icon-button
id="hide-btn"
@click=${this._hideSelected}
.path=${mdiCancel}
.label=${this.hass.localize("ui.common.hide")}
></ha-icon-button>
<paper-tooltip animation-delay="0" for="hide-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button"
)}
</paper-tooltip>
<ha-icon-button <ha-icon-button
class="warning" class="warning"
id="remove-btn" id="remove-btn"
@ -603,6 +639,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.filter.show_disabled" "ui.panel.config.entities.picker.filter.show_disabled"
)} )}
</ha-check-list-item> </ha-check-list-item>
<ha-check-list-item
@request-selected=${this._showHiddenChanged}
.selected=${this._showHidden}
left
>
${this.hass!.localize(
"ui.panel.config.entities.picker.filter.show_hidden"
)}
</ha-check-list-item>
<ha-check-list-item <ha-check-list-item
@request-selected=${this._showRestoredChanged} @request-selected=${this._showRestoredChanged}
graphic="control" graphic="control"
@ -671,6 +716,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
entity_id: entityId, entity_id: entityId,
platform: computeDomain(entityId), platform: computeDomain(entityId),
disabled_by: null, disabled_by: null,
hidden_by: null,
area_id: null, area_id: null,
config_entry_id: null, config_entry_id: null,
device_id: null, device_id: null,
@ -693,6 +739,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
this._showDisabled = ev.detail.selected; this._showDisabled = ev.detail.selected;
} }
private _showHiddenChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") {
return;
}
this._showHidden = ev.detail.selected;
}
private _showRestoredChanged(ev: CustomEvent<RequestSelectedDetail>) { private _showRestoredChanged(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "property") { if (ev.detail.source !== "property") {
return; 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() { private _removeSelected() {
const removeableEntities = this._selectedEntities.filter((entity) => { const removeableEntities = this._selectedEntities.filter((entity) => {
const stateObj = this.hass.states[entity]; const stateObj = this.hass.states[entity];

View File

@ -238,7 +238,10 @@ const computeDefaultViewStates = (
const hiddenEntities = new Set( const hiddenEntities = new Set(
entityEntries entityEntries
.filter( .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) .map((entry) => entry.entity_id)
); );

View File

@ -102,6 +102,11 @@
"integration": "Integration", "integration": "Integration",
"config_entry": "Config entry", "config_entry": "Config entry",
"device": "Device" "device": "Device"
},
"hidden_by": {
"user": "User",
"integration": "Integration",
"device": "Device"
} }
}, },
"ui": { "ui": {
@ -292,6 +297,7 @@
"remove": "Remove", "remove": "Remove",
"enable": "Enable", "enable": "Enable",
"disable": "Disable", "disable": "Disable",
"hide": "Hide",
"close": "Close", "close": "Close",
"clear": "Clear", "clear": "Clear",
"leave": "Leave", "leave": "Leave",
@ -783,13 +789,19 @@
} }
}, },
"unavailable": "This entity is unavailable.", "unavailable": "This entity is unavailable.",
"enabled_label": "Enable entity", "entity_status": "Entity status",
"enabled_cause": "Disabled by {cause}.", "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.", "device_disabled": "The device of this entity is disabled.",
"open_device_settings": "Open device settings", "open_device_settings": "Open device settings",
"enabled_description": "Disabled entities will not be added to Home Assistant.", "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_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", "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", "delete": "Delete",
"confirm_delete": "Are you sure you want to delete this entity?", "confirm_delete": "Are you sure you want to delete this entity?",
"update": "Update", "update": "Update",
@ -2378,8 +2390,8 @@
"config": "Configuration", "config": "Configuration",
"add_entities_lovelace": "Add to dashboard", "add_entities_lovelace": "Add to dashboard",
"none": "This device has no entities", "none": "This device has no entities",
"hide_disabled": "Hide disabled", "show_less": "Show less",
"disabled_entities": "+{count} {count, plural,\n one {disabled entity}\n other {disabled entities}\n}" "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": "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!", "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", "search": "Search entities",
"filter": { "filter": {
"filter": "Filter", "filter": "Filter",
"show_hidden": "Show hidden entities",
"show_disabled": "Show disabled entities", "show_disabled": "Show disabled entities",
"show_unavailable": "Show unavailable entities", "show_unavailable": "Show unavailable entities",
"show_readonly": "Show read-only entities", "show_readonly": "Show read-only entities",
@ -2430,6 +2443,7 @@
"unavailable": "Unavailable", "unavailable": "Unavailable",
"disabled": "Disabled", "disabled": "Disabled",
"readonly": "Read-only", "readonly": "Read-only",
"hidden": "Hidden",
"ok": "Ok" "ok": "Ok"
}, },
"headers": { "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_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_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?" "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."
} }
} }
}, },