Skip to content

Commit

Permalink
Add multiselect area to automation/scene/script (#20604)
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten authored Apr 24, 2024
1 parent 7e9b01b commit 87bcd3e
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 12 deletions.
117 changes: 115 additions & 2 deletions src/panels/config/automation/ha-automation-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
mdiPlus,
mdiRobotHappy,
mdiTag,
mdiTextureBox,
mdiToggleSwitch,
mdiToggleSwitchOffOutline,
mdiTransitConnection,
Expand Down Expand Up @@ -69,6 +70,7 @@ import type { HaMenu } from "../../../components/ha-menu";
import "../../../components/ha-menu-item";
import "../../../components/ha-sub-menu";
import "../../../components/ha-svg-icon";
import { createAreaRegistryEntry } from "../../../data/area_registry";
import {
AutomationEntity,
deleteAutomation,
Expand Down Expand Up @@ -106,6 +108,7 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route, ServiceCallResponse } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { turnOnOffEntity } from "../../lovelace/common/entity/turn-on-off-entity";
import { showAreaRegistryDetailDialog } from "../areas/show-dialog-area-registry-detail";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config";
Expand Down Expand Up @@ -403,6 +406,7 @@ 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 color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((entityId) =>
Expand Down Expand Up @@ -440,10 +444,45 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
</div></ha-menu-item
>`;

const labelsInOverflow =
(this._sizeController.value && this._sizeController.value < 700) ||
const areaItems = html`${Object.values(this.hass.areas).map(
(area) =>
html`<ha-menu-item
.value=${area.area_id}
@click=${this._handleBulkArea}
>
${area.icon
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
: html`<ha-svg-icon
slot="start"
.path=${mdiTextureBox}
></ha-svg-icon>`}
<div slot="headline">${area.name}</div>
</ha-menu-item>`
)}
<ha-menu-item .value=${null} @click=${this._handleBulkArea}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.no_area"
)}
</div>
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._bulkCreateArea}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.add_area"
)}
</div>
</ha-menu-item>`;

const areasInOverflow =
(this._sizeController.value && this._sizeController.value < 900) ||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");

const labelsInOverflow =
areasInOverflow &&
(!this._sizeController.value || this._sizeController.value < 700);

const automations = this._automations(
this.automations,
this._entityReg,
Expand Down Expand Up @@ -598,6 +637,22 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
></ha-svg-icon>
</ha-assist-chip>
${labelItems}
</ha-button-menu-new>`}
${areasInOverflow
? nothing
: html`<ha-button-menu-new slot="selection-bar">
<ha-assist-chip
slot="trigger"
.label=${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.move_area"
)}
>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
${areaItems}
</ha-button-menu-new>`}`
: nothing
}
Expand Down Expand Up @@ -662,6 +717,24 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
</ha-sub-menu>`
: nothing
}
${
this.narrow || areasInOverflow
? html`<ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.move_area"
)}
</div>
<ha-svg-icon
slot="end"
.path=${mdiChevronRight}
></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${areaItems}</ha-menu>
</ha-sub-menu>`
: nothing
}
<ha-menu-item @click=${this._handleBulkEnable}>
<ha-svg-icon slot="start" .path=${mdiToggleSwitch}></ha-svg-icon>
<div slot="headline">
Expand Down Expand Up @@ -1191,6 +1264,46 @@ ${rejected
}
}

private async _handleBulkArea(ev) {
const area = ev.currentTarget.value;
this._bulkAddArea(area);
}

private async _bulkAddArea(area: string) {
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
area_id: area,
})
);
});
const result = await Promise.allSettled(promises);
if (hasRejectedItems(result)) {
const rejected = rejectedItems(result);
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.common.multiselect.failed", {
number: rejected.length,
}),
text: html`<pre>
${rejected
.map((r) => r.reason.message || r.reason.code || r.reason)
.join("\r\n")}</pre
>`,
});
}
}

private async _bulkCreateArea() {
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
const area = await createAreaRegistryEntry(this.hass, values);
this._bulkAddArea(area.area_id);
return area;
},
});
}

