diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index 1dd2e4274c..c6db3a0e54 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -113,7 +113,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { @internalProperty() private _opened?: boolean; - @query("ha-combo-box", true) private _comboBox!: HaComboBox; + @query("ha-combo-box", true) public comboBox!: HaComboBox; private _init = false; @@ -242,11 +242,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { ); public open() { - this._comboBox?.open(); + this.comboBox?.open(); } public focus() { - this._comboBox?.focus(); + this.comboBox?.focus(); } public hassSubscribe(): UnsubscribeFunc[] { @@ -269,7 +269,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { (changedProps.has("_opened") && this._opened) ) { this._init = true; - (this._comboBox as any).items = this._getDevices( + (this.comboBox as any).items = this._getDevices( this.devices!, this.areas!, this.entities!, diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 10a9f9881e..45096550a4 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -127,7 +127,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { @internalProperty() private _opened?: boolean; - @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement; + @query("vaadin-combo-box-light", true) public comboBox!: HTMLElement; private _init = false; @@ -319,7 +319,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { (changedProps.has("_opened") && this._opened) ) { this._init = true; - (this._comboBox as any).items = this._getAreas( + (this.comboBox as any).items = this._getAreas( this._areas!, this._devices!, this._entities!, diff --git a/src/components/ha-button-menu.ts b/src/components/ha-button-menu.ts index 5aafc86d78..de50f7bd42 100644 --- a/src/components/ha-button-menu.ts +++ b/src/components/ha-button-menu.ts @@ -1,4 +1,3 @@ -import "@material/mwc-button"; import "@material/mwc-menu"; import type { Corner, Menu } from "@material/mwc-menu"; import { @@ -11,8 +10,6 @@ import { query, TemplateResult, } from "lit-element"; -import "./ha-icon-button"; - @customElement("ha-button-menu") export class HaButtonMenu extends LitElement { @property() public corner: Corner = "TOP_START"; diff --git a/src/components/ha-button-related-filter-menu.ts b/src/components/ha-button-related-filter-menu.ts new file mode 100644 index 0000000000..fa2c580735 --- /dev/null +++ b/src/components/ha-button-related-filter-menu.ts @@ -0,0 +1,163 @@ +import "@material/mwc-icon-button"; +import type { Corner } from "@material/mwc-menu"; +import { mdiFilterVariant } from "@mdi/js"; +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "@material/mwc-menu/mwc-menu-surface"; +import { fireEvent } from "../common/dom/fire_event"; +import { findRelated, RelatedResult } from "../data/search"; +import type { HomeAssistant } from "../types"; +import "./ha-svg-icon"; +import "./ha-area-picker"; +import "./device/ha-device-picker"; + +declare global { + // for fire event + interface HASSDomEvents { + "related-changed": { + value?: FilterValue; + items?: RelatedResult; + filter?: string; + }; + } +} + +interface FilterValue { + area?: string; + device?: string; +} + +@customElement("ha-button-related-filter-menu") +export class HaRelatedFilterButtonMenu extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public corner: Corner = "TOP_START"; + + @property({ type: Boolean, reflect: true }) public narrow = false; + + @property({ type: Boolean }) public disabled = false; + + @property({ attribute: false }) public value?: FilterValue; + + @internalProperty() private _open = false; + + protected render(): TemplateResult { + return html` + + + + + + + + `; + } + + private _handleClick(): void { + if (this.disabled) { + return; + } + this._open = true; + } + + private _onClosed(): void { + this._open = false; + } + + private async _devicePicked(ev: CustomEvent) { + const deviceId = ev.detail.value; + if (!deviceId) { + fireEvent(this, "related-changed", { value: undefined }); + return; + } + const filter = this.hass.localize( + "ui.components.related-filter-menu.filtered_by_device", + "device_name", + (ev.currentTarget as any).comboBox.selectedItem.name + ); + const items = await findRelated(this.hass, "device", deviceId); + + fireEvent(this, "related-changed", { + value: { device: deviceId }, + filter, + items, + }); + } + + private async _areaPicked(ev: CustomEvent) { + const areaId = ev.detail.value; + if (!areaId) { + fireEvent(this, "related-changed", { value: undefined }); + return; + } + const filter = this.hass.localize( + "ui.components.related-filter-menu.filtered_by_area", + "area_name", + (ev.currentTarget as any).comboBox.selectedItem.name + ); + const items = await findRelated(this.hass, "area", areaId); + fireEvent(this, "related-changed", { + value: { area: areaId }, + filter, + items, + }); + } + + static get styles(): CSSResult { + return css` + :host { + display: inline-block; + position: relative; + } + :host([narrow]) { + position: static; + } + ha-area-picker, + ha-device-picker { + display: block; + width: 300px; + padding: 4px 16px; + box-sizing: border-box; + } + :host([narrow]) ha-area-picker, + :host([narrow]) ha-device-picker { + width: 100%; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-button-related-filter-menu": HaRelatedFilterButtonMenu; + } +} diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index 6f6acd3346..19f89bd410 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -86,6 +86,10 @@ export class HaComboBox extends LitElement { }); } + public get selectedItem() { + return (this._comboBox as any).selectedItem; + } + protected render(): TemplateResult { return html` ) { + private _filterChanged(ev: PolymerChangedEvent) { // @ts-ignore - fireEvent(this, ev.type, ev.detail); + fireEvent(this, ev.type, ev.detail, { composed: false }); } private _valueChanged(ev: PolymerChangedEvent) { diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index a7a4df3e61..2f611f859c 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -5,6 +5,7 @@ import { CSSResult, customElement, html, + internalProperty, LitElement, property, TemplateResult, @@ -20,6 +21,7 @@ import { DataTableColumnContainer } from "../../../components/data-table/ha-data import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-fab"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-button-related-filter-menu"; import { AutomationEntity, triggerAutomationActions, @@ -45,14 +47,33 @@ class HaAutomationPicker extends LitElement { @property() public automations!: AutomationEntity[]; - private _automations = memoizeOne((automations: AutomationEntity[]) => { - return automations.map((automation) => { - return { - ...automation, - name: computeStateName(automation), - }; - }); - }); + @property() private _activeFilters?: string[]; + + @internalProperty() private _filteredAutomations?: string[] | null; + + @internalProperty() private _filterValue?; + + private _automations = memoizeOne( + ( + automations: AutomationEntity[], + filteredAutomations?: string[] | null + ) => { + if (filteredAutomations === null) { + return []; + } + return (filteredAutomations + ? automations.filter((automation) => + filteredAutomations!.includes(automation.entity_id) + ) + : automations + ).map((automation) => { + return { + ...automation, + name: computeStateName(automation), + }; + }); + } + ); private _columns = memoizeOne( (narrow: boolean, _locale): DataTableColumnContainer => { @@ -192,17 +213,27 @@ class HaAutomationPicker extends LitElement { back-path="/config" .route=${this.route} .tabs=${configSections.automation} + .activeFilters=${this._activeFilters} .columns=${this._columns(this.narrow, this.hass.locale)} - .data=${this._automations(this.automations)} - id="entity_id" + .data=${this._automations(this.automations, this._filteredAutomations)} .noDataText=${this.hass.localize( "ui.panel.config.automation.picker.no_automations" )} + @clear-filter=${this._clearFilter} hasFab > + +