Add multi select to automations (#20291)

* Add multi select to automations

* allow to clear category, add icons

* use popover

* revert changes to group. by and sort menu, fix dark mode

* ha-menu

* responsive
pull/20293/head
Bram Kragten 2024-04-02 10:14:17 +02:00 committed by GitHub
parent 4f8415e8a7
commit 1ce3347c2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 492 additions and 68 deletions

View File

@ -22,14 +22,6 @@ export class HaAssistChip extends MdAssistChip {
); );
--md-assist-chip-outline-color: var(--outline-color); --md-assist-chip-outline-color: var(--outline-color);
--md-assist-chip-label-text-weight: 400; --md-assist-chip-label-text-weight: 400;
--ha-assist-chip-filled-container-color: rgba(
var(--rgb-primary-text-color),
0.15
);
--ha-assist-chip-active-container-color: rgba(
var(--rgb-primary-color),
0.15
);
} }
/** Material 3 doesn't have a filled chip, so we have to make our own **/ /** Material 3 doesn't have a filled chip, so we have to make our own **/
.filled { .filled {
@ -52,6 +44,10 @@ export class HaAssistChip extends MdAssistChip {
margin-inline-end: unset; margin-inline-end: unset;
margin-inline-start: var(--_icon-label-space); margin-inline-start: var(--_icon-label-space);
} }
::before {
background: var(--ha-assist-chip-container-color);
opacity: var(--ha-assist-chip-container-opacity);
}
:where(.active)::before { :where(.active)::before {
background: var(--ha-assist-chip-active-container-color); background: var(--ha-assist-chip-active-container-color);
opacity: var(--ha-assist-chip-active-container-opacity); opacity: var(--ha-assist-chip-active-container-opacity);

View File

@ -181,6 +181,13 @@ export class HaDataTable extends LitElement {
this._checkedRowsChanged(); this._checkedRowsChanged();
} }
public selectAll(): void {
this._checkedRows = this._filteredData
.filter((data) => data.selectable !== false)
.map((data) => data[this.id]);
this._checkedRowsChanged();
}
public connectedCallback() { public connectedCallback() {
super.connectedCallback(); super.connectedCallback();
if (this._items.length) { if (this._items.length) {
@ -593,10 +600,7 @@ export class HaDataTable extends LitElement {
private _handleHeaderRowCheckboxClick(ev: Event) { private _handleHeaderRowCheckboxClick(ev: Event) {
const checkbox = ev.target as HaCheckbox; const checkbox = ev.target as HaCheckbox;
if (checkbox.checked) { if (checkbox.checked) {
this._checkedRows = this._filteredData this.selectAll();
.filter((data) => data.selectable !== false)
.map((data) => data[this.id]);
this._checkedRowsChanged();
} else { } else {
this._checkedRows = []; this._checkedRows = [];
this._checkedRowsChanged(); this._checkedRowsChanged();

View File

@ -0,0 +1,89 @@
import { Button } from "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query } from "lit/decorators";
import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
import type { HaIconButton } from "./ha-icon-button";
import "./ha-menu";
import type { HaMenu } from "./ha-menu";
@customElement("ha-button-menu-new")
export class HaButtonMenuNew extends LitElement {
protected readonly [FOCUS_TARGET];
@property({ type: Boolean }) public disabled = false;
@property() public positioning?: "fixed" | "absolute" | "popover";
@property({ type: Boolean, attribute: "has-overflow" }) public hasOverflow =
false;
@query("ha-menu", true) private _menu!: HaMenu;
public get items() {
return this._menu.items;
}
public override focus() {
if (this._menu.open) {
this._menu.focus();
} else {
this._triggerButton?.focus();
}
}
protected render(): TemplateResult {
return html`
<div @click=${this._handleClick}>
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
</div>
<ha-menu
.positioning=${this.positioning}
.hasOverflow=${this.hasOverflow}
>
<slot></slot>
</ha-menu>
`;
}
private _handleClick(): void {
if (this.disabled) {
return;
}
this._menu.anchorElement = this;
if (this._menu.open) {
this._menu.close();
} else {
this._menu.show();
}
}
private get _triggerButton() {
return this.querySelector(
'ha-icon-button[slot="trigger"], mwc-button[slot="trigger"], ha-assist-chip[slot="trigger"]'
) as HaIconButton | Button | null;
}
private _setTriggerAria() {
if (this._triggerButton) {
this._triggerButton.ariaHasPopup = "menu";
}
}
static get styles(): CSSResultGroup {
return css`
:host {
display: inline-block;
position: relative;
}
::slotted([disabled]) {
color: var(--disabled-text-color);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-button-menu-new": HaButtonMenuNew;
}
}

View File

@ -0,0 +1,38 @@
import { customElement } from "lit/decorators";
import "element-internals-polyfill";
import { CSSResult, css } from "lit";
import { MdSubMenu } from "@material/web/menu/sub-menu";
@customElement("ha-sub-menu")
// @ts-expect-error
export class HaSubMenu extends MdSubMenu {
static override styles: CSSResult[] = [
MdSubMenu.styles,
css`
:host {
--ha-icon-display: block;
--md-sys-color-primary: var(--primary-text-color);
--md-sys-color-on-primary: var(--primary-text-color);
--md-sys-color-secondary: var(--secondary-text-color);
--md-sys-color-surface: var(--card-background-color);
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-secondary-container: rgba(
var(--rgb-primary-color),
0.15
);
--md-sys-color-on-secondary-container: var(--text-primary-color);
--mdc-icon-size: 16px;
--md-sys-color-on-primary-container: var(--primary-text-color);
--md-sys-color-on-secondary-container: var(--primary-text-color);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-sub-menu": HaSubMenu;
}
}

View File

@ -1,12 +1,13 @@
import { ResizeController } from "@lit-labs/observers/resize-controller"; import { ResizeController } from "@lit-labs/observers/resize-controller";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/web/divider/divider";
import { import {
mdiArrowDown, mdiArrowDown,
mdiArrowUp, mdiArrowUp,
mdiClose, mdiClose,
mdiFilterVariantRemove,
mdiFilterVariant, mdiFilterVariant,
mdiFilterVariantRemove,
mdiFormatListChecks, mdiFormatListChecks,
mdiMenuDown, mdiMenuDown,
} from "@mdi/js"; } from "@mdi/js";
@ -31,14 +32,14 @@ import type {
HaDataTable, HaDataTable,
SortingDirection, SortingDirection,
} from "../components/data-table/ha-data-table"; } from "../components/data-table/ha-data-table";
import "../components/ha-button-menu-new";
import "../components/ha-dialog"; import "../components/ha-dialog";
import "../components/search-input-outlined"; import { HaMenu } from "../components/ha-menu";
import "../components/ha-menu";
import "../components/ha-menu-item"; import "../components/ha-menu-item";
import "../components/search-input-outlined";
import type { HomeAssistant, Route } from "../types"; import type { HomeAssistant, Route } from "../types";
import "./hass-tabs-subpage"; import "./hass-tabs-subpage";
import type { PageNavigation } from "./hass-tabs-subpage"; import type { PageNavigation } from "./hass-tabs-subpage";
import type { HaMenu } from "../components/ha-menu";
declare global { declare global {
// for fire event // for fire event
@ -227,7 +228,7 @@ export class HaTabsSubpageDataTable extends LitElement {
class="has-dropdown select-mode-chip" class="has-dropdown select-mode-chip"
.active=${this._selectMode} .active=${this._selectMode}
@click=${this._enableSelectMode} @click=${this._enableSelectMode}
.label=${localize( .title=${localize(
"ui.components.subpage-data-table.enter_selection_mode" "ui.components.subpage-data-table.enter_selection_mode"
)} )}
> >
@ -255,8 +256,11 @@ export class HaTabsSubpageDataTable extends LitElement {
id="sort-by-anchor" id="sort-by-anchor"
@click=${this._toggleSortBy} @click=${this._toggleSortBy}
> >
<ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon <ha-svg-icon
></ha-assist-chip> slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
` `
: nothing; : nothing;
@ -293,7 +297,7 @@ export class HaTabsSubpageDataTable extends LitElement {
> >
${this._selectMode ${this._selectMode
? html`<div class="selection-bar" slot="toolbar"> ? html`<div class="selection-bar" slot="toolbar">
<div class="center-vertical"> <div class="selection-controls">
<ha-icon-button <ha-icon-button
.path=${mdiClose} .path=${mdiClose}
@click=${this._disableSelectMode} @click=${this._disableSelectMode}
@ -301,6 +305,37 @@ export class HaTabsSubpageDataTable extends LitElement {
"ui.components.subpage-data-table.exit_selection_mode" "ui.components.subpage-data-table.exit_selection_mode"
)} )}
></ha-icon-button> ></ha-icon-button>
<ha-button-menu-new positioning="absolute">
<ha-assist-chip
.label=${localize(
"ui.components.subpage-data-table.select"
)}
slot="trigger"
>
<ha-svg-icon
slot="icon"
.path=${mdiFormatListChecks}
></ha-svg-icon>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon
></ha-assist-chip>
<ha-menu-item .value=${undefined} @click=${this._selectAll}
>${localize("ui.components.subpage-data-table.select_all")}
</ha-menu-item>
<ha-menu-item .value=${undefined} @click=${this._selectNone}
>${localize("ui.components.subpage-data-table.select_none")}
</ha-menu-item>
<md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item
.value=${undefined}
@click=${this._disableSelectMode}
>${localize(
"ui.components.subpage-data-table.close_select_mode"
)}
</ha-menu-item>
</ha-button-menu-new>
<p> <p>
${localize("ui.components.subpage-data-table.selected", { ${localize("ui.components.subpage-data-table.selected", {
selected: this.selected || "0", selected: this.selected || "0",
@ -440,16 +475,15 @@ export class HaTabsSubpageDataTable extends LitElement {
` `
: nothing : nothing
)} )}
<li divider role="separator"></li> <md-divider role="separator" tabindex="-1"></md-divider>
<ha-menu-item <ha-menu-item
.value=${undefined} .value=${undefined}
@click=${this._handleGroupBy} @click=${this._handleGroupBy}
.selected=${this._groupColumn === undefined} .selected=${this._groupColumn === undefined}
class=${classMap({ selected: this._groupColumn === undefined })} class=${classMap({ selected: this._groupColumn === undefined })}
>${localize(
"ui.components.subpage-data-table.dont_group_by"
)}</ha-menu-item
> >
${localize("ui.components.subpage-data-table.dont_group_by")}
</ha-menu-item>
</ha-menu> </ha-menu>
<ha-menu anchor="sort-by-anchor" id="sort-by-menu" positioning="fixed"> <ha-menu anchor="sort-by-anchor" id="sort-by-menu" positioning="fixed">
${Object.entries(this.columns).map(([id, column]) => ${Object.entries(this.columns).map(([id, column]) =>
@ -458,6 +492,7 @@ export class HaTabsSubpageDataTable extends LitElement {
<ha-menu-item <ha-menu-item
.value=${id} .value=${id}
@click=${this._handleSortBy} @click=${this._handleSortBy}
keep-open
.selected=${id === this._sortColumn} .selected=${id === this._sortColumn}
class=${classMap({ selected: id === this._sortColumn })} class=${classMap({ selected: id === this._sortColumn })}
> >
@ -494,8 +529,6 @@ export class HaTabsSubpageDataTable extends LitElement {
} }
private _handleSortBy(ev) { private _handleSortBy(ev) {
ev.stopPropagation();
ev.preventDefault();
const columnId = ev.currentTarget.value; const columnId = ev.currentTarget.value;
if (!this._sortDirection || this._sortColumn !== columnId) { if (!this._sortDirection || this._sortColumn !== columnId) {
this._sortDirection = "asc"; this._sortDirection = "asc";
@ -520,6 +553,14 @@ export class HaTabsSubpageDataTable extends LitElement {
this._dataTable.clearSelection(); this._dataTable.clearSelection();
} }
private _selectAll() {
this._dataTable.selectAll();
}
private _selectNone() {
this._dataTable.clearSelection();
}
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
if (this.filter === ev.detail.value) { if (this.filter === ev.detail.value) {
return; return;
@ -687,23 +728,31 @@ export class HaTabsSubpageDataTable extends LitElement {
padding: 8px 12px; padding: 8px 12px;
box-sizing: border-box; box-sizing: border-box;
font-size: 14px; font-size: 14px;
--ha-assist-chip-container-color: var(--primary-background-color);
}
.selection-controls {
display: flex;
align-items: center;
gap: 8px;
}
.selection-controls p {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
} }
.center-vertical { .center-vertical {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px;
} }
.relative { .relative {
position: relative; position: relative;
} }
.selection-bar p {
margin-left: 16px;
margin-inline-start: 16px;
margin-inline-end: initial;
}
ha-assist-chip { ha-assist-chip {
--ha-assist-chip-container-shape: 10px; --ha-assist-chip-container-shape: 10px;
} }
@ -732,8 +781,10 @@ export class HaTabsSubpageDataTable extends LitElement {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#sort-by-anchor, #sort-by-anchor,
#group-by-anchor { #group-by-anchor,
ha-button-menu-new ha-assist-chip {
--md-assist-chip-trailing-space: 8px; --md-assist-chip-trailing-space: 8px;
} }
`; `;

View File

@ -1,17 +1,22 @@
import { consume } from "@lit-labs/context"; import { consume } from "@lit-labs/context";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { import {
mdiChevronRight,
mdiCog, mdiCog,
mdiContentDuplicate, mdiContentDuplicate,
mdiDelete, mdiDelete,
mdiDotsVertical,
mdiHelpCircle, mdiHelpCircle,
mdiInformationOutline, mdiInformationOutline,
mdiMenuDown,
mdiPlay, mdiPlay,
mdiPlayCircleOutline, mdiPlayCircleOutline,
mdiPlus, mdiPlus,
mdiRobotHappy, mdiRobotHappy,
mdiStopCircleOutline, mdiStopCircleOutline,
mdiTag, mdiTag,
mdiToggleSwitch,
mdiToggleSwitchOffOutline,
mdiTransitConnection, mdiTransitConnection,
} from "@mdi/js"; } from "@mdi/js";
import { differenceInDays } from "date-fns/esm"; import { differenceInDays } from "date-fns/esm";
@ -28,6 +33,7 @@ import {
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
@ -39,6 +45,7 @@ import "../../../components/chips/ha-assist-chip";
import type { import type {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels"; import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-entity-toggle"; import "../../../components/entity/ha-entity-toggle";
@ -51,6 +58,8 @@ import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-labels"; import "../../../components/ha-filter-labels";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-menu-item";
import "../../../components/ha-sub-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { import {
AutomationEntity, AutomationEntity,
@ -67,7 +76,11 @@ import {
} from "../../../data/category_registry"; } from "../../../data/category_registry";
import { fullEntitiesContext } from "../../../data/context"; import { fullEntitiesContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import {
EntityRegistryEntry,
UpdateEntityRegistryEntryResult,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { import {
LabelRegistryEntry, LabelRegistryEntry,
subscribeLabelRegistry, subscribeLabelRegistry,
@ -80,8 +93,9 @@ import {
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route, ServiceCallResponse } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import { turnOnOffEntity } from "../../lovelace/common/entity/turn-on-off-entity";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category"; import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showNewAutomationDialog } from "./show-dialog-new-automation"; import { showNewAutomationDialog } from "./show-dialog-new-automation";
@ -117,6 +131,8 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@state() private _selected: string[] = [];
@state() @state()
_categories!: CategoryRegistryEntry[]; _categories!: CategoryRegistryEntry[];
@ -374,6 +390,40 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
} }
protected render(): TemplateResult { protected render(): TemplateResult {
const categoryItems = html`${this._categories?.map(
(category) =>
html`<ha-menu-item
.value=${category.category_id}
@click=${this._handleBulkCategory}
>
${category.icon
? html`<ha-icon slot="start" .icon=${category.icon}></ha-icon>`
: html`<ha-svg-icon slot="start" .path=${mdiTag}></ha-svg-icon>`}
<div slot="headline">${category.name}</div>
</ha-menu-item>`
)}
<ha-menu-item .value=${null} @click=${this._handleBulkCategory}>
<div slot="headline">
${this.hass.localize(
"ui.panel.config.automation.picker.bulk_actions.no_category"
)}
</div>
</ha-menu-item>`;
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}
@ -382,10 +432,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
id="entity_id" id="entity_id"
.route=${this.route} .route=${this.route}
.tabs=${configSections.automations} .tabs=${configSections.automations}
selectable
.selected=${this._selected.length}
@selection-changed=${this._handleSelectionChanged}
hasFilters hasFilters
.filters=${Object.values(this._filters).filter( .filters=${
(filter) => filter.value?.length Object.values(this._filters).filter((filter) => filter.value?.length)
).length} .length
}
.columns=${this._columns( .columns=${this._columns(
this.narrow, this.narrow,
this.hass.localize, this.hass.localize,
@ -474,36 +528,156 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
.narrow=${this.narrow} .narrow=${this.narrow}
@expanded-changed=${this._filterExpanded} @expanded-changed=${this._filterExpanded}
></ha-filter-blueprints> ></ha-filter-blueprints>
${!this.automations.length ${
? html`<div class="empty" slot="empty"> !this.narrow
<ha-svg-icon .path=${mdiRobotHappy}></ha-svg-icon> ? html`<ha-button-menu-new slot="selection-bar">
<h1> <ha-assist-chip
slot="trigger"
.label=${this.hass.localize(
"ui.panel.config.automation.picker.bulk_actions.move_category"
)}
>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
${categoryItems}
</ha-button-menu-new>
${this.hass.dockedSidebar === "docked"
? nothing
: html`<ha-button-menu-new slot="selection-bar">
<ha-assist-chip
slot="trigger"
.label=${this.hass.localize(
"ui.panel.config.automation.picker.bulk_actions.add_label"
)}
>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>
${labelItems}
</ha-button-menu-new>`}`
: nothing
}
<ha-button-menu-new has-overflow slot="selection-bar">
${
this.narrow
? html`<ha-assist-chip
.label=${this.hass.localize(
"ui.panel.config.automation.picker.bulk_action"
)}
slot="trigger"
>
<ha-svg-icon
slot="trailing-icon"
.path=${mdiMenuDown}
></ha-svg-icon>
</ha-assist-chip>`
: html`<ha-icon-button
.path=${mdiDotsVertical}
.label=${"ui.panel.config.automation.picker.bulk_action"}
slot="trigger"
></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(
"ui.panel.config.automation.picker.bulk_actions.move_category"
)}
</div>
<ha-svg-icon
slot="end"
.path=${mdiChevronRight}
></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${categoryItems}</ha-menu>
</ha-sub-menu>`
: nothing
}
${
this.narrow || this.hass.dockedSidebar === "docked"
? html` <ha-sub-menu>
<ha-menu-item slot="item">
<div slot="headline">
${this.hass.localize(
"ui.panel.config.automation.picker.bulk_actions.add_label"
)}
</div>
<ha-svg-icon
slot="end"
.path=${mdiChevronRight}
></ha-svg-icon>
</ha-menu-item>
<ha-menu slot="menu">${labelItems}</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">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.picker.empty_header" "ui.panel.config.automation.picker.bulk_actions.enable"
)} )}
</h1> </div>
<p> </ha-menu-item>
<ha-menu-item @click=${this._handleBulkDisable}>
<ha-svg-icon
slot="start"
.path=${mdiToggleSwitchOffOutline}
></ha-svg-icon>
<div slot="headline">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.picker.empty_text_1" "ui.panel.config.automation.picker.bulk_actions.disable"
)} )}
</p> </div>
<p> </ha-menu-item>
${this.hass.localize( </ha-button-menu-new>
"ui.panel.config.automation.picker.empty_text_2", ${
{ user: this.hass.user?.name || "Alice" } !this.automations.length
)} ? html`<div class="empty" slot="empty">
</p> <ha-svg-icon .path=${mdiRobotHappy}></ha-svg-icon>
<a <h1>
href=${documentationUrl(this.hass, "/docs/automation/editor/")} ${this.hass.localize(
target="_blank" "ui.panel.config.automation.picker.empty_header"
rel="noreferrer" )}
> </h1>
<ha-button> <p>
${this.hass.localize("ui.panel.config.common.learn_more")} ${this.hass.localize(
</ha-button> "ui.panel.config.automation.picker.empty_text_1"
</a> )}
</div>` </p>
: nothing} <p>
${this.hass.localize(
"ui.panel.config.automation.picker.empty_text_2",
{ user: this.hass.user?.name || "Alice" }
)}
</p>
<a
href=${documentationUrl(
this.hass,
"/docs/automation/editor/"
)}
target="_blank"
rel="noreferrer"
>
<ha-button>
${this.hass.localize("ui.panel.config.common.learn_more")}
</ha-button>
</a>
</div>`
: nothing
}
<ha-fab <ha-fab
slot="fab" slot="fab"
.label=${this.hass.localize( .label=${this.hass.localize(
@ -791,6 +965,12 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
} }
} }
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selected = ev.detail.value;
}
private _createNew() { private _createNew() {
if (isComponentLoaded(this.hass, "blueprint")) { if (isComponentLoaded(this.hass, "blueprint")) {
showNewAutomationDialog(this, { mode: "automation" }); showNewAutomationDialog(this, { mode: "automation" });
@ -799,6 +979,48 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
} }
} }
private async _handleBulkCategory(ev) {
const category = ev.currentTarget.value;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
categories: { automation: category },
})
);
});
await Promise.all(promises);
}
private async _handleBulkLabel(ev) {
const label = ev.currentTarget.value;
const promises: Promise<UpdateEntityRegistryEntryResult>[] = [];
this._selected.forEach((entityId) => {
promises.push(
updateEntityRegistryEntry(this.hass, entityId, {
labels: this.hass.entities[entityId].labels.concat(label),
})
);
});
await Promise.all(promises);
}
private async _handleBulkEnable() {
const promises: Promise<ServiceCallResponse>[] = [];
this._selected.forEach((entityId) => {
promises.push(turnOnOffEntity(this.hass, entityId, true));
});
await Promise.all(promises);
}
private async _handleBulkDisable() {
const promises: Promise<ServiceCallResponse>[] = [];
this._selected.forEach((entityId) => {
promises.push(turnOnOffEntity(this.hass, entityId, false));
});
await Promise.all(promises);
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
@ -814,6 +1036,16 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
--mdc-icon-size: 80px; --mdc-icon-size: 80px;
max-width: 500px; 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;
}
`, `,
]; ];
} }

View File

@ -527,11 +527,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
.filters=${Object.values(this._filters).filter( .filters=${Object.values(this._filters).filter(
(filter) => filter.value?.length (filter) => filter.value?.length
).length} ).length}
.selected=${this._selectedEntities.length}
.filter=${this._filter} .filter=${this._filter}
selectable selectable
clickable .selected=${this._selectedEntities.length}
@selection-changed=${this._handleSelectionChanged} @selection-changed=${this._handleSelectionChanged}
clickable
@clear-filter=${this._clearFilter} @clear-filter=${this._clearFilter}
@search-changed=${this._handleSearchChange} @search-changed=${this._handleSearchChange}
@row-click=${this._openEditEntry} @row-click=${this._openEditEntry}

View File

@ -143,7 +143,10 @@ export const derivedStyles = {
"mdc-select-disabled-ink-color": "var(--input-disabled-ink-color)", "mdc-select-disabled-ink-color": "var(--input-disabled-ink-color)",
"mdc-select-dropdown-icon-color": "var(--input-dropdown-icon-color)", "mdc-select-dropdown-icon-color": "var(--input-dropdown-icon-color)",
"mdc-select-disabled-dropdown-icon-color": "var(--input-disabled-ink-color)", "mdc-select-disabled-dropdown-icon-color": "var(--input-disabled-ink-color)",
"ha-assist-chip-filled-container-color":
"rgba(var(--rgb-primary-text-color),0.15)",
"ha-assist-chip-active-container-color":
"rgba(var(--rgb-primary-color),0.15)",
"chip-background-color": "rgba(var(--rgb-primary-text-color), 0.15)", "chip-background-color": "rgba(var(--rgb-primary-text-color), 0.15)",
// Vaadin // Vaadin
"material-body-text-color": "var(--primary-text-color)", "material-body-text-color": "var(--primary-text-color)",

View File

@ -509,7 +509,10 @@
"group_by": "Group by {groupColumn}", "group_by": "Group by {groupColumn}",
"dont_group_by": "Don't group", "dont_group_by": "Don't group",
"select": "Select", "select": "Select",
"selected": "Selected {selected}" "selected": "Selected {selected}",
"close_select_mode": "Close selection mode",
"select_all": "Select all",
"select_none": "Select none"
}, },
"config-entry-picker": { "config-entry-picker": {
"config_entry": "Integration" "config_entry": "Integration"
@ -2694,6 +2697,14 @@
"state": "State", "state": "State",
"category": "Category" "category": "Category"
}, },
"bulk_action": "Action",
"bulk_actions": {
"move_category": "Move to category",
"no_category": "No category",
"add_label": "Add label",
"enable": "Enable",
"disable": "Disable"
},
"empty_header": "Start automating", "empty_header": "Start automating",
"empty_text_1": "Automations make Home Assistant automatically respond to things happening in and around your home.", "empty_text_1": "Automations make Home Assistant automatically respond to things happening in and around your home.",
"empty_text_2": "Automations connect triggers to actions in a ''when trigger then action'' fashion with optional conditions. For example: ''When the sun sets and if {user} is home, then turn on the lights''." "empty_text_2": "Automations connect triggers to actions in a ''when trigger then action'' fashion with optional conditions. For example: ''When the sun sets and if {user} is home, then turn on the lights''."