Update multi select of entities config (#20319)

* Update multi select of entities config

* Update ha-config-entities.ts
This commit is contained in:
Bram Kragten 2024-04-02 15:16:20 +02:00 committed by GitHub
parent cbb08c6202
commit 8a015f4e38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 208 additions and 112 deletions

View File

@ -3,12 +3,17 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { import {
mdiAlertCircle, mdiAlertCircle,
mdiCancel, mdiCancel,
mdiChevronRight,
mdiDelete, mdiDelete,
mdiDotsVertical,
mdiEye,
mdiEyeOff, mdiEyeOff,
mdiMenuDown,
mdiPencilOff, mdiPencilOff,
mdiPlus, mdiPlus,
mdiRestoreAlert, mdiRestoreAlert,
mdiUndo, mdiToggleSwitch,
mdiToggleSwitchOffOutline,
} from "@mdi/js"; } from "@mdi/js";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
@ -24,6 +29,7 @@ import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { until } from "lit/directives/until"; import { until } from "lit/directives/until";
import memoize from "memoize-one"; import memoize from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import type { HASSDomEvent } from "../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
@ -44,16 +50,19 @@ import "../../../components/ha-check-list-item";
import "../../../components/ha-filter-devices"; import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-floor-areas"; import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-integrations"; import "../../../components/ha-filter-integrations";
import "../../../components/ha-filter-states";
import "../../../components/ha-filter-labels"; import "../../../components/ha-filter-labels";
import "../../../components/ha-filter-states";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-menu-item";
import "../../../components/ha-sub-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context"; import { fullEntitiesContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { import {
EntityRegistryEntry, EntityRegistryEntry,
UpdateEntityRegistryEntryResult,
computeEntityRegistryName, computeEntityRegistryName,
removeEntityRegistryEntry, removeEntityRegistryEntry,
updateEntityRegistryEntry, updateEntityRegistryEntry,
@ -505,13 +514,28 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
[...filteredDomains][0] [...filteredDomains][0]
); );
const labelItems = html` ${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
return html`<ha-menu-item
.value=${label.label_id}
@click=${this._handleBulkLabel}
>
<ha-label style=${color ? `--color: ${color}` : ""}>
${label.icon
? html`<ha-icon slot="icon" .icon=${label.icon}></ha-icon>`
: nothing}
${label.name}
</ha-label>
</ha-menu-item>`;
})}`;
return html` return html`
<hass-tabs-subpage-data-table <hass-tabs-subpage-data-table
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.backPath=${this._searchParms.has("historyBack") .backPath=${
? undefined this._searchParms.has("historyBack") ? undefined : "/config"
: "/config"} }
.route=${this.route} .route=${this.route}
.tabs=${configSections.devices} .tabs=${configSections.devices}
.columns=${this._columns( .columns=${this._columns(
@ -524,9 +548,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.search" "ui.panel.config.entities.picker.search"
)} )}
hasFilters hasFilters
.filters=${Object.values(this._filters).filter( .filters=${
(filter) => filter.value?.length Object.values(this._filters).filter((filter) => filter.value?.length)
).length} .length
}
.filter=${this._filter} .filter=${this._filter}
selectable selectable
.selected=${this._selectedEntities.length} .selected=${this._selectedEntities.length}
@ -543,100 +568,131 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
.hass=${this.hass} .hass=${this.hass}
slot="toolbar-icon" slot="toolbar-icon"
></ha-integration-overflow-menu> ></ha-integration-overflow-menu>
<div class="header-btns" slot="selection-bar">
${!this.narrow
? html` ${
<mwc-button !this.narrow
@click=${this._enableSelected} ? html`<ha-button-menu-new slot="selection-bar">
.disabled=${!this._selectedEntities.length} <ha-assist-chip
>${this.hass.localize( slot="trigger"
"ui.panel.config.entities.picker.enable_selected.button" .label=${this.hass.localize(
)}</mwc-button "ui.panel.config.automation.picker.bulk_actions.add_label"
> )}
<mwc-button >
@click=${this._disableSelected} <ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon>
.disabled=${!this._selectedEntities.length} </ha-assist-chip>
>${this.hass.localize( ${labelItems}
"ui.panel.config.entities.picker.disable_selected.button" </ha-button-menu-new>`
)}</mwc-button : nothing
> }
<mwc-button <ha-button-menu-new has-overflow slot="selection-bar">
@click=${this._hideSelected} ${
.disabled=${!this._selectedEntities.length} this.narrow
>${this.hass.localize( ? html`<ha-assist-chip
"ui.panel.config.entities.picker.hide_selected.button" .label=${this.hass.localize(
)}</mwc-button "ui.panel.config.automation.picker.bulk_action"
> )}
<mwc-button slot="trigger"
@click=${this._removeSelected} >
.disabled=${!this._selectedEntities.length} <ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon>
class="warning" </ha-assist-chip>`
>${this.hass.localize( : html`<ha-icon-button
"ui.panel.config.entities.picker.remove_selected.button" .path=${mdiDotsVertical}
)}</mwc-button .label=${"ui.panel.config.automation.picker.bulk_action"}
> slot="trigger"
` ></ha-icon-button>`
: html` }
<ha-icon-button <ha-svg-icon
id="enable-btn" slot="trailing-icon"
.disabled=${!this._selectedEntities.length} .path=${mdiMenuDown}
@click=${this._enableSelected} ></ha-svg-icon
.path=${mdiUndo} ></ha-assist-chip>
.label=${this.hass.localize("ui.common.enable")} ${
></ha-icon-button> this.narrow
<simple-tooltip animation-delay="0" for="enable-btn"> ? html`<ha-sub-menu>
${this.hass.localize( <ha-menu-item slot="item">
"ui.panel.config.entities.picker.enable_selected.button" <div slot="headline">
)} ${this.hass.localize(
</simple-tooltip> "ui.panel.config.automation.picker.bulk_actions.add_label"
<ha-icon-button )}
id="disable-btn" </div>
.disabled=${!this._selectedEntities.length} <ha-svg-icon slot="end" .path=${mdiChevronRight}></ha-svg-icon>
@click=${this._disableSelected} </ha-menu-item>
.path=${mdiCancel} <ha-menu slot="menu">${labelItems}</ha-menu>
.label=${this.hass.localize("ui.common.disable")} </ha-sub-menu>
></ha-icon-button> <md-divider role="separator" tabindex="-1"></md-divider>`
<simple-tooltip animation-delay="0" for="disable-btn"> : nothing
${this.hass.localize( }
"ui.panel.config.entities.picker.disable_selected.button"
)} <ha-menu-item @click=${this._enableSelected}>
</simple-tooltip> <ha-svg-icon slot="start" .path=${mdiToggleSwitch}></ha-svg-icon>
<ha-icon-button <div slot="headline">
id="hide-btn" ${this.hass.localize(
.disabled=${!this._selectedEntities.length} "ui.panel.config.entities.picker.enable_selected.button"
@click=${this._hideSelected} )}
.path=${mdiEyeOff} </div>
.label=${this.hass.localize("ui.common.hide")} </ha-menu-item>
></ha-icon-button> <ha-menu-item @click=${this._disableSelected}>
<simple-tooltip animation-delay="0" for="hide-btn"> <ha-svg-icon
${this.hass.localize( slot="start"
"ui.panel.config.entities.picker.hide_selected.button" .path=${mdiToggleSwitchOffOutline}
)} ></ha-svg-icon>
</simple-tooltip> <div slot="headline">
<ha-icon-button ${this.hass.localize(
class="warning" "ui.panel.config.entities.picker.disable_selected.button"
id="remove-btn" )}
.disabled=${!this._selectedEntities.length} </div>
@click=${this._removeSelected} </ha-menu-item>
.path=${mdiDelete} <md-divider role="separator" tabindex="-1"></md-divider>
.label=${this.hass.localize("ui.common.remove")}
></ha-icon-button> <ha-menu-item @click=${this._unhideSelected}>
<simple-tooltip animation-delay="0" for="remove-btn"> <ha-svg-icon
${this.hass.localize( slot="start"
"ui.panel.config.entities.picker.remove_selected.button" .path=${mdiEye}
)} ></ha-svg-icon>
</simple-tooltip> <div slot="headline">
`} ${this.hass.localize(
</div> "ui.panel.config.entities.picker.unhide_selected.button"
${this._filters.config_entry?.value?.length )}
? html`<ha-alert slot="filter-pane"> </div>
Filtering by config entry </ha-menu-item>
${this._entries?.find( <ha-menu-item @click=${this._hideSelected}>
(entry) => <ha-svg-icon
entry.entry_id === this._filters.config_entry!.value![0] slot="start"
)?.title || this._filters.config_entry.value[0]} .path=${mdiEyeOff}
</ha-alert>` ></ha-svg-icon>
: nothing} <div slot="headline">
${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button"
)}
</div>
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._removeSelected} class="warning">
<ha-svg-icon
slot="start"
.path=${mdiDelete}
></ha-svg-icon>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button"
)}
</div>
</ha-menu-item>
</ha-button-menu-new>
${
this._filters.config_entry?.value?.length
? html`<ha-alert slot="filter-pane">
Filtering by config entry
${this._entries?.find(
(entry) =>
entry.entry_id === this._filters.config_entry!.value![0]
)?.title || this._filters.config_entry.value[0]}
</ha-alert>`
: nothing
}
<ha-filter-floor-areas <ha-filter-floor-areas
.hass=${this.hass} .hass=${this.hass}
type="entity" type="entity"
@ -688,16 +744,20 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
.narrow=${this.narrow} .narrow=${this.narrow}
@expanded-changed=${this._filterExpanded} @expanded-changed=${this._filterExpanded}
></ha-filter-labels> ></ha-filter-labels>
${includeAddDeviceFab ${
? html`<ha-fab includeAddDeviceFab
.label=${this.hass.localize("ui.panel.config.devices.add_device")} ? html`<ha-fab
extended .label=${this.hass.localize(
@click=${this._addDevice} "ui.panel.config.devices.add_device"
slot="fab" )}
> extended
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> @click=${this._addDevice}
</ha-fab>` slot="fab"
: nothing} >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>`
: nothing
}
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
`; `;
} }
@ -931,6 +991,28 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
}); });
} }
private _unhideSelected() {
this._selectedEntities.forEach((entity) =>
updateEntityRegistryEntry(this.hass, entity, {
hidden_by: null,
})
);
this._clearSelection();
}
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selectedEntities.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
labels: this.hass.entities[entityId].labels.concat(label),
})
);
});
await Promise.all(promises);
}
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];
@ -1080,6 +1162,17 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
text-transform: uppercase; text-transform: uppercase;
direction: var(--direction); direction: var(--direction);
} }
ha-assist-chip {
--ha-assist-chip-container-shape: 10px;
}
ha-button-menu-new ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
ha-label {
--ha-label-background-color: var(--color, var(--grey-color));
--ha-label-background-opacity: 0.5;
}
`, `,
]; ];
} }

View File

@ -4052,6 +4052,9 @@
"button": "Hide selected", "button": "Hide selected",
"confirm_title": "Do you want to hide {number} {number, plural,\n one {entity}\n other {entities}\n}?", "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." "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."
},
"unhide_selected": {
"button": "Unhide selected"
} }
} }
}, },