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 * responsivepull/20293/head
parent
4f8415e8a7
commit
1ce3347c2e
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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)",
|
||||||
|
|
|
@ -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''."
|
||||||
|
|
Loading…
Reference in New Issue