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,92 +568,122 @@ 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}
.disabled=${!this._selectedEntities.length}
>${this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.button"
)}</mwc-button
>
<mwc-button
@click=${this._hideSelected}
.disabled=${!this._selectedEntities.length}
>${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button"
)}</mwc-button
>
<mwc-button
@click=${this._removeSelected}
.disabled=${!this._selectedEntities.length}
class="warning"
>${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button"
)}</mwc-button
>
`
: html`
<ha-icon-button
id="enable-btn"
.disabled=${!this._selectedEntities.length}
@click=${this._enableSelected}
.path=${mdiUndo}
.label=${this.hass.localize("ui.common.enable")}
></ha-icon-button>
<simple-tooltip animation-delay="0" for="enable-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.button"
)} )}
</simple-tooltip> >
<ha-icon-button <ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon>
id="disable-btn" </ha-assist-chip>
.disabled=${!this._selectedEntities.length} ${labelItems}
@click=${this._disableSelected} </ha-button-menu-new>`
.path=${mdiCancel} : nothing
.label=${this.hass.localize("ui.common.disable")} }
></ha-icon-button> <ha-button-menu-new has-overflow slot="selection-bar">
<simple-tooltip animation-delay="0" for="disable-btn"> ${
${this.hass.localize( this.narrow
"ui.panel.config.entities.picker.disable_selected.button" ? html`<ha-assist-chip
.label=${this.hass.localize(
"ui.panel.config.automation.picker.bulk_action"
)} )}
</simple-tooltip> slot="trigger"
<ha-icon-button >
id="hide-btn" <ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon>
.disabled=${!this._selectedEntities.length} </ha-assist-chip>`
@click=${this._hideSelected} : html`<ha-icon-button
.path=${mdiEyeOff} .path=${mdiDotsVertical}
.label=${this.hass.localize("ui.common.hide")} .label=${"ui.panel.config.automation.picker.bulk_action"}
></ha-icon-button> slot="trigger"
<simple-tooltip animation-delay="0" for="hide-btn"> ></ha-icon-button>`
}
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon
></ha-assist-chip>
${
this.narrow
? html`<ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.button" "ui.panel.config.automation.picker.bulk_actions.add_label"
)} )}
</simple-tooltip>
<ha-icon-button
class="warning"
id="remove-btn"
.disabled=${!this._selectedEntities.length}
@click=${this._removeSelected}
.path=${mdiDelete}
.label=${this.hass.localize("ui.common.remove")}
></ha-icon-button>
<simple-tooltip animation-delay="0" for="remove-btn">
${this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.button"
)}
</simple-tooltip>
`}
</div> </div>
${this._filters.config_entry?.value?.length <ha-svg-icon slot="end" .path=${mdiChevronRight}></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${labelItems}</ha-menu>
</ha-sub-menu>
<md-divider role="separator" tabindex="-1"></md-divider>`
: nothing
}
<ha-menu-item @click=${this._enableSelected}>
<ha-svg-icon slot="start" .path=${mdiToggleSwitch}></ha-svg-icon>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.button"
)}
</div>
</ha-menu-item>
<ha-menu-item @click=${this._disableSelected}>
<ha-svg-icon
slot="start"
.path=${mdiToggleSwitchOffOutline}
></ha-svg-icon>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.button"
)}
</div>
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._unhideSelected}>
<ha-svg-icon
slot="start"
.path=${mdiEye}
></ha-svg-icon>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.entities.picker.unhide_selected.button"
)}
</div>
</ha-menu-item>
<ha-menu-item @click=${this._hideSelected}>
<ha-svg-icon
slot="start"
.path=${mdiEyeOff}
></ha-svg-icon>
<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"> ? html`<ha-alert slot="filter-pane">
Filtering by config entry Filtering by config entry
${this._entries?.find( ${this._entries?.find(
@ -636,7 +691,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
entry.entry_id === this._filters.config_entry!.value![0] entry.entry_id === this._filters.config_entry!.value![0]
)?.title || this._filters.config_entry.value[0]} )?.title || this._filters.config_entry.value[0]}
</ha-alert>` </ha-alert>`
: nothing} : 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 ${
includeAddDeviceFab
? html`<ha-fab ? html`<ha-fab
.label=${this.hass.localize("ui.panel.config.devices.add_device")} .label=${this.hass.localize(
"ui.panel.config.devices.add_device"
)}
extended extended
@click=${this._addDevice} @click=${this._addDevice}
slot="fab" slot="fab"
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>` </ha-fab>`
: nothing} : 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"
} }
} }
}, },