diff --git a/gallery/src/data/media_players.ts b/gallery/src/data/media_players.ts index 07dc174e8c..1d343c5bad 100644 --- a/gallery/src/data/media_players.ts +++ b/gallery/src/data/media_players.ts @@ -7,8 +7,8 @@ export const createMediaPlayerEntities = () => [ media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", media_artist: "Technohead", // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + - // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media - supported_features: 195135, + // Select Source + Stop + Clear + Play + Shuffle Set + supported_features: 64063, entity_picture: "/images/album_cover_2.jpg", media_duration: 300, media_position: 50, @@ -24,8 +24,8 @@ export const createMediaPlayerEntities = () => [ media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", media_artist: "Technohead", // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + - // Select Source + Stop + Clear + Play + Shuffle Set - supported_features: 64063, + // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + supported_features: 195135, entity_picture: "/images/album_cover.jpg", media_duration: 300, media_position: 0, diff --git a/gallery/src/demos/demo-hui-media-control-card.ts b/gallery/src/demos/demo-hui-media-control-card.ts index 2847ee3e61..1f12c851bc 100644 --- a/gallery/src/demos/demo-hui-media-control-card.ts +++ b/gallery/src/demos/demo-hui-media-control-card.ts @@ -146,6 +146,16 @@ const CONFIGS = [ entity: media_player.receiver_off `, }, + { + heading: "Grid Full Size", + config: ` + - type: grid + columns: 1 + cards: + - type: media-control + entity: media_player.music_paused + `, + }, ]; class DemoHuiMediControlCard extends PolymerElement { diff --git a/hassio/src/dashboard/hassio-update.ts b/hassio/src/dashboard/hassio-update.ts index b8d9cef96b..b2c8bb8bd9 100644 --- a/hassio/src/dashboard/hassio-update.ts +++ b/hassio/src/dashboard/hassio-update.ts @@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement { "Supervisor", this.supervisor.supervisor, "hassio/supervisor/update", - `https://github.com//home-assistant/hassio/releases/tag/${ - this.supervisor.supervisor.version_latest - }` + `https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}` )} ${this.supervisor.host.features.includes("hassos") ? this._renderUpdateCard( diff --git a/hassio/src/dialogs/network/dialog-hassio-network.ts b/hassio/src/dialogs/network/dialog-hassio-network.ts index 382cc0ede3..6a8deb6088 100644 --- a/hassio/src/dialogs/network/dialog-hassio-network.ts +++ b/hassio/src/dialogs/network/dialog-hassio-network.ts @@ -137,8 +137,7 @@ export class DialogHassioNetwork extends LitElement )} ${this._interface?.type === "wireless" ? html` - - Wi-Fi + ${this._interface?.wifi?.ssid ? html`

Connected to: ${this._interface?.wifi?.ssid}

` : ""} @@ -281,8 +280,10 @@ export class DialogHassioNetwork extends LitElement private _renderIPConfiguration(version: string) { return html` - - IPv${version.charAt(version.length - 1)} +
{ + if (!value || Array.isArray(value)) { + return value; + } + return [value]; +}; diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 7a2bf77f2a..f946d34015 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -67,6 +67,10 @@ export const computeStateDisplay = ( } } + if (domain === "counter") { + return formatNumber(compareState, language); + } + return ( // Return device class translation (stateObj.attributes.device_class && diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index bfd61e7479..ed3d5c25f3 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -139,7 +139,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { private _filteredDevices: DeviceRegistryEntry[] = []; - private _getDevices = memoizeOne( + private _getAreasWithDevices = memoizeOne( ( devices: DeviceRegistryEntry[], areas: AreaRegistryEntry[], @@ -277,7 +277,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { if (!this._devices || !this._areas || !this._entities) { return html``; } - const areas = this._getDevices( + const areas = this._getAreasWithDevices( this._devices, this._areas, this._entities, diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index bc43292268..34897f2c5a 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -111,6 +111,18 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { @property({ type: Boolean }) private _opened?: boolean; + public open() { + this.updateComplete.then(() => { + (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open(); + }); + } + + public focus() { + this.updateComplete.then(() => { + this.shadowRoot?.querySelector("paper-input")?.focus(); + }); + } + private _getDevices = memoizeOne( ( devices: DeviceRegistryEntry[], @@ -126,14 +138,17 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { } const deviceEntityLookup: DeviceEntityLookup = {}; - for (const entity of entities) { - if (!entity.device_id) { - continue; + + if (includeDomains || excludeDomains || includeDeviceClasses) { + for (const entity of entities) { + if (!entity.device_id) { + continue; + } + if (!(entity.device_id in deviceEntityLookup)) { + deviceEntityLookup[entity.device_id] = []; + } + deviceEntityLookup[entity.device_id].push(entity); } - if (!(entity.device_id in deviceEntityLookup)) { - deviceEntityLookup[entity.device_id] = []; - } - deviceEntityLookup[entity.device_id].push(entity); } const areaLookup: { [areaId: string]: AreaRegistryEntry } = {}; @@ -141,7 +156,9 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { areaLookup[area.area_id] = area; } - let inputDevices = [...devices]; + let inputDevices = devices.filter( + (device) => device.id === this.value || !device.disabled_by + ); if (includeDomains) { inputDevices = inputDevices.filter((device) => { diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 6b06f855c2..c83c6a44e3 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -101,6 +101,18 @@ export class HaEntityPicker extends LitElement { @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement; + public open() { + this.updateComplete.then(() => { + (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open(); + }); + } + + public focus() { + this.updateComplete.then(() => { + this.shadowRoot?.querySelector("paper-input")?.focus(); + }); + } + private _initedStates = false; private _states: HassEntity[] = []; diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 0a12ecd0d8..7a9cb710d1 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -28,6 +28,18 @@ import { import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { PolymerChangedEvent } from "../polymer-types"; import { HomeAssistant } from "../types"; +import memoizeOne from "memoize-one"; +import { + DeviceEntityLookup, + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../data/device_registry"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../data/entity_registry"; +import { computeDomain } from "../common/entity/compute_domain"; +import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; const rowRenderer = ( root: HTMLElement, @@ -68,39 +80,227 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { @property() public value?: string; - @property() public _areas?: AreaRegistryEntry[]; + @property() public placeholder?: string; @property({ type: Boolean, attribute: "no-add" }) public noAdd?: boolean; + /** + * Show only areas with entities from specific domains. + * @type {Array} + * @attr include-domains + */ + @property({ type: Array, attribute: "include-domains" }) + public includeDomains?: string[]; + + /** + * Show no areas with entities of these domains. + * @type {Array} + * @attr exclude-domains + */ + @property({ type: Array, attribute: "exclude-domains" }) + public excludeDomains?: string[]; + + /** + * Show only areas with entities of these device classes. + * @type {Array} + * @attr include-device-classes + */ + @property({ type: Array, attribute: "include-device-classes" }) + public includeDeviceClasses?: string[]; + + @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; + + @property() public entityFilter?: (entity: EntityRegistryEntry) => boolean; + + @internalProperty() private _areas?: AreaRegistryEntry[]; + + @internalProperty() private _devices?: DeviceRegistryEntry[]; + + @internalProperty() private _entities?: EntityRegistryEntry[]; + @internalProperty() private _opened?: boolean; public hassSubscribe(): UnsubscribeFunc[] { return [ subscribeAreaRegistry(this.hass.connection!, (areas) => { - this._areas = this.noAdd - ? areas - : [ - ...areas, - { - area_id: "add_new", - name: this.hass.localize("ui.components.area-picker.add_new"), - }, - ]; + this._areas = areas; + }), + subscribeDeviceRegistry(this.hass.connection!, (devices) => { + this._devices = devices; + }), + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entities = entities; }), ]; } + public open() { + this.updateComplete.then(() => { + (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open(); + }); + } + + public focus() { + this.updateComplete.then(() => { + this.shadowRoot?.querySelector("paper-input")?.focus(); + }); + } + + private _getAreas = memoizeOne( + ( + areas: AreaRegistryEntry[], + devices: DeviceRegistryEntry[], + entities: EntityRegistryEntry[], + includeDomains: this["includeDomains"], + excludeDomains: this["excludeDomains"], + includeDeviceClasses: this["includeDeviceClasses"], + deviceFilter: this["deviceFilter"], + entityFilter: this["entityFilter"], + noAdd: this["noAdd"] + ): AreaRegistryEntry[] => { + const deviceEntityLookup: DeviceEntityLookup = {}; + let inputDevices: DeviceRegistryEntry[] | undefined; + let inputEntities: EntityRegistryEntry[] | undefined; + + if (includeDomains || excludeDomains || includeDeviceClasses) { + for (const entity of entities) { + if (!entity.device_id) { + continue; + } + if (!(entity.device_id in deviceEntityLookup)) { + deviceEntityLookup[entity.device_id] = []; + } + deviceEntityLookup[entity.device_id].push(entity); + } + inputDevices = devices; + inputEntities = entities.filter((entity) => entity.area_id); + } else if (deviceFilter) { + inputDevices = devices; + } else if (entityFilter) { + inputEntities = entities.filter((entity) => entity.area_id); + } + + if (includeDomains) { + inputDevices = inputDevices!.filter((device) => { + const devEntities = deviceEntityLookup[device.id]; + if (!devEntities || !devEntities.length) { + return false; + } + return deviceEntityLookup[device.id].some((entity) => + includeDomains.includes(computeDomain(entity.entity_id)) + ); + }); + inputEntities = inputEntities!.filter((entity) => + includeDomains.includes(computeDomain(entity.entity_id)) + ); + } + + if (excludeDomains) { + inputDevices = inputDevices!.filter((device) => { + const devEntities = deviceEntityLookup[device.id]; + if (!devEntities || !devEntities.length) { + return true; + } + return entities.every( + (entity) => + !excludeDomains.includes(computeDomain(entity.entity_id)) + ); + }); + inputEntities = inputEntities!.filter( + (entity) => !excludeDomains.includes(computeDomain(entity.entity_id)) + ); + } + + if (includeDeviceClasses) { + inputDevices = inputDevices!.filter((device) => { + const devEntities = deviceEntityLookup[device.id]; + if (!devEntities || !devEntities.length) { + return false; + } + return deviceEntityLookup[device.id].some((entity) => { + const stateObj = this.hass.states[entity.entity_id]; + if (!stateObj) { + return false; + } + return ( + stateObj.attributes.device_class && + includeDeviceClasses.includes(stateObj.attributes.device_class) + ); + }); + }); + inputEntities = inputEntities!.filter((entity) => { + const stateObj = this.hass.states[entity.entity_id]; + return ( + stateObj.attributes.device_class && + includeDeviceClasses.includes(stateObj.attributes.device_class) + ); + }); + } + + if (deviceFilter) { + inputDevices = inputDevices!.filter((device) => deviceFilter!(device)); + } + + if (entityFilter) { + entities = entities.filter((entity) => entityFilter!(entity)); + } + + let outputAreas = areas; + + let areaIds: string[] | undefined; + + if (inputDevices) { + areaIds = inputDevices + .filter((device) => device.area_id) + .map((device) => device.area_id!); + } + + if (inputEntities) { + areaIds = (areaIds ?? []).concat( + inputEntities + .filter((entity) => entity.area_id) + .map((entity) => entity.area_id!) + ); + } + + if (areaIds) { + outputAreas = areas.filter((area) => areaIds!.includes(area.area_id)); + } + + return noAdd + ? outputAreas + : [ + ...outputAreas, + { + area_id: "add_new", + name: this.hass.localize("ui.components.area-picker.add_new"), + }, + ]; + } + ); + protected render(): TemplateResult { - if (!this._areas) { + if (!this._devices || !this._areas || !this._entities) { return html``; } + const areas = this._getAreas( + this._areas, + this._devices, + this._entities, + this.includeDomains, + this.excludeDomains, + this.includeDeviceClasses, + this.deviceFilter, + this.entityFilter, + this.noAdd + ); return html` ` : ""} - ${this._areas.length > 0 + ${areas.length > 0 ? html` { + return this._areas?.find((area) => area.area_id === areaId); + }); + private _clearValue(ev: Event) { ev.stopPropagation(); this._setValue(""); diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts index cae0562cf0..f932cb4357 100644 --- a/src/components/ha-button-toggle-group.ts +++ b/src/components/ha-button-toggle-group.ts @@ -11,6 +11,7 @@ import { import { fireEvent } from "../common/dom/fire_event"; import type { ToggleButton } from "../types"; import "./ha-svg-icon"; +import "@material/mwc-button/mwc-button"; @customElement("ha-button-toggle-group") export class HaButtonToggleGroup extends LitElement { @@ -21,17 +22,22 @@ export class HaButtonToggleGroup extends LitElement { protected render(): TemplateResult { return html`
- ${this.buttons.map( - (button) => html` - - - - ` + ${this.buttons.map((button) => + button.iconPath + ? html` + + ` + : html`${button.label}` )}
`; @@ -49,13 +55,15 @@ export class HaButtonToggleGroup extends LitElement { --mdc-icon-button-size: var(--button-toggle-size, 36px); --mdc-icon-size: var(--button-toggle-icon-size, 20px); } - mwc-icon-button { + mwc-icon-button, + mwc-button { border: 1px solid var(--primary-color); border-right-width: 0px; position: relative; cursor: pointer; } - mwc-icon-button::before { + mwc-icon-button::before, + mwc-button::before { top: 0; left: 0; width: 100%; @@ -67,17 +75,21 @@ export class HaButtonToggleGroup extends LitElement { content: ""; transition: opacity 15ms linear, background-color 15ms linear; } - mwc-icon-button[active]::before { + mwc-icon-button[active]::before, + mwc-button[active]::before { opacity: var(--mdc-icon-button-ripple-opacity, 0.12); } - mwc-icon-button:first-child { + mwc-icon-button:first-child, + mwc-button:first-child { border-radius: 4px 0 0 4px; } - mwc-icon-button:last-child { + mwc-icon-button:last-child, + mwc-button:last-child { border-radius: 0 4px 4px 0; border-right-width: 1px; } - mwc-icon-button:only-child { + mwc-icon-button:only-child, + mwc-button:only-child { border-radius: 4px; border-right-width: 1px; } diff --git a/src/components/ha-expansion-panel.ts b/src/components/ha-expansion-panel.ts index b74022032d..f328e3590e 100644 --- a/src/components/ha-expansion-panel.ts +++ b/src/components/ha-expansion-panel.ts @@ -19,12 +19,14 @@ class HaExpansionPanel extends LitElement { @property({ type: Boolean, reflect: true }) outlined = false; + @property() header?: string; + @query(".container") private _container!: HTMLDivElement; protected render(): TemplateResult { return html`
- + ${this.header} ; + +@customElement("ha-fab") +export class HaFab extends MwcFab { + protected firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); + this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)"); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-fab": HaFab; + } +} diff --git a/src/components/ha-icon-input.ts b/src/components/ha-icon-input.ts index 83761e6c40..11f310c91f 100644 --- a/src/components/ha-icon-input.ts +++ b/src/components/ha-icon-input.ts @@ -60,8 +60,9 @@ export class HaIconInput extends LitElement { static get styles() { return css` ha-icon { - position: relative; - bottom: 4px; + position: absolute; + bottom: 2px; + right: 0; } `; } diff --git a/src/components/ha-selector/ha-selector-action.ts b/src/components/ha-selector/ha-selector-action.ts new file mode 100644 index 0000000000..c6e06e3a12 --- /dev/null +++ b/src/components/ha-selector/ha-selector-action.ts @@ -0,0 +1,45 @@ +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, +} from "lit-element"; +import { HomeAssistant } from "../../types"; +import { ActionSelector } from "../../data/selector"; +import { Action } from "../../data/script"; +import "../../panels/config/automation/action/ha-automation-action"; + +@customElement("ha-selector-action") +export class HaActionSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public selector!: ActionSelector; + + @property() public value?: Action; + + @property() public label?: string; + + protected render() { + return html``; + } + + static get styles(): CSSResult { + return css` + ha-automation-action { + display: block; + margin-bottom: 16px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-action": HaActionSelector; + } +} diff --git a/src/components/ha-selector/ha-selector-area.ts b/src/components/ha-selector/ha-selector-area.ts index fb3a051b69..d3f7d2f6a7 100644 --- a/src/components/ha-selector/ha-selector-area.ts +++ b/src/components/ha-selector/ha-selector-area.ts @@ -1,7 +1,16 @@ -import { customElement, html, LitElement, property } from "lit-element"; +import { + customElement, + html, + internalProperty, + LitElement, + property, +} from "lit-element"; import { HomeAssistant } from "../../types"; import { AreaSelector } from "../../data/selector"; import "../ha-area-picker"; +import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; +import { DeviceRegistryEntry } from "../../data/device_registry"; +import { EntityRegistryEntry } from "../../data/entity_registry"; @customElement("ha-selector-area") export class HaAreaSelector extends LitElement { @@ -13,14 +22,76 @@ export class HaAreaSelector extends LitElement { @property() public label?: string; + @internalProperty() public _configEntries?: ConfigEntry[]; + + protected updated(changedProperties) { + if (changedProperties.has("selector")) { + const oldSelector = changedProperties.get("selector"); + if ( + oldSelector !== this.selector && + this.selector.area.device?.integration + ) { + this._loadConfigEntries(); + } + } + } + protected render() { return html` this._filterDevices(device)} + .entityFilter=${(entity) => this._filterEntities(entity)} + .includeDeviceClasses=${this.selector.area.entity?.device_class + ? [this.selector.area.entity.device_class] + : undefined} + .includeDomains=${this.selector.area.entity?.domain + ? [this.selector.area.entity.domain] + : undefined} >`; } + + private _filterEntities(entity: EntityRegistryEntry): boolean { + if (this.selector.area.entity?.integration) { + if (entity.platform !== this.selector.area.entity.integration) { + return false; + } + } + return true; + } + + private _filterDevices(device: DeviceRegistryEntry): boolean { + if ( + this.selector.area.device?.manufacturer && + device.manufacturer !== this.selector.area.device.manufacturer + ) { + return false; + } + if ( + this.selector.area.device?.model && + device.model !== this.selector.area.device.model + ) { + return false; + } + if (this.selector.area.device?.integration) { + if ( + !this._configEntries?.some((entry) => + device.config_entries.includes(entry.entry_id) + ) + ) { + return false; + } + } + return true; + } + + private async _loadConfigEntries() { + this._configEntries = (await getConfigEntries(this.hass)).filter( + (entry) => entry.domain === this.selector.area.device?.integration + ); + } } declare global { diff --git a/src/components/ha-selector/ha-selector-entity.ts b/src/components/ha-selector/ha-selector-entity.ts index c32f995ae4..e6bfba75b5 100644 --- a/src/components/ha-selector/ha-selector-entity.ts +++ b/src/components/ha-selector/ha-selector-entity.ts @@ -19,7 +19,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) { @property() public selector!: EntitySelector; - @internalProperty() private _entities?: Record; + @internalProperty() private _entityPlaformLookup?: Record; @property() public value?: any; @@ -45,7 +45,7 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) { } entityLookup[confEnt.entity_id] = confEnt.platform; } - this._entities = entityLookup; + this._entityPlaformLookup = entityLookup; }), ]; } @@ -66,8 +66,9 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) { } if (this.selector.entity.integration) { if ( - !this._entities || - this._entities[entity.entity_id] !== this.selector.entity.integration + !this._entityPlaformLookup || + this._entityPlaformLookup[entity.entity_id] !== + this.selector.entity.integration ) { return false; } diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts new file mode 100644 index 0000000000..aa6d2cfdb5 --- /dev/null +++ b/src/components/ha-selector/ha-selector-target.ts @@ -0,0 +1,153 @@ +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, +} from "lit-element"; +import { HomeAssistant } from "../../types"; +import { TargetSelector } from "../../data/selector"; +import { ConfigEntry, getConfigEntries } from "../../data/config_entries"; +import { DeviceRegistryEntry } from "../../data/device_registry"; +import "../ha-target-picker"; +import "@material/mwc-list/mwc-list-item"; +import "@polymer/paper-input/paper-input"; +import "@material/mwc-list/mwc-list"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../data/entity_registry"; +import { Target } from "../../data/target"; +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { SubscribeMixin } from "../../mixins/subscribe-mixin"; + +@customElement("ha-selector-target") +export class HaTargetSelector extends SubscribeMixin(LitElement) { + @property() public hass!: HomeAssistant; + + @property() public selector!: TargetSelector; + + @property() public value?: Target; + + @property() public label?: string; + + @internalProperty() private _entityPlaformLookup?: Record; + + @internalProperty() private _configEntries?: ConfigEntry[]; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeEntityRegistry(this.hass.connection!, (entities) => { + const entityLookup = {}; + for (const confEnt of entities) { + if (!confEnt.platform) { + continue; + } + entityLookup[confEnt.entity_id] = confEnt.platform; + } + this._entityPlaformLookup = entityLookup; + }), + ]; + } + + protected updated(changedProperties) { + if (changedProperties.has("selector")) { + const oldSelector = changedProperties.get("selector"); + if ( + oldSelector !== this.selector && + this.selector.target.device?.integration + ) { + this._loadConfigEntries(); + } + } + } + + protected render() { + return html` this._filterDevices(device)} + .entityRegFilter=${(entity: EntityRegistryEntry) => + this._filterRegEntities(entity)} + .entityFilter=${(entity: HassEntity) => this._filterEntities(entity)} + .includeDeviceClasses=${this.selector.target.entity?.device_class + ? [this.selector.target.entity.device_class] + : undefined} + .includeDomains=${this.selector.target.entity?.domain + ? [this.selector.target.entity.domain] + : undefined} + >`; + } + + private _filterEntities(entity: HassEntity): boolean { + if (this.selector.target.entity?.integration) { + if ( + !this._entityPlaformLookup || + this._entityPlaformLookup[entity.entity_id] !== + this.selector.target.entity.integration + ) { + return false; + } + } + return true; + } + + private _filterRegEntities(entity: EntityRegistryEntry): boolean { + if (this.selector.target.entity?.integration) { + if (entity.platform !== this.selector.target.entity.integration) { + return false; + } + } + return true; + } + + private _filterDevices(device: DeviceRegistryEntry): boolean { + if ( + this.selector.target.device?.manufacturer && + device.manufacturer !== this.selector.target.device.manufacturer + ) { + return false; + } + if ( + this.selector.target.device?.model && + device.model !== this.selector.target.device.model + ) { + return false; + } + if (this.selector.target.device?.integration) { + if ( + !this._configEntries?.some((entry) => + device.config_entries.includes(entry.entry_id) + ) + ) { + return false; + } + } + return true; + } + + private async _loadConfigEntries() { + this._configEntries = (await getConfigEntries(this.hass)).filter( + (entry) => entry.domain === this.selector.target.device?.integration + ); + } + + static get styles(): CSSResult { + return css` + ha-target-picker { + margin: 0 -8px; + display: block; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-target": HaTargetSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 5e607d61fd..3c88b2da25 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -5,9 +5,11 @@ import { HomeAssistant } from "../../types"; import "./ha-selector-entity"; import "./ha-selector-device"; import "./ha-selector-area"; +import "./ha-selector-target"; import "./ha-selector-number"; import "./ha-selector-boolean"; import "./ha-selector-time"; +import "./ha-selector-action"; import { Selector } from "../../data/selector"; @customElement("ha-selector") diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts new file mode 100644 index 0000000000..68a6e0701e --- /dev/null +++ b/src/components/ha-target-picker.ts @@ -0,0 +1,595 @@ +import { + css, + CSSResult, + customElement, + html, + internalProperty, + LitElement, + property, + query, + unsafeCSS, +} from "lit-element"; +import { HomeAssistant } from "../types"; +// @ts-ignore +import chipStyles from "@material/chips/dist/mdc.chips.min.css"; +import { + mdiSofa, + mdiDevices, + mdiClose, + mdiPlus, + mdiUnfoldMoreVertical, +} from "@mdi/js"; +import "./ha-svg-icon"; +import "./ha-icon"; +import "@material/mwc-icon-button/mwc-icon-button"; +import { classMap } from "lit-html/directives/class-map"; +import "@material/mwc-button/mwc-button"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { + AreaRegistryEntry, + subscribeAreaRegistry, +} from "../data/area_registry"; +import { + DeviceRegistryEntry, + subscribeDeviceRegistry, +} from "../data/device_registry"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../data/entity_registry"; +import { SubscribeMixin } from "../mixins/subscribe-mixin"; +import { computeStateName } from "../common/entity/compute_state_name"; +import { stateIcon } from "../common/entity/state_icon"; +import { fireEvent } from "../common/dom/fire_event"; +import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; +import { computeDomain } from "../common/entity/compute_domain"; +import { Target } from "../data/target"; +import { ensureArray } from "../common/ensure-array"; +import "./entity/ha-entity-picker"; +import "./device/ha-device-picker"; +import "./ha-area-picker"; +import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; +import "@polymer/paper-tooltip/paper-tooltip"; + +@customElement("ha-target-picker") +export class HaTargetPicker extends SubscribeMixin(LitElement) { + @property() public hass!: HomeAssistant; + + @property() public value?: Target; + + @property() public label?: string; + + /** + * Show only targets with entities from specific domains. + * @type {Array} + * @attr include-domains + */ + @property({ type: Array, attribute: "include-domains" }) + public includeDomains?: string[]; + + /** + * Show only targets with entities of these device classes. + * @type {Array} + * @attr include-device-classes + */ + @property({ type: Array, attribute: "include-device-classes" }) + public includeDeviceClasses?: string[]; + + @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; + + @property() public entityRegFilter?: (entity: EntityRegistryEntry) => boolean; + + @property() public entityFilter?: HaEntityPickerEntityFilterFunc; + + @internalProperty() private _areas?: { [areaId: string]: AreaRegistryEntry }; + + @internalProperty() private _devices?: { + [deviceId: string]: DeviceRegistryEntry; + }; + + @internalProperty() private _entities?: EntityRegistryEntry[]; + + @internalProperty() private _addMode?: "area_id" | "entity_id" | "device_id"; + + @query("#input") private _inputElement?; + + public hassSubscribe(): UnsubscribeFunc[] { + return [ + subscribeAreaRegistry(this.hass.connection!, (areas) => { + const areaLookup: { [areaId: string]: AreaRegistryEntry } = {}; + for (const area of areas) { + areaLookup[area.area_id] = area; + } + this._areas = areaLookup; + }), + subscribeDeviceRegistry(this.hass.connection!, (devices) => { + const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {}; + for (const device of devices) { + deviceLookup[device.id] = device; + } + this._devices = deviceLookup; + }), + subscribeEntityRegistry(this.hass.connection!, (entities) => { + this._entities = entities; + }), + ]; + } + + protected render() { + if (!this._areas || !this._devices || !this._entities) { + return html``; + } + return html`
+ ${ensureArray(this.value?.area_id)?.map((area_id) => { + const area = this._areas![area_id]; + return this._renderChip( + "area_id", + area_id, + area?.name || area_id, + undefined, + mdiSofa + ); + })} + ${ensureArray(this.value?.device_id)?.map((device_id) => { + const device = this._devices![device_id]; + return this._renderChip( + "device_id", + device_id, + device?.name || device_id, + undefined, + mdiDevices + ); + })} + ${ensureArray(this.value?.entity_id)?.map((entity_id) => { + const entity = this.hass.states[entity_id]; + return this._renderChip( + "entity_id", + entity_id, + entity ? computeStateName(entity) : entity_id, + entity ? stateIcon(entity) : undefined + ); + })} +
+ ${this._renderPicker()} +
+
+
+ + + + ${this.hass.localize( + "ui.components.target-picker.add_area_id" + )} + + +
+
+
+ + + + ${this.hass.localize( + "ui.components.target-picker.add_device_id" + )} + + +
+
+
+ + + + ${this.hass.localize( + "ui.components.target-picker.add_entity_id" + )} + + +
+
`; + } + + private async _showPicker(ev) { + this._addMode = ev.currentTarget.type; + await this.updateComplete; + setTimeout(() => { + this._inputElement?.open(); + this._inputElement?.focus(); + }, 0); + } + + private _renderChip( + type: string, + id: string, + name: string, + icon?: string, + iconPath?: string + ) { + return html` +
+ ${iconPath + ? html`` + : ""} + ${icon + ? html`` + : ""} + + + ${name} + + + ${type === "entity_id" + ? "" + : html` + + + + ${this.hass.localize( + `ui.components.target-picker.expand_${type}` + )} + `} + + + + + ${this.hass.localize( + `ui.components.target-picker.remove_${type}` + )} + +
+ `; + } + + private _renderPicker() { + switch (this._addMode) { + case "area_id": + return html``; + case "device_id": + return html``; + case "entity_id": + return html``; + } + return html``; + } + + private _targetPicked(ev) { + ev.stopPropagation(); + if (!ev.detail.value) { + return; + } + const value = ev.detail.value; + const target = ev.currentTarget; + target.value = ""; + this._addMode = undefined; + fireEvent(this, "value-changed", { + value: this.value + ? { + ...this.value, + [target.type]: this.value[target.type] + ? [...ensureArray(this.value[target.type]), value] + : value, + } + : { [target.type]: value }, + }); + } + + private _handleExpand(ev) { + const target = ev.currentTarget as any; + const newDevices: string[] = []; + const newEntities: string[] = []; + if (target.type === "area_id") { + Object.values(this._devices!).forEach((device) => { + if ( + device.area_id === target.id && + !this.value!.device_id?.includes(device.id) && + this._deviceMeetsFilter(device) + ) { + newDevices.push(device.id); + } + }); + this._entities!.forEach((entity) => { + if ( + entity.area_id === target.id && + !this.value!.entity_id?.includes(entity.entity_id) && + this._entityRegMeetsFilter(entity) + ) { + newEntities.push(entity.entity_id); + } + }); + } else if (target.type === "device_id") { + this._entities!.forEach((entity) => { + if ( + entity.device_id === target.id && + !this.value!.entity_id?.includes(entity.entity_id) && + this._entityRegMeetsFilter(entity) + ) { + newEntities.push(entity.entity_id); + } + }); + } else { + return; + } + let value = this.value; + if (newEntities.length) { + value = this._addItems(value, "entity_id", newEntities); + } + if (newDevices.length) { + value = this._addItems(value, "device_id", newDevices); + } + value = this._removeItem(value, target.type, target.id); + fireEvent(this, "value-changed", { value }); + } + + private _handleRemove(ev) { + const target = ev.currentTarget as any; + fireEvent(this, "value-changed", { + value: this._removeItem(this.value, target.type, target.id), + }); + } + + private _addItems( + value: this["value"], + type: string, + ids: string[] + ): this["value"] { + return { + ...value, + [type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids, + }; + } + + private _removeItem( + value: this["value"], + type: string, + id: string + ): this["value"] { + return { + ...value, + [type]: ensureArray(value![type])!.filter((val) => val !== id), + }; + } + + private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean { + const devEntities = this._entities?.filter( + (entity) => entity.device_id === device.id + ); + if (this.includeDomains) { + if (!devEntities || !devEntities.length) { + return false; + } + if ( + !devEntities.some((entity) => + this.includeDomains!.includes(computeDomain(entity.entity_id)) + ) + ) { + return false; + } + } + + if (this.includeDeviceClasses) { + if (!devEntities || !devEntities.length) { + return false; + } + if ( + !devEntities.some((entity) => { + const stateObj = this.hass.states[entity.entity_id]; + if (!stateObj) { + return false; + } + return ( + stateObj.attributes.device_class && + this.includeDeviceClasses!.includes( + stateObj.attributes.device_class + ) + ); + }) + ) { + return false; + } + } + + if (this.deviceFilter) { + return this.deviceFilter(device); + } + return true; + } + + private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean { + if ( + this.includeDomains && + !this.includeDomains.includes(computeDomain(entity.entity_id)) + ) { + return false; + } + if (this.includeDeviceClasses) { + const stateObj = this.hass.states[entity.entity_id]; + if (!stateObj) { + return false; + } + if ( + !stateObj.attributes.device_class || + !this.includeDeviceClasses!.includes(stateObj.attributes.device_class) + ) { + return false; + } + } + if (this.entityRegFilter) { + return this.entityRegFilter(entity); + } + return true; + } + + static get styles(): CSSResult { + return css` + ${unsafeCSS(chipStyles)} + .mdc-chip { + color: var(--primary-text-color); + } + .items { + z-index: 2; + } + .mdc-chip.add { + color: rgba(0, 0, 0, 0.87); + } + .mdc-chip:not(.add) { + cursor: default; + } + .mdc-chip mwc-icon-button { + --mdc-icon-button-size: 24px; + display: flex; + align-items: center; + outline: none; + } + .mdc-chip mwc-icon-button ha-svg-icon { + border-radius: 50%; + background: var(--secondary-text-color); + } + .mdc-chip__icon.mdc-chip__icon--trailing { + width: 16px; + height: 16px; + --mdc-icon-size: 14px; + color: var(--card-background-color); + } + .mdc-chip__icon--leading { + display: flex; + align-items: center; + justify-content: center; + --mdc-icon-size: 20px; + border-radius: 50%; + padding: 6px; + margin-left: -14px !important; + } + .expand-btn { + margin-right: 0; + } + .mdc-chip.area_id:not(.add) { + border: 2px solid #fed6a4; + background: var(--card-background-color); + } + .mdc-chip.area_id:not(.add) .mdc-chip__icon--leading, + .mdc-chip.area_id.add { + background: #fed6a4; + } + .mdc-chip.device_id:not(.add) { + border: 2px solid #a8e1fb; + background: var(--card-background-color); + } + .mdc-chip.device_id:not(.add) .mdc-chip__icon--leading, + .mdc-chip.device_id.add { + background: #a8e1fb; + } + .mdc-chip.entity_id:not(.add) { + border: 2px solid #d2e7b9; + background: var(--card-background-color); + } + .mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading, + .mdc-chip.entity_id.add { + background: #d2e7b9; + } + .mdc-chip:hover { + z-index: 5; + } + paper-tooltip.expand { + min-width: 200px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-target-picker": HaTargetPicker; + } +} diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 93a5ca25e4..f1fbfab1d6 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -1,5 +1,5 @@ import "@material/mwc-button/mwc-button"; -import "@material/mwc-fab/mwc-fab"; +import "../ha-fab"; import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js"; @@ -170,7 +170,7 @@ export class HaMediaPlayerBrowse extends LitElement { > ${this._narrow && currentItem?.can_play ? html` - + ` : ""}
@@ -927,7 +927,7 @@ export class HaMediaPlayerBrowse extends LitElement { transition: width 0.4s, height 0.4s, padding-bottom 0.4s; } - mwc-fab { + ha-fab { position: absolute; --mdc-theme-secondary: var(--primary-color); bottom: -20px; @@ -1011,7 +1011,7 @@ export class HaMediaPlayerBrowse extends LitElement { margin-bottom: 0; } - :host([scroll]) mwc-fab { + :host([scroll]) ha-fab { bottom: 4px; right: 4px; --mdc-fab-box-shadow: none; diff --git a/src/data/automation.ts b/src/data/automation.ts index 88ee3f847c..272416abb0 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -6,7 +6,7 @@ import { navigate } from "../common/navigate"; import { Context, HomeAssistant } from "../types"; import { BlueprintInput } from "./blueprint"; import { DeviceCondition, DeviceTrigger } from "./device_automation"; -import { Action } from "./script"; +import { Action, MODES } from "./script"; export interface AutomationEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { @@ -26,7 +26,7 @@ export interface ManualAutomationConfig { trigger: Trigger[]; condition?: Condition[]; action: Action[]; - mode?: "single" | "restart" | "queued" | "parallel"; + mode?: typeof MODES[number]; max?: number; } diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 215110df1f..7d52f8dea9 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -17,6 +17,7 @@ export interface DeviceRegistryEntry { area_id?: string; name_by_user?: string; entry_type: "service" | null; + disabled_by: string | null; } export interface DeviceEntityLookup { @@ -26,6 +27,7 @@ export interface DeviceEntityLookup { export interface DeviceRegistryEntryMutableParams { area_id?: string | null; name_by_user?: string | null; + disabled_by?: string | null; } export const fallbackDeviceName = ( diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 179a41b496..81796ff822 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -10,6 +10,7 @@ export interface EntityRegistryEntry { platform: string; config_entry_id?: string; device_id?: string; + area_id?: string; disabled_by: string | null; } @@ -29,6 +30,7 @@ export interface UpdateEntityRegistryEntryResult { export interface EntityRegistryEntryUpdateParams { name?: string | null; icon?: string | null; + area_id?: string | null; disabled_by?: string | null; new_entity_id?: string; } diff --git a/src/data/script.ts b/src/data/script.ts index cb434d7dfc..a7cfa7506a 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -7,13 +7,13 @@ import { navigate } from "../common/navigate"; import { HomeAssistant } from "../types"; import { Condition, Trigger } from "./automation"; -export const MODES = ["single", "restart", "queued", "parallel"]; +export const MODES = ["single", "restart", "queued", "parallel"] as const; export const MODES_MAX = ["queued", "parallel"]; export interface ScriptEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { last_triggered: string; - mode: "single" | "restart" | "queued" | "parallel"; + mode: typeof MODES[number]; current?: number; max?: number; }; @@ -23,7 +23,7 @@ export interface ScriptConfig { alias: string; sequence: Action[]; icon?: string; - mode?: "single" | "restart" | "queued" | "parallel"; + mode?: typeof MODES[number]; max?: number; } diff --git a/src/data/selector.ts b/src/data/selector.ts index ab56f9f016..82749ae90c 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -2,9 +2,11 @@ export type Selector = | EntitySelector | DeviceSelector | AreaSelector + | TargetSelector | NumberSelector | BooleanSelector - | TimeSelector; + | TimeSelector + | ActionSelector; export interface EntitySelector { entity: { @@ -19,13 +21,41 @@ export interface DeviceSelector { integration?: string; manufacturer?: string; model?: string; - entity?: EntitySelector["entity"]; + entity?: { + domain?: EntitySelector["entity"]["domain"]; + device_class?: EntitySelector["entity"]["device_class"]; + }; }; } export interface AreaSelector { - // eslint-disable-next-line @typescript-eslint/ban-types - area: {}; + area: { + entity?: { + integration?: EntitySelector["entity"]["integration"]; + domain?: EntitySelector["entity"]["domain"]; + device_class?: EntitySelector["entity"]["device_class"]; + }; + device?: { + integration?: DeviceSelector["device"]["integration"]; + manufacturer?: DeviceSelector["device"]["manufacturer"]; + model?: DeviceSelector["device"]["model"]; + }; + }; +} + +export interface TargetSelector { + target: { + entity?: { + integration?: EntitySelector["entity"]["integration"]; + domain?: EntitySelector["entity"]["domain"]; + device_class?: EntitySelector["entity"]["device_class"]; + }; + device?: { + integration?: DeviceSelector["device"]["integration"]; + manufacturer?: DeviceSelector["device"]["manufacturer"]; + model?: DeviceSelector["device"]["model"]; + }; + }; } export interface NumberSelector { @@ -47,3 +77,8 @@ export interface TimeSelector { // eslint-disable-next-line @typescript-eslint/ban-types time: {}; } + +export interface ActionSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + action: {}; +} diff --git a/src/data/target.ts b/src/data/target.ts new file mode 100644 index 0000000000..afddff0688 --- /dev/null +++ b/src/data/target.ts @@ -0,0 +1,5 @@ +export interface Target { + entity_id?: string[]; + device_id?: string[]; + area_id?: string[]; +} diff --git a/src/data/user.ts b/src/data/user.ts index bd35ff1896..5fe1f4f259 100644 --- a/src/data/user.ts +++ b/src/data/user.ts @@ -9,6 +9,7 @@ export const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN]; export interface User { id: string; + username: string | null; name: string; is_owner: boolean; is_active: boolean; @@ -19,6 +20,7 @@ export interface User { export interface UpdateUserParams { name?: User["name"]; + is_active?: User["is_active"]; group_ids?: User["group_ids"]; } diff --git a/src/data/zha.ts b/src/data/zha.ts index 5f4cb3aa18..96c8166a24 100644 --- a/src/data/zha.ts +++ b/src/data/zha.ts @@ -27,6 +27,7 @@ export interface ZHADevice { device_type: string; signature: any; neighbors: Neighbor[]; + pairing_status?: string; } export interface Neighbor { @@ -270,3 +271,23 @@ export const addGroup = ( group_name: groupName, members: membersToAdd, }); + +export const INITIALIZED = "INITIALIZED"; +export const INTERVIEW_COMPLETE = "INTERVIEW_COMPLETE"; +export const CONFIGURED = "CONFIGURED"; +export const PAIRED = "PAIRED"; +export const INCOMPLETE_PAIRING_STATUSES = [ + PAIRED, + CONFIGURED, + INTERVIEW_COMPLETE, +]; + +export const DEVICE_JOINED = "device_joined"; +export const RAW_DEVICE_INITIALIZED = "raw_device_initialized"; +export const DEVICE_FULLY_INITIALIZED = "device_fully_initialized"; +export const DEVICE_MESSAGE_TYPES = [ + DEVICE_JOINED, + RAW_DEVICE_INITIALIZED, + DEVICE_FULLY_INITIALIZED, +]; +export const LOG_OUTPUT = "log_output"; diff --git a/src/dialogs/notifications/notification-item-template.ts b/src/dialogs/notifications/notification-item-template.ts index bde508416d..f6b5d319d8 100644 --- a/src/dialogs/notifications/notification-item-template.ts +++ b/src/dialogs/notifications/notification-item-template.ts @@ -46,7 +46,7 @@ export class HuiNotificationItemTemplate extends LitElement { } .actions { - border-top: 1px solid #e8e8e8; + border-top: 1px solid var(--divider-color, #e8e8e8); padding: 5px 16px; } diff --git a/src/html/index.html.template b/src/html/index.html.template index 4628170fe9..a21eef0737 100644 --- a/src/html/index.html.template +++ b/src/html/index.html.template @@ -42,7 +42,7 @@ #ha-init-skeleton::before { display: block; content: ""; - height: 112px; + height: 56px; background-color: #THEMEC; } html { diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index b9de85cb27..66cea0e01b 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -1,4 +1,4 @@ -import "@material/mwc-fab"; +import "../../../components/ha-fab"; import { mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; @@ -124,7 +124,7 @@ export class HaConfigAreasDashboard extends LitElement { icon="hass:help-circle" @click=${this._showHelp} >
- - + `; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index e12af7d52a..ca720af2a9 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -39,7 +39,7 @@ export class HaWaitForTriggerAction extends LitElement )} >
diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index 2a966254b0..88702161b4 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -63,7 +63,7 @@ export class HaBlueprintAutomationEditor extends LitElement { protected render() { const blueprint = this._blueprint; - return html` + return html` ${!this.narrow ? html` ${this.config.alias} ` : ""} @@ -119,7 +119,7 @@ export class HaBlueprintAutomationEditor extends LitElement { - + ${this.hass.localize( "ui.panel.config.automation.editor.blueprint.header" @@ -185,6 +185,7 @@ export class HaBlueprintAutomationEditor extends LitElement { >` : html` ` : ""} - - + `; } @@ -524,21 +526,18 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { .content { padding-bottom: 20px; } - span[slot="introduction"] a { - color: var(--primary-color); - } p { margin-bottom: 0; } ha-entity-toggle { margin-right: 8px; } - mwc-fab { + ha-fab { position: relative; bottom: calc(-80px - env(safe-area-inset-bottom)); transition: bottom 0.3s; } - mwc-fab.dirty { + ha-fab.dirty { bottom: 0; } .selected_menu_item { diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index e333c5c542..616c4b4b2f 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -1,4 +1,4 @@ -import "@material/mwc-fab"; +import "../../../components/ha-fab"; import "@material/mwc-icon-button"; import { mdiPlus, mdiHelpCircle } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; @@ -170,7 +170,7 @@ class HaAutomationPicker extends LitElement { - - + `; } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index d32fdf2dae..a3ad1219db 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -309,14 +309,6 @@ export class HaManualAutomationEditor extends LitElement { ha-card { overflow: hidden; } - .errors { - padding: 20px; - font-weight: bold; - color: var(--error-color); - } - .content { - padding-bottom: 20px; - } span[slot="introduction"] a { color: var(--primary-color); } @@ -326,20 +318,6 @@ export class HaManualAutomationEditor extends LitElement { ha-entity-toggle { margin-right: 8px; } - mwc-fab { - position: relative; - bottom: calc(-80px - env(safe-area-inset-bottom)); - transition: bottom 0.3s; - } - mwc-fab.dirty { - bottom: 0; - } - .selected_menu_item { - color: var(--primary-color); - } - li[role="separator"] { - border-bottom-color: var(--divider-color); - } `, ]; } diff --git a/src/panels/config/blueprint/dialog-import-blueprint.ts b/src/panels/config/blueprint/dialog-import-blueprint.ts index fbc82ed7bc..1abb786fec 100644 --- a/src/panels/config/blueprint/dialog-import-blueprint.ts +++ b/src/panels/config/blueprint/dialog-import-blueprint.ts @@ -4,7 +4,6 @@ import "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import "../../../components/ha-circular-progress"; import { - css, CSSResult, customElement, html, @@ -97,12 +96,11 @@ class DialogImportBlueprint extends LitElement { )} > `} - - ${this.hass.localize( - "ui.panel.config.blueprint.add.raw_blueprint" - )} +
${this._result.raw_data}
` : html`${this.hass.localize( @@ -201,15 +199,8 @@ class DialogImportBlueprint extends LitElement { } } - static get styles(): CSSResult[] { - return [ - haStyleDialog, - css` - ha-expansion-panel { - --expansion-panel-summary-padding: 0; - } - `, - ]; + static get styles(): CSSResult { + return haStyleDialog; } } diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 4da528173e..ce732ab17d 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -1,4 +1,4 @@ -import "@material/mwc-fab"; +import "../../../components/ha-fab"; import "@material/mwc-icon-button"; import { mdiPlus, mdiHelpCircle, mdiDelete, mdiRobot } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; @@ -174,7 +174,7 @@ class HaBlueprintOverview extends LitElement { - - + `; } diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index d37fd2eaf5..9f74efeef2 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -8,7 +8,6 @@ import { html, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; @@ -31,7 +30,7 @@ export class HaDeviceEntitiesCard extends LitElement { @property() public entities!: EntityRegistryStateEntry[]; - @internalProperty() private _showDisabled = false; + @property() public showDisabled = false; private _entityRows: Array = []; @@ -68,7 +67,7 @@ export class HaDeviceEntitiesCard extends LitElement { })}
${disabledEntities.length - ? !this._showDisabled + ? !this.showDisabled ? html`