From cbb08c6202bbd6ce980b8aeea4cafacffbf4f777 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 2 Apr 2024 15:16:10 +0200 Subject: [PATCH] Add multi select to scripts and scenes (#20318) --- src/panels/config/scene/ha-scene-dashboard.ts | 191 ++++++++++++++++- src/panels/config/script/ha-script-picker.ts | 192 +++++++++++++++++- 2 files changed, 381 insertions(+), 2 deletions(-) diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index ece548c706..cb8bebaaf1 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -1,10 +1,13 @@ import { consume } from "@lit-labs/context"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import { + mdiChevronRight, mdiContentDuplicate, mdiDelete, + mdiDotsVertical, mdiHelpCircle, mdiInformationOutline, + mdiMenuDown, mdiPalette, mdiPencilOff, mdiPlay, @@ -33,6 +36,7 @@ import { LocalizeFunc } from "../../../common/translations/localize"; import { DataTableColumnContainer, RowClickedEvent, + SelectionChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/ha-button"; @@ -46,13 +50,19 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-state-icon"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-menu-item"; +import "../../../components/ha-sub-menu"; import { CategoryRegistryEntry, subscribeCategoryRegistry, } from "../../../data/category_registry"; import { fullEntitiesContext } from "../../../data/context"; import { isUnavailableState } from "../../../data/entity"; -import { EntityRegistryEntry } from "../../../data/entity_registry"; +import { + EntityRegistryEntry, + UpdateEntityRegistryEntryResult, + updateEntityRegistryEntry, +} from "../../../data/entity_registry"; import { forwardHaptic } from "../../../data/haptics"; import { LabelRegistryEntry, @@ -77,6 +87,7 @@ import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; import { showAssignCategoryDialog } from "../category/show-dialog-assign-category"; import { configSections } from "../ha-panel-config"; +import { computeCssColor } from "../../../common/color/compute-color"; type SceneItem = SceneEntity & { name: string; @@ -98,6 +109,8 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { @state() private _searchParms = new URLSearchParams(window.location.search); + @state() private _selected: string[] = []; + @state() private _activeFilters?: string[]; @state() private _filteredScenes?: string[] | null; @@ -319,6 +332,40 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { } protected render(): TemplateResult { + const categoryItems = html`${this._categories?.map( + (category) => + html` + ${category.icon + ? html`` + : html``} +
${category.name}
+
` + )} + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.no_category" + )} +
+
`; + const labelItems = html` ${this._labels?.map((label) => { + const color = label.color ? computeCssColor(label.color) : undefined; + return html` + + ${label.icon + ? html`` + : nothing} + ${label.name} + + `; + })}`; + return html` filter.value?.length @@ -407,6 +457,103 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { @expanded-changed=${this._filterExpanded} > + ${!this.narrow + ? html` + + + + ${categoryItems} + + ${this.hass.dockedSidebar === "docked" + ? nothing + : html` + + + + ${labelItems} + `}` + : nothing} + ${this.narrow || this.hass.dockedSidebar === "docked" + ? html` + + ${ + this.narrow + ? html` + + ` + : html`` + } + + ${ + this.narrow + ? html` + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.move_category" + )} +
+ +
+ ${categoryItems} +
` + : nothing + } + ${ + this.narrow || this.hass.dockedSidebar === "docked" + ? html` + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.add_label" + )} +
+ +
+ ${labelItems} +
` + : nothing + } +
` + : nothing} ${!this.scenes.length ? html`
@@ -553,6 +700,12 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { this._applyFilters(); } + private _handleSelectionChanged( + ev: HASSDomEvent + ): void { + this._selected = ev.detail.value; + } + private _handleRowClicked(ev: HASSDomEvent) { const scene = this.scenes.find((a) => a.entity_id === ev.detail.id); @@ -561,6 +714,32 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { } } + private async _handleBulkCategory(ev) { + const category = ev.currentTarget.value; + const promises: Promise[] = []; + this._selected.forEach((entityId) => { + promises.push( + updateEntityRegistryEntry(this.hass, entityId, { + categories: { scene: category }, + }) + ); + }); + await Promise.all(promises); + } + + private async _handleBulkLabel(ev) { + const label = ev.currentTarget.value; + const promises: Promise[] = []; + this._selected.forEach((entityId) => { + promises.push( + updateEntityRegistryEntry(this.hass, entityId, { + labels: this.hass.entities[entityId].labels.concat(label), + }) + ); + }); + await Promise.all(promises); + } + private _editCategory(scene: any) { const entityReg = this._entityReg.find( (reg) => reg.entity_id === scene.entity_id @@ -664,6 +843,16 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) { --mdc-icon-size: 80px; max-width: 500px; } + 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; + } `, ]; } diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index b5ddc8cc22..15441b45da 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -1,9 +1,12 @@ import { consume } from "@lit-labs/context"; import { + mdiChevronRight, mdiContentDuplicate, mdiDelete, + mdiDotsVertical, mdiHelpCircle, mdiInformationOutline, + mdiMenuDown, mdiPlay, mdiPlus, mdiScriptText, @@ -34,6 +37,7 @@ import { LocalizeFunc } from "../../../common/translations/localize"; import { DataTableColumnContainer, RowClickedEvent, + SelectionChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/data-table/ha-data-table-labels"; import "../../../components/ha-fab"; @@ -46,13 +50,19 @@ import "../../../components/ha-filter-labels"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-menu-item"; +import "../../../components/ha-sub-menu"; import { CategoryRegistryEntry, subscribeCategoryRegistry, } from "../../../data/category_registry"; import { fullEntitiesContext } from "../../../data/context"; import { UNAVAILABLE } from "../../../data/entity"; -import { EntityRegistryEntry } from "../../../data/entity_registry"; +import { + EntityRegistryEntry, + UpdateEntityRegistryEntryResult, + updateEntityRegistryEntry, +} from "../../../data/entity_registry"; import { LabelRegistryEntry, subscribeLabelRegistry, @@ -79,6 +89,7 @@ import { showToast } from "../../../util/toast"; import { showNewAutomationDialog } from "../automation/show-dialog-new-automation"; import { showAssignCategoryDialog } from "../category/show-dialog-assign-category"; import { configSections } from "../ha-panel-config"; +import { computeCssColor } from "../../../common/color/compute-color"; type ScriptItem = ScriptEntity & { name: string; @@ -102,6 +113,8 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { @state() private _searchParms = new URLSearchParams(window.location.search); + @state() private _selected: string[] = []; + @state() private _activeFilters?: string[]; @state() private _filteredScripts?: string[] | null; @@ -331,6 +344,40 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { } protected render(): TemplateResult { + const categoryItems = html`${this._categories?.map( + (category) => + html` + ${category.icon + ? html`` + : html``} +
${category.name}
+
` + )} + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.no_category" + )} +
+
`; + const labelItems = html` ${this._labels?.map((label) => { + const color = label.color ? computeCssColor(label.color) : undefined; + return html` + + ${label.icon + ? html`` + : nothing} + ${label.name} + + `; + })}`; + return html` filter.value?.length ).length} @@ -432,6 +482,104 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { .narrow=${this.narrow} @expanded-changed=${this._filterExpanded} > + + ${!this.narrow + ? html` + + + + ${categoryItems} + + ${this.hass.dockedSidebar === "docked" + ? nothing + : html` + + + + ${labelItems} + `}` + : nothing} + ${this.narrow || this.hass.dockedSidebar === "docked" + ? html` + + ${ + this.narrow + ? html` + + ` + : html`` + } + + ${ + this.narrow + ? html` + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.move_category" + )} +
+ +
+ ${categoryItems} +
` + : nothing + } + ${ + this.narrow || this.hass.dockedSidebar === "docked" + ? html` + +
+ ${this.hass.localize( + "ui.panel.config.automation.picker.bulk_actions.add_label" + )} +
+ +
+ ${labelItems} +
` + : nothing + } +
` + : nothing} ${!this.scripts.length ? html`
@@ -629,6 +777,38 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { }); } + private _handleSelectionChanged( + ev: HASSDomEvent + ): void { + this._selected = ev.detail.value; + } + + private async _handleBulkCategory(ev) { + const category = ev.currentTarget.value; + const promises: Promise[] = []; + this._selected.forEach((entityId) => { + promises.push( + updateEntityRegistryEntry(this.hass, entityId, { + categories: { script: category }, + }) + ); + }); + await Promise.all(promises); + } + + private async _handleBulkLabel(ev) { + const label = ev.currentTarget.value; + const promises: Promise[] = []; + this._selected.forEach((entityId) => { + promises.push( + updateEntityRegistryEntry(this.hass, entityId, { + labels: this.hass.entities[entityId].labels.concat(label), + }) + ); + }); + await Promise.all(promises); + } + private _handleRowClicked(ev: HASSDomEvent) { const entry = this.entityRegistry.find((e) => e.entity_id === ev.detail.id); if (entry) { @@ -782,6 +962,16 @@ class HaScriptPicker extends SubscribeMixin(LitElement) { --mdc-icon-size: 80px; max-width: 500px; } + 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; + } `, ]; }