diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index 3c52475fd6..641eee4305 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -8,7 +8,8 @@ import { property, } from "lit-element"; import { LovelaceConfig } from "../../../../src/data/lovelace"; -import "../../../../src/panels/lovelace/hui-view"; +import "../../../../src/panels/lovelace/views/hui-view"; +import "../../../../src/panels/lovelace/views/hui-panel-view"; import { HomeAssistant } from "../../../../src/types"; import { Lovelace } from "../../../../src/panels/lovelace/types"; import "./hc-launch-screen"; @@ -40,14 +41,21 @@ class HcLovelace extends LitElement { saveConfig: async () => undefined, setEditMode: () => undefined, }; - return html` - - `; + return this.lovelaceConfig.views[index].panel + ? html` + + ` + : html` + + `; } protected updated(changedProps) { @@ -62,7 +70,9 @@ class HcLovelace extends LitElement { this.lovelaceConfig.background; if (configBackground) { - this.shadowRoot!.querySelector("hui-view")!.style.setProperty( + (this.shadowRoot!.querySelector( + "hui-view, hui-panel-view" + ) as HTMLElement)!.style.setProperty( "--lovelace-background", configBackground ); @@ -94,7 +104,7 @@ class HcLovelace extends LitElement { box-sizing: border-box; background: var(--primary-background-color); } - hui-view { + :host > * { flex: 1; } `; diff --git a/demo/src/stubs/translations.ts b/demo/src/stubs/translations.ts index 8dab63ef45..615159343d 100644 --- a/demo/src/stubs/translations.ts +++ b/demo/src/stubs/translations.ts @@ -97,7 +97,7 @@ export const mockTranslations = (hass: MockHomeAssistant) => { "component.nest.config.abort.authorize_url_timeout": "Timeout generating authorize url.", "component.nest.config.abort.no_flows": - "You need to configure Nest before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/nest/).", + "You need to configure Nest before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/integrations/nest/).", "component.nest.config.error.internal_error": "Internal error validating code", "component.nest.config.error.invalid_code": "Invalid code", @@ -199,7 +199,7 @@ export const mockTranslations = (hass: MockHomeAssistant) => { "component.point.config.abort.external_setup": "Point successfully configured from another flow.", "component.point.config.abort.no_flows": - "You need to configure Point before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/point/).", + "You need to configure Point before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/integrations/point/).", "component.point.config.create_entry.default": "Successfully authenticated with Minut for your Point device(s)", "component.point.config.error.follow_link": diff --git a/gallery/src/components/demo-cards.js b/gallery/src/components/demo-cards.js index 0516e3078a..a73bc53a2f 100644 --- a/gallery/src/components/demo-cards.js +++ b/gallery/src/components/demo-cards.js @@ -1,9 +1,9 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-toggle-button/paper-toggle-button"; import "./demo-card"; +import "../../../src/components/ha-switch"; class DemoCards extends PolymerElement { static get template() { @@ -26,9 +26,7 @@ class DemoCards extends PolymerElement {
- Show config + Show config
diff --git a/gallery/src/components/demo-more-infos.js b/gallery/src/components/demo-more-infos.js index 3bd1626a54..8f82e2a8ab 100644 --- a/gallery/src/components/demo-more-infos.js +++ b/gallery/src/components/demo-more-infos.js @@ -1,9 +1,9 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-toggle-button/paper-toggle-button"; import "./demo-more-info"; +import "../../../src/components/ha-switch"; class DemoMoreInfos extends PolymerElement { static get template() { @@ -26,9 +26,7 @@ class DemoMoreInfos extends PolymerElement {
- Show entity + Show entity
diff --git a/gallery/src/data/demo_states.js b/gallery/src/data/demo_states.js index dafbcfb866..a00841ffc4 100644 --- a/gallery/src/data/demo_states.js +++ b/gallery/src/data/demo_states.js @@ -36,7 +36,7 @@ export default { attributes: { title: "Welcome Home!", message: - "Here are some resources to get started:\n\n - [Configuring Home Assistant](https://home-assistant.io/getting-started/configuration/)\n - [Available components](https://home-assistant.io/components/)\n - [Troubleshooting your configuration](https://home-assistant.io/docs/configuration/troubleshooting/)\n - [Getting help](https://home-assistant.io/help/)\n\nTo not see this card popup in the future, edit your config in\n`configuration.yaml` and disable the `introduction` component.", + "Here are some resources to get started:\n\n - [Configuring Home Assistant](https://home-assistant.io/getting-started/configuration/)\n - [Available integrations](https://home-assistant.io/integrations/)\n - [Troubleshooting your configuration](https://home-assistant.io/docs/configuration/troubleshooting/)\n - [Getting help](https://home-assistant.io/help/)\n\nTo not see this card popup in the future, edit your config in\n`configuration.yaml` and disable the `introduction` integration.", }, last_changed: "2018-07-19T10:44:45.922241+00:00", last_updated: "2018-07-19T10:44:45.922241+00:00", diff --git a/hassio/src/addon-view/hassio-addon-info.js b/hassio/src/addon-view/hassio-addon-info.js index dc6c05d696..10df924f6e 100644 --- a/hassio/src/addon-view/hassio-addon-info.js +++ b/hassio/src/addon-view/hassio-addon-info.js @@ -2,19 +2,19 @@ import "@polymer/iron-icon/iron-icon"; import "@material/mwc-button"; import "@polymer/paper-card/paper-card"; import "@polymer/paper-tooltip/paper-tooltip"; -import "@polymer/paper-toggle-button/paper-toggle-button"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../../../src/components/ha-label-badge"; import "../../../src/components/ha-markdown"; import "../../../src/components/buttons/ha-call-api-button"; +import "../../../src/components/ha-switch"; import "../../../src/resources/ha-style"; +import "../components/hassio-card-content"; + import { EventsMixin } from "../../../src/mixins/events-mixin"; import { navigate } from "../../../src/common/navigate"; - import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; -import "../components/hassio-card-content"; const PERMIS_DESC = { rating: { @@ -122,7 +122,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) { width: 16px; color: var(--secondary-text-color); } - paper-toggle-button { + ha-switch { display: inline; } iron-icon.running { @@ -348,26 +348,26 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) { -
-
- -
+ `; } @@ -157,6 +159,7 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) { reflectToAttribute: true, }, hideSettings: { type: Boolean, value: false }, + hideEntities: { type: Boolean, value: false }, _childDevices: { type: Array, computed: "_computeChildDevices(device, devices)", diff --git a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts new file mode 100644 index 0000000000..b98e1a67c4 --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts @@ -0,0 +1,28 @@ +import { customElement } from "lit-element"; +import { + DeviceCondition, + fetchDeviceConditions, + localizeDeviceAutomationCondition, +} from "../../../../data/device_automation"; + +import "../../../../components/ha-card"; + +import { HaDeviceAutomationCard } from "./ha-device-automation-card"; + +@customElement("ha-device-conditions-card") +export class HaDeviceConditionsCard extends HaDeviceAutomationCard< + DeviceCondition +> { + protected type = "condition"; + protected headerKey = "ui.panel.config.devices.automation.conditions.caption"; + + constructor() { + super(localizeDeviceAutomationCondition, fetchDeviceConditions); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-device-conditions-card": HaDeviceConditionsCard; + } +} 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 new file mode 100644 index 0000000000..03129e5a1c --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -0,0 +1,171 @@ +import { + LitElement, + TemplateResult, + html, + property, + customElement, + css, + CSSResult, +} from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; + +import { HomeAssistant } from "../../../../types"; +import memoizeOne from "memoize-one"; + +import { compare } from "../../../../common/string/compare"; +import "../../../../components/entity/state-badge"; + +import "@polymer/paper-item/paper-item"; +import "@polymer/paper-item/paper-icon-item"; +import "@polymer/paper-item/paper-item-body"; + +import "../../../../components/ha-card"; +import "../../../../components/ha-icon"; +import "../../../../components/ha-switch"; +import { computeStateName } from "../../../../common/entity/compute_state_name"; +import { EntityRegistryEntry } from "../../../../data/entity_registry"; +import { showEntityRegistryDetailDialog } from "../../entity_registry/show-dialog-entity-registry-detail"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeDomain } from "../../../../common/entity/compute_domain"; +import { domainIcon } from "../../../../common/entity/domain_icon"; +// tslint:disable-next-line +import { HaSwitch } from "../../../../components/ha-switch"; + +@customElement("ha-device-entities-card") +export class HaDeviceEntitiesCard extends LitElement { + @property() public hass!: HomeAssistant; + @property() public deviceId!: string; + @property() public entities!: EntityRegistryEntry[]; + @property() public narrow!: boolean; + @property() private _showDisabled = false; + + private _entities = memoizeOne( + ( + deviceId: string, + entities: EntityRegistryEntry[] + ): EntityRegistryEntry[] => + entities + .filter((entity) => entity.device_id === deviceId) + .sort((ent1, ent2) => + compare( + this._computeEntityName(ent1) || `zzz${ent1.entity_id}`, + this._computeEntityName(ent2) || `zzz${ent2.entity_id}` + ) + ) + ); + + protected render(): TemplateResult { + const entities = this._entities(this.deviceId, this.entities); + return html` + + + ${this.hass.localize( + "ui.panel.config.entity_registry.picker.show_disabled" + )} + + + ${entities.length + ? entities.map((entry: EntityRegistryEntry) => { + if (!this._showDisabled && entry.disabled_by) { + return ""; + } + const stateObj = this.hass.states[entry.entity_id]; + return html` + + ${stateObj + ? html` + + ` + : html` + + `} + +
${this._computeEntityName(entry)}
+
${entry.entity_id}
+
+
+ ${stateObj + ? html` + + ` + : ""} + +
+
+ `; + }) + : html` +
+ +
+ ${this.hass.localize( + "ui.panel.config.devices.entities.none" + )} +
+
+
+ `} +
+ `; + } + + private _showDisabledChanged(ev: Event) { + this._showDisabled = (ev.target as HaSwitch).checked; + } + + private _openEditEntry(ev: MouseEvent): void { + const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry; + showEntityRegistryDetailDialog(this, { + entry, + }); + } + + private _openMoreInfo(ev: MouseEvent) { + const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry; + fireEvent(this, "hass-more-info", { entityId: entry.entity_id }); + } + + private _computeEntityName(entity) { + if (entity.name) { + return entity.name; + } + const state = this.hass.states[entity.entity_id]; + return state ? computeStateName(state) : null; + } + + static get styles(): CSSResult { + return css` + ha-icon { + width: 40px; + } + .entity-id { + color: var(--secondary-text-color); + } + .buttons { + text-align: right; + margin: 0 0 0 8px; + } + .disabled-entry { + color: var(--secondary-text-color); + } + `; + } +} diff --git a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts new file mode 100644 index 0000000000..76dba47577 --- /dev/null +++ b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts @@ -0,0 +1,26 @@ +import { customElement } from "lit-element"; +import { + DeviceTrigger, + fetchDeviceTriggers, + localizeDeviceAutomationTrigger, +} from "../../../../data/device_automation"; + +import { HaDeviceAutomationCard } from "./ha-device-automation-card"; + +@customElement("ha-device-triggers-card") +export class HaDeviceTriggersCard extends HaDeviceAutomationCard< + DeviceTrigger +> { + protected type = "trigger"; + protected headerKey = "ui.panel.config.devices.automation.triggers.caption"; + + constructor() { + super(localizeDeviceAutomationTrigger, fetchDeviceTriggers); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-device-triggers-card": HaDeviceTriggersCard; + } +} diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index a88a082c1a..c47cf2610d 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -1,11 +1,22 @@ -import { property, LitElement, html, customElement } from "lit-element"; +import { + property, + LitElement, + html, + customElement, + css, + CSSResult, +} from "lit-element"; import memoizeOne from "memoize-one"; import "../../../layouts/hass-subpage"; import "../../../layouts/hass-error-screen"; +import "../ha-config-section"; -import "./ha-device-card"; +import "./device-detail/ha-device-triggers-card"; +import "./device-detail/ha-device-conditions-card"; +import "./device-detail/ha-device-actions-card"; +import "./device-detail/ha-device-entities-card"; import { HomeAssistant } from "../../../types"; import { ConfigEntry } from "../../../data/config_entries"; import { EntityRegistryEntry } from "../../../data/entity_registry"; @@ -27,6 +38,7 @@ export class HaConfigDevicePage extends LitElement { @property() public entities!: EntityRegistryEntry[]; @property() public areas!: AreaRegistryEntry[]; @property() public deviceId!: string; + @property() public narrow!: boolean; private _device = memoizeOne( ( @@ -57,14 +69,43 @@ export class HaConfigDevicePage extends LitElement { icon="hass:settings" @click=${this._showSettings} > - + + Device info + + Here are all the details of your device. + + + +
Entities
+ + + +
Automations
+ + + +
`; } @@ -77,4 +118,24 @@ export class HaConfigDevicePage extends LitElement { }, }); } + + static get styles(): CSSResult { + return css` + .header { + font-family: var(--paper-font-display1_-_font-family); + -webkit-font-smoothing: var( + --paper-font-display1_-_-webkit-font-smoothing + ); + font-size: var(--paper-font-display1_-_font-size); + font-weight: var(--paper-font-display1_-_font-weight); + letter-spacing: var(--paper-font-display1_-_letter-spacing); + line-height: var(--paper-font-display1_-_line-height); + opacity: var(--dark-primary-opacity); + } + + ha-config-section *:last-child { + padding-bottom: 24px; + } + `; + } } diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index a78239873e..d448859b0f 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -5,7 +5,7 @@ import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import "../../../components/ha-card"; -import "../../../components/ha-data-table"; +import "../../../components/data-table/ha-data-table"; import "../../../components/entity/ha-state-icon"; import "../../../layouts/hass-subpage"; import "../../../resources/ha-style"; @@ -28,7 +28,7 @@ import { DataTabelColumnContainer, RowClickedEvent, DataTabelRowData, -} from "../../../components/ha-data-table"; +} from "../../../components/data-table/ha-data-table"; // tslint:disable-next-line import { DeviceRegistryEntry } from "../../../data/device_registry"; import { EntityRegistryEntry } from "../../../data/entity_registry"; @@ -36,7 +36,7 @@ import { ConfigEntry } from "../../../data/config_entries"; import { AreaRegistryEntry } from "../../../data/area_registry"; import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; interface DeviceRowData extends DeviceRegistryEntry { device?: DeviceRowData; diff --git a/src/panels/config/entity_registry/dialog-entity-registry-detail.ts b/src/panels/config/entity_registry/dialog-entity-registry-detail.ts index 2a6ac0553f..e65ea47d7c 100644 --- a/src/panels/config/entity_registry/dialog-entity-registry-detail.ts +++ b/src/panels/config/entity_registry/dialog-entity-registry-detail.ts @@ -8,21 +8,29 @@ import { } from "lit-element"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-toggle-button/paper-toggle-button"; import "../../../components/dialog/ha-paper-dialog"; +import "../../../components/ha-switch"; import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail"; import { PolymerChangedEvent } from "../../../polymer-types"; import { haStyleDialog } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; -import computeDomain from "../../../common/entity/compute_domain"; import { HassEntity } from "home-assistant-js-websocket"; -import computeStateName from "../../../common/entity/compute_state_name"; +// tslint:disable-next-line: no-duplicate-imports +import { HaSwitch } from "../../../components/ha-switch"; + +import { computeDomain } from "../../../common/entity/compute_domain"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { + updateEntityRegistryEntry, + removeEntityRegistryEntry, +} from "../../../data/entity_registry"; class DialogEntityRegistryDetail extends LitElement { @property() public hass!: HomeAssistant; @property() private _name!: string; + @property() private _platform!: string; @property() private _entityId!: string; @property() private _disabledBy!: string | null; @property() private _error?: string; @@ -35,6 +43,7 @@ class DialogEntityRegistryDetail extends LitElement { this._params = params; this._error = undefined; this._name = this._params.entry.name || ""; + this._platform = this._params.entry.platform; this._entityId = this._params.entry.entity_id; this._disabledBy = this._params.entry.disabled_by; await this.updateComplete; @@ -95,9 +104,9 @@ class DialogEntityRegistryDetail extends LitElement { .disabled=${this._submitting} >
-
@@ -121,7 +130,7 @@ class DialogEntityRegistryDetail extends LitElement {
Note: this might not work yet with all integrations.
-
+
@@ -161,7 +170,7 @@ class DialogEntityRegistryDetail extends LitElement { private async _updateEntry(): Promise { this._submitting = true; try { - await this._params!.updateEntry({ + await updateEntityRegistryEntry(this.hass!, this._entityId, { name: this._name.trim() || null, disabled_by: this._disabledBy, new_entity_id: this._entityId.trim(), @@ -175,11 +184,27 @@ class DialogEntityRegistryDetail extends LitElement { } private async _deleteEntry(): Promise { + if ( + !confirm( + `${this.hass.localize( + "ui.panel.config.entity_registry.editor.confirm_delete" + )} + +${this.hass.localize( + "ui.panel.config.entity_registry.editor.confirm_delete2", + "platform", + this._platform +)}` + ) + ) { + return; + } + this._submitting = true; + try { - if (await this._params!.removeEntry()) { - this._params = undefined; - } + await removeEntityRegistryEntry(this.hass!, this._entityId); + this._params = undefined; } finally { this._submitting = false; } @@ -190,8 +215,8 @@ class DialogEntityRegistryDetail extends LitElement { this._params = undefined; } } - private _disabledByChanged(ev: PolymerChangedEvent): void { - this._disabledBy = ev.detail.value ? null : "user"; + private _disabledByChanged(ev: Event): void { + this._disabledBy = (ev.target as HaSwitch).checked ? null : "user"; } static get styles(): CSSResult[] { diff --git a/src/panels/config/entity_registry/ha-config-entity-registry.ts b/src/panels/config/entity_registry/ha-config-entity-registry.ts index 8e5abfc3b8..e720e6e319 100644 --- a/src/panels/config/entity_registry/ha-config-entity-registry.ts +++ b/src/panels/config/entity_registry/ha-config-entity-registry.ts @@ -7,23 +7,23 @@ import { property, } from "lit-element"; import "@polymer/paper-item/paper-icon-item"; +import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import { HomeAssistant } from "../../../types"; import { EntityRegistryEntry, computeEntityRegistryName, - updateEntityRegistryEntry, - removeEntityRegistryEntry, subscribeEntityRegistry, } from "../../../data/entity_registry"; import "../../../layouts/hass-subpage"; import "../../../layouts/hass-loading-screen"; import "../../../components/ha-card"; import "../../../components/ha-icon"; -import domainIcon from "../../../common/entity/domain_icon"; -import stateIcon from "../../../common/entity/state_icon"; -import computeDomain from "../../../common/entity/compute_domain"; +import "../../../components/ha-switch"; +import { domainIcon } from "../../../common/entity/domain_icon"; +import { stateIcon } from "../../../common/entity/state_icon"; +import { computeDomain } from "../../../common/entity/compute_domain"; import "../ha-config-section"; import { showEntityRegistryDetailDialog, @@ -32,13 +32,24 @@ import { import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { compare } from "../../../common/string/compare"; import { classMap } from "lit-html/directives/class-map"; +// tslint:disable-next-line +import { HaSwitch } from "../../../components/ha-switch"; +import memoize from "memoize-one"; class HaConfigEntityRegistry extends LitElement { @property() public hass!: HomeAssistant; @property() public isWide?: boolean; @property() private _entities?: EntityRegistryEntry[]; + @property() private _showDisabled = false; private _unsubEntities?: UnsubscribeFunc; + private _filteredEntities = memoize( + (entities: EntityRegistryEntry[], showDisabled: boolean) => + showDisabled + ? entities + : entities.filter((entity) => !Boolean(entity.disabled_by)) + ); + public disconnectedCallback() { super.disconnectedCallback(); if (this._unsubEntities) { @@ -80,40 +91,53 @@ class HaConfigEntityRegistry extends LitElement { - ${this._entities.map((entry) => { - const state = this.hass!.states[entry.entity_id]; - return html` - - - -
- ${computeEntityRegistryName(this.hass!, entry) || - `(${this.hass!.localize("state.default.unavailable")})`} + + ${this.hass.localize( + "ui.panel.config.entity_registry.picker.show_disabled" + )} + ${this._filteredEntities(this._entities, this._showDisabled).map( + (entry) => { + const state = this.hass!.states[entry.entity_id]; + return html` + + + +
+ ${computeEntityRegistryName(this.hass!, entry) || + `(${this.hass!.localize( + "state.default.unavailable" + )})`} +
+
+ ${entry.entity_id} +
+
+
+ ${entry.platform} + ${entry.disabled_by + ? html` +
(disabled) + ` + : ""}
-
- ${entry.entity_id} -
- -
- ${entry.platform} - ${entry.disabled_by - ? html` -
(disabled) - ` - : ""} -
-
- `; - })} + + `; + } + )} @@ -139,39 +163,14 @@ class HaConfigEntityRegistry extends LitElement { } } + private _showDisabledChanged(ev: Event) { + this._showDisabled = (ev.target as HaSwitch).checked; + } + private _openEditEntry(ev: MouseEvent): void { const entry = (ev.currentTarget! as any).entry; showEntityRegistryDetailDialog(this, { entry, - updateEntry: async (updates) => { - const updated = await updateEntityRegistryEntry( - this.hass!, - entry.entity_id, - updates - ); - this._entities = this._entities!.map((ent) => - ent === entry ? updated : ent - ); - }, - removeEntry: async () => { - if ( - !confirm(`Are you sure you want to delete this entry? - -Deleting an entry will not remove the entity from Home Assistant. To do this, you will need to remove the integration "${ - entry.platform - }" from Home Assistant.`) - ) { - return false; - } - - try { - await removeEntityRegistryEntry(this.hass!, entry.entity_id); - this._entities = this._entities!.filter((ent) => ent !== entry); - return true; - } catch (err) { - return false; - } - }, }); } diff --git a/src/panels/config/entity_registry/show-dialog-entity-registry-detail.ts b/src/panels/config/entity_registry/show-dialog-entity-registry-detail.ts index 372c5f2efc..8985f4cffa 100644 --- a/src/panels/config/entity_registry/show-dialog-entity-registry-detail.ts +++ b/src/panels/config/entity_registry/show-dialog-entity-registry-detail.ts @@ -1,15 +1,8 @@ import { fireEvent } from "../../../common/dom/fire_event"; -import { - EntityRegistryEntry, - EntityRegistryEntryUpdateParams, -} from "../../../data/entity_registry"; +import { EntityRegistryEntry } from "../../../data/entity_registry"; export interface EntityRegistryDetailDialogParams { entry: EntityRegistryEntry; - updateEntry: ( - updates: Partial - ) => Promise; - removeEntry: () => Promise; } export const loadEntityRegistryDetailDialog = () => diff --git a/src/panels/config/ha-entity-config.js b/src/panels/config/ha-entity-config.js index 15661b5c62..c026143958 100644 --- a/src/panels/config/ha-entity-config.js +++ b/src/panels/config/ha-entity-config.js @@ -7,7 +7,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../../components/ha-card"; -import computeStateName from "../../common/entity/compute_state_name"; +import { computeStateName } from "../../common/entity/compute_state_name"; class HaEntityConfig extends PolymerElement { static get template() { diff --git a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts index 2235fcc5aa..5a10e4b87c 100644 --- a/src/panels/config/integrations/config-entry/ha-config-entry-page.ts +++ b/src/panels/config/integrations/config-entry/ha-config-entry-page.ts @@ -5,7 +5,7 @@ import "../../../../layouts/hass-error-screen"; import "../../../../components/entity/state-badge"; import { compare } from "../../../../common/string/compare"; -import "../../devices/ha-device-card"; +import "../../devices/device-detail/ha-device-card"; import "./ha-ce-entities-card"; import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow"; import { property, LitElement, CSSResult, css, html } from "lit-element"; diff --git a/src/panels/config/integrations/ha-config-entries-dashboard.ts b/src/panels/config/integrations/ha-config-entries-dashboard.ts index d15ae2c569..03a420d37a 100644 --- a/src/panels/config/integrations/ha-config-entries-dashboard.ts +++ b/src/panels/config/integrations/ha-config-entries-dashboard.ts @@ -18,7 +18,7 @@ import "../../../components/ha-icon"; import { computeRTL } from "../../../common/util/compute_rtl"; import "../ha-config-section"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import { loadConfigFlowDialog, showConfigFlowDialog, diff --git a/src/panels/config/js/automation.tsx b/src/panels/config/js/automation.tsx index bb3ff51ee8..e94284cbf2 100644 --- a/src/panels/config/js/automation.tsx +++ b/src/panels/config/js/automation.tsx @@ -3,6 +3,7 @@ import { h, Component } from "preact"; import "@polymer/paper-input/paper-input"; import "../ha-config-section"; import "../../../components/ha-card"; +import "../../../components/ha-textarea"; import Trigger from "./trigger/index"; import Condition from "./condition/index"; @@ -38,7 +39,7 @@ export default class Automation extends Component { } public render({ automation, isWide, hass, localize }) { - const { alias, trigger, condition, action } = automation; + const { alias, description, trigger, condition, action } = automation; return (
@@ -55,6 +56,17 @@ export default class Automation extends Component { value={alias} onvalue-changed={this.onChange} /> +
diff --git a/src/panels/config/js/condition/condition_edit.tsx b/src/panels/config/js/condition/condition_edit.tsx index 3816675aff..2ffd01ef9d 100644 --- a/src/panels/config/js/condition/condition_edit.tsx +++ b/src/panels/config/js/condition/condition_edit.tsx @@ -4,6 +4,7 @@ import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-item/paper-item"; import DeviceCondition from "./device"; +import LogicalCondition from "./logical"; import NumericStateCondition from "./numeric_state"; import StateCondition from "./state"; import SunCondition from "./sun"; @@ -12,9 +13,11 @@ import TimeCondition from "./time"; import ZoneCondition from "./zone"; const TYPES = { + and: LogicalCondition, device: DeviceCondition, - state: StateCondition, numeric_state: NumericStateCondition, + or: LogicalCondition, + state: StateCondition, sun: SunCondition, template: TemplateCondition, time: TimeCondition, @@ -23,7 +26,7 @@ const TYPES = { const OPTIONS = Object.keys(TYPES).sort(); -export default class ConditionRow extends Component { +export default class ConditionEdit extends Component { constructor() { super(); diff --git a/src/panels/config/js/condition/logical.tsx b/src/panels/config/js/condition/logical.tsx new file mode 100644 index 0000000000..a81cfedecd --- /dev/null +++ b/src/panels/config/js/condition/logical.tsx @@ -0,0 +1,46 @@ +import { h, Component } from "preact"; + +import Condition from "./index"; + +export default class LogicalCondition extends Component { + private _mounted = false; + constructor() { + super(); + this.conditionChanged = this.conditionChanged.bind(this); + } + + public conditionChanged(conditions) { + if (this._mounted) { + this.props.onChange(this.props.index, { + ...this.props.condition, + conditions, + }); + } + } + + public componentWillMount() { + this._mounted = true; + } + + public componentWillUnmount() { + this._mounted = false; + } + + /* eslint-disable camelcase */ + public render({ condition, hass, localize }) { + return ( +
+ +
+ ); + } +} + +(LogicalCondition as any).defaultConfig = { + conditions: [{ condition: "state" }], +}; diff --git a/src/panels/config/js/condition/zone.tsx b/src/panels/config/js/condition/zone.tsx index 4dc79962bc..5d8ba95431 100644 --- a/src/panels/config/js/condition/zone.tsx +++ b/src/panels/config/js/condition/zone.tsx @@ -1,7 +1,7 @@ import { h, Component } from "preact"; import "../../../../components/entity/ha-entity-picker"; -import hasLocation from "../../../../common/entity/has_location"; -import computeStateDomain from "../../../../common/entity/compute_state_domain"; +import { hasLocation } from "../../../../common/entity/has_location"; +import { computeStateDomain } from "../../../../common/entity/compute_state_domain"; function zoneAndLocationFilter(stateObj) { return hasLocation(stateObj) && computeStateDomain(stateObj) !== "zone"; diff --git a/src/panels/config/js/script/call_service.tsx b/src/panels/config/js/script/call_service.tsx index 5182248c26..754171ab6e 100644 --- a/src/panels/config/js/script/call_service.tsx +++ b/src/panels/config/js/script/call_service.tsx @@ -1,7 +1,7 @@ import { h, Component } from "preact"; import "../../../../components/ha-service-picker"; -import JSONTextArea from "../json_textarea"; +import YAMLTextArea from "../yaml_textarea"; export default class CallServiceAction extends Component { constructor() { @@ -32,7 +32,7 @@ export default class CallServiceAction extends Component { value={service} onChange={this.serviceChanged} /> - { value={event} onvalue-changed={this.onChange} /> - { @@ -34,7 +34,7 @@ export default class EventTrigger extends Component { value={event_type} onvalue-changed={this.onChange} /> - { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + return false; + } + } + return true; +}; + +export default class YAMLTextArea extends Component { + constructor(props) { + super(props); + + let value: string | undefined; + try { + value = + props.value && !isEmpty(props.value) + ? yaml.safeDump(props.value) + : undefined; + } catch (err) { + alert(`There was an error converting to YAML: ${err}`); + } + + this.state = { + isvalid: true, + value, + }; + + this.onChange = this.onChange.bind(this); + } + + public onChange(ev) { + const value = ev.target.value; + let parsed; + let isValid = true; + + if (value) { + try { + parsed = yaml.safeLoad(value); + isValid = true; + } catch (err) { + // Invalid YAML + isValid = false; + } + } else { + parsed = {}; + } + + this.setState({ + value, + isValid, + }); + if (isValid) { + this.props.onChange(parsed); + } + } + + public render({ label }, { value, isValid }) { + const style: any = { + minWidth: 300, + width: "100%", + }; + if (!isValid) { + style.border = "1px solid red"; + } + return ( + + ); + } +} diff --git a/src/panels/config/script/ha-config-script.js b/src/panels/config/script/ha-config-script.js index 88a1f0f1af..a0e7fcf1ed 100644 --- a/src/panels/config/script/ha-config-script.js +++ b/src/panels/config/script/ha-config-script.js @@ -5,8 +5,8 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "./ha-script-editor"; import "./ha-script-picker"; -import computeStateName from "../../../common/entity/compute_state_name"; -import computeStateDomain from "../../../common/entity/compute_state_domain"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; class HaConfigScript extends PolymerElement { static get template() { diff --git a/src/panels/config/script/ha-script-editor.js b/src/panels/config/script/ha-script-editor.js index 98bafc668e..8de26171b0 100644 --- a/src/panels/config/script/ha-script-editor.js +++ b/src/panels/config/script/ha-script-editor.js @@ -12,8 +12,8 @@ import "../../../components/ha-fab"; import Script from "../js/script"; import unmountPreact from "../../../common/preact/unmount"; -import computeObjectId from "../../../common/entity/compute_object_id"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeObjectId } from "../../../common/entity/compute_object_id"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import NavigateMixin from "../../../mixins/navigate-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index be4b30a862..e9bef2de42 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -20,7 +20,7 @@ import "../../../components/ha-fab"; import "../ha-config-section"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { triggerScript } from "../../../data/script"; diff --git a/src/panels/config/zha/zha-device-card.ts b/src/panels/config/zha/zha-device-card.ts index 886fb6c2f4..bec3ef9c90 100644 --- a/src/panels/config/zha/zha-device-card.ts +++ b/src/panels/config/zha/zha-device-card.ts @@ -41,7 +41,7 @@ import { ItemSelectedEvent, NodeServiceData } from "./types"; import { navigate } from "../../../common/navigate"; import { UnsubscribeFunc, HassEvent } from "home-assistant-js-websocket"; import { formatAsPaddedHex } from "./functions"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; declare global { // for fire event diff --git a/src/panels/config/zwave/ha-config-zwave.js b/src/panels/config/zwave/ha-config-zwave.js index 440a2c901b..6169a54bdc 100644 --- a/src/panels/config/zwave/ha-config-zwave.js +++ b/src/panels/config/zwave/ha-config-zwave.js @@ -26,9 +26,9 @@ import "./zwave-usercodes"; import "./zwave-values"; import "./zwave-node-protection"; -import sortByName from "../../../common/entity/states_sort_by_name"; -import computeStateName from "../../../common/entity/compute_state_name"; -import computeStateDomain from "../../../common/entity/compute_state_domain"; +import { sortStatesByName } from "../../../common/entity/states_sort_by_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; @@ -461,7 +461,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) { return Object.keys(hass.states) .map((key) => hass.states[key]) .filter((ent) => ent.entity_id.match("zwave[.]")) - .sort(sortByName); + .sort(sortStatesByName); } computeEntities(selectedNode) { @@ -481,7 +481,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) { !ent.entity_id.match("zwave[.]") ); }) - .sort(sortByName); + .sort(sortStatesByName); } selectedNodeChanged(selectedNode) { diff --git a/src/panels/config/zwave/zwave-groups.js b/src/panels/config/zwave/zwave-groups.js index 991b47db4c..5b67a07f3c 100644 --- a/src/panels/config/zwave/zwave-groups.js +++ b/src/panels/config/zwave/zwave-groups.js @@ -7,7 +7,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../../../components/buttons/ha-call-service-button"; import "../../../components/ha-card"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; class ZwaveGroups extends PolymerElement { static get template() { diff --git a/src/panels/developer-tools/event/developer-tools-event.js b/src/panels/developer-tools/event/developer-tools-event.js index 3bf1ccee0a..25fd569034 100644 --- a/src/panels/developer-tools/event/developer-tools-event.js +++ b/src/panels/developer-tools/event/developer-tools-event.js @@ -5,6 +5,8 @@ import "@polymer/paper-input/paper-textarea"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import yaml from "js-yaml"; + import "../../../resources/ha-style"; import "./events-list"; import "./event-subscribe-card"; @@ -61,7 +63,7 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) { value="{{eventType}}" > Fire Event @@ -106,10 +108,10 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) { var eventData; try { - eventData = this.eventData ? JSON.parse(this.eventData) : {}; + eventData = this.eventData ? yaml.safeLoad(this.eventData) : {}; } catch (err) { /* eslint-disable no-alert */ - alert("Error parsing JSON: " + err); + alert("Error parsing YAML: " + err); /* eslint-enable no-alert */ return; } diff --git a/src/panels/developer-tools/service/developer-tools-service.js b/src/panels/developer-tools/service/developer-tools-service.js index 59f14a119e..acbe2856c2 100644 --- a/src/panels/developer-tools/service/developer-tools-service.js +++ b/src/panels/developer-tools/service/developer-tools-service.js @@ -3,6 +3,8 @@ import "@polymer/paper-input/paper-textarea"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import yaml from "js-yaml"; + import { ENTITY_COMPONENT_DOMAINS } from "../../../data/entity"; import "../../../components/entity/ha-entity-picker"; import "../../../components/ha-service-picker"; @@ -109,7 +111,7 @@ class HaPanelDevService extends PolymerElement {
@@ -249,7 +251,7 @@ class HaPanelDevService extends PolymerElement { _computeParsedServiceData(serviceData) { try { - return serviceData ? JSON.parse(serviceData) : {}; + return serviceData ? yaml.safeLoad(serviceData) : {}; } catch (err) { return ERROR_SENTINEL; } @@ -283,17 +285,24 @@ class HaPanelDevService extends PolymerElement { _fillExampleData() { const example = {}; this._attributes.forEach((attribute) => { - example[attribute.key] = attribute.example; + if (attribute.example) { + let value = ""; + try { + value = yaml.safeLoad(attribute.example); + } catch (err) { + value = attribute.example; + } + example[attribute.key] = value; + } }); - this.serviceData = JSON.stringify(example, null, 2); + this.serviceData = yaml.safeDump(example); } _entityPicked(ev) { - this.serviceData = JSON.stringify( - { ...this.parsedJSON, entity_id: ev.target.value }, - null, - 2 - ); + this.serviceData = yaml.safeDump({ + ...this.parsedJSON, + entity_id: ev.target.value, + }); } } diff --git a/src/panels/developer-tools/state/developer-tools-state.js b/src/panels/developer-tools/state/developer-tools-state.js index c02ec0de90..65c518253a 100644 --- a/src/panels/developer-tools/state/developer-tools-state.js +++ b/src/panels/developer-tools/state/developer-tools-state.js @@ -5,6 +5,8 @@ import "@polymer/paper-input/paper-textarea"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; +import yaml from "js-yaml"; + import "../../../components/entity/ha-entity-picker"; import "../../../resources/ha-style"; import { EventsMixin } from "../../../mixins/events-mixin"; @@ -88,7 +90,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) { class="state-input" > + typeof entity === "object" && + entity.state_filter && + Array.isArray(entity.state_filter) + ) + ) { throw new Error("Incorrect filter config."); } @@ -56,7 +69,26 @@ class EntityFilterCard extends HTMLElement implements LovelaceCard { const entitiesList = this._configEntities.filter((entityConf) => { const stateObj = hass.states[entityConf.entity]; - return stateObj && this._config!.state_filter.includes(stateObj.state); + + if (!stateObj) { + return false; + } + + if (entityConf.state_filter) { + for (const filter of entityConf.state_filter) { + if (evaluateFilter(stateObj, filter)) { + return true; + } + } + } else { + for (const filter of this._config!.state_filter) { + if (evaluateFilter(stateObj, filter)) { + return true; + } + } + } + + return false; }); if (entitiesList.length === 0 && this._config.show_empty === false) { diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 25b00ddb61..cf00c29b04 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -15,7 +15,7 @@ import "../components/hui-warning"; import isValidEntityId from "../../../common/entity/valid_entity_id"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import { HomeAssistant } from "../../../types"; import { fireEvent } from "../../../common/dom/fire_event"; diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index f33d00674a..067aa6df86 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -10,8 +10,7 @@ import { } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; -import computeStateDisplay from "../../../common/entity/compute_state_display"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import relativeTime from "../../../common/datetime/relative_time"; @@ -20,6 +19,7 @@ import "../../../components/ha-card"; import "../../../components/ha-icon"; import "../components/hui-warning-element"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { HomeAssistant } from "../../../types"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { longPress } from "../common/directives/long-press-directive"; diff --git a/src/panels/lovelace/cards/hui-history-graph-card.js b/src/panels/lovelace/cards/hui-history-graph-card.js index 129d83d1a7..e6ce58032f 100644 --- a/src/panels/lovelace/cards/hui-history-graph-card.js +++ b/src/panels/lovelace/cards/hui-history-graph-card.js @@ -8,6 +8,11 @@ import "../../../data/ha-state-history-data"; import { processConfigEntities } from "../common/process-config-entities"; class HuiHistoryGraphCard extends PolymerElement { + static async getConfigElement() { + await import(/* webpackChunkName: "hui-history-graph-card-editor" */ "../editor/config-elements/hui-history-graph-card-editor"); + return document.createElement("hui-history-graph-card-editor"); + } + static getStubConfig() { return { entities: [] }; } diff --git a/src/panels/lovelace/cards/hui-legacy-wrapper-card.js b/src/panels/lovelace/cards/hui-legacy-wrapper-card.js index c1b36d2e78..0bc35371bd 100644 --- a/src/panels/lovelace/cards/hui-legacy-wrapper-card.js +++ b/src/panels/lovelace/cards/hui-legacy-wrapper-card.js @@ -1,5 +1,5 @@ import { createErrorCardConfig } from "./hui-error-card"; -import computeDomain from "../../../common/entity/compute_domain"; +import { computeDomain } from "../../../common/entity/compute_domain"; export default class LegacyWrapperCard extends HTMLElement { constructor(tag, domain) { diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index a1efbf274c..babb4f6004 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -9,8 +9,8 @@ import { import "@polymer/paper-icon-button/paper-icon-button"; import "@thomasloven/round-slider"; -import stateIcon from "../../../common/entity/state_icon"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { stateIcon } from "../../../common/entity/state_icon"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import applyThemesOnElement from "../../../common/dom/apply_themes_on_element"; import "../../../components/ha-card"; @@ -107,7 +107,7 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
-
+
${this._config.elements.map( (elementConfig: LovelaceElementConfig) => { diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index 7c719ce96c..69ebefe75c 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -14,13 +14,13 @@ import "../../../components/ha-card"; import "../components/hui-image"; import "../components/hui-warning"; -import computeDomain from "../../../common/entity/compute_domain"; -import computeStateDisplay from "../../../common/entity/compute_state_display"; -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { longPress } from "../common/directives/long-press-directive"; import { HomeAssistant } from "../../../types"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { handleClick } from "../common/handle-click"; import { UNAVAILABLE } from "../../../data/entity"; import { hasConfigOrEntityChanged } from "../common/has-changed"; @@ -28,6 +28,18 @@ import { PictureEntityCardConfig } from "./types"; @customElement("hui-picture-entity-card") class HuiPictureEntityCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import(/* webpackChunkName: "hui-picture-entity-card-editor" */ "../editor/config-elements/hui-picture-entity-card-editor"); + return document.createElement("hui-picture-entity-card-editor"); + } + public static getStubConfig(): object { + return { + entity: "", + image: + "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", + }; + } + @property() public hass?: HomeAssistant; @property() private _config?: PictureEntityCardConfig; @@ -102,21 +114,22 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { return html` ${footer} diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index a57978fdfc..58992f7afa 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -10,38 +10,47 @@ import { } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; -import computeStateDisplay from "../../../common/entity/compute_state_display"; -import computeStateName from "../../../common/entity/compute_state_name"; -import computeDomain from "../../../common/entity/compute_domain"; -import stateIcon from "../../../common/entity/state_icon"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { stateIcon } from "../../../common/entity/state_icon"; import "../../../components/ha-card"; import "../../../components/ha-icon"; import "../components/hui-image"; import "../components/hui-warning-element"; +import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { DOMAINS_TOGGLE } from "../../../common/const"; -import { LovelaceCard } from "../types"; -import { EntityConfig } from "../entity-rows/types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { HomeAssistant } from "../../../types"; import { longPress } from "../common/directives/long-press-directive"; import { processConfigEntities } from "../common/process-config-entities"; import { handleClick } from "../common/handle-click"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { toggleEntity } from "../common/entity/toggle-entity"; -import { PictureGlanceCardConfig } from "./types"; +import { PictureGlanceCardConfig, ConfigEntity } from "./types"; const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]); @customElement("hui-picture-glance-card") class HuiPictureGlanceCard extends LitElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import(/* webpackChunkName: "hui-picture-glance-card-editor" */ "../editor/config-elements/hui-picture-glance-card-editor"); + return document.createElement("hui-picture-glance-card-editor"); + } + public static getStubConfig(): object { + return { + image: + "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", + entities: [], + }; + } + @property() public hass?: HomeAssistant; @property() private _config?: PictureGlanceCardConfig; - private _entitiesDialog?: EntityConfig[]; + private _entitiesDialog?: ConfigEntity[]; - private _entitiesToggle?: EntityConfig[]; + private _entitiesToggle?: ConfigEntity[]; public getCardSize(): number { return 3; @@ -117,23 +126,25 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { return html`
${this._config.title @@ -157,11 +168,16 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { } private renderEntity( - entityConf: EntityConfig, + entityConf: ConfigEntity, dialog: boolean ): TemplateResult { const stateObj = this.hass!.states[entityConf.entity]; + entityConf = { + tap_action: { action: dialog ? "more-info" : "toggle" }, + ...entityConf, + }; + if (!stateObj) { return html` ; - state_filter: string[]; + entities: Array; + state_filter: Array<{ key: string } | string>; card: Partial; show_empty?: boolean; } @@ -107,6 +107,7 @@ export interface LightCardConfig extends LovelaceCardConfig { entity: string; name?: string; theme?: string; + icon?: string; } export interface MapCardConfig extends LovelaceCardConfig { @@ -143,6 +144,7 @@ export interface PictureElementsCardConfig extends LovelaceCardConfig { camera_image?: string; camera_view?: HuiImage["cameraView"]; state_image?: {}; + state_filter: string[]; aspect_ratio?: string; entity?: string; elements: LovelaceElementConfig[]; @@ -155,6 +157,7 @@ export interface PictureEntityCardConfig extends LovelaceCardConfig { camera_image?: string; camera_view?: HuiImage["cameraView"]; state_image?: {}; + state_filter: string[]; aspect_ratio?: string; tap_action?: ActionConfig; hold_action?: ActionConfig; @@ -169,6 +172,7 @@ export interface PictureGlanceCardConfig extends LovelaceCardConfig { camera_image?: string; camera_view?: HuiImage["cameraView"]; state_image?: {}; + state_filter: string[]; aspect_ratio?: string; entity?: string; tap_action?: ActionConfig; diff --git a/src/panels/lovelace/common/compute-tooltip.ts b/src/panels/lovelace/common/compute-tooltip.ts index f3e77143f2..6093fa2b79 100644 --- a/src/panels/lovelace/common/compute-tooltip.ts +++ b/src/panels/lovelace/common/compute-tooltip.ts @@ -1,4 +1,4 @@ -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import { HomeAssistant } from "../../../types"; import { LovelaceElementConfig } from "../elements/types"; import { ActionConfig } from "../../../data/lovelace"; @@ -66,6 +66,13 @@ function computeActionTooltip( config.navigation_path )}`; break; + case "url": + tooltip += `${hass.localize( + "ui.panel.lovelace.cards.picture-elements.url", + "url_path", + config.url_path + )}`; + break; case "toggle": tooltip += `${hass.localize( "ui.panel.lovelace.cards.picture-elements.toggle", diff --git a/src/panels/lovelace/common/entity/turn-on-off-entities.ts b/src/panels/lovelace/common/entity/turn-on-off-entities.ts index 495413e9d9..1ad24203b2 100644 --- a/src/panels/lovelace/common/entity/turn-on-off-entities.ts +++ b/src/panels/lovelace/common/entity/turn-on-off-entities.ts @@ -1,4 +1,4 @@ -import computeDomain from "../../../../common/entity/compute_domain"; +import { computeDomain } from "../../../../common/entity/compute_domain"; import { STATES_OFF } from "../../../../common/const"; import { HomeAssistant } from "../../../../types"; diff --git a/src/panels/lovelace/common/entity/turn-on-off-entity.ts b/src/panels/lovelace/common/entity/turn-on-off-entity.ts index 8823438bf3..b7ffcbcc89 100644 --- a/src/panels/lovelace/common/entity/turn-on-off-entity.ts +++ b/src/panels/lovelace/common/entity/turn-on-off-entity.ts @@ -1,4 +1,4 @@ -import computeDomain from "../../../../common/entity/compute_domain"; +import { computeDomain } from "../../../../common/entity/compute_domain"; import { HomeAssistant } from "../../../../types"; export const turnOnOffEntity = ( diff --git a/src/panels/lovelace/common/evaluate-filter.ts b/src/panels/lovelace/common/evaluate-filter.ts new file mode 100644 index 0000000000..fd2e341563 --- /dev/null +++ b/src/panels/lovelace/common/evaluate-filter.ts @@ -0,0 +1,29 @@ +import { HassEntity } from "home-assistant-js-websocket"; + +export const evaluateFilter = (stateObj: HassEntity, filter: any): boolean => { + const operator = filter.operator || "=="; + const value = filter.value || filter; + const state = filter.attribute + ? stateObj.attributes[filter.attribute] + : stateObj.state; + + switch (operator) { + case "==": + return state === value; + case "<=": + return state <= value; + case "<": + return state < value; + case ">=": + return state >= value; + case ">": + return state > value; + case "!=": + return state !== value; + case "regex": { + return state.match(value); + } + default: + return false; + } +}; diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index fbd053bae8..de86979f83 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -10,13 +10,13 @@ import { HassConfig, } from "home-assistant-js-websocket"; -import extractViews from "../../../common/entity/extract_views"; -import getViewEntities from "../../../common/entity/get_view_entities"; -import computeStateName from "../../../common/entity/compute_state_name"; -import splitByGroups from "../../../common/entity/split_by_groups"; -import computeObjectId from "../../../common/entity/compute_object_id"; -import computeStateDomain from "../../../common/entity/compute_state_domain"; -import computeDomain from "../../../common/entity/compute_domain"; +import { extractViews } from "../../../common/entity/extract_views"; +import { getViewEntities } from "../../../common/entity/get_view_entities"; +import { computeStateName } from "../../../common/entity/compute_state_name"; +import { splitByGroups } from "../../../common/entity/split_by_groups"; +import { computeObjectId } from "../../../common/entity/compute_object_id"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { computeDomain } from "../../../common/entity/compute_domain"; import { EntityRowConfig, WeblinkConfig } from "../entity-rows/types"; import { LocalizeFunc } from "../../../common/translations/localize"; diff --git a/src/panels/lovelace/common/handle-click.ts b/src/panels/lovelace/common/handle-click.ts index 001ceb6d50..d4ee03899a 100644 --- a/src/panels/lovelace/common/handle-click.ts +++ b/src/panels/lovelace/common/handle-click.ts @@ -43,6 +43,11 @@ export const handleClick = ( navigate(node, actionConfig.navigation_path); } break; + case "url": + if (actionConfig.url_path) { + window.open(actionConfig.url_path); + } + break; case "toggle": if (config.entity) { toggleEntity(hass, config.entity!); diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts index 472de0d87d..65d0ce16ea 100644 --- a/src/panels/lovelace/components/hui-action-editor.ts +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -19,6 +19,7 @@ import { ActionConfig, NavigateActionConfig, CallServiceActionConfig, + UrlActionConfig, } from "../../../data/lovelace"; declare global { @@ -51,6 +52,11 @@ export class HuiActionEditor extends LitElement { return config.navigation_path || ""; } + get _url_path(): string { + const config = this.config! as UrlActionConfig; + return config.url_path || ""; + } + get _service(): string { const config = this.config! as CallServiceActionConfig; return config.service || ""; @@ -87,6 +93,16 @@ export class HuiActionEditor extends LitElement { > ` : ""} + ${this._action === "url" + ? html` + + ` + : ""} ${this.config && this.config.action === "call-service" ? html` + > `; } @@ -57,9 +60,7 @@ class HuiEntitiesToggle extends LitElement { width: 38px; display: block; } - paper-toggle-button { - cursor: pointer; - --paper-toggle-button-label-spacing: 0; + ha-switch { padding: 13px 5px; margin: -4px -5px; } @@ -68,7 +69,7 @@ class HuiEntitiesToggle extends LitElement { private _callService(ev: MouseEvent): void { forwardHaptic("light"); - const turnOn = (ev.target as PaperToggleButtonElement).checked; + const turnOn = (ev.target as HaSwitch).checked; turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!); } } diff --git a/src/panels/lovelace/components/hui-entity-editor.ts b/src/panels/lovelace/components/hui-entity-editor.ts index ae6ca4ef9e..c7fc17e00b 100644 --- a/src/panels/lovelace/components/hui-entity-editor.ts +++ b/src/panels/lovelace/components/hui-entity-editor.ts @@ -22,13 +22,25 @@ export class HuiEntityEditor extends LitElement { @property() protected entities?: EntityConfig[]; + @property() protected label?: string; + protected render(): TemplateResult | void { if (!this.entities) { return html``; } return html` -

Entities

+

+ ${this.label || + this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entities" + ) + + " (" + + this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + ) + + ")"} +

${this.entities.map((entityConf, index) => { return html` diff --git a/src/panels/lovelace/components/hui-generic-entity-row.ts b/src/panels/lovelace/components/hui-generic-entity-row.ts index 2245716652..9ec024dc0f 100644 --- a/src/panels/lovelace/components/hui-generic-entity-row.ts +++ b/src/panels/lovelace/components/hui-generic-entity-row.ts @@ -1,4 +1,4 @@ -import computeStateName from "../../../common/entity/compute_state_name"; +import { computeStateName } from "../../../common/entity/compute_state_name"; import { LitElement, html, @@ -51,6 +51,7 @@ class HuiGenericEntityRow extends LitElement { .hass=${this.hass} .stateObj=${stateObj} .overrideIcon=${this.config.icon} + .overrideImage=${this.config.image} >
diff --git a/src/panels/lovelace/components/hui-image.ts b/src/panels/lovelace/components/hui-image.ts index 35be160e27..b2785b31c9 100644 --- a/src/panels/lovelace/components/hui-image.ts +++ b/src/panels/lovelace/components/hui-image.ts @@ -1,5 +1,3 @@ -import "@polymer/paper-toggle-button/paper-toggle-button"; - import { STATES_OFF } from "../../../common/const"; import parseAspectRatio from "../../../common/util/parse-aspect-ratio"; diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 31b8593306..8d1b210945 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -26,7 +26,7 @@ declare global { @customElement("hui-theme-select-editor") export class HuiThemeSelectEditor extends LitElement { @property() public value?: string; - + @property() public label?: string; @property() public hass?: HomeAssistant; protected render(): TemplateResult | void { @@ -36,7 +36,13 @@ export class HuiThemeSelectEditor extends LitElement { return html` diff --git a/src/panels/lovelace/editor/config-elements/config-elements-style.ts b/src/panels/lovelace/editor/config-elements/config-elements-style.ts index f153988b27..c5091d23f8 100644 --- a/src/panels/lovelace/editor/config-elements/config-elements-style.ts +++ b/src/panels/lovelace/editor/config-elements/config-elements-style.ts @@ -2,8 +2,8 @@ import { html } from "lit-element"; export const configElementStyle = html`