private async _handleBulkEnable() {
const promises: Promise<ServiceCallResponse>[] = [];
this._selected.forEach((entityId) => {
Expand Down
126 changes: 121 additions & 5 deletions src/panels/config/scene/ha-scene-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
mdiPlay,
mdiPlus,
mdiTag,
mdiTextureBox,
} from "@mdi/js";
import { differenceInDays } from "date-fns";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
Expand Down Expand Up @@ -61,6 +62,7 @@ import "../../../components/ha-menu-item";
import "../../../components/ha-state-icon";
import "../../../components/ha-sub-menu";
import "../../../components/ha-svg-icon";
import { createAreaRegistryEntry } from "../../../data/area_registry";
import {
CategoryRegistryEntry,
createCategoryRegistryEntry,
Expand Down Expand Up @@ -97,6 +99,7 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast";
import { showAreaRegistryDetailDialog } from "../areas/show-dialog-area-registry-detail";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config";
Expand Down Expand Up @@ -406,6 +409,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
${this.hass.localize("ui.panel.config.category.editor.add")}
</div>
</ha-menu-item>`;

const labelItems = html` ${this._labels?.map((label) => {
const color = label.color ? computeCssColor(label.color) : undefined;
const selected = this._selected.every((entityId) =>
Expand Down Expand Up @@ -442,9 +446,46 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
${this.hass.localize("ui.panel.config.labels.add_label")}
</div></ha-menu-item
>`;
const labelsInOverflow =
(this._sizeController.value && this._sizeController.value < 700) ||

const areaItems = html`${Object.values(this.hass.areas).map(
(area) =>
html`<ha-menu-item
.value=${area.area_id}
@click=${this._handleBulkArea}
>
${area.icon
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
: html`<ha-svg-icon
slot="start"
.path=${mdiTextureBox}
></ha-svg-icon>`}
<div slot="headline">${area.name}</div>
</ha-menu-item>`
)}
<ha-menu-item .value=${null} @click=${this._handleBulkArea}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.no_area"
)}
</div>
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item @click=${this._bulkCreateArea}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.add_area"
)}
</div>
</ha-menu-item>`;

const areasInOverflow =
(this._sizeController.value && this._sizeController.value < 900) ||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");

const labelsInOverflow =
areasInOverflow &&
(!this._sizeController.value || this._sizeController.value < 700);

const scenes = this._scenes(
this.scenes,
this._entityReg,
Expand All @@ -453,6 +494,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
this._labels,
this._filteredScenes
);

return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
Expand Down Expand Up @@ -582,9 +624,25 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
></ha-svg-icon>
</ha-assist-chip>
${labelItems}
</ha-button-menu-new>`}
${areasInOverflow
? nothing
: html`<ha-button-menu-new slot="selection-bar">
<ha-assist-chip
slot="trigger"
.label=${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.move_area"
)}
>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
${areaItems}
</ha-button-menu-new>`}`
: nothing}
${this.narrow || labelsInOverflow
${this.narrow || areasInOverflow
? html`
<ha-button-menu-new has-overflow slot="selection-bar">
${
Expand Down Expand Up @@ -630,8 +688,8 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
: nothing
}
${
this.narrow || this.hass.dockedSidebar === "docked"
? html` <ha-sub-menu>
this.narrow || labelsInOverflow
? html`<ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize(
Expand All @@ -647,6 +705,24 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
</ha-sub-menu>`
: nothing
}
${
this.narrow || areasInOverflow
? html`<ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize(
"ui.panel.config.devices.picker.bulk_actions.move_area"
)}
</div>
<ha-svg-icon
slot="end"
.path=${mdiChevronRight}
></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${areaItems}</ha-menu>
</ha-sub-menu>`
: nothing
}
</ha-button-menu-new>`
: nothing}
${!this.scenes.length
Expand Down Expand Up @@ -875,6 +951,46 @@ ${rejected
}
}

private async _handleBulkArea(ev) {
const area = ev.currentTarget.value;
this._bulkAddArea(area);
}

private async _bulkAddArea(area: string) {
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
area_id: area,
})
);
});
const result = await Promise.allSettled(promises);
if (hasRejectedItems(result)) {
const rejected = rejectedItems(result);
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.common.multiselect.failed", {
number: rejected.length,
}),
text: html`<pre>
${rejected
.map((r) => r.reason.message || r.reason.code || r.reason)
.join("\r\n")}</pre
>`,
});
}
}

private async _bulkCreateArea() {
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
const area = await createAreaRegistryEntry(this.hass, values);
this._bulkAddArea(area.area_id);
return area;
},
});
}

private _editCategory(scene: any) {
const entityReg = this._entityReg.find(
(reg) => reg.entity_id === scene.entity_id
Expand Down
Loading

0 comments on commit 87bcd3e

Please sign in to comment.