From 0a6ffb6bc8394cfec14376619606e163a411079a Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:59:39 +0100 Subject: [PATCH 01/19] change device_tracker icon to reflect state (#10501) Co-authored-by: Bram Kragten --- src/common/const.ts | 1 - src/common/entity/domain_icon.ts | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/const.ts b/src/common/const.ts index 6559ece953..0a1943b601 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -76,7 +76,6 @@ export const FIXED_DOMAIN_ICONS = { configurator: mdiCog, conversation: mdiTextToSpeech, counter: mdiCounter, - device_tracker: mdiAccount, fan: mdiFan, google_assistant: mdiGoogleAssistant, group: mdiGoogleCirclesCommunities, diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index 3816c925de..4d41728cdd 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -1,6 +1,9 @@ import { + mdiAccount, mdiAirHumidifierOff, mdiAirHumidifier, + mdiLanConnect, + mdiLanDisconnect, mdiLockOpen, mdiLockAlert, mdiLockClock, @@ -44,6 +47,12 @@ export const domainIcon = ( case "cover": return coverIcon(compareState, stateObj); + case "device_tracker": + if (stateObj?.attributes.source_type === "router") { + return compareState === "home" ? mdiLanConnect : mdiLanDisconnect; + } + return mdiAccount; + case "humidifier": return state && state === "off" ? mdiAirHumidifierOff : mdiAirHumidifier; From c8804160bf782b4a7a65affaf59d2bc81c316e33 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 3 Nov 2021 23:38:52 +0100 Subject: [PATCH 02/19] Add basic support for button entity (#10504) --- src/common/const.ts | 1 + src/common/entity/compute_state_display.ts | 5 ++ .../create-element/create-row-element.ts | 2 + .../entity-rows/hui-button-entity-row.ts | 82 +++++++++++++++++++ src/state-summary/state-card-button.ts | 54 ++++++++++++ src/state-summary/state-card-content.js | 1 + src/translations/en.json | 3 + 7 files changed, 148 insertions(+) create mode 100644 src/panels/lovelace/entity-rows/hui-button-entity-row.ts create mode 100644 src/state-summary/state-card-button.ts diff --git a/src/common/const.ts b/src/common/const.ts index 0a1943b601..1222fa9ebf 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -144,6 +144,7 @@ export const FIXED_DEVICE_CLASS_ICONS = { /** Domains that have a state card. */ export const DOMAINS_WITH_CARD = [ + "button", "climate", "cover", "configurator", diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 1836d5d6a5..9e4d8d87e4 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -116,6 +116,11 @@ export const computeStateDisplay = ( return formatNumber(compareState, locale); } + // state of button is a timestamp + if (domain === "button") { + return formatDateTime(new Date(compareState), locale); + } + return ( // Return device class translation (stateObj.attributes.device_class && diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index f1b63c717c..b4c9afc556 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -24,6 +24,7 @@ const ALWAYS_LOADED_TYPES = new Set([ "call-service", ]); const LAZY_LOAD_TYPES = { + "button-entity": () => import("../entity-rows/hui-button-entity-row"), "climate-entity": () => import("../entity-rows/hui-climate-entity-row"), "cover-entity": () => import("../entity-rows/hui-cover-entity-row"), "group-entity": () => import("../entity-rows/hui-group-entity-row"), @@ -53,6 +54,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { _domain_not_found: "text", alert: "toggle", automation: "toggle", + button: "button", climate: "climate", cover: "cover", fan: "toggle", diff --git a/src/panels/lovelace/entity-rows/hui-button-entity-row.ts b/src/panels/lovelace/entity-rows/hui-button-entity-row.ts new file mode 100644 index 0000000000..1184fb67ce --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-button-entity-row.ts @@ -0,0 +1,82 @@ +import "@material/mwc-button/mwc-button"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { UNAVAILABLE } from "../../../data/entity"; +import { HomeAssistant } from "../../../types"; +import { hasConfigOrEntityChanged } from "../common/has-changed"; +import "../components/hui-generic-entity-row"; +import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { ActionRowConfig, LovelaceRow } from "./types"; + +@customElement("hui-button-entity-row") +class HuiButtonEntityRow extends LitElement implements LovelaceRow { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _config?: ActionRowConfig; + + public setConfig(config: ActionRowConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return hasConfigOrEntityChanged(this, changedProps); + } + + protected render(): TemplateResult { + if (!this._config || !this.hass) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity]; + + if (!stateObj) { + return html` + + ${createEntityNotFoundWarning(this.hass, this._config.entity)} + + `; + } + + return html` + + + ${this.hass.localize("ui.card.button.press")} + + + `; + } + + static get styles(): CSSResultGroup { + return css` + mwc-button:last-child { + margin-right: -0.57em; + } + `; + } + + private _pressButton(ev): void { + ev.stopPropagation(); + this.hass.callService("button", "press", { + entity_id: this._config!.entity, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-button-entity-row": HuiButtonEntityRow; + } +} diff --git a/src/state-summary/state-card-button.ts b/src/state-summary/state-card-button.ts new file mode 100644 index 0000000000..3ce46e43ff --- /dev/null +++ b/src/state-summary/state-card-button.ts @@ -0,0 +1,54 @@ +import "@material/mwc-button"; +import { HassEntity } from "home-assistant-js-websocket"; +import { CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../components/entity/ha-entity-toggle"; +import "../components/entity/state-info"; +import { UNAVAILABLE } from "../data/entity"; +import { haStyle } from "../resources/styles"; +import { HomeAssistant } from "../types"; + +@customElement("state-card-button") +export class StateCardButton extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public stateObj!: HassEntity; + + @property({ type: Boolean }) public inDialog = false; + + protected render() { + const stateObj = this.stateObj; + return html` +
+ + + ${this.hass.localize("ui.card.button.press")} + +
+ `; + } + + private _pressButton(ev: Event) { + ev.stopPropagation(); + this.hass.callService("button", "press", { + entity_id: this.stateObj.entity_id, + }); + } + + static get styles(): CSSResultGroup { + return haStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "state-card-button": StateCardButton; + } +} diff --git a/src/state-summary/state-card-content.js b/src/state-summary/state-card-content.js index f3ee74bbf3..f5c1a34a0b 100644 --- a/src/state-summary/state-card-content.js +++ b/src/state-summary/state-card-content.js @@ -2,6 +2,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import dynamicContentUpdater from "../common/dom/dynamic_content_updater"; import { stateCardType } from "../common/entity/state_card_type"; +import "./state-card-button"; import "./state-card-climate"; import "./state-card-configurator"; import "./state-card-cover"; diff --git a/src/translations/en.json b/src/translations/en.json index e0ff0ec0a4..132847b37d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -120,6 +120,9 @@ "last_triggered": "Last triggered", "trigger": "Run Actions" }, + "button": { + "press": "Press" + }, "camera": { "not_available": "Image not available" }, From 1c35571ef06f62ee9d99a66bcbe06d8bab8b1153 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 4 Nov 2021 04:41:30 -0500 Subject: [PATCH 03/19] Fix Device Page (#10513) --- .../config/devices/ha-config-device-page.ts | 201 ++++++++++-------- 1 file changed, 110 insertions(+), 91 deletions(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 386e7efe14..dd0603faf7 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -1,19 +1,20 @@ -import { mdiPencil, mdiPlusCircle, mdiOpenInNew } from "@mdi/js"; +import { mdiOpenInNew, mdiPencil, mdiPlusCircle } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; import { stringCompare } from "../../../common/string/compare"; -import { groupBy } from "../../../common/util/group-by"; import { slugify } from "../../../common/string/slugify"; +import { groupBy } from "../../../common/util/group-by"; import "../../../components/entity/ha-battery-icon"; +import "../../../components/ha-alert"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-next"; -import "../../../components/ha-alert"; import "../../../components/ha-svg-icon"; import { AreaRegistryEntry } from "../../../data/area_registry"; import { @@ -52,7 +53,6 @@ import { loadDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog, } from "./device-registry-detail/show-dialog-device-registry-detail"; -import { computeDomain } from "../../../common/entity/compute_domain"; export interface EntityRegistryStateEntry extends EntityRegistryEntry { stateName?: string | null; @@ -407,41 +407,45 @@ export class HaConfigDevicePage extends LitElement { > ${this._related?.automation?.length - ? this._related.automation.map((automation) => { - const entityState = this.hass.states[automation]; - return entityState - ? html` -
- - + ${this._related.automation.map((automation) => { + const entityState = + this.hass.states[automation]; + return entityState + ? html`
+ - - ${computeStateName(entityState)} - - - - - ${!entityState.attributes.id - ? html` - - ${this.hass.localize( - "ui.panel.config.devices.cant_edit" - )} - - ` - : ""} -
- ` - : ""; - }) + + + ${computeStateName(entityState)} + + + + + ${!entityState.attributes.id + ? html` + + ${this.hass.localize( + "ui.panel.config.devices.cant_edit" + )} + + ` + : ""} +
` + : ""; + })} + + ` : html`
${this.hass.localize( @@ -479,43 +483,49 @@ export class HaConfigDevicePage extends LitElement { .path=${mdiPlusCircle} > - ${this._related?.scene?.length - ? this._related.scene.map((scene) => { - const entityState = this.hass.states[scene]; - return entityState - ? html` -
- - - - ${computeStateName(entityState)} - - - - - ${!entityState.attributes.id - ? html` - - ${this.hass.localize( - "ui.panel.config.devices.cant_edit" - )} - - ` - : ""} -
- ` - : ""; - }) + ? html` +
+ ${this._related.scene.map((scene) => { + const entityState = this.hass.states[scene]; + return entityState + ? html` +
+ + + + ${computeStateName(entityState)} + + + + + ${!entityState.attributes.id + ? html` + + ${this.hass.localize( + "ui.panel.config.devices.cant_edit" + )} + + ` + : ""} +
+ ` + : ""; + })} +
+ ` : html`
${this.hass.localize( @@ -553,23 +563,27 @@ export class HaConfigDevicePage extends LitElement { > ${this._related?.script?.length - ? this._related.script.map((script) => { - const entityState = this.hass.states[script]; - return entityState - ? html` - - - - ${computeStateName(entityState)} - - - - - ` - : ""; - }) + ? html` +
+ ${this._related.script.map((script) => { + const entityState = this.hass.states[script]; + return entityState + ? html` + + + + ${computeStateName(entityState)} + + + + + ` + : ""; + })} +
+ ` : html`
${this.hass.localize( @@ -869,6 +883,7 @@ export class HaConfigDevicePage extends LitElement { display: flex; align-items: center; justify-content: space-between; + padding-bottom: 12px; } .card-header ha-icon-button { @@ -978,6 +993,10 @@ export class HaConfigDevicePage extends LitElement { ha-svg-icon[slot="trailingIcon"] { display: block; } + + .items { + padding-bottom: 16px; + } `, ]; } From 77d54df0076c53fcf14ae1e4b0e3fdd191036ccf Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Thu, 4 Nov 2021 12:18:36 +0100 Subject: [PATCH 04/19] Add Bluetooth source_type icon (#10507) Co-authored-by: Bram Kragten --- src/common/entity/domain_icon.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index 4d41728cdd..b3033c346c 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -1,7 +1,10 @@ import { mdiAccount, + mdiAccountArrowRight, mdiAirHumidifierOff, mdiAirHumidifier, + mdiBluetooth, + mdiBluetoothConnect, mdiLanConnect, mdiLanDisconnect, mdiLockOpen, @@ -51,7 +54,12 @@ export const domainIcon = ( if (stateObj?.attributes.source_type === "router") { return compareState === "home" ? mdiLanConnect : mdiLanDisconnect; } - return mdiAccount; + if ( + ["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type) + ) { + return compareState === "home" ? mdiBluetoothConnect : mdiBluetooth; + } + return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount; case "humidifier": return state && state === "off" ? mdiAirHumidifierOff : mdiAirHumidifier; From 2bbb4acf3d13d0aca79e9c549b67a5a1da2e373d Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Thu, 4 Nov 2021 13:37:38 +0100 Subject: [PATCH 05/19] change switch icon to mdiToggleSwitch (#10475) Co-authored-by: Bram Kragten --- src/common/const.ts | 1 - src/common/entity/domain_icon.ts | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/common/const.ts b/src/common/const.ts index 1222fa9ebf..d75aa3f3dc 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -103,7 +103,6 @@ export const FIXED_DOMAIN_ICONS = { siren: mdiBullhorn, simple_alarm: mdiBell, sun: mdiWhiteBalanceSunny, - switch: mdiFlash, timer: mdiTimerOutline, updater: mdiCloudUpload, vacuum: mdiRobotVacuum, diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index b3033c346c..2a0c3031b3 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -3,6 +3,7 @@ import { mdiAccountArrowRight, mdiAirHumidifierOff, mdiAirHumidifier, + mdiFlash, mdiBluetooth, mdiBluetoothConnect, mdiLanConnect, @@ -14,8 +15,12 @@ import { mdiCastConnected, mdiCast, mdiEmoticonDead, + mdiPowerPlug, + mdiPowerPlugOff, mdiSleep, mdiTimerSand, + mdiToggleSwitch, + mdiToggleSwitchOff, mdiZWave, mdiClock, mdiCalendar, @@ -80,6 +85,16 @@ export const domainIcon = ( case "media_player": return compareState === "playing" ? mdiCastConnected : mdiCast; + case "switch": + switch (stateObj?.attributes.device_class) { + case "outlet": + return state === "on" ? mdiPowerPlug : mdiPowerPlugOff; + case "switch": + return state === "on" ? mdiToggleSwitch : mdiToggleSwitchOff; + default: + return mdiFlash; + } + case "zwave": switch (compareState) { case "dead": From 12ef191a0ff027f3e84c940973fc40769d138c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Nov 2021 13:47:30 +0100 Subject: [PATCH 06/19] Handle missing hass with backup upload during onboarding (#10523) --- hassio/src/components/hassio-upload-backup.ts | 18 +-- .../backup/dialog-hassio-backup-upload.ts | 4 +- .../dialogs/backup/dialog-hassio-backup.ts | 115 ++++++++---------- src/components/ha-file-upload.ts | 5 +- src/data/hassio/backup.ts | 4 +- 5 files changed, 62 insertions(+), 84 deletions(-) diff --git a/hassio/src/components/hassio-upload-backup.ts b/hassio/src/components/hassio-upload-backup.ts index 70b326bff9..ae19a12c81 100644 --- a/hassio/src/components/hassio-upload-backup.ts +++ b/hassio/src/components/hassio-upload-backup.ts @@ -16,11 +16,9 @@ declare global { } } -const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB - @customElement("hassio-upload-backup") export class HassioUploadBackup extends LitElement { - public hass!: HomeAssistant; + public hass?: HomeAssistant; @state() public value: string | null = null; @@ -43,20 +41,6 @@ export class HassioUploadBackup extends LitElement { private async _uploadFile(ev) { const file = ev.detail.files[0]; - if (file.size > MAX_FILE_SIZE) { - showAlertDialog(this, { - title: "Backup file is too big", - text: html`The maximum allowed filesize is 1GB.
- Have a look here on how to restore it.`, - confirmText: "ok", - }); - return; - } - if (!["application/x-tar"].includes(file.type)) { showAlertDialog(this, { title: "Unsupported file format", diff --git a/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts b/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts index 2b835f0ad9..4fd6751d36 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts @@ -15,7 +15,7 @@ export class DialogHassioBackupUpload extends LitElement implements HassDialog { - @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _params?: HassioBackupUploadDialogParams; @@ -54,7 +54,7 @@ export class DialogHassioBackupUpload Upload backup { - @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @state() private _error?: string; @@ -77,7 +77,7 @@ class HassioBackupDialog ${this._backup.name} @@ -192,25 +192,23 @@ class HassioBackupDialog } if (!this._dialogParams?.onboarding) { - this.hass - .callApi( - "POST", + this.hass!.callApi( + "POST", - `hassio/${ - atLeastVersion(this.hass.config.version, 2021, 9) - ? "backups" - : "snapshots" - }/${this._backup!.slug}/restore/partial`, - backupDetails - ) - .then( - () => { - this.closeDialog(); - }, - (error) => { - this._error = error.body.message; - } - ); + `hassio/${ + atLeastVersion(this.hass!.config.version, 2021, 9) + ? "backups" + : "snapshots" + }/${this._backup!.slug}/restore/partial`, + backupDetails + ).then( + () => { + this.closeDialog(); + }, + (error) => { + this._error = error.body.message; + } + ); } else { fireEvent(this, "restoring"); fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, { @@ -244,24 +242,22 @@ class HassioBackupDialog } if (!this._dialogParams?.onboarding) { - this.hass - .callApi( - "POST", - `hassio/${ - atLeastVersion(this.hass.config.version, 2021, 9) - ? "backups" - : "snapshots" - }/${this._backup!.slug}/restore/full`, - backupDetails - ) - .then( - () => { - this.closeDialog(); - }, - (error) => { - this._error = error.body.message; - } - ); + this.hass!.callApi( + "POST", + `hassio/${ + atLeastVersion(this.hass!.config.version, 2021, 9) + ? "backups" + : "snapshots" + }/${this._backup!.slug}/restore/full`, + backupDetails + ).then( + () => { + this.closeDialog(); + }, + (error) => { + this._error = error.body.message; + } + ); } else { fireEvent(this, "restoring"); fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, { @@ -283,36 +279,33 @@ class HassioBackupDialog return; } - this.hass - - .callApi( - atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST", - `hassio/${ - atLeastVersion(this.hass.config.version, 2021, 9) - ? `backups/${this._backup!.slug}` - : `snapshots/${this._backup!.slug}/remove` - }` - ) - .then( - () => { - if (this._dialogParams!.onDelete) { - this._dialogParams!.onDelete(); - } - this.closeDialog(); - }, - (error) => { - this._error = error.body.message; + this.hass!.callApi( + atLeastVersion(this.hass!.config.version, 2021, 9) ? "DELETE" : "POST", + `hassio/${ + atLeastVersion(this.hass!.config.version, 2021, 9) + ? `backups/${this._backup!.slug}` + : `snapshots/${this._backup!.slug}/remove` + }` + ).then( + () => { + if (this._dialogParams!.onDelete) { + this._dialogParams!.onDelete(); } - ); + this.closeDialog(); + }, + (error) => { + this._error = error.body.message; + } + ); } private async _downloadClicked() { let signedPath: { path: string }; try { signedPath = await getSignedPath( - this.hass, + this.hass!, `/api/hassio/${ - atLeastVersion(this.hass.config.version, 2021, 9) + atLeastVersion(this.hass!.config.version, 2021, 9) ? "backups" : "snapshots" }/${this._backup!.slug}/download` diff --git a/src/components/ha-file-upload.ts b/src/components/ha-file-upload.ts index 611ff357d5..31dd853e06 100644 --- a/src/components/ha-file-upload.ts +++ b/src/components/ha-file-upload.ts @@ -17,7 +17,7 @@ declare global { @customElement("ha-file-upload") export class HaFileUpload extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property() public accept!: string; @@ -88,7 +88,8 @@ export class HaFileUpload extends LitElement { ` diff --git a/src/data/hassio/backup.ts b/src/data/hassio/backup.ts index b19d4c1c66..c5af101857 100644 --- a/src/data/hassio/backup.ts +++ b/src/data/hassio/backup.ts @@ -79,7 +79,7 @@ export const fetchHassioBackups = async ( }; export const fetchHassioBackupInfo = async ( - hass: HomeAssistant, + hass: HomeAssistant | undefined, backup: string ): Promise => { if (hass) { @@ -202,7 +202,7 @@ export const createHassioPartialBackup = async ( }; export const uploadBackup = async ( - hass: HomeAssistant, + hass: HomeAssistant | undefined, file: File ): Promise> => { const fd = new FormData(); From 3fd0becfd4379750b44fda2f1ec6ae2c8696e60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 4 Nov 2021 13:47:53 +0100 Subject: [PATCH 07/19] Update registry dialog (#10524) --- .../registries/dialog-hassio-registries.ts | 198 ++++++++---------- 1 file changed, 93 insertions(+), 105 deletions(-) diff --git a/hassio/src/dialogs/registries/dialog-hassio-registries.ts b/hassio/src/dialogs/registries/dialog-hassio-registries.ts index 28803a3298..1eb0072a3f 100644 --- a/hassio/src/dialogs/registries/dialog-hassio-registries.ts +++ b/hassio/src/dialogs/registries/dialog-hassio-registries.ts @@ -1,12 +1,12 @@ import "@material/mwc-button/mwc-button"; -import "@material/mwc-list/mwc-list-item"; import { mdiDelete } from "@mdi/js"; -import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import "../../../../src/components/ha-circular-progress"; import { createCloseHeading } from "../../../../src/components/ha-dialog"; +import "../../../../src/components/ha-form/ha-form"; +import { HaFormSchema } from "../../../../src/components/ha-form/types"; import "../../../../src/components/ha-icon-button"; +import "../../../../src/components/ha-settings-row"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { addHassioDockerRegistry, @@ -19,22 +19,41 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import type { HomeAssistant } from "../../../../src/types"; import { RegistriesDialogParams } from "./show-dialog-registries"; +const SCHEMA = [ + { + type: "string", + name: "registry", + required: true, + }, + { + type: "string", + name: "username", + required: true, + }, + { + type: "string", + name: "password", + required: true, + format: "password", + }, +]; + @customElement("dialog-hassio-registries") class HassioRegistriesDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public supervisor!: Supervisor; - @property({ attribute: false }) private _registries?: { + @state() private _registries?: { registry: string; username: string; }[]; - @state() private _registry?: string; - - @state() private _username?: string; - - @state() private _password?: string; + @state() private _input: { + registry?: string; + username?: string; + password?: string; + } = {}; @state() private _opened = false; @@ -47,6 +66,7 @@ class HassioRegistriesDialog extends LitElement { @closed=${this.closeDialog} scrimClickAction escapeKeyAction + hideActions .heading=${createCloseHeading( this.hass, this._addingRegistry @@ -54,99 +74,77 @@ class HassioRegistriesDialog extends LitElement { : this.supervisor.localize("dialog.registries.title_manage") )} > -
- ${this._addingRegistry - ? html` - - - - + ${this._addingRegistry + ? html` + +
${this.supervisor.localize("dialog.registries.add_registry")} - ` - : html`${this._registries?.length - ? this._registries.map( - (entry) => html` - - ${entry.registry} - ${this.supervisor.localize( - "dialog.registries.username" - )}: - ${entry.username} - - - ` - ) - : html` - - ${this.supervisor.localize( - "dialog.registries.no_registries" - )} - - `} +
+ ` + : html`${this._registries?.length + ? this._registries.map( + (entry) => html` + + ${entry.registry} + + ${this.supervisor.localize( + "dialog.registries.username" + )}: + ${entry.username} + + + + ` + ) + : html` + + ${this.supervisor.localize( + "dialog.registries.no_registries" + )} + + `} +
${this.supervisor.localize( "dialog.registries.add_new_registry" )} - `} -
+ +
`} `; } - private _inputChanged(ev: Event) { - const target = ev.currentTarget as PaperInputElement; - this[`_${target.name}`] = target.value; + private _computeLabel = (schema: HaFormSchema) => + this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name; + + private _valueChanged(ev: CustomEvent) { + this._input = ev.detail.value; } public async showDialog(dialogParams: RegistriesDialogParams): Promise { this._opened = true; + this._input = {}; this.supervisor = dialogParams.supervisor; await this._loadRegistries(); await this.updateComplete; @@ -155,6 +153,7 @@ class HassioRegistriesDialog extends LitElement { public closeDialog(): void { this._addingRegistry = false; this._opened = false; + this._input = {}; } public focus(): void { @@ -179,15 +178,16 @@ class HassioRegistriesDialog extends LitElement { private async _addNewRegistry(): Promise { const data = {}; - data[this._registry!] = { - username: this._username, - password: this._password, + data[this._input.registry!] = { + username: this._input.username, + password: this._input.password, }; try { await addHassioDockerRegistry(this.hass, data); await this._loadRegistries(); this._addingRegistry = false; + this._input = {}; } catch (err: any) { showAlertDialog(this, { title: this.supervisor.localize("dialog.registries.failed_to_add"), @@ -215,32 +215,20 @@ class HassioRegistriesDialog extends LitElement { haStyle, haStyleDialog, css` - ha-dialog.button-left { - --justify-action-buttons: flex-start; - } - paper-icon-item { - cursor: pointer; - } - .form { - color: var(--primary-text-color); - } - .option { + .registry { border: 1px solid var(--divider-color); border-radius: 4px; margin-top: 4px; } - mwc-button { - margin-left: 8px; + .action { + margin-top: 24px; + width: 100%; + display: flex; + justify-content: flex-end; } ha-icon-button { color: var(--error-color); - margin: -10px; - } - mwc-list-item { - cursor: default; - } - mwc-list-item span[slot="secondary"] { - color: var(--secondary-text-color); + margin-right: -10px; } `, ]; From 9425b943dd56b3ca4789ed6a02a94c9ccab691da Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Nov 2021 18:09:21 +0100 Subject: [PATCH 08/19] Add separate cast media entrypoint (with ES5) (#10527) --- build-scripts/bundle.js | 1 + build-scripts/gulp/entry-html.js | 18 +++++++++++++ cast/src/html/media.html.template | 45 +++++++++++++++++++++++++++++++ cast/src/media/entrypoint.ts | 25 +++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 cast/src/html/media.html.template create mode 100644 cast/src/media/entrypoint.ts diff --git a/build-scripts/bundle.js b/build-scripts/bundle.js index c15e1ab1dc..2325d80bca 100644 --- a/build-scripts/bundle.js +++ b/build-scripts/bundle.js @@ -165,6 +165,7 @@ module.exports.config = { cast({ isProdBuild, latestBuild }) { const entry = { launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), + media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"), }; if (latestBuild) { diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index 8627d61713..156d100cf9 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -154,6 +154,15 @@ gulp.task("gen-index-cast-dev", (done) => { contentReceiver ); + const contentMedia = renderCastTemplate("media", { + latestMediaJS: "/frontend_latest/media.js", + es5MediaJS: "/frontend_es5/media.js", + }); + fs.outputFileSync( + path.resolve(paths.cast_output_root, "media.html"), + contentMedia + ); + const contentFAQ = renderCastTemplate("launcher-faq", { latestLauncherJS: "/frontend_latest/launcher.js", es5LauncherJS: "/frontend_es5/launcher.js", @@ -192,6 +201,15 @@ gulp.task("gen-index-cast-prod", (done) => { contentReceiver ); + const contentMedia = renderCastTemplate("media", { + latestMediaJS: latestManifest["media.js"], + es5MediaJS: es5Manifest["media.js"], + }); + fs.outputFileSync( + path.resolve(paths.cast_output_root, "media.html"), + contentMedia + ); + const contentFAQ = renderCastTemplate("launcher-faq", { latestLauncherJS: latestManifest["launcher.js"], es5LauncherJS: es5Manifest["launcher.js"], diff --git a/cast/src/html/media.html.template b/cast/src/html/media.html.template new file mode 100644 index 0000000000..e8ad478488 --- /dev/null +++ b/cast/src/html/media.html.template @@ -0,0 +1,45 @@ + + + + + + + + + <%= renderTemplate('_js_base') %> + + + + + + + + diff --git a/cast/src/media/entrypoint.ts b/cast/src/media/entrypoint.ts new file mode 100644 index 0000000000..3648e8b318 --- /dev/null +++ b/cast/src/media/entrypoint.ts @@ -0,0 +1,25 @@ +import { CastReceiverContext } from "chromecast-caf-receiver/cast.framework"; + +const castContext = + cast.framework.CastContext.getInstance() as unknown as CastReceiverContext; + +const playerManager = castContext.getPlayerManager(); + +playerManager.setMessageInterceptor( + cast.framework.messages.MessageType.LOAD, + (loadRequestData) => { + const media = loadRequestData.media; + // Special handling if it came from Google Assistant + if (media.entity) { + media.contentId = media.entity; + media.streamType = cast.framework.messages.StreamType.LIVE; + media.contentType = "application/vnd.apple.mpegurl"; + // @ts-ignore + media.hlsVideoSegmentFormat = + cast.framework.messages.HlsVideoSegmentFormat.FMP4; + } + return loadRequestData; + } +); + +castContext.start(); From b97d6d7059960c5aa548a40bcab294b33b0a91fa Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 4 Nov 2021 18:12:07 +0100 Subject: [PATCH 09/19] Play dummy media to prevent app from closing (#10531) --- cast/src/receiver/entrypoint.ts | 24 ++++++++++++++++++++++++ cast/src/receiver/layout/hc-main.ts | 5 +++++ 2 files changed, 29 insertions(+) diff --git a/cast/src/receiver/entrypoint.ts b/cast/src/receiver/entrypoint.ts index e5c9791276..19053a23cd 100644 --- a/cast/src/receiver/entrypoint.ts +++ b/cast/src/receiver/entrypoint.ts @@ -28,11 +28,29 @@ const setTouchControlsVisibility = (visible: boolean) => { } }; +const playDummyMedia = () => { + const playerManager = castContext.getPlayerManager(); + const loadRequestData = new cast.framework.messages.LoadRequestData(); + loadRequestData.autoplay = true; + loadRequestData.media = new cast.framework.messages.MediaInformation(); + loadRequestData.media.contentId = + "https://www.home-assistant.io/images/blog/2018-09-thinking-big/social.png"; + loadRequestData.media.contentType = "image/jpeg"; + loadRequestData.media.streamType = cast.framework.messages.StreamType.NONE; + const metadata = new cast.framework.messages.GenericMediaMetadata(); + metadata.title = "Home Assistant Lovelace"; + loadRequestData.media.metadata = metadata; + + loadRequestData.requestId = 0; + playerManager.load(loadRequestData); +}; + const showLovelaceController = () => { mediaPlayer.style.display = "none"; lovelaceController.style.display = "initial"; document.body.setAttribute("style", "overflow-y: auto !important"); setTouchControlsVisibility(false); + playDummyMedia(); }; const showMediaPlayer = () => { @@ -103,6 +121,12 @@ const playerManager = castContext.getPlayerManager(); playerManager.setMessageInterceptor( cast.framework.messages.MessageType.LOAD, (loadRequestData) => { + if ( + loadRequestData.media.contentId === + "https://www.home-assistant.io/images/blog/2018-09-thinking-big/social.png" + ) { + return loadRequestData; + } // We received a play media command, hide Lovelace and show media player showMediaPlayer(); const media = loadRequestData.media; diff --git a/cast/src/receiver/layout/hc-main.ts b/cast/src/receiver/layout/hc-main.ts index 4e47ad61ae..207d46bb4a 100644 --- a/cast/src/receiver/layout/hc-main.ts +++ b/cast/src/receiver/layout/hc-main.ts @@ -107,6 +107,7 @@ export class HcMain extends HassElement { this._sendStatus(); } }); + this.addEventListener("dialog-closed", this._dialogClosed); } private _sendStatus(senderId?: string) { @@ -131,6 +132,10 @@ export class HcMain extends HassElement { } } + private _dialogClosed = () => { + document.body.setAttribute("style", "overflow-y: auto !important"); + }; + private async _handleGetStatusMessage(msg: GetStatusMessage) { this._sendStatus(msg.senderId!); } From ea331dbe0b5d107bff1383a78ceb7741c6d7af22 Mon Sep 17 00:00:00 2001 From: chriss158 Date: Fri, 5 Nov 2021 11:55:03 +0100 Subject: [PATCH 10/19] Fix cut off slider value (#10250) --- src/panels/lovelace/cards/hui-entities-card.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index a9aba32a3d..f733c678a3 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -231,7 +231,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { display: flex; flex-direction: column; justify-content: space-between; - overflow: hidden; } .card-header { display: flex; @@ -261,7 +260,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { } #states > div > * { - overflow: hidden; + overflow: clip visible; } #states > div { From c26a59d8059497104ed52ad44b17146547f0173c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 5 Nov 2021 12:01:14 +0100 Subject: [PATCH 11/19] Format timestamp sensor states (#10525) --- src/common/entity/compute_state_display.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 9e4d8d87e4..c07be7594d 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -117,7 +117,10 @@ export const computeStateDisplay = ( } // state of button is a timestamp - if (domain === "button") { + if ( + domain === "button" || + (domain === "sensor" && stateObj.attributes.device_class === "timestamp") + ) { return formatDateTime(new Date(compareState), locale); } From fe5a582a74d51bb5ea1441f03ab4c23621e7edad Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 8 Nov 2021 01:22:52 -0800 Subject: [PATCH 12/19] Filter out entities when expanding device in target (#10570) --- src/components/ha-target-picker.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index 4edf45230d..85fb984a59 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -502,6 +502,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { } private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean { + if (entity.entity_category) { + return false; + } if ( this.includeDomains && !this.includeDomains.includes(computeDomain(entity.entity_id)) From 729a928cfe4bfa63986e4e2b70bcec593cb34148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=2E=20=C3=81rkosi=20R=C3=B3bert?= Date: Mon, 8 Nov 2021 10:27:08 +0100 Subject: [PATCH 13/19] Update connectivity icons (#10558) --- src/common/entity/binary_sensor_icon.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/entity/binary_sensor_icon.ts b/src/common/entity/binary_sensor_icon.ts index 0f1dfaeaf2..0d104e87e9 100644 --- a/src/common/entity/binary_sensor_icon.ts +++ b/src/common/entity/binary_sensor_icon.ts @@ -6,6 +6,8 @@ import { mdiBrightness5, mdiBrightness7, mdiCheckboxMarkedCircle, + mdiCheckNetworkOutline, + mdiCloseNetworkOutline, mdiCheckCircle, mdiCropPortrait, mdiDoorClosed, @@ -26,8 +28,6 @@ import { mdiPowerPlugOff, mdiRadioboxBlank, mdiRun, - mdiServerNetwork, - mdiServerNetworkOff, mdiSmoke, mdiSnowflake, mdiSquare, @@ -55,7 +55,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => { case "cold": return is_off ? mdiThermometer : mdiSnowflake; case "connectivity": - return is_off ? mdiServerNetworkOff : mdiServerNetwork; + return is_off ? mdiCloseNetworkOutline : mdiCheckNetworkOutline; case "door": return is_off ? mdiDoorClosed : mdiDoorOpen; case "garage_door": From d7732ee8502697b5d05b4fc4de9a95089d56cfe9 Mon Sep 17 00:00:00 2001 From: rianadon Date: Mon, 8 Nov 2021 01:29:34 -0800 Subject: [PATCH 14/19] Improve accessibility on login page (#9731) --- src/auth/ha-pick-auth-provider.ts | 6 +++++- src/html/authorize.html.template | 4 ++-- src/html/onboarding.html.template | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/auth/ha-pick-auth-provider.ts b/src/auth/ha-pick-auth-provider.ts index ecaff2ebd8..3ee7638b10 100644 --- a/src/auth/ha-pick-auth-provider.ts +++ b/src/auth/ha-pick-auth-provider.ts @@ -21,7 +21,11 @@ class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {

${this.localize("ui.panel.page-authorize.pick_auth_provider")}:

${this.authProviders.map( (provider) => html` - + ${provider.name} diff --git a/src/html/authorize.html.template b/src/html/authorize.html.template index 4b7fbc9c53..988170769f 100644 --- a/src/html/authorize.html.template +++ b/src/html/authorize.html.template @@ -33,7 +33,7 @@
- + Home Assistant

Initializing

@@ -70,4 +70,4 @@ })(); - \ No newline at end of file + diff --git a/src/html/onboarding.html.template b/src/html/onboarding.html.template index 5d002d1e8e..e6e6891256 100644 --- a/src/html/onboarding.html.template +++ b/src/html/onboarding.html.template @@ -64,7 +64,7 @@
- + Home Assistant
From 0e8a06e24d28048f6c4dec01b4852fd85453adc7 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 8 Nov 2021 11:41:17 +0100 Subject: [PATCH 15/19] Use correct darkMode flag for image variant selection (#10574) --- src/dialogs/config-flow/step-flow-pick-flow.ts | 2 +- src/dialogs/config-flow/step-flow-pick-handler.ts | 2 +- src/onboarding/onboarding-integrations.ts | 4 ++-- src/panels/config/devices/ha-config-device-page.ts | 2 +- .../config/energy/components/ha-energy-grid-settings.ts | 4 ++-- .../config/energy/dialogs/dialog-energy-solar-settings.ts | 2 +- src/panels/config/info/integrations-card.ts | 2 +- src/panels/config/integrations/ha-integration-header.ts | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dialogs/config-flow/step-flow-pick-flow.ts b/src/dialogs/config-flow/step-flow-pick-flow.ts index 82578354d3..2296d465a0 100644 --- a/src/dialogs/config-flow/step-flow-pick-flow.ts +++ b/src/dialogs/config-flow/step-flow-pick-flow.ts @@ -45,7 +45,7 @@ class StepFlowPickFlow extends LitElement { domain: flow.handler, type: "icon", useFallback: true, - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} referrerpolicy="no-referrer" /> diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index e15a2d1316..d3b0028b3e 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -102,7 +102,7 @@ class StepFlowPickHandler extends LitElement { domain: handler.slug, type: "icon", useFallback: true, - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} referrerpolicy="no-referrer" /> diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index 9b0370bb9b..d6874a6ae8 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -81,7 +81,7 @@ class OnboardingIntegrations extends LitElement { .domain=${entry.domain} .title=${title} .badgeIcon=${mdiCheck} - .darkOptimizedIcon=${this.hass.selectedTheme?.dark} + .darkOptimizedIcon=${this.hass.themes?.darkMode} > `, ]; @@ -98,7 +98,7 @@ class OnboardingIntegrations extends LitElement { clickable .domain=${flow.handler} .title=${title} - .darkOptimizedIcon=${this.hass.selectedTheme?.dark} + .darkOptimizedIcon=${this.hass.themes?.darkMode} > `, diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index dd0603faf7..9b4de45b9b 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -293,7 +293,7 @@ export class HaConfigDevicePage extends LitElement { src=${brandsUrl({ domain: integrations[0], type: "logo", - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} referrerpolicy="no-referrer" @load=${this._onImageLoad} diff --git a/src/panels/config/energy/components/ha-energy-grid-settings.ts b/src/panels/config/energy/components/ha-energy-grid-settings.ts index d0b3df4e76..700329dd1c 100644 --- a/src/panels/config/energy/components/ha-energy-grid-settings.ts +++ b/src/panels/config/energy/components/ha-energy-grid-settings.ts @@ -202,7 +202,7 @@ export class EnergyGridSettings extends LitElement { src=${brandsUrl({ domain: "co2signal", type: "icon", - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} /> ${entry.title} @@ -223,7 +223,7 @@ export class EnergyGridSettings extends LitElement { src=${brandsUrl({ domain: "co2signal", type: "icon", - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} /> diff --git a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts index 320f94b1c0..25f6073e85 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts @@ -136,7 +136,7 @@ export class DialogEnergySolarSettings src=${brandsUrl({ domain: entry.domain, type: "icon", - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} />${entry.title}
`} diff --git a/src/panels/config/info/integrations-card.ts b/src/panels/config/info/integrations-card.ts index 68bd6625e9..979393eac1 100644 --- a/src/panels/config/info/integrations-card.ts +++ b/src/panels/config/info/integrations-card.ts @@ -98,7 +98,7 @@ class IntegrationsCard extends LitElement { domain: domain, type: "icon", useFallback: true, - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} referrerpolicy="no-referrer" /> diff --git a/src/panels/config/integrations/ha-integration-header.ts b/src/panels/config/integrations/ha-integration-header.ts index 9f35837e14..30053e4405 100644 --- a/src/panels/config/integrations/ha-integration-header.ts +++ b/src/panels/config/integrations/ha-integration-header.ts @@ -84,7 +84,7 @@ export class HaIntegrationHeader extends LitElement { src=${brandsUrl({ domain: this.domain, type: "icon", - darkOptimized: this.hass.selectedTheme?.dark, + darkOptimized: this.hass.themes?.darkMode, })} referrerpolicy="no-referrer" @error=${this._onImageError} From 67d79d618ab4173ce139eadeee3fe5bc48902473 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Nov 2021 18:28:17 +0100 Subject: [PATCH 16/19] Allow to input decimal in ha-form-float (#10575) --- src/components/ha-form/ha-form-float.ts | 10 ++++++++-- src/panels/lovelace/cards/hui-logbook-card.ts | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/ha-form/ha-form-float.ts b/src/components/ha-form/ha-form-float.ts index f27c669247..7cae936ef7 100644 --- a/src/components/ha-form/ha-form-float.ts +++ b/src/components/ha-form/ha-form-float.ts @@ -47,12 +47,19 @@ export class HaFormFloat extends LitElement implements HaFormElement { private _valueChanged(ev: Event) { const source = ev.target as TextField; - const rawValue = source.value; + const rawValue = source.value.replace(",", "."); let value: number | undefined; + if (rawValue.endsWith(".")) { + return; + } + if (rawValue !== "") { value = parseFloat(rawValue); + if (isNaN(value)) { + value = undefined; + } } // Detect anything changed @@ -61,7 +68,6 @@ export class HaFormFloat extends LitElement implements HaFormElement { const newRawValue = value === undefined ? "" : String(value); if (source.value !== newRawValue) { source.value = newRawValue; - return; } return; } diff --git a/src/panels/lovelace/cards/hui-logbook-card.ts b/src/panels/lovelace/cards/hui-logbook-card.ts index c9c4befabb..ae459a11d3 100644 --- a/src/panels/lovelace/cards/hui-logbook-card.ts +++ b/src/panels/lovelace/cards/hui-logbook-card.ts @@ -241,7 +241,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard { ); const lastDate = this._lastLogbookDate || hoursToShowDate; const now = new Date(); - let newEntries; + let newEntries: LogbookEntry[]; try { [newEntries] = await Promise.all([ @@ -256,6 +256,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard { ]); } catch (err: any) { this._error = err.message; + return; } const logbookEntries = this._logbookEntries From a268040ae79d733409f6a6b62e5b41ef2900f21b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Nov 2021 18:28:27 +0100 Subject: [PATCH 17/19] Fix datetime polyfill for latest build (#10572) --- src/common/translations/localize.ts | 1 + src/state/connection-mixin.ts | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index a094d48b3e..6ff6e24b90 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -32,6 +32,7 @@ if (__BUILD__ === "latest") { } if (shouldPolyfillDateTime()) { polyfills.push(import("@formatjs/intl-datetimeformat/polyfill")); + polyfills.push(import("@formatjs/intl-datetimeformat/add-all-tz")); } } diff --git a/src/state/connection-mixin.ts b/src/state/connection-mixin.ts index 5c5f0b1468..5858ac67ef 100644 --- a/src/state/connection-mixin.ts +++ b/src/state/connection-mixin.ts @@ -24,6 +24,7 @@ import { getState } from "../util/ha-pref-storage"; import hassCallApi from "../util/hass-call-api"; import { getLocalLanguage } from "../util/common-translation"; import { HassBaseEl } from "./hass-base-mixin"; +import { polyfillsLoaded } from "../common/translations/localize"; export const connectionMixin = >( superClass: T @@ -180,12 +181,18 @@ export const connectionMixin = >( subscribeEntities(conn, (states) => this._updateHass({ states })); subscribeConfig(conn, (config) => { - if ( - this.hass?.config?.time_zone !== config.time_zone && - "__setDefaultTimeZone" in Intl.DateTimeFormat - ) { - // @ts-ignore - Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); + if (this.hass?.config?.time_zone !== config.time_zone) { + if (__BUILD__ === "latest" && polyfillsLoaded) { + polyfillsLoaded.then(() => { + if ("__setDefaultTimeZone" in Intl.DateTimeFormat) { + // @ts-ignore + Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); + } + }); + } else if ("__setDefaultTimeZone" in Intl.DateTimeFormat) { + // @ts-ignore + Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone); + } } this._updateHass({ config }); }); From 06d4ccf3441b49cdfe88289d321f78d31519abed Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Nov 2021 18:29:10 +0100 Subject: [PATCH 18/19] Allow create zone without icon + add icon picker (#10447) --- src/components/ha-icon-picker.ts | 4 +++ src/data/zone.ts | 2 +- src/panels/config/zone/dialog-zone-detail.ts | 33 ++++++++++++-------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 9197d5bb7b..93da7a5d87 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -60,6 +60,8 @@ export class HaIconPicker extends LitElement { @property({ type: Boolean }) public disabled = false; + @property({ type: Boolean }) public invalid = false; + @state() private _opened = false; @query("vaadin-combo-box-light", true) private comboBox!: HTMLElement; @@ -86,6 +88,8 @@ export class HaIconPicker extends LitElement { autocomplete="off" autocorrect="off" spellcheck="false" + .errorMessage=${this.errorMessage} + .invalid=${this.invalid} > ${this._value || this.placeholder ? html` diff --git a/src/data/zone.ts b/src/data/zone.ts index e1c630f020..a4e091c30e 100644 --- a/src/data/zone.ts +++ b/src/data/zone.ts @@ -12,7 +12,7 @@ export interface Zone { } export interface ZoneMutableParams { - icon: string; + icon?: string; latitude: number; longitude: number; name: string; diff --git a/src/panels/config/zone/dialog-zone-detail.ts b/src/panels/config/zone/dialog-zone-detail.ts index 8adc9b1591..81c3e78807 100644 --- a/src/panels/config/zone/dialog-zone-detail.ts +++ b/src/panels/config/zone/dialog-zone-detail.ts @@ -8,6 +8,7 @@ import { addDistanceToCoord } from "../../../common/location/add_distance_to_coo import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-formfield"; +import "../../../components/ha-icon-picker"; import "../../../components/ha-switch"; import "../../../components/map/ha-locations-editor"; import type { MarkerLocation } from "../../../components/map/ha-locations-editor"; @@ -77,14 +78,18 @@ class DialogZoneDetail extends LitElement { if (!this._params) { return html``; } - const nameValid = this._name.trim() === ""; - const iconValid = !this._icon.trim().includes(":"); - const latValid = String(this._latitude) === ""; - const lngValid = String(this._longitude) === ""; - const radiusValid = String(this._radius) === ""; + const nameInvalid = this._name.trim() === ""; + const iconInvalid = Boolean(this._icon && !this._icon.trim().includes(":")); + const latInvalid = String(this._latitude) === ""; + const lngInvalid = String(this._longitude) === ""; + const radiusInvalid = String(this._radius) === ""; const valid = - !nameValid && !iconValid && !latValid && !lngValid && !radiusValid; + !nameInvalid && + !iconInvalid && + !latInvalid && + !lngInvalid && + !radiusInvalid; return html` - + .invalid=${iconInvalid} + >

${this.hass!.localize("ui.panel.config.zone.detail.passive_note")} @@ -268,12 +273,14 @@ class DialogZoneDetail extends LitElement { try { const values: ZoneMutableParams = { name: this._name.trim(), - icon: this._icon.trim(), latitude: this._latitude, longitude: this._longitude, passive: this._passive, radius: this._radius, }; + if (this._icon) { + values.icon = this._icon.trim(); + } if (this._params!.entry) { await this._params!.updateEntry!(values); } else { From fcdceba09d115c31058074cfc191c62716b2d90f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 8 Nov 2021 18:30:59 +0100 Subject: [PATCH 19/19] Bumped version to 20211108.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a6fa1bada9..aeece81649 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name="home-assistant-frontend", - version="20211103.0", + version="20211108.0", description="The Home Assistant frontend", url="https://github.com/home-assistant/frontend", author="The Home Assistant Authors",