Allow to remove labels in multi select (#20368)

* Allow to remove labels in multi select

* reducedTouchTarget

* fix devices

* Update ha-config-devices-dashboard.ts
This commit is contained in:
Bram Kragten 2024-04-03 14:17:21 +02:00 committed by GitHub
parent e25d4f17aa
commit eb79a1e7d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 155 additions and 41 deletions

View File

@ -378,25 +378,42 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
${this.hass.localize("ui.panel.config.category.editor.add")}
</div>
</ha-menu-item>`;
const labelItems = html` ${this._labels?.map((label) => {
const labelItems = html`${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
const partial =
!selected &&
this._selected.some((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
return html`<ha-menu-item
.value=${label.label_id}
.action=${selected ? "remove" : "add"}
@click=${this._handleBulkLabel}
keep-open
>
<ha-checkbox
slot="start"
.checked=${selected}
.indeterminate=${partial}
reducedTouchTarget
></ha-checkbox>
<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> `;
})} <md-divider role="separator" tabindex="-1"></md-divider>
</ha-menu-item>`;
})}
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._createLabel}>
<div slot="headline">
${this.hass.localize("ui.panel.config.labels.add_label")}
</div>
</ha-menu-item>`;
</div></ha-menu-item
>`;
return html`
<hass-tabs-subpage-data-table
@ -1078,11 +1095,17 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const action = ev.currentTarget.action;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
labels: this.hass.entities[entityId].labels.concat(label),
labels:
action === "add"
? this.hass.entities[entityId].labels.concat(label)
: this.hass.entities[entityId].labels.filter(
(lbl) => lbl !== label
),
})
);
});

View File

@ -546,23 +546,40 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
const labelItems = html`${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((deviceId) =>
this.hass.devices[deviceId]?.labels.includes(label.label_id)
);
const partial =
!selected &&
this._selected.some((deviceId) =>
this.hass.devices[deviceId]?.labels.includes(label.label_id)
);
return html`<ha-menu-item
.value=${label.label_id}
.action=${selected ? "remove" : "add"}
@click=${this._handleBulkLabel}
keep-open
>
<ha-checkbox
slot="start"
.checked=${selected}
.indeterminate=${partial}
reducedTouchTarget
></ha-checkbox>
<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> `;
})}<md-divider role="separator" tabindex="-1"></md-divider>
</ha-menu-item>`;
})}
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._createLabel}>
<div slot="headline">
${this.hass.localize("ui.panel.config.labels.add_label")}
</div>
</ha-menu-item>`;
</div></ha-menu-item
>`;
return html`
<hass-tabs-subpage-data-table
@ -783,11 +800,17 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const action = ev.currentTarget.action;
const promises: Promise<DeviceRegistryEntry>[] = [];
this._selected.forEach((deviceId) => {
promises.push(
updateDeviceRegistryEntry(this.hass, deviceId, {
labels: this.hass.devices[deviceId].labels.concat(label),
labels:
action === "add"
? this.hass.devices[deviceId].labels.concat(label)
: this.hass.devices[deviceId].labels.filter(
(lbl) => lbl !== label
),
})
);
});

View File

@ -134,7 +134,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
{ value: string[] | undefined; items: Set<string> | undefined }
> = {};
@state() private _selectedEntities: string[] = [];
@state() private _selected: string[] = [];
@state() private _expandedFilter?: string;
@ -518,10 +518,26 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
const labelItems = html` ${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
const partial =
!selected &&
this._selected.some((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
return html`<ha-menu-item
.value=${label.label_id}
.action=${selected ? "remove" : "add"}
@click=${this._handleBulkLabel}
keep-open
>
<ha-checkbox
slot="start"
.checked=${selected}
.indeterminate=${partial}
reducedTouchTarget
></ha-checkbox>
<ha-label style=${color ? `--color: ${color}` : ""}>
${label.icon
? html`<ha-icon slot="icon" .icon=${label.icon}></ha-icon>`
@ -529,12 +545,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
${label.name}
</ha-label>
</ha-menu-item>`;
})}<md-divider role="separator" tabindex="-1"></md-divider>
})}
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._createLabel}>
<div slot="headline">
${this.hass.localize("ui.panel.config.labels.add_label")}
</div>
</ha-menu-item>`;
</div></ha-menu-item
>`;
return html`
<hass-tabs-subpage-data-table
@ -561,7 +578,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
}
.filter=${this._filter}
selectable
.selected=${this._selectedEntities.length}
.selected=${this._selected.length}
@selection-changed=${this._handleSelectionChanged}
clickable
@clear-filter=${this._clearFilter}
@ -903,14 +920,14 @@ ${
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedEntities = ev.detail.value;
this._selected = ev.detail.value;
}
private async _enableSelected() {
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.confirm_title",
{ number: this._selectedEntities.length }
{ number: this._selected.length }
),
text: this.hass.localize(
"ui.panel.config.entities.picker.enable_selected.confirm_text"
@ -921,7 +938,7 @@ ${
let require_restart = false;
let reload_delay = 0;
await Promise.all(
this._selectedEntities.map(async (entity) => {
this._selected.map(async (entity) => {
const result = await updateEntityRegistryEntry(this.hass, entity, {
disabled_by: null,
});
@ -958,7 +975,7 @@ ${
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.confirm_title",
{ number: this._selectedEntities.length }
{ number: this._selected.length }
),
text: this.hass.localize(
"ui.panel.config.entities.picker.disable_selected.confirm_text"
@ -966,7 +983,7 @@ ${
confirmText: this.hass.localize("ui.common.disable"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
this._selectedEntities.forEach((entity) =>
this._selected.forEach((entity) =>
updateEntityRegistryEntry(this.hass, entity, {
disabled_by: "user",
})
@ -980,7 +997,7 @@ ${
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.confirm_title",
{ number: this._selectedEntities.length }
{ number: this._selected.length }
),
text: this.hass.localize(
"ui.panel.config.entities.picker.hide_selected.confirm_text"
@ -988,7 +1005,7 @@ ${
confirmText: this.hass.localize("ui.common.hide"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
this._selectedEntities.forEach((entity) =>
this._selected.forEach((entity) =>
updateEntityRegistryEntry(this.hass, entity, {
hidden_by: "user",
})
@ -999,7 +1016,7 @@ ${
}
private _unhideSelected() {
this._selectedEntities.forEach((entity) =>
this._selected.forEach((entity) =>
updateEntityRegistryEntry(this.hass, entity, {
hidden_by: null,
})
@ -1009,11 +1026,17 @@ ${
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const action = ev.currentTarget.action;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selectedEntities.forEach((entityId) => {
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
labels: this.hass.entities[entityId].labels.concat(label),
labels:
action === "add"
? this.hass.entities[entityId].labels.concat(label)
: this.hass.entities[entityId].labels.filter(
(lbl) => lbl !== label
),
})
);
});
@ -1021,21 +1044,19 @@ ${
}
private _removeSelected() {
const removeableEntities = this._selectedEntities.filter((entity) => {
const removeableEntities = this._selected.filter((entity) => {
const stateObj = this.hass.states[entity];
return stateObj?.attributes.restored;
});
showConfirmationDialog(this, {
title: this.hass.localize(
`ui.panel.config.entities.picker.remove_selected.confirm_${
removeableEntities.length !== this._selectedEntities.length
? "partly_"
: ""
removeableEntities.length !== this._selected.length ? "partly_" : ""
}title`,
{ number: removeableEntities.length }
),
text:
removeableEntities.length === this._selectedEntities.length
removeableEntities.length === this._selected.length
? this.hass.localize(
"ui.panel.config.entities.picker.remove_selected.confirm_text"
)
@ -1043,7 +1064,7 @@ ${
"ui.panel.config.entities.picker.remove_selected.confirm_partly_text",
{
removable: removeableEntities.length,
selected: this._selectedEntities.length,
selected: this._selected.length,
}
),
confirmText: this.hass.localize("ui.common.remove"),

View File

@ -456,6 +456,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
.value=${label.label_id}
.action=${selected ? "remove" : "add"}
@click=${this._handleBulkLabel}
keep-open
>
<ha-checkbox
slot="start"

View File

@ -374,10 +374,26 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
</ha-menu-item>`;
const labelItems = html` ${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
const partial =
!selected &&
this._selected.some((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
return html`<ha-menu-item
.value=${label.label_id}
.action=${selected ? "remove" : "add"}
@click=${this._handleBulkLabel}
keep-open
>
<ha-checkbox
slot="start"
.checked=${selected}
.indeterminate=${partial}
reducedTouchTarget
></ha-checkbox>
<ha-label style=${color ? `--color: ${color}` : ""}>
${label.icon
? html`<ha-icon slot="icon" .icon=${label.icon}></ha-icon>`
@ -385,12 +401,13 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
${label.name}
</ha-label>
</ha-menu-item>`;
})}<md-divider role="separator" tabindex="-1"></md-divider>
})}
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._createLabel}>
<div slot="headline">
${this.hass.localize("ui.panel.config.labels.add_label")}
</div>
</ha-menu-item>`;
</div></ha-menu-item
>`;
return html`
<hass-tabs-subpage-data-table
@ -756,11 +773,17 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const action = ev.currentTarget.action;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
labels: this.hass.entities[entityId].labels.concat(label),
labels:
action === "add"
? this.hass.entities[entityId].labels.concat(label)
: this.hass.entities[entityId].labels.filter(
(lbl) => lbl !== label
),
})
);
});

View File

@ -384,12 +384,28 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
${this.hass.localize("ui.panel.config.category.editor.add")}
</div>
</ha-menu-item>`;
const labelItems = html` ${this._labels?.map((label) => {
const labelItems = html`${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
const partial =
!selected &&
this._selected.some((entityId) =>
this.hass.entities[entityId]?.labels.includes(label.label_id)
);
return html`<ha-menu-item
.value=${label.label_id}
.action=${selected ? "remove" : "add"}
@click=${this._handleBulkLabel}
keep-open
reducedTouchTarget
>
<ha-checkbox
slot="start"
.checked=${selected}
.indeterminate=${partial}
></ha-checkbox>
<ha-label style=${color ? `--color: ${color}` : ""}>
${label.icon
? html`<ha-icon slot="icon" .icon=${label.icon}></ha-icon>`
@ -397,12 +413,13 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
${label.name}
</ha-label>
</ha-menu-item>`;
})}<md-divider role="separator" tabindex="-1"></md-divider>
})}
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._createLabel}>
<div slot="headline">
${this.hass.localize("ui.panel.config.labels.add_label")}
</div>
</ha-menu-item>`;
</div></ha-menu-item
>`;
return html`
<hass-tabs-subpage-data-table
@ -825,11 +842,17 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const action = ev.currentTarget.action;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
labels: this.hass.entities[entityId].labels.concat(label),
labels:
action === "add"
? this.hass.entities[entityId].labels.concat(label)
: this.hass.entities[entityId].labels.filter(
(lbl) => lbl !== label
),
})
);
});