From 351ec08a71a81632a0bcb77b36c0db92f83b6417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 6 Apr 2022 09:57:17 +0200 Subject: [PATCH 001/181] Use selectors for add-on configurations (#12234) --- .../addon-view/config/hassio-addon-config.ts | 77 +++++++++++++++---- .../ha-selector/ha-selector-object.ts | 21 +++-- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts index dfdc31dbbc..95b80ff359 100644 --- a/hassio/src/addon-view/config/hassio-addon-config.ts +++ b/hassio/src/addon-view/config/hassio-addon-config.ts @@ -39,7 +39,14 @@ import type { HomeAssistant } from "../../../../src/types"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { hassioStyle } from "../../resources/hassio-style"; -const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"]; +const SUPPORTED_UI_TYPES = [ + "string", + "select", + "boolean", + "integer", + "float", + "schema", +]; const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([ new Type("!secret", { @@ -78,16 +85,56 @@ class HassioAddonConfig extends LitElement { this.addon.translations.en?.configuration?.[entry.name].name || entry.name; - private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] => - // @ts-expect-error supervisor does not implement [string, string] for select.options[] - schema.map((entry) => - entry.type === "select" - ? { - ...entry, - options: entry.options.map((option) => [option, option]), - } - : entry - ) + public computeHelper = (entry: HaFormSchema): string => + this.addon.translations[this.hass.language]?.configuration?.[entry.name] + ?.description || + this.addon.translations.en?.configuration?.[entry.name].description || + ""; + + private _convertSchema = memoizeOne( + // Convert supervisor schema to selectors + (schema: Record): HaFormSchema[] => + schema.map((entry) => + entry.type === "select" + ? { + name: entry.name, + required: entry.required, + selector: { select: { options: entry.options } }, + } + : entry.type === "string" + ? entry.multiple + ? { + name: entry.name, + required: entry.required, + selector: { + select: { options: [], multiple: true, custom_value: true }, + }, + } + : { + name: entry.name, + required: entry.required, + selector: { text: { type: "text" } }, + } + : entry.type === "boolean" + ? { + name: entry.name, + required: entry.required, + selector: { boolean: {} }, + } + : entry.type === "schema" + ? { + name: entry.name, + required: entry.required, + selector: { object: {} }, + } + : entry.type === "float" || entry.type === "integer" + ? { + name: entry.name, + required: entry.required, + selector: { number: { mode: "box" } }, + } + : entry + ) ); private _filteredShchema = memoizeOne( @@ -140,7 +187,8 @@ class HassioAddonConfig extends LitElement { .data=${this._options!} @value-changed=${this._configChanged} .computeLabel=${this.computeLabel} - .schema=${this._schema( + .computeHelper=${this.computeHelper} + .schema=${this._convertSchema( this._showOptional ? this.addon.schema! : this._filteredShchema( @@ -197,8 +245,9 @@ class HassioAddonConfig extends LitElement { protected firstUpdated(changedProps) { super.firstUpdated(changedProps); this._canShowSchema = !this.addon.schema!.find( - // @ts-ignore - (entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple + (entry) => + // @ts-ignore + !SUPPORTED_UI_TYPES.includes(entry.type) ); this._yamlMode = !this._canShowSchema; } diff --git a/src/components/ha-selector/ha-selector-object.ts b/src/components/ha-selector/ha-selector-object.ts index ee6c0940b3..3d2bad4c09 100644 --- a/src/components/ha-selector/ha-selector-object.ts +++ b/src/components/ha-selector/ha-selector-object.ts @@ -3,6 +3,7 @@ import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { HomeAssistant } from "../../types"; import "../ha-yaml-editor"; +import "../ha-input-helper-text"; @customElement("ha-selector-object") export class HaObjectSelector extends LitElement { @@ -12,6 +13,8 @@ export class HaObjectSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property() public placeholder?: string; @property({ type: Boolean }) public disabled = false; @@ -20,13 +23,17 @@ export class HaObjectSelector extends LitElement { protected render() { return html``; + .hass=${this.hass} + .readonly=${this.disabled} + .label=${this.label} + .required=${this.required} + .placeholder=${this.placeholder} + .defaultValue=${this.value} + @value-changed=${this._handleChange} + > + ${this.helper + ? html`${this.helper}` + : ""} `; } private _handleChange(ev) { From aff1ec10bf8d881f0bc618a468df9b2cdb86d26a Mon Sep 17 00:00:00 2001 From: Marius <33354141+Mariusthvdb@users.noreply.github.com> Date: Wed, 6 Apr 2022 16:26:34 +0200 Subject: [PATCH 002/181] replace ToggleSwitch with new LightSwitch (#12218) --- src/common/entity/domain_icon.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/common/entity/domain_icon.ts b/src/common/entity/domain_icon.ts index af89f00d16..d849ae3bfd 100644 --- a/src/common/entity/domain_icon.ts +++ b/src/common/entity/domain_icon.ts @@ -8,26 +8,25 @@ import { mdiCalendar, mdiCast, mdiCastConnected, + mdiCheckCircleOutline, mdiClock, + mdiCloseCircleOutline, mdiGestureTapButton, mdiLanConnect, mdiLanDisconnect, - mdiLightSwitch, mdiLock, mdiLockAlert, mdiLockClock, mdiLockOpen, + mdiPackage, + mdiPackageDown, mdiPackageUp, mdiPowerPlug, mdiPowerPlugOff, mdiRestart, - mdiToggleSwitch, - mdiToggleSwitchOff, - mdiCheckCircleOutline, - mdiCloseCircleOutline, + mdiToggleSwitchVariant, + mdiToggleSwitchVariantOff, mdiWeatherNight, - mdiPackage, - mdiPackageDown, } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; import { updateIsInstalling, UpdateEntity } from "../../data/update"; @@ -109,9 +108,11 @@ export const domainIcon = ( case "outlet": return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff; case "switch": - return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff; + return compareState === "on" + ? mdiToggleSwitchVariant + : mdiToggleSwitchVariantOff; default: - return mdiLightSwitch; + return mdiToggleSwitchVariant; } case "sensor": { From 1dba84956703eea50edfc4448fb94aec20c696d8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 6 Apr 2022 20:54:11 +0200 Subject: [PATCH 003/181] Fix statistics chart for sum stat without state (#12238) --- src/components/chart/statistics-chart.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 944b467a41..9f793a6977 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -347,8 +347,8 @@ class StatisticsChart extends LitElement { statTypes.forEach((type) => { let val: number | null; if (type === "sum") { - if (!initVal) { - initVal = val = stat.state; + if (initVal === null) { + initVal = val = stat.state || 0; prevSum = stat.sum; } else { val = initVal + ((stat.sum || 0) - prevSum!); From bbca7b762bf92570044a23bf8a0e8dbc59b57004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 6 Apr 2022 22:21:06 +0200 Subject: [PATCH 004/181] Use selectors for add-on network configuration (#12235) * Use selectors for add-on network configuration * Show container port as UOM if advanced user * adjust --- .../addon-view/config/hassio-addon-network.ts | 172 ++++++++++-------- src/data/hassio/addon.ts | 5 +- src/translations/en.json | 3 +- 3 files changed, 98 insertions(+), 82 deletions(-) diff --git a/hassio/src/addon-view/config/hassio-addon-network.ts b/hassio/src/addon-view/config/hassio-addon-network.ts index ae3deed1db..66c4853551 100644 --- a/hassio/src/addon-view/config/hassio-addon-network.ts +++ b/hassio/src/addon-view/config/hassio-addon-network.ts @@ -1,4 +1,3 @@ -import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, @@ -8,10 +7,13 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-form/ha-form"; +import type { HaFormSchema } from "../../../../src/components/ha-form/types"; import { HassioAddonDetails, HassioAddonSetOptionParams, @@ -24,16 +26,6 @@ import { HomeAssistant } from "../../../../src/types"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { hassioStyle } from "../../resources/hassio-style"; -interface NetworkItem { - description: string; - container: string; - host: number | null; -} - -interface NetworkItemInput extends PaperInputElement { - container: string; -} - @customElement("hassio-addon-network") class HassioAddonNetwork extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -42,9 +34,13 @@ class HassioAddonNetwork extends LitElement { @property({ attribute: false }) public addon!: HassioAddonDetails; + @state() private _showOptional = false; + + @state() private _configHasChanged = false; + @state() private _error?: string; - @state() private _config?: NetworkItem[]; + @state() private _config?: Record; public connectedCallback(): void { super.connectedCallback(); @@ -56,6 +52,10 @@ class HassioAddonNetwork extends LitElement { return html``; } + const hasHiddenOptions = Object.keys(this._config).find( + (entry) => this._config![entry] === null + ); + return html`
+

+ ${this.supervisor.localize( + "addon.configuration.network.introduction" + )} +

${this._error ? html`${this._error}` : ""} - - - - - - - - ${this._config!.map( - (item) => html` - - - - - - ` - )} - -
- ${this.supervisor.localize( - "addon.configuration.network.container" - )} - - ${this.supervisor.localize( - "addon.configuration.network.host" - )} - ${this.supervisor.localize("common.description")}
${item.container} - - ${this._computeDescription(item)}
+
+ ${hasHiddenOptions + ? html` + + + ` + : ""}
${this.supervisor.localize("common.reset_defaults")} - + ${this.supervisor.localize("common.save")}
@@ -123,50 +120,60 @@ class HassioAddonNetwork extends LitElement { } } - private _computeDescription = (item: NetworkItem): string => - this.addon.translations[this.hass.language]?.network?.[item.container] - ?.description || - this.addon.translations.en?.network?.[item.container]?.description || - item.description; + private _createSchema = memoizeOne( + ( + config: Record, + showOptional: boolean, + advanced: boolean + ): HaFormSchema[] => + (showOptional + ? Object.keys(config) + : Object.keys(config).filter((entry) => config[entry] !== null) + ).map((entry) => ({ + name: entry, + selector: { + number: { + mode: "box", + min: 0, + max: 65535, + unit_of_measurement: advanced ? entry : undefined, + }, + }, + })) + ); + + private _computeLabel = (_: HaFormSchema): string => ""; + + private _computeHelper = (item: HaFormSchema): string => + this.addon.translations[this.hass.language]?.network?.[item.name] || + this.addon.translations.en?.network?.[item.name] || + this.addon.network_description?.[item.name] || + item.name; private _setNetworkConfig(): void { - const network = this.addon.network || {}; - const description = this.addon.network_description || {}; - const items: NetworkItem[] = Object.keys(network).map((key) => ({ - container: key, - host: network[key], - description: description[key], - })); - this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1)); + this._config = this.addon.network || {}; } - private async _configChanged(ev: Event): Promise { - const target = ev.target as NetworkItemInput; - this._config!.forEach((item) => { - if ( - item.container === target.container && - item.host !== parseInt(String(target.value), 10) - ) { - item.host = target.value ? parseInt(String(target.value), 10) : null; - } - }); + private async _configChanged(ev: CustomEvent): Promise { + this._configHasChanged = true; + this._config! = ev.detail.value; } private async _resetTapped(ev: CustomEvent): Promise { const button = ev.currentTarget as any; - button.progress = true; - const data: HassioAddonSetOptionParams = { network: null, }; try { await setHassioAddonOption(this.hass, this.addon.slug, data); + this._configHasChanged = false; const eventdata = { success: true, response: undefined, path: "option", }; + button.actionSuccess(); fireEvent(this, "hass-api-called", eventdata); if (this.addon?.state === "started") { await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); @@ -177,19 +184,21 @@ class HassioAddonNetwork extends LitElement { "error", extractApiErrorMessage(err) ); + button.actionError(); } + } - button.progress = false; + private _toggleOptional() { + this._showOptional = !this._showOptional; } private async _saveTapped(ev: CustomEvent): Promise { const button = ev.currentTarget as any; - button.progress = true; this._error = undefined; const networkconfiguration = {}; - this._config!.forEach((item) => { - networkconfiguration[item.container] = parseInt(String(item.host), 10); + Object.entries(this._config!).forEach(([key, value]) => { + networkconfiguration[key] = value ?? null; }); const data: HassioAddonSetOptionParams = { @@ -198,11 +207,13 @@ class HassioAddonNetwork extends LitElement { try { await setHassioAddonOption(this.hass, this.addon.slug, data); + this._configHasChanged = false; const eventdata = { success: true, response: undefined, path: "option", }; + button.actionSuccess(); fireEvent(this, "hass-api-called", eventdata); if (this.addon?.state === "started") { await suggestAddonRestart(this, this.hass, this.supervisor, this.addon); @@ -213,8 +224,8 @@ class HassioAddonNetwork extends LitElement { "error", extractApiErrorMessage(err) ); + button.actionError(); } - button.progress = false; } static get styles(): CSSResultGroup { @@ -232,6 +243,9 @@ class HassioAddonNetwork extends LitElement { display: flex; justify-content: space-between; } + .show-optional { + padding: 16px; + } `, ]; } diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index 0cead4ee9a..c27d735b0e 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -21,7 +21,8 @@ export type AddonState = "started" | "stopped" | null; export type AddonRepository = "core" | "local" | string; interface AddonTranslations { - [key: string]: Record>>; + network?: Record; + configuration?: Record; } export interface HassioAddonInfo { @@ -91,7 +92,7 @@ export interface HassioAddonDetails extends HassioAddonInfo { slug: string; startup: AddonStartup; stdin: boolean; - translations: AddonTranslations; + translations: Record; watchdog: null | boolean; webui: null | string; } diff --git a/src/translations/en.json b/src/translations/en.json index e4cde9f090..eebf0e0304 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4171,7 +4171,8 @@ "container": "Container", "disabled": "Disabled", "header": "Network", - "host": "Host" + "show_disabled": "Show disabled ports", + "introduction": "Change the ports on your host that are exposed by the add-on" } }, "dashboard": { From cfa048ea4e31218550280f0bcfcd142278d24427 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 7 Apr 2022 00:11:12 +0200 Subject: [PATCH 005/181] Only show "required" indicator if we have a selector label (#12241) --- src/components/ha-base-time-input.ts | 2 +- src/components/ha-labeled-slider.js | 2 +- src/components/ha-selector/ha-selector-color-temp.ts | 2 +- src/components/ha-selector/ha-selector-number.ts | 2 +- src/components/ha-yaml-editor.ts | 4 +++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index 7c3f59a629..9b35e60ca2 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -131,7 +131,7 @@ export class HaBaseTimeInput extends LitElement { protected render(): TemplateResult { return html` ${this.label - ? html`` + ? html`` : ""}
${this.enableDay diff --git a/src/components/ha-labeled-slider.js b/src/components/ha-labeled-slider.js index 00d1b1848f..0cb42de57d 100644 --- a/src/components/ha-labeled-slider.js +++ b/src/components/ha-labeled-slider.js @@ -53,7 +53,7 @@ class HaLabeledSlider extends PolymerElement { } _getTitle() { - return `${this.caption}${this.required ? "*" : ""}`; + return `${this.caption}${this.caption && this.required ? " *" : ""}`; } static get properties() { diff --git a/src/components/ha-selector/ha-selector-color-temp.ts b/src/components/ha-selector/ha-selector-color-temp.ts index c3cbb5ffe8..e94519df24 100644 --- a/src/components/ha-selector/ha-selector-color-temp.ts +++ b/src/components/ha-selector/ha-selector-color-temp.ts @@ -26,7 +26,7 @@ export class HaColorTempSelector extends LitElement { ${!isBox ? html`${this.label}${this.required ? "*" : ""}

` : ""} + ${this.label + ? html`

${this.label}${this.required ? " *" : ""}

` + : ""} Date: Thu, 7 Apr 2022 06:54:24 +0000 Subject: [PATCH 006/181] Lineup sidebar badges --- src/components/ha-sidebar.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 5e3d63b9b1..ff377c1c5c 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -1039,6 +1039,8 @@ class HaSidebar extends LitElement { .notification-badge, .configuration-badge { + left: calc(var(--app-drawer-width) - 42px); + position: absolute; min-width: 20px; box-sizing: border-box; border-radius: 50%; From 611cd2818e9f691ac74233c525c0b8769c649ebd Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 7 Apr 2022 20:58:21 +0200 Subject: [PATCH 007/181] Exclude hidden entities from area card --- src/panels/lovelace/cards/hui-area-card.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/cards/hui-area-card.ts b/src/panels/lovelace/cards/hui-area-card.ts index ad7e7f028d..41d5345728 100644 --- a/src/panels/lovelace/cards/hui-area-card.ts +++ b/src/panels/lovelace/cards/hui-area-card.ts @@ -113,6 +113,7 @@ export class HuiAreaCard .filter( (entry) => !entry.entity_category && + !entry.hidden_by && (entry.area_id ? entry.area_id === areaId : entry.device_id && devicesInArea.has(entry.device_id)) From 8f67aa38af502e57a7ec157157a59173889451d0 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 7 Apr 2022 21:39:04 +0200 Subject: [PATCH 008/181] Fix entity and device selector with `multiple: true` --- src/components/device/ha-devices-picker.ts | 11 ++++++++++- src/components/entity/ha-entities-picker.ts | 5 +++++ src/components/ha-selector/ha-selector-device.ts | 2 ++ src/components/ha-selector/ha-selector-entity.ts | 3 ++- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/components/device/ha-devices-picker.ts b/src/components/device/ha-devices-picker.ts index 688467c2a8..940ce962bb 100644 --- a/src/components/device/ha-devices-picker.ts +++ b/src/components/device/ha-devices-picker.ts @@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; -import "./ha-device-picker"; +import { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker"; @customElement("ha-devices-picker") class HaDevicesPicker extends LitElement { @@ -13,6 +13,8 @@ class HaDevicesPicker extends LitElement { @property() public helper?: string; + @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public required?: boolean; /** @@ -39,6 +41,8 @@ class HaDevicesPicker extends LitElement { @property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string; + @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; + protected render(): TemplateResult { if (!this.hass) { return html``; @@ -53,11 +57,13 @@ class HaDevicesPicker extends LitElement { allow-custom-entity .curValue=${entityId} .hass=${this.hass} + .deviceFilter=${this.deviceFilter} .includeDomains=${this.includeDomains} .excludeDomains=${this.excludeDomains} .includeDeviceClasses=${this.includeDeviceClasses} .value=${entityId} .label=${this.pickedDeviceLabel} + .disabled=${this.disabled} @value-changed=${this._deviceChanged} >
@@ -65,12 +71,15 @@ class HaDevicesPicker extends LitElement { )}
diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index 063c257c73..039eaa8261 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -14,6 +14,8 @@ class HaEntitiesPickerLight extends LitElement { @property({ type: Array }) public value?: string[]; + @property({ type: Boolean }) public disabled?: boolean; + @property({ type: Boolean }) public required?: boolean; @property() public helper?: string; @@ -96,6 +98,7 @@ class HaEntitiesPickerLight extends LitElement { .entityFilter=${this._entityFilter} .value=${entityId} .label=${this.pickedEntityLabel} + .disabled=${this.disabled} @value-changed=${this._entityChanged} >
@@ -103,6 +106,7 @@ class HaEntitiesPickerLight extends LitElement { )}
diff --git a/src/components/ha-selector/ha-selector-device.ts b/src/components/ha-selector/ha-selector-device.ts index b6358bf284..3c9c80872b 100644 --- a/src/components/ha-selector/ha-selector-device.ts +++ b/src/components/ha-selector/ha-selector-device.ts @@ -66,12 +66,14 @@ export class HaDeviceSelector extends LitElement { .hass=${this.hass} .value=${this.value} .helper=${this.helper} + .deviceFilter=${this._filterDevices} .includeDeviceClasses=${this.selector.device.entity?.device_class ? [this.selector.device.entity.device_class] : undefined} .includeDomains=${this.selector.device.entity?.domain ? [this.selector.device.entity.domain] : undefined} + .disabled=${this.disabled} .required=${this.required} > `; diff --git a/src/components/ha-selector/ha-selector-entity.ts b/src/components/ha-selector/ha-selector-entity.ts index bffc1acc76..8cb925f5d1 100644 --- a/src/components/ha-selector/ha-selector-entity.ts +++ b/src/components/ha-selector/ha-selector-entity.ts @@ -51,9 +51,10 @@ export class HaEntitySelector extends LitElement { .hass=${this.hass} .value=${this.value} .helper=${this.helper} - .entityFilter=${this._filterEntities} .includeEntities=${this.selector.entity.include_entities} .excludeEntities=${this.selector.entity.exclude_entities} + .entityFilter=${this._filterEntities} + .disabled=${this.disabled} .required=${this.required} > `; From 16a09029899176b0e3045867a427fb2c19fdab90 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Thu, 7 Apr 2022 22:41:37 +0200 Subject: [PATCH 009/181] Adjust import --- src/components/device/ha-devices-picker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/device/ha-devices-picker.ts b/src/components/device/ha-devices-picker.ts index 940ce962bb..1f1340ef04 100644 --- a/src/components/device/ha-devices-picker.ts +++ b/src/components/device/ha-devices-picker.ts @@ -3,7 +3,8 @@ import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; -import { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker"; +import "./ha-device-picker"; +import type { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker"; @customElement("ha-devices-picker") class HaDevicesPicker extends LitElement { From 8b823837909228a97a7deb46b404a48f9b3b45d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 11 Apr 2022 13:03:41 +0200 Subject: [PATCH 010/181] Guard for partial translations (#12296) --- hassio/src/addon-view/config/hassio-addon-config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts index 95b80ff359..75d70b2227 100644 --- a/hassio/src/addon-view/config/hassio-addon-config.ts +++ b/hassio/src/addon-view/config/hassio-addon-config.ts @@ -82,13 +82,13 @@ class HassioAddonConfig extends LitElement { public computeLabel = (entry: HaFormSchema): string => this.addon.translations[this.hass.language]?.configuration?.[entry.name] ?.name || - this.addon.translations.en?.configuration?.[entry.name].name || + this.addon.translations.en?.configuration?.[entry.name]?.name || entry.name; public computeHelper = (entry: HaFormSchema): string => this.addon.translations[this.hass.language]?.configuration?.[entry.name] ?.description || - this.addon.translations.en?.configuration?.[entry.name].description || + this.addon.translations.en?.configuration?.[entry.name]?.description || ""; private _convertSchema = memoizeOne( From d900e40d04ee748a329bd6ff875d78dfe92194a8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 11 Apr 2022 14:04:54 +0200 Subject: [PATCH 011/181] Fix add-on security rating range (#12300) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index eebf0e0304..30383cc444 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4203,7 +4203,7 @@ }, "rating": { "title": "Add-on Security Rating", - "description": "Home Assistant provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk)." + "description": "Home Assistant provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 8. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 8 is the highest score (considered the most secure and lowest risk)." }, "host_network": { "title": "Host Network", From 714f2447b782ab4a08f14540cd011fcf60f12e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 11 Apr 2022 16:01:30 +0200 Subject: [PATCH 012/181] Use more text selector types for add-on configuration (#12303) --- hassio/src/addon-view/config/hassio-addon-config.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts index 75d70b2227..e99aa61aa4 100644 --- a/hassio/src/addon-view/config/hassio-addon-config.ts +++ b/hassio/src/addon-view/config/hassio-addon-config.ts @@ -55,6 +55,8 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([ }), ]); +const MASKED_FIELDS = ["password", "secret", "token"]; + @customElement("hassio-addon-config") class HassioAddonConfig extends LitElement { @property({ attribute: false }) public addon!: HassioAddonDetails; @@ -113,7 +115,14 @@ class HassioAddonConfig extends LitElement { : { name: entry.name, required: entry.required, - selector: { text: { type: "text" } }, + selector: { + text: { + type: + entry.format || MASKED_FIELDS.includes(entry.name) + ? "password" + : "text", + }, + }, } : entry.type === "boolean" ? { From e751abd77542f8c8fda5d8f7f5d17611ee45e34a Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 11 Apr 2022 16:06:29 +0200 Subject: [PATCH 013/181] Prevent empty brackets if no manufacturer during config entry creation (#12288) --- src/dialogs/config-flow/step-flow-create-entry.ts | 15 ++++++++++++--- src/translations/en.json | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/dialogs/config-flow/step-flow-create-entry.ts b/src/dialogs/config-flow/step-flow-create-entry.ts index 6431bcc48b..32fb7f09cb 100644 --- a/src/dialogs/config-flow/step-flow-create-entry.ts +++ b/src/dialogs/config-flow/step-flow-create-entry.ts @@ -28,7 +28,7 @@ class StepFlowCreateEntry extends LitElement { const localize = this.hass.localize; return html` -

Success!

+

${localize("ui.panel.config.integrations.config_flow.success")}!

${this.flowConfig.renderCreateEntryDescription(this.hass, this.step)} ${this.step.result?.state === "not_loaded" @@ -41,7 +41,11 @@ class StepFlowCreateEntry extends LitElement { ${this.devices.length === 0 ? "" : html` -

We found the following devices:

+

+ ${localize( + "ui.panel.config.integrations.config_flow.found_following_devices" + )}: +

${this.devices.map( (device) => @@ -49,7 +53,12 @@ class StepFlowCreateEntry extends LitElement {
${computeDeviceName(device, this.hass)}
- ${device.model} (${device.manufacturer}) + ${!device.model && !device.manufacturer + ? html` ` + : html`${device.model} + ${device.manufacturer + ? html`(${device.manufacturer})` + : ""}`}
Date: Mon, 11 Apr 2022 16:56:03 +0200 Subject: [PATCH 014/181] Fix endless loading screen in zwave-js config (#12295) --- .../integration-panels/zwave_js/zwave_js-node-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index bc84b9a78a..e6d2e52857 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -179,7 +179,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {

- ${this._nodeMetadata.comments.length > 0 + ${this._nodeMetadata.comments?.length > 0 ? html`
${this._nodeMetadata.comments.map( From 3c549c6b31897f8ec7916c911e4bb99e581e8a62 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 11 Apr 2022 09:47:31 -0700 Subject: [PATCH 015/181] Update cloud text (#12305) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index f2d9fecf4d..9b9be70c02 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1450,7 +1450,7 @@ "internal_url_label": "Local Network", "external_url_label": "Internet", "external_use_ha_cloud": "Use Home Assistant Cloud", - "external_get_ha_cloud": "Access from anywhere using Home Assistant Cloud", + "external_get_ha_cloud": "Access from anywhere, add Google & Alexa easily", "ha_cloud_remote_not_enabled": "Your Home Assistant Cloud remote connection is currently not enabled.", "enable_remote": "[%key:ui::common::enable%]", "internal_url_automatic": "Automatic", From 125a601ae37368b32557ca455b4d6c324865a3a6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 11 Apr 2022 10:08:37 -0700 Subject: [PATCH 016/181] Select default mode if none set (#12306) --- src/data/automation.ts | 2 ++ src/panels/config/automation/manual-automation-editor.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index 87b0f9c828..1b8de5855e 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -8,6 +8,8 @@ import { BlueprintInput } from "./blueprint"; import { DeviceCondition, DeviceTrigger } from "./device_automation"; import { Action, MODES } from "./script"; +export const AUTOMATION_DEFAULT_MODE: ManualAutomationConfig["mode"] = "single"; + export interface AutomationEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { id?: string; diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index cefd4ea91c..b2c2f289ba 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -8,6 +8,7 @@ import "../../../components/ha-card"; import "../../../components/ha-textarea"; import "../../../components/ha-textfield"; import { + AUTOMATION_DEFAULT_MODE, Condition, ManualAutomationConfig, Trigger, @@ -99,7 +100,7 @@ export class HaManualAutomationEditor extends LitElement { .label=${this.hass.localize( "ui.panel.config.automation.editor.modes.label" )} - .value=${this.config.mode} + .value=${this.config.mode || AUTOMATION_DEFAULT_MODE} @selected=${this._modeChanged} fixedMenuPosition > From 323d98ecf7af193b6f399a36b06712b74ff30c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 12 Apr 2022 13:52:17 +0200 Subject: [PATCH 017/181] Decode view path URL (#12310) --- src/panels/lovelace/hui-root.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index d2c2a1a6b7..731b9a2fd4 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -551,7 +551,7 @@ class HUIRoot extends LitElement { let newSelectView; let force = false; - const viewPath = this.route!.path.split("/")[1]; + const viewPath = decodeURI(this.route!.path.split("/")[1]); if (changedProperties.has("route")) { const views = this.config.views; From a124ec0717cb2329acf4b88aa11614755043400a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 13 Apr 2022 14:20:00 +0200 Subject: [PATCH 018/181] Always render title field (#12319) --- gallery/src/pages/more-info/update.ts | 6 ++++++ src/dialogs/more-info/controls/more-info-update.ts | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gallery/src/pages/more-info/update.ts b/gallery/src/pages/more-info/update.ts index 65d8e0bf7f..872a7b2e41 100644 --- a/gallery/src/pages/more-info/update.ts +++ b/gallery/src/pages/more-info/update.ts @@ -133,6 +133,12 @@ const ENTITIES = [ friendly_name: "Update with auto update", auto_update: true, }), + getEntity("update", "update20", "on", { + ...base_attributes, + in_progress: true, + title: undefined, + friendly_name: "Installing without title", + }), ]; @customElement("demo-more-info-update") diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index 57c1ba574b..e8a2ed89d8 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -56,13 +56,10 @@ class MoreInfoUpdate extends LitElement { >` : html`` : ""} - ${this.stateObj.attributes.title - ? html`

${this.stateObj.attributes.title}

` - : ""} +

${this.stateObj.attributes.title}

${this._error ? html`${this._error}` : ""} -
${this.hass.localize( From 30b130ca7401221a14e3c130bf635c85a785e8ba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 14 Apr 2022 12:42:54 -1000 Subject: [PATCH 019/181] Use new mdi icons for smoke and co detection (#12323) --- src/common/entity/binary_sensor_icon.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/common/entity/binary_sensor_icon.ts b/src/common/entity/binary_sensor_icon.ts index a80d621812..fa2d81bda3 100644 --- a/src/common/entity/binary_sensor_icon.ts +++ b/src/common/entity/binary_sensor_icon.ts @@ -29,8 +29,11 @@ import { mdiPowerPlug, mdiPowerPlugOff, mdiRadioboxBlank, - mdiSmoke, mdiSnowflake, + mdiSmokeDetector, + mdiSmokeDetectorAlert, + mdiSmokeDetectorVariant, + mdiSmokeDetectorVariantAlert, mdiSquare, mdiSquareOutline, mdiStop, @@ -52,6 +55,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => { return is_off ? mdiBattery : mdiBatteryOutline; case "battery_charging": return is_off ? mdiBattery : mdiBatteryCharging; + case "carbon_monoxide": + return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert; case "cold": return is_off ? mdiThermometer : mdiSnowflake; case "connectivity": @@ -68,7 +73,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => { case "tamper": return is_off ? mdiCheckCircle : mdiAlertCircle; case "smoke": - return is_off ? mdiCheckCircle : mdiSmoke; + return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert; case "heat": return is_off ? mdiThermometer : mdiFire; case "light": From 7723d47ac102e6da6976b74771a29c368c8ef4d4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 14 Apr 2022 15:52:49 -0700 Subject: [PATCH 020/181] Split only on first comma in media browser (#12331) --- src/panels/media-browser/ha-panel-media-browser.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 30cc866b17..cfda5876f9 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -170,11 +170,12 @@ class PanelMediaBrowser extends LitElement { media_content_id: undefined, }, ...navigateIdsEncoded.map((navigateId) => { - const [media_content_type, media_content_id] = - decodeURIComponent(navigateId).split(","); + const decoded = decodeURIComponent(navigateId); + // Don't use split because media_content_id could contain commas + const delimiter = decoded.indexOf(","); return { - media_content_type, - media_content_id, + media_content_type: decoded.substring(0, delimiter), + media_content_id: decoded.substring(delimiter + 1), }; }), ]; From 85d1f49763a715583c964c56707d553ea14d6b5d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 15 Apr 2022 06:55:22 -0700 Subject: [PATCH 021/181] Allow tapping on the name on a picture entity card (#12332) --- src/panels/lovelace/cards/hui-picture-entity-card.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index 087e5e5069..aaab63cb0f 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -200,6 +200,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { font-size: 16px; line-height: 16px; color: var(--ha-picture-card-text-color, white); + pointer-events: none; } .both { From bad5a389b5013351baf65d8e0136edde1648ee82 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Fri, 15 Apr 2022 21:47:46 +0300 Subject: [PATCH 022/181] RTL calendar fix - arrows fix and views fix (#12314) * RTL calendar fix - arrows fix and views fix * Removed path attributes --- src/components/ha-button-toggle-group.ts | 13 +++++++++++++ src/panels/calendar/ha-full-calendar.ts | 15 +++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts index 330a6ba134..8534cfba0b 100644 --- a/src/components/ha-button-toggle-group.ts +++ b/src/components/ha-button-toggle-group.ts @@ -117,6 +117,19 @@ export class HaButtonToggleGroup extends LitElement { --mdc-shape-small: 4px; border-right-width: 1px; } + + :host([dir="rtl"]) ha-icon-button:first-child, + :host([dir="rtl"]) mwc-button:first-child { + border-radius: 0 4px 4px 0; + border-right-width: 1px; + --mdc-shape-small: 0 4px 4px 0; + --mdc-button-outline-width: 1px; + } + :host([dir="rtl"]) ha-icon-button:last-child, + :host([dir="rtl"]) mwc-button:last-child { + --mdc-shape-small: 4px 0 0 4px; + border-radius: 4px 0 0 4px; + } `; } } diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index 3516a14f06..7ee2042304 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -34,7 +34,10 @@ import { useAmPm } from "../../common/datetime/use_am_pm"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-button-toggle-group"; import "../../components/ha-icon-button"; +import "../../components/ha-icon-button-prev"; +import "../../components/ha-icon-button-next"; import { haStyle } from "../../resources/styles"; +import { computeRTLDirection } from "../../common/util/compute_rtl"; import type { CalendarEvent, CalendarViewChanged, @@ -124,26 +127,25 @@ export class HAFullCalendar extends LitElement { "ui.components.calendar.today" )} - - - + - +

${this.calendar.view.title}

` : html` @@ -179,6 +181,7 @@ export class HAFullCalendar extends LitElement { .buttons=${viewToggleButtons} .active=${this._activeView} @value-changed=${this._handleView} + .dir=${computeRTLDirection(this.hass)} >
`} From 76e1721c5852687317ff401d226944dda178904d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Apr 2022 08:54:57 -1000 Subject: [PATCH 023/181] Quickly search for entities from the Overview Dashboard (#12324) --- src/panels/lovelace/hui-root.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 731b9a2fd4..e3525383ed 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -7,6 +7,7 @@ import { mdiFileMultiple, mdiFormatListBulletedTriangle, mdiHelp, + mdiMagnify, mdiHelpCircle, mdiMicrophone, mdiPencil, @@ -72,6 +73,7 @@ import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog"; import type { Lovelace } from "./types"; import "./views/hui-view"; import type { HUIView } from "./views/hui-view"; +import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar"; class HUIRoot extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -264,6 +266,10 @@ class HUIRoot extends LitElement { ` : html`
${this.config.title}
`} + ${!this.narrow && this._conversation(this.hass.config.components) ? html` @@ -673,6 +679,13 @@ class HUIRoot extends LitElement { }); } + private _showQuickBar(): void { + showQuickBar(this, { + commandMode: false, + hint: this.hass.localize("ui.dialogs.quick-bar.key_e_hint"), + }); + } + private _handleRawEditor(ev: CustomEvent): void { if (!shouldHandleRequestSelectedEvent(ev)) { return; From 511368da13fb07d12d1fc7d217dbd9a7ff68736c Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 15 Apr 2022 21:03:14 +0200 Subject: [PATCH 024/181] Allow selecting multiple entities for state trigger (#12334) Co-authored-by: Zack Barett --- src/data/automation.ts | 2 +- .../types/ha-automation-trigger-state.ts | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index 1b8de5855e..dda0711a2f 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -69,7 +69,7 @@ export interface BaseTrigger { export interface StateTrigger extends BaseTrigger { platform: "state"; - entity_id: string; + entity_id: string | string[]; attribute?: string; from?: string | number; to?: string | string[] | number; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index 44d3183e0d..60997afcde 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -1,6 +1,7 @@ import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { + array, assert, assign, literal, @@ -10,6 +11,7 @@ import { union, } from "superstruct"; import memoizeOne from "memoize-one"; +import { ensureArray } from "../../../../../common/ensure-array"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; import { StateTrigger } from "../../../../../data/automation"; @@ -24,7 +26,7 @@ const stateTriggerStruct = assign( baseTriggerStruct, object({ platform: literal("state"), - entity_id: optional(string()), + entity_id: optional(union([string(), array(string())])), attribute: optional(string()), from: optional(string()), to: optional(string()), @@ -39,11 +41,15 @@ export class HaStateTrigger extends LitElement implements TriggerElement { @property() public trigger!: StateTrigger; public static get defaultConfig() { - return { entity_id: "" }; + return { entity_id: [] }; } private _schema = memoizeOne((entityId) => [ - { name: "entity_id", required: true, selector: { entity: {} } }, + { + name: "entity_id", + required: true, + selector: { entity: { multiple: true } }, + }, { name: "attribute", selector: { attribute: { entity_id: entityId } }, @@ -85,7 +91,11 @@ export class HaStateTrigger extends LitElement implements TriggerElement { protected render() { const trgFor = createDurationData(this.trigger.for); - const data = { ...this.trigger, ...{ for: trgFor } }; + const data = { + ...this.trigger, + entity_id: ensureArray(this.trigger.entity_id), + for: trgFor, + }; const schema = this._schema(this.trigger.entity_id); return html` From 6fd4cda53488c570504f78a578ef3e7bd6baa6b8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Mon, 18 Apr 2022 18:17:44 +0200 Subject: [PATCH 025/181] Add Template selector (#12348) --- gallery/src/pages/components/ha-selector.ts | 1 + .../ha-selector/ha-selector-template.ts | 56 +++++++++++++++++++ src/components/ha-selector/ha-selector.ts | 1 + src/data/selector.ts | 6 ++ 4 files changed, 64 insertions(+) create mode 100644 src/components/ha-selector/ha-selector-template.ts diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 972c7d7f26..6ce04e0995 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -170,6 +170,7 @@ const SCHEMAS: { select: { options: ["Option 1", "Option 2"], mode: "list" }, }, }, + template: { name: "Template", selector: { template: {} } }, select: { name: "Select", selector: { diff --git a/src/components/ha-selector/ha-selector-template.ts b/src/components/ha-selector/ha-selector-template.ts new file mode 100644 index 0000000000..1140ec83a7 --- /dev/null +++ b/src/components/ha-selector/ha-selector-template.ts @@ -0,0 +1,56 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../common/dom/fire_event"; +import { HomeAssistant } from "../../types"; +import "../ha-code-editor"; +import "../ha-input-helper-text"; + +@customElement("ha-selector-template") +export class HaTemplateSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public value?: string; + + @property() public label?: string; + + @property() public helper?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + protected render() { + return html` + ${this.label + ? html`

${this.label}${this.required ? " *" : ""}

` + : ""} + + ${this.helper + ? html`${this.helper}` + : ""} + `; + } + + private _handleChange(ev) { + const value = ev.target.value; + if (this.value === value) { + return; + } + fireEvent(this, "value-changed", { value }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-template": HaTemplateSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 09a234e55c..e2b3fdbf97 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -18,6 +18,7 @@ import "./ha-selector-number"; import "./ha-selector-object"; import "./ha-selector-select"; import "./ha-selector-target"; +import "./ha-selector-template"; import "./ha-selector-text"; import "./ha-selector-time"; import "./ha-selector-icon"; diff --git a/src/data/selector.ts b/src/data/selector.ts index 10cda864b7..d0f3b0e61c 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -19,6 +19,7 @@ export type Selector = | SelectSelector | StringSelector | TargetSelector + | TemplateSelector | ThemeSelector | TimeSelector; @@ -213,6 +214,11 @@ export interface TargetSelector { }; } +export interface TemplateSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + template: {}; +} + export interface ThemeSelector { // eslint-disable-next-line @typescript-eslint/ban-types theme: {}; From 0685fdf7c6ec384611a79beef12c3175d10b271e Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:19:01 -0400 Subject: [PATCH 026/181] Add basic frontend support for siren (#12345) --- src/panels/lovelace/create-element/create-row-element.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index 6794fdfb58..b805707259 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -76,8 +76,9 @@ const DOMAIN_TO_ELEMENT_TYPE = { script: "script", select: "select", sensor: "sensor", - timer: "timer", + siren: "toggle", switch: "toggle", + timer: "timer", vacuum: "toggle", // Temporary. Once climate is rewritten, // water heater should get its own row. From f648317206639bc0044fa0cf63550fbf388f15ec Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 19 Apr 2022 14:28:07 +0200 Subject: [PATCH 027/181] Fix strict error handling in developer tools templates (#12352) --- src/data/ws-templates.ts | 1 + src/panels/developer-tools/template/developer-tools-template.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/data/ws-templates.ts b/src/data/ws-templates.ts index a1c294bab7..57bfde655b 100644 --- a/src/data/ws-templates.ts +++ b/src/data/ws-templates.ts @@ -20,6 +20,7 @@ export const subscribeRenderTemplate = ( entity_ids?: string | string[]; variables?: Record; timeout?: number; + strict?: boolean; } ): Promise => conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), { diff --git a/src/panels/developer-tools/template/developer-tools-template.ts b/src/panels/developer-tools/template/developer-tools-template.ts index 97fa25cc84..a0c91ec175 100644 --- a/src/panels/developer-tools/template/developer-tools-template.ts +++ b/src/panels/developer-tools/template/developer-tools-template.ts @@ -329,6 +329,7 @@ class HaPanelDevTemplate extends LitElement { { template: this._template, timeout: 3, + strict: true, } ); await this._unsubRenderTemplate; From aa129aa1233d49e606d202ff7df544f696000645 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 19 Apr 2022 10:50:18 -0700 Subject: [PATCH 028/181] Bump HAWS to 7.0.3 (#12358) --- package.json | 2 +- src/panels/config/entities/ha-config-entities.ts | 2 +- yarn.lock | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 304c43e2aa..25cbfbe761 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "fuse.js": "^6.0.0", "google-timezones-json": "^1.0.2", "hls.js": "^1.1.5", - "home-assistant-js-websocket": "^7.0.1", + "home-assistant-js-websocket": "^7.0.3", "idb-keyval": "^5.1.3", "intl-messageformat": "^9.9.1", "js-yaml": "^4.1.0", diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 9cfb164d8f..6fd3314830 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -384,7 +384,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { for (const entry of filteredEntities) { const entity = this.hass.states[entry.entity_id]; const unavailable = entity?.state === UNAVAILABLE; - const restored = entity?.attributes.restored; + const restored = entity?.attributes.restored === true; const areaId = entry.area_id ?? deviceLookup[entry.device_id!]?.area_id; const area = areaId ? areaLookup[areaId] : undefined; diff --git a/yarn.lock b/yarn.lock index a055a2b6f8..af02cfdc2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9128,7 +9128,7 @@ fsevents@^1.2.7: gulp-rename: ^2.0.0 gulp-zopfli-green: ^3.0.1 hls.js: ^1.1.5 - home-assistant-js-websocket: ^7.0.1 + home-assistant-js-websocket: ^7.0.3 html-minifier: ^4.0.0 husky: ^1.3.1 idb-keyval: ^5.1.3 @@ -9198,10 +9198,10 @@ fsevents@^1.2.7: languageName: unknown linkType: soft -"home-assistant-js-websocket@npm:^7.0.1": - version: 7.0.1 - resolution: "home-assistant-js-websocket@npm:7.0.1" - checksum: c9a87f11222571226adff43f022008d35df1f78799efae43e9a36f768eef10d21aed99886c905086c42c24d85d47c78e328c1be9593c117b397a18ee86b2fe64 +"home-assistant-js-websocket@npm:^7.0.3": + version: 7.0.3 + resolution: "home-assistant-js-websocket@npm:7.0.3" + checksum: f2647fab4599069a6422b53661de0c8c5177408e297e89f35b442c5d8e65d31d7f607e6e6a813f4ec8af9d581b45a20b88b44bfedbe25b6c3f01fcc38d0e396e languageName: node linkType: hard From d97763a3e85020b743becfcf2e754c0cf09f0e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 20 Apr 2022 15:17:56 +0200 Subject: [PATCH 029/181] Add clear skipped to update more-info dialog (#12361) --- .../more-info/controls/more-info-update.ts | 20 +++++++++++++++++-- src/translations/en.json | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index e8a2ed89d8..ee855ddb13 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -21,6 +21,7 @@ import { UPDATE_SUPPORT_SPECIFIC_VERSION, } from "../../../data/update"; import type { HomeAssistant } from "../../../types"; +import { BINARY_STATE_OFF } from "../../../common/const"; @customElement("more-info-update") class MoreInfoUpdate extends LitElement { @@ -129,11 +130,20 @@ class MoreInfoUpdate extends LitElement {
${this.stateObj.attributes.auto_update ? "" + : this.stateObj.state === BINARY_STATE_OFF && + this.stateObj.attributes.skipped_version + ? html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.update.clear_skipped" + )} + + ` : html` ${this.hass.localize( @@ -145,7 +155,7 @@ class MoreInfoUpdate extends LitElement { ? html` @@ -207,6 +217,12 @@ class MoreInfoUpdate extends LitElement { }); } + private _handleClearSkipped(): void { + this.hass.callService("update", "clear_skipped", { + entity_id: this.stateObj!.entity_id, + }); + } + static get styles(): CSSResultGroup { return css` hr { diff --git a/src/translations/en.json b/src/translations/en.json index 9b9be70c02..4e545b1f28 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -735,6 +735,7 @@ "latest_version": "Latest version", "release_announcement": "Read release announcement", "skip": "Skip", + "clear_skipped": "Clear skipped", "install": "Install", "create_backup": "Create backup before updating" }, From 58d94da8b3f63f230f9126e256fdb16b16d134df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Valli=C3=A8res?= Date: Wed, 20 Apr 2022 09:36:18 -0400 Subject: [PATCH 030/181] Adding blueprint input description markdown/multi-line support (#12291) --- .../config/automation/blueprint-automation-editor.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index 37b08264de..0c5da1c822 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -188,7 +188,12 @@ export class HaBlueprintAutomationEditor extends LitElement { ([key, value]) => html` ${value?.name || key} - ${value?.description} + ${value?.selector ? html` Date: Wed, 20 Apr 2022 06:52:12 -0700 Subject: [PATCH 031/181] Github no longer supports the (insecure) git protocol (#12359) --- script/core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/core b/script/core index 79ba38a348..8ac6da6284 100755 --- a/script/core +++ b/script/core @@ -15,7 +15,7 @@ if [ -z $(which hass) ]; then echo "Installing Home Asstant core from dev." python3 -m pip install --upgrade \ colorlog \ - git+git://github.com/home-assistant/home-assistant.git@dev + git+https://github.com/home-assistant/home-assistant.git@dev fi if [ ! -d "${WD}/config" ]; then From 22175a7271a7e9eade875721213a84cac6a93667 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 20 Apr 2022 18:27:16 +0200 Subject: [PATCH 032/181] Add if/else automation/script action (#12301) Co-authored-by: Zack Barett --- src/data/script.ts | 12 ++ .../action/ha-automation-action-row.ts | 2 + .../action/types/ha-automation-action-if.ts | 109 ++++++++++++++++++ src/translations/en.json | 6 + 4 files changed, 129 insertions(+) create mode 100644 src/panels/config/automation/action/types/ha-automation-action-if.ts diff --git a/src/data/script.ts b/src/data/script.ts index 41db0160c1..4262fcd1e4 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -194,6 +194,13 @@ export interface ChooseAction { default?: Action | Action[]; } +export interface IfAction { + alias?: string; + if: string | Condition[]; + then: Action | Action[]; + else?: Action | Action[]; +} + export interface VariablesAction { alias?: string; variables: Record; @@ -215,6 +222,7 @@ export type Action = | WaitForTriggerAction | RepeatAction | ChooseAction + | IfAction | VariablesAction | PlayMediaAction | UnknownAction; @@ -228,6 +236,7 @@ export interface ActionTypes { activate_scene: SceneAction; repeat: RepeatAction; choose: ChooseAction; + if: IfAction; wait_for_trigger: WaitForTriggerAction; variables: VariablesAction; service: ServiceAction; @@ -299,6 +308,9 @@ export const getActionType = (action: Action): ActionType => { if ("choose" in action) { return "choose"; } + if ("if" in action) { + return "if"; + } if ("wait_for_trigger" in action) { return "wait_for_trigger"; } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index fce39cee2c..c049ca493c 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -32,6 +32,7 @@ import "./types/ha-automation-action-condition"; import "./types/ha-automation-action-delay"; import "./types/ha-automation-action-device_id"; import "./types/ha-automation-action-event"; +import "./types/ha-automation-action-if"; import "./types/ha-automation-action-play_media"; import "./types/ha-automation-action-repeat"; import "./types/ha-automation-action-service"; @@ -49,6 +50,7 @@ const OPTIONS = [ "wait_for_trigger", "repeat", "choose", + "if", "device_id", ]; diff --git a/src/panels/config/automation/action/types/ha-automation-action-if.ts b/src/panels/config/automation/action/types/ha-automation-action-if.ts new file mode 100644 index 0000000000..6742adf13c --- /dev/null +++ b/src/panels/config/automation/action/types/ha-automation-action-if.ts @@ -0,0 +1,109 @@ +import { CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { Action, IfAction } from "../../../../../data/script"; +import { HaDeviceCondition } from "../../condition/types/ha-automation-condition-device"; +import { HaDeviceAction } from "./ha-automation-action-device_id"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant } from "../../../../../types"; +import type { Condition } from "../../../../lovelace/common/validate-condition"; +import "../ha-automation-action"; +import "../../../../../components/ha-textfield"; +import type { ActionElement } from "../ha-automation-action-row"; + +@customElement("ha-automation-action-if") +export class HaIfAction extends LitElement implements ActionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public action!: IfAction; + + public static get defaultConfig() { + return { + if: [{ ...HaDeviceCondition.defaultConfig, condition: "device" }], + then: [HaDeviceAction.defaultConfig], + }; + } + + protected render() { + const action = this.action; + + return html` +

+ ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.if.if" + )}*: +

+ + +

+ ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.if.then" + )}*: +

+ + +

+ ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.if.else" + )}: +

+ + `; + } + + private _ifChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value as Condition[]; + fireEvent(this, "value-changed", { + value: { + ...this.action, + if: value, + }, + }); + } + + private _thenChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value as Action[]; + fireEvent(this, "value-changed", { + value: { + ...this.action, + then: value, + }, + }); + } + + private _elseChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value as Action[]; + + fireEvent(this, "value-changed", { + value: { + ...this.action, + else: value, + }, + }); + } + + static get styles(): CSSResultGroup { + return haStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-action-if": HaIfAction; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 4e545b1f28..4aa28eb0a4 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2014,6 +2014,12 @@ "remove_option": "Remove option", "conditions": "Conditions", "sequence": "Actions" + }, + "if": { + "label": "If-then", + "if": "If", + "then": "Then", + "else": "Else" } } } From aa562c21a8f0dbd5b36bfa40df2119ac37ac5ec8 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 20 Apr 2022 23:50:09 +0200 Subject: [PATCH 033/181] Add stop script/automation action (#12299) --- src/data/script.ts | 11 +++ .../action/ha-automation-action-row.ts | 2 + .../action/types/ha-automation-action-stop.ts | 71 +++++++++++++++++++ src/translations/en.json | 5 ++ 4 files changed, 89 insertions(+) create mode 100644 src/panels/config/automation/action/types/ha-automation-action-stop.ts diff --git a/src/data/script.ts b/src/data/script.ts index 4262fcd1e4..3966651a4a 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -206,6 +206,12 @@ export interface VariablesAction { variables: Record; } +export interface StopAction { + alias?: string; + stop: string; + error?: boolean; +} + interface UnknownAction { alias?: string; [key: string]: unknown; @@ -225,6 +231,7 @@ export type Action = | IfAction | VariablesAction | PlayMediaAction + | StopAction | UnknownAction; export interface ActionTypes { @@ -241,6 +248,7 @@ export interface ActionTypes { variables: VariablesAction; service: ServiceAction; play_media: PlayMediaAction; + stop: StopAction; unknown: UnknownAction; } @@ -317,6 +325,9 @@ export const getActionType = (action: Action): ActionType => { if ("variables" in action) { return "variables"; } + if ("stop" in action) { + return "stop"; + } if ("service" in action) { if ("metadata" in action) { if (is(action, activateSceneActionStruct)) { diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index c049ca493c..40e9b4764f 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -36,6 +36,7 @@ import "./types/ha-automation-action-if"; import "./types/ha-automation-action-play_media"; import "./types/ha-automation-action-repeat"; import "./types/ha-automation-action-service"; +import "./types/ha-automation-action-stop"; import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_template"; @@ -52,6 +53,7 @@ const OPTIONS = [ "choose", "if", "device_id", + "stop", ]; const getType = (action: Action | undefined) => { diff --git a/src/panels/config/automation/action/types/ha-automation-action-stop.ts b/src/panels/config/automation/action/types/ha-automation-action-stop.ts new file mode 100644 index 0000000000..711784f767 --- /dev/null +++ b/src/panels/config/automation/action/types/ha-automation-action-stop.ts @@ -0,0 +1,71 @@ +import { css, CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-textfield"; +import { StopAction } from "../../../../../data/script"; +import { HomeAssistant } from "../../../../../types"; +import { ActionElement } from "../ha-automation-action-row"; + +@customElement("ha-automation-action-stop") +export class HaStopAction extends LitElement implements ActionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public action!: StopAction; + + public static get defaultConfig() { + return { stop: "" }; + } + + protected render() { + const { error, stop } = this.action; + + return html` + + + + + `; + } + + private _stopChanged(ev: CustomEvent) { + ev.stopPropagation(); + fireEvent(this, "value-changed", { + value: { ...this.action, stop: (ev.target as any).value }, + }); + } + + private _errorChanged(ev: CustomEvent) { + ev.stopPropagation(); + fireEvent(this, "value-changed", { + value: { ...this.action, error: (ev.target as any).checked }, + }); + } + + static get styles(): CSSResultGroup { + return css` + ha-textfield { + display: block; + margin-bottom: 24px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-action-stop": HaStopAction; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 4aa28eb0a4..f165746cfb 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2020,6 +2020,11 @@ "if": "If", "then": "Then", "else": "Else" + }, + "stop": { + "label": "Stop", + "stop": "Reason for stopping", + "error": "Stop because of an unexpected error" } } } From cabe10ffdb8227d058616c5508165b7c134d5228 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 20 Apr 2022 16:57:51 -0500 Subject: [PATCH 034/181] Getting started on Configuration Changes (#12309) Co-authored-by: Bram Kragten --- src/common/dom/is-navigation-click.ts | 2 +- src/components/ha-analytics.ts | 108 +++++------ src/components/ha-clickable-list-item.ts | 69 +++++++ src/components/ha-navigation-list.ts | 92 +++++++++ src/components/map/ha-locations-editor.ts | 4 +- .../config/areas/ha-config-area-page.ts | 2 +- .../config/areas/ha-config-areas-dashboard.ts | 2 +- src/panels/config/backup/ha-config-backup.ts | 7 +- src/panels/config/core/ha-config-analytics.ts | 2 +- src/panels/config/core/ha-config-core.js | 47 ++--- src/panels/config/core/ha-config-network.ts | 13 +- .../core/ha-config-section-analytics.ts | 43 +++++ .../config/core/ha-config-section-core.js | 70 ------- .../config/core/ha-config-section-network.ts | 49 +++++ .../config/core/ha-config-section-storage.ts | 40 ++++ .../core/ha-config-system-navigation.ts | 115 ++++++++++++ src/panels/config/core/ha-config-url-form.ts | 12 +- .../config/dashboard/ha-config-dashboard.ts | 41 ++-- .../config/dashboard/ha-config-navigation.ts | 175 ++++++------------ src/panels/config/ha-panel-config.ts | 134 ++++++++++---- .../config/helpers/ha-config-helpers.ts | 20 +- src/panels/config/info/ha-config-info.ts | 4 +- src/panels/config/logs/ha-config-logs.ts | 15 +- .../ha-config-server-control.ts | 10 +- src/panels/config/users/ha-config-users.ts | 2 +- src/panels/config/zone/ha-config-zone.ts | 2 +- src/translations/en.json | 49 +++-- 27 files changed, 742 insertions(+), 387 deletions(-) create mode 100644 src/components/ha-clickable-list-item.ts create mode 100644 src/components/ha-navigation-list.ts create mode 100644 src/panels/config/core/ha-config-section-analytics.ts delete mode 100644 src/panels/config/core/ha-config-section-core.js create mode 100644 src/panels/config/core/ha-config-section-network.ts create mode 100644 src/panels/config/core/ha-config-section-storage.ts create mode 100644 src/panels/config/core/ha-config-system-navigation.ts diff --git a/src/common/dom/is-navigation-click.ts b/src/common/dom/is-navigation-click.ts index 05edb52e14..9cc92a5832 100644 --- a/src/common/dom/is-navigation-click.ts +++ b/src/common/dom/is-navigation-click.ts @@ -12,7 +12,7 @@ export const isNavigationClick = (e: MouseEvent) => { const anchor = e .composedPath() - .filter((n) => (n as HTMLElement).tagName === "A")[0] as + .find((n) => (n as HTMLElement).tagName === "A") as | HTMLAnchorElement | undefined; if ( diff --git a/src/components/ha-analytics.ts b/src/components/ha-analytics.ts index d81cbf379c..33756e4c28 100644 --- a/src/components/ha-analytics.ts +++ b/src/components/ha-analytics.ts @@ -2,12 +2,12 @@ import "@polymer/paper-tooltip/paper-tooltip"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; -import { Analytics, AnalyticsPreferences } from "../data/analytics"; +import type { Analytics, AnalyticsPreferences } from "../data/analytics"; import { haStyle } from "../resources/styles"; -import { HomeAssistant } from "../types"; -import "./ha-checkbox"; -import type { HaCheckbox } from "./ha-checkbox"; +import type { HomeAssistant } from "../types"; import "./ha-settings-row"; +import "./ha-switch"; +import type { HaSwitch } from "./ha-switch"; const ADDITIONAL_PREFERENCES = [ { @@ -40,62 +40,62 @@ export class HaAnalytics extends LitElement { return html` - - - - Basic analytics This includes information about your system. + + ${ADDITIONAL_PREFERENCES.map( (preference) => - html` - - - - ${!baseEnabled - ? html` - You need to enable basic analytics for this option to be - available - ` - : ""} - - - ${preference.title} - - - ${preference.description} - - ` + html` + + + ${preference.title} + + + ${preference.description} + + + + + ${!baseEnabled + ? html` + + You need to enable basic analytics for this option to be + available + + ` + : ""} + + + ` )} - - - - Diagnostics Share crash reports when unexpected errors occur. + + `; } @@ -120,23 +120,23 @@ export class HaAnalytics extends LitElement { }); } - private _handleRowCheckboxClick(ev: Event) { - const checkbox = ev.currentTarget as HaCheckbox; - const preference = (checkbox as any).preference; + private _handleRowClick(ev: Event) { + const target = ev.currentTarget as HaSwitch; + const preference = (target as any).preference; const preferences = this.analytics ? { ...this.analytics.preferences } : {}; - if (preferences[preference] === checkbox.checked) { + if (preferences[preference] === target.checked) { return; } - preferences[preference] = checkbox.checked; + preferences[preference] = target.checked; if ( ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) && - checkbox.checked + target.checked ) { preferences.base = true; - } else if (preference === "base" && !checkbox.checked) { + } else if (preference === "base" && !target.checked) { preferences.usage = false; preferences.statistics = false; } diff --git a/src/components/ha-clickable-list-item.ts b/src/components/ha-clickable-list-item.ts new file mode 100644 index 0000000000..895c67954a --- /dev/null +++ b/src/components/ha-clickable-list-item.ts @@ -0,0 +1,69 @@ +import { ListItemBase } from "@material/mwc-list/mwc-list-item-base"; +import { styles } from "@material/mwc-list/mwc-list-item.css"; +import { css, CSSResult, html } from "lit"; +import { customElement, property, query } from "lit/decorators"; + +@customElement("ha-clickable-list-item") +export class HaClickableListItem extends ListItemBase { + @property() public href?: string; + + @property({ type: Boolean }) public disableHref = false; + + // property used only in css + @property({ type: Boolean, reflect: true }) public rtl = false; + + @query("a") private _anchor!: HTMLAnchorElement; + + public render() { + const r = super.render(); + const href = this.href || ""; + + return html`${this.disableHref + ? html`${r}` + : html`${r}`}`; + } + + firstUpdated() { + super.firstUpdated(); + this.addEventListener("keydown", (ev) => { + if (ev.key === "Enter" || ev.key === " ") { + this._anchor.click(); + } + }); + } + + static get styles(): CSSResult[] { + return [ + styles, + css` + :host { + padding-left: 0px; + padding-right: 0px; + } + :host([rtl]) span { + margin-left: var(--mdc-list-item-graphic-margin, 20px) !important; + margin-right: 0px !important; + } + :host([graphic="avatar"]:not([twoLine])), + :host([graphic="icon"]:not([twoLine])) { + height: 48px; + } + a { + width: 100%; + height: 100%; + display: flex; + align-items: center; + padding-left: var(--mdc-list-side-padding, 20px); + padding-right: var(--mdc-list-side-padding, 20px); + font-weight: 500; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-clickable-list-item": HaClickableListItem; + } +} diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts new file mode 100644 index 0000000000..734e4b8cf8 --- /dev/null +++ b/src/components/ha-navigation-list.ts @@ -0,0 +1,92 @@ +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import type { PageNavigation } from "../layouts/hass-tabs-subpage"; +import type { HomeAssistant } from "../types"; +import "./ha-icon-next"; +import "./ha-svg-icon"; +import "./ha-clickable-list-item"; + +@customElement("ha-navigation-list") +class HaNavigationList extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + @property({ attribute: false }) public pages!: PageNavigation[]; + + @property({ type: Boolean }) public hasSecondary = false; + + public render(): TemplateResult { + return html` + + ${this.pages.map( + (page) => html` + +
+ +
+ ${page.name} + ${this.hasSecondary + ? html`${page.description}` + : ""} + ${!this.narrow + ? html`` + : ""} +
+ ` + )} +
+ `; + } + + private _entryClicked(ev) { + ev.currentTarget.blur(); + } + + static styles: CSSResultGroup = css` + a { + text-decoration: none; + color: var(--primary-text-color); + position: relative; + display: block; + outline: 0; + } + ha-svg-icon, + ha-icon-next { + color: var(--secondary-text-color); + height: 24px; + width: 24px; + } + ha-svg-icon { + padding: 8px; + } + .icon-background { + border-radius: 50%; + } + .icon-background ha-svg-icon { + color: #fff; + } + mwc-list-item { + cursor: pointer; + font-size: var(--navigation-list-item-title-font-size); + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-navigation-list": HaNavigationList; + } +} diff --git a/src/components/map/ha-locations-editor.ts b/src/components/map/ha-locations-editor.ts index 2dcc446099..43f68553a7 100644 --- a/src/components/map/ha-locations-editor.ts +++ b/src/components/map/ha-locations-editor.ts @@ -19,9 +19,9 @@ import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map"; import type { HomeAssistant } from "../../types"; +import "../ha-input-helper-text"; import "./ha-map"; import type { HaMap } from "./ha-map"; -import "../ha-input-helper-text"; declare global { // for fire event @@ -297,7 +297,7 @@ export class HaLocationsEditor extends LitElement { return css` ha-map { display: block; - height: 300px; + height: 100%; } `; } diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index dfb038e375..a34cbc5841 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -202,7 +202,7 @@ class HaConfigAreaPage extends LitElement { ${this.narrow diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index 3b13c7f89c..a82b567eee 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -82,7 +82,7 @@ export class HaConfigAreasDashboard extends LitElement { .narrow=${this.narrow} .isWide=${this.isWide} back-path="/config" - .tabs=${configSections.devices} + .tabs=${configSections.areas} .route=${this.route} > + ${this.hass.localize("ui.panel.config.backup.caption")} +
${error ? html`
${error}
` : ""}

diff --git a/src/panels/config/core/ha-config-core.js b/src/panels/config/core/ha-config-core.js index 4e01b9d6af..b76dd9b678 100644 --- a/src/panels/config/core/ha-config-core.js +++ b/src/panels/config/core/ha-config-core.js @@ -3,11 +3,11 @@ import "@polymer/app-layout/app-toolbar/app-toolbar"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../layouts/hass-tabs-subpage"; +import "../../../layouts/hass-subpage"; import LocalizeMixin from "../../../mixins/localize-mixin"; import "../../../styles/polymer-ha-style"; -import { configSections } from "../ha-panel-config"; -import "./ha-config-section-core"; +import "./ha-config-core-form"; +import "./ha-config-name-form"; /* * @appliesMixin LocalizeMixin @@ -17,36 +17,29 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) { return html` - -

- +
+ +
- + `; } @@ -59,14 +52,6 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) { route: Object, }; } - - _computeTabs() { - return configSections.general; - } - - computeClasses(isWide) { - return isWide ? "content" : "content narrow"; - } } customElements.define("ha-config-core", HaConfigCore); diff --git a/src/panels/config/core/ha-config-network.ts b/src/panels/config/core/ha-config-network.ts index 3fa243c0b6..42cecfe606 100644 --- a/src/panels/config/core/ha-config-network.ts +++ b/src/panels/config/core/ha-config-network.ts @@ -9,6 +9,7 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import "../../../components/ha-alert"; import "../../../components/ha-card"; import "../../../components/ha-checkbox"; import "../../../components/ha-network"; @@ -28,7 +29,7 @@ class ConfigNetwork extends LitElement { @state() private _networkConfig?: NetworkConfig; - @state() private _error?: string; + @state() private _error?: { code: string; message: string }; protected render(): TemplateResult { if ( @@ -39,9 +40,15 @@ class ConfigNetwork extends LitElement { } return html` - +
- ${this._error ? html`
${this._error}
` : ""} + ${this._error + ? html` + ${this._error.message || this._error.code} + ` + : ""}

Configure which network adapters integrations will use. Currently this setting only affects multicast traffic. A restart is required diff --git a/src/panels/config/core/ha-config-section-analytics.ts b/src/panels/config/core/ha-config-section-analytics.ts new file mode 100644 index 0000000000..200f1879b3 --- /dev/null +++ b/src/panels/config/core/ha-config-section-analytics.ts @@ -0,0 +1,43 @@ +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant, Route } from "../../../types"; +import "./ha-config-analytics"; + +@customElement("ha-config-section-analytics") +class HaConfigSectionAnalytics extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public route!: Route; + + @property({ type: Boolean }) public narrow!: boolean; + + protected render(): TemplateResult { + return html` + +

+ +
+ + `; + } + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-section-analytics": HaConfigSectionAnalytics; + } +} diff --git a/src/panels/config/core/ha-config-section-core.js b/src/panels/config/core/ha-config-section-core.js deleted file mode 100644 index 8a807fa346..0000000000 --- a/src/panels/config/core/ha-config-section-core.js +++ /dev/null @@ -1,70 +0,0 @@ -import "@material/mwc-button"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-card"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import "../../../styles/polymer-ha-style"; -import "../ha-config-section"; -import "./ha-config-analytics"; -import "./ha-config-core-form"; -import "./ha-config-name-form"; -import "./ha-config-network"; -import "./ha-config-url-form"; - -/* - * @appliesMixin LocalizeMixin - */ -class HaConfigSectionCore extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - [[localize('ui.panel.config.core.section.core.header')]] - [[localize('ui.panel.config.core.section.core.introduction')]] - - - - - - - - `; - } - - static get properties() { - return { - hass: { - type: Object, - }, - - isWide: { - type: Boolean, - value: false, - }, - - validating: { - type: Boolean, - value: false, - }, - - isValid: { - type: Boolean, - value: null, - }, - - validateLog: { - type: String, - value: "", - }, - - showAdvanced: Boolean, - }; - } -} - -customElements.define("ha-config-section-core", HaConfigSectionCore); diff --git a/src/panels/config/core/ha-config-section-network.ts b/src/panels/config/core/ha-config-section-network.ts new file mode 100644 index 0000000000..7f87ae1d6c --- /dev/null +++ b/src/panels/config/core/ha-config-section-network.ts @@ -0,0 +1,49 @@ +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant, Route } from "../../../types"; +import "./ha-config-network"; +import "./ha-config-url-form"; + +@customElement("ha-config-section-network") +class HaConfigSectionNetwork extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public route!: Route; + + @property({ type: Boolean }) public narrow!: boolean; + + protected render(): TemplateResult { + return html` + +
+ + +
+
+ `; + } + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + ha-config-network { + display: block; + margin-top: 24px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-section-network": HaConfigSectionNetwork; + } +} diff --git a/src/panels/config/core/ha-config-section-storage.ts b/src/panels/config/core/ha-config-section-storage.ts new file mode 100644 index 0000000000..6e3a96f140 --- /dev/null +++ b/src/panels/config/core/ha-config-section-storage.ts @@ -0,0 +1,40 @@ +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant, Route } from "../../../types"; +import "./ha-config-analytics"; + +@customElement("ha-config-section-storage") +class HaConfigSectionStorage extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public route!: Route; + + @property({ type: Boolean }) public narrow!: boolean; + + protected render(): TemplateResult { + return html` + +
+
+ `; + } + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-section-storage": HaConfigSectionStorage; + } +} diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts new file mode 100644 index 0000000000..0db85573b0 --- /dev/null +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -0,0 +1,115 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../../components/ha-card"; +import "../../../components/ha-navigation-list"; +import { CloudStatus } from "../../../data/cloud"; +import "../../../layouts/hass-subpage"; +import { haStyle } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; +import "../ha-config-section"; +import { configSections } from "../ha-panel-config"; + +@customElement("ha-config-system-navigation") +class HaConfigSystemNavigation extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean, reflect: true }) + public narrow!: boolean; + + @property({ type: Boolean }) public isWide!: boolean; + + @property({ attribute: false }) public cloudStatus?: CloudStatus; + + @property({ type: Boolean }) public showAdvanced!: boolean; + + protected render(): TemplateResult { + const pages = configSections.general.map((page) => ({ + ...page, + name: page.translationKey + ? this.hass.localize(page.translationKey) + : page.name, + })); + + return html` + + + + ${this.narrow + ? html`
+ ${this.hass.localize( + "ui.panel.config.dashboard.system.title" + )} +
` + : ""} + +
+
+
+ `; + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + ha-card { + margin-bottom: env(safe-area-inset-bottom); + } + :host(:not([narrow])) ha-card { + margin-bottom: max(24px, env(safe-area-inset-bottom)); + } + + ha-config-section { + margin: auto; + margin-top: -32px; + max-width: 600px; + } + + ha-card { + overflow: hidden; + } + + ha-card a { + text-decoration: none; + color: var(--primary-text-color); + } + + .title { + font-size: 16px; + padding: 16px; + padding-bottom: 0; + } + + :host([narrow]) ha-card { + border-radius: 0; + box-shadow: unset; + } + + :host([narrow]) ha-config-section { + margin-top: -42px; + } + + ha-navigation-list { + --navigation-list-item-title-font-size: 16px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-system-navigation": HaConfigSystemNavigation; + } +} diff --git a/src/panels/config/core/ha-config-url-form.ts b/src/panels/config/core/ha-config-url-form.ts index 80830d6c7a..f8af5f8781 100644 --- a/src/panels/config/core/ha-config-url-form.ts +++ b/src/panels/config/core/ha-config-url-form.ts @@ -9,17 +9,17 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; -import "../../../components/ha-card"; -import "../../../components/ha-switch"; +import { isIPAddress } from "../../../common/string/is_ip_address"; import "../../../components/ha-alert"; +import "../../../components/ha-card"; import "../../../components/ha-formfield"; +import "../../../components/ha-switch"; import "../../../components/ha-textfield"; import type { HaTextField } from "../../../components/ha-textfield"; import { CloudStatus, fetchCloudStatus } from "../../../data/cloud"; import { saveCoreConfig } from "../../../data/core"; import type { PolymerChangedEvent } from "../../../polymer-types"; import type { HomeAssistant } from "../../../types"; -import { isIPAddress } from "../../../common/string/is_ip_address"; @customElement("ha-config-url-form") class ConfigUrlForm extends LitElement { @@ -74,7 +74,10 @@ class ConfigUrlForm extends LitElement { } return html` - +
${!canEdit ? html` @@ -335,6 +338,7 @@ class ConfigUrlForm extends LitElement { a { color: var(--primary-color); + text-decoration: none; } `; } diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 3f0e3e4849..e690e37b59 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -34,6 +34,7 @@ import { updateCanInstall, UpdateEntity } from "../../../data/update"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar"; import "../../../layouts/ha-app-layout"; +import { PageNavigation } from "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; @@ -119,10 +120,26 @@ class HaConfigDashboard extends LitElement { private _notifyUpdates = false; + private _pages = memoizeOne((clouStatus, isLoaded) => { + const pages: PageNavigation[] = []; + if (clouStatus && isLoaded) { + pages.push({ + component: "cloud", + path: "/config/cloud", + name: "Home Assistant Cloud", + info: this.cloudStatus, + iconPath: mdiCloudLock, + iconColor: "#3B808E", + }); + } + return [...pages, ...configSections.dashboard]; + }); + protected render(): TemplateResult { const canInstallUpdates = this._filterUpdateEntitiesWithInstall( this.hass.states ); + return html` @@ -175,30 +192,14 @@ class HaConfigDashboard extends LitElement { ${this.hass.localize("panel.config")}
` : ""} - ${this.cloudStatus && isComponentLoaded(this.hass, "cloud") - ? html` - - ` - : ""}
diff --git a/src/panels/config/dashboard/ha-config-navigation.ts b/src/panels/config/dashboard/ha-config-navigation.ts index d457429318..9341432955 100644 --- a/src/panels/config/dashboard/ha-config-navigation.ts +++ b/src/panels/config/dashboard/ha-config-navigation.ts @@ -1,13 +1,14 @@ -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { canShowPage } from "../../../common/config/can_show_page"; import "../../../components/ha-card"; import "../../../components/ha-icon-next"; -import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; -import { PageNavigation } from "../../../layouts/hass-tabs-subpage"; -import { HomeAssistant } from "../../../types"; +import "../../../components/ha-navigation-list"; +import type { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; +import type { PageNavigation } from "../../../layouts/hass-tabs-subpage"; +import type { HomeAssistant } from "../../../types"; @customElement("ha-config-navigation") class HaConfigNavigation extends LitElement { @@ -15,129 +16,71 @@ class HaConfigNavigation extends LitElement { @property({ type: Boolean }) public narrow!: boolean; - @property() public showAdvanced!: boolean; - - @property() public pages!: PageNavigation[]; + @property({ attribute: false }) public pages!: PageNavigation[]; protected render(): TemplateResult { + const pages = this.pages + .filter((page) => + page.path === "#external-app-configuration" + ? this.hass.auth.external?.config.hasSettingsScreen + : canShowPage(this.hass, page) + ) + .map((page) => ({ + ...page, + name: + page.name || + this.hass.localize( + `ui.panel.config.dashboard.${page.translationKey}.title` + ), + description: + page.component === "cloud" && (page.info as CloudStatus) + ? page.info.logged_in + ? ` + ${this.hass.localize( + "ui.panel.config.cloud.description_login", + "email", + (page.info as CloudStatusLoggedIn).email + )} + ` + : ` + ${this.hass.localize( + "ui.panel.config.cloud.description_features" + )} + ` + : ` + ${ + page.description || + this.hass.localize( + `ui.panel.config.dashboard.${page.translationKey}.description` + ) + } + `, + })); return html` - ${this.pages.map((page) => - ( - page.path === "#external-app-configuration" - ? this.hass.auth.external?.config.hasSettingsScreen - : canShowPage(this.hass, page) - ) - ? html` - - -
- -
- - ${page.name || - this.hass.localize( - `ui.panel.config.dashboard.${page.translationKey}.title` - )} - ${page.component === "cloud" && (page.info as CloudStatus) - ? page.info.logged_in - ? html` -
- ${this.hass.localize( - "ui.panel.config.cloud.description_login", - "email", - (page.info as CloudStatusLoggedIn).email - )} -
- ` - : html` -
- ${this.hass.localize( - "ui.panel.config.cloud.description_features" - )} -
- ` - : html` -
- ${page.description || - this.hass.localize( - `ui.panel.config.dashboard.${page.translationKey}.description` - )} -
- `} -
- ${!this.narrow ? html`` : ""} -
-
- ` - : "" - )} + `; } private _entryClicked(ev) { - ev.currentTarget.blur(); - if ( - ev.currentTarget.parentElement.href.endsWith( - "#external-app-configuration" - ) - ) { + const anchor = ev + .composedPath() + .find((n) => (n as HTMLElement).tagName === "A") as + | HTMLAnchorElement + | undefined; + + if (anchor?.href?.endsWith("#external-app-configuration")) { ev.preventDefault(); this.hass.auth.external!.fireMessage({ type: "config_screen/show", }); } } - - static get styles(): CSSResultGroup { - return css` - a { - text-decoration: none; - color: var(--primary-text-color); - position: relative; - display: block; - outline: 0; - } - ha-svg-icon, - ha-icon-next { - color: var(--secondary-text-color); - height: 24px; - width: 24px; - } - ha-svg-icon { - padding: 8px; - } - .iron-selected paper-item::before, - a:not(.iron-selected):focus::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - content: ""; - transition: opacity 15ms linear; - will-change: opacity; - } - a:not(.iron-selected):focus::before { - background-color: currentColor; - opacity: var(--dark-divider-opacity); - } - .iron-selected paper-item:focus::before, - .iron-selected:focus paper-item::before { - opacity: 0.2; - } - .icon-background { - border-radius: 50%; - } - .icon-background ha-svg-icon { - color: #fff; - } - `; - } } declare global { diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 7483960acd..a467dcbe6f 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -4,12 +4,15 @@ import { mdiBadgeAccountHorizontal, mdiCellphoneCog, mdiCog, + mdiCpu32Bit, mdiDevices, mdiHomeAssistant, mdiInformation, + mdiInformationOutline, mdiLightningBolt, mdiMapMarkerRadius, mdiMathLog, + mdiNetwork, mdiNfcVariant, mdiPalette, mdiPaletteSwatch, @@ -20,6 +23,7 @@ import { mdiShape, mdiSofa, mdiTools, + mdiUpdate, mdiViewDashboard, } from "@mdi/js"; import { PolymerElement } from "@polymer/polymer"; @@ -58,11 +62,11 @@ export const configSections: { [name: string]: PageNavigation[] } = { core: true, }, { - path: "/config/blueprint", - translationKey: "blueprints", - iconPath: mdiPaletteSwatch, - iconColor: "#64B5F6", - component: "blueprint", + path: "/config/areas", + translationKey: "areas", + iconPath: mdiSofa, + iconColor: "#E48629", + components: ["zone"], }, { path: "/config/backup", @@ -74,8 +78,8 @@ export const configSections: { [name: string]: PageNavigation[] } = { { path: "/hassio", translationKey: "supervisor", - iconPath: mdiHomeAssistant, - iconColor: "#4084CD", + iconPath: mdiPuzzle, + iconColor: "#F1C447", component: "hassio", }, { @@ -97,7 +101,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { translationKey: "people", iconPath: mdiAccount, iconColor: "#E48629", - components: ["person", "zone", "users"], + components: ["person", "users"], }, { path: "#external-app-configuration", @@ -106,9 +110,16 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#8E24AA", }, { - path: "/config/server_control", - translationKey: "settings", + path: "/config/system", + translationKey: "system", iconPath: mdiCog, + iconColor: "#301ABE", + core: true, + }, + { + path: "/config/info", + translationKey: "about", + iconPath: mdiInformationOutline, iconColor: "#4A5963", core: true, }, @@ -148,11 +159,11 @@ export const configSections: { [name: string]: PageNavigation[] } = { core: true, }, { - component: "areas", - path: "/config/areas", - translationKey: "ui.panel.config.areas.caption", - iconPath: mdiSofa, - iconColor: "#2D338F", + component: "helpers", + path: "/config/helpers", + translationKey: "ui.panel.config.helpers.caption", + iconPath: mdiTools, + iconColor: "#4D2EA4", core: true, }, ], @@ -178,16 +189,6 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconPath: mdiScriptText, iconColor: "#518C43", }, - { - component: "helpers", - path: "/config/helpers", - translationKey: "ui.panel.config.helpers.caption", - iconPath: mdiTools, - iconColor: "#4D2EA4", - core: true, - }, - ], - blueprints: [ { component: "blueprint", path: "/config/blueprint", @@ -232,13 +233,6 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconPath: mdiAccount, iconColor: "#E48629", }, - { - component: "zone", - path: "/config/zone", - translationKey: "ui.panel.config.zone.caption", - iconPath: mdiMapMarkerRadius, - iconColor: "#E48629", - }, { component: "users", path: "/config/users", @@ -249,6 +243,23 @@ export const configSections: { [name: string]: PageNavigation[] } = { advancedOnly: true, }, ], + areas: [ + { + component: "areas", + path: "/config/areas", + translationKey: "ui.panel.config.areas.caption", + iconPath: mdiSofa, + iconColor: "#2D338F", + core: true, + }, + { + component: "zone", + path: "/config/location", + translationKey: "ui.panel.config.zone.caption", + iconPath: mdiMapMarkerRadius, + iconColor: "#E48629", + }, + ], general: [ { component: "core", @@ -274,6 +285,45 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#4A5963", core: true, }, + { + path: "/config/backup", + translationKey: "ui.panel.config.backup.caption", + iconPath: mdiBackupRestore, + iconColor: "#4084CD", + component: "backup", + }, + { + path: "/config/analytics", + translationKey: "ui.panel.config.analytics.caption", + iconPath: mdiShape, + iconColor: "#f1c447", + }, + { + path: "/config/hardware", + translationKey: "ui.panel.config.hardware.caption", + iconPath: mdiCpu32Bit, + iconColor: "#4A5963", + }, + { + path: "/config/network", + translationKey: "ui.panel.config.network.caption", + iconPath: mdiNetwork, + iconColor: "#B1345C", + }, + { + path: "/config/storage", + translationKey: "ui.panel.config.storage.caption", + iconPath: mdiServer, + iconColor: "#518C43", + }, + { + path: "/config/update", + translationKey: "ui.panel.config.updates.caption", + iconPath: mdiUpdate, + iconColor: "#4A5963", + }, + ], + about: [ { component: "info", path: "/config/info", @@ -296,6 +346,10 @@ class HaPanelConfig extends HassRouterPage { protected routerOptions: RouterOptions = { defaultPage: "dashboard", routes: { + analytics: { + tag: "ha-config-section-analytics", + load: () => import("./core/ha-config-section-analytics"), + }, areas: { tag: "ha-config-areas", load: () => import("./areas/ha-config-areas"), @@ -328,9 +382,9 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-devices", load: () => import("./devices/ha-config-devices"), }, - server_control: { - tag: "ha-config-server-control", - load: () => import("./server_control/ha-config-server-control"), + system: { + tag: "ha-config-system-navigation", + load: () => import("./core/ha-config-system-navigation"), }, logs: { tag: "ha-config-logs", @@ -362,6 +416,10 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-lovelace", load: () => import("./lovelace/ha-config-lovelace"), }, + network: { + tag: "ha-config-section-network", + load: () => import("./core/ha-config-section-network"), + }, person: { tag: "ha-config-person", load: () => import("./person/ha-config-person"), @@ -378,11 +436,15 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-helpers", load: () => import("./helpers/ha-config-helpers"), }, + storage: { + tag: "ha-config-section-storage", + load: () => import("./core/ha-config-section-storage"), + }, users: { tag: "ha-config-users", load: () => import("./users/ha-config-users"), }, - zone: { + location: { tag: "ha-config-zone", load: () => import("./zone/ha-config-zone"), }, diff --git a/src/panels/config/helpers/ha-config-helpers.ts b/src/panels/config/helpers/ha-config-helpers.ts index 4173fd69ef..fbaa0e1c2f 100644 --- a/src/panels/config/helpers/ha-config-helpers.ts +++ b/src/panels/config/helpers/ha-config-helpers.ts @@ -6,21 +6,29 @@ import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { domainIcon } from "../../../common/entity/domain_icon"; +import { navigate } from "../../../common/navigate"; import { LocalizeFunc } from "../../../common/translations/localize"; +import { extractSearchParam } from "../../../common/url/search-params"; import { DataTableColumnContainer, RowClickedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-fab"; -import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-icon"; +import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-svg-icon"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; +import { getConfigFlowHandlers } from "../../../data/config_flow"; import { EntityRegistryEntry, subscribeEntityRegistry, } from "../../../data/entity_registry"; import { domainToName } from "../../../data/integration"; +import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage-data-table"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; @@ -29,14 +37,6 @@ import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor"; import { configSections } from "../ha-panel-config"; import { HELPER_DOMAINS } from "./const"; import { showHelperDetailDialog } from "./show-dialog-helper-detail"; -import { navigate } from "../../../common/navigate"; -import { extractSearchParam } from "../../../common/url/search-params"; -import { getConfigFlowHandlers } from "../../../data/config_flow"; -import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow"; -import { - showAlertDialog, - showConfirmationDialog, -} from "../../../dialogs/generic/show-dialog-box"; // This groups items by a key but only returns last entry per key. const groupByOne = ( @@ -196,7 +196,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) { .narrow=${this.narrow} back-path="/config" .route=${this.route} - .tabs=${configSections.automations} + .tabs=${configSections.devices} .columns=${this._columns(this.narrow, this.hass.localize)} .data=${this._getItems( this._stateItems, diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index a2459d1565..cb61dbc46f 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -1,7 +1,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property } from "lit/decorators"; -import "../../../layouts/hass-tabs-subpage"; import "../../../components/ha-logo-svg"; +import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; @@ -34,7 +34,7 @@ class HaConfigInfo extends LitElement { .narrow=${this.narrow} back-path="/config" .route=${this.route} - .tabs=${configSections.general} + .tabs=${configSections.about} >
${search}
@@ -80,7 +79,7 @@ export class HaConfigLogs extends LitElement { .filter=${this._filter} >
- + `; } diff --git a/src/panels/config/server_control/ha-config-server-control.ts b/src/panels/config/server_control/ha-config-server-control.ts index 9c0011c049..6686590646 100644 --- a/src/panels/config/server_control/ha-config-server-control.ts +++ b/src/panels/config/server_control/ha-config-server-control.ts @@ -8,11 +8,11 @@ import "../../../components/buttons/ha-call-service-button"; import "../../../components/ha-card"; import { checkCoreConfig } from "../../../data/core"; import { domainToName } from "../../../data/integration"; +import "../../../layouts/hass-subpage"; import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import "../ha-config-section"; -import { configSections } from "../ha-panel-config"; @customElement("ha-config-server-control") export class HaConfigServerControl extends LitElement { @@ -49,12 +49,10 @@ export class HaConfigServerControl extends LitElement { protected render(): TemplateResult { return html` - @@ -203,7 +201,7 @@ export class HaConfigServerControl extends LitElement { ` : ""} - + `; } diff --git a/src/panels/config/users/ha-config-users.ts b/src/panels/config/users/ha-config-users.ts index d93a01748b..ba4d4746ff 100644 --- a/src/panels/config/users/ha-config-users.ts +++ b/src/panels/config/users/ha-config-users.ts @@ -157,7 +157,7 @@ export class HaConfigUsers extends LitElement { .narrow=${this.narrow} .route=${this.route} backPath="/config" - .tabs=${configSections.persons} + .tabs=${configSections.areas} .columns=${this._columns(this.narrow, this.hass.localize)} .data=${this._users} @row-click=${this._editUser} diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 5533dcff03..23db9af09f 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -228,7 +228,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { .narrow=${this.narrow} .route=${this.route} back-path="/config" - .tabs=${configSections.persons} + .tabs=${configSections.areas} > ${this.narrow ? html` diff --git a/src/translations/en.json b/src/translations/en.json index f165746cfb..d6c1811a0a 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1067,23 +1067,19 @@ "dashboard": { "devices": { "title": "Devices & Services", - "description": "Integrations, devices, entities and areas" + "description": "Integrations, devices, entities and helpers" }, "automations": { "title": "Automations & Scenes", - "description": "Manage automations, scenes, scripts and helpers" + "description": "Manage automations, scenes, scripts and blueprints" }, "backup": { "title": "Backup", "description": "Generate backups of your Home Assistant configuration" }, - "blueprints": { - "title": "Blueprints", - "description": "Pre-made automations and scripts by the community" - }, "supervisor": { - "title": "Add-ons, Backups & Supervisor", - "description": "Create backups, check logs or reboot your system" + "title": "Add-ons", + "description": "Extend the function around Home Assistant" }, "dashboards": { "title": "Dashboards", @@ -1098,16 +1094,24 @@ "description": "Trigger automations when an NFC tag, QR code, etc. is scanned" }, "people": { - "title": "People & Zones", - "description": "Manage the people and zones that Home Assistant tracks" + "title": "People", + "description": "Manage the people that Home Assistant tracks" + }, + "areas": { + "title": "Areas & Zones", + "description": "Manage areas & zones that Home Assistant tracks" }, "companion": { "title": "Companion App", "description": "Location and notifications" }, - "settings": { - "title": "Settings", - "description": "Basic settings, server controls, logs and info" + "system": { + "title": "System", + "description": "Create backups, check logs or reboot your system" + }, + "about": { + "title": "About", + "description": "Version, system health and links to documentation" } }, "common": { @@ -1117,6 +1121,7 @@ "learn_more": "Learn more" }, "updates": { + "caption": "Updates", "no_update_entities": { "title": "Unable to check for updates", "description": "You do not have any integrations that provide updates." @@ -1168,7 +1173,7 @@ } }, "backup": { - "caption": "[%key:ui::panel::config::dashboard::backup::title%]", + "caption": "Backups", "create_backup": "[%key:supervisor::backup::create_backup%]", "creating_backup": "Backup is currently being created", "download_backup": "[%key:supervisor::backup::download_backup%]", @@ -1458,6 +1463,9 @@ "internal_url_https_error_title": "Invalid local network URL", "internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate." }, + "hardware": { + "caption": "Hardware" + }, "info": { "caption": "Info", "copy_menu": "Copy menu", @@ -1764,7 +1772,7 @@ "geo_location": { "label": "Geolocation", "source": "Source", - "zone": "Zone", + "zone": "Location", "event": "Event", "enter": "Enter", "leave": "Leave" @@ -3087,6 +3095,15 @@ "tips": { "tip": "Tip!", "join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}" + }, + "analytics": { + "caption": "Analytics" + }, + "network": { + "caption": "Network" + }, + "storage": { + "caption": "Storage" } }, "lovelace": { @@ -3680,7 +3697,7 @@ } }, "map": { - "edit_zones": "Edit Zones" + "edit_zones": "Edit zones" }, "profile": { "current_user": "You are currently logged in as {fullName}.", From d290c11219b33281599fa6ce6e0a2f73e7a59185 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 20 Apr 2022 17:38:35 -0500 Subject: [PATCH 035/181] Config menu updates to get it ready for nightly (#12368) --- src/components/ha-clickable-list-item.ts | 1 - src/panels/config/ha-panel-config.ts | 8 +- src/panels/config/info/ha-config-info.ts | 40 ++-- .../ha-config-server-control.ts | 194 +++++++++--------- 4 files changed, 126 insertions(+), 117 deletions(-) diff --git a/src/components/ha-clickable-list-item.ts b/src/components/ha-clickable-list-item.ts index 895c67954a..41c3734ab4 100644 --- a/src/components/ha-clickable-list-item.ts +++ b/src/components/ha-clickable-list-item.ts @@ -55,7 +55,6 @@ export class HaClickableListItem extends ListItemBase { align-items: center; padding-left: var(--mdc-list-side-padding, 20px); padding-right: var(--mdc-list-side-padding, 20px); - font-weight: 500; } `, ]; diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index a467dcbe6f..313a9b18f7 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -254,7 +254,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { }, { component: "zone", - path: "/config/location", + path: "/config/zone", translationKey: "ui.panel.config.zone.caption", iconPath: mdiMapMarkerRadius, iconColor: "#E48629", @@ -436,6 +436,10 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-helpers", load: () => import("./helpers/ha-config-helpers"), }, + server_control: { + tag: "ha-config-server-control", + load: () => import("./server_control/ha-config-server-control"), + }, storage: { tag: "ha-config-section-storage", load: () => import("./core/ha-config-section-storage"), @@ -444,7 +448,7 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-users", load: () => import("./users/ha-config-users"), }, - location: { + zone: { tag: "ha-config-zone", load: () => import("./zone/ha-config-zone"), }, diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index cb61dbc46f..e27f489d92 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -1,11 +1,10 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property } from "lit/decorators"; import "../../../components/ha-logo-svg"; -import "../../../layouts/hass-tabs-subpage"; +import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; -import { configSections } from "../ha-panel-config"; import "./integrations-card"; import "./system-health-card"; @@ -29,12 +28,11 @@ class HaConfigInfo extends LitElement { (window as any).CUSTOM_UI_LIST || []; return html` -
0 - ? html` - - ` - : ""} + ${ + customUiList.length > 0 + ? html` +
+ ${this.hass.localize("ui.panel.config.info.custom_uis")} + ${customUiList.map( + (item) => html` +
+ ${item.name}: ${item.version} +
+ ` + )} +
+ ` + : "" + }

diff --git a/src/panels/config/server_control/ha-config-server-control.ts b/src/panels/config/server_control/ha-config-server-control.ts index 6686590646..03440dd2f5 100644 --- a/src/panels/config/server_control/ha-config-server-control.ts +++ b/src/panels/config/server_control/ha-config-server-control.ts @@ -18,13 +18,13 @@ import "../ha-config-section"; export class HaConfigServerControl extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced!: boolean; @state() private _validating = false; @@ -54,81 +54,73 @@ export class HaConfigServerControl extends LitElement { .narrow=${this.narrow} back-path="/config/system" .showAdvanced=${this.showAdvanced} + .header=${this.hass.localize("ui.panel.config.server_control.caption")} > - - ${this.hass.localize( - "ui.panel.config.server_control.caption" - )} - ${this.hass.localize( - "ui.panel.config.server_control.description" - )} - +
${this.showAdvanced - ? html` -
- ${this.hass.localize( - "ui.panel.config.server_control.section.validation.introduction" + ? html` + - ${!this._validating - ? html` - ${this._isValid - ? html`
- ${this.hass.localize( - "ui.panel.config.server_control.section.validation.valid" - )} -
` - : ""} - - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.check_config" - )} - - ` - : html` - - `} -
- ` - : html` -
- - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.invalid" - )} - - - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.check_config" - )} - -
-
- ${this._validateLog} -
- `} -
- ` + > +
+ ${this.hass.localize( + "ui.panel.config.server_control.section.validation.introduction" + )} + ${!this._validateLog + ? html` +
+ ${!this._validating + ? html` + ${this._isValid + ? html`
+ ${this.hass.localize( + "ui.panel.config.server_control.section.validation.valid" + )} +
` + : ""} + + ${this.hass.localize( + "ui.panel.config.server_control.section.validation.check_config" + )} + + ` + : html` + + `} +
+ ` + : html` +
+ + ${this.hass.localize( + "ui.panel.config.server_control.section.validation.invalid" + )} + + + ${this.hass.localize( + "ui.panel.config.server_control.section.validation.check_config" + )} + +
+
+ ${this._validateLog} +
+ `} +
+ + ` : ""} ${this._reloadableDomains.map( (domain) => - html`
- ${this.hass.localize( - `ui.panel.config.server_control.section.reloading.${domain}` - ) || - this.hass.localize( - "ui.panel.config.server_control.section.reloading.reload", - "domain", - domainToName(this.hass.localize, domain) - )} - -
` + html` +
+ ${this.hass.localize( + `ui.panel.config.server_control.section.reloading.${domain}` + ) || + this.hass.localize( + "ui.panel.config.server_control.section.reloading.reload", + "domain", + domainToName(this.hass.localize, domain) + )} + +
+ ` )}
` : ""} -
+
`; } @@ -251,10 +245,22 @@ export class HaConfigServerControl extends LitElement { direction: ltr; } - ha-config-section { - padding-bottom: 24px; + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + + ha-card { + margin-top: 24px; } `, ]; } } + +declare global { + interface HTMLElementTagNameMap { + "ha-config-server-control": HaConfigServerControl; + } +} From 27ca45dc70f30e19d29cd09581f59da6a3205bf6 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 20 Apr 2022 17:43:40 -0500 Subject: [PATCH 036/181] Bumped version to 20220420.0 (#12369) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7f23b545dd..9c9dfb640e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220405.0 +version = 20220420.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 2099259393bdbd65da44de0f030d99155ca6a978 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 22 Apr 2022 03:32:25 +0200 Subject: [PATCH 037/181] Use template selector in wait_template (#12366) --- .../action/types/ha-automation-action-wait_template.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts index 423c0f224c..17c28ab665 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts @@ -10,9 +10,7 @@ const SCHEMA: HaFormSchema[] = [ { name: "wait_template", selector: { - text: { - multiline: true, - }, + template: {}, }, }, { From 68657915963aba89b6391fb098302eb0749ef1d2 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 22 Apr 2022 03:42:28 +0200 Subject: [PATCH 038/181] Add jinja2 editor to template triggers/conditions (#12365) Co-authored-by: Zack --- .../types/ha-automation-condition-template.ts | 27 +++++++++---------- .../types/ha-automation-trigger-template.ts | 25 +++++++++-------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts index 6ada82f152..ed9bc32e82 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts @@ -1,6 +1,6 @@ -import "../../../../../components/ha-textarea"; -import { css, html, LitElement } from "lit"; +import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import "../../../../../components/ha-textarea"; import type { TemplateCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import { handleChangeEvent } from "../ha-automation-condition-row"; @@ -18,28 +18,27 @@ export class HaTemplateCondition extends LitElement { protected render() { const { value_template } = this.condition; return html` - + ${this.hass.localize( "ui.panel.config.automation.editor.conditions.type.template.value_template" )} + * +

+
+ > `; } private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } - - static styles = css` - ha-textarea { - display: block; - } - `; } declare global { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts index 1bc192a231..eeb78b986a 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-template.ts @@ -1,5 +1,5 @@ import "../../../../../components/ha-textarea"; -import { css, html, LitElement } from "lit"; +import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import type { TemplateTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; @@ -18,28 +18,27 @@ export class HaTemplateTrigger extends LitElement { protected render() { const { value_template } = this.trigger; return html` - + ${this.hass.localize( "ui.panel.config.automation.editor.triggers.type.template.value_template" )} + * +

+
+ > `; } private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } - - static styles = css` - ha-textarea { - display: block; - } - `; } declare global { From c305dd4cd56f6eee199f7b3c2022a8ce166757cc Mon Sep 17 00:00:00 2001 From: Wesley Vos <17592840+Wesley-Vos@users.noreply.github.com> Date: Fri, 22 Apr 2022 04:01:09 +0200 Subject: [PATCH 039/181] Fix for monetary entities (#12378) --- src/common/entity/compute_state_display.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index e78b889457..ed890603a9 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -33,6 +33,7 @@ export const computeStateDisplay = ( return formatNumber(compareState, locale, { style: "currency", currency: stateObj.attributes.unit_of_measurement, + minimumFractionDigits: 2, }); } catch (_err) { // fallback to default From ee7aa54ab4e19386329397da270cd80373da9bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Fri, 22 Apr 2022 04:06:35 +0200 Subject: [PATCH 040/181] Add entity search tip to dev-tools set state (#12355) --- gallery/src/pages/components/ha-alert.ts | 20 +++-- gallery/src/pages/components/ha-tip.markdown | 3 + gallery/src/pages/components/ha-tip.ts | 73 +++++++++++++++++++ src/components/ha-tip.ts | 38 ++++++++++ src/dialogs/quick-bar/ha-quick-bar.ts | 6 +- .../config/dashboard/ha-config-dashboard.ts | 28 ++----- .../state/developer-tools-state.js | 6 ++ src/translations/en.json | 4 + 8 files changed, 144 insertions(+), 34 deletions(-) create mode 100644 gallery/src/pages/components/ha-tip.markdown create mode 100644 gallery/src/pages/components/ha-tip.ts create mode 100644 src/components/ha-tip.ts diff --git a/gallery/src/pages/components/ha-alert.ts b/gallery/src/pages/components/ha-alert.ts index c4f44e80a9..ced4c5a44b 100644 --- a/gallery/src/pages/components/ha-alert.ts +++ b/gallery/src/pages/components/ha-alert.ts @@ -159,13 +159,19 @@ export class DemoHaAlert extends LitElement { firstUpdated(changedProps) { super.firstUpdated(changedProps); - applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), { - default_theme: "default", - default_dark_theme: "default", - themes: {}, - darkMode: true, - theme: "default", - }); + applyThemesOnElement( + this.shadowRoot!.querySelector(".dark"), + { + default_theme: "default", + default_dark_theme: "default", + themes: {}, + darkMode: true, + theme: "default", + }, + undefined, + undefined, + true + ); } static get styles() { diff --git a/gallery/src/pages/components/ha-tip.markdown b/gallery/src/pages/components/ha-tip.markdown new file mode 100644 index 0000000000..a3bc162733 --- /dev/null +++ b/gallery/src/pages/components/ha-tip.markdown @@ -0,0 +1,3 @@ +--- +title: Tips +--- diff --git a/gallery/src/pages/components/ha-tip.ts b/gallery/src/pages/components/ha-tip.ts new file mode 100644 index 0000000000..49fa1f2c71 --- /dev/null +++ b/gallery/src/pages/components/ha-tip.ts @@ -0,0 +1,73 @@ +import { html, css, LitElement, TemplateResult } from "lit"; +import { customElement } from "lit/decorators"; +import "../../../../src/components/ha-tip"; +import "../../../../src/components/ha-card"; +import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element"; + +const tips: (string | TemplateResult)[] = [ + "Test tip", + "Bigger test tip, with some random text just to fill up as much space as possible without it looking like I'm really trying to to that", + html`Tip with HTML`, +]; + +@customElement("demo-components-ha-tip") +export class DemoHaTip extends LitElement { + protected render(): TemplateResult { + return html` ${["light", "dark"].map( + (mode) => html` +
+ +
+ ${tips.map((tip) => html`${tip}`)} +
+
+
+ ` + )}`; + } + + firstUpdated(changedProps) { + super.firstUpdated(changedProps); + applyThemesOnElement( + this.shadowRoot!.querySelector(".dark"), + { + default_theme: "default", + default_dark_theme: "default", + themes: {}, + darkMode: true, + theme: "default", + }, + undefined, + undefined, + true + ); + } + + static get styles() { + return css` + :host { + display: flex; + flex-direction: row; + justify-content: space-between; + } + .dark, + .light { + display: block; + background-color: var(--primary-background-color); + padding: 0 50px; + } + ha-tip { + margin-bottom: 14px; + } + ha-card { + margin: 24px auto; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "demo-components-ha-tip": DemoHaTip; + } +} diff --git a/src/components/ha-tip.ts b/src/components/ha-tip.ts new file mode 100644 index 0000000000..ef4e36fa05 --- /dev/null +++ b/src/components/ha-tip.ts @@ -0,0 +1,38 @@ +import { mdiLightbulbOutline } from "@mdi/js"; +import { css, html, LitElement } from "lit"; +import { customElement } from "lit/decorators"; + +import "./ha-svg-icon"; + +@customElement("ha-tip") +class HaTip extends LitElement { + public render() { + return html` + + Tip! + + `; + } + + static styles = css` + :host { + display: block; + text-align: center; + } + + .text { + margin-left: 2px; + color: var(--secondary-text-color); + } + + .prefix { + font-weight: 500; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-tip": HaTip; + } +} diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 6135b6f4fd..0165a14aca 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -240,7 +240,7 @@ export class QuickBar extends LitElement { : ""} `} - ${this._hint ? html`
${this._hint}
` : ""} + ${this._hint ? html`${this._hint}` : ""} `; } @@ -782,10 +782,8 @@ export class QuickBar extends LitElement { text-transform: capitalize; } - .hint { + ha-tip { padding: 20px; - font-style: italic; - text-align: center; } .nothing-found { diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index e690e37b59..b56c5be7c4 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -1,12 +1,6 @@ import type { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; -import { - mdiCloudLock, - mdiDotsVertical, - mdiLightbulbOutline, - mdiMagnify, - mdiNewBox, -} from "@mdi/js"; +import { mdiCloudLock, mdiDotsVertical, mdiMagnify, mdiNewBox } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import type { HassEntities } from "home-assistant-js-websocket"; @@ -29,6 +23,7 @@ import "../../../components/ha-icon-button"; import "../../../components/ha-icon-next"; import "../../../components/ha-menu-button"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-tip"; import { CloudStatus } from "../../../data/cloud"; import { updateCanInstall, UpdateEntity } from "../../../data/update"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; @@ -91,7 +86,7 @@ const randomTip = (hass: HomeAssistant) => { ), weight: 2, }, - { content: hass.localize("ui.dialogs.quick-bar.key_c_hint"), weight: 1 }, + { content: hass.localize("ui.tips.key_c_hint"), weight: 1 }, ]; tips.forEach((tip) => { @@ -202,11 +197,7 @@ class HaConfigDashboard extends LitElement { )} > -
- - Tip! - ${this._tip} -
+ ${this._tip} `; @@ -343,19 +334,10 @@ class HaConfigDashboard extends LitElement { margin-top: -42px; } - .tips { - text-align: center; + ha-tip { margin-bottom: max(env(safe-area-inset-bottom), 8px); } - .tips .text { - color: var(--secondary-text-color); - } - - .tip-word { - font-weight: 500; - } - .new { color: var(--primary-color); } diff --git a/src/panels/developer-tools/state/developer-tools-state.js b/src/panels/developer-tools/state/developer-tools-state.js index f4069c9824..a6f89dac31 100644 --- a/src/panels/developer-tools/state/developer-tools-state.js +++ b/src/panels/developer-tools/state/developer-tools-state.js @@ -18,6 +18,7 @@ import "../../../components/ha-code-editor"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; import "../../../components/ha-checkbox"; +import "../../../components/ha-tip"; import "../../../components/search-input"; import "../../../components/ha-expansion-panel"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; @@ -90,6 +91,10 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { display: block; --mdc-text-field-fill-color: transparent; } + ha-tip { + display: flex; + padding: 8px 0; + } th.attributes { position: relative; @@ -181,6 +186,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { on-change="entityIdChanged" allow-custom-entity > + [[localize('ui.tips.key_e_hint')]] Date: Thu, 21 Apr 2022 21:11:32 -0500 Subject: [PATCH 041/181] Added ability to retry on initialization errors. (#12103) --- src/layouts/ha-init-page.ts | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/layouts/ha-init-page.ts b/src/layouts/ha-init-page.ts index 305bc18c84..42df3b8d69 100644 --- a/src/layouts/ha-init-page.ts +++ b/src/layouts/ha-init-page.ts @@ -5,15 +5,22 @@ import { property, state } from "lit/decorators"; class HaInitPage extends LitElement { @property({ type: Boolean }) public error = false; - @state() showProgressIndicator = false; + @state() private _showProgressIndicator = false; - private _showProgressIndicatorTimeout; + @state() private _retryInSeconds = 60; + + private _showProgressIndicatorTimeout?: NodeJS.Timeout; + + private _retryInterval?: NodeJS.Timeout; protected render() { return this.error ? html`

Unable to connect to Home Assistant.

- Retry +

+ Retrying in ${this._retryInSeconds} seconds... +

+ Retry now ${location.host.includes("ui.nabu.casa") ? html`

@@ -29,7 +36,7 @@ class HaInitPage extends LitElement { ` : html`

- ${this.showProgressIndicator + ${this._showProgressIndicator ? html`` : ""}
@@ -39,14 +46,26 @@ class HaInitPage extends LitElement { disconnectedCallback() { super.disconnectedCallback(); - clearTimeout(this._showProgressIndicatorTimeout); + if (this._showProgressIndicatorTimeout) { + clearTimeout(this._showProgressIndicatorTimeout); + } + if (this._retryInterval) { + clearInterval(this._retryInterval); + } } protected firstUpdated() { this._showProgressIndicatorTimeout = setTimeout(async () => { await import("../components/ha-circular-progress"); - this.showProgressIndicator = true; + this._showProgressIndicator = true; }, 5000); + + this._retryInterval = setInterval(() => { + const remainingSeconds = this._retryInSeconds--; + if (remainingSeconds <= 0) { + this._retry(); + } + }, 1000); } private _retry() { @@ -70,6 +89,9 @@ class HaInitPage extends LitElement { a { color: var(--primary-color); } + .retry-text { + margin-top: 0; + } p, #loading-text { max-width: 350px; From 3b8b6eb315af59aa806bc6f845e28b7a825a526f Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Fri, 22 Apr 2022 18:27:49 +0300 Subject: [PATCH 042/181] RTL fixes (#12367) --- src/components/ha-combo-box.ts | 12 ++++++++++++ src/components/ha-dialog.ts | 4 ++++ src/components/ha-fab.ts | 12 ++++++++++++ src/components/ha-file-upload.ts | 14 ++++++++++++++ src/components/ha-select.ts | 4 ++++ src/components/ha-target-picker.ts | 4 ++++ src/components/ha-textfield.ts | 13 +++++++++++++ src/dialogs/config-flow/dialog-data-entry-flow.ts | 5 +---- src/dialogs/config-flow/step-flow-form.ts | 4 ++++ src/dialogs/config-flow/step-flow-pick-flow.ts | 4 ++++ src/dialogs/config-flow/step-flow-pick-handler.ts | 4 ++++ .../automation/action/ha-automation-action-row.ts | 2 +- .../condition/ha-automation-condition-row.ts | 2 +- .../trigger/ha-automation-trigger-row.ts | 2 +- .../config/integrations/ha-integration-header.ts | 4 ++++ src/panels/config/logs/dialog-system-log-detail.ts | 1 + 16 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index c0166ddfa8..fb28ab211c 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -250,6 +250,18 @@ export class HaComboBox extends LitElement { top: -7px; right: 36px; } + + :host-context([style*="direction: rtl;"]) .toggle-button { + left: 12px; + right: auto; + top: -10px; + } + :host-context([style*="direction: rtl;"]) .clear-button { + --mdc-icon-size: 20px; + top: -7px; + left: 36px; + right: auto; + } `; } } diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 693bae6367..3f33a4e349 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -98,6 +98,10 @@ export class HaDialog extends DialogBase { margin-left: 40px; margin-right: 0px; } + :host-context([style*="direction: rtl;"]) .dialog-actions { + left: 0px !important; + right: auto !important; + } `, ]; } diff --git a/src/components/ha-fab.ts b/src/components/ha-fab.ts index c2f58b8f0f..563ee32e32 100644 --- a/src/components/ha-fab.ts +++ b/src/components/ha-fab.ts @@ -1,5 +1,6 @@ import { Fab } from "@material/mwc-fab"; import { customElement } from "lit/decorators"; +import { css } from "lit"; @customElement("ha-fab") export class HaFab extends Fab { @@ -7,6 +8,17 @@ export class HaFab extends Fab { super.firstUpdated(changedProperties); this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)"); } + + static override styles = Fab.styles.concat([ + css` + :host-context([style*="direction: rtl;"]) + .mdc-fab--extended + .mdc-fab__icon { + margin-left: 12px !important; + margin-right: calc(12px - 20px) !important; + } + `, + ]); } declare global { diff --git a/src/components/ha-file-upload.ts b/src/components/ha-file-upload.ts index 9c81a3f17a..6e90308ee4 100644 --- a/src/components/ha-file-upload.ts +++ b/src/components/ha-file-upload.ts @@ -176,10 +176,24 @@ export class HaFileUpload extends LitElement { .mdc-text-field__icon--leading { margin-bottom: 12px; } + :host-context([style*="direction: rtl;"]) + .mdc-text-field__icon--leading { + margin-right: 0px; + } .mdc-text-field--filled .mdc-floating-label--float-above { transform: scale(0.75); top: 8px; } + :host-context([style*="direction: rtl;"]) .mdc-floating-label { + left: initial; + right: 16px; + } + :host-context([style*="direction: rtl;"]) + .mdc-text-field--filled + .mdc-floating-label { + left: initial; + right: 48px; + } .dragged:before { position: var(--layout-fit_-_position); top: var(--layout-fit_-_top); diff --git a/src/components/ha-select.ts b/src/components/ha-select.ts index b90133b8eb..148ffcd21d 100644 --- a/src/components/ha-select.ts +++ b/src/components/ha-select.ts @@ -47,6 +47,10 @@ export class HaSelect extends SelectBase { .mdc-select__anchor { width: var(--ha-select-min-width, 200px); } + :host-context([style*="direction: rtl;"]) .mdc-floating-label { + right: 16px !important; + left: initial !important; + } `, ]; } diff --git a/src/components/ha-target-picker.ts b/src/components/ha-target-picker.ts index b1182779bb..825b828cd2 100644 --- a/src/components/ha-target-picker.ts +++ b/src/components/ha-target-picker.ts @@ -616,6 +616,10 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) { opacity: var(--light-disabled-opacity); pointer-events: none; } + :host-context([style*="direction: rtl;"]) .mdc-chip__icon { + margin-right: -14px !important; + margin-left: 4px !important; + } `; } } diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index 3da39e1647..0e2c5a8cf8 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -91,6 +91,19 @@ export class HaTextField extends TextFieldBase { .mdc-text-field { overflow: var(--text-field-overflow); } + + :host-context([style*="direction: rtl;"]) .mdc-floating-label { + right: 10px !important; + left: initial !important; + } + + :host-context([style*="direction: rtl;"]) + .mdc-text-field--with-leading-icon.mdc-text-field--filled + .mdc-floating-label { + max-width: calc(100% - 48px); + right: 48px !important; + left: initial !important; + } `, ]; } diff --git a/src/dialogs/config-flow/dialog-data-entry-flow.ts b/src/dialogs/config-flow/dialog-data-entry-flow.ts index 18de3e81d9..023807a439 100644 --- a/src/dialogs/config-flow/dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/dialog-data-entry-flow.ts @@ -11,7 +11,6 @@ import { } from "lit"; import { customElement, state } from "lit/decorators"; import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; -import { computeRTL } from "../../common/util/compute_rtl"; import "../../components/ha-circular-progress"; import "../../components/ha-dialog"; import "../../components/ha-icon-button"; @@ -261,7 +260,6 @@ class DataEntryFlowDialog extends LitElement { @@ -273,7 +271,6 @@ class DataEntryFlowDialog extends LitElement { )} .path=${mdiClose} dialogAction="close" - ?rtl=${computeRTL(this.hass)} >
${this._step === null @@ -521,7 +518,7 @@ class DataEntryFlowDialog extends LitElement { top: 0; right: 0; } - .dialog-actions[rtl] { + :host-context([style*="direction: rtl;"]) .dialog-actions { right: auto; left: 0; } diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 5ec2e499d8..1b0c8b4c1b 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -194,6 +194,10 @@ class StepFlowForm extends LitElement { word-break: break-word; padding-right: 72px; } + :host-context([style*="direction: rtl;"]) h2 { + padding-right: auto !important; + padding-left: 72px !important; + } `, ]; } diff --git a/src/dialogs/config-flow/step-flow-pick-flow.ts b/src/dialogs/config-flow/step-flow-pick-flow.ts index 2296d465a0..1b7d401e8e 100644 --- a/src/dialogs/config-flow/step-flow-pick-flow.ts +++ b/src/dialogs/config-flow/step-flow-pick-flow.ts @@ -106,6 +106,10 @@ class StepFlowPickFlow extends LitElement { h2 { padding-right: 66px; } + :host-context([style*="direction: rtl;"]) h2 { + padding-right: auto !important; + padding-left: 66px !important; + } @media all and (max-height: 900px) { div { max-height: calc(100vh - 134px); diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index e40158dd97..c71ccb338f 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -313,6 +313,10 @@ class StepFlowPickHandler extends LitElement { h2 { padding-right: 66px; } + :host-context([style*="direction: rtl;"]) h2 { + padding-right: auto !important; + padding-left: 66px !important; + } @media all and (max-height: 900px) { mwc-list { max-height: calc(100vh - 134px); diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 40e9b4764f..2b45d064f9 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -414,7 +414,7 @@ export default class HaAutomationActionRow extends LitElement { z-index: 3; --mdc-theme-text-primary-on-background: var(--primary-text-color); } - .rtl .card-menu { + :host-context([style*="direction: rtl;"]) .card-menu { right: initial; left: 16px; } diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 8585e232e9..7024771aec 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -245,7 +245,7 @@ export default class HaAutomationConditionRow extends LitElement { display: flex; align-items: center; } - .rtl .card-menu { + :host-context([style*="direction: rtl;"]) .card-menu { float: left; } mwc-list-item[disabled] { diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 13254779b1..b10e3a63ee 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -442,7 +442,7 @@ export default class HaAutomationTriggerRow extends LitElement { z-index: 3; --mdc-theme-text-primary-on-background: var(--primary-text-color); } - .rtl .card-menu { + :host-context([style*="direction: rtl;"]) .card-menu { float: left; } .triggered { diff --git a/src/panels/config/integrations/ha-integration-header.ts b/src/panels/config/integrations/ha-integration-header.ts index 72d40619be..05506898cc 100644 --- a/src/panels/config/integrations/ha-integration-header.ts +++ b/src/panels/config/integrations/ha-integration-header.ts @@ -143,6 +143,10 @@ export class HaIntegrationHeader extends LitElement { width: 40px; height: 40px; } + :host-context([style*="direction: rtl;"]) .header img { + margin-right: auto !important; + margin-left: 16px; + } .header .info { flex: 1; align-self: center; diff --git a/src/panels/config/logs/dialog-system-log-detail.ts b/src/panels/config/logs/dialog-system-log-detail.ts index 2765ffa532..f73f0b8f43 100644 --- a/src/panels/config/logs/dialog-system-log-detail.ts +++ b/src/panels/config/logs/dialog-system-log-detail.ts @@ -228,6 +228,7 @@ class DialogSystemLogDetail extends LitElement { .contents { padding: 16px; outline: none; + direction: ltr; } .error { color: var(--error-color); From ba2958ecd2b23ca77d98f36009ef25548e8e09da Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Fri, 22 Apr 2022 20:17:42 +0200 Subject: [PATCH 043/181] zwave_js: Add title tag to config box heading (#12387) --- .../integration-panels/zwave_js/zwave_js-node-config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index e6d2e52857..e5d0142239 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -214,7 +214,9 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
${item.property} - ${item.metadata.label} + + ${item.metadata.label} + ${item.metadata.description} ${item.metadata.description !== null && !item.metadata.writeable From 269ef370e478e22436fc6d67d219935d7c865434 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Fri, 22 Apr 2022 12:23:34 -0700 Subject: [PATCH 044/181] Accept new value when hitting ENTER to close a prompt dialog (#12360) Co-authored-by: Zack Barett --- src/dialogs/generic/dialog-box.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index d57508e3cc..1e8aae7406 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -77,7 +77,7 @@ class DialogBox extends LitElement { dialogInitialFocus .value=${this._value || ""} @keyup=${this._handleKeyUp} - @change=${this._valueChanged} + @input=${this._valueChanged} .label=${this._params.inputLabel ? this._params.inputLabel : ""} From 558ab9761d7cc16f6b0dcd7b6557c896930d5685 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Fri, 22 Apr 2022 23:19:38 +0300 Subject: [PATCH 045/181] RTL reading orders and alignments in system log (#12388) --- src/panels/config/logs/error-log-card.ts | 4 ++++ src/panels/config/logs/system-log-card.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index 3008ec2484..50dc5db3a9 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -84,6 +84,10 @@ class ErrorLogCard extends LitElement { .warning { color: var(--warning-color); } + + :host-context([style*="direction: rtl;"]) mwc-button { + direction: rtl; + } `; } diff --git a/src/panels/config/logs/system-log-card.ts b/src/panels/config/logs/system-log-card.ts index 373b797a06..112d61118d 100644 --- a/src/panels/config/logs/system-log-card.ts +++ b/src/panels/config/logs/system-log-card.ts @@ -204,6 +204,10 @@ export class SystemLogCard extends LitElement { .warning { color: var(--warning-color); } + + :host-context([style*="direction: rtl;"]) .card-actions { + direction: rtl; + } `; } } From 36d30266e30cff522a6c7efd6f4880ac831e9656 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 22 Apr 2022 14:53:45 -0700 Subject: [PATCH 046/181] Add automation editor for calendar trigger (#12343) --- src/data/automation.ts | 9 ++- .../trigger/ha-automation-trigger-row.ts | 2 + .../types/ha-automation-trigger-calendar.ts | 73 +++++++++++++++++++ src/translations/en.json | 5 ++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts diff --git a/src/data/automation.ts b/src/data/automation.ts index dda0711a2f..23158f6566 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -152,6 +152,12 @@ export interface EventTrigger extends BaseTrigger { context?: ContextConstraint; } +export interface CalendarTrigger extends BaseTrigger { + platform: "calendar"; + event: "start"; + entity_id: string; +} + export type Trigger = | StateTrigger | MqttTrigger @@ -166,7 +172,8 @@ export type Trigger = | TimeTrigger | TemplateTrigger | EventTrigger - | DeviceTrigger; + | DeviceTrigger + | CalendarTrigger; interface BaseCondition { condition: string; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index b10e3a63ee..cb821f6758 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -28,6 +28,7 @@ import { } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; +import "./types/ha-automation-trigger-calendar"; import "./types/ha-automation-trigger-device"; import "./types/ha-automation-trigger-event"; import "./types/ha-automation-trigger-geo_location"; @@ -44,6 +45,7 @@ import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; const OPTIONS = [ + "calendar", "device", "event", "state", diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts new file mode 100644 index 0000000000..069e88794e --- /dev/null +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts @@ -0,0 +1,73 @@ +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { CalendarTrigger } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; +import type { TriggerElement } from "../ha-automation-trigger-row"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; + +@customElement("ha-automation-trigger-calendar") +export class HaCalendarTrigger extends LitElement implements TriggerElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public trigger!: CalendarTrigger; + + private _schema = memoizeOne((localize: LocalizeFunc) => [ + { + name: "entity_id", + required: true, + selector: { entity: { domain: "calendar" } }, + }, + { + name: "event", + type: "select", + required: true, + options: [ + [ + "start", + localize( + "ui.panel.config.automation.editor.triggers.type.calendar.start" + ), + ], + ], + }, + ]); + + public static get defaultConfig() { + return { + event: "start" as CalendarTrigger["event"], + }; + } + + protected render() { + const schema = this._schema(this.hass.localize); + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); + } + + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.calendar.${schema.name}` + ); +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-trigger-calendar": HaCalendarTrigger; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index e0ffddcfb8..f7c083cdf7 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1751,6 +1751,11 @@ "unsupported_platform": "No visual editor support for platform: {platform}", "type_select": "Trigger type", "type": { + "calendar": { + "label": "Calendar", + "event": "[%key:ui::panel::config::automation::editor::triggers::type::homeassistant::event%]", + "start": "Event Start" + }, "device": { "label": "Device", "trigger": "Trigger", From 958a1de2fdf1da9b0339a6f059c9d1bedfa479d3 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Fri, 22 Apr 2022 22:19:23 -0700 Subject: [PATCH 047/181] Add calendar event end trigger to automation editor (#12389) --- src/data/automation.ts | 2 +- .../trigger/types/ha-automation-trigger-calendar.ts | 6 ++++++ src/translations/en.json | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index 23158f6566..a73e0b1fb5 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -154,7 +154,7 @@ export interface EventTrigger extends BaseTrigger { export interface CalendarTrigger extends BaseTrigger { platform: "calendar"; - event: "start"; + event: "start" | "end"; entity_id: string; } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts index 069e88794e..8a458434aa 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-calendar.ts @@ -31,6 +31,12 @@ export class HaCalendarTrigger extends LitElement implements TriggerElement { "ui.panel.config.automation.editor.triggers.type.calendar.start" ), ], + [ + "end", + localize( + "ui.panel.config.automation.editor.triggers.type.calendar.end" + ), + ], ], }, ]); diff --git a/src/translations/en.json b/src/translations/en.json index f7c083cdf7..a71198f227 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1754,7 +1754,8 @@ "calendar": { "label": "Calendar", "event": "[%key:ui::panel::config::automation::editor::triggers::type::homeassistant::event%]", - "start": "Event Start" + "start": "Event Start", + "end": "Event End" }, "device": { "label": "Device", From 70836597e951ca842a4b7def98a6f9d006d0dd2b Mon Sep 17 00:00:00 2001 From: yangqian Date: Sat, 23 Apr 2022 14:32:59 +0800 Subject: [PATCH 048/181] Show vacuum state in more-info dialog for StateVacuumEntity (#12391) --- src/dialogs/more-info/controls/more-info-vacuum.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 852a41b98a..e53c4e9118 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -119,7 +119,15 @@ class MoreInfoVacuum extends LitElement { "ui.dialogs.more_info_control.vacuum.status" )}: - ${stateObj.attributes.status} + + + ${stateObj.attributes.status || + this.hass.localize( + `component.vacuum.state._.${stateObj.state}` + ) || + stateObj.state} + +
` : ""} From c5aac3b81d981857f22b670f4d74140e5c15d0f0 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Sat, 23 Apr 2022 10:11:44 -0500 Subject: [PATCH 049/181] Add Empty list item for None (#12356) --- src/panels/config/entities/entity-registry-settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index eabbf8fbaf..69a4b5e7b2 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -276,6 +276,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { @selected=${this._deviceClassChanged} @closed=${stopPropagation} > + ${this._deviceClassesSorted( domain, this._deviceClassOptions[0], From 28f1b6bdf44ad14cd09ac429e1bc7628fd338599 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Sun, 24 Apr 2022 03:48:17 +0300 Subject: [PATCH 050/181] Force LTR on time & number inputs (#12393) --- src/components/ha-base-time-input.ts | 1 + src/components/ha-selector/ha-selector-number.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index 9b35e60ca2..f1efcf9df3 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -310,6 +310,7 @@ export class HaBaseTimeInput extends LitElement { border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; overflow: hidden; position: relative; + direction: ltr; } ha-textfield { width: 40px; diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 6fa656635e..43ae1430ee 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -107,6 +107,7 @@ export class HaNumberSelector extends LitElement { display: flex; justify-content: space-between; align-items: center; + direction: ltr; } ha-slider { flex: 1; From bd339fa96324b89da5475193b059cc4f1fa3a128 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Sun, 24 Apr 2022 16:55:04 -0500 Subject: [PATCH 051/181] Fix Dashboard URLs (#12394) --- src/panels/lovelace/hui-root.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index e3525383ed..0f92dcec8e 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -7,8 +7,8 @@ import { mdiFileMultiple, mdiFormatListBulletedTriangle, mdiHelp, - mdiMagnify, mdiHelpCircle, + mdiMagnify, mdiMicrophone, mdiPencil, mdiPlus, @@ -29,7 +29,7 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { property, state, query } from "lit/decorators"; +import { property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; @@ -61,6 +61,7 @@ import { showAlertDialog, showConfirmationDialog, } from "../../dialogs/generic/show-dialog-box"; +import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar"; import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; import "../../layouts/ha-app-layout"; import type { haAppLayout } from "../../layouts/ha-app-layout"; @@ -73,7 +74,6 @@ import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog"; import type { Lovelace } from "./types"; import "./views/hui-view"; import type { HUIView } from "./views/hui-view"; -import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar"; class HUIRoot extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -557,7 +557,8 @@ class HUIRoot extends LitElement { let newSelectView; let force = false; - const viewPath = decodeURI(this.route!.path.split("/")[1]); + let viewPath: string | undefined = this.route!.path.split("/")[1]; + viewPath = viewPath ? decodeURI(viewPath) : undefined; if (changedProperties.has("route")) { const views = this.config.views; From 3677c5be2c388048ec344f504ed456faa5f97202 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Sun, 24 Apr 2022 17:55:31 -0400 Subject: [PATCH 052/181] Update zwavejs controller model (#12390) --- src/data/zwave_js.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index a1dc54f852..f11b5db8db 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -127,7 +127,7 @@ export interface ZWaveJSClient { export interface ZWaveJSController { home_id: number; - library_version: string; + sdk_version: string; type: number; own_node_id: number; is_secondary: boolean; @@ -136,7 +136,7 @@ export interface ZWaveJSController { was_real_primary: boolean; is_static_update_controller: boolean; is_slave: boolean; - serial_api_version: string; + firmware_version: string; manufacturer_id: number; product_id: number; product_type: number; From 9706c56c5c8c6425f0b013a4692f19d8deb49927 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Sun, 24 Apr 2022 17:26:01 -0500 Subject: [PATCH 053/181] Configuration Menu Updates 3 (#12377) --- src/components/currency-datalist.ts | 320 ++++++++-------- src/components/ha-metric.ts | 77 ++++ src/components/ha-navigation-list.ts | 4 +- .../config/blueprint/ha-blueprint-overview.ts | 2 +- src/panels/config/core/ha-config-core-form.ts | 342 ------------------ src/panels/config/core/ha-config-core.js | 57 --- src/panels/config/core/ha-config-name-form.ts | 97 ----- src/panels/config/core/ha-config-network.ts | 2 +- .../config/core/ha-config-section-storage.ts | 94 ++++- .../config/core/ha-config-section-updates.ts | 137 +++++++ .../core/ha-config-system-navigation.ts | 6 +- .../config/dashboard/ha-config-navigation.ts | 12 +- .../config/dashboard/ha-config-updates.ts | 10 +- src/panels/config/ha-panel-config.ts | 41 +-- .../ha-config-lovelace-dashboards.ts | 34 +- .../config/lovelace/ha-config-lovelace.ts | 7 - src/panels/config/users/ha-config-users.ts | 2 +- .../config/zone/dialog-core-zone-detail.ts | 278 ++++++++++++++ src/panels/config/zone/ha-config-zone.ts | 30 +- .../zone/show-dialog-core-zone-detail.ts | 12 + src/translations/en.json | 55 +-- 21 files changed, 860 insertions(+), 759 deletions(-) create mode 100644 src/components/ha-metric.ts delete mode 100644 src/panels/config/core/ha-config-core-form.ts delete mode 100644 src/panels/config/core/ha-config-core.js delete mode 100644 src/panels/config/core/ha-config-name-form.ts create mode 100644 src/panels/config/core/ha-config-section-updates.ts create mode 100644 src/panels/config/zone/dialog-core-zone-detail.ts create mode 100644 src/panels/config/zone/show-dialog-core-zone-detail.ts diff --git a/src/components/currency-datalist.ts b/src/components/currency-datalist.ts index a5ec24d762..69a69e7222 100644 --- a/src/components/currency-datalist.ts +++ b/src/components/currency-datalist.ts @@ -1,165 +1,167 @@ +export const currencies = [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BYR", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "INR", + "IQD", + "IRR", + "ISK", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LTL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STD", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMK", + "ZWL", +]; + export const createCurrencyListEl = () => { const list = document.createElement("datalist"); list.id = "currencies"; - for (const currency of [ - "AED", - "AFN", - "ALL", - "AMD", - "ANG", - "AOA", - "ARS", - "AUD", - "AWG", - "AZN", - "BAM", - "BBD", - "BDT", - "BGN", - "BHD", - "BIF", - "BMD", - "BND", - "BOB", - "BRL", - "BSD", - "BTN", - "BWP", - "BYN", - "BYR", - "BZD", - "CAD", - "CDF", - "CHF", - "CLP", - "CNY", - "COP", - "CRC", - "CUP", - "CVE", - "CZK", - "DJF", - "DKK", - "DOP", - "DZD", - "EGP", - "ERN", - "ETB", - "EUR", - "FJD", - "FKP", - "GBP", - "GEL", - "GHS", - "GIP", - "GMD", - "GNF", - "GTQ", - "GYD", - "HKD", - "HNL", - "HRK", - "HTG", - "HUF", - "IDR", - "ILS", - "INR", - "IQD", - "IRR", - "ISK", - "JMD", - "JOD", - "JPY", - "KES", - "KGS", - "KHR", - "KMF", - "KPW", - "KRW", - "KWD", - "KYD", - "KZT", - "LAK", - "LBP", - "LKR", - "LRD", - "LSL", - "LTL", - "LYD", - "MAD", - "MDL", - "MGA", - "MKD", - "MMK", - "MNT", - "MOP", - "MRO", - "MUR", - "MVR", - "MWK", - "MXN", - "MYR", - "MZN", - "NAD", - "NGN", - "NIO", - "NOK", - "NPR", - "NZD", - "OMR", - "PAB", - "PEN", - "PGK", - "PHP", - "PKR", - "PLN", - "PYG", - "QAR", - "RON", - "RSD", - "RUB", - "RWF", - "SAR", - "SBD", - "SCR", - "SDG", - "SEK", - "SGD", - "SHP", - "SLL", - "SOS", - "SRD", - "SSP", - "STD", - "SYP", - "SZL", - "THB", - "TJS", - "TMT", - "TND", - "TOP", - "TRY", - "TTD", - "TWD", - "TZS", - "UAH", - "UGX", - "USD", - "UYU", - "UZS", - "VEF", - "VND", - "VUV", - "WST", - "XAF", - "XCD", - "XOF", - "XPF", - "YER", - "ZAR", - "ZMK", - "ZWL", - ]) { + for (const currency of currencies) { const option = document.createElement("option"); option.value = currency; option.innerHTML = currency; diff --git a/src/components/ha-metric.ts b/src/components/ha-metric.ts new file mode 100644 index 0000000000..2c576b9fde --- /dev/null +++ b/src/components/ha-metric.ts @@ -0,0 +1,77 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { roundWithOneDecimal } from "../util/calculate"; +import "./ha-bar"; +import "./ha-settings-row"; + +@customElement("ha-metric") +class HaMetric extends LitElement { + @property({ type: Number }) public value!: number; + + @property({ type: String }) public description!: string; + + @property({ type: String }) public tooltip?: string; + + protected render(): TemplateResult { + const roundedValue = roundWithOneDecimal(this.value); + return html` + ${this.description} +
+ ${roundedValue} % + 50, + "target-critical": roundedValue > 85, + })} + .value=${this.value} + > +
+
`; + } + + static get styles(): CSSResultGroup { + return css` + ha-settings-row { + padding: 0; + height: 54px; + width: 100%; + } + ha-settings-row > div[slot="description"] { + white-space: normal; + color: var(--secondary-text-color); + display: flex; + justify-content: space-between; + } + ha-bar { + --ha-bar-primary-color: var( + --metric-bar-ok-color, + var(--success-color) + ); + } + .target-warning { + --ha-bar-primary-color: var( + --metric-bar-warning-color, + var(--warning-color) + ); + } + .target-critical { + --ha-bar-primary-color: var( + --metric-bar-critical-color, + var(--error-color) + ); + } + .value { + width: 48px; + padding-right: 4px; + flex-shrink: 0; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-metric": HaMetric; + } +} diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index 734e4b8cf8..4336bb2b1e 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -4,9 +4,9 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import type { PageNavigation } from "../layouts/hass-tabs-subpage"; import type { HomeAssistant } from "../types"; +import "./ha-clickable-list-item"; import "./ha-icon-next"; import "./ha-svg-icon"; -import "./ha-clickable-list-item"; @customElement("ha-navigation-list") class HaNavigationList extends LitElement { @@ -78,7 +78,7 @@ class HaNavigationList extends LitElement { .icon-background ha-svg-icon { color: #fff; } - mwc-list-item { + ha-clickable-list-item { cursor: pointer; font-size: var(--navigation-list-item-title-font-size); } diff --git a/src/panels/config/blueprint/ha-blueprint-overview.ts b/src/panels/config/blueprint/ha-blueprint-overview.ts index 35fd53a523..0e65094222 100644 --- a/src/panels/config/blueprint/ha-blueprint-overview.ts +++ b/src/panels/config/blueprint/ha-blueprint-overview.ts @@ -224,7 +224,7 @@ class HaBlueprintOverview extends LitElement { .narrow=${this.narrow} back-path="/config" .route=${this.route} - .tabs=${configSections.blueprints} + .tabs=${configSections.automations} .columns=${this._columns(this.narrow, this.hass.language)} .data=${this._processedBlueprints(this.blueprints)} id="entity_id" diff --git a/src/panels/config/core/ha-config-core-form.ts b/src/panels/config/core/ha-config-core-form.ts deleted file mode 100644 index 495b963121..0000000000 --- a/src/panels/config/core/ha-config-core-form.ts +++ /dev/null @@ -1,342 +0,0 @@ -import "@material/mwc-button/mwc-button"; -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import memoizeOne from "memoize-one"; -import { UNIT_C } from "../../../common/const"; -import { createCurrencyListEl } from "../../../components/currency-datalist"; -import "../../../components/ha-card"; -import "../../../components/map/ha-locations-editor"; -import type { MarkerLocation } from "../../../components/map/ha-locations-editor"; -import { createTimezoneListEl } from "../../../components/timezone-datalist"; -import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; -import { SYMBOL_TO_ISO } from "../../../data/currency"; -import type { PolymerChangedEvent } from "../../../polymer-types"; -import type { HomeAssistant } from "../../../types"; -import "../../../components/ha-formfield"; -import "../../../components/ha-radio"; -import type { HaRadio } from "../../../components/ha-radio"; - -@customElement("ha-config-core-form") -class ConfigCoreForm extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _working = false; - - @state() private _location?: [number, number]; - - @state() private _currency?: string; - - @state() private _elevation?: string; - - @state() private _unitSystem?: ConfigUpdateValues["unit_system"]; - - @state() private _timeZone?: string; - - protected render(): TemplateResult { - const canEdit = ["storage", "default"].includes( - this.hass.config.config_source - ); - const disabled = this._working || !canEdit; - - return html` - -
- ${!canEdit - ? html` -

- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.edit_requires_storage" - )} -

- ` - : ""} - -
- -
- -
-
- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.time_zone" - )} -
- - -
-
-
- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.elevation" - )} -
- - - - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.elevation_meters" - )} - - -
- -
-
- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.unit_system" - )} -
-
- - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.metric_example" - )} -
`} - > - - - - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.imperial_example" - )} -
`} - > - - -
-
-
-
- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.currency" - )}
- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.find_currency_value" - )} -
- - -
-
-
- - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.save_button" - )} - -
- - `; - } - - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - - const tzInput = this.shadowRoot!.querySelector( - "[name=timeZone]" - ) as PaperInputElement; - tzInput.inputElement.appendChild(createTimezoneListEl()); - - const cInput = this.shadowRoot!.querySelector( - "[name=currency]" - ) as PaperInputElement; - cInput.inputElement.appendChild(createCurrencyListEl()); - } - - private _markerLocation = memoizeOne( - ( - lat: number, - lng: number, - location?: [number, number] - ): MarkerLocation[] => [ - { - id: "location", - latitude: location ? location[0] : lat, - longitude: location ? location[1] : lng, - location_editable: true, - }, - ] - ); - - private get _currencyValue() { - return this._currency !== undefined - ? this._currency - : this.hass.config.currency; - } - - private get _elevationValue() { - return this._elevation !== undefined - ? this._elevation - : this.hass.config.elevation; - } - - private get _timeZoneValue() { - return this._timeZone !== undefined - ? this._timeZone - : this.hass.config.time_zone; - } - - private get _unitSystemValue() { - return this._unitSystem !== undefined - ? this._unitSystem - : this.hass.config.unit_system.temperature === UNIT_C - ? "metric" - : "imperial"; - } - - private _handleChange(ev: PolymerChangedEvent) { - const target = ev.currentTarget as PaperInputElement; - let value = target.value; - - if (target.name === "currency" && value) { - if (value in SYMBOL_TO_ISO) { - value = SYMBOL_TO_ISO[value]; - } - } - - this[`_${target.name}`] = value; - } - - private _locationChanged(ev) { - this._location = ev.detail.location; - } - - private _unitSystemChanged(ev: CustomEvent) { - this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial"; - } - - private async _save() { - this._working = true; - try { - const location = this._location || [ - this.hass.config.latitude, - this.hass.config.longitude, - ]; - await saveCoreConfig(this.hass, { - latitude: location[0], - longitude: location[1], - currency: this._currencyValue, - elevation: Number(this._elevationValue), - unit_system: this._unitSystemValue, - time_zone: this._timeZoneValue, - }); - } catch (err: any) { - alert(`Error saving config: ${err.message}`); - } finally { - this._working = false; - } - } - - static get styles(): CSSResultGroup { - return css` - .row { - display: flex; - flex-direction: row; - margin: 0 -8px; - align-items: center; - } - - .secondary { - color: var(--secondary-text-color); - } - - .flex { - flex: 1; - } - - .row > * { - margin: 0 8px; - } - - .radio-group { - display: flex; - flex-direction: column; - flex: 1; - } - - .card-actions { - text-align: right; - } - - a { - color: var(--primary-color); - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-core-form": ConfigCoreForm; - } -} diff --git a/src/panels/config/core/ha-config-core.js b/src/panels/config/core/ha-config-core.js deleted file mode 100644 index b76dd9b678..0000000000 --- a/src/panels/config/core/ha-config-core.js +++ /dev/null @@ -1,57 +0,0 @@ -import "@polymer/app-layout/app-header/app-header"; -import "@polymer/app-layout/app-toolbar/app-toolbar"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../layouts/hass-subpage"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import "../../../styles/polymer-ha-style"; -import "./ha-config-core-form"; -import "./ha-config-name-form"; - -/* - * @appliesMixin LocalizeMixin - */ -class HaConfigCore extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - - -
- - -
-
- `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - showAdvanced: Boolean, - route: Object, - }; - } -} - -customElements.define("ha-config-core", HaConfigCore); diff --git a/src/panels/config/core/ha-config-name-form.ts b/src/panels/config/core/ha-config-name-form.ts deleted file mode 100644 index c380c37901..0000000000 --- a/src/panels/config/core/ha-config-name-form.ts +++ /dev/null @@ -1,97 +0,0 @@ -import "@material/mwc-button/mwc-button"; -import { css, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import "../../../components/ha-card"; -import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; -import type { HomeAssistant } from "../../../types"; -import "../../../components/ha-textfield"; -import type { HaTextField } from "../../../components/ha-textfield"; - -@customElement("ha-config-name-form") -class ConfigNameForm extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _working = false; - - @state() private _name!: ConfigUpdateValues["location_name"]; - - protected render(): TemplateResult { - const canEdit = ["storage", "default"].includes( - this.hass.config.config_source - ); - const disabled = this._working || !canEdit; - - return html` - -
- ${!canEdit - ? html` -

- ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.edit_requires_storage" - )} -

- ` - : ""} - -
-
- - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.save_button" - )} - -
-
- `; - } - - private get _nameValue() { - return this._name !== undefined - ? this._name - : this.hass.config.location_name; - } - - private _handleChange(ev) { - const target = ev.currentTarget as HaTextField; - this._name = target.value; - } - - private async _save() { - this._working = true; - try { - await saveCoreConfig(this.hass, { - location_name: this._nameValue, - }); - } catch (err: any) { - alert("FAIL"); - } finally { - this._working = false; - } - } - - static get styles() { - return css` - .card-actions { - text-align: right; - } - ha-textfield { - display: block; - } - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-name-form": ConfigNameForm; - } -} diff --git a/src/panels/config/core/ha-config-network.ts b/src/panels/config/core/ha-config-network.ts index 42cecfe606..5ac9ff5cf6 100644 --- a/src/panels/config/core/ha-config-network.ts +++ b/src/panels/config/core/ha-config-network.ts @@ -40,7 +40,7 @@ class ConfigNetwork extends LitElement { } return html` - +
${this._error ? html` diff --git a/src/panels/config/core/ha-config-section-storage.ts b/src/panels/config/core/ha-config-section-storage.ts index 6e3a96f140..b40eaa4d44 100644 --- a/src/panels/config/core/ha-config-section-storage.ts +++ b/src/panels/config/core/ha-config-section-storage.ts @@ -1,7 +1,17 @@ -import { css, html, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import "../../../components/ha-alert"; +import "../../../components/ha-bar"; +import "../../../components/ha-metric"; +import { fetchHassioHostInfo, HassioHostInfo } from "../../../data/hassio/host"; import "../../../layouts/hass-subpage"; import type { HomeAssistant, Route } from "../../../types"; +import { + getValueInPercentage, + roundWithOneDecimal, +} from "../../../util/calculate"; import "./ha-config-analytics"; @customElement("ha-config-section-storage") @@ -12,6 +22,17 @@ class HaConfigSectionStorage extends LitElement { @property({ type: Boolean }) public narrow!: boolean; + @state() private _error?: { code: string; message: string }; + + @state() private _storageData?: HassioHostInfo; + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + if (isComponentLoaded(this.hass, "hassio")) { + this._load(); + } + } + protected render(): TemplateResult { return html` -
+
+ ${this._error + ? html` + ${this._error.message || this._error.code} + ` + : ""} + ${this._storageData + ? html` + + + ${this._storageData.disk_life_time !== "" && + this._storageData.disk_life_time >= 10 + ? html` + + ` + : ""} + + ` + : ""} +
`; } + private async _load() { + this._error = undefined; + try { + if (isComponentLoaded(this.hass, "hassio")) { + this._storageData = await fetchHassioHostInfo(this.hass); + } + } catch (err: any) { + this._error = err.message || err; + } + } + + private _getUsedSpace = memoizeOne((used: number, total: number) => + roundWithOneDecimal(getValueInPercentage(used, 0, total)) + ); + static styles = css` .content { padding: 28px 20px 0; max-width: 1040px; margin: 0 auto; } + ha-card { + padding: 16px; + max-width: 500px; + margin: 0 auto; + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + .emmc { + --metric-bar-ok-color: #000; + } `; } diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts new file mode 100644 index 0000000000..0853566da3 --- /dev/null +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -0,0 +1,137 @@ +import { HassEntities } from "home-assistant-js-websocket"; +import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { computeStateDomain } from "../../../common/entity/compute_state_domain"; +import { caseInsensitiveStringCompare } from "../../../common/string/compare"; +import "../../../components/ha-alert"; +import "../../../components/ha-bar"; +import "../../../components/ha-metric"; +import { updateCanInstall, UpdateEntity } from "../../../data/update"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant } from "../../../types"; +import { showToast } from "../../../util/toast"; +import "../dashboard/ha-config-updates"; +import "./ha-config-analytics"; + +@customElement("ha-config-section-updates") +class HaConfigSectionUpdates extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + private _notifyUpdates = false; + + protected render(): TemplateResult { + const canInstallUpdates = this._filterUpdateEntitiesWithInstall( + this.hass.states + ); + + return html` + +
+ + ${canInstallUpdates.length + ? html` + + ` + : html` + ${this.hass.localize("ui.panel.config.updates.no_updates")} + `} + +
+
+ `; + } + + protected override updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + if (!changedProps.has("hass") || !this._notifyUpdates) { + return; + } + this._notifyUpdates = false; + if (this._filterUpdateEntitiesWithInstall(this.hass.states).length) { + showToast(this, { + message: this.hass.localize( + "ui.panel.config.updates.updates_refreshed" + ), + }); + } else { + showToast(this, { + message: this.hass.localize("ui.panel.config.updates.no_new_updates"), + }); + } + } + + private _filterUpdateEntities = memoizeOne((entities: HassEntities) => + ( + Object.values(entities).filter( + (entity) => computeStateDomain(entity) === "update" + ) as UpdateEntity[] + ).sort((a, b) => { + if (a.attributes.title === "Home Assistant Core") { + return -3; + } + if (b.attributes.title === "Home Assistant Core") { + return 3; + } + if (a.attributes.title === "Home Assistant Operating System") { + return -2; + } + if (b.attributes.title === "Home Assistant Operating System") { + return 2; + } + if (a.attributes.title === "Home Assistant Supervisor") { + return -1; + } + if (b.attributes.title === "Home Assistant Supervisor") { + return 1; + } + return caseInsensitiveStringCompare( + a.attributes.title || a.attributes.friendly_name || "", + b.attributes.title || b.attributes.friendly_name || "" + ); + }) + ); + + private _filterUpdateEntitiesWithInstall = memoizeOne( + (entities: HassEntities) => + this._filterUpdateEntities(entities).filter((entity) => + updateCanInstall(entity) + ) + ); + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + ha-card { + padding: 16px; + max-width: 500px; + margin: 0 auto; + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-section-updates": HaConfigSectionUpdates; + } +} diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 0db85573b0..8792ef009a 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -33,7 +33,7 @@ class HaConfigSystemNavigation extends LitElement { return html` ${this.narrow ? html`
- ${this.hass.localize( - "ui.panel.config.dashboard.system.title" - )} + ${this.hass.localize("ui.panel.config.dashboard.system.main")}
` : ""} ` )} - ${!this._showAll && this.updateEntities.length >= 4 + ${!this.showAll && this.updateEntities.length >= 4 ? html`
`} + > + + +
+
+ + ${currencies.map( + (currency) => + html`${currency}` + )} + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.find_currency_value" + )} +
+ + ${this.hass!.localize("ui.panel.config.zone.detail.update")} + + + `; + } + + private _handleChange(ev) { + const target = ev.currentTarget; + let value = target.value; + + if (target.name === "currency" && value) { + if (value in SYMBOL_TO_ISO) { + value = SYMBOL_TO_ISO[value]; + } + } + + this[`_${target.name}`] = value; + } + + private _unitSystemChanged(ev: CustomEvent) { + this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial"; + } + + private async _updateEntry() { + this._submitting = true; + try { + await saveCoreConfig(this.hass, { + currency: this._currency, + elevation: Number(this._elevation), + unit_system: this._unitSystem, + time_zone: this._timeZone, + location_name: this._name, + }); + } catch (err: any) { + alert(`Error saving config: ${err.message}`); + } finally { + this._submitting = false; + } + + this.closeDialog(); + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + ha-dialog { + --mdc-dialog-min-width: 600px; + } + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-dialog { + --mdc-dialog-min-width: calc( + 100vw - env(safe-area-inset-right) - env(safe-area-inset-left) + ); + } + } + .card-actions { + text-align: right; + } + ha-dialog > * { + display: block; + margin-top: 16px; + } + ha-select { + display: block; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-core-zone-detail": DialogZoneDetail; + } +} diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 23db9af09f..e5802d069e 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -13,7 +13,6 @@ import { TemplateResult, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { navigate } from "../../../common/navigate"; @@ -44,6 +43,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant, Route } from "../../../types"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; +import { showCoreZoneDetailDialog } from "./show-dialog-core-zone-detail"; import { showZoneDetailDialog } from "./show-dialog-zone-detail"; @customElement("ha-config-zone") @@ -186,15 +186,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { + import("./dialog-core-zone-detail"); + +export const showCoreZoneDetailDialog = (element: HTMLElement): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-core-zone-detail", + dialogImport: loadCoreZoneDetailDialog, + dialogParams: {}, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index a71198f227..da380f5dfc 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1066,52 +1066,52 @@ "header": "Configure Home Assistant", "dashboard": { "devices": { - "title": "Devices & Services", - "description": "Integrations, devices, entities and helpers" + "main": "Devices & Services", + "secondary": "Integrations, devices, entities and helpers" }, "automations": { - "title": "Automations & Scenes", - "description": "Manage automations, scenes, scripts and blueprints" + "main": "Automations & Scenes", + "secondary": "Manage automations, scenes, scripts and blueprints" }, "backup": { - "title": "Backup", - "description": "Generate backups of your Home Assistant configuration" + "main": "Backup", + "secondary": "Generate backups of your Home Assistant configuration" }, "supervisor": { - "title": "Add-ons", - "description": "Extend the function around Home Assistant" + "main": "Add-ons", + "secondary": "Extend the function around Home Assistant" }, "dashboards": { - "title": "Dashboards", - "description": "Create customized sets of cards to control your home" + "main": "Dashboards", + "secondary": "Create customized sets of cards to control your home" }, "energy": { - "title": "Energy", - "description": "Monitor your energy production and consumption" + "main": "Energy", + "secondary": "Monitor your energy production and consumption" }, "tags": { - "title": "Tags", - "description": "Trigger automations when an NFC tag, QR code, etc. is scanned" + "main": "Tags", + "secondary": "Trigger automations when an NFC tag, QR code, etc. is scanned" }, "people": { - "title": "People", - "description": "Manage the people that Home Assistant tracks" + "main": "People", + "secondary": "Manage the people that Home Assistant tracks" }, "areas": { - "title": "Areas & Zones", - "description": "Manage areas & zones that Home Assistant tracks" + "main": "Areas & Zones", + "secondary": "Manage areas and zones that Home Assistant tracks" }, "companion": { - "title": "Companion App", - "description": "Location and notifications" + "main": "Companion App", + "secondary": "Location and notifications" }, "system": { - "title": "System", - "description": "Create backups, check logs or reboot your system" + "main": "System", + "secondary": "Create backups, check logs or reboot your system" }, "about": { - "title": "About", - "description": "Version, system health and links to documentation" + "main": "About", + "secondary": "Version, system health and links to documentation" } }, "common": { @@ -1122,6 +1122,7 @@ }, "updates": { "caption": "Updates", + "no_updates": "No updates available", "no_update_entities": { "title": "Unable to check for updates", "description": "You do not have any integrations that provide updates." @@ -1778,7 +1779,7 @@ "geo_location": { "label": "Geolocation", "source": "Source", - "zone": "Location", + "zone": "Zone", "event": "Event", "enter": "Enter", "leave": "Leave" @@ -3109,7 +3110,9 @@ "caption": "Network" }, "storage": { - "caption": "Storage" + "caption": "Storage", + "used_space": "Used Space", + "emmc_lifetime_used": "eMMC Lifetime Used" } }, "lovelace": { From dfe348187f49dd7ac235492dfd42f54cf85b3d88 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 24 Apr 2022 15:26:42 -0700 Subject: [PATCH 054/181] Bumped version to 20220424.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9c9dfb640e..f397c0f797 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220420.0 +version = 20220424.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From f69eb15a90c3a1f4deee2e3350806fe6ab94a0f3 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Sun, 24 Apr 2022 22:25:47 -0500 Subject: [PATCH 055/181] Config Menu: Addressing Comments in #12377 (#12399) --- src/components/ha-metric.ts | 30 ++++++++++--------- .../config/core/ha-config-section-storage.ts | 15 ++++------ .../config/zone/dialog-core-zone-detail.ts | 2 ++ src/panels/config/zone/ha-config-zone.ts | 24 ++++++++++----- src/translations/en.json | 1 + 5 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/components/ha-metric.ts b/src/components/ha-metric.ts index 2c576b9fde..77bf266941 100644 --- a/src/components/ha-metric.ts +++ b/src/components/ha-metric.ts @@ -9,25 +9,27 @@ import "./ha-settings-row"; class HaMetric extends LitElement { @property({ type: Number }) public value!: number; - @property({ type: String }) public description!: string; + @property({ type: String }) public heading!: string; @property({ type: String }) public tooltip?: string; protected render(): TemplateResult { const roundedValue = roundWithOneDecimal(this.value); - return html` - ${this.description} -
- ${roundedValue} % - 50, - "target-critical": roundedValue > 85, - })} - .value=${this.value} - > -
-
`; + return html` + + ${this.heading} +
+ ${roundedValue} % + 50, + "target-critical": roundedValue > 85, + })} + .value=${this.value} + > +
+
+ `; } static get styles(): CSSResultGroup { diff --git a/src/panels/config/core/ha-config-section-storage.ts b/src/panels/config/core/ha-config-section-storage.ts index b40eaa4d44..0d92424c96 100644 --- a/src/panels/config/core/ha-config-section-storage.ts +++ b/src/panels/config/core/ha-config-section-storage.ts @@ -1,6 +1,5 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-alert"; import "../../../components/ha-bar"; @@ -52,7 +51,7 @@ class HaConfigSectionStorage extends LitElement { ? html` = 10 ? html` - roundWithOneDecimal(getValueInPercentage(used, 0, total)) - ); + private _getUsedSpace = (used: number, total: number) => + roundWithOneDecimal(getValueInPercentage(used, 0, total)); static styles = css` .content { diff --git a/src/panels/config/zone/dialog-core-zone-detail.ts b/src/panels/config/zone/dialog-core-zone-detail.ts index f72a959351..7c388bd964 100644 --- a/src/panels/config/zone/dialog-core-zone-detail.ts +++ b/src/panels/config/zone/dialog-core-zone-detail.ts @@ -9,8 +9,10 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { currencies } from "../../../components/currency-datalist"; import { createCloseHeading } from "../../../components/ha-dialog"; +import "../../../components/ha-formfield"; import { HaRadio } from "../../../components/ha-radio"; import "../../../components/ha-select"; +import "../../../components/ha-textfield"; import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; import { SYMBOL_TO_ISO } from "../../../data/currency"; import { haStyleDialog } from "../../../resources/styles"; diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index e5802d069e..7b7cc1373b 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -1,4 +1,4 @@ -import { mdiPencil, mdiPencilOff, mdiPlus } from "@mdi/js"; +import { mdiPencil, mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-listbox/paper-listbox"; @@ -36,7 +36,10 @@ import { Zone, ZoneMutableParams, } from "../../../data/zone"; -import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; @@ -186,12 +189,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { {}, + }); + return; + } showCoreZoneDetailDialog(this); } diff --git a/src/translations/en.json b/src/translations/en.json index da380f5dfc..c7fe10dbd8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2622,6 +2622,7 @@ "add_zone": "Add Zone", "edit_zone": "Edit Zone", "confirm_delete": "Are you sure you want to delete this zone?", + "can_not_edit": "Unable to edit zone", "configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.", "edit_home_zone": "The radius of the Home zone can't be edited from the frontend yet. Drag the marker on the map to move the home zone.", "edit_home_zone_narrow": "The radius of the Home zone can't be edited from the frontend yet. The location can be changed from the general configuration.", From 3e188d1f87125a19d36b53a4713feceb06ddfee3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 25 Apr 2022 01:00:28 -0700 Subject: [PATCH 056/181] Add shorthand condition to the gallery (#12400) --- .../src/pages/automation/editor-condition.ts | 12 +++++++++-- src/data/automation.ts | 20 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/gallery/src/pages/automation/editor-condition.ts b/gallery/src/pages/automation/editor-condition.ts index 77e42e6171..8f8ee17604 100644 --- a/gallery/src/pages/automation/editor-condition.ts +++ b/gallery/src/pages/automation/editor-condition.ts @@ -8,7 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; -import type { Condition } from "../../../../src/data/automation"; +import type { ConditionWithShorthand } from "../../../../src/data/automation"; import "../../../../src/panels/config/automation/condition/ha-automation-condition"; import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device"; import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical"; @@ -20,7 +20,7 @@ import { HaTimeCondition } from "../../../../src/panels/config/automation/condit import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger"; import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone"; -const SCHEMAS: { name: string; conditions: Condition[] }[] = [ +const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [ { name: "State", conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }], @@ -69,6 +69,14 @@ const SCHEMAS: { name: string; conditions: Condition[] }[] = [ name: "Trigger", conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }], }, + { + name: "Shorthand", + conditions: [ + { and: HaLogicalCondition.defaultConfig.conditions }, + { or: HaLogicalCondition.defaultConfig.conditions }, + { not: HaLogicalCondition.defaultConfig.conditions }, + ], + }, ]; @customElement("demo-automation-editor-condition") diff --git a/src/data/automation.ts b/src/data/automation.ts index a73e0b1fb5..4e8694b6fa 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -233,6 +233,20 @@ export interface TriggerCondition extends BaseCondition { id: string; } +type ShorthandBaseCondition = Omit; + +export interface ShorthandAndCondition extends ShorthandBaseCondition { + and: Condition[]; +} + +export interface ShorthandOrCondition extends ShorthandBaseCondition { + or: Condition[]; +} + +export interface ShorthandNotCondition extends ShorthandBaseCondition { + not: Condition[]; +} + export type Condition = | StateCondition | NumericStateCondition @@ -244,6 +258,12 @@ export type Condition = | LogicalCondition | TriggerCondition; +export type ConditionWithShorthand = + | Condition + | ShorthandAndCondition + | ShorthandOrCondition + | ShorthandNotCondition; + export const triggerAutomationActions = ( hass: HomeAssistant, entityId: string From 6b67546dafa9d4fa076917cb2d148b2fa1e1a655 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 05:32:50 -0500 Subject: [PATCH 057/181] Virtualize Media Player Grid (#11898) --- build-scripts/webpack.js | 6 +- .../dialog-media-player-browse.ts | 2 + .../media-player/ha-media-player-browse.ts | 783 +++++++++--------- 3 files changed, 400 insertions(+), 391 deletions(-) diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index 185ef3aa65..c5d741a5af 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -3,10 +3,10 @@ const webpack = require("webpack"); const path = require("path"); const TerserPlugin = require("terser-webpack-plugin"); const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); -const paths = require("./paths.js"); -const bundle = require("./bundle.js"); const log = require("fancy-log"); const WebpackBar = require("webpackbar"); +const paths = require("./paths.js"); +const bundle = require("./bundle.js"); class LogStartCompilePlugin { ignoredFirst = false; @@ -138,6 +138,8 @@ const createWebpackConfig = ({ "lit/directives/cache$": "lit/directives/cache.js", "lit/directives/repeat$": "lit/directives/repeat.js", "lit/polyfill-support$": "lit/polyfill-support.js", + "@lit-labs/virtualizer/layouts/grid": + "@lit-labs/virtualizer/layouts/grid.js", }, }, output: { diff --git a/src/components/media-player/dialog-media-player-browse.ts b/src/components/media-player/dialog-media-player-browse.ts index 01f7efbbd8..8a4f3f905e 100644 --- a/src/components/media-player/dialog-media-player-browse.ts +++ b/src/components/media-player/dialog-media-player-browse.ts @@ -151,6 +151,7 @@ class DialogMediaPlayerBrowse extends LitElement { ha-media-player-browse { --media-browser-max-height: calc(100vh - 65px); + height: calc(100vh - 65px); } @media (min-width: 800px) { @@ -163,6 +164,7 @@ class DialogMediaPlayerBrowse extends LitElement { ha-media-player-browse { position: initial; --media-browser-max-height: 100vh - 137px; + height: 100vh - 137px; width: 700px; } } diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 3de84c431d..5f219bf247 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -3,6 +3,8 @@ import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiArrowUpRight, mdiPlay, mdiPlus } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; +import { grid } from "@lit-labs/virtualizer/layouts/grid"; +import "@lit-labs/virtualizer"; import { css, CSSResultGroup, @@ -16,16 +18,13 @@ import { eventOptions, property, query, - queryAll, state, } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; -import { ifDefined } from "lit/directives/if-defined"; -import { styleMap } from "lit/directives/style-map"; +import { until } from "lit/directives/until"; import { fireEvent } from "../../common/dom/fire_event"; import { computeRTLDirection } from "../../common/util/compute_rtl"; import { debounce } from "../../common/util/debounce"; -import { getSignedPath } from "../../data/auth"; import type { MediaPlayerItem } from "../../data/media-player"; import { browseMediaPlayer, @@ -40,18 +39,18 @@ import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; -import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url"; import { documentationUrl } from "../../util/documentation-url"; import "../entity/ha-entity-picker"; import "../ha-button-menu"; import "../ha-card"; -import type { HaCard } from "../ha-card"; import "../ha-circular-progress"; import "../ha-fab"; import "../ha-icon-button"; import "../ha-svg-icon"; import "./ha-browse-media-tts"; import type { TtsMediaPickedEvent } from "./ha-browse-media-tts"; +import { getSignedPath } from "../../data/auth"; +import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url"; declare global { interface HASSDomEvents { @@ -101,8 +100,6 @@ export class HaMediaPlayerBrowse extends LitElement { @query(".content") private _content?: HTMLDivElement; - @queryAll(".lazythumbnail") private _thumbnails?: HaCard[]; - private _headerOffsetHeight = 0; private _resizeObserver?: ResizeObserver; @@ -148,326 +145,6 @@ export class HaMediaPlayerBrowse extends LitElement { } } - protected render(): TemplateResult { - if (this._error) { - return html` -
${this._renderError(this._error)}
- `; - } - - if (!this._currentItem) { - return html``; - } - - const currentItem = this._currentItem; - - const subtitle = this.hass.localize( - `ui.components.media-browser.class.${currentItem.media_class}` - ); - const children = currentItem.children || []; - const mediaClass = MediaClassBrowserSettings[currentItem.media_class]; - const childrenMediaClass = currentItem.children_media_class - ? MediaClassBrowserSettings[currentItem.children_media_class] - : MediaClassBrowserSettings.directory; - - return html` - ${ - currentItem.can_play - ? html`
-
- ${currentItem.thumbnail - ? html` -
- ${this._narrow && currentItem?.can_play - ? html` - - - ${this.hass.localize( - `ui.components.media-browser.${this.action}` - )} - - ` - : ""} -
- ` - : html``} -
- - ${currentItem.can_play && - (!currentItem.thumbnail || !this._narrow) - ? html` - - - ${this.hass.localize( - `ui.components.media-browser.${this.action}` - )} - - ` - : ""} -
-
-
` - : "" - } -
- ${ - this._error - ? html` -
- ${this._renderError(this._error)} -
- ` - : isTTSMediaSource(currentItem.media_content_id) - ? html` - - ` - : !children.length && !currentItem.not_shown - ? html` -
- ${currentItem.media_content_id === - "media-source://media_source/local/." - ? html` -
- - - - - ${this.hass.localize( - "ui.components.media-browser.file_management.highlight_button" - )} - -
- ` - : this.hass.localize( - "ui.components.media-browser.no_items" - )} -
- ` - : childrenMediaClass.layout === "grid" - ? html` -
- ${children.map( - (child) => html` -
- -
- ${child.thumbnail - ? html` -
- ` - : html` -
- -
- `} - ${child.can_play - ? html` - - ` - : ""} -
-
- ${child.title} - ${child.title} -
-
-
- ` - )} - ${currentItem.not_shown - ? html` -
-
- ${this.hass.localize( - "ui.components.media-browser.not_shown", - { count: currentItem.not_shown } - )} -
-
- ` - : ""} -
- ` - : html` - - ${children.map( - (child) => html` - -
- -
- ${child.title} -
-
  • - ` - )} - ${currentItem.not_shown - ? html` - - - ${this.hass.localize( - "ui.components.media-browser.not_shown", - { count: currentItem.not_shown } - )} - - - ` - : ""} -
    - ` - } -
    -
    -
    - `; - } - - protected firstUpdated(): void { - this._measureCard(); - this._attachResizeObserver(); - } - - protected shouldUpdate(changedProps: PropertyValues): boolean { - if (changedProps.size > 1 || !changedProps.has("hass")) { - return true; - } - const oldHass = changedProps.get("hass") as this["hass"]; - return oldHass === undefined || oldHass.localize !== this.hass.localize; - } - public willUpdate(changedProps: PropertyValues): void { super.willUpdate(changedProps); @@ -583,6 +260,19 @@ export class HaMediaPlayerBrowse extends LitElement { } } + protected shouldUpdate(changedProps: PropertyValues): boolean { + if (changedProps.size > 1 || !changedProps.has("hass")) { + return true; + } + const oldHass = changedProps.get("hass") as this["hass"]; + return oldHass === undefined || oldHass.localize !== this.hass.localize; + } + + protected firstUpdated(): void { + this._measureCard(); + this._attachResizeObserver(); + } + protected updated(changedProps: PropertyValues): void { super.updated(changedProps); @@ -590,16 +280,368 @@ export class HaMediaPlayerBrowse extends LitElement { this._animateHeaderHeight(); } else if (changedProps.has("_currentItem")) { this._setHeaderHeight(); - this._attachIntersectionObserver(); } } - private _actionClicked(ev: MouseEvent): void { + protected render(): TemplateResult { + if (this._error) { + return html` +
    ${this._renderError(this._error)}
    + `; + } + + if (!this._currentItem) { + return html``; + } + + const currentItem = this._currentItem; + + const subtitle = this.hass.localize( + `ui.components.media-browser.class.${currentItem.media_class}` + ); + const children = currentItem.children || []; + const mediaClass = MediaClassBrowserSettings[currentItem.media_class]; + const childrenMediaClass = currentItem.children_media_class + ? MediaClassBrowserSettings[currentItem.children_media_class] + : MediaClassBrowserSettings.directory; + + const backgroundImage = currentItem.thumbnail + ? this._getSignedThumbnail(currentItem.thumbnail).then( + (value) => `url(${value})` + ) + : "none"; + + return html` + ${ + currentItem.can_play + ? html` +
    +
    + ${currentItem.thumbnail + ? html` +
    + ${this._narrow && currentItem?.can_play + ? html` + + + ${this.hass.localize( + `ui.components.media-browser.${this.action}` + )} + + ` + : ""} +
    + ` + : html``} +
    + + ${currentItem.can_play && + (!currentItem.thumbnail || !this._narrow) + ? html` + + + ${this.hass.localize( + `ui.components.media-browser.${this.action}` + )} + + ` + : ""} +
    +
    +
    + ` + : "" + } +
    + ${ + this._error + ? html` +
    + ${this._renderError(this._error)} +
    + ` + : isTTSMediaSource(currentItem.media_content_id) + ? html` + + ` + : !children.length && !currentItem.not_shown + ? html` +
    + ${currentItem.media_content_id === + "media-source://media_source/local/." + ? html` +
    + + + + + ${this.hass.localize( + "ui.components.media-browser.file_management.highlight_button" + )} + +
    + ` + : this.hass.localize( + "ui.components.media-browser.no_items" + )} +
    + ` + : childrenMediaClass.layout === "grid" + ? html` + + ${currentItem.not_shown + ? html` +
    +
    + ${this.hass.localize( + "ui.components.media-browser.not_shown", + { count: currentItem.not_shown } + )} +
    +
    + ` + : ""} + ` + : html` + + + ${currentItem.not_shown + ? html` + + + ${this.hass.localize( + "ui.components.media-browser.not_shown", + { count: currentItem.not_shown } + )} + + + ` + : ""} + + ` + } +
    +
    +
    + `; + } + + private _renderGridItem = (child: MediaPlayerItem): TemplateResult => { + const backgroundImage = child.thumbnail + ? this._getSignedThumbnail(child.thumbnail).then( + (value) => `url(${value})` + ) + : "none"; + + return html` +
    + +
    + ${child.thumbnail + ? html` +
    + ` + : html` +
    + +
    + `} + ${child.can_play + ? html` + + ` + : ""} +
    +
    + ${child.title} + ${child.title} +
    +
    +
    + `; + }; + + private _renderListItem = (child: MediaPlayerItem): TemplateResult => { + const currentItem = this._currentItem; + const mediaClass = MediaClassBrowserSettings[currentItem!.media_class]; + + const backgroundImage = + mediaClass.show_list_images && child.thumbnail + ? this._getSignedThumbnail(child.thumbnail).then( + (value) => `url(${value})` + ) + : "none"; + + return html` + +
    + +
    + ${child.title} +
    +
  • + `; + }; + + private async _getSignedThumbnail( + thumbnailUrl: string | undefined + ): Promise { + if (!thumbnailUrl) { + return ""; + } + + if (thumbnailUrl.startsWith("/")) { + // Thumbnails served by local API require authentication + return (await getSignedPath(this.hass, thumbnailUrl)).path; + } + + if (thumbnailUrl.startsWith("https://brands.home-assistant.io")) { + // The backend is not aware of the theme used by the users, + // so we rewrite the URL to show a proper icon + thumbnailUrl = brandsUrl({ + domain: extractDomainFromBrandUrl(thumbnailUrl), + type: "icon", + useFallback: true, + darkOptimized: this.hass.themes?.darkMode, + }); + } + + return thumbnailUrl; + } + + private _actionClicked = (ev: MouseEvent): void => { ev.stopPropagation(); const item = (ev.currentTarget as any).item; this._runAction(item); - } + }; private _runAction(item: MediaPlayerItem): void { fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds }); @@ -615,7 +657,7 @@ export class HaMediaPlayerBrowse extends LitElement { }); } - private async _childClicked(ev: MouseEvent): Promise { + private _childClicked = async (ev: MouseEvent): Promise => { const target = ev.currentTarget as any; const item: MediaPlayerItem = target.item; @@ -631,7 +673,7 @@ export class HaMediaPlayerBrowse extends LitElement { fireEvent(this, "media-browsed", { ids: [...this.navigateIds, item], }); - } + }; private async _fetchData( entityId: string, @@ -658,55 +700,6 @@ export class HaMediaPlayerBrowse extends LitElement { this._resizeObserver.observe(this); } - /** - * Load thumbnails for images on demand as they become visible. - */ - private async _attachIntersectionObserver(): Promise { - if (!("IntersectionObserver" in window) || !this._thumbnails) { - return; - } - if (!this._intersectionObserver) { - this._intersectionObserver = new IntersectionObserver( - async (entries, observer) => { - await Promise.all( - entries.map(async (entry) => { - if (!entry.isIntersecting) { - return; - } - const thumbnailCard = entry.target as HTMLElement; - let thumbnailUrl = thumbnailCard.dataset.src; - if (!thumbnailUrl) { - return; - } - if (thumbnailUrl.startsWith("/")) { - // Thumbnails served by local API require authentication - const signedPath = await getSignedPath(this.hass, thumbnailUrl); - thumbnailUrl = signedPath.path; - } else if ( - thumbnailUrl.startsWith("https://brands.home-assistant.io") - ) { - // The backend is not aware of the theme used by the users, - // so we rewrite the URL to show a proper icon - thumbnailUrl = brandsUrl({ - domain: extractDomainFromBrandUrl(thumbnailUrl), - type: "icon", - useFallback: true, - darkOptimized: this.hass.themes?.darkMode, - }); - } - thumbnailCard.style.backgroundImage = `url(${thumbnailUrl})`; - observer.unobserve(thumbnailCard); // loaded, so no need to observe anymore - }) - ); - } - ); - } - const observer = this._intersectionObserver!; - for (const thumbnailCard of this._thumbnails) { - observer.observe(thumbnailCard); - } - } - private _closeDialogAction(): void { fireEvent(this, "close-dialog"); } @@ -841,6 +834,7 @@ export class HaMediaPlayerBrowse extends LitElement { .content { overflow-y: auto; box-sizing: border-box; + height: 100%; } /* HEADER */ @@ -926,6 +920,7 @@ export class HaMediaPlayerBrowse extends LitElement { .not-shown { font-style: italic; color: var(--secondary-text-color); + padding: 8px 16px 8px; } .grid.not-shown { @@ -951,7 +946,11 @@ export class HaMediaPlayerBrowse extends LitElement { border-bottom-color: var(--divider-color); } - .children { + mwc-list-item { + width: 100%; + } + + div.children { display: grid; grid-template-columns: repeat( auto-fit, @@ -988,7 +987,7 @@ export class HaMediaPlayerBrowse extends LitElement { padding-bottom: 100%; } - .portrait.children ha-card .thumbnail { + .portrait ha-card .thumbnail { padding-bottom: 150%; } @@ -1062,10 +1061,6 @@ export class HaMediaPlayerBrowse extends LitElement { color: var(--primary-color); } - ha-card:hover .lazythumbnail { - opacity: 0.5; - } - .child .title { font-size: 16px; padding-top: 16px; @@ -1127,7 +1122,7 @@ export class HaMediaPlayerBrowse extends LitElement { padding: 0 24px; } - :host([narrow]) .children { + :host([narrow]) div.children { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important; } @@ -1232,6 +1227,16 @@ export class HaMediaPlayerBrowse extends LitElement { --mdc-fab-box-shadow: none; --mdc-theme-secondary: rgba(var(--rgb-primary-color), 0.5); } + + lit-virtualizer { + height: 100%; + overflow: overlay !important; + contain: size layout !important; + } + + lit-virtualizer.not_shown { + height: calc(100% - 36px); + } `, ]; } From 94953ddf6cb0a7d7d934b1cbd04b6ec022538665 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 14:09:23 +0200 Subject: [PATCH 058/181] Hide supervisor only config, fix backup config page (#12401) --- src/layouts/hass-tabs-subpage-data-table.ts | 2 +- .../config/core/ha-config-system-navigation.ts | 15 +++++++++------ src/panels/config/ha-panel-config.ts | 2 ++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 5085aba824..7a176549d7 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -130,7 +130,7 @@ export class HaTabsSubpageDataTable extends LitElement { * Array of tabs to show on the page. * @type {Array} */ - @property() public tabs!: PageNavigation[]; + @property() public tabs: PageNavigation[] = []; /** * Force hides the filter menu. diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 8792ef009a..2bd3e86b18 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -1,5 +1,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; +import { canShowPage } from "../../../common/config/can_show_page"; import "../../../components/ha-card"; import "../../../components/ha-navigation-list"; import { CloudStatus } from "../../../data/cloud"; @@ -23,12 +24,14 @@ class HaConfigSystemNavigation extends LitElement { @property({ type: Boolean }) public showAdvanced!: boolean; protected render(): TemplateResult { - const pages = configSections.general.map((page) => ({ - ...page, - name: page.translationKey - ? this.hass.localize(page.translationKey) - : page.name, - })); + const pages = configSections.general + .filter((page) => canShowPage(this.hass, page)) + .map((page) => ({ + ...page, + name: page.translationKey + ? this.hass.localize(page.translationKey) + : page.name, + })); return html` Date: Mon, 25 Apr 2022 14:37:32 +0200 Subject: [PATCH 059/181] Fix broken cards being able to crash entire view (#11440) --- src/common/util/promise-timeout.ts | 2 +- .../lovelace/common/compute-card-size.ts | 9 ++- src/panels/lovelace/views/hui-view.ts | 66 ++++++++++++++++--- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/common/util/promise-timeout.ts b/src/common/util/promise-timeout.ts index b26e8ba4b8..43b3359026 100644 --- a/src/common/util/promise-timeout.ts +++ b/src/common/util/promise-timeout.ts @@ -1,4 +1,4 @@ -export const promiseTimeout = (ms: number, promise: Promise) => { +export const promiseTimeout = (ms: number, promise: Promise | any) => { const timeout = new Promise((_resolve, reject) => { setTimeout(() => { reject(`Timed out in ${ms} ms.`); diff --git a/src/panels/lovelace/common/compute-card-size.ts b/src/panels/lovelace/common/compute-card-size.ts index 60ca74c4a6..ee22dcbe2d 100644 --- a/src/panels/lovelace/common/compute-card-size.ts +++ b/src/panels/lovelace/common/compute-card-size.ts @@ -1,10 +1,17 @@ +import { promiseTimeout } from "../../../common/util/promise-timeout"; import { LovelaceCard, LovelaceHeaderFooter } from "../types"; export const computeCardSize = ( card: LovelaceCard | LovelaceHeaderFooter ): number | Promise => { if (typeof card.getCardSize === "function") { - return card.getCardSize(); + try { + return promiseTimeout(500, card.getCardSize()).catch( + () => 1 + ) as Promise; + } catch (_e: any) { + return 1; + } } if (customElements.get(card.localName)) { return 1; diff --git a/src/panels/lovelace/views/hui-view.ts b/src/panels/lovelace/views/hui-view.ts index 685adce5c9..bcf440f328 100644 --- a/src/panels/lovelace/views/hui-view.ts +++ b/src/panels/lovelace/views/hui-view.ts @@ -10,10 +10,18 @@ import type { LovelaceViewElement, } from "../../../data/lovelace"; import type { HomeAssistant } from "../../../types"; +import { + createErrorBadgeConfig, + createErrorBadgeElement, +} from "../badges/hui-error-badge"; import type { HuiErrorCard } from "../cards/hui-error-card"; import { processConfigEntities } from "../common/process-config-entities"; import { createBadgeElement } from "../create-element/create-badge-element"; import { createCardElement } from "../create-element/create-card-element"; +import { + createErrorCardConfig, + createErrorCardElement, +} from "../create-element/create-element-base"; import { createViewElement } from "../create-element/create-view-element"; import { showCreateCardDialog } from "../editor/card-editor/show-create-card-dialog"; import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"; @@ -54,7 +62,13 @@ export class HUIView extends ReactiveElement { // Public to make demo happy public createCardElement(cardConfig: LovelaceCardConfig) { const element = createCardElement(cardConfig) as LovelaceCard; - element.hass = this.hass; + try { + element.hass = this.hass; + } catch (e: any) { + return createErrorCardElement( + createErrorCardConfig(e.message, cardConfig) + ); + } element.addEventListener( "ll-rebuild", (ev: Event) => { @@ -71,7 +85,11 @@ export class HUIView extends ReactiveElement { public createBadgeElement(badgeConfig: LovelaceBadgeConfig) { const element = createBadgeElement(badgeConfig) as LovelaceBadge; - element.hass = this.hass; + try { + element.hass = this.hass; + } catch (e: any) { + return createErrorBadgeElement(createErrorBadgeConfig(e.message)); + } element.addEventListener( "ll-badge-rebuild", () => { @@ -121,11 +139,19 @@ export class HUIView extends ReactiveElement { // Config has not changed. Just props if (changedProperties.has("hass")) { this._badges.forEach((badge) => { - badge.hass = this.hass; + try { + badge.hass = this.hass; + } catch (e: any) { + this._rebuildBadge(badge, createErrorBadgeConfig(e.message)); + } }); this._cards.forEach((element) => { - element.hass = this.hass; + try { + element.hass = this.hass; + } catch (e: any) { + this._rebuildCard(element, createErrorCardConfig(e.message, null)); + } }); this._layoutElement.hass = this.hass; @@ -238,7 +264,11 @@ export class HUIView extends ReactiveElement { const badges = processConfigEntities(config.badges as any); this._badges = badges.map((badge) => { const element = createBadgeElement(badge); - element.hass = this.hass; + try { + element.hass = this.hass; + } catch (e: any) { + return createErrorBadgeElement(createErrorBadgeConfig(e.message)); + } return element; }); } @@ -251,7 +281,13 @@ export class HUIView extends ReactiveElement { this._cards = config.cards.map((cardConfig) => { const element = this.createCardElement(cardConfig); - element.hass = this.hass; + try { + element.hass = this.hass; + } catch (e: any) { + return createErrorCardElement( + createErrorCardConfig(e.message, cardConfig) + ); + } return element; }); } @@ -260,8 +296,14 @@ export class HUIView extends ReactiveElement { cardElToReplace: LovelaceCard, config: LovelaceCardConfig ): void { - const newCardEl = this.createCardElement(config); - newCardEl.hass = this.hass; + let newCardEl = this.createCardElement(config); + try { + newCardEl.hass = this.hass; + } catch (e: any) { + newCardEl = createErrorCardElement( + createErrorCardConfig(e.message, config) + ); + } if (cardElToReplace.parentElement) { cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace); } @@ -274,8 +316,12 @@ export class HUIView extends ReactiveElement { badgeElToReplace: LovelaceBadge, config: LovelaceBadgeConfig ): void { - const newBadgeEl = this.createBadgeElement(config); - newBadgeEl.hass = this.hass; + let newBadgeEl = this.createBadgeElement(config); + try { + newBadgeEl.hass = this.hass; + } catch (e: any) { + newBadgeEl = createErrorBadgeElement(createErrorBadgeConfig(e.message)); + } if (badgeElToReplace.parentElement) { badgeElToReplace.parentElement!.replaceChild( newBadgeEl, From bfa7bccfa68f226f5a2322880e541ffbb2c7b470 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 16:21:03 +0200 Subject: [PATCH 060/181] Add supervisor network interface settings (#12403) --- src/panels/config/ha-panel-config.ts | 2 +- .../{core => network}/ha-config-network.ts | 0 .../ha-config-section-network.ts | 10 +- .../{core => network}/ha-config-url-form.ts | 0 .../config/network/supervisor-network.ts | 577 ++++++++++++++++++ src/translations/en.json | 19 +- 6 files changed, 604 insertions(+), 4 deletions(-) rename src/panels/config/{core => network}/ha-config-network.ts (100%) rename src/panels/config/{core => network}/ha-config-section-network.ts (79%) rename src/panels/config/{core => network}/ha-config-url-form.ts (100%) create mode 100644 src/panels/config/network/supervisor-network.ts diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 315c7d192c..df915c398d 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -407,7 +407,7 @@ class HaPanelConfig extends HassRouterPage { }, network: { tag: "ha-config-section-network", - load: () => import("./core/ha-config-section-network"), + load: () => import("./network/ha-config-section-network"), }, person: { tag: "ha-config-person", diff --git a/src/panels/config/core/ha-config-network.ts b/src/panels/config/network/ha-config-network.ts similarity index 100% rename from src/panels/config/core/ha-config-network.ts rename to src/panels/config/network/ha-config-network.ts diff --git a/src/panels/config/core/ha-config-section-network.ts b/src/panels/config/network/ha-config-section-network.ts similarity index 79% rename from src/panels/config/core/ha-config-section-network.ts rename to src/panels/config/network/ha-config-section-network.ts index 7f87ae1d6c..daf8b15fc4 100644 --- a/src/panels/config/core/ha-config-section-network.ts +++ b/src/panels/config/network/ha-config-section-network.ts @@ -1,9 +1,11 @@ import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../layouts/hass-subpage"; import type { HomeAssistant, Route } from "../../../types"; import "./ha-config-network"; import "./ha-config-url-form"; +import "./supervisor-network"; @customElement("ha-config-section-network") class HaConfigSectionNetwork extends LitElement { @@ -22,6 +24,9 @@ class HaConfigSectionNetwork extends LitElement { .header=${this.hass.localize("ui.panel.config.network.caption")} >
    + ${isComponentLoaded(this.hass, "hassio") + ? html`` + : ""}
    @@ -35,9 +40,10 @@ class HaConfigSectionNetwork extends LitElement { max-width: 1040px; margin: 0 auto; } - ha-config-network { + supervisor-network, + ha-config-url-form { display: block; - margin-top: 24px; + margin-bottom: 24px; } `; } diff --git a/src/panels/config/core/ha-config-url-form.ts b/src/panels/config/network/ha-config-url-form.ts similarity index 100% rename from src/panels/config/core/ha-config-url-form.ts rename to src/panels/config/network/ha-config-url-form.ts diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts new file mode 100644 index 0000000000..5621a666e6 --- /dev/null +++ b/src/panels/config/network/supervisor-network.ts @@ -0,0 +1,577 @@ +import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-tab"; +import "@material/mwc-tab-bar"; +import { PaperInputElement } from "@polymer/paper-input/paper-input"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { cache } from "lit/directives/cache"; +import "../../../components/ha-alert"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-expansion-panel"; +import "../../../components/ha-formfield"; +import "../../../components/ha-header-bar"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-radio"; +import "../../../components/ha-related-items"; +import { extractApiErrorMessage } from "../../../data/hassio/common"; +import { + AccessPoints, + accesspointScan, + fetchNetworkInfo, + NetworkInterface, + updateNetworkInterface, + WifiConfiguration, +} from "../../../data/hassio/network"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; +import type { HomeAssistant } from "../../../types"; +import "../../../components/ha-card"; + +const IP_VERSIONS = ["ipv4", "ipv6"]; + +@customElement("supervisor-network") +export class HassioNetwork extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _accessPoints?: AccessPoints; + + @state() private _curTabIndex = 0; + + @state() private _dirty = false; + + @state() private _interface?: NetworkInterface; + + @state() private _interfaces!: NetworkInterface[]; + + @state() private _processing = false; + + @state() private _scanning = false; + + @state() private _wifiConfiguration?: WifiConfiguration; + + protected firstUpdated() { + this._fetchNetworkInfo(); + } + + private async _fetchNetworkInfo() { + const network = await fetchNetworkInfo(this.hass); + this._interfaces = network.interfaces.sort((a, b) => + a.primary > b.primary ? -1 : 1 + ); + this._interface = { ...this._interfaces[this._curTabIndex] }; + } + + protected render(): TemplateResult { + if (!this._interface) { + return html``; + } + + return html` + + ${this._interfaces.length > 1 + ? html`${this._interfaces.map( + (device) => + html` + ` + )} + ` + : ""} + ${cache(this._renderTab())} + + `; + } + + private _renderTab() { + return html`
    + ${IP_VERSIONS.map((version) => + this._interface![version] ? this._renderIPConfiguration(version) : "" + )} + ${this._interface?.type === "wireless" + ? html` + + ${this._interface?.wifi?.ssid + ? html`

    + ${this.hass.localize( + "ui.panel.config.network.supervisor.connected_to", + "ssid", + this._interface?.wifi?.ssid + )} +

    ` + : ""} + + ${this._scanning + ? html` + ` + : this.hass.localize( + "ui.panel.config.network.supervisor.scan_ap" + )} + + ${this._accessPoints && + this._accessPoints.accesspoints && + this._accessPoints.accesspoints.length !== 0 + ? html` + + ${this._accessPoints.accesspoints + .filter((ap) => ap.ssid) + .map( + (ap) => + html` + + ${ap.ssid} + + ${ap.mac} - Strength: ${ap.signal} + + + ` + )} + + ` + : ""} + ${this._wifiConfiguration + ? html` +
    + + + + + + + + + + + + +
    + ${this._wifiConfiguration.auth === "wpa-psk" || + this._wifiConfiguration.auth === "wep" + ? html` + + + ` + : ""} + ` + : ""} +
    + ` + : ""} + ${this._dirty + ? html` + ${this.hass.localize( + "ui.panel.config.network.supervisor.warning" + )} + ` + : ""} +
    +
    + + ${this._processing + ? html` + ` + : this.hass.localize("ui.common.save")} + +
    `; + } + + private _selectAP(event) { + this._wifiConfiguration = event.currentTarget.ap; + this._dirty = true; + } + + private async _scanForAP() { + if (!this._interface) { + return; + } + this._scanning = true; + try { + this._accessPoints = await accesspointScan( + this.hass, + this._interface.interface + ); + } catch (err: any) { + showAlertDialog(this, { + title: "Failed to scan for accesspoints", + text: extractApiErrorMessage(err), + }); + } finally { + this._scanning = false; + } + } + + private _renderIPConfiguration(version: string) { + return html` + +
    + + + + + + + + + + + + +
    + ${this._interface![version].method === "static" + ? html` + + + + + + + ` + : ""} +
    + `; + } + + _toArray(data: string | string[]): string[] { + if (Array.isArray(data)) { + if (data && typeof data[0] === "string") { + data = data[0]; + } + } + if (!data) { + return []; + } + if (typeof data === "string") { + return data.replace(/ /g, "").split(","); + } + return data; + } + + _toString(data: string | string[]): string { + if (!data) { + return ""; + } + if (Array.isArray(data)) { + return data.join(", "); + } + return data; + } + + private async _updateNetwork() { + this._processing = true; + let interfaceOptions: Partial = {}; + + IP_VERSIONS.forEach((version) => { + interfaceOptions[version] = { + method: this._interface![version]?.method || "auto", + }; + if (this._interface![version]?.method === "static") { + interfaceOptions[version] = { + ...interfaceOptions[version], + address: this._toArray(this._interface![version]?.address), + gateway: this._interface![version]?.gateway, + nameservers: this._toArray(this._interface![version]?.nameservers), + }; + } + }); + + if (this._wifiConfiguration) { + interfaceOptions = { + ...interfaceOptions, + wifi: { + ssid: this._wifiConfiguration.ssid, + mode: this._wifiConfiguration.mode, + auth: this._wifiConfiguration.auth || "open", + }, + }; + if (interfaceOptions.wifi!.auth !== "open") { + interfaceOptions.wifi = { + ...interfaceOptions.wifi, + psk: this._wifiConfiguration.psk, + }; + } + } + + interfaceOptions.enabled = + this._wifiConfiguration !== undefined || + interfaceOptions.ipv4?.method !== "disabled" || + interfaceOptions.ipv6?.method !== "disabled"; + + try { + await updateNetworkInterface( + this.hass, + this._interface!.interface, + interfaceOptions + ); + this._dirty = false; + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.network.supervisor.failed_to_change" + ), + text: extractApiErrorMessage(err), + }); + } finally { + this._processing = false; + } + } + + private async _handleTabActivated(ev: CustomEvent): Promise { + if (this._dirty) { + const confirm = await showConfirmationDialog(this, { + text: this.hass.localize("ui.panel.config.network.supervisor.unsaved"), + confirmText: this.hass.localize("ui.common.yes"), + dismissText: this.hass.localize("ui.common.no"), + }); + if (!confirm) { + this.requestUpdate("_interface"); + return; + } + } + this._curTabIndex = ev.detail.index; + this._interface = { ...this._interfaces[ev.detail.index] }; + } + + private _handleRadioValueChanged(ev: CustomEvent): void { + const value = (ev.target as any).value as "disabled" | "auto" | "static"; + const version = (ev.target as any).version as "ipv4" | "ipv6"; + + if ( + !value || + !this._interface || + this._interface[version]!.method === value + ) { + return; + } + this._dirty = true; + + this._interface[version]!.method = value; + this.requestUpdate("_interface"); + } + + private _handleRadioValueChangedAp(ev: CustomEvent): void { + const value = (ev.target as any).value as string as + | "open" + | "wep" + | "wpa-psk"; + this._wifiConfiguration!.auth = value; + this._dirty = true; + this.requestUpdate("_wifiConfiguration"); + } + + private _handleInputValueChanged(ev: CustomEvent): void { + const value: string | null | undefined = (ev.target as PaperInputElement) + .value; + const version = (ev.target as any).version as "ipv4" | "ipv6"; + const id = (ev.target as PaperInputElement).id; + + if ( + !value || + !this._interface || + this._toString(this._interface[version]![id]) === this._toString(value) + ) { + return; + } + + this._dirty = true; + this._interface[version]![id] = value; + } + + private _handleInputValueChangedWifi(ev: CustomEvent): void { + const value: string | null | undefined = (ev.target as PaperInputElement) + .value; + const id = (ev.target as PaperInputElement).id; + + if ( + !value || + !this._wifiConfiguration || + this._wifiConfiguration![id] === value + ) { + return; + } + this._dirty = true; + this._wifiConfiguration![id] = value; + } + + static get styles(): CSSResultGroup { + return [ + css` + ha-header-bar { + --mdc-theme-on-primary: var(--primary-text-color); + --mdc-theme-primary: var(--mdc-theme-surface); + flex-shrink: 0; + } + + mwc-tab-bar { + border-bottom: 1px solid + var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); + margin-bottom: 24px; + } + + .content { + display: block; + padding: 20px 24px; + } + + mwc-button.warning { + --mdc-theme-primary: var(--error-color); + } + + mwc-button.scan { + margin-left: 8px; + } + + :host([rtl]) app-toolbar { + direction: rtl; + text-align: right; + } + ha-expansion-panel { + --expansion-panel-summary-padding: 0 16px; + margin: 4px 0; + } + paper-input { + padding: 0 14px; + } + mwc-list-item { + --mdc-list-side-padding: 10px; + } + .card-actions { + display: flex; + flex-direction: row-reverse; + justify-content: space-between; + align-items: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "supervisor-network": HassioNetwork; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index c7fe10dbd8..a24bccef9f 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3108,7 +3108,24 @@ "caption": "Analytics" }, "network": { - "caption": "Network" + "caption": "Network", + "supervisor": { + "title": "Configure network interfaces", + "connected_to": "Connected to {ssid}", + "scan_ap": "Scan for access points", + "open": "Open", + "wep": "WEP", + "wpa": "wpa-psk", + "warning": "If you are changing the Wi-Fi, IP or gateway addresses, you might lose the connection!", + "static": "Static", + "dhcp": "DHCP", + "disabled": "Disabled", + "ip_netmask": "IP address/Netmask", + "gateway": "Gateway address", + "dns_servers": "DNS Servers", + "unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?", + "failed_to_change": "Failed to change network settings" + } }, "storage": { "caption": "Storage", From 77ef509aea03a442594abcffd0e035abf0a1dd4f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 17:13:31 +0200 Subject: [PATCH 061/181] Fix zones (#12409) --- src/components/ha-selector/ha-selector-location.ts | 9 ++++++++- src/panels/config/zone/ha-config-zone.ts | 9 ++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/ha-selector/ha-selector-location.ts b/src/components/ha-selector/ha-selector-location.ts index 2dc9ecc1e2..05a2b3b451 100644 --- a/src/components/ha-selector/ha-selector-location.ts +++ b/src/components/ha-selector/ha-selector-location.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; @@ -76,6 +76,13 @@ export class HaLocationSelector extends LitElement { const radius = ev.detail.radius; fireEvent(this, "value-changed", { value: { ...this.value, radius } }); } + + static styles = css` + :host { + display: block; + height: 400px; + } + `; } declare global { diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index 7b7cc1373b..e46ed02abf 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -1,4 +1,4 @@ -import { mdiPencil, mdiPlus } from "@mdi/js"; +import { mdiPencil, mdiPencilOff, mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-listbox/paper-listbox"; @@ -189,9 +189,12 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { Date: Mon, 25 Apr 2022 17:27:38 +0200 Subject: [PATCH 062/181] Add supervisor hostname config (#12407) --- .../network/ha-config-section-network.ts | 7 +- .../config/network/supervisor-hostname.ts | 122 ++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/panels/config/network/supervisor-hostname.ts diff --git a/src/panels/config/network/ha-config-section-network.ts b/src/panels/config/network/ha-config-section-network.ts index daf8b15fc4..a8a162f18f 100644 --- a/src/panels/config/network/ha-config-section-network.ts +++ b/src/panels/config/network/ha-config-section-network.ts @@ -6,6 +6,7 @@ import type { HomeAssistant, Route } from "../../../types"; import "./ha-config-network"; import "./ha-config-url-form"; import "./supervisor-network"; +import "./supervisor-hostname"; @customElement("ha-config-section-network") class HaConfigSectionNetwork extends LitElement { @@ -25,7 +26,10 @@ class HaConfigSectionNetwork extends LitElement { >
    ${isComponentLoaded(this.hass, "hassio") - ? html`` + ? html` + ` : ""} @@ -40,6 +44,7 @@ class HaConfigSectionNetwork extends LitElement { max-width: 1040px; margin: 0 auto; } + supervisor-hostname, supervisor-network, ha-config-url-form { display: block; diff --git a/src/panels/config/network/supervisor-hostname.ts b/src/panels/config/network/supervisor-hostname.ts new file mode 100644 index 0000000000..f18c173217 --- /dev/null +++ b/src/panels/config/network/supervisor-hostname.ts @@ -0,0 +1,122 @@ +import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-tab"; +import "@material/mwc-tab-bar"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../components/ha-alert"; +import "../../../components/ha-card"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-expansion-panel"; +import "../../../components/ha-formfield"; +import "../../../components/ha-header-bar"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-radio"; +import "../../../components/ha-related-items"; +import "../../../components/ha-textfield"; +import { extractApiErrorMessage } from "../../../data/hassio/common"; +import { + changeHostOptions, + fetchHassioHostInfo, +} from "../../../data/hassio/host"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import type { HomeAssistant } from "../../../types"; +import "../../../components/ha-settings-row"; + +@customElement("supervisor-hostname") +export class HassioHostname extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _processing = false; + + @state() private _hostname?: string; + + protected firstUpdated() { + this._fetchHostInfo(); + } + + private async _fetchHostInfo() { + const hostInfo = await fetchHassioHostInfo(this.hass); + this._hostname = hostInfo.hostname; + } + + protected render(): TemplateResult { + if (!this._hostname) { + return html``; + } + + return html` + +
    + + Hostname + The name your instance will have on your network + + + +
    +
    + + ${this._processing + ? html` + ` + : this.hass.localize("ui.common.save")} + +
    +
    + `; + } + + private _handleChange(ev) { + this._hostname = ev.target.value; + } + + private async _save() { + this._processing = true; + try { + await changeHostOptions(this.hass, { hostname: this._hostname }); + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.network.hostname.failed_to_set_hostname" + ), + text: extractApiErrorMessage(err), + }); + } finally { + this._processing = false; + } + } + + static get styles(): CSSResultGroup { + return [ + css` + ha-textfield { + width: 100%; + } + .card-actions { + display: flex; + flex-direction: row-reverse; + justify-content: space-between; + align-items: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "supervisor-hostname": HassioHostname; + } +} From 8e55c8399698560ce68d83c3edf61cade1ca0c12 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 10:30:53 -0500 Subject: [PATCH 063/181] Add Hardware Page to Configuration System Menu (#12405) --- src/panels/config/ha-panel-config.ts | 4 + .../hardware/dialog-hardware-available.ts | 213 +++++++++++++++ .../config/hardware/ha-config-hardware.ts | 257 ++++++++++++++++++ .../show-dialog-hardware-available.ts | 12 + .../config/zone/dialog-core-zone-detail.ts | 3 +- src/translations/en.json | 17 +- 6 files changed, 504 insertions(+), 2 deletions(-) create mode 100644 src/panels/config/hardware/dialog-hardware-available.ts create mode 100644 src/panels/config/hardware/ha-config-hardware.ts create mode 100644 src/panels/config/hardware/show-dialog-hardware-available.ts diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index df915c398d..94ceca01b5 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -397,6 +397,10 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-energy", load: () => import("./energy/ha-config-energy"), }, + hardware: { + tag: "ha-config-hardware", + load: () => import("./hardware/ha-config-hardware"), + }, integrations: { tag: "ha-config-integrations", load: () => import("./integrations/ha-config-integrations"), diff --git a/src/panels/config/hardware/dialog-hardware-available.ts b/src/panels/config/hardware/dialog-hardware-available.ts new file mode 100644 index 0000000000..9413c8acb7 --- /dev/null +++ b/src/panels/config/hardware/dialog-hardware-available.ts @@ -0,0 +1,213 @@ +import { mdiClose } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { stringCompare } from "../../../common/string/compare"; +import "../../../components/ha-dialog"; +import "../../../components/ha-expansion-panel"; +import "../../../components/ha-icon-next"; +import "../../../components/search-input"; +import { extractApiErrorMessage } from "../../../data/hassio/common"; +import { + fetchHassioHardwareInfo, + HassioHardwareInfo, +} from "../../../data/hassio/hardware"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import type { HassDialog } from "../../../dialogs/make-dialog-manager"; +import { dump } from "../../../resources/js-yaml-dump"; +import { haStyle, haStyleDialog } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; + +const _filterDevices = memoizeOne( + (showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) => + hardware.devices + .filter( + (device) => + (showAdvanced || + ["tty", "gpio", "input"].includes(device.subsystem)) && + (device.by_id?.toLowerCase().includes(filter) || + device.name.toLowerCase().includes(filter) || + device.dev_path.toLocaleLowerCase().includes(filter) || + JSON.stringify(device.attributes) + .toLocaleLowerCase() + .includes(filter)) + ) + .sort((a, b) => stringCompare(a.name, b.name)) +); + +@customElement("ha-dialog-hardware-available") +class DialogHardwareAvailable extends LitElement implements HassDialog { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _hardware?: HassioHardwareInfo; + + @state() private _filter?: string; + + public async showDialog(): Promise> { + try { + this._hardware = await fetchHassioHardwareInfo(this.hass); + } catch (err: any) { + await showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.hardware.available_hardware.failed_to_get" + ), + text: extractApiErrorMessage(err), + }); + } + } + + public closeDialog(): void { + this._hardware = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._hardware) { + return html``; + } + + const devices = _filterDevices( + this.hass.userData?.showAdvanced || false, + this._hardware, + (this._filter || "").toLowerCase() + ); + + return html` + +
    +

    + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.title" + )} +

    + + + +
    + ${devices.map( + (device) => + html` + +
    + + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.subsystem" + )}: + + ${device.subsystem} +
    +
    + + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.device_path" + )}: + + ${device.dev_path} +
    + ${device.by_id + ? html` +
    + + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.id" + )}: + + ${device.by_id} +
    + ` + : ""} +
    + + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.attributes" + )}: + +
    ${dump(device.attributes, { indent: 2 })}
    +
    +
    + ` + )} +
    + `; + } + + private _handleSearchChange(ev: CustomEvent) { + this._filter = ev.detail.value; + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + ha-icon-button { + position: absolute; + right: 16px; + top: 10px; + text-decoration: none; + color: var(--primary-text-color); + } + h2 { + margin: 18px 42px 0 18px; + color: var(--primary-text-color); + } + ha-expansion-panel { + margin: 4px 0; + } + pre, + code { + background-color: var(--markdown-code-background-color, none); + border-radius: 3px; + } + pre { + padding: 16px; + overflow: auto; + line-height: 1.45; + font-family: var(--code-font-family, monospace); + } + code { + font-size: 85%; + padding: 0.2em 0.4em; + } + search-input { + margin: 8px 16px 0; + display: block; + } + .device-property { + display: flex; + justify-content: space-between; + } + .attributes { + margin-top: 12px; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-hardware-available": DialogHardwareAvailable; + } +} diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts new file mode 100644 index 0000000000..6b22057e59 --- /dev/null +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -0,0 +1,257 @@ +import "@material/mwc-list/mwc-list-item"; +import { mdiDotsVertical } from "@mdi/js"; +import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import "../../../components/buttons/ha-progress-button"; +import "../../../components/ha-alert"; +import "../../../components/ha-button-menu"; +import "../../../components/ha-card"; +import "../../../components/ha-settings-row"; +import { + extractApiErrorMessage, + ignoreSupervisorError, +} from "../../../data/hassio/common"; +import { + fetchHassioHassOsInfo, + fetchHassioHostInfo, + HassioHassOSInfo, + HassioHostInfo, + rebootHost, + shutdownHost, +} from "../../../data/hassio/host"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; +import "../../../layouts/hass-subpage"; +import { haStyle } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; +import { showhardwareAvailableDialog } from "./show-dialog-hardware-available"; + +@customElement("ha-config-hardware") +class HaConfigHardware extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + @state() private _error?: { code: string; message: string }; + + @state() private _OSData?: HassioHassOSInfo; + + @state() private _hostData?: HassioHostInfo; + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + if (isComponentLoaded(this.hass, "hassio")) { + this._load(); + } + } + + protected render(): TemplateResult { + return html` + + ${this._error + ? html` + ${this._error.message || this._error.code} + ` + : ""} + ${this._OSData && this._hostData + ? html` +
    + +
    + + ${this.hass.localize( + "ui.panel.config.hardware.board" + )} +
    + ${this._OSData.board} +
    +
    +
    +
    +
    + ${this._hostData.features.includes("reboot") + ? html` + + ${this.hass.localize( + "ui.panel.config.hardware.reboot_host" + )} + + ` + : ""} + ${this._hostData.features.includes("shutdown") + ? html` + + ${this.hass.localize( + "ui.panel.config.hardware.shutdown_host" + )} + + ` + : ""} +
    + + + + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.title" + )} + + +
    +
    +
    + ` + : ""} +
    + `; + } + + private async _load() { + try { + this._OSData = await fetchHassioHassOsInfo(this.hass); + this._hostData = await fetchHassioHostInfo(this.hass); + } catch (err: any) { + this._error = err.message || err; + } + } + + private async _openHardware() { + showhardwareAvailableDialog(this); + } + + private async _hostReboot(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + const confirmed = await showConfirmationDialog(this, { + title: this.hass.localize("ui.panel.config.hardware.reboot_host"), + text: this.hass.localize("ui.panel.config.hardware.reboot_host_confirm"), + confirmText: this.hass.localize("ui.panel.config.hardware.reboot_host"), + dismissText: this.hass.localize("common.cancel"), + }); + + if (!confirmed) { + button.progress = false; + return; + } + + try { + await rebootHost(this.hass); + } catch (err: any) { + // Ignore connection errors, these are all expected + if (this.hass.connection.connected && !ignoreSupervisorError(err)) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.hardware.failed_to_reboot_host" + ), + text: extractApiErrorMessage(err), + }); + } + } + button.progress = false; + } + + private async _hostShutdown(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + const confirmed = await showConfirmationDialog(this, { + title: this.hass.localize("ui.panel.config.hardware.shutdown_host"), + text: this.hass.localize( + "ui.panel.config.hardware.shutdown_host_confirm" + ), + confirmText: this.hass.localize("ui.panel.config.hardware.shutdown_host"), + dismissText: this.hass.localize("common.cancel"), + }); + + if (!confirmed) { + button.progress = false; + return; + } + + try { + await shutdownHost(this.hass); + } catch (err: any) { + // Ignore connection errors, these are all expected + if (this.hass.connection.connected && !ignoreSupervisorError(err)) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.hardware.failed_to_shutdown_host" + ), + text: extractApiErrorMessage(err), + }); + } + } + button.progress = false; + } + + static styles = [ + haStyle, + css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + ha-card { + max-width: 500px; + margin: 0 auto; + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + .card-content { + display: flex; + justify-content: space-between; + flex-direction: column; + padding: 16px 16px 0 16px; + } + ha-button-menu { + color: var(--secondary-text-color); + --mdc-menu-min-width: 200px; + } + .card-actions { + height: 48px; + border-top: none; + display: flex; + justify-content: space-between; + align-items: center; + } + .buttons { + display: flex; + align-items: center; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-hardware": HaConfigHardware; + } +} diff --git a/src/panels/config/hardware/show-dialog-hardware-available.ts b/src/panels/config/hardware/show-dialog-hardware-available.ts new file mode 100644 index 0000000000..c11356ae8b --- /dev/null +++ b/src/panels/config/hardware/show-dialog-hardware-available.ts @@ -0,0 +1,12 @@ +import { fireEvent } from "../../../common/dom/fire_event"; + +export const loadHardwareAvailableDialog = () => + import("./dialog-hardware-available"); + +export const showhardwareAvailableDialog = (element: HTMLElement): void => { + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-hardware-available", + dialogImport: loadHardwareAvailableDialog, + dialogParams: {}, + }); +}; diff --git a/src/panels/config/zone/dialog-core-zone-detail.ts b/src/panels/config/zone/dialog-core-zone-detail.ts index 7c388bd964..13f06113b9 100644 --- a/src/panels/config/zone/dialog-core-zone-detail.ts +++ b/src/panels/config/zone/dialog-core-zone-detail.ts @@ -10,7 +10,8 @@ import { stopPropagation } from "../../../common/dom/stop_propagation"; import { currencies } from "../../../components/currency-datalist"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-formfield"; -import { HaRadio } from "../../../components/ha-radio"; +import "../../../components/ha-radio"; +import type { HaRadio } from "../../../components/ha-radio"; import "../../../components/ha-select"; import "../../../components/ha-textfield"; import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; diff --git a/src/translations/en.json b/src/translations/en.json index a24bccef9f..6dd926e542 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1465,7 +1465,22 @@ "internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate." }, "hardware": { - "caption": "Hardware" + "caption": "Hardware", + "available_hardware": { + "failed_to_get": "Failed to get available hardware", + "title": "Available hardware", + "subsystem": "Subsystem", + "device_path": "Device path", + "id": "ID", + "attributes": "Attributes" + }, + "reboot_host": "Reboot host", + "reboot_host_confirm": "Are you sure you want to reboot your host?", + "failed_to_reboot_host": "Failed to reboot host", + "shutdown_host": "Shutdown host", + "shutdown_host_confirm": "Are you sure you want to shutdown your host?", + "failed_to_shutdown_host": "Failed to shutdown host", + "board": "Board" }, "info": { "caption": "Info", From 3f04abfa9d8030e043f88cf9adeae6ae34d239c9 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 10:35:03 -0500 Subject: [PATCH 064/181] Add Supervisor logs to core page (#12410) --- src/data/error_log.ts | 5 + src/panels/config/logs/error-log-card.ts | 243 ++++++++++++++++++----- src/translations/en.json | 2 + 3 files changed, 195 insertions(+), 55 deletions(-) diff --git a/src/data/error_log.ts b/src/data/error_log.ts index b1bb4de170..a84925f70a 100644 --- a/src/data/error_log.ts +++ b/src/data/error_log.ts @@ -1,4 +1,9 @@ import { HomeAssistant } from "../types"; +export interface LogProvider { + key: string; + name: string; +} + export const fetchErrorLog = (hass: HomeAssistant) => hass.callApi("GET", "error_log"); diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index 50dc5db3a9..190fdd9437 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -1,11 +1,53 @@ import "@material/mwc-button"; +import "@material/mwc-list/mwc-list-item"; import { mdiRefresh } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { property, state } from "lit/decorators"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import "../../../components/ha-alert"; +import "../../../components/ha-card"; import "../../../components/ha-icon-button"; -import { fetchErrorLog } from "../../../data/error_log"; +import "../../../components/ha-select"; +import { fetchErrorLog, LogProvider } from "../../../data/error_log"; +import { extractApiErrorMessage } from "../../../data/hassio/common"; +import { fetchHassioLogs } from "../../../data/hassio/supervisor"; import { HomeAssistant } from "../../../types"; +const logProviders: LogProvider[] = [ + { + key: "supervisor", + name: "Supervisor", + }, + { + key: "core", + name: "Home Assistant Core", + }, + { + key: "host", + name: "Host", + }, + { + key: "dns", + name: "DNS", + }, + { + key: "audio", + name: "Audio", + }, + { + key: "multicast", + name: "Multicast", + }, +]; + +@customElement("error-log-card") class ErrorLogCard extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -13,37 +55,69 @@ class ErrorLogCard extends LitElement { @state() private _isLogLoaded = false; - @state() private _errorHTML!: TemplateResult[] | string; + @state() private _logHTML!: TemplateResult[] | string; + + @state() private _error?: string; + + @state() private _selectedLogProvider?: string; protected render(): TemplateResult { return html`
    - ${this._errorHTML + ${this._error + ? html`${this._error}` + : ""} + ${this._logHTML ? html` - -
    ${this._errorHTML}
    +
    + ${this.hass.userData?.showAdvanced && + isComponentLoaded(this.hass, "hassio") + ? html` + + ${logProviders.map( + (provider) => html` + + ${provider.name} + + ` + )} + + ` + : ""} + +
    +
    ${this._logHTML}
    ` - : html` - + : ""} + ${!this._logHTML + ? html` + ${this.hass.localize("ui.panel.config.logs.load_full_log")} - `} + ` + : ""}
    `; } - protected firstUpdated(changedProps) { + protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); if (this.hass?.config.safe_mode) { this.hass.loadFragmentTranslation("config"); - this._refreshErrorLog(); + this._refreshLogs(); } } @@ -51,52 +125,56 @@ class ErrorLogCard extends LitElement { super.updated(changedProps); if (changedProps.has("filter") && this._isLogLoaded) { - this._refreshErrorLog(); + this._refreshLogs(); } } - static get styles(): CSSResultGroup { - return css` - .error-log-intro { - text-align: center; - margin: 16px; - } + private async _setLogProvider(ev): Promise { + const provider = ev.target.value; + if (provider === this._selectedLogProvider) { + return; + } - ha-icon-button { - float: right; - } - - .error-log { - font-family: var(--code-font-family, monospace); - clear: both; - text-align: left; - padding-top: 12px; - } - - .error-log > div:hover { - background-color: var(--secondary-background-color); - } - - .error { - color: var(--error-color); - } - - .warning { - color: var(--warning-color); - } - - :host-context([style*="direction: rtl;"]) mwc-button { - direction: rtl; - } - `; + this._selectedLogProvider = provider; + this._refreshLogs(); } - private async _refreshErrorLog(): Promise { - this._errorHTML = this.hass.localize("ui.panel.config.logs.loading_log"); - const log = await fetchErrorLog(this.hass!); + private async _refresh(ev: CustomEvent): Promise { + const button = ev.currentTarget as any; + button.progress = true; + + await this._refreshLogs(); + button.progress = false; + } + + private async _refreshLogs(): Promise { + this._logHTML = this.hass.localize("ui.panel.config.logs.loading_log"); + let log: string; + + if (!this._selectedLogProvider && isComponentLoaded(this.hass, "hassio")) { + this._selectedLogProvider = "core"; + } + + if (this._selectedLogProvider) { + try { + log = await fetchHassioLogs(this.hass, this._selectedLogProvider); + } catch (err: any) { + this._error = this.hass.localize( + "ui.panel.config.logs.failed_get_logs", + "provider", + this._selectedLogProvider, + "error", + extractApiErrorMessage(err) + ); + return; + } + } else { + log = await fetchErrorLog(this.hass!); + } + this._isLogLoaded = true; - this._errorHTML = log + this._logHTML = log ? log .split("\n") .filter((entry) => { @@ -123,6 +201,61 @@ class ErrorLogCard extends LitElement { }) : this.hass.localize("ui.panel.config.logs.no_errors"); } + + static styles: CSSResultGroup = css` + .error-log-intro { + text-align: center; + margin: 16px; + } + + .header { + display: flex; + justify-content: space-between; + padding: 16px; + } + + ha-select { + display: block; + max-width: 500px; + width: 100%; + } + + ha-icon-button { + float: right; + } + + .error-log { + font-family: var(--code-font-family, monospace); + clear: both; + text-align: left; + padding-top: 12px; + } + + .error-log > div { + overflow: auto; + overflow-wrap: break-word; + } + + .error-log > div:hover { + background-color: var(--secondary-background-color); + } + + .error { + color: var(--error-color); + } + + .warning { + color: var(--warning-color); + } + + :host-context([style*="direction: rtl;"]) mwc-button { + direction: rtl; + } + `; } -customElements.define("error-log-card", ErrorLogCard); +declare global { + interface HTMLElementTagNameMap { + "error-log-card": ErrorLogCard; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 6dd926e542..8a6481ac1d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1514,6 +1514,7 @@ "description": "View the Home Assistant logs", "details": "Log Details ({level})", "search": "Search logs", + "failed_get_logs": "Failed to get {provider} logs, {error}", "no_issues_search": "No issues found for search term ''{term}''", "load_full_log": "Load Full Home Assistant Log", "loading_log": "Loading error log…", @@ -1522,6 +1523,7 @@ "clear": "Clear", "refresh": "Refresh", "copy": "Copy log entry", + "log_provider": "Log Provider", "multiple_messages": "message first occurred at {time} and shows up {counter} times", "level": { "critical": "CRITICAL", From 5deccefb15817bed88686ac13ac36ee6507f77e2 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 12:50:30 -0500 Subject: [PATCH 065/181] Allow Showing Skipped Updates on Updates Page (#12415) --- src/data/update.ts | 8 +++- .../config/core/ha-config-section-updates.ts | 45 ++++++++++++++++--- src/translations/en.json | 4 +- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/data/update.ts b/src/data/update.ts index 1818f3d4d7..8976e66f36 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -31,8 +31,12 @@ export const updateUsesProgress = (entity: UpdateEntity): boolean => supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) && typeof entity.attributes.in_progress === "number"; -export const updateCanInstall = (entity: UpdateEntity): boolean => - entity.state === BINARY_STATE_ON && +export const updateCanInstall = ( + entity: UpdateEntity, + showSkipped = false +): boolean => + (entity.state === BINARY_STATE_ON || + (showSkipped && Boolean(entity.attributes.skipped_version))) && supportsFeature(entity, UPDATE_SUPPORT_INSTALL); export const updateIsInstalling = (entity: UpdateEntity): boolean => diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 0853566da3..6a2c0ca399 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -1,11 +1,14 @@ +import "@material/mwc-list/mwc-list-item"; +import { mdiDotsVertical } from "@mdi/js"; import { HassEntities } from "home-assistant-js-websocket"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import "../../../components/ha-alert"; import "../../../components/ha-bar"; +import "../../../components/ha-button-menu"; import "../../../components/ha-metric"; import { updateCanInstall, UpdateEntity } from "../../../data/update"; import "../../../layouts/hass-subpage"; @@ -20,11 +23,14 @@ class HaConfigSectionUpdates extends LitElement { @property({ type: Boolean }) public narrow!: boolean; + @state() private _showSkipped = false; + private _notifyUpdates = false; protected render(): TemplateResult { const canInstallUpdates = this._filterUpdateEntitiesWithInstall( - this.hass.states + this.hass.states, + this._showSkipped ); return html` @@ -34,6 +40,22 @@ class HaConfigSectionUpdates extends LitElement { .narrow=${this.narrow} .header=${this.hass.localize("ui.panel.config.updates.caption")} > + + + + ${this._showSkipped + ? this.hass.localize("ui.panel.config.updates.hide_skipped") + : this.hass.localize("ui.panel.config.updates.show_skipped")} + +
    ${canInstallUpdates.length @@ -57,11 +79,18 @@ class HaConfigSectionUpdates extends LitElement { protected override updated(changedProps: PropertyValues): void { super.updated(changedProps); - if (!changedProps.has("hass") || !this._notifyUpdates) { + if ( + !changedProps.has("hass") || + !this._notifyUpdates || + !changedProps.has("_showSkipped") + ) { return; } this._notifyUpdates = false; - if (this._filterUpdateEntitiesWithInstall(this.hass.states).length) { + if ( + this._filterUpdateEntitiesWithInstall(this.hass.states, this._showSkipped) + .length + ) { showToast(this, { message: this.hass.localize( "ui.panel.config.updates.updates_refreshed" @@ -74,6 +103,10 @@ class HaConfigSectionUpdates extends LitElement { } } + private _toggleSkipped(): void { + this._showSkipped = !this._showSkipped; + } + private _filterUpdateEntities = memoizeOne((entities: HassEntities) => ( Object.values(entities).filter( @@ -106,9 +139,9 @@ class HaConfigSectionUpdates extends LitElement { ); private _filterUpdateEntitiesWithInstall = memoizeOne( - (entities: HassEntities) => + (entities: HassEntities, showSkipped: boolean) => this._filterUpdateEntities(entities).filter((entity) => - updateCanInstall(entity) + updateCanInstall(entity, showSkipped) ) ); diff --git a/src/translations/en.json b/src/translations/en.json index 8a6481ac1d..8e680621fa 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1134,7 +1134,9 @@ "unable_to_fetch": "Unable to load updates", "version_available": "Version {version_available} is available", "more_updates": "+{count} updates", - "show": "show" + "show": "show", + "show_skipped": "Show skipped", + "hide_skipped": "Hide skipped" }, "areas": { "caption": "Areas", From 8f2ed747e693652cfe629ca87d072ee98d04f626 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 12:53:02 -0500 Subject: [PATCH 066/181] Configuration Menu Cleanup items (#12413) --- src/components/ha-navigation-list.ts | 1 + src/panels/config/core/ha-config-section-analytics.ts | 5 +++++ src/panels/config/core/ha-config-section-storage.ts | 4 +--- src/panels/config/core/ha-config-system-navigation.ts | 3 ++- src/panels/config/dashboard/ha-config-dashboard.ts | 4 ++-- src/panels/config/ha-panel-config.ts | 9 +++++---- src/panels/config/logs/error-log-card.ts | 2 +- src/panels/config/logs/system-log-card.ts | 2 +- src/panels/config/network/ha-config-section-network.ts | 7 +++++-- .../config/server_control/ha-config-server-control.ts | 3 +++ src/panels/config/zone/dialog-core-zone-detail.ts | 5 ++++- src/translations/en.json | 5 +++-- 12 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index 4336bb2b1e..41200f14b5 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -81,6 +81,7 @@ class HaNavigationList extends LitElement { ha-clickable-list-item { cursor: pointer; font-size: var(--navigation-list-item-title-font-size); + padding: var(--navigation-list-item-padding) 0; } `; } diff --git a/src/panels/config/core/ha-config-section-analytics.ts b/src/panels/config/core/ha-config-section-analytics.ts index 200f1879b3..085e4691e5 100644 --- a/src/panels/config/core/ha-config-section-analytics.ts +++ b/src/panels/config/core/ha-config-section-analytics.ts @@ -33,6 +33,11 @@ class HaConfigSectionAnalytics extends LitElement { max-width: 1040px; margin: 0 auto; } + ha-config-analytics { + display: block; + max-width: 600px; + margin: 0 auto; + } `; } diff --git a/src/panels/config/core/ha-config-section-storage.ts b/src/panels/config/core/ha-config-section-storage.ts index 0d92424c96..7899861d87 100644 --- a/src/panels/config/core/ha-config-section-storage.ts +++ b/src/panels/config/core/ha-config-section-storage.ts @@ -38,6 +38,7 @@ class HaConfigSectionStorage extends LitElement { back-path="/config/system" .hass=${this.hass} .narrow=${this.narrow} + .header=${this.hass.localize("ui.panel.config.storage.caption")} >
    ${this._error @@ -110,9 +111,6 @@ class HaConfigSectionStorage extends LitElement { flex-direction: column; display: flex; } - .emmc { - --metric-bar-ok-color: #000; - } `; } diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 2bd3e86b18..7b29e93c5f 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -43,7 +43,7 @@ class HaConfigSystemNavigation extends LitElement { .isWide=${this.isWide} full-width > - + ${this.narrow ? html`
    ${this.hass.localize("ui.panel.config.dashboard.system.main")} @@ -103,6 +103,7 @@ class HaConfigSystemNavigation extends LitElement { ha-navigation-list { --navigation-list-item-title-font-size: 16px; + --navigation-list-item-padding: 4px; } `, ]; diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index b56c5be7c4..d26660345e 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -173,7 +173,7 @@ class HaConfigDashboard extends LitElement { full-width > ${canInstallUpdates.length - ? html` + ? html` ` : ""} - + ${this.narrow && canInstallUpdates.length ? html`
    ${this.hass.localize("panel.config")} diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 94ceca01b5..e837214464 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -4,13 +4,14 @@ import { mdiBadgeAccountHorizontal, mdiCellphoneCog, mdiCog, - mdiCpu32Bit, + mdiDatabase, mdiDevices, mdiInformation, mdiInformationOutline, mdiLightningBolt, mdiMapMarkerRadius, mdiMathLog, + mdiMemory, mdiNetwork, mdiNfcVariant, mdiPalette, @@ -99,7 +100,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { path: "/config/person", translationKey: "people", iconPath: mdiAccount, - iconColor: "#E48629", + iconColor: "#832EA6", components: ["person", "users"], }, { @@ -304,14 +305,14 @@ export const configSections: { [name: string]: PageNavigation[] } = { { path: "/config/storage", translationKey: "ui.panel.config.storage.caption", - iconPath: mdiServer, + iconPath: mdiDatabase, iconColor: "#518C43", component: "hassio", }, { path: "/config/hardware", translationKey: "ui.panel.config.hardware.caption", - iconPath: mdiCpu32Bit, + iconPath: mdiMemory, iconColor: "#301A8E", component: "hassio", }, diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index 190fdd9437..36ecf7b7c2 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -69,7 +69,7 @@ class ErrorLogCard extends LitElement { : ""} ${this._logHTML ? html` - +
    ${this.hass.userData?.showAdvanced && isComponentLoaded(this.hass, "hassio") diff --git a/src/panels/config/logs/system-log-card.ts b/src/panels/config/logs/system-log-card.ts index 112d61118d..6c1c6715c0 100644 --- a/src/panels/config/logs/system-log-card.ts +++ b/src/panels/config/logs/system-log-card.ts @@ -75,7 +75,7 @@ export class SystemLogCard extends LitElement { : []; return html`
    - + ${this._items === undefined ? html`
    diff --git a/src/panels/config/network/ha-config-section-network.ts b/src/panels/config/network/ha-config-section-network.ts index a8a162f18f..bdc61aaa0f 100644 --- a/src/panels/config/network/ha-config-section-network.ts +++ b/src/panels/config/network/ha-config-section-network.ts @@ -5,8 +5,8 @@ import "../../../layouts/hass-subpage"; import type { HomeAssistant, Route } from "../../../types"; import "./ha-config-network"; import "./ha-config-url-form"; -import "./supervisor-network"; import "./supervisor-hostname"; +import "./supervisor-network"; @customElement("ha-config-section-network") class HaConfigSectionNetwork extends LitElement { @@ -46,9 +46,12 @@ class HaConfigSectionNetwork extends LitElement { } supervisor-hostname, supervisor-network, - ha-config-url-form { + ha-config-url-form, + ha-config-network { display: block; + margin: 0 auto; margin-bottom: 24px; + max-width: 600px; } `; } diff --git a/src/panels/config/server_control/ha-config-server-control.ts b/src/panels/config/server_control/ha-config-server-control.ts index 03440dd2f5..9bf4a8a728 100644 --- a/src/panels/config/server_control/ha-config-server-control.ts +++ b/src/panels/config/server_control/ha-config-server-control.ts @@ -60,6 +60,7 @@ export class HaConfigServerControl extends LitElement { ${this.showAdvanced ? html` ${!canEdit ? html` diff --git a/src/translations/en.json b/src/translations/en.json index 8e680621fa..6be593f38e 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1435,7 +1435,7 @@ "introduction": "Manage your location, network and analytics.", "core_config": { "edit_requires_storage": "Editor disabled because config stored in configuration.yaml.", - "location_name": "Name of your Home Assistant installation", + "location_name": "Name", "latitude": "Latitude", "longitude": "Longitude", "elevation": "Elevation", @@ -2661,7 +2661,8 @@ "delete": "Delete", "create": "Add", "update": "Update" - } + }, + "core_location_dialog": "Home Assistant Location" }, "integrations": { "caption": "Integrations", From b70a523bdfb998584dda53aa49f2b94bab88e816 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 12:54:11 -0500 Subject: [PATCH 067/181] Backup Page - Will load which is available (#12414) --- src/panels/config/ha-panel-config.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index e837214464..2f1e3fd402 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -68,13 +68,6 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#E48629", components: ["zone"], }, - { - path: "/config/backup", - translationKey: "backup", - iconPath: mdiBackupRestore, - iconColor: "#4084CD", - component: "backup", - }, { path: "/hassio", translationKey: "supervisor", @@ -290,6 +283,13 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#0D47A1", component: "backup", }, + { + path: "/hassio/backups", + translationKey: "ui.panel.config.backup.caption", + iconPath: mdiBackupRestore, + iconColor: "#0D47A1", + component: "hassio", + }, { path: "/config/analytics", translationKey: "ui.panel.config.analytics.caption", From f8a52d250ef3cd9b1fe3eb8ebeeb658ca7a492cf Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 13:26:53 -0500 Subject: [PATCH 068/181] Move System Health to a page (#12412) --- src/panels/config/ha-panel-config.ts | 12 ++ src/panels/config/info/ha-config-info.ts | 3 - .../ha-config-system-health.ts} | 186 +++++++++--------- src/translations/en.json | 4 + 4 files changed, 110 insertions(+), 95 deletions(-) rename src/panels/config/{info/system-health-card.ts => system-health/ha-config-system-health.ts} (72%) diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 2f1e3fd402..a5b23c5af6 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -6,6 +6,7 @@ import { mdiCog, mdiDatabase, mdiDevices, + mdiHeart, mdiInformation, mdiInformationOutline, mdiLightningBolt, @@ -316,6 +317,13 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#301A8E", component: "hassio", }, + { + path: "/config/system_health", + translationKey: "ui.panel.config.system_health.caption", + iconPath: mdiHeart, + iconColor: "#507FfE", + component: "system_health", + }, ], about: [ { @@ -438,6 +446,10 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-section-storage", load: () => import("./core/ha-config-section-storage"), }, + system_health: { + tag: "ha-config-system-health", + load: () => import("./system-health/ha-config-system-health"), + }, updates: { tag: "ha-config-section-updates", load: () => import("./core/ha-config-section-updates"), diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index e27f489d92..aad0d589be 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -6,7 +6,6 @@ import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import "./integrations-card"; -import "./system-health-card"; const JS_TYPE = __BUILD__; const JS_VERSION = __VERSION__; @@ -131,7 +130,6 @@ class HaConfigInfo extends LitElement {

    - { @@ -37,15 +34,25 @@ const sortKeys = (a: string, b: string) => { return 0; }; -class SystemHealthCard extends LitElement { +@customElement("ha-config-system-health") +class HaConfigSystemHealth extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ type: Boolean }) public narrow!: boolean; + @state() private _info?: SystemHealthInfo; + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + + this.hass!.loadBackendTranslation("system_health"); + + subscribeSystemHealthInfo(this.hass!, (info) => { + this._info = info; + }); + } + protected render(): TemplateResult { - if (!this.hass) { - return html``; - } const sections: TemplateResult[] = []; if (!this._info) { @@ -139,57 +146,38 @@ class SystemHealthCard extends LitElement { } return html` - -

    -
    - ${domainToName(this.hass.localize, "system_health")} -
    - - - - ${this.hass.localize("ui.panel.config.info.copy_raw")} - - - ${this.hass.localize("ui.panel.config.info.copy_github")} - - -

    -
    ${sections}
    -
    + + + + + ${this.hass.localize("ui.panel.config.info.copy_raw")} + + + ${this.hass.localize("ui.panel.config.info.copy_github")} + + +
    + +
    ${sections}
    +
    +
    +
    `; } - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - - this.hass!.loadBackendTranslation("system_health"); - - if (!isComponentLoaded(this.hass!, "system_health")) { - this._info = { - system_health: { - info: { - error: this.hass.localize( - "ui.panel.config.info.system_health_error" - ), - }, - }, - }; - return; - } - - subscribeSystemHealthInfo(this.hass!, (info) => { - this._info = info; - }); - } - private async _copyInfo(ev: CustomEvent): Promise { const github = ev.detail.index === 1; let haContent: string | undefined; @@ -254,45 +242,59 @@ class SystemHealthCard extends LitElement { }); } - static get styles(): CSSResultGroup { - return css` - table { - width: 100%; - } + static styles: CSSResultGroup = css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + ha-card { + display: block; + max-width: 500px; + margin: 0 auto; + padding-bottom: 16px; + margin-bottom: max(24px, env(safe-area-inset-bottom)); + } + table { + width: 100%; + } - td:first-child { - width: 45%; - } + td:first-child { + width: 45%; + } - td:last-child { - direction: ltr; - } + td:last-child { + direction: ltr; + } - .loading-container { - display: flex; - align-items: center; - justify-content: center; - } + .loading-container { + display: flex; + align-items: center; + justify-content: center; + } - .card-header { - justify-content: space-between; - display: flex; - align-items: center; - } + .card-header { + justify-content: space-between; + display: flex; + align-items: center; + } - .error { - color: var(--error-color); - } + .error { + color: var(--error-color); + } - a { - color: var(--primary-color); - } + a { + color: var(--primary-color); + } - a.manage { - text-decoration: none; - } - `; - } + a.manage { + text-decoration: none; + } + `; } -customElements.define("system-health-card", SystemHealthCard); +declare global { + interface HTMLElementTagNameMap { + "ha-config-system-health": HaConfigSystemHealth; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 6be593f38e..813307db81 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1676,6 +1676,7 @@ } } }, + "automation": { "caption": "Automations", "description": "Create custom behavior rules for your home", @@ -3151,6 +3152,9 @@ "caption": "Storage", "used_space": "Used Space", "emmc_lifetime_used": "eMMC Lifetime Used" + }, + "system_health": { + "caption": "System Health" } }, "lovelace": { From a743e3bbba5d60720c9ca774700fc4ec44c5e2f2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 20:37:24 +0200 Subject: [PATCH 069/181] Show what updates are skipped (#12418) --- src/panels/config/core/ha-config-section-updates.ts | 4 ++++ src/panels/config/dashboard/ha-config-updates.ts | 7 +++++++ src/translations/en.json | 3 ++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 6a2c0ca399..352a5cbdfa 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -160,6 +160,10 @@ class HaConfigSectionUpdates extends LitElement { flex-direction: column; display: flex; } + ha-card { + margin-bottom: 24px; + margin-bottom: max(24px, env(safe-area-inset-bottom)); + } `; } diff --git a/src/panels/config/dashboard/ha-config-updates.ts b/src/panels/config/dashboard/ha-config-updates.ts index cb59250175..08406b0b50 100644 --- a/src/panels/config/dashboard/ha-config-updates.ts +++ b/src/panels/config/dashboard/ha-config-updates.ts @@ -42,6 +42,7 @@ class HaConfigUpdates extends LitElement { ${!this.narrow ? html`` : ""} @@ -96,6 +100,9 @@ class HaConfigUpdates extends LitElement { padding: 16px; padding-bottom: 0; } + .skipped { + background: var(--secondary-background-color); + } .icon { display: inline-flex; height: 100%; diff --git a/src/translations/en.json b/src/translations/en.json index 813307db81..e74e2aa172 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1136,7 +1136,8 @@ "more_updates": "+{count} updates", "show": "show", "show_skipped": "Show skipped", - "hide_skipped": "Hide skipped" + "hide_skipped": "Hide skipped", + "skipped": "Skipped" }, "areas": { "caption": "Areas", From cff2f856b3f38149df3cf9b8f30e35400f671e30 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 20:37:48 +0200 Subject: [PATCH 070/181] Don't show tabs in supervisor (#12417) --- hassio/src/dashboard/hassio-dashboard.ts | 32 +++++++++++++++++ hassio/src/hassio-tabs.ts | 45 +++++++++++++----------- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts index 917ff856a1..89cb7c500d 100644 --- a/hassio/src/dashboard/hassio-dashboard.ts +++ b/hassio/src/dashboard/hassio-dashboard.ts @@ -10,6 +10,7 @@ import { HomeAssistant, Route } from "../../../src/types"; import { supervisorTabs } from "../hassio-tabs"; import "./hassio-addons"; import "./hassio-update"; +import "../../../src/layouts/hass-subpage"; @customElement("hassio-dashboard") class HassioDashboard extends LitElement { @@ -22,6 +23,31 @@ class HassioDashboard extends LitElement { @property({ attribute: false }) public route!: Route; protected render(): TemplateResult { + if (atLeastVersion(this.hass.config.version, 2022, 5)) { + return html` + + + + + `; + } + return html` [ - { - translationKey: atLeastVersion(hass.config.version, 2021, 12) - ? "panel.addons" - : "panel.dashboard", - path: `/hassio/dashboard`, - iconPath: atLeastVersion(hass.config.version, 2021, 12) - ? mdiPuzzle - : mdiViewDashboard, - }, - { - translationKey: "panel.backups", - path: `/hassio/backups`, - iconPath: mdiBackupRestore, - }, - { - translationKey: "panel.system", - path: `/hassio/system`, - iconPath: mdiCogs, - }, -]; +export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] => + atLeastVersion(hass.config.version, 2022, 5) + ? [] + : [ + { + translationKey: atLeastVersion(hass.config.version, 2021, 12) + ? "panel.addons" + : "panel.dashboard", + path: `/hassio/dashboard`, + iconPath: atLeastVersion(hass.config.version, 2021, 12) + ? mdiPuzzle + : mdiViewDashboard, + }, + { + translationKey: "panel.backups", + path: `/hassio/backups`, + iconPath: mdiBackupRestore, + }, + { + translationKey: "panel.system", + path: `/hassio/system`, + iconPath: mdiCogs, + }, + ]; From e927091d210b15937deec40618df26cc219bc2b3 Mon Sep 17 00:00:00 2001 From: Netzwerkfehler <16437929+Netzwerkfehler@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:43:53 +0200 Subject: [PATCH 071/181] Better gauge segment coloring (#11570) --- src/panels/lovelace/cards/hui-gauge-card.ts | 30 +++++++++++++++++++ src/panels/lovelace/cards/types.ts | 6 ++++ .../config-elements/hui-gauge-card-editor.ts | 7 +++++ 3 files changed, 43 insertions(+) diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 5253f95b1e..4a6d1f60d5 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -174,6 +174,26 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { if (this._config!.needle) { return undefined; } + + // new format + let segments = this._config!.segments; + if (segments) { + segments = [...segments].sort((a, b) => a?.from - b?.from); + + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + if ( + segment && + numberValue >= segment.from && + (i + 1 === segments.length || numberValue < segments[i + 1]?.from) + ) { + return segment.color; + } + } + return severityMap.normal; + } + + // old format const sections = this._config!.severity; if (!sections) { @@ -206,6 +226,16 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { } private _severityLevels() { + // new format + const segments = this._config!.segments; + if (segments) { + return segments.map((segment) => ({ + level: segment?.from, + stroke: segment?.color, + })); + } + + // old format const sections = this._config!.severity; if (!sections) { diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 9c043e4699..b4c2a74abc 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -176,6 +176,11 @@ export interface SeverityConfig { red?: number; } +export interface GaugeSegment { + from: number; + color: string; +} + export interface GaugeCardConfig extends LovelaceCardConfig { entity: string; name?: string; @@ -185,6 +190,7 @@ export interface GaugeCardConfig extends LovelaceCardConfig { severity?: SeverityConfig; theme?: string; needle?: boolean; + segments?: GaugeSegment[]; } export interface ConfigEntity extends EntityConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts index 2469a18a5a..8ae5fcbf5e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -2,6 +2,7 @@ import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { + array, assert, assign, boolean, @@ -18,6 +19,11 @@ import type { GaugeCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +const gaugeSegmentStruct = object({ + from: number(), + color: string(), +}); + const cardConfigStruct = assign( baseLovelaceCardConfig, object({ @@ -29,6 +35,7 @@ const cardConfigStruct = assign( severity: optional(object()), theme: optional(string()), needle: optional(boolean()), + segments: optional(array(gaugeSegmentStruct)), }) ); From 1faf60444d550d8bedd4a1e2a0e4d63011c441f9 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 13:49:44 -0500 Subject: [PATCH 072/181] Move Data Disk Moving to Storage (#12416) --- src/panels/config/ha-panel-config.ts | 2 +- .../config/storage/dialog-move-datadisk.ts | 200 ++++++++++++++++++ .../ha-config-section-storage.ts | 92 +++++--- .../storage/show-dialog-move-datadisk.ts | 17 ++ src/translations/en.json | 14 +- 5 files changed, 291 insertions(+), 34 deletions(-) create mode 100644 src/panels/config/storage/dialog-move-datadisk.ts rename src/panels/config/{core => storage}/ha-config-section-storage.ts (51%) create mode 100644 src/panels/config/storage/show-dialog-move-datadisk.ts diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index a5b23c5af6..74836d5d34 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -444,7 +444,7 @@ class HaPanelConfig extends HassRouterPage { }, storage: { tag: "ha-config-section-storage", - load: () => import("./core/ha-config-section-storage"), + load: () => import("./storage/ha-config-section-storage"), }, system_health: { tag: "ha-config-system-health", diff --git a/src/panels/config/storage/dialog-move-datadisk.ts b/src/panels/config/storage/dialog-move-datadisk.ts new file mode 100644 index 0000000000..ad9dec13f4 --- /dev/null +++ b/src/panels/config/storage/dialog-move-datadisk.ts @@ -0,0 +1,200 @@ +import "@material/mwc-list/mwc-list-item"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-markdown"; +import "../../../components/ha-select"; +import { + extractApiErrorMessage, + ignoreSupervisorError, +} from "../../../data/hassio/common"; +import { + DatadiskList, + fetchHassioHassOsInfo, + HassioHassOSInfo, + HassioHostInfo, + listDatadisks, + moveDatadisk, +} from "../../../data/hassio/host"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { haStyle, haStyleDialog } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; +import { MoveDatadiskDialogParams } from "./show-dialog-move-datadisk"; + +const calculateMoveTime = memoizeOne((hostInfo: HassioHostInfo): number => { + const speed = hostInfo.disk_life_time !== "" ? 30 : 10; + const moveTime = (hostInfo.disk_used * 1000) / 60 / speed; + const rebootTime = (hostInfo.startup_time * 4) / 60; + return Math.ceil((moveTime + rebootTime) / 10) * 10; +}); + +@customElement("dialog-move-datadisk") +class MoveDatadiskDialog extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _hostInfo?: HassioHostInfo; + + @state() private _selectedDevice?: string; + + @state() private _devices?: DatadiskList["devices"]; + + @state() private _osInfo?: HassioHassOSInfo; + + @state() private _moving = false; + + public async showDialog( + dialogParams: MoveDatadiskDialogParams + ): Promise> { + this._hostInfo = dialogParams.hostInfo; + + try { + this._osInfo = await fetchHassioHassOsInfo(this.hass); + } catch (err: any) { + await showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.hardware.available_hardware.failed_to_get" + ), + text: extractApiErrorMessage(err), + }); + } + + listDatadisks(this.hass).then((data) => { + this._devices = data.devices; + }); + } + + public closeDialog(): void { + this._selectedDevice = undefined; + this._devices = undefined; + this._moving = false; + this._hostInfo = undefined; + this._osInfo = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._hostInfo || !this._osInfo) { + return html``; + } + return html` + + ${this._moving + ? html` + +

    + ${this.hass.localize( + "ui.panel.config.storage.datadisk.moving_desc" + )} +

    ` + : html` ${this._devices?.length + ? html` + ${this.hass.localize( + "ui.panel.config.storage.datadisk.description", + { + current_path: this._osInfo.data_disk, + time: calculateMoveTime(this._hostInfo), + } + )} +

    + + + ${this._devices.map( + (device) => + html`${device}` + )} + + ` + : this._devices === undefined + ? this.hass.localize( + "ui.panel.config.storage.datadisk.loading_devices" + ) + : this.hass.localize( + "ui.panel.config.storage.datadisk.no_devices" + )} + + + ${this.hass.localize("ui.panel.config.storage.datadisk.cancel")} + + + + ${this.hass.localize("ui.panel.config.storage.datadisk.move")} + `} +
    + `; + } + + private _select_device(ev) { + this._selectedDevice = ev.target.value; + } + + private async _moveDatadisk() { + this._moving = true; + try { + await moveDatadisk(this.hass, this._selectedDevice!); + } catch (err: any) { + if (this.hass.connection.connected && !ignoreSupervisorError(err)) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.storage.datadisk.failed_to_move" + ), + text: extractApiErrorMessage(err), + }); + this.closeDialog(); + } + } + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + ha-select { + width: 100%; + } + ha-circular-progress { + display: block; + margin: 32px; + text-align: center; + } + + .progress-text { + text-align: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-move-datadisk": MoveDatadiskDialog; + } +} diff --git a/src/panels/config/core/ha-config-section-storage.ts b/src/panels/config/storage/ha-config-section-storage.ts similarity index 51% rename from src/panels/config/core/ha-config-section-storage.ts rename to src/panels/config/storage/ha-config-section-storage.ts index 7899861d87..3fe07a365d 100644 --- a/src/panels/config/core/ha-config-section-storage.ts +++ b/src/panels/config/storage/ha-config-section-storage.ts @@ -2,7 +2,6 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-alert"; -import "../../../components/ha-bar"; import "../../../components/ha-metric"; import { fetchHassioHostInfo, HassioHostInfo } from "../../../data/hassio/host"; import "../../../layouts/hass-subpage"; @@ -11,7 +10,8 @@ import { getValueInPercentage, roundWithOneDecimal, } from "../../../util/calculate"; -import "./ha-config-analytics"; +import "../core/ha-config-analytics"; +import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk"; @customElement("ha-config-section-storage") class HaConfigSectionStorage extends LitElement { @@ -23,7 +23,7 @@ class HaConfigSectionStorage extends LitElement { @state() private _error?: { code: string; message: string }; - @state() private _storageData?: HassioHostInfo; + @state() private _hostInfo?: HassioHostInfo; protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); @@ -48,35 +48,44 @@ class HaConfigSectionStorage extends LitElement { > ` : ""} - ${this._storageData + ${this._hostInfo ? html` - - ${this._storageData.disk_life_time !== "" && - this._storageData.disk_life_time >= 10 - ? html` - - ` - : ""} +
    + + ${this._hostInfo.disk_life_time !== "" && + this._hostInfo.disk_life_time >= 10 + ? html` + + ` + : ""} +
    +
    + + ${this.hass.localize( + "ui.panel.config.storage.datadisk.title" + )} + +
    ` : ""} @@ -87,12 +96,18 @@ class HaConfigSectionStorage extends LitElement { private async _load() { try { - this._storageData = await fetchHassioHostInfo(this.hass); + this._hostInfo = await fetchHassioHostInfo(this.hass); } catch (err: any) { this._error = err.message || err; } } + private _moveDatadisk(): void { + showMoveDatadiskDialog(this, { + hostInfo: this._hostInfo!, + }); + } + private _getUsedSpace = (used: number, total: number) => roundWithOneDecimal(getValueInPercentage(used, 0, total)); @@ -103,7 +118,6 @@ class HaConfigSectionStorage extends LitElement { margin: 0 auto; } ha-card { - padding: 16px; max-width: 500px; margin: 0 auto; height: 100%; @@ -111,6 +125,20 @@ class HaConfigSectionStorage extends LitElement { flex-direction: column; display: flex; } + .card-actions { + height: 48px; + border-top: none; + display: flex; + justify-content: space-between; + align-items: center; + } + + .card-content { + display: flex; + justify-content: space-between; + flex-direction: column; + padding: 16px 16px 0 16px; + } `; } diff --git a/src/panels/config/storage/show-dialog-move-datadisk.ts b/src/panels/config/storage/show-dialog-move-datadisk.ts new file mode 100644 index 0000000000..11a3b296c1 --- /dev/null +++ b/src/panels/config/storage/show-dialog-move-datadisk.ts @@ -0,0 +1,17 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { HassioHostInfo } from "../../../data/hassio/host"; + +export interface MoveDatadiskDialogParams { + hostInfo: HassioHostInfo; +} + +export const showMoveDatadiskDialog = ( + element: HTMLElement, + dialogParams: MoveDatadiskDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-move-datadisk", + dialogImport: () => import("./dialog-move-datadisk"), + dialogParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index e74e2aa172..2b4b05c119 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3152,7 +3152,19 @@ "storage": { "caption": "Storage", "used_space": "Used Space", - "emmc_lifetime_used": "eMMC Lifetime Used" + "emmc_lifetime_used": "eMMC Lifetime Used", + "datadisk": { + "title": "Move datadisk", + "description": "You are currently using ''{current_path}'' as datadisk. Moving data disks will reboot your device and it's estimated to take {time} minutes. Your Home Assistant installation will not be accessible during this period. Do not disconnect the power during the move!", + "select_device": "Select new datadisk", + "no_devices": "No suitable attached devices found", + "moving_desc": "Rebooting and moving datadisk. Please have patience", + "moving": "Moving datadisk", + "loading_devices": "Loading devices", + "cancel": "[%key:ui::common::cancel%]", + "failed_to_move": "Failed to move datadisk", + "move": "Move" + } }, "system_health": { "caption": "System Health" From 86f39d1d439793d6a0852b916711acb1ef74efc9 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 22:14:32 +0200 Subject: [PATCH 073/181] Add supervisor, OS version info to about page (#12421) * Add supervisor, OS version info to about page * description * description --- src/panels/config/info/ha-config-info.ts | 80 ++++++++++++++++++------ src/translations/en.json | 6 +- 2 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index aad0d589be..6ca1a73e04 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -1,6 +1,14 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { property } from "lit/decorators"; +import { property, state } from "lit/decorators"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-logo-svg"; +import { + fetchHassioHostInfo, + fetchHassioHassOsInfo, + HassioHassOSInfo, + HassioHostInfo, +} from "../../../data/hassio/host"; +import { HassioInfo, fetchHassioInfo } from "../../../data/hassio/supervisor"; import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; @@ -21,6 +29,12 @@ class HaConfigInfo extends LitElement { @property() public route!: Route; + @state() private _hostInfo?: HassioHostInfo; + + @state() private _osInfo?: HassioHassOSInfo; + + @state() private _hassioInfo?: HassioInfo; + protected render(): TemplateResult { const hass = this.hass; const customUiList: Array<{ name: string; url: string; version: string }> = @@ -47,7 +61,19 @@ class HaConfigInfo extends LitElement {
    -

    Home Assistant ${hass.connection.haVersion}

    +

    Home Assistant Core ${hass.connection.haVersion}

    + ${this._hassioInfo + ? html`

    + Home Assistant Supervisor ${this._hassioInfo.supervisor} +

    ` + : ""} + ${this._osInfo + ? html`

    Home Assistant OS ${this._osInfo.version}

    ` + : ""} + ${this._hostInfo + ? html`

    Kernel version ${this._hostInfo.kernel}

    +

    Agent version ${this._hostInfo.agent_version}

    ` + : ""}

    ${this.hass.localize( "ui.panel.config.info.path_configuration", @@ -110,23 +136,21 @@ class HaConfigInfo extends LitElement { "type", JS_TYPE )} - ${ - customUiList.length > 0 - ? html` -

    - ${this.hass.localize("ui.panel.config.info.custom_uis")} - ${customUiList.map( - (item) => html` -
    - ${item.name}: ${item.version} -
    - ` - )} -
    - ` - : "" - } + ${customUiList.length > 0 + ? html` +
    + ${this.hass.localize("ui.panel.config.info.custom_uis")} + ${customUiList.map( + (item) => html` +
    + ${item.name}: + ${item.version} +
    + ` + )} +
    + ` + : ""}

    @@ -135,7 +159,7 @@ class HaConfigInfo extends LitElement { .narrow=${this.narrow} >
    - + `; } @@ -149,6 +173,22 @@ class HaConfigInfo extends LitElement { this.requestUpdate(); } }, 1000); + + if (isComponentLoaded(this.hass, "hassio")) { + this._loadSupervisorInfo(); + } + } + + private async _loadSupervisorInfo(): Promise { + const [hostInfo, osInfo, hassioInfo] = await Promise.all([ + fetchHassioHostInfo(this.hass), + fetchHassioHassOsInfo(this.hass), + fetchHassioInfo(this.hass), + ]); + + this._hassioInfo = hassioInfo; + this._osInfo = osInfo; + this._hostInfo = hostInfo; } static get styles(): CSSResultGroup { diff --git a/src/translations/en.json b/src/translations/en.json index 2b4b05c119..6179127c20 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1111,7 +1111,7 @@ }, "about": { "main": "About", - "secondary": "Version, system health and links to documentation" + "secondary": "Version, loaded integrations and links to documentation" } }, "common": { @@ -1486,11 +1486,11 @@ "board": "Board" }, "info": { - "caption": "Info", + "caption": "About", "copy_menu": "Copy menu", "copy_raw": "Raw Text", "copy_github": "For GitHub", - "description": "Version, system health and links to documentation", + "description": "Version, loaded integration and links to documentation", "home_assistant_logo": "Home Assistant logo", "path_configuration": "Path to configuration.yaml: {path}", "developed_by": "Developed by a bunch of awesome people.", From 2fceb0aeeedf38bb94d6b42a935a7b723954345c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 15:15:26 -0500 Subject: [PATCH 074/181] Allow for checking for updates (#12422) --- .../config/core/ha-config-section-updates.ts | 73 +++++++++++++++---- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 352a5cbdfa..3207620850 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -11,6 +11,7 @@ import "../../../components/ha-bar"; import "../../../components/ha-button-menu"; import "../../../components/ha-metric"; import { updateCanInstall, UpdateEntity } from "../../../data/update"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; import type { HomeAssistant } from "../../../types"; import { showToast } from "../../../util/toast"; @@ -58,18 +59,25 @@ class HaConfigSectionUpdates extends LitElement {
    - ${canInstallUpdates.length - ? html` - - ` - : html` - ${this.hass.localize("ui.panel.config.updates.no_updates")} - `} +
    + ${canInstallUpdates.length + ? html` + + ` + : html` + ${this.hass.localize("ui.panel.config.updates.no_updates")} + `} +
    +
    + + ${this.hass.localize("ui.panel.config.updates.check_updates")} + +
    @@ -107,6 +115,29 @@ class HaConfigSectionUpdates extends LitElement { this._showSkipped = !this._showSkipped; } + private async _checkUpdates(): Promise { + const _entities = this._filterUpdateEntities(this.hass.states).map( + (entity) => entity.entity_id + ); + + if (_entities.length) { + this._notifyUpdates = true; + await this.hass.callService("homeassistant", "update_entity", { + entity_id: _entities, + }); + return; + } + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.updates.no_update_entities.title" + ), + text: this.hass.localize( + "ui.panel.config.updates.no_update_entities.description" + ), + warning: true, + }); + } + private _filterUpdateEntities = memoizeOne((entities: HassEntities) => ( Object.values(entities).filter( @@ -152,18 +183,28 @@ class HaConfigSectionUpdates extends LitElement { margin: 0 auto; } ha-card { - padding: 16px; max-width: 500px; margin: 0 auto; height: 100%; justify-content: space-between; flex-direction: column; display: flex; - } - ha-card { - margin-bottom: 24px; margin-bottom: max(24px, env(safe-area-inset-bottom)); } + .card-actions { + height: 48px; + border-top: none; + display: flex; + justify-content: space-between; + align-items: center; + } + + .card-content { + display: flex; + justify-content: space-between; + flex-direction: column; + padding: 16px 16px 0 16px; + } `; } From ca28178b86edfe4ff20a198553e9a449d2ab1b60 Mon Sep 17 00:00:00 2001 From: Artem Sorokin Date: Mon, 25 Apr 2022 23:26:05 +0300 Subject: [PATCH 075/181] Fix title and description for menu step in options flow (#12420) --- src/dialogs/config-flow/show-dialog-options-flow.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dialogs/config-flow/show-dialog-options-flow.ts b/src/dialogs/config-flow/show-dialog-options-flow.ts index 14adfcb9e4..560d9a2946 100644 --- a/src/dialogs/config-flow/show-dialog-options-flow.ts +++ b/src/dialogs/config-flow/show-dialog-options-flow.ts @@ -146,14 +146,14 @@ export const showOptionsFlowDialog = ( renderMenuHeader(hass, step) { return ( hass.localize( - `component.${step.handler}.option.step.${step.step_id}.title` - ) || hass.localize(`component.${step.handler}.title`) + `component.${configEntry.domain}.options.step.${step.step_id}.title` + ) || hass.localize(`component.${configEntry.domain}.title`) ); }, renderMenuDescription(hass, step) { const description = hass.localize( - `component.${step.handler}.option.step.${step.step_id}.description`, + `component.${configEntry.domain}.options.step.${step.step_id}.description`, step.description_placeholders ); return description @@ -169,7 +169,7 @@ export const showOptionsFlowDialog = ( renderMenuOption(hass, step, option) { return hass.localize( - `component.${step.handler}.options.step.${step.step_id}.menu_options.${option}`, + `component.${configEntry.domain}.options.step.${step.step_id}.menu_options.${option}`, step.description_placeholders ); }, From 490f84a7b11a3b5a79c190a41e5258b7ef9166ba Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 25 Apr 2022 22:56:34 +0200 Subject: [PATCH 076/181] link to updates page (#12423) --- .../config/dashboard/ha-config-dashboard.ts | 32 +++++++++++++++---- .../config/dashboard/ha-config-updates.ts | 23 +++---------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index d26660345e..5eaa08aaa6 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -131,9 +131,8 @@ class HaConfigDashboard extends LitElement { }); protected render(): TemplateResult { - const canInstallUpdates = this._filterUpdateEntitiesWithInstall( - this.hass.states - ); + const [canInstallUpdates, totalUpdates] = + this._filterUpdateEntitiesWithInstall(this.hass.states); return html` @@ -177,8 +176,19 @@ class HaConfigDashboard extends LitElement { + ${totalUpdates > canInstallUpdates.length + ? html` + ${this.hass.localize( + "ui.panel.config.updates.more_updates", + { + count: totalUpdates - canInstallUpdates.length, + } + )} + ` + : ""}
    ` : ""} @@ -259,10 +269,15 @@ class HaConfigDashboard extends LitElement { ); private _filterUpdateEntitiesWithInstall = memoizeOne( - (entities: HassEntities) => - this._filterUpdateEntities(entities).filter((entity) => + (entities: HassEntities): [UpdateEntity[], number] => { + const updates = this._filterUpdateEntities(entities).filter((entity) => updateCanInstall(entity) - ) + ); + return [ + updates.slice(0, updates.length === 3 ? updates.length : 2), + updates.length, + ]; + } ); private _showQuickBar(): void { @@ -320,6 +335,11 @@ class HaConfigDashboard extends LitElement { text-decoration: none; color: var(--primary-text-color); } + a.button { + display: block; + color: var(--primary-color); + padding: 16px; + } .title { font-size: 16px; padding: 16px; diff --git a/src/panels/config/dashboard/ha-config-updates.ts b/src/panels/config/dashboard/ha-config-updates.ts index 08406b0b50..f2d823bd01 100644 --- a/src/panels/config/dashboard/ha-config-updates.ts +++ b/src/panels/config/dashboard/ha-config-updates.ts @@ -19,22 +19,20 @@ class HaConfigUpdates extends LitElement { @property({ attribute: false }) public updateEntities?: UpdateEntity[]; - @property({ type: Boolean, reflect: true }) showAll = false; + @property({ type: Number }) + public total?: number; protected render(): TemplateResult { if (!this.updateEntities?.length) { return html``; } - const updates = - this.showAll || this.updateEntities.length <= 3 - ? this.updateEntities - : this.updateEntities.slice(0, 2); + const updates = this.updateEntities; return html`
    ${this.hass.localize("ui.panel.config.updates.title", { - count: this.updateEntities.length, + count: this.total || this.updateEntities.length, })}
    ${updates.map( @@ -70,15 +68,6 @@ class HaConfigUpdates extends LitElement { ` )} - ${!this.showAll && this.updateEntities.length >= 4 - ? html` - - ` - : ""} `; } @@ -88,10 +77,6 @@ class HaConfigUpdates extends LitElement { }); } - private _showAllClicked() { - this.showAll = true; - } - static get styles(): CSSResultGroup[] { return [ css` From e8c30cabcae4eca084a1c95a891b97ff4614ea7c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 18:24:58 -0500 Subject: [PATCH 077/181] Show usage stats in System Health (#12424) --- src/common/util/subscribe-polling.ts | 18 +++ src/panels/config/ha-panel-config.ts | 2 +- .../system-health/ha-config-system-health.ts | 143 +++++++++++++++--- src/translations/en.json | 7 +- 4 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 src/common/util/subscribe-polling.ts diff --git a/src/common/util/subscribe-polling.ts b/src/common/util/subscribe-polling.ts new file mode 100644 index 0000000000..e4770be8ec --- /dev/null +++ b/src/common/util/subscribe-polling.ts @@ -0,0 +1,18 @@ +import { HomeAssistant } from "../../types"; + +export const subscribePollingCollection = ( + hass: HomeAssistant, + updateData: (hass: HomeAssistant) => void, + interval: number +) => { + let timeout; + const fetchData = async () => { + try { + await updateData(hass); + } finally { + timeout = setTimeout(() => fetchData(), interval); + } + }; + fetchData(); + return () => clearTimeout(timeout); +}; diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 74836d5d34..81b97537ef 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -322,7 +322,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { translationKey: "ui.panel.config.system_health.caption", iconPath: mdiHeart, iconColor: "#507FfE", - component: "system_health", + components: ["system_health", "hassio"], }, ], about: [ diff --git a/src/panels/config/system-health/ha-config-system-health.ts b/src/panels/config/system-health/ha-config-system-health.ts index 2df1526a38..48103bdc33 100644 --- a/src/panels/config/system-health/ha-config-system-health.ts +++ b/src/panels/config/system-health/ha-config-system-health.ts @@ -1,13 +1,19 @@ import { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiContentCopy } from "@mdi/js"; +import { UnsubscribeFunc } from "home-assistant-js-websocket/dist/types"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { formatDateTime } from "../../../common/datetime/format_date_time"; import { copyToClipboard } from "../../../common/util/copy-clipboard"; +import { subscribePollingCollection } from "../../../common/util/subscribe-polling"; +import "../../../components/ha-alert"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; import "../../../components/ha-circular-progress"; +import "../../../components/ha-metric"; +import { fetchHassioStats, HassioStats } from "../../../data/hassio/common"; import { domainToName } from "../../../data/integration"; import { subscribeSystemHealthInfo, @@ -15,6 +21,7 @@ import { SystemHealthInfo, } from "../../../data/system_health"; import "../../../layouts/hass-subpage"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../types"; import { showToast } from "../../../util/toast"; @@ -35,21 +42,52 @@ const sortKeys = (a: string, b: string) => { }; @customElement("ha-config-system-health") -class HaConfigSystemHealth extends LitElement { +class HaConfigSystemHealth extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean }) public narrow!: boolean; @state() private _info?: SystemHealthInfo; + @state() private _supervisorStats?: HassioStats; + + @state() private _coreStats?: HassioStats; + + @state() private _error?: { code: string; message: string }; + + public hassSubscribe(): Array> { + const subs: Array> = []; + if (isComponentLoaded(this.hass, "system_health")) { + subs.push( + subscribeSystemHealthInfo(this.hass!, (info) => { + this._info = info; + }) + ); + } + + if (isComponentLoaded(this.hass, "hassio")) { + subs.push( + subscribePollingCollection( + this.hass, + async () => { + this._supervisorStats = await fetchHassioStats( + this.hass, + "supervisor" + ); + this._coreStats = await fetchHassioStats(this.hass, "core"); + }, + 10000 + ) + ); + } + + return subs; + } + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); this.hass!.loadBackendTranslation("system_health"); - - subscribeSystemHealthInfo(this.hass!, (info) => { - this._info = info; - }); } protected render(): TemplateResult { @@ -152,27 +190,88 @@ class HaConfigSystemHealth extends LitElement { back-path="/config/system" .header=${this.hass.localize("ui.panel.config.system_health.caption")} > - - - - ${this.hass.localize("ui.panel.config.info.copy_raw")} - - - ${this.hass.localize("ui.panel.config.info.copy_github")} - - + ${this._error + ? html` + ${this._error.message || this._error.code} + ` + : ""} + ${this._info + ? html` + + + + ${this.hass.localize("ui.panel.config.info.copy_raw")} + + + ${this.hass.localize("ui.panel.config.info.copy_github")} + + + ` + : ""}
    ${sections}
    + ${!this._coreStats && !this._supervisorStats + ? "" + : html` + +
    + ${this._coreStats + ? html` +

    + ${this.hass.localize( + "ui.panel.config.system_health.core_stats" + )} +

    + + + ` + : ""} + ${this._supervisorStats + ? html` +

    + ${this.hass.localize( + "ui.panel.config.system_health.supervisor_stats" + )} +

    + + + ` + : ""} +
    +
    + `}
    `; diff --git a/src/translations/en.json b/src/translations/en.json index 6179127c20..09f01e1a1b 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1677,7 +1677,6 @@ } } }, - "automation": { "caption": "Automations", "description": "Create custom behavior rules for your home", @@ -3167,7 +3166,11 @@ } }, "system_health": { - "caption": "System Health" + "caption": "System Health", + "cpu_usage": "CPU Usage", + "ram_usage": "RAM Usage", + "core_stats": "Core Stats", + "supervisor_stats": "Supervisor Stats" } }, "lovelace": { From 876fd9e85a62292dbd2829539dbb5f6947579e35 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 25 Apr 2022 18:25:07 -0500 Subject: [PATCH 078/181] Bumped version to 20220425.0 (#12425) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f397c0f797..ecacd56aa2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220424.0 +version = 20220425.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 145e5d7bc6f639a29c4f37b9a4aacc2b0093f3af Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 25 Apr 2022 16:07:11 -1000 Subject: [PATCH 079/181] Format sensors with state class duration (#12426) --- src/common/datetime/duration.ts | 16 +++++++++ src/common/entity/compute_state_display.ts | 16 +++++++++ test/common/datetime/duration.ts | 34 +++++++++++++++++++ .../datetime/seconds_to_duration_test.ts | 1 + 4 files changed, 67 insertions(+) create mode 100644 src/common/datetime/duration.ts create mode 100644 test/common/datetime/duration.ts diff --git a/src/common/datetime/duration.ts b/src/common/datetime/duration.ts new file mode 100644 index 0000000000..2d6c46ff87 --- /dev/null +++ b/src/common/datetime/duration.ts @@ -0,0 +1,16 @@ +import secondsToDuration from "./seconds_to_duration"; + +const DAY_IN_SECONDS = 86400; +const HOUR_IN_SECONDS = 3600; +const MINUTE_IN_SECONDS = 60; + +export const UNIT_TO_SECOND_CONVERT = { + s: 1, + min: MINUTE_IN_SECONDS, + h: HOUR_IN_SECONDS, + d: DAY_IN_SECONDS, +}; + +export const formatDuration = (duration: string, units: string): string => + secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) || + "0"; diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index ed890603a9..d796a80e64 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -13,6 +13,7 @@ import { formatNumber, isNumericState } from "../number/format_number"; import { LocalizeFunc } from "../translations/localize"; import { computeStateDomain } from "./compute_state_domain"; import { supportsFeature } from "./supports-feature"; +import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; export const computeStateDisplay = ( localize: LocalizeFunc, @@ -28,6 +29,21 @@ export const computeStateDisplay = ( // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` if (isNumericState(stateObj)) { + // state is duration + if ( + stateObj.attributes.device_class === "duration" && + stateObj.attributes.unit_of_measurement && + UNIT_TO_SECOND_CONVERT[stateObj.attributes.unit_of_measurement] + ) { + try { + return formatDuration( + compareState, + stateObj.attributes.unit_of_measurement + ); + } catch (_err) { + // fallback to default + } + } if (stateObj.attributes.device_class === "monetary") { try { return formatNumber(compareState, locale, { diff --git a/test/common/datetime/duration.ts b/test/common/datetime/duration.ts new file mode 100644 index 0000000000..3e3a037451 --- /dev/null +++ b/test/common/datetime/duration.ts @@ -0,0 +1,34 @@ +import { assert } from "chai"; + +import { formatDuration } from "../../../src/common/datetime/duration"; + +describe("formatDuration", () => { + it("works", () => { + assert.strictEqual(formatDuration("0", "s"), "0"); + assert.strictEqual(formatDuration("65", "s"), "1:05"); + assert.strictEqual(formatDuration("3665", "s"), "1:01:05"); + assert.strictEqual(formatDuration("39665", "s"), "11:01:05"); + assert.strictEqual(formatDuration("932093", "s"), "258:54:53"); + + assert.strictEqual(formatDuration("0", "min"), "0"); + assert.strictEqual(formatDuration("65", "min"), "1:05:00"); + assert.strictEqual(formatDuration("3665", "min"), "61:05:00"); + assert.strictEqual(formatDuration("39665", "min"), "661:05:00"); + assert.strictEqual(formatDuration("932093", "min"), "15534:53:00"); + assert.strictEqual(formatDuration("12.4", "min"), "12:24"); + + assert.strictEqual(formatDuration("0", "h"), "0"); + assert.strictEqual(formatDuration("65", "h"), "65:00:00"); + assert.strictEqual(formatDuration("3665", "h"), "3665:00:00"); + assert.strictEqual(formatDuration("39665", "h"), "39665:00:00"); + assert.strictEqual(formatDuration("932093", "h"), "932093:00:00"); + assert.strictEqual(formatDuration("24.3", "h"), "24:18:00"); + assert.strictEqual(formatDuration("24.32423", "h"), "24:19:27"); + + assert.strictEqual(formatDuration("0", "d"), "0"); + assert.strictEqual(formatDuration("65", "d"), "1560:00:00"); + assert.strictEqual(formatDuration("3665", "d"), "87960:00:00"); + assert.strictEqual(formatDuration("39665", "d"), "951960:00:00"); + assert.strictEqual(formatDuration("932093", "d"), "22370232:00:00"); + }); +}); diff --git a/test/common/datetime/seconds_to_duration_test.ts b/test/common/datetime/seconds_to_duration_test.ts index 33f7a9a9a5..3b361a8635 100644 --- a/test/common/datetime/seconds_to_duration_test.ts +++ b/test/common/datetime/seconds_to_duration_test.ts @@ -8,5 +8,6 @@ describe("secondsToDuration", () => { assert.strictEqual(secondsToDuration(65), "1:05"); assert.strictEqual(secondsToDuration(3665), "1:01:05"); assert.strictEqual(secondsToDuration(39665), "11:01:05"); + assert.strictEqual(secondsToDuration(932093), "258:54:53"); }); }); From 95d494a54cae5691dbbb4b192a0fd0d139329528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 26 Apr 2022 12:18:43 +0200 Subject: [PATCH 080/181] Guard against non OS installation (#12427) --- src/panels/config/info/ha-config-info.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index 6ca1a73e04..93c55db689 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -67,7 +67,7 @@ class HaConfigInfo extends LitElement { Home Assistant Supervisor ${this._hassioInfo.supervisor} ` : ""} - ${this._osInfo + ${this._osInfo?.version ? html`

    Home Assistant OS ${this._osInfo.version}

    ` : ""} ${this._hostInfo From 3081425ccda6bdab00e6dc5bc9127a266a5cdbbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Hansl=C3=ADk?= Date: Tue, 26 Apr 2022 12:20:26 +0200 Subject: [PATCH 081/181] Typo in en.json (#12428) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 09f01e1a1b..cd5f4d079f 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1490,7 +1490,7 @@ "copy_menu": "Copy menu", "copy_raw": "Raw Text", "copy_github": "For GitHub", - "description": "Version, loaded integration and links to documentation", + "description": "Version, loaded integrations and links to documentation", "home_assistant_logo": "Home Assistant logo", "path_configuration": "Path to configuration.yaml: {path}", "developed_by": "Developed by a bunch of awesome people.", From 7363838f86286c4c3d852f40cd77f8527501b971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 26 Apr 2022 12:24:55 +0200 Subject: [PATCH 082/181] Move unsupported and unhealthy alerts (#12431) --- hassio/src/system/hassio-supervisor-info.ts | 9 +- .../system-health/ha-config-system-health.ts | 109 ++++++++++++++++++ src/translations/en.json | 32 +++++ 3 files changed, 145 insertions(+), 5 deletions(-) diff --git a/hassio/src/system/hassio-supervisor-info.ts b/hassio/src/system/hassio-supervisor-info.ts index ed2e0de246..6df0affafc 100644 --- a/hassio/src/system/hassio-supervisor-info.ts +++ b/hassio/src/system/hassio-supervisor-info.ts @@ -23,6 +23,10 @@ import { showAlertDialog, showConfirmationDialog, } from "../../../src/dialogs/generic/show-dialog-box"; +import { + UNHEALTHY_REASON_URL, + UNSUPPORTED_REASON_URL, +} from "../../../src/panels/config/system-health/ha-config-system-health"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant } from "../../../src/types"; import { bytesToString } from "../../../src/util/bytes-to-string"; @@ -30,11 +34,6 @@ import { documentationUrl } from "../../../src/util/documentation-url"; import "../components/supervisor-metric"; import { hassioStyle } from "../resources/hassio-style"; -const UNSUPPORTED_REASON_URL = {}; -const UNHEALTHY_REASON_URL = { - privileged: "/more-info/unsupported/privileged", -}; - @customElement("hassio-supervisor-info") class HassioSupervisorInfo extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; diff --git a/src/panels/config/system-health/ha-config-system-health.ts b/src/panels/config/system-health/ha-config-system-health.ts index 48103bdc33..8c15021747 100644 --- a/src/panels/config/system-health/ha-config-system-health.ts +++ b/src/panels/config/system-health/ha-config-system-health.ts @@ -14,15 +14,21 @@ import "../../../components/ha-card"; import "../../../components/ha-circular-progress"; import "../../../components/ha-metric"; import { fetchHassioStats, HassioStats } from "../../../data/hassio/common"; +import { + fetchHassioResolution, + HassioResolution, +} from "../../../data/hassio/resolution"; import { domainToName } from "../../../data/integration"; import { subscribeSystemHealthInfo, SystemCheckValueObject, SystemHealthInfo, } from "../../../data/system_health"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; const sortKeys = (a: string, b: string) => { @@ -41,6 +47,11 @@ const sortKeys = (a: string, b: string) => { return 0; }; +export const UNSUPPORTED_REASON_URL = {}; +export const UNHEALTHY_REASON_URL = { + privileged: "/more-info/unsupported/privileged", +}; + @customElement("ha-config-system-health") class HaConfigSystemHealth extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; @@ -51,6 +62,8 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { @state() private _supervisorStats?: HassioStats; + @state() private _resolutionInfo?: HassioResolution; + @state() private _coreStats?: HassioStats; @state() private _error?: { code: string; message: string }; @@ -79,6 +92,9 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { 10000 ) ); + fetchHassioResolution(this.hass).then((data) => { + this._resolutionInfo = data; + }); } return subs; @@ -219,6 +235,35 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { ` : ""}
    + ${this._resolutionInfo + ? html`${this._resolutionInfo.unhealthy.length + ? html` + ${this.hass.localize("ui.dialogs.unhealthy.title")} + + ` + : ""} + ${this._resolutionInfo.unsupported.length + ? html` + ${this.hass.localize("ui.dialogs.unsupported.title")} + + + ` + : ""} ` + : ""} +
    ${sections}
    @@ -277,6 +322,64 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { `; } + private async _unsupportedDialog(): Promise { + await showAlertDialog(this, { + title: this.hass.localize("ui.dialogs.unsupported.title"), + text: html`${this.hass.localize("ui.dialogs.unsupported.description")} +

    + `, + }); + } + + private async _unhealthyDialog(): Promise { + await showAlertDialog(this, { + title: this.hass.localize("ui.dialogs.unhealthy.title"), + text: html`${this.hass.localize("ui.dialogs.unhealthy.description")} +

    + `, + }); + } + private async _copyInfo(ev: CustomEvent): Promise { const github = ev.detail.index === 1; let haContent: string | undefined; @@ -354,6 +457,12 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { padding-bottom: 16px; margin-bottom: max(24px, env(safe-area-inset-bottom)); } + ha-alert { + display: block; + max-width: 500px; + margin: 0 auto; + margin-bottom: max(24px, env(safe-area-inset-bottom)); + } table { width: 100%; } diff --git a/src/translations/en.json b/src/translations/en.json index cd5f4d079f..cb884a4a66 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -994,6 +994,38 @@ "recent_tx_messages": "{n} most recently transmitted message(s)", "show_as_yaml": "Show as YAML", "triggers": "Triggers" + }, + "unsupported": { + "title": "[%key:supervisor::system::supervisor::unsupported_title%]", + "description": "[%key:supervisor::system::supervisor::unsupported_description%]", + "reasons": { + "apparmor": "[%key:supervisor::system::supervisor::unsupported_reason::apparmor%]", + "content_trust": "[%key:supervisor::system::supervisor::unsupported_reason::content_trust%]", + "dbus": "[%key:supervisor::system::supervisor::unsupported_reason::dbus%]", + "docker_configuration": "[%key:supervisor::system::supervisor::unsupported_reason::docker_configuration%]", + "docker_version": "[%key:supervisor::system::supervisor::unsupported_reason::docker_version%]", + "job_conditions": "[%key:supervisor::system::supervisor::unsupported_reason::job_conditions%]", + "lxc": "[%key:supervisor::system::supervisor::unsupported_reason::lxc%]", + "network_manager": "[%key:supervisor::system::supervisor::unsupported_reason::network_manager%]", + "os": "[%key:supervisor::system::supervisor::unsupported_reason::os%]", + "os_agent": "[%key:supervisor::system::supervisor::unsupported_reason::os_agent%]", + "privileged": "[%key:supervisor::system::supervisor::unsupported_reason::privileged%]", + "software": "[%key:supervisor::system::supervisor::unsupported_reason::software%]", + "source_mods": "[%key:supervisor::system::supervisor::unsupported_reason::source_mods%]", + "systemd": "[%key:supervisor::system::supervisor::unsupported_reason::systemd%]", + "systemd_resolved": "[%key:supervisor::system::supervisor::unsupported_reason::systemd_resolved%]" + } + }, + "unhealthy": { + "title": "[%key:supervisor::system::supervisor::unhealthy_title%]", + "description": "[%key:supervisor::system::supervisor::unhealthy_description%]", + "reasons": { + "privileged": "[%key:supervisor::system::supervisor::unhealthy_reason::privileged%]", + "supervisor": "[%key:supervisor::system::supervisor::unhealthy_reason::supervisor%]", + "setup": "[%key:supervisor::system::supervisor::unhealthy_reason::setup%]", + "docker": "[%key:supervisor::system::supervisor::unhealthy_reason::docker%]", + "untrusted": "[%key:supervisor::system::supervisor::unhealthy_reason::untrusted%]" + } } }, "duration": { From c34fe184e8742c221ebca028b69fe8772aa31320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 26 Apr 2022 13:09:39 +0200 Subject: [PATCH 083/181] Fix log syntax highlight when fetching logs from supervisor (#12430) --- hassio/src/addon-view/log/hassio-addon-logs.ts | 6 +++--- hassio/src/system/hassio-supervisor-log.ts | 6 +++--- .../components/ha-ansi-to-html.ts | 6 +++--- src/onboarding/onboarding-restore-backup.ts | 4 ++-- src/panels/config/logs/error-log-card.ts | 7 ++++++- 5 files changed, 17 insertions(+), 12 deletions(-) rename hassio/src/components/hassio-ansi-to-html.ts => src/components/ha-ansi-to-html.ts (97%) diff --git a/hassio/src/addon-view/log/hassio-addon-logs.ts b/hassio/src/addon-view/log/hassio-addon-logs.ts index 133e888bb9..7104e7ed50 100644 --- a/hassio/src/addon-view/log/hassio-addon-logs.ts +++ b/hassio/src/addon-view/log/hassio-addon-logs.ts @@ -2,6 +2,7 @@ import "@material/mwc-button"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import "../../../../src/components/ha-alert"; +import "../../../../src/components/ha-ansi-to-html"; import "../../../../src/components/ha-card"; import { fetchHassioAddonLogs, @@ -11,7 +12,6 @@ import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { haStyle } from "../../../../src/resources/styles"; import { HomeAssistant } from "../../../../src/types"; -import "../../components/hassio-ansi-to-html"; import { hassioStyle } from "../../resources/hassio-style"; @customElement("hassio-addon-logs") @@ -40,9 +40,9 @@ class HassioAddonLogs extends LitElement { : ""}
    ${this._content - ? html`` + >` : ""}
    diff --git a/hassio/src/system/hassio-supervisor-log.ts b/hassio/src/system/hassio-supervisor-log.ts index a18f3c1d3b..c3f80e12a6 100644 --- a/hassio/src/system/hassio-supervisor-log.ts +++ b/hassio/src/system/hassio-supervisor-log.ts @@ -1,3 +1,4 @@ +import "../../../src/components/ha-ansi-to-html"; import "@material/mwc-button"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -11,7 +12,6 @@ import { Supervisor } from "../../../src/data/supervisor/supervisor"; import "../../../src/layouts/hass-loading-screen"; import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant } from "../../../src/types"; -import "../components/hassio-ansi-to-html"; import { hassioStyle } from "../resources/hassio-style"; interface LogProvider { @@ -89,8 +89,8 @@ class HassioSupervisorLog extends LitElement {
    ${this._content - ? html` - ` + ? html` + ` : html``}
    diff --git a/hassio/src/components/hassio-ansi-to-html.ts b/src/components/ha-ansi-to-html.ts similarity index 97% rename from hassio/src/components/hassio-ansi-to-html.ts rename to src/components/ha-ansi-to-html.ts index 60c8f51532..c28fff8b05 100644 --- a/hassio/src/components/hassio-ansi-to-html.ts +++ b/src/components/ha-ansi-to-html.ts @@ -10,8 +10,8 @@ interface State { backgroundColor: null | string; } -@customElement("hassio-ansi-to-html") -class HassioAnsiToHtml extends LitElement { +@customElement("ha-ansi-to-html") +class HaAnsiToHtml extends LitElement { @property() public content!: string; protected render(): TemplateResult | void { @@ -241,6 +241,6 @@ class HassioAnsiToHtml extends LitElement { declare global { interface HTMLElementTagNameMap { - "hassio-ansi-to-html": HassioAnsiToHtml; + "ha-ansi-to-html": HaAnsiToHtml; } } diff --git a/src/onboarding/onboarding-restore-backup.ts b/src/onboarding/onboarding-restore-backup.ts index 658593f5c9..49dbf5db83 100644 --- a/src/onboarding/onboarding-restore-backup.ts +++ b/src/onboarding/onboarding-restore-backup.ts @@ -1,11 +1,11 @@ import "@material/mwc-button/mwc-button"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; -import "../../hassio/src/components/hassio-ansi-to-html"; import { showBackupUploadDialog } from "../../hassio/src/dialogs/backup/show-dialog-backup-upload"; import { showHassioBackupDialog } from "../../hassio/src/dialogs/backup/show-dialog-hassio-backup"; import type { LocalizeFunc } from "../common/translations/localize"; import "../components/ha-card"; +import "../components/ha-ansi-to-html"; import { fetchInstallationType } from "../data/onboarding"; import { makeDialogManager } from "../dialogs/make-dialog-manager"; import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin"; @@ -86,7 +86,7 @@ class OnboardingRestoreBackup extends ProvideHassLitMixin(LitElement) { padding: 4px; margin-top: 8px; } - hassio-ansi-to-html { + ha-ansi-to-html { display: block; line-height: 22px; padding: 0 8px; diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index 36ecf7b7c2..1ad78d38ae 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -12,6 +12,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-alert"; +import "../../../components/ha-ansi-to-html"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; import "../../../components/ha-select"; @@ -55,7 +56,7 @@ class ErrorLogCard extends LitElement { @state() private _isLogLoaded = false; - @state() private _logHTML!: TemplateResult[] | string; + @state() private _logHTML!: TemplateResult[] | TemplateResult | string; @state() private _error?: string; @@ -158,6 +159,10 @@ class ErrorLogCard extends LitElement { if (this._selectedLogProvider) { try { log = await fetchHassioLogs(this.hass, this._selectedLogProvider); + this._logHTML = html` + `; + this._isLogLoaded = true; + return; } catch (err: any) { this._error = this.hass.localize( "ui.panel.config.logs.failed_get_logs", From 8b8d6e5fa3dbcc3818e473dac6fde4cc23ce75ed Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 26 Apr 2022 13:12:14 +0200 Subject: [PATCH 084/181] Resources lovelace should just go back (#12432) --- .../config/lovelace/resources/ha-config-lovelace-resources.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index b8a9367e06..5aaad37f09 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -77,7 +77,6 @@ export class HaConfigLovelaceRescources extends LitElement { Date: Tue, 26 Apr 2022 13:15:29 +0200 Subject: [PATCH 085/181] Redirect hassio system my links to new locations (#12429) --- src/panels/my/ha-panel-my.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index 14015d2629..bda9e95233 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -176,6 +176,18 @@ const getRedirect = ( component: hasSupervisor ? "hassio" : "backup", redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", }, + supervisor_system: { + // Moved from Supervisor panel in 2022.5 + redirect: "/config/system", + }, + supervisor_logs: { + // Moved from Supervisor panel in 2022.5 + redirect: "/config/logs", + }, + supervisor_info: { + // Moved from Supervisor panel in 2022.5 + redirect: "/config/info", + }, } as Redirects )[path]); From bb1e6bf35b3b6f0d0df92e1e2cfef931170ed88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 26 Apr 2022 15:29:56 +0200 Subject: [PATCH 086/181] Fix backup back path (#12435) --- hassio/src/backups/hassio-backups.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hassio/src/backups/hassio-backups.ts b/hassio/src/backups/hassio-backups.ts index 577b36a8ea..659ba82606 100644 --- a/hassio/src/backups/hassio-backups.ts +++ b/hassio/src/backups/hassio-backups.ts @@ -182,7 +182,9 @@ export class HassioBackups extends LitElement { selectable hasFab .mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)} - back-path="/config" + back-path=${atLeastVersion(this.hass.config.version, 2022, 5) + ? "/config/system" + : "/config"} supervisor > Date: Tue, 26 Apr 2022 16:39:37 +0200 Subject: [PATCH 087/181] Add join/leave beta to updates panel (#12436) --- .../config/core/ha-config-section-updates.ts | 91 +++++++++++++++++-- src/translations/en.json | 9 ++ 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 3207620850..e0c0c6411e 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -1,22 +1,35 @@ +import type { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; import { HassEntities } from "home-assistant-js-websocket"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import "../../../components/ha-alert"; import "../../../components/ha-bar"; import "../../../components/ha-button-menu"; +import "../../../components/ha-card"; import "../../../components/ha-metric"; +import { extractApiErrorMessage } from "../../../data/hassio/common"; +import { + fetchHassioSupervisorInfo, + HassioSupervisorInfo, + reloadSupervisor, + setSupervisorOption, + SupervisorOptions, +} from "../../../data/hassio/supervisor"; import { updateCanInstall, UpdateEntity } from "../../../data/update"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; import type { HomeAssistant } from "../../../types"; import { showToast } from "../../../util/toast"; import "../dashboard/ha-config-updates"; -import "./ha-config-analytics"; @customElement("ha-config-section-updates") class HaConfigSectionUpdates extends LitElement { @@ -26,8 +39,20 @@ class HaConfigSectionUpdates extends LitElement { @state() private _showSkipped = false; + @state() private _supervisorInfo?: HassioSupervisorInfo; + private _notifyUpdates = false; + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + + if (isComponentLoaded(this.hass, "hassio")) { + fetchHassioSupervisorInfo(this.hass).then((data) => { + this._supervisorInfo = data; + }); + } + } + protected render(): TemplateResult { const canInstallUpdates = this._filterUpdateEntitiesWithInstall( this.hass.states, @@ -44,18 +69,27 @@ class HaConfigSectionUpdates extends LitElement { - + ${this._showSkipped ? this.hass.localize("ui.panel.config.updates.hide_skipped") : this.hass.localize("ui.panel.config.updates.show_skipped")} + ${this._supervisorInfo?.channel !== "dev" + ? html` + + ${this._supervisorInfo?.channel === "stable" + ? this.hass.localize("ui.panel.config.updates.join_beta") + : this.hass.localize("ui.panel.config.updates.leave_beta")} + + ` + : ""}
    @@ -111,8 +145,53 @@ class HaConfigSectionUpdates extends LitElement { } } - private _toggleSkipped(): void { - this._showSkipped = !this._showSkipped; + private _handleAction(ev: CustomEvent) { + switch (ev.detail.index) { + case 0: + this._showSkipped = !this._showSkipped; + break; + case 1: + this._toggleBeta(); + break; + } + } + + private async _toggleBeta(): Promise { + if (this._supervisorInfo!.channel === "stable") { + const confirmed = await showConfirmationDialog(this, { + title: this.hass.localize("ui.dialogs.join_beta_channel.title"), + text: html`${this.hass.localize("ui.dialogs.join_beta_channel.warning")} +
    + ${this.hass.localize("ui.dialogs.join_beta_channel.backup")} +

    + ${this.hass.localize("ui.dialogs.join_beta_channel.release_items")} +
      +
    • Home Assistant Core
    • +
    • Home Assistant Supervisor
    • +
    • Home Assistant Operating System
    • +
    +
    + ${this.hass.localize("ui.dialogs.join_beta_channel.confirm")}`, + confirmText: this.hass.localize("ui.panel.config.updates.join_beta"), + dismissText: this.hass.localize("ui.common.cancel"), + }); + + if (!confirmed) { + return; + } + } + + try { + const data: Partial = { + channel: this._supervisorInfo!.channel === "stable" ? "beta" : "stable", + }; + await setSupervisorOption(this.hass, data); + await reloadSupervisor(this.hass); + } catch (err: any) { + showAlertDialog(this, { + text: extractApiErrorMessage(err), + }); + } } private async _checkUpdates(): Promise { diff --git a/src/translations/en.json b/src/translations/en.json index cb884a4a66..80a30f6966 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1026,6 +1026,13 @@ "docker": "[%key:supervisor::system::supervisor::unhealthy_reason::docker%]", "untrusted": "[%key:supervisor::system::supervisor::unhealthy_reason::untrusted%]" } + }, + "join_beta_channel": { + "title": "Join the beta channel", + "warning": "[%key:supervisor::system::supervisor::beta_warning%]", + "backup": "[%key:supervisor::system::supervisor::beta_backup%]", + "release_items": "[%key:supervisor::system::supervisor::beta_release_items%]", + "confirm": "[%key:supervisor::system::supervisor::beta_join_confirm%]" } }, "duration": { @@ -1169,6 +1176,8 @@ "show": "show", "show_skipped": "Show skipped", "hide_skipped": "Hide skipped", + "join_beta": "[%key:supervisor::system::supervisor::join_beta_action%]", + "leave_beta": "[%key:supervisor::system::supervisor::leave_beta_action%]", "skipped": "Skipped" }, "areas": { From 8996361b2655f6b09b6ef081c7a9a2b80037813f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 26 Apr 2022 17:17:00 +0200 Subject: [PATCH 088/181] Fix settings row width (#12438) --- src/components/ha-service-control.ts | 1 + src/components/ha-settings-row.ts | 2 +- src/panels/config/automation/blueprint-automation-editor.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 438adb66d4..966052e4a6 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -472,6 +472,7 @@ export class HaServiceControl extends LitElement { ha-settings-row { --paper-time-input-justify-content: flex-end; --settings-row-content-width: 100%; + --settings-row-prefix-display: contents; border-top: var( --service-control-items-border-top, 1px solid var(--divider-color) diff --git a/src/components/ha-settings-row.ts b/src/components/ha-settings-row.ts index 252bb80dc8..dac28cdd5d 100644 --- a/src/components/ha-settings-row.ts +++ b/src/components/ha-settings-row.ts @@ -68,7 +68,7 @@ export class HaSettingsRow extends LitElement { white-space: normal; } .prefix-wrap { - display: contents; + display: var(--settings-row-prefix-display); } :host([narrow]) .prefix-wrap { display: flex; diff --git a/src/panels/config/automation/blueprint-automation-editor.ts b/src/panels/config/automation/blueprint-automation-editor.ts index 0c5da1c822..250744d424 100644 --- a/src/panels/config/automation/blueprint-automation-editor.ts +++ b/src/panels/config/automation/blueprint-automation-editor.ts @@ -332,6 +332,7 @@ export class HaBlueprintAutomationEditor extends LitElement { ha-settings-row { --paper-time-input-justify-content: flex-end; --settings-row-content-width: 100%; + --settings-row-prefix-display: contents; border-top: 1px solid var(--divider-color); } `, From 9572a2a46b634fff5ad7b72f88f86ca8121f3588 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 26 Apr 2022 17:39:50 +0200 Subject: [PATCH 089/181] Dont show tabs when less than 2 (#12439) --- src/layouts/hass-tabs-subpage-data-table.ts | 3 +++ src/layouts/hass-tabs-subpage.ts | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 7a176549d7..27d2c8cde3 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -283,6 +283,9 @@ export class HaTabsSubpageDataTable extends LitElement { height: calc(100vh - 1px - var(--header-height)); display: block; } + :host([narrow]) hass-tabs-subpage { + --main-title-margin: 0; + } .table-header { display: flex; align-items: center; diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 1c749ad090..003d0993b1 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -82,6 +82,16 @@ class HassTabsSubpage extends LitElement { (!page.advancedOnly || showAdvanced) ); + if (shownTabs.length < 2) { + if (shownTabs.length === 1) { + const page = shownTabs[0]; + return [ + page.translationKey ? localizeFunc(page.translationKey) : page.name, + ]; + } + return [""]; + } + return shownTabs.map( (page) => html` @@ -134,7 +144,7 @@ class HassTabsSubpage extends LitElement { this.narrow, this.localizeFunc || this.hass.localize ); - const showTabs = tabs.length > 1 || !this.narrow; + const showTabs = tabs.length > 1; return html`
    ${this.mainPage || (!this.backPath && history.state?.root) @@ -159,8 +169,10 @@ class HassTabsSubpage extends LitElement { @click=${this._backTapped} > `} - ${this.narrow - ? html`
    ` + ${this.narrow || !showTabs + ? html`
    + ${!showTabs ? tabs[0] : ""} +
    ` : ""} ${showTabs ? html` @@ -283,6 +295,7 @@ class HassTabsSubpage extends LitElement { max-height: var(--header-height); line-height: 20px; color: var(--sidebar-text-color); + margin: var(--main-title-margin, 0 0 0 24px); } .content { From 35d892c4187251ea37cf93aa4bd6dd3d8a6eb915 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 26 Apr 2022 18:50:36 +0200 Subject: [PATCH 090/181] Set border radius in config to 8px (#12437) --- src/panels/config/ha-panel-config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 81b97537ef..e9e624e7fa 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -540,6 +540,10 @@ class HaPanelConfig extends HassRouterPage { "--app-header-border-bottom", "1px solid var(--divider-color)" ); + this.style.setProperty( + "--ha-card-border-radius", + "var(--ha-config-card-border-radius, 8px)" + ); } protected updatePageEl(el) { From 9caee357c0de8c3b5e67fb01522292f52c14045c Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Tue, 26 Apr 2022 19:32:04 +0200 Subject: [PATCH 091/181] Fix incorrect text if no backups are found (#12441) --- src/panels/config/backup/ha-config-backup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index 2470fb9a01..59ab680839 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -132,7 +132,7 @@ class HaConfigBackup extends LitElement { .route=${this.route} .columns=${this._columns(this.narrow, this.hass.language)} .data=${this._getItems(this._backupData.backups)} - .noDataText=${this.hass.localize("ui.panel.config.backup.no_bakcups")} + .noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")} > ${this.hass.localize("ui.panel.config.backup.caption")} Date: Tue, 26 Apr 2022 19:53:32 +0200 Subject: [PATCH 092/181] Add header to supervisor backups page (#12444) --- hassio/src/backups/hassio-backups.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hassio/src/backups/hassio-backups.ts b/hassio/src/backups/hassio-backups.ts index 659ba82606..c8e8e5e30c 100644 --- a/hassio/src/backups/hassio-backups.ts +++ b/hassio/src/backups/hassio-backups.ts @@ -1,7 +1,7 @@ import "@material/mwc-button"; import { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; -import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js"; +import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js"; import { css, CSSResultGroup, @@ -166,7 +166,15 @@ export class HassioBackups extends LitElement { } return html` Date: Tue, 26 Apr 2022 20:41:19 +0200 Subject: [PATCH 093/181] Fix content display for `ha-network` after #12438 (#12445) * Fix content display for `ha-network` after #12438 * Add var default --- src/components/ha-network.ts | 3 +++ src/components/ha-settings-row.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ha-network.ts b/src/components/ha-network.ts index 6b7957573a..9175b68e38 100644 --- a/src/components/ha-network.ts +++ b/src/components/ha-network.ts @@ -163,6 +163,9 @@ export class HaNetwork extends LitElement { ha-settings-row { padding: 0; + --paper-time-input-justify-content: flex-end; + --settings-row-content-display: contents; + --settings-row-prefix-display: contents; } span[slot="heading"], diff --git a/src/components/ha-settings-row.ts b/src/components/ha-settings-row.ts index dac28cdd5d..c0bec72f89 100644 --- a/src/components/ha-settings-row.ts +++ b/src/components/ha-settings-row.ts @@ -47,7 +47,7 @@ export class HaSettingsRow extends LitElement { display: contents; } :host(:not([narrow])) .content { - display: flex; + display: var(--settings-row-content-display, flex); justify-content: flex-end; flex: 1; padding: 16px 0; From 824bb9ba35943e77800f1bb9ce966650e2ae6ccd Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Tue, 26 Apr 2022 21:04:32 +0200 Subject: [PATCH 094/181] Add title to backups config page (#12442) --- src/panels/config/backup/ha-config-backup.ts | 12 +++++++++--- src/translations/en.json | 3 +++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index 59ab680839..6574150209 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -126,6 +126,12 @@ class HaConfigBackup extends LitElement { return html` - ${this.hass.localize("ui.panel.config.backup.caption")} Date: Tue, 26 Apr 2022 21:38:59 +0200 Subject: [PATCH 095/181] Fix integration page on mobile (#12447) --- src/panels/config/integrations/ha-config-integrations.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 79cd2d7828..945cf5279f 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -111,7 +111,7 @@ const groupByIntegration = ( class HaConfigIntegrations extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public narrow!: boolean; @property() public isWide!: boolean; @@ -709,6 +709,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { return [ haStyle, css` + :host([narrow]) hass-tabs-subpage { + --main-title-margin: 0; + } ha-button-menu { margin-left: 8px; } From 049ddd5f84da2aead3fd050cb1249c9bb2e3147c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 27 Apr 2022 00:11:09 +0200 Subject: [PATCH 096/181] Add "m" keyboard shortcut to get to the create my link page (#12451) --- src/panels/my/ha-panel-my.ts | 349 +++++++++++++++++------------------ src/state/quick-bar-mixin.ts | 34 ++++ src/translations/en.json | 3 +- 3 files changed, 210 insertions(+), 176 deletions(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index bda9e95233..bfd163c187 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -12,184 +12,183 @@ import "../../layouts/hass-error-screen"; import { HomeAssistant, Route } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; +export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ + developer_states: { + redirect: "/developer-tools/state", + }, + developer_services: { + redirect: "/developer-tools/service", + }, + developer_call_service: { + redirect: "/developer-tools/service", + params: { + service: "string", + }, + }, + developer_template: { + redirect: "/developer-tools/template", + }, + developer_events: { + redirect: "/developer-tools/event", + }, + developer_statistics: { + redirect: "/developer-tools/statistics", + }, + config: { + redirect: "/config", + }, + cloud: { + component: "cloud", + redirect: "/config/cloud", + }, + integrations: { + redirect: "/config/integrations", + }, + config_flow_start: { + redirect: "/config/integrations/add", + params: { + domain: "string", + }, + }, + config_mqtt: { + component: "mqtt", + redirect: "/config/mqtt", + }, + config_zha: { + component: "zha", + redirect: "/config/zha/dashboard", + }, + config_zwave_js: { + component: "zwave_js", + redirect: "/config/zwave_js/dashboard", + }, + config_energy: { + component: "energy", + redirect: "/config/energy/dashboard", + }, + devices: { + redirect: "/config/devices/dashboard", + }, + entities: { + redirect: "/config/entities", + }, + energy: { + component: "energy", + redirect: "/energy", + }, + areas: { + redirect: "/config/areas/dashboard", + }, + blueprints: { + component: "blueprint", + redirect: "/config/blueprint/dashboard", + }, + blueprint_import: { + component: "blueprint", + redirect: "/config/blueprint/dashboard/import", + params: { + blueprint_url: "url", + }, + }, + automations: { + component: "automation", + redirect: "/config/automation/dashboard", + }, + scenes: { + component: "scene", + redirect: "/config/scene/dashboard", + }, + scripts: { + component: "script", + redirect: "/config/script/dashboard", + }, + helpers: { + redirect: "/config/helpers", + }, + tags: { + component: "tag", + redirect: "/config/tags", + }, + lovelace_dashboards: { + component: "lovelace", + redirect: "/config/lovelace/dashboards", + }, + lovelace_resources: { + component: "lovelace", + redirect: "/config/lovelace/resources", + }, + people: { + component: "person", + redirect: "/config/person", + }, + zones: { + component: "zone", + redirect: "/config/zone", + }, + users: { + redirect: "/config/users", + }, + general: { + redirect: "/config/core", + }, + server_controls: { + redirect: "/config/server_control", + }, + logs: { + redirect: "/config/logs", + }, + info: { + redirect: "/config/info", + }, + customize: { + // customize was removed in 2021.12, fallback to dashboard + redirect: "/config/dashboard", + }, + profile: { + redirect: "/profile/dashboard", + }, + logbook: { + component: "logbook", + redirect: "/logbook", + }, + history: { + component: "history", + redirect: "/history", + }, + media_browser: { + component: "media_source", + redirect: "/media-browser", + }, + backup: { + component: hasSupervisor ? "hassio" : "backup", + redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", + }, + supervisor_snapshots: { + component: hasSupervisor ? "hassio" : "backup", + redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", + }, + supervisor_backups: { + component: hasSupervisor ? "hassio" : "backup", + redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", + }, + supervisor_system: { + // Moved from Supervisor panel in 2022.5 + redirect: "/config/system", + }, + supervisor_logs: { + // Moved from Supervisor panel in 2022.5 + redirect: "/config/logs", + }, + supervisor_info: { + // Moved from Supervisor panel in 2022.5 + redirect: "/config/info", + }, +}); + const getRedirect = ( path: string, hasSupervisor: boolean -): Redirect | undefined => - (( - { - developer_states: { - redirect: "/developer-tools/state", - }, - developer_services: { - redirect: "/developer-tools/service", - }, - developer_call_service: { - redirect: "/developer-tools/service", - params: { - service: "string", - }, - }, - developer_template: { - redirect: "/developer-tools/template", - }, - developer_events: { - redirect: "/developer-tools/event", - }, - developer_statistics: { - redirect: "/developer-tools/statistics", - }, - config: { - redirect: "/config", - }, - cloud: { - component: "cloud", - redirect: "/config/cloud", - }, - integrations: { - redirect: "/config/integrations", - }, - config_flow_start: { - redirect: "/config/integrations/add", - params: { - domain: "string", - }, - }, - config_mqtt: { - component: "mqtt", - redirect: "/config/mqtt", - }, - config_zha: { - component: "zha", - redirect: "/config/zha/dashboard", - }, - config_zwave_js: { - component: "zwave_js", - redirect: "/config/zwave_js/dashboard", - }, - config_energy: { - component: "energy", - redirect: "/config/energy/dashboard", - }, - devices: { - redirect: "/config/devices/dashboard", - }, - entities: { - redirect: "/config/entities", - }, - energy: { - component: "energy", - redirect: "/energy", - }, - areas: { - redirect: "/config/areas/dashboard", - }, - blueprints: { - component: "blueprint", - redirect: "/config/blueprint/dashboard", - }, - blueprint_import: { - component: "blueprint", - redirect: "/config/blueprint/dashboard/import", - params: { - blueprint_url: "url", - }, - }, - automations: { - component: "automation", - redirect: "/config/automation/dashboard", - }, - scenes: { - component: "scene", - redirect: "/config/scene/dashboard", - }, - scripts: { - component: "script", - redirect: "/config/script/dashboard", - }, - helpers: { - redirect: "/config/helpers", - }, - tags: { - component: "tag", - redirect: "/config/tags", - }, - lovelace_dashboards: { - component: "lovelace", - redirect: "/config/lovelace/dashboards", - }, - lovelace_resources: { - component: "lovelace", - redirect: "/config/lovelace/resources", - }, - people: { - component: "person", - redirect: "/config/person", - }, - zones: { - component: "zone", - redirect: "/config/zone", - }, - users: { - redirect: "/config/users", - }, - general: { - redirect: "/config/core", - }, - server_controls: { - redirect: "/config/server_control", - }, - logs: { - redirect: "/config/logs", - }, - info: { - redirect: "/config/info", - }, - customize: { - // customize was removed in 2021.12, fallback to dashboard - redirect: "/config/dashboard", - }, - profile: { - redirect: "/profile/dashboard", - }, - logbook: { - component: "logbook", - redirect: "/logbook", - }, - history: { - component: "history", - redirect: "/history", - }, - media_browser: { - component: "media_source", - redirect: "/media-browser", - }, - backup: { - component: hasSupervisor ? "hassio" : "backup", - redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", - }, - supervisor_snapshots: { - component: hasSupervisor ? "hassio" : "backup", - redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", - }, - supervisor_backups: { - component: hasSupervisor ? "hassio" : "backup", - redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", - }, - supervisor_system: { - // Moved from Supervisor panel in 2022.5 - redirect: "/config/system", - }, - supervisor_logs: { - // Moved from Supervisor panel in 2022.5 - redirect: "/config/logs", - }, - supervisor_info: { - // Moved from Supervisor panel in 2022.5 - redirect: "/config/info", - }, - } as Redirects - )[path]); +): Redirect | undefined => getMyRedirects(hasSupervisor)?.[path]; export type ParamType = "url" | "string"; diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index d70d0d1b4d..d9067a48da 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -1,11 +1,14 @@ import type { PropertyValues } from "lit"; import tinykeys from "tinykeys"; +import { isComponentLoaded } from "../common/config/is_component_loaded"; +import { mainWindow } from "../common/dom/get_main_window"; import { QuickBarParams, showQuickBar, } from "../dialogs/quick-bar/show-dialog-quick-bar"; import { Constructor, HomeAssistant } from "../types"; import { storeState } from "../util/ha-pref-storage"; +import { showToast } from "../util/toast"; import { HassElement } from "./hass-element"; declare global { @@ -32,6 +35,7 @@ export default >(superClass: T) => tinykeys(window, { e: (ev) => this._showQuickBar(ev), c: (ev) => this._showQuickBar(ev, true), + m: (ev) => this._createMyLink(ev), }); } @@ -43,6 +47,36 @@ export default >(superClass: T) => showQuickBar(this, { commandMode }); } + private async _createMyLink(e: KeyboardEvent) { + if (!this._canOverrideAlphanumericInput(e) || !this.hass) { + return; + } + + const targetPath = mainWindow.location.pathname; + const myPanel = await import("../panels/my/ha-panel-my"); + + for (const [slug, redirect] of Object.entries( + myPanel.getMyRedirects(isComponentLoaded(this.hass, "hassio")) + )) { + if (redirect.redirect === targetPath) { + window.open( + `https://my.home-assistant.io/create-link/?redirect=${slug}`, + "_blank" + ); + + return; + } + } + showToast(this, { + message: this.hass.localize( + "ui.notification_toast.no_matching_link_found", + { + path: targetPath, + } + ), + }); + } + private _canShowQuickBar(e: KeyboardEvent) { return ( this.hass?.user?.is_admin && diff --git a/src/translations/en.json b/src/translations/en.json index de290c283b..500a94caf5 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1083,7 +1083,8 @@ "wrapping_up_startup": "Wrapping up startup, not everything will be available until it is finished.", "integration_starting": "Starting {integration}, not everything will be available until it is finished.", "triggered": "Triggered {name}", - "dismiss": "Dismiss" + "dismiss": "Dismiss", + "no_matching_link_found": "No matching my link found for {path}" }, "sidebar": { "external_app_configuration": "App Configuration", From 8da73d49d7b2e160ae3b0e7b692fe9c84818741a Mon Sep 17 00:00:00 2001 From: Johann Vanackere Date: Wed, 27 Apr 2022 02:39:58 +0200 Subject: [PATCH 097/181] Terms based entities search (#10991) --- package.json | 1 + src/common/string/filter/char-code.ts | 244 -------- src/common/string/filter/filter.ts | 551 ------------------ src/common/string/filter/sequence-matching.ts | 90 ++- .../data-table/sort_filter_worker.ts | 15 +- src/components/entity/ha-entity-picker.ts | 18 +- src/dialogs/quick-bar/ha-quick-bar.ts | 4 +- test/common/string/sequence_matching.test.ts | 207 +++++-- yarn.lock | 8 + 9 files changed, 227 insertions(+), 911 deletions(-) delete mode 100644 src/common/string/filter/char-code.ts delete mode 100644 src/common/string/filter/filter.ts diff --git a/package.json b/package.json index 25cbfbe761..23438c75eb 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "deep-clone-simple": "^1.1.1", "deep-freeze": "^0.0.1", "fuse.js": "^6.0.0", + "fuzzysort": "^1.2.1", "google-timezones-json": "^1.0.2", "hls.js": "^1.1.5", "home-assistant-js-websocket": "^7.0.3", diff --git a/src/common/string/filter/char-code.ts b/src/common/string/filter/char-code.ts deleted file mode 100644 index faa7210898..0000000000 --- a/src/common/string/filter/char-code.ts +++ /dev/null @@ -1,244 +0,0 @@ -// MIT License - -// Copyright (c) 2015 - present Microsoft Corporation - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/ - -/** - * An inlined enum containing useful character codes (to be used with String.charCodeAt). - * Please leave the const keyword such that it gets inlined when compiled to JavaScript! - */ -export enum CharCode { - Null = 0, - /** - * The `\b` character. - */ - Backspace = 8, - /** - * The `\t` character. - */ - Tab = 9, - /** - * The `\n` character. - */ - LineFeed = 10, - /** - * The `\r` character. - */ - CarriageReturn = 13, - Space = 32, - /** - * The `!` character. - */ - ExclamationMark = 33, - /** - * The `"` character. - */ - DoubleQuote = 34, - /** - * The `#` character. - */ - Hash = 35, - /** - * The `$` character. - */ - DollarSign = 36, - /** - * The `%` character. - */ - PercentSign = 37, - /** - * The `&` character. - */ - Ampersand = 38, - /** - * The `'` character. - */ - SingleQuote = 39, - /** - * The `(` character. - */ - OpenParen = 40, - /** - * The `)` character. - */ - CloseParen = 41, - /** - * The `*` character. - */ - Asterisk = 42, - /** - * The `+` character. - */ - Plus = 43, - /** - * The `,` character. - */ - Comma = 44, - /** - * The `-` character. - */ - Dash = 45, - /** - * The `.` character. - */ - Period = 46, - /** - * The `/` character. - */ - Slash = 47, - - Digit0 = 48, - Digit1 = 49, - Digit2 = 50, - Digit3 = 51, - Digit4 = 52, - Digit5 = 53, - Digit6 = 54, - Digit7 = 55, - Digit8 = 56, - Digit9 = 57, - - /** - * The `:` character. - */ - Colon = 58, - /** - * The `;` character. - */ - Semicolon = 59, - /** - * The `<` character. - */ - LessThan = 60, - /** - * The `=` character. - */ - Equals = 61, - /** - * The `>` character. - */ - GreaterThan = 62, - /** - * The `?` character. - */ - QuestionMark = 63, - /** - * The `@` character. - */ - AtSign = 64, - - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - - /** - * The `[` character. - */ - OpenSquareBracket = 91, - /** - * The `\` character. - */ - Backslash = 92, - /** - * The `]` character. - */ - CloseSquareBracket = 93, - /** - * The `^` character. - */ - Caret = 94, - /** - * The `_` character. - */ - Underline = 95, - /** - * The ``(`)`` character. - */ - BackTick = 96, - - a = 97, - b = 98, - c = 99, - d = 100, - e = 101, - f = 102, - g = 103, - h = 104, - i = 105, - j = 106, - k = 107, - l = 108, - m = 109, - n = 110, - o = 111, - p = 112, - q = 113, - r = 114, - s = 115, - t = 116, - u = 117, - v = 118, - w = 119, - x = 120, - y = 121, - z = 122, - - /** - * The `{` character. - */ - OpenCurlyBrace = 123, - /** - * The `|` character. - */ - Pipe = 124, - /** - * The `}` character. - */ - CloseCurlyBrace = 125, - /** - * The `~` character. - */ - Tilde = 126, -} diff --git a/src/common/string/filter/filter.ts b/src/common/string/filter/filter.ts deleted file mode 100644 index e7c0103263..0000000000 --- a/src/common/string/filter/filter.ts +++ /dev/null @@ -1,551 +0,0 @@ -/* eslint-disable no-console */ -// MIT License - -// Copyright (c) 2015 - present Microsoft Corporation - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { CharCode } from "./char-code"; - -const _debug = false; - -export interface Match { - start: number; - end: number; -} - -const _maxLen = 128; - -function initTable() { - const table: number[][] = []; - const row: number[] = []; - for (let i = 0; i <= _maxLen; i++) { - row[i] = 0; - } - for (let i = 0; i <= _maxLen; i++) { - table.push(row.slice(0)); - } - return table; -} - -function isSeparatorAtPos(value: string, index: number): boolean { - if (index < 0 || index >= value.length) { - return false; - } - const code = value.codePointAt(index); - switch (code) { - case CharCode.Underline: - case CharCode.Dash: - case CharCode.Period: - case CharCode.Space: - case CharCode.Slash: - case CharCode.Backslash: - case CharCode.SingleQuote: - case CharCode.DoubleQuote: - case CharCode.Colon: - case CharCode.DollarSign: - case CharCode.LessThan: - case CharCode.OpenParen: - case CharCode.OpenSquareBracket: - return true; - case undefined: - return false; - default: - if (isEmojiImprecise(code)) { - return true; - } - return false; - } -} - -function isWhitespaceAtPos(value: string, index: number): boolean { - if (index < 0 || index >= value.length) { - return false; - } - const code = value.charCodeAt(index); - switch (code) { - case CharCode.Space: - case CharCode.Tab: - return true; - default: - return false; - } -} - -function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean { - return word[pos] !== wordLow[pos]; -} - -export function isPatternInWord( - patternLow: string, - patternPos: number, - patternLen: number, - wordLow: string, - wordPos: number, - wordLen: number, - fillMinWordPosArr = false -): boolean { - while (patternPos < patternLen && wordPos < wordLen) { - if (patternLow[patternPos] === wordLow[wordPos]) { - if (fillMinWordPosArr) { - // Remember the min word position for each pattern position - _minWordMatchPos[patternPos] = wordPos; - } - patternPos += 1; - } - wordPos += 1; - } - return patternPos === patternLen; // pattern must be exhausted -} - -enum Arrow { - Diag = 1, - Left = 2, - LeftLeft = 3, -} - -/** - * An array representing a fuzzy match. - * - * 0. the score - * 1. the offset at which matching started - * 2. `` - * 3. `` - * 4. `` etc - */ -// export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]];// [number, number, number]; -export type FuzzyScore = Array; - -export function fuzzyScore( - pattern: string, - patternLow: string, - patternStart: number, - word: string, - wordLow: string, - wordStart: number, - firstMatchCanBeWeak: boolean -): FuzzyScore | undefined { - const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length; - const wordLen = word.length > _maxLen ? _maxLen : word.length; - - if ( - patternStart >= patternLen || - wordStart >= wordLen || - patternLen - patternStart > wordLen - wordStart - ) { - return undefined; - } - - // Run a simple check if the characters of pattern occur - // (in order) at all in word. If that isn't the case we - // stop because no match will be possible - if ( - !isPatternInWord( - patternLow, - patternStart, - patternLen, - wordLow, - wordStart, - wordLen, - true - ) - ) { - return undefined; - } - - // Find the max matching word position for each pattern position - // NOTE: the min matching word position was filled in above, in the `isPatternInWord` call - _fillInMaxWordMatchPos( - patternLen, - wordLen, - patternStart, - wordStart, - patternLow, - wordLow - ); - - let row: number; - let column = 1; - let patternPos: number; - let wordPos: number; - - const hasStrongFirstMatch = [false]; - - // There will be a match, fill in tables - for ( - row = 1, patternPos = patternStart; - patternPos < patternLen; - row++, patternPos++ - ) { - // Reduce search space to possible matching word positions and to possible access from next row - const minWordMatchPos = _minWordMatchPos[patternPos]; - const maxWordMatchPos = _maxWordMatchPos[patternPos]; - const nextMaxWordMatchPos = - patternPos + 1 < patternLen ? _maxWordMatchPos[patternPos + 1] : wordLen; - - for ( - column = minWordMatchPos - wordStart + 1, wordPos = minWordMatchPos; - wordPos < nextMaxWordMatchPos; - column++, wordPos++ - ) { - let score = Number.MIN_SAFE_INTEGER; - let canComeDiag = false; - - if (wordPos <= maxWordMatchPos) { - score = _doScore( - pattern, - patternLow, - patternPos, - patternStart, - word, - wordLow, - wordPos, - wordLen, - wordStart, - _diag[row - 1][column - 1] === 0, - hasStrongFirstMatch - ); - } - - let diagScore = 0; - if (score !== Number.MAX_SAFE_INTEGER) { - canComeDiag = true; - diagScore = score + _table[row - 1][column - 1]; - } - - const canComeLeft = wordPos > minWordMatchPos; - const leftScore = canComeLeft - ? _table[row][column - 1] + (_diag[row][column - 1] > 0 ? -5 : 0) - : 0; // penalty for a gap start - - const canComeLeftLeft = - wordPos > minWordMatchPos + 1 && _diag[row][column - 1] > 0; - const leftLeftScore = canComeLeftLeft - ? _table[row][column - 2] + (_diag[row][column - 2] > 0 ? -5 : 0) - : 0; // penalty for a gap start - - if ( - canComeLeftLeft && - (!canComeLeft || leftLeftScore >= leftScore) && - (!canComeDiag || leftLeftScore >= diagScore) - ) { - // always prefer choosing left left to jump over a diagonal because that means a match is earlier in the word - _table[row][column] = leftLeftScore; - _arrows[row][column] = Arrow.LeftLeft; - _diag[row][column] = 0; - } else if (canComeLeft && (!canComeDiag || leftScore >= diagScore)) { - // always prefer choosing left since that means a match is earlier in the word - _table[row][column] = leftScore; - _arrows[row][column] = Arrow.Left; - _diag[row][column] = 0; - } else if (canComeDiag) { - _table[row][column] = diagScore; - _arrows[row][column] = Arrow.Diag; - _diag[row][column] = _diag[row - 1][column - 1] + 1; - } else { - throw new Error(`not possible`); - } - } - } - - if (_debug) { - printTables(pattern, patternStart, word, wordStart); - } - - if (!hasStrongFirstMatch[0] && !firstMatchCanBeWeak) { - return undefined; - } - - row--; - column--; - - const result: FuzzyScore = [_table[row][column], wordStart]; - - let backwardsDiagLength = 0; - let maxMatchColumn = 0; - - while (row >= 1) { - // Find the column where we go diagonally up - let diagColumn = column; - do { - const arrow = _arrows[row][diagColumn]; - if (arrow === Arrow.LeftLeft) { - diagColumn -= 2; - } else if (arrow === Arrow.Left) { - diagColumn -= 1; - } else { - // found the diagonal - break; - } - } while (diagColumn >= 1); - - // Overturn the "forwards" decision if keeping the "backwards" diagonal would give a better match - if ( - backwardsDiagLength > 1 && // only if we would have a contiguous match of 3 characters - patternLow[patternStart + row - 1] === wordLow[wordStart + column - 1] && // only if we can do a contiguous match diagonally - !isUpperCaseAtPos(diagColumn + wordStart - 1, word, wordLow) && // only if the forwards chose diagonal is not an uppercase - backwardsDiagLength + 1 > _diag[row][diagColumn] // only if our contiguous match would be longer than the "forwards" contiguous match - ) { - diagColumn = column; - } - - if (diagColumn === column) { - // this is a contiguous match - backwardsDiagLength++; - } else { - backwardsDiagLength = 1; - } - - if (!maxMatchColumn) { - // remember the last matched column - maxMatchColumn = diagColumn; - } - - row--; - column = diagColumn - 1; - result.push(column); - } - - if (wordLen === patternLen) { - // the word matches the pattern with all characters! - // giving the score a total match boost (to come up ahead other words) - result[0] += 2; - } - - // Add 1 penalty for each skipped character in the word - const skippedCharsCount = maxMatchColumn - patternLen; - result[0] -= skippedCharsCount; - - return result; -} - -function _doScore( - pattern: string, - patternLow: string, - patternPos: number, - patternStart: number, - word: string, - wordLow: string, - wordPos: number, - wordLen: number, - wordStart: number, - newMatchStart: boolean, - outFirstMatchStrong: boolean[] -): number { - if (patternLow[patternPos] !== wordLow[wordPos]) { - return Number.MIN_SAFE_INTEGER; - } - - let score = 1; - let isGapLocation = false; - if (wordPos === patternPos - patternStart) { - // common prefix: `foobar <-> foobaz` - // ^^^^^ - score = pattern[patternPos] === word[wordPos] ? 7 : 5; - } else if ( - isUpperCaseAtPos(wordPos, word, wordLow) && - (wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow)) - ) { - // hitting upper-case: `foo <-> forOthers` - // ^^ ^ - score = pattern[patternPos] === word[wordPos] ? 7 : 5; - isGapLocation = true; - } else if ( - isSeparatorAtPos(wordLow, wordPos) && - (wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1)) - ) { - // hitting a separator: `. <-> foo.bar` - // ^ - score = 5; - } else if ( - isSeparatorAtPos(wordLow, wordPos - 1) || - isWhitespaceAtPos(wordLow, wordPos - 1) - ) { - // post separator: `foo <-> bar_foo` - // ^^^ - score = 5; - isGapLocation = true; - } - - if (score > 1 && patternPos === patternStart) { - outFirstMatchStrong[0] = true; - } - - if (!isGapLocation) { - isGapLocation = - isUpperCaseAtPos(wordPos, word, wordLow) || - isSeparatorAtPos(wordLow, wordPos - 1) || - isWhitespaceAtPos(wordLow, wordPos - 1); - } - - // - if (patternPos === patternStart) { - // first character in pattern - if (wordPos > wordStart) { - // the first pattern character would match a word character that is not at the word start - // so introduce a penalty to account for the gap preceding this match - score -= isGapLocation ? 3 : 5; - } - } else if (newMatchStart) { - // this would be the beginning of a new match (i.e. there would be a gap before this location) - score += isGapLocation ? 2 : 0; - } else { - // this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a prefered gap location - score += isGapLocation ? 0 : 1; - } - - if (wordPos + 1 === wordLen) { - // we always penalize gaps, but this gives unfair advantages to a match that would match the last character in the word - // so pretend there is a gap after the last character in the word to normalize things - score -= isGapLocation ? 3 : 5; - } - - return score; -} - -function printTable( - table: number[][], - pattern: string, - patternLen: number, - word: string, - wordLen: number -): string { - function pad(s: string, n: number, _pad = " ") { - while (s.length < n) { - s = _pad + s; - } - return s; - } - let ret = ` | |${word - .split("") - .map((c) => pad(c, 3)) - .join("|")}\n`; - - for (let i = 0; i <= patternLen; i++) { - if (i === 0) { - ret += " |"; - } else { - ret += `${pattern[i - 1]}|`; - } - ret += - table[i] - .slice(0, wordLen + 1) - .map((n) => pad(n.toString(), 3)) - .join("|") + "\n"; - } - return ret; -} - -function printTables( - pattern: string, - patternStart: number, - word: string, - wordStart: number -): void { - pattern = pattern.substr(patternStart); - word = word.substr(wordStart); - console.log(printTable(_table, pattern, pattern.length, word, word.length)); - console.log(printTable(_arrows, pattern, pattern.length, word, word.length)); - console.log(printTable(_diag, pattern, pattern.length, word, word.length)); -} - -const _minWordMatchPos = initArr(2 * _maxLen); // min word position for a certain pattern position -const _maxWordMatchPos = initArr(2 * _maxLen); // max word position for a certain pattern position -const _diag = initTable(); // the length of a contiguous diagonal match -const _table = initTable(); -const _arrows = initTable(); - -function initArr(maxLen: number) { - const row: number[] = []; - for (let i = 0; i <= maxLen; i++) { - row[i] = 0; - } - return row; -} - -function _fillInMaxWordMatchPos( - patternLen: number, - wordLen: number, - patternStart: number, - wordStart: number, - patternLow: string, - wordLow: string -) { - let patternPos = patternLen - 1; - let wordPos = wordLen - 1; - while (patternPos >= patternStart && wordPos >= wordStart) { - if (patternLow[patternPos] === wordLow[wordPos]) { - _maxWordMatchPos[patternPos] = wordPos; - patternPos--; - } - wordPos--; - } -} - -export interface FuzzyScorer { - ( - pattern: string, - lowPattern: string, - patternPos: number, - word: string, - lowWord: string, - wordPos: number, - firstMatchCanBeWeak: boolean - ): FuzzyScore | undefined; -} - -export function createMatches(score: undefined | FuzzyScore): Match[] { - if (typeof score === "undefined") { - return []; - } - const res: Match[] = []; - const wordPos = score[1]; - for (let i = score.length - 1; i > 1; i--) { - const pos = score[i] + wordPos; - const last = res[res.length - 1]; - if (last && last.end === pos) { - last.end = pos + 1; - } else { - res.push({ start: pos, end: pos + 1 }); - } - } - return res; -} - -/** - * A fast function (therefore imprecise) to check if code points are emojis. - * Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js - */ -export function isEmojiImprecise(x: number): boolean { - return ( - (x >= 0x1f1e6 && x <= 0x1f1ff) || - x === 8986 || - x === 8987 || - x === 9200 || - x === 9203 || - (x >= 9728 && x <= 10175) || - x === 11088 || - x === 11093 || - (x >= 127744 && x <= 128591) || - (x >= 128640 && x <= 128764) || - (x >= 128992 && x <= 129003) || - (x >= 129280 && x <= 129535) || - (x >= 129648 && x <= 129750) - ); -} diff --git a/src/common/string/filter/sequence-matching.ts b/src/common/string/filter/sequence-matching.ts index d502cec350..d0ec1f1e52 100644 --- a/src/common/string/filter/sequence-matching.ts +++ b/src/common/string/filter/sequence-matching.ts @@ -1,52 +1,4 @@ -import { fuzzyScore } from "./filter"; - -/** - * Determine whether a sequence of letters exists in another string, - * in that order, allowing for skipping. Ex: "chdr" exists in "chandelier") - * - * @param {string} filter - Sequence of letters to check for - * @param {ScorableTextItem} item - Item against whose strings will be checked - * - * @return {number} Score representing how well the word matches the filter. Return of 0 means no match. - */ - -export const fuzzySequentialMatch = ( - filter: string, - item: ScorableTextItem -) => { - let topScore = Number.NEGATIVE_INFINITY; - - for (const word of item.strings) { - const scores = fuzzyScore( - filter, - filter.toLowerCase(), - 0, - word, - word.toLowerCase(), - 0, - true - ); - - if (!scores) { - continue; - } - - // The VS Code implementation of filter returns a 0 for a weak match. - // But if .filter() sees a "0", it considers that a failed match and will remove it. - // So, we set score to 1 in these cases so the match will be included, and mostly respect correct ordering. - const score = scores[0] === 0 ? 1 : scores[0]; - - if (score > topScore) { - topScore = score; - } - } - - if (topScore === Number.NEGATIVE_INFINITY) { - return undefined; - } - - return topScore; -}; +import fuzzysort from "fuzzysort"; /** * An interface that objects must extend in order to use the fuzzy sequence matcher @@ -66,18 +18,48 @@ export interface ScorableTextItem { strings: string[]; } -type FuzzyFilterSort = ( +export type FuzzyFilterSort = ( filter: string, items: T[] ) => T[]; -export const fuzzyFilterSort: FuzzyFilterSort = (filter, items) => - items +export function fuzzyMatcher(search: string | null): (string) => boolean { + const scorer = fuzzyScorer(search); + return (value: string) => scorer([value]) !== Number.NEGATIVE_INFINITY; +} + +export function fuzzyScorer( + search: string | null +): (values: string[]) => number { + const searchTerms = (search || "").match(/("[^"]+"|[^"\s]+)/g); + if (!searchTerms) { + return () => 0; + } + return (values) => + searchTerms + .map((term) => { + const resultsForTerm = fuzzysort.go(term, values, { + allowTypo: true, + }); + if (resultsForTerm.length > 0) { + return Math.max(...resultsForTerm.map((result) => result.score)); + } + return Number.NEGATIVE_INFINITY; + }) + .reduce((partial, current) => partial + current, 0); +} + +export const fuzzySortFilterSort: FuzzyFilterSort = (filter, items) => { + const scorer = fuzzyScorer(filter); + return items .map((item) => { - item.score = fuzzySequentialMatch(filter, item); + item.score = scorer(item.strings); return item; }) - .filter((item) => item.score !== undefined) + .filter((item) => item.score !== undefined && item.score > -100000) .sort(({ score: scoreA = 0 }, { score: scoreB = 0 }) => scoreA > scoreB ? -1 : scoreA < scoreB ? 1 : 0 ); +}; + +export const defaultFuzzyFilterSort = fuzzySortFilterSort; diff --git a/src/components/data-table/sort_filter_worker.ts b/src/components/data-table/sort_filter_worker.ts index 286bae3014..c2b252f859 100644 --- a/src/components/data-table/sort_filter_worker.ts +++ b/src/components/data-table/sort_filter_worker.ts @@ -7,25 +7,26 @@ import type { SortableColumnContainer, SortingDirection, } from "./ha-data-table"; +import { fuzzyMatcher } from "../../common/string/filter/sequence-matching"; const filterData = ( data: DataTableRowData[], columns: SortableColumnContainer, filter: string ) => { - filter = filter.toUpperCase(); + const matcher = fuzzyMatcher(filter); return data.filter((row) => Object.entries(columns).some((columnEntry) => { const [key, column] = columnEntry; if (column.filterable) { if ( - String( - column.filterKey - ? row[column.valueColumn || key][column.filterKey] - : row[column.valueColumn || key] + matcher( + String( + column.filterKey + ? row[column.valueColumn || key][column.filterKey] + : row[column.valueColumn || key] + ) ) - .toUpperCase() - .includes(filter) ) { return true; } diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 7f9c8d3d80..ad921650ce 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -15,6 +15,7 @@ import type { HaComboBox } from "../ha-combo-box"; import "../ha-icon-button"; import "../ha-svg-icon"; import "./state-badge"; +import { defaultFuzzyFilterSort } from "../../common/string/filter/sequence-matching"; interface HassEntityWithCachedName extends HassEntity { friendly_name: string; @@ -336,11 +337,18 @@ export class HaEntityPicker extends LitElement { } private _filterChanged(ev: CustomEvent): void { - const filterString = ev.detail.value.toLowerCase(); - (this.comboBox as any).filteredItems = this._states.filter( - (entityState) => - entityState.entity_id.toLowerCase().includes(filterString) || - computeStateName(entityState).toLowerCase().includes(filterString) + const filterString = ev.detail.value; + + const sortableEntityStates = this._states.map((entityState) => ({ + strings: [entityState.entity_id, computeStateName(entityState)], + entityState: entityState, + })); + const sortedEntityStates = defaultFuzzyFilterSort( + filterString, + sortableEntityStates + ); + (this.comboBox as any).filteredItems = sortedEntityStates.map( + (sortableItem) => sortableItem.entityState ); } diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 0165a14aca..8e7fbefd74 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -24,7 +24,7 @@ import { domainIcon } from "../../common/entity/domain_icon"; import { navigate } from "../../common/navigate"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import { - fuzzyFilterSort, + defaultFuzzyFilterSort, ScorableTextItem, } from "../../common/string/filter/sequence-matching"; import { debounce } from "../../common/util/debounce"; @@ -694,7 +694,7 @@ export class QuickBar extends LitElement { private _filterItems = memoizeOne( (items: QuickBarItem[], filter: string): QuickBarItem[] => - fuzzyFilterSort(filter.trimLeft(), items) + defaultFuzzyFilterSort(filter.trimLeft(), items) ); static get styles() { diff --git a/test/common/string/sequence_matching.test.ts b/test/common/string/sequence_matching.test.ts index f631a23285..8f8f63bada 100644 --- a/test/common/string/sequence_matching.test.ts +++ b/test/common/string/sequence_matching.test.ts @@ -1,8 +1,7 @@ -import { assert } from "chai"; +import { assert, expect } from "chai"; import { - fuzzyFilterSort, - fuzzySequentialMatch, + fuzzySortFilterSort, ScorableTextItem, } from "../../../src/common/string/filter/sequence-matching"; @@ -11,45 +10,34 @@ describe("fuzzySequentialMatch", () => { strings: ["automation.ticker", "Stocks"], }; - const createExpectation: ( - pattern, - expected - ) => { - pattern: string; - expected: string | number | undefined; - } = (pattern, expected) => ({ - pattern, - expected, - }); - const shouldMatchEntity = [ - createExpectation("automation.ticker", 131), - createExpectation("automation.ticke", 121), - createExpectation("automation.", 82), - createExpectation("au", 10), - createExpectation("automationticker", 85), - createExpectation("tion.tick", 8), - createExpectation("ticker", -4), - createExpectation("automation.r", 73), - createExpectation("tick", -8), - createExpectation("aumatick", 9), - createExpectation("aion.tck", 4), - createExpectation("ioticker", -4), - createExpectation("atmto.ikr", -34), - createExpectation("uoaintce", -39), - createExpectation("au.tce", -3), - createExpectation("tomaontkr", -19), - createExpectation("s", 1), - createExpectation("stocks", 42), - createExpectation("sks", -5), + "", + " ", + "automation.ticker", + "stocks", + "automation.ticke", + "automation. ticke", + "automation.", + "automationticker", + "automation.r", + "aumatick", + "tion.tick", + "aion.tck", + "s", + "au.tce", + "au", + "ticker", + "tick", + "ioticker", + "sks", + "tomaontkr", + "atmto.ikr", + "uoaintce", ]; const shouldNotMatchEntity = [ - "", - " ", "abcdefghijklmnopqrstuvwxyz", "automation.tickerz", - "automation. ticke", "1", "noitamotua", "autostocks", @@ -57,23 +45,23 @@ describe("fuzzySequentialMatch", () => { ]; describe(`Entity '${item.strings[0]}'`, () => { - for (const expectation of shouldMatchEntity) { - it(`matches '${expectation.pattern}' with return of '${expectation.expected}'`, () => { - const res = fuzzySequentialMatch(expectation.pattern, item); - assert.equal(res, expectation.expected); + for (const filter of shouldMatchEntity) { + it(`Should matches ${filter}`, () => { + const res = fuzzySortFilterSort(filter, [item]); + assert.lengthOf(res, 1); }); } for (const badFilter of shouldNotMatchEntity) { it(`fails to match with '${badFilter}'`, () => { - const res = fuzzySequentialMatch(badFilter, item); - assert.equal(res, undefined); + const res = fuzzySortFilterSort(badFilter, [item]); + assert.lengthOf(res, 0); }); } }); }); -describe("fuzzyFilterSort", () => { +describe("fuzzyFilterSort original tests", () => { const filter = "ticker"; const automationTicker = { strings: ["automation.ticker", "Stocks"], @@ -105,14 +93,137 @@ describe("fuzzyFilterSort", () => { it(`filters and sorts correctly`, () => { const expectedItemsAfterFilter = [ - { ...ticker, score: 44 }, - { ...sensorTicker, score: 1 }, - { ...automationTicker, score: -4 }, - { ...timerCheckRouter, score: -8 }, + { ...ticker, score: 0 }, + { ...sensorTicker, score: -14 }, + { ...automationTicker, score: -22 }, + { ...timerCheckRouter, score: -32012 }, ]; - const res = fuzzyFilterSort(filter, itemsBeforeFilter); + const res = fuzzySortFilterSort(filter, itemsBeforeFilter); assert.deepEqual(res, expectedItemsAfterFilter); }); }); + +describe("Fuzzy filter new tests", () => { + const testEntities = [ + { + id: "binary_sensor.garage_door_opened", + name: "Garage Door Opened (Sensor, Binary)", + }, + { + id: "sensor.garage_door_status", + name: "Garage Door Opened (Sensor)", + }, + { + id: "sensor.temperature_living_room", + name: "[Living room] temperature", + }, + { + id: "sensor.temperature_parents_bedroom", + name: "[Parents bedroom] temperature", + }, + { + id: "sensor.temperature_children_bedroom", + name: "[Children bedroom] temperature", + }, + ]; + + function testEntitySearch( + searchInput: string | null, + expectedResults: string[] + ) { + const sortableEntities = testEntities.map((entity) => ({ + strings: [entity.id, entity.name], + entity: entity, + })); + const sortedEntities = fuzzySortFilterSort( + searchInput || "", + sortableEntities + ); + // console.log(sortedEntities); + expect(sortedEntities.map((it) => it.entity.id)).to.have.ordered.members( + expectedResults + ); + } + + it(`test empty or null query`, () => { + testEntitySearch( + "", + testEntities.map((it) => it.id) + ); + testEntitySearch( + null, + testEntities.map((it) => it.id) + ); + }); + + it(`test single word search`, () => { + testEntitySearch("bedroom", [ + "sensor.temperature_parents_bedroom", + "sensor.temperature_children_bedroom", + ]); + }); + + it(`test no result`, () => { + testEntitySearch("does not exist", []); + testEntitySearch("betroom", []); + }); + + it(`test single word search with typo`, () => { + testEntitySearch("bedorom", [ + "sensor.temperature_parents_bedroom", + "sensor.temperature_children_bedroom", + ]); + }); + + it(`test multi word search`, () => { + testEntitySearch("bedroom children", [ + "sensor.temperature_children_bedroom", + ]); + }); + + it(`test partial word search`, () => { + testEntitySearch("room", [ + "sensor.temperature_living_room", + "sensor.temperature_parents_bedroom", + "sensor.temperature_children_bedroom", + ]); + }); + + it(`test mixed cased word search`, () => { + testEntitySearch("garage binary", ["binary_sensor.garage_door_opened"]); + }); + + it(`test mixed id and name search`, () => { + testEntitySearch("status opened", ["sensor.garage_door_status"]); + }); + + it(`test special chars in query`, () => { + testEntitySearch("sensor.temperature", [ + "sensor.temperature_living_room", + "sensor.temperature_parents_bedroom", + "sensor.temperature_children_bedroom", + ]); + + testEntitySearch("sensor.temperature parents", [ + "sensor.temperature_parents_bedroom", + ]); + testEntitySearch("parents_Bedroom", ["sensor.temperature_parents_bedroom"]); + }); + + it(`test search in name`, () => { + testEntitySearch("Binary)", ["binary_sensor.garage_door_opened"]); + + testEntitySearch("Binary)NotExists", []); + }); + + it(`test regex special chars`, () => { + // Should return an empty result, but no error + testEntitySearch("\\{}()*+?.,[])", []); + + testEntitySearch("[Children bedroom]", [ + "sensor.temperature_children_bedroom", + ]); + }); +}); diff --git a/yarn.lock b/yarn.lock index af02cfdc2a..1275415a57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8433,6 +8433,13 @@ fsevents@^1.2.7: languageName: node linkType: hard +"fuzzysort@npm:^1.2.1": + version: 1.2.1 + resolution: "fuzzysort@npm:1.2.1" + checksum: 74dad902a0aef6c3237d5ae5330aacca23d408f0e07125fcc39b57561b4c29da512fbf3826c3f3918da89f132f5b393cf5d56b3217282ecfb80a90124bdf03d1 + languageName: node + linkType: hard + "gauge@npm:~2.7.3": version: 2.7.4 resolution: "gauge@npm:2.7.4" @@ -9119,6 +9126,7 @@ fsevents@^1.2.7: fancy-log: ^1.3.3 fs-extra: ^7.0.1 fuse.js: ^6.0.0 + fuzzysort: ^1.2.1 glob: ^7.2.0 google-timezones-json: ^1.0.2 gulp: ^4.0.2 From 28f5611df58256d503f349735246553d130c962e Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:07:53 -0500 Subject: [PATCH 098/181] Small edits on config menu (#12440) --- src/components/ha-navigation-list.ts | 3 + .../config/core/ha-config-section-updates.ts | 5 +- .../core/ha-config-system-navigation.ts | 5 +- .../config/dashboard/ha-config-updates.ts | 64 ++++++++++--------- .../config/hardware/ha-config-hardware.ts | 3 +- .../network/ha-config-section-network.ts | 1 + .../config/network/supervisor-hostname.ts | 35 +++++----- .../storage/ha-config-section-storage.ts | 36 +++++------ .../system-health/ha-config-system-health.ts | 2 +- src/translations/en.json | 4 +- 10 files changed, 81 insertions(+), 77 deletions(-) diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index 41200f14b5..782c5d4975 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -56,6 +56,9 @@ class HaNavigationList extends LitElement { } static styles: CSSResultGroup = css` + :host { + --mdc-list-vertical-padding: 0; + } a { text-decoration: none; color: var(--primary-text-color); diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index e0c0c6411e..708d44de51 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -262,7 +262,7 @@ class HaConfigSectionUpdates extends LitElement { margin: 0 auto; } ha-card { - max-width: 500px; + max-width: 600px; margin: 0 auto; height: 100%; justify-content: space-between; @@ -272,7 +272,6 @@ class HaConfigSectionUpdates extends LitElement { } .card-actions { height: 48px; - border-top: none; display: flex; justify-content: space-between; align-items: center; @@ -282,7 +281,7 @@ class HaConfigSectionUpdates extends LitElement { display: flex; justify-content: space-between; flex-direction: column; - padding: 16px 16px 0 16px; + padding: 16px; } `; } diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 7b29e93c5f..ad5edab63f 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -64,9 +64,6 @@ class HaConfigSystemNavigation extends LitElement { return [ haStyle, css` - ha-card { - margin-bottom: env(safe-area-inset-bottom); - } :host(:not([narrow])) ha-card { margin-bottom: max(24px, env(safe-area-inset-bottom)); } @@ -79,6 +76,8 @@ class HaConfigSystemNavigation extends LitElement { ha-card { overflow: hidden; + margin-bottom: 24px; + margin-bottom: max(24px, env(safe-area-inset-bottom)); } ha-card a { diff --git a/src/panels/config/dashboard/ha-config-updates.ts b/src/panels/config/dashboard/ha-config-updates.ts index f2d823bd01..416b0d9d8d 100644 --- a/src/panels/config/dashboard/ha-config-updates.ts +++ b/src/panels/config/dashboard/ha-config-updates.ts @@ -1,6 +1,6 @@ import "@material/mwc-button/mwc-button"; -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -8,7 +8,7 @@ import "../../../components/entity/state-badge"; import "../../../components/ha-alert"; import "../../../components/ha-icon-next"; import type { UpdateEntity } from "../../../data/update"; -import { HomeAssistant } from "../../../types"; +import type { HomeAssistant } from "../../../types"; @customElement("ha-config-updates") class HaConfigUpdates extends LitElement { @@ -35,39 +35,44 @@ class HaConfigUpdates extends LitElement { count: this.total || this.updateEntities.length, })}
    - ${updates.map( - (entity) => html` - - + + ${updates.map( + (entity) => html` + - - - ${entity.attributes.title || entity.attributes.friendly_name} -
    + ${entity.attributes.title || + entity.attributes.friendly_name} + ${this.hass.localize( "ui.panel.config.updates.version_available", { version_available: entity.attributes.latest_version, } - )} - ${entity.attributes.skipped_version + )}${entity.attributes.skipped_version ? `(${this.hass.localize("ui.panel.config.updates.skipped")})` : ""} -
    -
    - ${!this.narrow ? html`` : ""} -
    - ` - )} + + ${!this.narrow + ? html`` + : ""} + + ` + )} + `; } @@ -80,6 +85,9 @@ class HaConfigUpdates extends LitElement { static get styles(): CSSResultGroup[] { return [ css` + :host { + --mdc-list-vertical-padding: 0; + } .title { font-size: 16px; padding: 16px; @@ -88,11 +96,6 @@ class HaConfigUpdates extends LitElement { .skipped { background: var(--secondary-background-color); } - .icon { - display: inline-flex; - height: 100%; - align-items: center; - } ha-icon-next { color: var(--secondary-text-color); height: 24px; @@ -114,8 +117,9 @@ class HaConfigUpdates extends LitElement { outline: none; text-decoration: underline; } - paper-icon-item { + mwc-list-item { cursor: pointer; + font-size: 16px; } `, ]; diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index 6b22057e59..833f40b9ee 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -218,7 +218,7 @@ class HaConfigHardware extends LitElement { margin: 0 auto; } ha-card { - max-width: 500px; + max-width: 600px; margin: 0 auto; height: 100%; justify-content: space-between; @@ -237,7 +237,6 @@ class HaConfigHardware extends LitElement { } .card-actions { height: 48px; - border-top: none; display: flex; justify-content: space-between; align-items: center; diff --git a/src/panels/config/network/ha-config-section-network.ts b/src/panels/config/network/ha-config-section-network.ts index bdc61aaa0f..c7edf88f3b 100644 --- a/src/panels/config/network/ha-config-section-network.ts +++ b/src/panels/config/network/ha-config-section-network.ts @@ -28,6 +28,7 @@ class HaConfigSectionNetwork extends LitElement { ${isComponentLoaded(this.hass, "hassio") ? html` ` : ""} diff --git a/src/panels/config/network/supervisor-hostname.ts b/src/panels/config/network/supervisor-hostname.ts index f18c173217..acb58df7b3 100644 --- a/src/panels/config/network/supervisor-hostname.ts +++ b/src/panels/config/network/supervisor-hostname.ts @@ -14,6 +14,7 @@ import "../../../components/ha-header-bar"; import "../../../components/ha-icon-button"; import "../../../components/ha-radio"; import "../../../components/ha-related-items"; +import "../../../components/ha-settings-row"; import "../../../components/ha-textfield"; import { extractApiErrorMessage } from "../../../data/hassio/common"; import { @@ -22,12 +23,13 @@ import { } from "../../../data/hassio/host"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import type { HomeAssistant } from "../../../types"; -import "../../../components/ha-settings-row"; @customElement("supervisor-hostname") export class HassioHostname extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ type: Boolean }) narrow!: boolean; + @state() private _processing = false; @state() private _hostname?: string; @@ -48,11 +50,12 @@ export class HassioHostname extends LitElement { return html` -
    - +
    + Hostname The name your instance will have on your network + ${this._hostInfo + ? html` + + + + ${this.hass.localize( + "ui.panel.config.storage.datadisk.title" + )} + + + ` + : ""}
    ${this._error ? html` @@ -79,13 +96,6 @@ class HaConfigSectionStorage extends LitElement { ` : ""}
    -
    - - ${this.hass.localize( - "ui.panel.config.storage.datadisk.title" - )} - -
    ` : ""} @@ -118,26 +128,16 @@ class HaConfigSectionStorage extends LitElement { margin: 0 auto; } ha-card { - max-width: 500px; + max-width: 600px; margin: 0 auto; - height: 100%; justify-content: space-between; flex-direction: column; display: flex; } - .card-actions { - height: 48px; - border-top: none; - display: flex; - justify-content: space-between; - align-items: center; - } - .card-content { display: flex; justify-content: space-between; flex-direction: column; - padding: 16px 16px 0 16px; } `; } diff --git a/src/panels/config/system-health/ha-config-system-health.ts b/src/panels/config/system-health/ha-config-system-health.ts index 8c15021747..efc357cf2c 100644 --- a/src/panels/config/system-health/ha-config-system-health.ts +++ b/src/panels/config/system-health/ha-config-system-health.ts @@ -452,7 +452,7 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { } ha-card { display: block; - max-width: 500px; + max-width: 600px; margin: 0 auto; padding-bottom: 16px; margin-bottom: max(24px, env(safe-area-inset-bottom)); diff --git a/src/translations/en.json b/src/translations/en.json index 500a94caf5..5ab75292b8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3212,8 +3212,8 @@ }, "system_health": { "caption": "System Health", - "cpu_usage": "CPU Usage", - "ram_usage": "RAM Usage", + "cpu_usage": "Process Usage", + "ram_usage": "Memory Usage", "core_stats": "Core Stats", "supervisor_stats": "Supervisor Stats" } From 684e4421bc21221154ac30e9be86002c56a479ba Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:10:35 -0500 Subject: [PATCH 099/181] Fix for backup overflow (#12454) --- src/panels/config/backup/ha-config-backup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index 6574150209..bb4d1104af 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -80,6 +80,7 @@ class HaConfigBackup extends LitElement { actions: { title: "", width: "15%", + type: "overflow-menu", template: (_: string, backup: BackupContent) => html` Date: Tue, 26 Apr 2022 23:11:18 -0500 Subject: [PATCH 100/181] Update the hint for key C (#12458) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 5ab75292b8..b134b283f0 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -691,7 +691,7 @@ }, "filter_placeholder": "Entity Filter", "title": "Quick Search", - "key_c_hint": "Press 'c' on any page to open this search bar", + "key_c_hint": "Press 'c' on any page to open the search bar", "nothing_found": "Nothing found!" }, "voice_command": { From 3605f7b70f20a9bb6e9b5c2fab7e89ca1974c963 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:11:38 -0500 Subject: [PATCH 101/181] Fix when creating new area in picker #11392 (#12457) --- src/components/ha-area-picker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 2192bd931c..8225a73eee 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -409,7 +409,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { name, }); this._areas = [...this._areas!, area]; - (this.comboBox as any).items = this._getAreas( + (this.comboBox as any).filteredItems = this._getAreas( this._areas!, this._devices!, this._entities!, From fa004de2d1ba55c155d81ec586071e76faf00a50 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:11:54 -0500 Subject: [PATCH 102/181] Fix more info input number #12396 (#12456) --- src/state-summary/state-card-input_number.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/state-summary/state-card-input_number.js b/src/state-summary/state-card-input_number.js index 2fabea717a..85cda4ffc1 100644 --- a/src/state-summary/state-card-input_number.js +++ b/src/state-summary/state-card-input_number.js @@ -30,7 +30,8 @@ class StateCardInputNumber extends mixinBehaviors( .sliderstate { min-width: 45px; } - ha-slider[hidden] { + ha-slider[hidden], + ha-textfield[hidden] { display: none !important; } ha-textfield { From 53b6e31881c1260f652f0766d77c7cdceffb0e86 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:12:09 -0500 Subject: [PATCH 103/181] Update Configuration badge color to be accent color to match (#12455) --- src/components/ha-sidebar.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index ff377c1c5c..e55e4d14fd 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -1051,9 +1051,6 @@ class HaSidebar extends LitElement { padding: 0px 6px; color: var(--text-accent-color, var(--text-primary-color)); } - .configuration-badge { - background-color: var(--primary-color); - } ha-svg-icon + .notification-badge, ha-svg-icon + .configuration-badge { position: absolute; From 6747375a1b7696ae8274b473744a3c5df731f358 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:27:15 -0500 Subject: [PATCH 104/181] Move Provider Selection to Menu on top header (#12443) --- src/panels/config/logs/error-log-card.ts | 94 ++++++------------------ src/panels/config/logs/ha-config-logs.ts | 93 +++++++++++++++++++++-- src/translations/en.json | 5 +- 3 files changed, 111 insertions(+), 81 deletions(-) diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index 1ad78d38ae..e1818d6e7f 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -16,52 +16,27 @@ import "../../../components/ha-ansi-to-html"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; import "../../../components/ha-select"; -import { fetchErrorLog, LogProvider } from "../../../data/error_log"; +import { fetchErrorLog } from "../../../data/error_log"; import { extractApiErrorMessage } from "../../../data/hassio/common"; import { fetchHassioLogs } from "../../../data/hassio/supervisor"; import { HomeAssistant } from "../../../types"; -const logProviders: LogProvider[] = [ - { - key: "supervisor", - name: "Supervisor", - }, - { - key: "core", - name: "Home Assistant Core", - }, - { - key: "host", - name: "Host", - }, - { - key: "dns", - name: "DNS", - }, - { - key: "audio", - name: "Audio", - }, - { - key: "multicast", - name: "Multicast", - }, -]; - @customElement("error-log-card") class ErrorLogCard extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public filter = ""; + @property() public provider!: string; + + @property({ type: Boolean, attribute: true }) public show = false; + @state() private _isLogLoaded = false; - @state() private _logHTML!: TemplateResult[] | TemplateResult | string; + @state() private _logHTML?: TemplateResult[] | TemplateResult | string; @state() private _error?: string; - @state() private _selectedLogProvider?: string; - protected render(): TemplateResult { return html`
    @@ -72,26 +47,9 @@ class ErrorLogCard extends LitElement { ? html`
    - ${this.hass.userData?.showAdvanced && - isComponentLoaded(this.hass, "hassio") - ? html` - - ${logProviders.map( - (provider) => html` - - ${provider.name} - - ` - )} - - ` - : ""} +

    + ${this.hass.localize("ui.panel.config.logs.full_logs")} +

    - ${this.hass.localize("ui.panel.config.logs.load_full_log")} + ${this.hass.localize("ui.panel.config.logs.load_logs")} ` : ""} @@ -116,7 +74,7 @@ class ErrorLogCard extends LitElement { protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); - if (this.hass?.config.safe_mode) { + if (this.hass?.config.safe_mode || this.show) { this.hass.loadFragmentTranslation("config"); this._refreshLogs(); } @@ -125,21 +83,19 @@ class ErrorLogCard extends LitElement { protected updated(changedProps) { super.updated(changedProps); - if (changedProps.has("filter") && this._isLogLoaded) { + if (changedProps.has("provider")) { + this._logHTML = undefined; + } + + if ( + (changedProps.has("filter") && this._isLogLoaded) || + (changedProps.has("show") && this.show) || + (changedProps.has("provider") && this.show) + ) { this._refreshLogs(); } } - private async _setLogProvider(ev): Promise { - const provider = ev.target.value; - if (provider === this._selectedLogProvider) { - return; - } - - this._selectedLogProvider = provider; - this._refreshLogs(); - } - private async _refresh(ev: CustomEvent): Promise { const button = ev.currentTarget as any; button.progress = true; @@ -152,13 +108,9 @@ class ErrorLogCard extends LitElement { this._logHTML = this.hass.localize("ui.panel.config.logs.loading_log"); let log: string; - if (!this._selectedLogProvider && isComponentLoaded(this.hass, "hassio")) { - this._selectedLogProvider = "core"; - } - - if (this._selectedLogProvider) { + if (isComponentLoaded(this.hass, "hassio")) { try { - log = await fetchHassioLogs(this.hass, this._selectedLogProvider); + log = await fetchHassioLogs(this.hass, this.provider); this._logHTML = html` `; this._isLogLoaded = true; @@ -167,7 +119,7 @@ class ErrorLogCard extends LitElement { this._error = this.hass.localize( "ui.panel.config.logs.failed_get_logs", "provider", - this._selectedLogProvider, + this.provider, "error", extractApiErrorMessage(err) ); diff --git a/src/panels/config/logs/ha-config-logs.ts b/src/panels/config/logs/ha-config-logs.ts index f3f7915b30..803cc7e3f0 100644 --- a/src/panels/config/logs/ha-config-logs.ts +++ b/src/panels/config/logs/ha-config-logs.ts @@ -1,7 +1,11 @@ +import { mdiChevronDown } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { extractSearchParam } from "../../../common/url/search-params"; +import "../../../components/ha-button-menu"; import "../../../components/search-input"; +import { LogProvider } from "../../../data/error_log"; import "../../../layouts/hass-subpage"; import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; @@ -10,22 +14,51 @@ import "./error-log-card"; import "./system-log-card"; import type { SystemLogCard } from "./system-log-card"; +const logProviders: LogProvider[] = [ + { + key: "core", + name: "Home Assistant Core", + }, + { + key: "supervisor", + name: "Supervisor", + }, + { + key: "host", + name: "Host", + }, + { + key: "dns", + name: "DNS", + }, + { + key: "audio", + name: "Audio", + }, + { + key: "multicast", + name: "Multicast", + }, +]; + @customElement("ha-config-logs") export class HaConfigLogs extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced!: boolean; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; @state() private _filter = extractSearchParam("filter") || ""; @query("system-log-card", true) private systemLog?: SystemLogCard; + @state() private _selectedLogProvider = "core"; + public connectedCallback() { super.connectedCallback(); if (this.systemLog && this.systemLog.loaded) { @@ -68,21 +101,60 @@ export class HaConfigLogs extends LitElement { .header=${this.hass.localize("ui.panel.config.logs.caption")} back-path="/config/system" > + ${isComponentLoaded(this.hass, "hassio") && + this.hass.userData?.showAdvanced + ? html` + + p.key === this._selectedLogProvider + )!.name} + > + + + ${logProviders.map( + (provider) => html` + + ${provider.name} + + ` + )} + + ` + : ""} ${search}
    - + ${this._selectedLogProvider === "core" + ? html` + + ` + : ""}
    `; } + private _selectProvider(ev) { + this._selectedLogProvider = (ev.currentTarget as any).provider; + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -108,6 +180,11 @@ export class HaConfigLogs extends LitElement { .content { direction: ltr; } + + mwc-button[slot="trigger"] { + --mdc-theme-primary: var(--primary-text-color); + --mdc-icon-size: 36px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index b134b283f0..d97778537e 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1564,7 +1564,7 @@ "search": "Search logs", "failed_get_logs": "Failed to get {provider} logs, {error}", "no_issues_search": "No issues found for search term ''{term}''", - "load_full_log": "Load Full Home Assistant Log", + "load_logs": "Load Full Logs", "loading_log": "Loading error log…", "no_errors": "No errors have been reported", "no_issues": "There are no new issues!", @@ -1581,7 +1581,8 @@ "debug": "DEBUG" }, "custom_integration": "custom integration", - "error_from_custom_integration": "This error originated from a custom integration." + "error_from_custom_integration": "This error originated from a custom integration.", + "full_logs": "Full logs" }, "lovelace": { "caption": "Dashboards", From fda03918b95c353be152bc57b05bff51ce0eb5e4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Apr 2022 21:40:01 -0700 Subject: [PATCH 105/181] Move the analytics link (#12459) --- src/panels/config/core/ha-config-analytics.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/panels/config/core/ha-config-analytics.ts b/src/panels/config/core/ha-config-analytics.ts index 7efedea239..0fe2a1763c 100644 --- a/src/panels/config/core/ha-config-analytics.ts +++ b/src/panels/config/core/ha-config-analytics.ts @@ -58,9 +58,9 @@ class ConfigAnalytics extends LitElement { "ui.panel.config.core.section.core.core_config.save_button" )} - ${analyticsLearnMore(this.hass)}
    + `; } @@ -117,6 +117,10 @@ class ConfigAnalytics extends LitElement { justify-content: space-between; align-items: center; } + .footer { + padding: 32px 0 16px; + text-align: center; + } `, // row-reverse so we tab first to "save" ]; } From b03abc249be706992b2a3a7626c73d5f0abcfd40 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:52:22 -0500 Subject: [PATCH 106/181] Fix Updates Page Toast - Move to overflow (#12453) --- src/data/update.ts | 77 +++++++++ .../config/core/ha-config-section-updates.ts | 161 +++++------------- .../config/dashboard/ha-config-dashboard.ts | 86 +--------- 3 files changed, 124 insertions(+), 200 deletions(-) diff --git a/src/data/update.ts b/src/data/update.ts index 8976e66f36..e76a8947a3 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -1,10 +1,15 @@ import type { + HassEntities, HassEntityAttributeBase, HassEntityBase, } from "home-assistant-js-websocket"; import { BINARY_STATE_ON } from "../common/const"; +import { computeStateDomain } from "../common/entity/compute_state_domain"; import { supportsFeature } from "../common/entity/supports-feature"; +import { caseInsensitiveStringCompare } from "../common/string/compare"; +import { showAlertDialog } from "../dialogs/generic/show-dialog-box"; import { HomeAssistant } from "../types"; +import { showToast } from "../util/toast"; export const UPDATE_SUPPORT_INSTALL = 1; export const UPDATE_SUPPORT_SPECIFIC_VERSION = 2; @@ -47,3 +52,75 @@ export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) => type: "update/release_notes", entity_id: entityId, }); + +export const filterUpdateEntities = (entities: HassEntities) => + ( + Object.values(entities).filter( + (entity) => computeStateDomain(entity) === "update" + ) as UpdateEntity[] + ).sort((a, b) => { + if (a.attributes.title === "Home Assistant Core") { + return -3; + } + if (b.attributes.title === "Home Assistant Core") { + return 3; + } + if (a.attributes.title === "Home Assistant Operating System") { + return -2; + } + if (b.attributes.title === "Home Assistant Operating System") { + return 2; + } + if (a.attributes.title === "Home Assistant Supervisor") { + return -1; + } + if (b.attributes.title === "Home Assistant Supervisor") { + return 1; + } + return caseInsensitiveStringCompare( + a.attributes.title || a.attributes.friendly_name || "", + b.attributes.title || b.attributes.friendly_name || "" + ); + }); + +export const filterUpdateEntitiesWithInstall = ( + entities: HassEntities, + showSkipped = false +) => + filterUpdateEntities(entities).filter((entity) => + updateCanInstall(entity, showSkipped) + ); + +export const checkForEntityUpdates = async ( + element: HTMLElement, + hass: HomeAssistant +) => { + const entities = filterUpdateEntities(hass.states).map( + (entity) => entity.entity_id + ); + + if (!entities.length) { + showAlertDialog(element, { + title: hass.localize("ui.panel.config.updates.no_update_entities.title"), + text: hass.localize( + "ui.panel.config.updates.no_update_entities.description" + ), + warning: true, + }); + return; + } + + await hass.callService("homeassistant", "update_entity", { + entity_id: entities, + }); + + if (filterUpdateEntitiesWithInstall(hass.states).length) { + showToast(element, { + message: hass.localize("ui.panel.config.updates.updates_refreshed"), + }); + } else { + showToast(element, { + message: hass.localize("ui.panel.config.updates.no_new_updates"), + }); + } +}; diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index 708d44de51..f5cc96ecae 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -1,13 +1,11 @@ import type { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; -import { mdiDotsVertical } from "@mdi/js"; +import { mdiDotsVertical, mdiRefresh } from "@mdi/js"; import { HassEntities } from "home-assistant-js-websocket"; -import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; -import { computeStateDomain } from "../../../common/entity/compute_state_domain"; -import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import "../../../components/ha-alert"; import "../../../components/ha-bar"; import "../../../components/ha-button-menu"; @@ -21,14 +19,16 @@ import { setSupervisorOption, SupervisorOptions, } from "../../../data/hassio/supervisor"; -import { updateCanInstall, UpdateEntity } from "../../../data/update"; +import { + checkForEntityUpdates, + filterUpdateEntitiesWithInstall, +} from "../../../data/update"; import { showAlertDialog, showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; import type { HomeAssistant } from "../../../types"; -import { showToast } from "../../../util/toast"; import "../dashboard/ha-config-updates"; @customElement("ha-config-section-updates") @@ -41,8 +41,6 @@ class HaConfigSectionUpdates extends LitElement { @state() private _supervisorInfo?: HassioSupervisorInfo; - private _notifyUpdates = false; - protected firstUpdated(changedProps) { super.firstUpdated(changedProps); @@ -66,31 +64,38 @@ class HaConfigSectionUpdates extends LitElement { .narrow=${this.narrow} .header=${this.hass.localize("ui.panel.config.updates.caption")} > - +
    - - ${this._showSkipped - ? this.hass.localize("ui.panel.config.updates.hide_skipped") - : this.hass.localize("ui.panel.config.updates.show_skipped")} - - ${this._supervisorInfo?.channel !== "dev" - ? html` - - ${this._supervisorInfo?.channel === "stable" - ? this.hass.localize("ui.panel.config.updates.join_beta") - : this.hass.localize("ui.panel.config.updates.leave_beta")} - - ` - : ""} - + + + + ${this._showSkipped + ? this.hass.localize("ui.panel.config.updates.hide_skipped") + : this.hass.localize("ui.panel.config.updates.show_skipped")} + + ${this._supervisorInfo?.channel !== "dev" + ? html` + + ${this._supervisorInfo?.channel === "stable" + ? this.hass.localize("ui.panel.config.updates.join_beta") + : this.hass.localize( + "ui.panel.config.updates.leave_beta" + )} + + ` + : ""} + +
    @@ -107,44 +112,12 @@ class HaConfigSectionUpdates extends LitElement { ${this.hass.localize("ui.panel.config.updates.no_updates")} `}
    -
    - - ${this.hass.localize("ui.panel.config.updates.check_updates")} - -
    `; } - protected override updated(changedProps: PropertyValues): void { - super.updated(changedProps); - - if ( - !changedProps.has("hass") || - !this._notifyUpdates || - !changedProps.has("_showSkipped") - ) { - return; - } - this._notifyUpdates = false; - if ( - this._filterUpdateEntitiesWithInstall(this.hass.states, this._showSkipped) - .length - ) { - showToast(this, { - message: this.hass.localize( - "ui.panel.config.updates.updates_refreshed" - ), - }); - } else { - showToast(this, { - message: this.hass.localize("ui.panel.config.updates.no_new_updates"), - }); - } - } - private _handleAction(ev: CustomEvent) { switch (ev.detail.index) { case 0: @@ -195,64 +168,12 @@ class HaConfigSectionUpdates extends LitElement { } private async _checkUpdates(): Promise { - const _entities = this._filterUpdateEntities(this.hass.states).map( - (entity) => entity.entity_id - ); - - if (_entities.length) { - this._notifyUpdates = true; - await this.hass.callService("homeassistant", "update_entity", { - entity_id: _entities, - }); - return; - } - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.updates.no_update_entities.title" - ), - text: this.hass.localize( - "ui.panel.config.updates.no_update_entities.description" - ), - warning: true, - }); + checkForEntityUpdates(this, this.hass); } - private _filterUpdateEntities = memoizeOne((entities: HassEntities) => - ( - Object.values(entities).filter( - (entity) => computeStateDomain(entity) === "update" - ) as UpdateEntity[] - ).sort((a, b) => { - if (a.attributes.title === "Home Assistant Core") { - return -3; - } - if (b.attributes.title === "Home Assistant Core") { - return 3; - } - if (a.attributes.title === "Home Assistant Operating System") { - return -2; - } - if (b.attributes.title === "Home Assistant Operating System") { - return 2; - } - if (a.attributes.title === "Home Assistant Supervisor") { - return -1; - } - if (b.attributes.title === "Home Assistant Supervisor") { - return 1; - } - return caseInsensitiveStringCompare( - a.attributes.title || a.attributes.friendly_name || "", - b.attributes.title || b.attributes.friendly_name || "" - ); - }) - ); - private _filterUpdateEntitiesWithInstall = memoizeOne( (entities: HassEntities, showSkipped: boolean) => - this._filterUpdateEntities(entities).filter((entity) => - updateCanInstall(entity, showSkipped) - ) + filterUpdateEntitiesWithInstall(entities, showSkipped) ); static styles = css` @@ -270,12 +191,6 @@ class HaConfigSectionUpdates extends LitElement { display: flex; margin-bottom: max(24px, env(safe-area-inset-bottom)); } - .card-actions { - height: 48px; - display: flex; - justify-content: space-between; - align-items: center; - } .card-content { display: flex; diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 5eaa08aaa6..f4ad00f027 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -3,7 +3,7 @@ import "@material/mwc-list/mwc-list-item"; import { mdiCloudLock, mdiDotsVertical, mdiMagnify, mdiNewBox } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import type { HassEntities } from "home-assistant-js-websocket"; +import { HassEntities } from "home-assistant-js-websocket"; import { css, CSSResultGroup, @@ -15,8 +15,6 @@ import { import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; -import { computeStateDomain } from "../../../common/entity/compute_state_domain"; -import { caseInsensitiveStringCompare } from "../../../common/string/compare"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; @@ -25,15 +23,17 @@ import "../../../components/ha-menu-button"; import "../../../components/ha-svg-icon"; import "../../../components/ha-tip"; import { CloudStatus } from "../../../data/cloud"; -import { updateCanInstall, UpdateEntity } from "../../../data/update"; -import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { + checkForEntityUpdates, + filterUpdateEntitiesWithInstall, + UpdateEntity, +} from "../../../data/update"; import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar"; import "../../../layouts/ha-app-layout"; import { PageNavigation } from "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; -import { showToast } from "../../../util/toast"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; import "./ha-config-navigation"; @@ -113,8 +113,6 @@ class HaConfigDashboard extends LitElement { @state() private _tip?: string; - private _notifyUpdates = false; - private _pages = memoizeOne((clouStatus, isLoaded) => { const pages: PageNavigation[] = []; if (clouStatus && isLoaded) { @@ -219,60 +217,12 @@ class HaConfigDashboard extends LitElement { if (!this._tip && changedProps.has("hass")) { this._tip = randomTip(this.hass); } - - if (!changedProps.has("hass") || !this._notifyUpdates) { - return; - } - this._notifyUpdates = false; - if (this._filterUpdateEntitiesWithInstall(this.hass.states).length) { - showToast(this, { - message: this.hass.localize( - "ui.panel.config.updates.updates_refreshed" - ), - }); - } else { - showToast(this, { - message: this.hass.localize("ui.panel.config.updates.no_new_updates"), - }); - } } - private _filterUpdateEntities = memoizeOne((entities: HassEntities) => - ( - Object.values(entities).filter( - (entity) => computeStateDomain(entity) === "update" - ) as UpdateEntity[] - ).sort((a, b) => { - if (a.attributes.title === "Home Assistant Core") { - return -3; - } - if (b.attributes.title === "Home Assistant Core") { - return 3; - } - if (a.attributes.title === "Home Assistant Operating System") { - return -2; - } - if (b.attributes.title === "Home Assistant Operating System") { - return 2; - } - if (a.attributes.title === "Home Assistant Supervisor") { - return -1; - } - if (b.attributes.title === "Home Assistant Supervisor") { - return 1; - } - return caseInsensitiveStringCompare( - a.attributes.title || a.attributes.friendly_name || "", - b.attributes.title || b.attributes.friendly_name || "" - ); - }) - ); - private _filterUpdateEntitiesWithInstall = memoizeOne( (entities: HassEntities): [UpdateEntity[], number] => { - const updates = this._filterUpdateEntities(entities).filter((entity) => - updateCanInstall(entity) - ); + const updates = filterUpdateEntitiesWithInstall(entities); + return [ updates.slice(0, updates.length === 3 ? updates.length : 2), updates.length, @@ -288,27 +238,9 @@ class HaConfigDashboard extends LitElement { } private async _handleMenuAction(ev: CustomEvent) { - const _entities = this._filterUpdateEntities(this.hass.states).map( - (entity) => entity.entity_id - ); switch (ev.detail.index) { case 0: - if (_entities.length) { - this._notifyUpdates = true; - await this.hass.callService("homeassistant", "update_entity", { - entity_id: _entities, - }); - return; - } - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.updates.no_update_entities.title" - ), - text: this.hass.localize( - "ui.panel.config.updates.no_update_entities.description" - ), - warning: true, - }); + checkForEntityUpdates(this, this.hass); break; } } From 7877dd8e6b827f25c1e2e23b39928173ad5704e2 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 26 Apr 2022 23:53:29 -0500 Subject: [PATCH 107/181] Move Zones Edit to General config + add general config page (#12452) * Move Zones Edit to General config + add general * Update src/translations/en.json Co-authored-by: Paulus Schoutsen * add paper tooltip back for yaml Co-authored-by: Paulus Schoutsen --- .../config/core/ha-config-section-general.ts | 313 ++++++++++++++++++ src/panels/config/ha-panel-config.ts | 11 + .../config/zone/dialog-core-zone-detail.ts | 284 ---------------- src/panels/config/zone/ha-config-zone.ts | 33 +- .../zone/show-dialog-core-zone-detail.ts | 12 - src/translations/en.json | 6 +- 6 files changed, 343 insertions(+), 316 deletions(-) create mode 100644 src/panels/config/core/ha-config-section-general.ts delete mode 100644 src/panels/config/zone/dialog-core-zone-detail.ts delete mode 100644 src/panels/config/zone/show-dialog-core-zone-detail.ts diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts new file mode 100644 index 0000000000..97f3552607 --- /dev/null +++ b/src/panels/config/core/ha-config-section-general.ts @@ -0,0 +1,313 @@ +import timezones from "google-timezones-json"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { UNIT_C } from "../../../common/const"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; +import { navigate } from "../../../common/navigate"; +import { HaProgressButton } from "../../../components/buttons/ha-progress-button"; +import { currencies } from "../../../components/currency-datalist"; +import "../../../components/ha-formfield"; +import "../../../components/ha-radio"; +import type { HaRadio } from "../../../components/ha-radio"; +import "../../../components/ha-settings-row"; +import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; +import { SYMBOL_TO_ISO } from "../../../data/currency"; +import "../../../layouts/hass-subpage"; +import { haStyle } from "../../../resources/styles"; +import type { HomeAssistant } from "../../../types"; + +@customElement("ha-config-section-general") +class HaConfigSectionGeneral extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + @state() private _submitting = false; + + @state() private _unitSystem?: ConfigUpdateValues["unit_system"]; + + @state() private _currency?: string; + + @state() private _name?: string; + + @state() private _elevation?: number; + + @state() private _timeZone?: string; + + protected render(): TemplateResult { + const canEdit = ["storage", "default"].includes( + this.hass.config.config_source + ); + const disabled = this._submitting || !canEdit; + return html` + +
    + +
    + ${!canEdit + ? html` +

    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.edit_requires_storage" + )} +

    + ` + : ""} + + + ${Object.keys(timezones).map( + (tz) => + html`${timezones[tz]}` + )} + + + + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.elevation_meters" + )} + + +
    +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.unit_system" + )} +
    + + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.metric_example" + )} + +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.unit_system_metric" + )} +
    + `} + > + +
    + + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.imperial_example" + )} + +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.unit_system_imperial" + )} +
    + `} + > + +
    +
    +
    + + ${currencies.map( + (currency) => + html`${currency}` + )} + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.find_currency_value" + )} +
    +
    + +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.edit_location" + )} +
    +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.edit_location_description" + )} +
    + ${this.hass.localize("ui.common.edit")} +
    +
    + + ${this.hass!.localize("ui.panel.config.zone.detail.update")} + +
    +
    +
    +
    + `; + } + + protected firstUpdated(): void { + this._unitSystem = + this.hass.config.unit_system.temperature === UNIT_C + ? "metric" + : "imperial"; + this._currency = this.hass.config.currency; + this._elevation = this.hass.config.elevation; + this._timeZone = this.hass.config.time_zone; + this._name = this.hass.config.location_name; + } + + private _handleChange(ev) { + const target = ev.currentTarget; + let value = target.value; + + if (target.name === "currency" && value) { + if (value in SYMBOL_TO_ISO) { + value = SYMBOL_TO_ISO[value]; + } + } + + this[`_${target.name}`] = value; + } + + private _unitSystemChanged(ev: CustomEvent) { + this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial"; + } + + private async _updateEntry(ev) { + const button = ev.target as HaProgressButton; + if (button.progress) { + return; + } + button.progress = true; + + try { + await saveCoreConfig(this.hass, { + currency: this._currency, + elevation: Number(this._elevation), + unit_system: this._unitSystem, + time_zone: this._timeZone, + location_name: this._name, + }); + button.actionSuccess(); + } catch (err: any) { + button.actionError(); + alert(`Error saving config: ${err.message}`); + } finally { + button.progress = false; + } + } + + private _editLocation() { + navigate("/config/zone"); + } + + static styles = [ + haStyle, + css` + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + ha-card { + max-width: 500px; + margin: 0 auto; + height: 100%; + justify-content: space-between; + flex-direction: column; + display: flex; + } + .card-content { + display: flex; + justify-content: space-between; + flex-direction: column; + padding: 16px 16px 0 16px; + } + .card-actions { + text-align: right; + height: 48px; + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 16px; + } + .card-content > * { + display: block; + margin-top: 16px; + } + ha-select { + display: block; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-section-general": HaConfigSectionGeneral; + } +} diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index e9e624e7fa..360d7b8ea7 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -324,6 +324,13 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#507FfE", components: ["system_health", "hassio"], }, + { + path: "/config/general", + translationKey: "ui.panel.config.core.caption", + iconPath: mdiCog, + iconColor: "#653249", + core: true, + }, ], about: [ { @@ -462,6 +469,10 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-zone", load: () => import("./zone/ha-config-zone"), }, + general: { + tag: "ha-config-section-general", + load: () => import("./core/ha-config-section-general"), + }, zha: { tag: "zha-config-dashboard-router", load: () => diff --git a/src/panels/config/zone/dialog-core-zone-detail.ts b/src/panels/config/zone/dialog-core-zone-detail.ts deleted file mode 100644 index 411be8bf04..0000000000 --- a/src/panels/config/zone/dialog-core-zone-detail.ts +++ /dev/null @@ -1,284 +0,0 @@ -import "@material/mwc-button"; -import "@material/mwc-list/mwc-list"; -import "@material/mwc-list/mwc-list-item"; -import timezones from "google-timezones-json"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { UNIT_C } from "../../../common/const"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { stopPropagation } from "../../../common/dom/stop_propagation"; -import { currencies } from "../../../components/currency-datalist"; -import { createCloseHeading } from "../../../components/ha-dialog"; -import "../../../components/ha-formfield"; -import "../../../components/ha-radio"; -import type { HaRadio } from "../../../components/ha-radio"; -import "../../../components/ha-select"; -import "../../../components/ha-textfield"; -import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; -import { SYMBOL_TO_ISO } from "../../../data/currency"; -import { haStyleDialog } from "../../../resources/styles"; -import type { HomeAssistant } from "../../../types"; - -@customElement("dialog-core-zone-detail") -class DialogZoneDetail extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @state() private _submitting = false; - - @state() private _open = false; - - @state() private _unitSystem?: ConfigUpdateValues["unit_system"]; - - @state() private _currency?: string; - - @state() private _name?: string; - - @state() private _elevation?: number; - - @state() private _timeZone?: string; - - public showDialog(): void { - this._submitting = false; - this._unitSystem = - this.hass.config.unit_system.temperature === UNIT_C - ? "metric" - : "imperial"; - this._currency = this.hass.config.currency; - this._elevation = this.hass.config.elevation; - this._timeZone = this.hass.config.time_zone; - this._name = this.hass.config.location_name; - this._open = true; - } - - public closeDialog(): void { - this._open = false; - this._currency = undefined; - this._elevation = undefined; - this._timeZone = undefined; - this._unitSystem = undefined; - this._name = undefined; - - fireEvent(this, "dialog-closed", { dialog: this.localName }); - } - - protected render(): TemplateResult { - const canEdit = ["storage", "default"].includes( - this.hass.config.config_source - ); - const disabled = this._submitting || !canEdit; - - if (!this._open) { - return html``; - } - - return html` - - ${!canEdit - ? html` -

    - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.edit_requires_storage" - )} -

    - ` - : ""} - - - ${Object.keys(timezones).map( - (tz) => - html`${timezones[tz]}` - )} - - - - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.elevation_meters" - )} - - -
    -
    - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.unit_system" - )} -
    - - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.metric_example" - )} -
    `} - > - - - - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.imperial_example" - )} -
    `} - > - - -
    -
    - - ${currencies.map( - (currency) => - html`${currency}` - )} - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.find_currency_value" - )} -
    - - ${this.hass!.localize("ui.panel.config.zone.detail.update")} - - - `; - } - - private _handleChange(ev) { - const target = ev.currentTarget; - let value = target.value; - - if (target.name === "currency" && value) { - if (value in SYMBOL_TO_ISO) { - value = SYMBOL_TO_ISO[value]; - } - } - - this[`_${target.name}`] = value; - } - - private _unitSystemChanged(ev: CustomEvent) { - this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial"; - } - - private async _updateEntry() { - this._submitting = true; - try { - await saveCoreConfig(this.hass, { - currency: this._currency, - elevation: Number(this._elevation), - unit_system: this._unitSystem, - time_zone: this._timeZone, - location_name: this._name, - }); - } catch (err: any) { - alert(`Error saving config: ${err.message}`); - } finally { - this._submitting = false; - } - - this.closeDialog(); - } - - static get styles(): CSSResultGroup { - return [ - haStyleDialog, - css` - ha-dialog { - --mdc-dialog-min-width: 600px; - } - @media all and (max-width: 450px), all and (max-height: 500px) { - ha-dialog { - --mdc-dialog-min-width: calc( - 100vw - env(safe-area-inset-right) - env(safe-area-inset-left) - ); - } - } - .card-actions { - text-align: right; - } - ha-dialog > * { - display: block; - margin-top: 16px; - } - ha-select { - display: block; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "dialog-core-zone-detail": DialogZoneDetail; - } -} diff --git a/src/panels/config/zone/ha-config-zone.ts b/src/panels/config/zone/ha-config-zone.ts index e46ed02abf..cd91aeb472 100644 --- a/src/panels/config/zone/ha-config-zone.ts +++ b/src/panels/config/zone/ha-config-zone.ts @@ -46,7 +46,6 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant, Route } from "../../../types"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; -import { showCoreZoneDetailDialog } from "./show-dialog-core-zone-detail"; import { showZoneDetailDialog } from "./show-dialog-zone-detail"; @customElement("ha-config-zone") @@ -188,30 +187,26 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
    - - ${stateObject.entity_id === "zone.home" - ? hass.localize( - `ui.panel.config.zone.${ - this.narrow - ? "edit_home_zone_narrow" - : "edit_home_zone" - }` - ) - : hass.localize( - "ui.panel.config.zone.configured_in_yaml" - )} - + ${stateObject.entity_id !== "zone.home" + ? html` + + ${hass.localize( + "ui.panel.config.zone.configured_in_yaml" + )} + + ` + : ""}
    ` @@ -397,7 +392,7 @@ export class HaConfigZone extends SubscribeMixin(LitElement) { }); return; } - showCoreZoneDetailDialog(this); + navigate("/config/general"); } private async _createEntry(values: ZoneMutableParams) { diff --git a/src/panels/config/zone/show-dialog-core-zone-detail.ts b/src/panels/config/zone/show-dialog-core-zone-detail.ts deleted file mode 100644 index 4bf747169c..0000000000 --- a/src/panels/config/zone/show-dialog-core-zone-detail.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { fireEvent } from "../../../common/dom/fire_event"; - -export const loadCoreZoneDetailDialog = () => - import("./dialog-core-zone-detail"); - -export const showCoreZoneDetailDialog = (element: HTMLElement): void => { - fireEvent(element, "show-dialog", { - dialogTag: "dialog-core-zone-detail", - dialogImport: loadCoreZoneDetailDialog, - dialogParams: {}, - }); -}; diff --git a/src/translations/en.json b/src/translations/en.json index d97778537e..4f4ed026b9 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -314,6 +314,7 @@ "undo": "Undo", "move": "Move", "save": "Save", + "edit": "Edit", "submit": "Submit", "rename": "Rename", "yes": "Yes", @@ -1494,7 +1495,9 @@ "metric_example": "Celsius, kilograms", "find_currency_value": "Find your value", "save_button": "Save", - "currency": "Currency" + "currency": "Currency", + "edit_location": "Edit location", + "edit_location_description": "Location can be changed in zone settings" } } } @@ -2687,6 +2690,7 @@ "create_zone": "Add Zone", "add_zone": "Add Zone", "edit_zone": "Edit Zone", + "edit_home": "Edit Home", "confirm_delete": "Are you sure you want to delete this zone?", "can_not_edit": "Unable to edit zone", "configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.", From 186550229c53a0bfb249f3eec6962313fa91192c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Apr 2022 21:53:42 -0700 Subject: [PATCH 108/181] Tweak menu descriptions (#12460) --- src/translations/en.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 4f4ed026b9..606c63338a 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1112,7 +1112,7 @@ }, "automations": { "main": "Automations & Scenes", - "secondary": "Manage automations, scenes, scripts and blueprints" + "secondary": "Automations, scenes, scripts and blueprints" }, "backup": { "main": "Backup", @@ -1120,11 +1120,11 @@ }, "supervisor": { "main": "Add-ons", - "secondary": "Extend the function around Home Assistant" + "secondary": "Run extra applications next to Home Assistant" }, "dashboards": { "main": "Dashboards", - "secondary": "Create customized sets of cards to control your home" + "secondary": "Organize how you interact with your home" }, "energy": { "main": "Energy", @@ -1132,15 +1132,15 @@ }, "tags": { "main": "Tags", - "secondary": "Trigger automations when an NFC tag, QR code, etc. is scanned" + "secondary": "Manage NFC tags and QR codes" }, "people": { "main": "People", - "secondary": "Manage the people that Home Assistant tracks" + "secondary": "Manage who can access your home" }, "areas": { "main": "Areas & Zones", - "secondary": "Manage areas and zones that Home Assistant tracks" + "secondary": "Manage locations in and around your house" }, "companion": { "main": "Companion App", From e5824c4794934a9b62d3655e2229a676e6d19178 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Apr 2022 21:58:18 -0700 Subject: [PATCH 109/181] Fix my link for config dashboard and profile (#12461) * Fix my link for config dashboard and profile * add server control redirect Co-authored-by: Zack --- src/panels/my/ha-panel-my.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index bfd163c187..ab5ae0cda9 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -35,7 +35,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ redirect: "/developer-tools/statistics", }, config: { - redirect: "/config", + redirect: "/config/dashboard", }, cloud: { component: "cloud", @@ -132,7 +132,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ redirect: "/config/core", }, server_controls: { - redirect: "/config/server_control", + redirect: "/developer-tools/yaml", }, logs: { redirect: "/config/logs", @@ -145,7 +145,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ redirect: "/config/dashboard", }, profile: { - redirect: "/profile/dashboard", + redirect: "/profile", }, logbook: { component: "logbook", From f82dada3e5864aec0be61973555421f1e5715e95 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Apr 2022 21:58:26 -0700 Subject: [PATCH 110/181] Fix icon alignment in nav list (#12463) --- src/components/ha-navigation-list.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index 782c5d4975..fe1c044c35 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -71,6 +71,7 @@ class HaNavigationList extends LitElement { color: var(--secondary-text-color); height: 24px; width: 24px; + display: block; } ha-svg-icon { padding: 8px; From 293df61872b8d1cc06aa6f0ff1f55084af153d7b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Apr 2022 22:01:40 -0700 Subject: [PATCH 111/181] Add a tip for my shortcut (#12462) --- src/panels/config/dashboard/ha-config-dashboard.ts | 6 +++--- src/translations/en.json | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index f4ad00f027..326f173499 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -1,6 +1,6 @@ import type { ActionDetail } from "@material/mwc-list"; import "@material/mwc-list/mwc-list-item"; -import { mdiCloudLock, mdiDotsVertical, mdiMagnify, mdiNewBox } from "@mdi/js"; +import { mdiCloudLock, mdiDotsVertical, mdiMagnify } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import { HassEntities } from "home-assistant-js-websocket"; @@ -81,12 +81,12 @@ const randomTip = (hass: HomeAssistant) => { rel="noreferrer" >Newsletter - ` + ` ), weight: 2, }, { content: hass.localize("ui.tips.key_c_hint"), weight: 1 }, + { content: hass.localize("ui.tips.key_m_hint"), weight: 1 }, ]; tips.forEach((tip) => { diff --git a/src/translations/en.json b/src/translations/en.json index 606c63338a..412bbd4272 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1085,7 +1085,7 @@ "integration_starting": "Starting {integration}, not everything will be available until it is finished.", "triggered": "Triggered {name}", "dismiss": "Dismiss", - "no_matching_link_found": "No matching my link found for {path}" + "no_matching_link_found": "No matching My link found for {path}" }, "sidebar": { "external_app_configuration": "App Configuration", @@ -4286,7 +4286,8 @@ }, "tips": { "key_c_hint": "Press 'c' on any page to open the command dialog", - "key_e_hint": "Press 'e' on any page to open the entity search dialog" + "key_e_hint": "Press 'e' on any page to open the entity search dialog", + "key_m_hint": "Press 'm' on any page to get the My Home Assistant link" } }, "supervisor": { From 27884b9a54ac52505ca854a4dd44fa9806397112 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 27 Apr 2022 00:12:44 -0500 Subject: [PATCH 112/181] Move Restart to Overflow and yaml config advanced (#12446) * Move Restart to Overflow and yaml config advanced * Move around YAML Config page * Move to developer tools * Make card actions * Update Translations --- .../core/ha-config-system-navigation.ts | 34 +++ src/panels/config/ha-panel-config.ts | 13 - .../ha-config-server-control.ts | 269 ------------------ .../developer-tools/developer-tools-router.ts | 4 + .../ha-panel-developer-tools.ts | 3 + .../developer-yaml-config.ts | 240 ++++++++++++++++ src/translations/en.json | 192 +++++++------ 7 files changed, 379 insertions(+), 376 deletions(-) delete mode 100644 src/panels/config/server_control/ha-config-server-control.ts create mode 100644 src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index ad5edab63f..b9f9ff955a 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -1,9 +1,12 @@ +import { ActionDetail } from "@material/mwc-list"; +import { mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { canShowPage } from "../../../common/config/can_show_page"; import "../../../components/ha-card"; import "../../../components/ha-navigation-list"; import { CloudStatus } from "../../../data/cloud"; +import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; @@ -38,6 +41,22 @@ class HaConfigSystemNavigation extends LitElement { back-path="/config" .header=${this.hass.localize("ui.panel.config.dashboard.system.main")} > + + + + ${this.hass.localize( + "ui.panel.config.system_dashboard.restart_homeassistant" + )} + + ) { + switch (ev.detail.index) { + case 0: + showConfirmationDialog(this, { + text: this.hass.localize( + "ui.panel.config.system_dashboard.confirm_restart" + ), + confirm: () => { + this.hass.callService("homeassistant", "restart"); + }, + }); + break; + } + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 360d7b8ea7..5c7b11a2fc 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -20,7 +20,6 @@ import { mdiPuzzle, mdiRobot, mdiScriptText, - mdiServer, mdiShape, mdiSofa, mdiTools, @@ -255,14 +254,6 @@ export const configSections: { [name: string]: PageNavigation[] } = { }, ], general: [ - { - component: "server_control", - path: "/config/server_control", - translationKey: "ui.panel.config.server_control.caption", - iconPath: mdiServer, - iconColor: "#4A5963", - core: true, - }, { path: "/config/updates", translationKey: "ui.panel.config.updates.caption", @@ -445,10 +436,6 @@ class HaPanelConfig extends HassRouterPage { tag: "ha-config-helpers", load: () => import("./helpers/ha-config-helpers"), }, - server_control: { - tag: "ha-config-server-control", - load: () => import("./server_control/ha-config-server-control"), - }, storage: { tag: "ha-config-section-storage", load: () => import("./storage/ha-config-section-storage"), diff --git a/src/panels/config/server_control/ha-config-server-control.ts b/src/panels/config/server_control/ha-config-server-control.ts deleted file mode 100644 index 9bf4a8a728..0000000000 --- a/src/panels/config/server_control/ha-config-server-control.ts +++ /dev/null @@ -1,269 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/app-layout/app-header/app-header"; -import "@polymer/app-layout/app-toolbar/app-toolbar"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { componentsWithService } from "../../../common/config/components_with_service"; -import "../../../components/buttons/ha-call-service-button"; -import "../../../components/ha-card"; -import { checkCoreConfig } from "../../../data/core"; -import { domainToName } from "../../../data/integration"; -import "../../../layouts/hass-subpage"; -import "../../../layouts/hass-tabs-subpage"; -import { haStyle } from "../../../resources/styles"; -import { HomeAssistant, Route } from "../../../types"; -import "../ha-config-section"; - -@customElement("ha-config-server-control") -export class HaConfigServerControl extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ type: Boolean }) public isWide!: boolean; - - @property({ type: Boolean }) public narrow!: boolean; - - @property({ attribute: false }) public route!: Route; - - @property({ type: Boolean }) public showAdvanced!: boolean; - - @state() private _validating = false; - - @state() private _reloadableDomains: string[] = []; - - private _validateLog = ""; - - private _isValid: boolean | null = null; - - protected updated(changedProperties) { - const oldHass = changedProperties.get("hass"); - if ( - changedProperties.has("hass") && - (!oldHass || oldHass.config.components !== this.hass.config.components) - ) { - this._reloadableDomains = componentsWithService( - this.hass, - "reload" - ).sort(); - } - } - - protected render(): TemplateResult { - return html` - -
    - ${this.showAdvanced - ? html` - -
    - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.introduction" - )} - ${!this._validateLog - ? html` -
    - ${!this._validating - ? html` - ${this._isValid - ? html`
    - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.valid" - )} -
    ` - : ""} - - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.check_config" - )} - - ` - : html` - - `} -
    - ` - : html` -
    - - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.invalid" - )} - - - ${this.hass.localize( - "ui.panel.config.server_control.section.validation.check_config" - )} - -
    -
    - ${this._validateLog} -
    - `} -
    -
    - ` - : ""} - - -
    - ${this.hass.localize( - "ui.panel.config.server_control.section.server_management.introduction" - )} -
    -
    - ${this.hass.localize( - "ui.panel.config.server_control.section.server_management.restart" - )} - -
    -
    - - ${this.showAdvanced - ? html` - -
    - ${this.hass.localize( - "ui.panel.config.server_control.section.reloading.introduction" - )} -
    -
    - ${this.hass.localize( - "ui.panel.config.server_control.section.reloading.core" - )} - -
    - ${this._reloadableDomains.map( - (domain) => - html` -
    - ${this.hass.localize( - `ui.panel.config.server_control.section.reloading.${domain}` - ) || - this.hass.localize( - "ui.panel.config.server_control.section.reloading.reload", - "domain", - domainToName(this.hass.localize, domain) - )} - -
    - ` - )} -
    - ` - : ""} -
    -
    - `; - } - - private async _validateConfig() { - this._validating = true; - this._validateLog = ""; - this._isValid = null; - - const configCheck = await checkCoreConfig(this.hass); - this._validating = false; - this._isValid = configCheck.result === "valid"; - - if (configCheck.errors) { - this._validateLog = configCheck.errors; - } - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - .validate-container { - height: 140px; - } - - .validate-result { - color: var(--success-color); - font-weight: 500; - margin-bottom: 1em; - } - - .config-invalid { - margin: 1em 0; - } - - .config-invalid .text { - color: var(--error-color); - font-weight: 500; - } - - .config-invalid mwc-button { - float: right; - } - - .validate-log { - white-space: pre-line; - direction: ltr; - } - - .content { - padding: 28px 20px 0; - max-width: 1040px; - margin: 0 auto; - } - - ha-card { - margin-top: 24px; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-config-server-control": HaConfigServerControl; - } -} diff --git a/src/panels/developer-tools/developer-tools-router.ts b/src/panels/developer-tools/developer-tools-router.ts index 308a1cc348..7fc389ae84 100644 --- a/src/panels/developer-tools/developer-tools-router.ts +++ b/src/panels/developer-tools/developer-tools-router.ts @@ -41,6 +41,10 @@ class DeveloperToolsRouter extends HassRouterPage { tag: "developer-tools-statistics", load: () => import("./statistics/developer-tools-statistics"), }, + yaml: { + tag: "developer-yaml-config", + load: () => import("./yaml_configuration/developer-yaml-config"), + }, }, }; diff --git a/src/panels/developer-tools/ha-panel-developer-tools.ts b/src/panels/developer-tools/ha-panel-developer-tools.ts index a3c1c7a897..cc8e80500c 100644 --- a/src/panels/developer-tools/ha-panel-developer-tools.ts +++ b/src/panels/developer-tools/ha-panel-developer-tools.ts @@ -67,6 +67,9 @@ class PanelDeveloperTools extends LitElement { "ui.panel.developer-tools.tabs.statistics.title" )} + + ${this.hass.localize("ui.panel.developer-tools.tabs.yaml.title")} + + +
    + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.validation.introduction" + )} + ${!this._validateLog + ? html` +
    + ${!this._validating + ? html` + ${this._isValid + ? html`
    + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.validation.valid" + )} +
    ` + : ""} + ` + : html` + + `} +
    + ` + : html` +
    + + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.validation.invalid" + )} + + + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.validation.check_config" + )} + +
    +
    + ${this._validateLog} +
    + `} +
    +
    + + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.validation.check_config" + )} + + + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.server_management.restart" + )} + +
    +
    + +
    + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.reloading.introduction" + )} +
    +
    + ${this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.reloading.core" + )} + +
    + ${this._reloadableDomains.map( + (domain) => + html` +
    + ${this.hass.localize( + `ui.panel.developer-tools.tabs.yaml.section.reloading.${domain}` + ) || + this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.reloading.reload", + "domain", + domainToName(this.hass.localize, domain) + )} + +
    + ` + )} +
    +
    + `; + } + + private async _validateConfig() { + this._validating = true; + this._validateLog = ""; + this._isValid = null; + + const configCheck = await checkCoreConfig(this.hass); + this._validating = false; + this._isValid = configCheck.result === "valid"; + + if (configCheck.errors) { + this._validateLog = configCheck.errors; + } + } + + private _restart() { + showConfirmationDialog(this, { + text: this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart" + ), + confirmText: this.hass!.localize("ui.common.leave"), + dismissText: this.hass!.localize("ui.common.stay"), + confirm: () => { + this.hass.callService("homeassistant", "restart"); + }, + }); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + .validate-container { + height: 140px; + } + + .validate-result { + color: var(--success-color); + font-weight: 500; + margin-bottom: 1em; + } + + .config-invalid { + margin: 1em 0; + } + + .config-invalid .text { + color: var(--error-color); + font-weight: 500; + } + + .validate-log { + white-space: pre-line; + direction: ltr; + } + + .content { + padding: 28px 20px 0; + max-width: 1040px; + margin: 0 auto; + } + + ha-card { + margin-top: 24px; + } + + .card-actions { + display: flex; + justify-content: space-between; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "developer-yaml-config": DeveloperYamlConfig; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 412bbd4272..fcc5dab6be 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -625,43 +625,43 @@ "quick-bar": { "commands": { "reload": { - "reload": "[%key:ui::panel::config::server_control::section::reloading::reload%]", - "core": "[%key:ui::panel::config::server_control::section::reloading::core%]", - "group": "[%key:ui::panel::config::server_control::section::reloading::group%]", - "automation": "[%key:ui::panel::config::server_control::section::reloading::automation%]", - "script": "[%key:ui::panel::config::server_control::section::reloading::script%]", - "scene": "[%key:ui::panel::config::server_control::section::reloading::scene%]", - "person": "[%key:ui::panel::config::server_control::section::reloading::person%]", - "zone": "[%key:ui::panel::config::server_control::section::reloading::zone%]", - "input_boolean": "[%key:ui::panel::config::server_control::section::reloading::input_boolean%]", - "input_text": "[%key:ui::panel::config::server_control::section::reloading::input_text%]", - "input_number": "[%key:ui::panel::config::server_control::section::reloading::input_number%]", - "input_datetime": "[%key:ui::panel::config::server_control::section::reloading::input_datetime%]", - "input_select": "[%key:ui::panel::config::server_control::section::reloading::input_select%]", - "template": "[%key:ui::panel::config::server_control::section::reloading::template%]", - "universal": "[%key:ui::panel::config::server_control::section::reloading::universal%]", - "rest": "[%key:ui::panel::config::server_control::section::reloading::rest%]", - "command_line": "[%key:ui::panel::config::server_control::section::reloading::command_line%]", - "filter": "[%key:ui::panel::config::server_control::section::reloading::filter%]", - "statistics": "[%key:ui::panel::config::server_control::section::reloading::statistics%]", - "generic": "[%key:ui::panel::config::server_control::section::reloading::generic%]", - "generic_thermostat": "[%key:ui::panel::config::server_control::section::reloading::generic_thermostat%]", - "homekit": "[%key:ui::panel::config::server_control::section::reloading::homekit%]", - "min_max": "[%key:ui::panel::config::server_control::section::reloading::min_max%]", - "history_stats": "[%key:ui::panel::config::server_control::section::reloading::history_stats%]", - "trend": "[%key:ui::panel::config::server_control::section::reloading::trend%]", - "ping": "[%key:ui::panel::config::server_control::section::reloading::ping%]", - "filesize": "[%key:ui::panel::config::server_control::section::reloading::filesize%]", - "telegram": "[%key:ui::panel::config::server_control::section::reloading::telegram%]", - "smtp": "[%key:ui::panel::config::server_control::section::reloading::smtp%]", - "mqtt": "[%key:ui::panel::config::server_control::section::reloading::mqtt%]", - "rpi_gpio": "[%key:ui::panel::config::server_control::section::reloading::rpi_gpio%]", - "themes": "[%key:ui::panel::config::server_control::section::reloading::themes%]" + "reload": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::reload%]", + "core": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::core%]", + "group": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::group%]", + "automation": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::automation%]", + "script": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::script%]", + "scene": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::scene%]", + "person": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::person%]", + "zone": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::zone%]", + "input_boolean": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::input_boolean%]", + "input_text": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::input_text%]", + "input_number": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::input_number%]", + "input_datetime": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::input_datetime%]", + "input_select": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::input_select%]", + "template": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::template%]", + "universal": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::universal%]", + "rest": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::rest%]", + "command_line": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::command_line%]", + "filter": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::filter%]", + "statistics": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::statistics%]", + "generic": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::generic%]", + "generic_thermostat": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::generic_thermostat%]", + "homekit": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::homekit%]", + "min_max": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::min_max%]", + "history_stats": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::history_stats%]", + "trend": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::trend%]", + "ping": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::ping%]", + "filesize": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::filesize%]", + "telegram": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::telegram%]", + "smtp": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::smtp%]", + "mqtt": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::mqtt%]", + "rpi_gpio": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::rpi_gpio%]", + "themes": "[%key:ui::panel::developer-tools::tabs::yaml::section::reloading::themes%]" }, "server_control": { "perform_action": "{action} server", - "restart": "[%key:ui::panel::config::server_control::section::server_management::restart%]", - "stop": "[%key:ui::panel::config::server_control::section::server_management::stop%]" + "restart": "[%key:ui::panel::developer-tools::tabs::yaml::section::server_management::restart%]", + "stop": "[%key:ui::panel::developer-tools::tabs::yaml::section::server_management::stop%]" }, "types": { "reload": "Reload", @@ -687,7 +687,7 @@ "users": "[%key:ui::panel::config::users::caption%]", "info": "[%key:ui::panel::config::info::caption%]", "blueprint": "[%key:ui::panel::config::blueprint::caption%]", - "server_control": "[%key:ui::panel::config::server_control::caption%]" + "server_control": "[%key:ui::panel::developer-tools::tabs::yaml::title%]" } }, "filter_placeholder": "Entity Filter", @@ -1667,65 +1667,6 @@ } } }, - "server_control": { - "caption": "Server Controls", - "description": "Validate and restart the Home Assistant server", - "section": { - "validation": { - "heading": "Configuration validation", - "introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid.", - "check_config": "Check configuration", - "valid": "Configuration valid!", - "invalid": "Configuration invalid" - }, - "reloading": { - "heading": "YAML configuration reloading", - "introduction": "Some parts of Home Assistant can reload without requiring a restart. Clicking one of the options below will unload their current YAML configuration and load the new one.", - "reload": "{domain}", - "core": "Location & customizations", - "group": "Groups, group entities, and notify services", - "automation": "Automations", - "script": "Scripts", - "scene": "Scenes", - "person": "People", - "zone": "Zones", - "input_boolean": "Input booleans", - "input_button": "Input buttons", - "input_text": "Input texts", - "input_number": "Input numbers", - "input_datetime": "Input date times", - "input_select": "Input selects", - "template": "Template entities", - "universal": "Universal media player entities", - "rest": "Rest entities and notify services", - "command_line": "Command line entities", - "filter": "Filter entities", - "statistics": "Statistics entities", - "generic": "Generic IP camera entities", - "generic_thermostat": "Generic thermostat entities", - "homekit": "HomeKit", - "min_max": "Min/max entities", - "history_stats": "History stats entities", - "trend": "Trend entities", - "ping": "Ping binary sensor entities", - "filesize": "File size entities", - "telegram": "Telegram notify services", - "smtp": "SMTP notify services", - "mqtt": "Manually configured MQTT entities", - "rpi_gpio": "Raspberry Pi GPIO entities", - "timer": "Timers", - "themes": "Themes" - }, - "server_management": { - "heading": "Home Assistant", - "introduction": "Restarting Home Assistant will stop your dashboard and automations. After the reboot, each configuration will be reloaded.", - "restart": "Restart", - "confirm_restart": "Are you sure you want to restart Home Assistant?", - "stop": "Stop", - "confirm_stop": "Are you sure you want to stop Home Assistant?" - } - } - }, "automation": { "caption": "Automations", "description": "Create custom behavior rules for your home", @@ -3221,6 +3162,10 @@ "ram_usage": "Memory Usage", "core_stats": "Core Stats", "supervisor_stats": "Supervisor Stats" + }, + "system_dashboard": { + "confirm_restart": "Are you sure you want to restart Home Assistant?", + "restart_homeassistant": "Restart Home Assistant" } }, "lovelace": { @@ -4200,6 +4145,65 @@ } }, "adjust_sum": "Adjust sum" + }, + "yaml": { + "title": "YAML Configuration", + "section": { + "validation": { + "heading": "Configuration validation", + "introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid.", + "check_config": "Check configuration", + "valid": "Configuration valid!", + "invalid": "Configuration invalid" + }, + "reloading": { + "heading": "YAML configuration reloading", + "introduction": "Some parts of Home Assistant can reload without requiring a restart. Clicking one of the options below will unload their current YAML configuration and load the new one.", + "reload": "{domain}", + "core": "Location & customizations", + "group": "Groups, group entities, and notify services", + "automation": "Automations", + "script": "Scripts", + "scene": "Scenes", + "person": "People", + "zone": "Zones", + "input_boolean": "Input booleans", + "input_button": "Input buttons", + "input_text": "Input texts", + "input_number": "Input numbers", + "input_datetime": "Input date times", + "input_select": "Input selects", + "template": "Template entities", + "universal": "Universal media player entities", + "rest": "Rest entities and notify services", + "command_line": "Command line entities", + "filter": "Filter entities", + "statistics": "Statistics entities", + "generic": "Generic IP camera entities", + "generic_thermostat": "Generic thermostat entities", + "homekit": "HomeKit", + "min_max": "Min/max entities", + "history_stats": "History stats entities", + "trend": "Trend entities", + "ping": "Ping binary sensor entities", + "filesize": "File size entities", + "telegram": "Telegram notify services", + "smtp": "SMTP notify services", + "mqtt": "Manually configured MQTT entities", + "rpi_gpio": "Raspberry Pi GPIO entities", + "timer": "Timers", + "themes": "Themes" + }, + "server_management": { + "heading": "Home Assistant", + "introduction": "Restarting Home Assistant will stop your dashboard and automations. After the reboot, each configuration will be reloaded.", + "restart": "Restart", + "restart_home_assistant": "Restart Home Assistant", + "confirm_restart": "Are you sure you want to restart Home Assistant?", + "stop": "Stop", + "confirm_stop": "Are you sure you want to stop Home Assistant?" + } + } } } }, From a2c0c0474a5dcae108fa3e49e10748254849a790 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 26 Apr 2022 22:13:16 -0700 Subject: [PATCH 113/181] Bumped version to 20220427.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ecacd56aa2..f1f72e8597 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220425.0 +version = 20220427.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 2cc6432a0f03ae79a10360a5b9c0e2ff8a8244ef Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Apr 2022 13:37:50 +0200 Subject: [PATCH 114/181] Use correct label for update config menu (#12465) --- src/panels/config/core/ha-config-section-updates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/core/ha-config-section-updates.ts b/src/panels/config/core/ha-config-section-updates.ts index f5cc96ecae..372dfbe663 100644 --- a/src/panels/config/core/ha-config-section-updates.ts +++ b/src/panels/config/core/ha-config-section-updates.ts @@ -75,7 +75,7 @@ class HaConfigSectionUpdates extends LitElement { From 66adecdfc95d6da870045fe49bd6c44192de643f Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Apr 2022 15:07:57 +0200 Subject: [PATCH 115/181] Make helper option button more user friendly (#12468) --- .../entities/entity-registry-settings.ts | 33 +++++++++++-------- .../helpers/forms/ha-input_select-form.ts | 12 +++++-- src/translations/en.json | 2 +- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index 69a4b5e7b2..0c6ff704df 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -356,6 +356,25 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { )} ` : ""} + ${this._helperConfigEntry + ? html` +
    + + ${this.hass.localize( + "ui.dialogs.entity_registry.editor.configure_state", + "integration", + domainToName( + this.hass.localize, + this._helperConfigEntry.domain + ) + )} + +
    + ` + : ""} ` : ""} - ${this._helperConfigEntry - ? html` -
    - - ${this.hass.localize( - "ui.dialogs.entity_registry.editor.configure_state" - )} - -
    - ` - : ""} - ${this.hass!.localize( - "ui.dialogs.helper_settings.input_select.options" - )}: +
    + ${this.hass!.localize( + "ui.dialogs.helper_settings.input_select.options" + )}: +
    ${this._options.length ? this._options.map( (option, index) => html` @@ -206,6 +208,10 @@ class HaInputSelectForm extends LitElement { #option_input { margin-top: 8px; } + .header { + margin-top: 8px; + margin-bottom: 8px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index fcc5dab6be..af3bb998da 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -858,7 +858,7 @@ "area_note": "By default the entities of a device are in the same area as the device. If you change the area of this entity, it will no longer follow the area of the device.", "follow_device_area": "Follow device area", "change_device_area": "Change device area", - "configure_state": "Configure State" + "configure_state": "{integration} options" } }, "helper_settings": { From ebc807a6a42699da8d232fe867db948ce5649952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 27 Apr 2022 15:08:45 +0200 Subject: [PATCH 116/181] Add hass-quick-bar-trigger event to trigger quickbar from supervisor (#12467) --- hassio/src/hassio-main.ts | 10 +++++++++- src/state/quick-bar-mixin.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index 53df932748..d4a3a450e6 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -3,8 +3,8 @@ import { customElement, property } from "lit/decorators"; import { atLeastVersion } from "../../src/common/config/version"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { fireEvent } from "../../src/common/dom/fire_event"; -import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; import { mainWindow } from "../../src/common/dom/get_main_window"; +import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; import { navigate } from "../../src/common/navigate"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor"; @@ -73,6 +73,14 @@ export class HassioMain extends SupervisorBaseElement { }); }); + // Forward keydown events to the main window for quickbar access + document.body.addEventListener("keydown", (ev) => { + // @ts-ignore + fireEvent(mainWindow, "hass-quick-bar-trigger", ev, { + bubbles: false, + }); + }); + makeDialogManager(this, this.shadowRoot!); } diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index d9067a48da..ec2738b78b 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -14,6 +14,7 @@ import { HassElement } from "./hass-element"; declare global { interface HASSDomEvents { "hass-quick-bar": QuickBarParams; + "hass-quick-bar-trigger": KeyboardEvent; "hass-enable-shortcuts": HomeAssistant["enableShortcuts"]; } } @@ -28,6 +29,20 @@ export default >(superClass: T) => storeState(this.hass!); }); + mainWindow.addEventListener("hass-quick-bar-trigger", (ev) => { + switch (ev.detail.key) { + case "e": + this._showQuickBar(ev.detail); + break; + case "c": + this._showQuickBar(ev.detail, true); + break; + case "m": + this._createMyLink(ev.detail); + break; + } + }); + this._registerShortcut(); } From 307cd5ad8c4658a4ac1a68edb26122915a2b0630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 27 Apr 2022 15:10:38 +0200 Subject: [PATCH 117/181] Use startsWith for m shortcut for partial match (#12464) --- src/panels/my/ha-panel-my.ts | 14 +++++++------- src/state/quick-bar-mixin.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index ab5ae0cda9..733c44e606 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -41,15 +41,15 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ component: "cloud", redirect: "/config/cloud", }, - integrations: { - redirect: "/config/integrations", - }, config_flow_start: { redirect: "/config/integrations/add", params: { domain: "string", }, }, + integrations: { + redirect: "/config/integrations", + }, config_mqtt: { component: "mqtt", redirect: "/config/mqtt", @@ -79,10 +79,6 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ areas: { redirect: "/config/areas/dashboard", }, - blueprints: { - component: "blueprint", - redirect: "/config/blueprint/dashboard", - }, blueprint_import: { component: "blueprint", redirect: "/config/blueprint/dashboard/import", @@ -90,6 +86,10 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ blueprint_url: "url", }, }, + blueprints: { + component: "blueprint", + redirect: "/config/blueprint/dashboard", + }, automations: { component: "automation", redirect: "/config/automation/dashboard", diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index ec2738b78b..72b4902edf 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -73,7 +73,7 @@ export default >(superClass: T) => for (const [slug, redirect] of Object.entries( myPanel.getMyRedirects(isComponentLoaded(this.hass, "hassio")) )) { - if (redirect.redirect === targetPath) { + if (targetPath.startsWith(redirect.redirect)) { window.open( `https://my.home-assistant.io/create-link/?redirect=${slug}`, "_blank" From 4b644d8bc53d5df11e770bbaf9aa8fb7edd6fcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 27 Apr 2022 15:36:47 +0200 Subject: [PATCH 118/181] Add supervisor redirects to m keyboard shortcut (#12466) --- hassio/src/hassio-my-redirect.ts | 2 +- src/state/quick-bar-mixin.ts | 35 ++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/hassio/src/hassio-my-redirect.ts b/hassio/src/hassio-my-redirect.ts index 777672870a..fc0e1c0808 100644 --- a/hassio/src/hassio-my-redirect.ts +++ b/hassio/src/hassio-my-redirect.ts @@ -15,7 +15,7 @@ import { } from "../../src/panels/my/ha-panel-my"; import { HomeAssistant, Route } from "../../src/types"; -const REDIRECTS: Redirects = { +export const REDIRECTS: Redirects = { supervisor: { redirect: "/hassio/dashboard", }, diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index 72b4902edf..532ffbe49e 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -63,22 +63,49 @@ export default >(superClass: T) => } private async _createMyLink(e: KeyboardEvent) { - if (!this._canOverrideAlphanumericInput(e) || !this.hass) { + if ( + !this.hass?.enableShortcuts || + !this._canOverrideAlphanumericInput(e) + ) { return; } const targetPath = mainWindow.location.pathname; + const isHassio = isComponentLoaded(this.hass, "hassio"); + const myParams = new URLSearchParams(); + + if (isHassio && targetPath.startsWith("/hassio")) { + const myPanelSupervisor = await import( + "../../hassio/src/hassio-my-redirect" + ); + for (const [slug, redirect] of Object.entries( + myPanelSupervisor.REDIRECTS + )) { + if (targetPath.startsWith(redirect.redirect)) { + myParams.append("redirect", slug); + if (redirect.redirect === "/hassio/addon") { + myParams.append("addon", targetPath.split("/")[3]); + } + window.open( + `https://my.home-assistant.io/create-link/?${myParams.toString()}`, + "_blank" + ); + return; + } + } + } + const myPanel = await import("../panels/my/ha-panel-my"); for (const [slug, redirect] of Object.entries( - myPanel.getMyRedirects(isComponentLoaded(this.hass, "hassio")) + myPanel.getMyRedirects(isHassio) )) { if (targetPath.startsWith(redirect.redirect)) { + myParams.append("redirect", slug); window.open( - `https://my.home-assistant.io/create-link/?redirect=${slug}`, + `https://my.home-assistant.io/create-link/?${myParams.toString()}`, "_blank" ); - return; } } From 4cd8b76d7eb4fce170d3191a6389833ca643f249 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Apr 2022 19:25:13 +0200 Subject: [PATCH 119/181] Safeguard against non-existant area in device handling (#12475) --- src/components/device/ha-device-picker.ts | 7 ++++--- src/panels/config/devices/ha-config-devices-dashboard.ts | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index 9ff64d812f..e2abdac836 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -198,9 +198,10 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { this.hass, deviceEntityLookup[device.id] ), - area: device.area_id - ? areaLookup[device.area_id].name - : this.hass.localize("ui.components.device-picker.no_area"), + area: + device.area_id && areaLookup[device.area_id] + ? areaLookup[device.area_id].name + : this.hass.localize("ui.components.device-picker.no_area"), })); if (!outputDevices.length) { return [ diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index a2e23079bf..d7027a9512 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -197,7 +197,10 @@ export class HaConfigDeviceDashboard extends LitElement { ), model: device.model || "", manufacturer: device.manufacturer || "", - area: device.area_id ? areaLookup[device.area_id].name : "—", + area: + device.area_id && areaLookup[device.area_id] + ? areaLookup[device.area_id].name + : "—", integration: device.config_entries.length ? device.config_entries .filter((entId) => entId in entryLookup) From 3149ffbf19d08c550cbba99c8ad84ea2ab2e6208 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Wed, 27 Apr 2022 20:26:19 +0300 Subject: [PATCH 120/181] RTL fix for log buttons (#12474) --- src/panels/config/logs/error-log-card.ts | 7 ++++++- src/panels/config/logs/system-log-card.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/panels/config/logs/error-log-card.ts b/src/panels/config/logs/error-log-card.ts index e1818d6e7f..6963afb744 100644 --- a/src/panels/config/logs/error-log-card.ts +++ b/src/panels/config/logs/error-log-card.ts @@ -16,6 +16,7 @@ import "../../../components/ha-ansi-to-html"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; import "../../../components/ha-select"; +import { computeRTLDirection } from "../../../common/util/compute_rtl"; import { fetchErrorLog } from "../../../data/error_log"; import { extractApiErrorMessage } from "../../../data/hassio/common"; import { fetchHassioLogs } from "../../../data/hassio/supervisor"; @@ -62,7 +63,11 @@ class ErrorLogCard extends LitElement { : ""} ${!this._logHTML ? html` - + ${this.hass.localize("ui.panel.config.logs.load_logs")} ` diff --git a/src/panels/config/logs/system-log-card.ts b/src/panels/config/logs/system-log-card.ts index 6c1c6715c0..f774a657c3 100644 --- a/src/panels/config/logs/system-log-card.ts +++ b/src/panels/config/logs/system-log-card.ts @@ -18,6 +18,7 @@ import { import { HomeAssistant } from "../../../types"; import { showSystemLogDetailDialog } from "./show-dialog-system-log-detail"; import { formatSystemLogTime } from "./util"; +import { computeRTLDirection } from "../../../common/util/compute_rtl"; @customElement("system-log-card") export class SystemLogCard extends LitElement { @@ -131,7 +132,7 @@ export class SystemLogCard extends LitElement { ` )} -
    +
    Date: Wed, 27 Apr 2022 13:57:57 -0500 Subject: [PATCH 121/181] Fix YAML Config Invalid button (#12476) --- .../yaml_configuration/developer-yaml-config.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts index cd9bec5416..d6ffc0ef0a 100644 --- a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts +++ b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts @@ -82,11 +82,6 @@ export class DeveloperYamlConfig extends LitElement { "ui.panel.developer-tools.tabs.yaml.section.validation.invalid" )} - - ${this.hass.localize( - "ui.panel.developer-tools.tabs.yaml.section.validation.check_config" - )} -
    ${this._validateLog} @@ -94,10 +89,7 @@ export class DeveloperYamlConfig extends LitElement { `}
    - + ${this.hass.localize( "ui.panel.developer-tools.tabs.yaml.section.validation.check_config" )} From 6822f0d067c7458808dc4988726c89152f8f6257 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 27 Apr 2022 14:22:57 -0500 Subject: [PATCH 122/181] Small config fixes (#12472) --- src/layouts/hass-subpage.ts | 1 + .../config/core/ha-config-section-general.ts | 86 +++++++++++++++---- .../core/ha-config-system-navigation.ts | 7 +- 3 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/layouts/hass-subpage.ts b/src/layouts/hass-subpage.ts index ca25c6669b..d8658966e5 100644 --- a/src/layouts/hass-subpage.ts +++ b/src/layouts/hass-subpage.ts @@ -99,6 +99,7 @@ class HassSubpage extends LitElement { ha-icon-button-arrow-prev, ::slotted([slot="toolbar-icon"]) { pointer-events: auto; + color: var(--sidebar-icon-color); } .main-title { diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts index 97f3552607..f19c20d5b7 100644 --- a/src/panels/config/core/ha-config-section-general.ts +++ b/src/panels/config/core/ha-config-section-general.ts @@ -1,15 +1,23 @@ +import "@material/mwc-list/mwc-list-item"; import timezones from "google-timezones-json"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { UNIT_C } from "../../../common/const"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { navigate } from "../../../common/navigate"; -import { HaProgressButton } from "../../../components/buttons/ha-progress-button"; +import "../../../components/buttons/ha-progress-button"; +import type { HaProgressButton } from "../../../components/buttons/ha-progress-button"; import { currencies } from "../../../components/currency-datalist"; +import "../../../components/ha-card"; import "../../../components/ha-formfield"; import "../../../components/ha-radio"; import type { HaRadio } from "../../../components/ha-radio"; +import "../../../components/ha-select"; import "../../../components/ha-settings-row"; +import "../../../components/ha-textfield"; +import "../../../components/map/ha-locations-editor"; +import type { MarkerLocation } from "../../../components/map/ha-locations-editor"; import { ConfigUpdateValues, saveCoreConfig } from "../../../data/core"; import { SYMBOL_TO_ISO } from "../../../data/currency"; import "../../../layouts/hass-subpage"; @@ -34,6 +42,8 @@ class HaConfigSectionGeneral extends LitElement { @state() private _timeZone?: string; + @state() private _location?: [number, number]; + protected render(): TemplateResult { const canEdit = ["storage", "default"].includes( this.hass.config.config_source @@ -47,7 +57,7 @@ class HaConfigSectionGeneral extends LitElement { .header=${this.hass.localize("ui.panel.config.core.caption")} >
    - +
    ${!canEdit ? html` @@ -183,21 +193,35 @@ class HaConfigSectionGeneral extends LitElement { >
    - -
    - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.edit_location" - )} -
    -
    - ${this.hass.localize( - "ui.panel.config.core.section.core.core_config.edit_location_description" - )} -
    - ${this.hass.localize("ui.common.edit")} -
    + ${this.narrow + ? html` + + ` + : html` + +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.edit_location" + )} +
    +
    + ${this.hass.localize( + "ui.panel.config.core.section.core.core_config.edit_location_description" + )} +
    + ${this.hass.localize("ui.common.edit")} +
    + `}
    ${this.hass!.localize("ui.panel.config.zone.detail.update")} @@ -237,7 +261,11 @@ class HaConfigSectionGeneral extends LitElement { this._unitSystem = (ev.target as HaRadio).value as "metric" | "imperial"; } - private async _updateEntry(ev) { + private _locationChanged(ev: CustomEvent) { + this._location = ev.detail.location; + } + + private async _updateEntry(ev: CustomEvent) { const button = ev.target as HaProgressButton; if (button.progress) { return; @@ -261,6 +289,21 @@ class HaConfigSectionGeneral extends LitElement { } } + private _markerLocation = memoizeOne( + ( + lat: number, + lng: number, + location?: [number, number] + ): MarkerLocation[] => [ + { + id: "location", + latitude: location ? location[0] : lat, + longitude: location ? location[1] : lng, + location_editable: true, + }, + ] + ); + private _editLocation() { navigate("/config/zone"); } @@ -274,7 +317,7 @@ class HaConfigSectionGeneral extends LitElement { margin: 0 auto; } ha-card { - max-width: 500px; + max-width: 600px; margin: 0 auto; height: 100%; justify-content: space-between; @@ -302,6 +345,11 @@ class HaConfigSectionGeneral extends LitElement { ha-select { display: block; } + ha-locations-editor { + display: block; + height: 400px; + padding: 16px; + } `, ]; } diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index b9f9ff955a..e1589eb1fe 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -43,8 +43,8 @@ class HaConfigSystemNavigation extends LitElement { > - ${this.narrow - ? html`
    - ${this.hass.localize("ui.panel.config.dashboard.system.main")} -
    ` - : ""} Date: Wed, 27 Apr 2022 21:57:41 +0200 Subject: [PATCH 123/181] Visual tweaks to YAML validation results (#12479) --- .../yaml_configuration/developer-yaml-config.ts | 4 ++-- src/translations/en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts index d6ffc0ef0a..8d5bfe47a1 100644 --- a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts +++ b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts @@ -183,17 +183,17 @@ export class DeveloperYamlConfig extends LitElement { haStyle, css` .validate-container { - height: 140px; + height: 60px; } .validate-result { color: var(--success-color); font-weight: 500; - margin-bottom: 1em; } .config-invalid { margin: 1em 0; + text-align: center; } .config-invalid .text { diff --git a/src/translations/en.json b/src/translations/en.json index af3bb998da..1479dbf2d8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4151,10 +4151,10 @@ "section": { "validation": { "heading": "Configuration validation", - "introduction": "Validate your configuration if you recently made some changes to your configuration and want to make sure that it is all valid.", + "introduction": "Validate your configuration if you recently made some changes to it and want to make sure that it is all valid.", "check_config": "Check configuration", "valid": "Configuration valid!", - "invalid": "Configuration invalid" + "invalid": "Configuration invalid!" }, "reloading": { "heading": "YAML configuration reloading", From 2751f8f33bcb9ff075d4e42f5fba27d843aade1f Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Apr 2022 22:18:25 +0200 Subject: [PATCH 124/181] Add some bottom padding to YAML conf dev tools page (#12477) Co-authored-by: Zack Barett --- .../developer-tools/yaml_configuration/developer-yaml-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts index 8d5bfe47a1..e975cfc27d 100644 --- a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts +++ b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts @@ -207,7 +207,7 @@ export class DeveloperYamlConfig extends LitElement { } .content { - padding: 28px 20px 0; + padding: 28px 20px 16px; max-width: 1040px; margin: 0 auto; } From e4f91195d81cfef01e99c37e3676129f089ae49c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 27 Apr 2022 17:55:04 -0500 Subject: [PATCH 125/181] Fix Restarting Home Assistant (#12480) * Fix Restarting Home ASsistant * Update src/panels/config/core/ha-config-system-navigation.ts Co-authored-by: Paulus Schoutsen * Update src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts Co-authored-by: Paulus Schoutsen * reviews Co-authored-by: Paulus Schoutsen --- .../config/core/ha-config-system-navigation.ts | 16 ++++++++++++++-- .../yaml_configuration/developer-yaml-config.ts | 11 ++++++----- src/translations/en.json | 6 ++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index e1589eb1fe..548cc51e6c 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -6,7 +6,10 @@ import { canShowPage } from "../../../common/config/can_show_page"; import "../../../components/ha-card"; import "../../../components/ha-navigation-list"; import { CloudStatus } from "../../../data/cloud"; -import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; +import { + showAlertDialog, + showConfirmationDialog, +} from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; @@ -82,7 +85,16 @@ class HaConfigSystemNavigation extends LitElement { "ui.panel.config.system_dashboard.confirm_restart" ), confirm: () => { - this.hass.callService("homeassistant", "restart"); + this.hass + .callService("homeassistant", "restart") + .catch((reason) => { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.system_dashboard.restart_error" + ), + text: reason.message, + }); + }); }, }); break; diff --git a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts index e975cfc27d..57ca442c49 100644 --- a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts +++ b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts @@ -27,9 +27,9 @@ export class DeveloperYamlConfig extends LitElement { @state() private _reloadableDomains: string[] = []; - private _validateLog = ""; + @state() private _isValid: boolean | null = null; - private _isValid: boolean | null = null; + private _validateLog = ""; protected updated(changedProperties) { const oldHass = changedProperties.get("hass"); @@ -170,10 +170,11 @@ export class DeveloperYamlConfig extends LitElement { text: this.hass.localize( "ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart" ), - confirmText: this.hass!.localize("ui.common.leave"), - dismissText: this.hass!.localize("ui.common.stay"), confirm: () => { - this.hass.callService("homeassistant", "restart"); + this.hass.callService("homeassistant", "restart").catch((reason) => { + this._isValid = false; + this._validateLog = reason.message; + }); }, }); } diff --git a/src/translations/en.json b/src/translations/en.json index 1479dbf2d8..4e7957a766 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3165,7 +3165,8 @@ }, "system_dashboard": { "confirm_restart": "Are you sure you want to restart Home Assistant?", - "restart_homeassistant": "Restart Home Assistant" + "restart_homeassistant": "Restart Home Assistant", + "restart_error": "Failed to restart Home Assistant" } }, "lovelace": { @@ -4201,7 +4202,8 @@ "restart_home_assistant": "Restart Home Assistant", "confirm_restart": "Are you sure you want to restart Home Assistant?", "stop": "Stop", - "confirm_stop": "Are you sure you want to stop Home Assistant?" + "confirm_stop": "Are you sure you want to stop Home Assistant?", + "restart_error": "Failed to restart Home Assistant" } } } From 3ccbf6983effcef7d0a1eee0c4cf5151d5e4f50c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Apr 2022 00:08:21 -0500 Subject: [PATCH 126/181] Move General Up in the system menu (#12483) --- src/panels/config/ha-panel-config.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 5c7b11a2fc..e9167234c2 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -254,6 +254,13 @@ export const configSections: { [name: string]: PageNavigation[] } = { }, ], general: [ + { + path: "/config/general", + translationKey: "ui.panel.config.core.caption", + iconPath: mdiCog, + iconColor: "#653249", + core: true, + }, { path: "/config/updates", translationKey: "ui.panel.config.updates.caption", @@ -315,13 +322,6 @@ export const configSections: { [name: string]: PageNavigation[] } = { iconColor: "#507FfE", components: ["system_health", "hassio"], }, - { - path: "/config/general", - translationKey: "ui.panel.config.core.caption", - iconPath: mdiCog, - iconColor: "#653249", - core: true, - }, ], about: [ { From 81356116887b0df0267be25381676085483784eb Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Thu, 28 Apr 2022 08:16:18 +0300 Subject: [PATCH 127/181] Media panel fix (#12485) --- .../media-player/dialog-media-player-browse.ts | 1 + src/panels/media-browser/ha-bar-media-player.ts | 10 ++++++++++ src/panels/media-browser/ha-panel-media-browser.ts | 1 + 3 files changed, 12 insertions(+) diff --git a/src/components/media-player/dialog-media-player-browse.ts b/src/components/media-player/dialog-media-player-browse.ts index 8a4f3f905e..dd77ff8daa 100644 --- a/src/components/media-player/dialog-media-player-browse.ts +++ b/src/components/media-player/dialog-media-player-browse.ts @@ -152,6 +152,7 @@ class DialogMediaPlayerBrowse extends LitElement { ha-media-player-browse { --media-browser-max-height: calc(100vh - 65px); height: calc(100vh - 65px); + direction: ltr; } @media (min-width: 800px) { diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index bfc2e63656..efff417516 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -686,6 +686,16 @@ export class BarMediaPlayer extends LitElement { mwc-list-item[selected] { font-weight: bold; } + + :host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { + margin-left: 8px !important; + margin-right: 8px !important; + } + :host-context([style*="direction: rtl;"]) + ha-svg-icon[slot="trailingIcon"] { + margin-left: 0px !important; + margin-right: 8px !important; + } `; } } diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index cfda5876f9..049fb3d5e3 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -278,6 +278,7 @@ class PanelMediaBrowser extends LitElement { ha-media-player-browse { height: calc(100vh - (100px + var(--header-height))); + direction: ltr; } :host([narrow]) ha-media-player-browse { From 67626d4a06477aa70a578a015ba5c1616ff0f608 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Apr 2022 05:39:35 -0500 Subject: [PATCH 128/181] add my redirects for new config pages (#12481) --- src/panels/my/ha-panel-my.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index 733c44e606..836d3061f5 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -34,6 +34,9 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ developer_statistics: { redirect: "/developer-tools/statistics", }, + server_controls: { + redirect: "/developer-tools/yaml", + }, config: { redirect: "/config/dashboard", }, @@ -129,10 +132,7 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ redirect: "/config/users", }, general: { - redirect: "/config/core", - }, - server_controls: { - redirect: "/developer-tools/yaml", + redirect: "/config/general", }, logs: { redirect: "/config/logs", @@ -140,6 +140,27 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ info: { redirect: "/config/info", }, + system_health: { + redirect: "/config/system_health", + }, + hardware: { + redirect: "/config/hardware", + }, + storage: { + redirect: "/config/storage", + }, + network: { + redirect: "/config/network", + }, + analytics: { + redirect: "/config/analytics", + }, + updates: { + redirect: "/config/updates", + }, + system_dashboard: { + redirect: "/config/system", + }, customize: { // customize was removed in 2021.12, fallback to dashboard redirect: "/config/dashboard", From 2c9411c6c3b557409faf4df6b2398a3850a63807 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Apr 2022 12:40:39 +0200 Subject: [PATCH 129/181] Add template editor to Markdown card editor (#12490) --- .../lovelace/editor/config-elements/hui-markdown-card-editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts index 54dc74bcd7..e22892d392 100644 --- a/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-markdown-card-editor.ts @@ -20,7 +20,7 @@ const cardConfigStruct = assign( const SCHEMA: HaFormSchema[] = [ { name: "title", selector: { text: {} } }, - { name: "content", required: true, selector: { text: { multiline: true } } }, + { name: "content", required: true, selector: { template: {} } }, { name: "theme", selector: { theme: {} } }, ]; From 1617a9dfed767025db9afddbd01ac96cbe2116d7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 28 Apr 2022 15:44:01 +0200 Subject: [PATCH 130/181] Address minor comments about config menu (#12492) --- src/panels/config/core/ha-config-analytics.ts | 4 +++- .../config/core/ha-config-section-general.ts | 5 +++++ .../config/dashboard/ha-config-dashboard.ts | 15 ++++++++++----- src/panels/config/ha-panel-config.ts | 4 ++-- src/panels/config/network/supervisor-hostname.ts | 3 +++ src/translations/en.json | 2 +- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/panels/config/core/ha-config-analytics.ts b/src/panels/config/core/ha-config-analytics.ts index 0fe2a1763c..a9a90b3099 100644 --- a/src/panels/config/core/ha-config-analytics.ts +++ b/src/panels/config/core/ha-config-analytics.ts @@ -110,7 +110,9 @@ class ConfigAnalytics extends LitElement { ha-settings-row { padding: 0; } - + p { + margin-top: 0; + } .card-actions { display: flex; flex-direction: row-reverse; diff --git a/src/panels/config/core/ha-config-section-general.ts b/src/panels/config/core/ha-config-section-general.ts index f19c20d5b7..2f9588e8ad 100644 --- a/src/panels/config/core/ha-config-section-general.ts +++ b/src/panels/config/core/ha-config-section-general.ts @@ -187,6 +187,7 @@ class HaConfigSectionGeneral extends LitElement { href="https://en.wikipedia.org/wiki/ISO_4217#Active_codes" target="_blank" rel="noopener noreferrer" + class="find-value" >${this.hass.localize( "ui.panel.config.core.section.core.core_config.find_currency_value" )} { +const randomTip = (hass: HomeAssistant, narrow: boolean) => { const weighted: string[] = []; - const tips = [ + let tips = [ { content: hass.localize( "ui.panel.config.tips.join", @@ -84,11 +84,16 @@ const randomTip = (hass: HomeAssistant) => { ` ), weight: 2, + narrow: true, }, - { content: hass.localize("ui.tips.key_c_hint"), weight: 1 }, - { content: hass.localize("ui.tips.key_m_hint"), weight: 1 }, + { content: hass.localize("ui.tips.key_c_hint"), weight: 1, narrow: false }, + { content: hass.localize("ui.tips.key_m_hint"), weight: 1, narrow: false }, ]; + if (narrow) { + tips = tips.filter((tip) => tip.narrow); + } + tips.forEach((tip) => { for (let i = 0; i < tip.weight; i++) { weighted.push(tip.content); @@ -215,7 +220,7 @@ class HaConfigDashboard extends LitElement { super.updated(changedProps); if (!this._tip && changedProps.has("hass")) { - this._tip = randomTip(this.hass); + this._tip = randomTip(this.hass, this.narrow); } } diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index e9167234c2..abe2a11b13 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -224,14 +224,14 @@ export const configSections: { [name: string]: PageNavigation[] } = { path: "/config/person", translationKey: "ui.panel.config.person.caption", iconPath: mdiAccount, - iconColor: "#E48629", + iconColor: "#5A87FA", }, { component: "users", path: "/config/users", translationKey: "ui.panel.config.users.caption", iconPath: mdiBadgeAccountHorizontal, - iconColor: "#E48629", + iconColor: "#5A87FA", core: true, advancedOnly: true, }, diff --git a/src/panels/config/network/supervisor-hostname.ts b/src/panels/config/network/supervisor-hostname.ts index acb58df7b3..45a42ff711 100644 --- a/src/panels/config/network/supervisor-hostname.ts +++ b/src/panels/config/network/supervisor-hostname.ts @@ -111,6 +111,9 @@ export class HassioHostname extends LitElement { justify-content: space-between; align-items: center; } + ha-settings-row { + border-top: none; + } `; } diff --git a/src/translations/en.json b/src/translations/en.json index 4e7957a766..5230ec1ecb 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1493,7 +1493,7 @@ "unit_system_metric": "Metric", "imperial_example": "Fahrenheit, pounds", "metric_example": "Celsius, kilograms", - "find_currency_value": "Find your value", + "find_currency_value": "Find my value", "save_button": "Save", "currency": "Currency", "edit_location": "Edit location", From 7ca379e0a1a4a4ae9a1dffc60dcfe8875d4d005e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 28 Apr 2022 15:53:56 +0200 Subject: [PATCH 131/181] Hide and sort secondary device automations (#12496) --- .../device/ha-device-automation-picker.ts | 10 ++-- src/data/device_automation.ts | 14 ++++++ .../ha-device-automation-card.ts | 47 +++++++++++++++---- .../ha-device-automation-dialog.ts | 7 +-- 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/components/device/ha-device-automation-picker.ts b/src/components/device/ha-device-automation-picker.ts index 57b46e5b5b..9e12fe6e8e 100644 --- a/src/components/device/ha-device-automation-picker.ts +++ b/src/components/device/ha-device-automation-picker.ts @@ -5,6 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { DeviceAutomation, deviceAutomationsEqual, + sortDeviceAutomations, } from "../../data/device_automation"; import { HomeAssistant } from "../../types"; import "../ha-select"; @@ -127,7 +128,9 @@ export abstract class HaDeviceAutomationPicker< private async _updateDeviceInfo() { this._automations = this.deviceId - ? await this._fetchDeviceAutomations(this.hass, this.deviceId) + ? (await this._fetchDeviceAutomations(this.hass, this.deviceId)).sort( + sortDeviceAutomations + ) : // No device, clear the list of automations []; @@ -161,8 +164,9 @@ export abstract class HaDeviceAutomationPicker< if (this.value && deviceAutomationsEqual(automation, this.value)) { return; } - fireEvent(this, "change"); - fireEvent(this, "value-changed", { value: automation }); + const value = { ...automation }; + delete value.metadata; + fireEvent(this, "value-changed", { value }); } static get styles(): CSSResultGroup { diff --git a/src/data/device_automation.ts b/src/data/device_automation.ts index b047f87f1e..9a6685e8bd 100644 --- a/src/data/device_automation.ts +++ b/src/data/device_automation.ts @@ -11,6 +11,7 @@ export interface DeviceAutomation { type?: string; subtype?: string; event?: string; + metadata?: { secondary: boolean }; } export interface DeviceAction extends DeviceAutomation { @@ -179,3 +180,16 @@ export const localizeDeviceAutomationTrigger = ( (trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!) ); }; + +export const sortDeviceAutomations = ( + automationA: DeviceAutomation, + automationB: DeviceAutomation +) => { + if (automationA.metadata?.secondary && !automationB.metadata?.secondary) { + return 1; + } + if (!automationA.metadata?.secondary && automationB.metadata?.secondary) { + return -1; + } + return 0; +}; diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts index 47577805c3..59ba9aa5d2 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-card.ts @@ -1,5 +1,5 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { property } from "lit/decorators"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-card"; import "../../../../components/ha-chip"; @@ -10,6 +10,7 @@ import { DeviceAutomation, } from "../../../../data/device_automation"; import { showScriptEditor } from "../../../../data/script"; +import { buttonLinkStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; declare global { @@ -29,6 +30,8 @@ export abstract class HaDeviceAutomationCard< @property() public automations: T[] = []; + @state() public _showSecondary = false; + protected headerKey = ""; protected type = ""; @@ -60,28 +63,47 @@ export abstract class HaDeviceAutomationCard< if (this.automations.length === 0) { return html``; } + const automations = this._showSecondary + ? this.automations + : this.automations.filter( + (automation) => automation.metadata?.secondary === false + ); return html`

    ${this.hass.localize(this.headerKey)}

    - ${this.automations.map( + ${automations.map( (automation, idx) => html` - + ${this._localizeDeviceAutomation(this.hass, automation)} ` )} + ${!this._showSecondary && automations.length < this.automations.length + ? html`` + : ""}
    `; } + private _toggleSecondary() { + this._showSecondary = !this._showSecondary; + } + private _handleAutomationClicked(ev: CustomEvent) { - const automation = this.automations[(ev.currentTarget as any).index]; + const automation = { ...this.automations[(ev.currentTarget as any).index] }; if (!automation) { return; } + delete automation.metadata; if (this.script) { showScriptEditor({ sequence: [automation as DeviceAction] }); fireEvent(this, "entry-selected"); @@ -93,11 +115,18 @@ export abstract class HaDeviceAutomationCard< fireEvent(this, "entry-selected"); } - static get styles(): CSSResultGroup { - return css` + static styles = [ + buttonLinkStyle, + css` h3 { color: var(--primary-text-color); } - `; - } + .secondary { + --ha-chip-background-color: rgba(var(--rgb-primary-text-color), 0.07); + } + button.link { + color: var(--primary-color); + } + `, + ]; } diff --git a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts index cef11fc0fe..de0c862a0c 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts @@ -10,6 +10,7 @@ import { fetchDeviceActions, fetchDeviceConditions, fetchDeviceTriggers, + sortDeviceAutomations, } from "../../../../data/device_automation"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; @@ -63,16 +64,16 @@ export class DialogDeviceAutomation extends LitElement { const { device, script } = this._params; fetchDeviceActions(this.hass, device.id).then((actions) => { - this._actions = actions; + this._actions = actions.sort(sortDeviceAutomations); }); if (script) { return; } fetchDeviceTriggers(this.hass, device.id).then((triggers) => { - this._triggers = triggers; + this._triggers = triggers.sort(sortDeviceAutomations); }); fetchDeviceConditions(this.hass, device.id).then((conditions) => { - this._conditions = conditions; + this._conditions = conditions.sort(sortDeviceAutomations); }); } From b8c55f2f651314f9c602c0b508bf618664685944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Thu, 28 Apr 2022 17:36:17 +0200 Subject: [PATCH 132/181] Evaluate condition shorthands in editors (#12473) Co-authored-by: Paulus Schoutsen --- src/data/automation.ts | 27 +++++++++++++++++++ .../ha-automation-condition-editor.ts | 20 +++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index 4e8694b6fa..19aae9e594 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -235,6 +235,10 @@ export interface TriggerCondition extends BaseCondition { type ShorthandBaseCondition = Omit; +export interface ShorthandAndConditionList extends ShorthandBaseCondition { + condition: Condition[]; +} + export interface ShorthandAndCondition extends ShorthandBaseCondition { and: Condition[]; } @@ -260,10 +264,33 @@ export type Condition = export type ConditionWithShorthand = | Condition + | ShorthandAndConditionList | ShorthandAndCondition | ShorthandOrCondition | ShorthandNotCondition; +export const expandConditionWithShorthand = ( + cond: ConditionWithShorthand +): Condition => { + if ("condition" in cond && Array.isArray(cond.condition)) { + return { + condition: "and", + conditions: cond.condition, + }; + } + + for (const condition of ["and", "or", "not"]) { + if (condition in cond) { + return { + condition, + conditions: cond[condition], + } as Condition; + } + } + + return cond as Condition; +}; + export const triggerAutomationActions = ( hass: HomeAssistant, entityId: string diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index 80d2f58ac3..401ff3b14d 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -10,6 +10,7 @@ import "../../../../components/ha-select"; import type { HaSelect } from "../../../../components/ha-select"; import "../../../../components/ha-yaml-editor"; import type { Condition } from "../../../../data/automation"; +import { expandConditionWithShorthand } from "../../../../data/automation"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import "./types/ha-automation-condition-and"; @@ -42,10 +43,14 @@ const OPTIONS = [ export default class HaAutomationConditionEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: Condition; + @property() condition!: Condition; @property() public yamlMode = false; + private _processedCondition = memoizeOne((condition) => + expandConditionWithShorthand(condition) + ); + private _processedTypes = memoizeOne( (localize: LocalizeFunc): [string, string][] => OPTIONS.map( @@ -60,7 +65,8 @@ export default class HaAutomationConditionEditor extends LitElement { ); protected render() { - const selected = OPTIONS.indexOf(this.condition.condition); + const condition = this._processedCondition(this.condition); + const selected = OPTIONS.indexOf(condition.condition); const yamlMode = this.yamlMode || selected === -1; return html` ${yamlMode @@ -70,7 +76,7 @@ export default class HaAutomationConditionEditor extends LitElement { ${this.hass.localize( "ui.panel.config.automation.editor.conditions.unsupported_condition", "condition", - this.condition.condition + condition.condition )} ` : ""} @@ -90,7 +96,7 @@ export default class HaAutomationConditionEditor extends LitElement { .label=${this.hass.localize( "ui.panel.config.automation.editor.conditions.type_select" )} - .value=${this.condition.condition} + .value=${condition.condition} naturalMenuWidth @selected=${this._typeChanged} > @@ -103,8 +109,8 @@ export default class HaAutomationConditionEditor extends LitElement {
    ${dynamicElement( - `ha-automation-condition-${this.condition.condition}`, - { hass: this.hass, condition: this.condition } + `ha-automation-condition-${condition.condition}`, + { hass: this.hass, condition: condition } )}
    `} @@ -124,7 +130,7 @@ export default class HaAutomationConditionEditor extends LitElement { defaultConfig: Omit; }; - if (type !== this.condition.condition) { + if (type !== this._processedCondition(this.condition).condition) { fireEvent(this, "value-changed", { value: { condition: type, From 38b4090daa828dccac0831893ba82f8ae5385372 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 28 Apr 2022 18:37:58 +0200 Subject: [PATCH 133/181] Add support for enabling/disabling trigger/condition/action (#12493) * Add support for enabling/disabling trigger/condition/action * Add more visual indication of disabled * review * margin * Dont make overflow transparent * Change color of bar --- src/data/automation.ts | 2 + src/data/device_automation.ts | 1 + src/data/script.ts | 58 +++---- .../action/ha-automation-action-row.ts | 164 +++++++++++------- .../condition/ha-automation-condition-row.ts | 118 +++++++++---- src/panels/config/automation/structs.ts | 3 +- .../trigger/ha-automation-trigger-row.ts | 126 ++++++++++---- src/translations/en.json | 5 +- 8 files changed, 311 insertions(+), 166 deletions(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index 19aae9e594..a4fbcf4f47 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -65,6 +65,7 @@ export interface BaseTrigger { platform: string; id?: string; variables?: Record; + enabled?: boolean; } export interface StateTrigger extends BaseTrigger { @@ -178,6 +179,7 @@ export type Trigger = interface BaseCondition { condition: string; alias?: string; + enabled?: boolean; } export interface LogicalCondition extends BaseCondition { diff --git a/src/data/device_automation.ts b/src/data/device_automation.ts index 9a6685e8bd..9458452675 100644 --- a/src/data/device_automation.ts +++ b/src/data/device_automation.ts @@ -11,6 +11,7 @@ export interface DeviceAutomation { type?: string; subtype?: string; event?: string; + enabled?: boolean; metadata?: { secondary: boolean }; } diff --git a/src/data/script.ts b/src/data/script.ts index 3966651a4a..a28369a289 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -13,6 +13,7 @@ import { literal, is, Describe, + boolean, } from "superstruct"; import { computeObjectId } from "../common/entity/compute_object_id"; import { navigate } from "../common/navigate"; @@ -25,6 +26,7 @@ export const MODES_MAX = ["queued", "parallel"]; export const baseActionStruct = object({ alias: optional(string()), + enabled: optional(boolean()), }); const targetStruct = object({ @@ -88,15 +90,18 @@ export interface BlueprintScriptConfig extends ManualScriptConfig { use_blueprint: { path: string; input?: BlueprintInput }; } -export interface EventAction { +interface BaseAction { alias?: string; + enabled?: boolean; +} + +export interface EventAction extends BaseAction { event: string; event_data?: Record; event_data_template?: Record; } -export interface ServiceAction { - alias?: string; +export interface ServiceAction extends BaseAction { service?: string; service_template?: string; entity_id?: string; @@ -104,55 +109,48 @@ export interface ServiceAction { data?: Record; } -export interface DeviceAction { - alias?: string; +export interface DeviceAction extends BaseAction { type: string; device_id: string; domain: string; entity_id: string; } -export interface DelayActionParts { +export interface DelayActionParts extends BaseAction { milliseconds?: number; seconds?: number; minutes?: number; hours?: number; days?: number; } -export interface DelayAction { - alias?: string; +export interface DelayAction extends BaseAction { delay: number | Partial | string; } -export interface ServiceSceneAction { - alias?: string; +export interface ServiceSceneAction extends BaseAction { service: "scene.turn_on"; target?: { entity_id?: string }; entity_id?: string; metadata: Record; } -export interface LegacySceneAction { - alias?: string; +export interface LegacySceneAction extends BaseAction { scene: string; } export type SceneAction = ServiceSceneAction | LegacySceneAction; -export interface WaitAction { - alias?: string; +export interface WaitAction extends BaseAction { wait_template: string; timeout?: number; continue_on_timeout?: boolean; } -export interface WaitForTriggerAction { - alias?: string; +export interface WaitForTriggerAction extends BaseAction { wait_for_trigger: Trigger | Trigger[]; timeout?: number; continue_on_timeout?: boolean; } -export interface PlayMediaAction { - alias?: string; +export interface PlayMediaAction extends BaseAction { service: "media_player.play_media"; target?: { entity_id?: string }; entity_id?: string; @@ -160,13 +158,11 @@ export interface PlayMediaAction { metadata: Record; } -export interface RepeatAction { - alias?: string; +export interface RepeatAction extends BaseAction { repeat: CountRepeat | WhileRepeat | UntilRepeat; } -interface BaseRepeat { - alias?: string; +interface BaseRepeat extends BaseAction { sequence: Action | Action[]; } @@ -182,38 +178,32 @@ export interface UntilRepeat extends BaseRepeat { until: Condition[]; } -export interface ChooseActionChoice { - alias?: string; +export interface ChooseActionChoice extends BaseAction { conditions: string | Condition[]; sequence: Action | Action[]; } -export interface ChooseAction { - alias?: string; +export interface ChooseAction extends BaseAction { choose: ChooseActionChoice | ChooseActionChoice[] | null; default?: Action | Action[]; } -export interface IfAction { - alias?: string; +export interface IfAction extends BaseAction { if: string | Condition[]; then: Action | Action[]; else?: Action | Action[]; } -export interface VariablesAction { - alias?: string; +export interface VariablesAction extends BaseAction { variables: Record; } -export interface StopAction { - alias?: string; +export interface StopAction extends BaseAction { stop: string; error?: boolean; } -interface UnknownAction { - alias?: string; +interface UnknownAction extends BaseAction { [key: string]: unknown; } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 2b45d064f9..126468e545 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -160,62 +160,82 @@ export default class HaAutomationActionRow extends LitElement { return html` -
    -
    - ${this.index !== 0 - ? html` - - ` - : ""} - ${this.index !== this.totalActions - 1 - ? html` - - ` - : ""} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.run_action" - )} - - - ${yamlMode - ? this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    + ${this.action.enabled === false + ? html`
    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.disabled" + )} +
    ` + : ""} +
    + ${this.index !== 0 + ? html` + + ` + : ""} + ${this.index !== this.totalActions - 1 + ? html` + + ` + : ""} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.run_action" + )} + + + ${yamlMode + ? this.hass.localize( + "ui.panel.config.automation.editor.edit_ui" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + + ${this.action.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    +
    ${this._warnings ? html` -
    -
    - + ${this.condition.enabled === false + ? html`
    ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.test" + "ui.panel.config.automation.editor.actions.disabled" )} - - - - - - ${this._yamlMode - ? this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    +
    ` + : ""} +
    + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.test" + )} + + + + + + ${this._yamlMode + ? this.hass.localize( + "ui.panel.config.automation.editor.edit_ui" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + + ${this.condition.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    +
    ${this._warnings ? html`; private _processedTypes = memoizeOne( @@ -126,40 +128,60 @@ export default class HaAutomationTriggerRow extends LitElement { return html` -
    -
    - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" - )} - - - ${yamlMode - ? this.hass.localize( - "ui.panel.config.automation.editor.edit_ui" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.edit_yaml" - )} - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
    + ${this.trigger.enabled === false + ? html`
    + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.disabled" + )} +
    ` + : ""} +
    + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.edit_id" + )} + + + ${yamlMode + ? this.hass.localize( + "ui.panel.config.automation.editor.edit_ui" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.edit_yaml" + )} + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + + ${this.trigger.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
    +
    ${this._warnings ? html` - ${showId ? html` ): void { super.updated(changedProps); if (changedProps.has("trigger")) { this._subscribeTrigger(); @@ -347,6 +368,9 @@ export default class HaAutomationTriggerRow extends LitElement { fireEvent(this, "duplicate"); break; case 3: + this._onDisable(); + break; + case 4: this._onDelete(); break; } @@ -365,6 +389,15 @@ export default class HaAutomationTriggerRow extends LitElement { }); } + private _onDisable() { + const enabled = !(this.trigger.enabled ?? true); + const value = { ...this.trigger, enabled }; + fireEvent(this, "value-changed", { value }); + if (this._yamlMode) { + this._yamlEditor?.setValue(value); + } + } + private _typeChanged(ev: CustomEvent) { const type = (ev.target as HaSelect).value; @@ -439,10 +472,27 @@ export default class HaAutomationTriggerRow extends LitElement { return [ haStyle, css` + .disabled { + opacity: 0.5; + pointer-events: none; + } + .card-content { + padding-top: 16px; + margin-top: 0; + } + .disabled-bar { + background: var(--divider-color, #e0e0e0); + text-align: center; + border-top-right-radius: var(--ha-card-border-radius); + border-top-left-radius: var(--ha-card-border-radius); + } .card-menu { float: right; z-index: 3; + margin: 4px; --mdc-theme-text-primary-on-background: var(--primary-text-color); + display: flex; + align-items: center; } :host-context([style*="direction: rtl;"]) .card-menu { float: left; diff --git a/src/translations/en.json b/src/translations/en.json index 5230ec1ecb..84f7b9f664 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1675,7 +1675,7 @@ "introduction": "The automation editor allows you to create and edit automations. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.", "learn_more": "Learn more about automations", "pick_automation": "Pick automation to edit", - "no_automations": "We couldn’t find any automations", + "no_automations": "We couldn't find any automations", "add_automation": "Create automation", "only_editable": "Only automations in automations.yaml are editable.", "dev_only_editable": "Only automations that have a unique ID assigned are debuggable.", @@ -1961,6 +1961,9 @@ "run_action_error": "Error running action", "run_action_success": "Action run successfully", "duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]", + "enable": "Enable", + "disable": "Disable", + "disabled": "Disabled", "delete": "[%key:ui::panel::mailbox::delete_button%]", "delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]", "unsupported_action": "No visual editor support for action: {action}", From 8c97aee1fe4df539d65ef1a30e2940fcbf389806 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 28 Apr 2022 22:09:03 +0200 Subject: [PATCH 134/181] Add parallel automation/script action (#12491) --- .../src/pages/automation/describe-action.ts | 11 ++++ gallery/src/pages/automation/editor-action.ts | 2 + src/data/script.ts | 9 +++ src/data/script_i18n.ts | 4 ++ .../action/ha-automation-action-row.ts | 2 + .../types/ha-automation-action-parallel.ts | 56 +++++++++++++++++++ src/translations/en.json | 3 + 7 files changed, 87 insertions(+) create mode 100644 src/panels/config/automation/action/types/ha-automation-action-parallel.ts diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index dd3d6c6e93..286a25dc3d 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -62,6 +62,17 @@ const ACTIONS = [ entity_id: "input_boolean.toggle_4", }, }, + { + parallel: [ + { scene: "scene.kitchen_morning" }, + { + service: "media_player.play_media", + target: { entity_id: "media_player.living_room" }, + data: { media_content_id: "", media_content_type: "" }, + metadata: { title: "Happy Song" }, + }, + ], + }, ]; @customElement("demo-automation-describe-action") diff --git a/gallery/src/pages/automation/editor-action.ts b/gallery/src/pages/automation/editor-action.ts index 1f7a0d8206..6d675f974c 100644 --- a/gallery/src/pages/automation/editor-action.ts +++ b/gallery/src/pages/automation/editor-action.ts @@ -20,6 +20,7 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template"; import { Action } from "../../../../src/data/script"; import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition"; +import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel"; const SCHEMAS: { name: string; actions: Action[] }[] = [ { name: "Event", actions: [HaEventAction.defaultConfig] }, @@ -33,6 +34,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [ { name: "Repeat", actions: [HaRepeatAction.defaultConfig] }, { name: "Choose", actions: [HaChooseAction.defaultConfig] }, { name: "Variables", actions: [{ variables: { hello: "1" } }] }, + { name: "Parallel", actions: [HaParallelAction.defaultConfig] }, ]; @customElement("demo-automation-editor-action") diff --git a/src/data/script.ts b/src/data/script.ts index a28369a289..dec65806c5 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -203,6 +203,10 @@ export interface StopAction extends BaseAction { error?: boolean; } +export interface ParallelAction extends BaseAction { + parallel: Action | Action[]; +} + interface UnknownAction extends BaseAction { [key: string]: unknown; } @@ -222,6 +226,7 @@ export type Action = | VariablesAction | PlayMediaAction | StopAction + | ParallelAction | UnknownAction; export interface ActionTypes { @@ -239,6 +244,7 @@ export interface ActionTypes { service: ServiceAction; play_media: PlayMediaAction; stop: StopAction; + parallel: ParallelAction; unknown: UnknownAction; } @@ -318,6 +324,9 @@ export const getActionType = (action: Action): ActionType => { if ("stop" in action) { return "stop"; } + if ("parallel" in action) { + return "parallel"; + } if ("service" in action) { if ("metadata" in action) { if (is(action, activateSceneActionStruct)) { diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 230c867ef0..f6c1931cf9 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -169,5 +169,9 @@ export const describeAction = ( }`; } + if (actionType === "parallel") { + return "Run in parallel"; + } + return actionType; }; diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 126468e545..119f22ca5c 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -33,6 +33,7 @@ import "./types/ha-automation-action-delay"; import "./types/ha-automation-action-device_id"; import "./types/ha-automation-action-event"; import "./types/ha-automation-action-if"; +import "./types/ha-automation-action-parallel"; import "./types/ha-automation-action-play_media"; import "./types/ha-automation-action-repeat"; import "./types/ha-automation-action-service"; @@ -54,6 +55,7 @@ const OPTIONS = [ "if", "device_id", "stop", + "parallel", ]; const getType = (action: Action | undefined) => { diff --git a/src/panels/config/automation/action/types/ha-automation-action-parallel.ts b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts new file mode 100644 index 0000000000..0320bb2e26 --- /dev/null +++ b/src/panels/config/automation/action/types/ha-automation-action-parallel.ts @@ -0,0 +1,56 @@ +import { CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { Action, ParallelAction } from "../../../../../data/script"; +import { HaDeviceAction } from "./ha-automation-action-device_id"; +import { haStyle } from "../../../../../resources/styles"; +import type { HomeAssistant } from "../../../../../types"; +import "../ha-automation-action"; +import "../../../../../components/ha-textfield"; +import type { ActionElement } from "../ha-automation-action-row"; + +@customElement("ha-automation-action-parallel") +export class HaParallelAction extends LitElement implements ActionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public action!: ParallelAction; + + public static get defaultConfig() { + return { + parallel: [HaDeviceAction.defaultConfig], + }; + } + + protected render() { + const action = this.action; + + return html` + + `; + } + + private _actionsChanged(ev: CustomEvent) { + ev.stopPropagation(); + const value = ev.detail.value as Action[]; + fireEvent(this, "value-changed", { + value: { + ...this.action, + parallel: value, + }, + }); + } + + static get styles(): CSSResultGroup { + return haStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-action-parallel": HaParallelAction; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 84f7b9f664..c84157181d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2053,6 +2053,9 @@ "label": "Stop", "stop": "Reason for stopping", "error": "Stop because of an unexpected error" + }, + "parallel": { + "label": "Run in parallel" } } } From bdfb17d957aaf7d29652b78c480851ee4bc65732 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Apr 2022 15:42:18 -0500 Subject: [PATCH 135/181] Add Board Names, Move All Hardware (#12484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen Co-authored-by: Paulus Schoutsen --- src/data/hardware.ts | 22 ++++ .../config/hardware/ha-config-hardware.ts | 107 +++++++++--------- src/translations/en.json | 2 +- 3 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 src/data/hardware.ts diff --git a/src/data/hardware.ts b/src/data/hardware.ts new file mode 100644 index 0000000000..54ea838675 --- /dev/null +++ b/src/data/hardware.ts @@ -0,0 +1,22 @@ +// Keep in sync with https://github.com/home-assistant/analytics.home-assistant.io/blob/dev/site/src/analytics-os-boards.ts#L6-L24 +export const BOARD_NAMES: Record = { + "odroid-n2": "Home Assistant Blue / ODROID-N2", + "odroid-xu4": "ODROID-XU4", + "odroid-c2": "ODROID-C2", + "odroid-c4": "ODROID-C4", + rpi: "Raspberry Pi", + rpi0: "Raspberry Pi Zero", + "rpi0-w": "Raspberry Pi Zero W", + rpi2: "Raspberry Pi 2", + rpi3: "Raspberry Pi 3 (32-bit)", + "rpi3-64": "Raspberry Pi 3", + rpi4: "Raspberry Pi 4 (32-bit)", + "rpi4-64": "Raspberry Pi 4", + tinker: "ASUS Tinker Board", + "khadas-vim3": "Khadas VIM3", + "generic-aarch64": "Generic AArch64", + ova: "Virtual Machine", + "generic-x86-64": "Generic x86-64", + "intel-nuc": "Intel NUC", + yellow: "Home Assistant Yellow", +}; diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index 833f40b9ee..3430fd1ca3 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -8,6 +8,7 @@ import "../../../components/ha-alert"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; import "../../../components/ha-settings-row"; +import { BOARD_NAMES } from "../../../data/hardware"; import { extractApiErrorMessage, ignoreSupervisorError, @@ -56,6 +57,18 @@ class HaConfigHardware extends LitElement { .narrow=${this.narrow} .header=${this.hass.localize("ui.panel.config.hardware.caption")} > + + + ${this.hass.localize( + "ui.panel.config.hardware.available_hardware.title" + )} + ${this._error ? html`
    - - ${this.hass.localize( - "ui.panel.config.hardware.board" - )} -
    - ${this._OSData.board} -
    -
    + ${this._OSData?.board + ? html` + + ${BOARD_NAMES[this._OSData.board] || + this.hass.localize( + "ui.panel.config.hardware.board" + )} +
    + ${this._OSData.board} +
    +
    + ` + : ""}
    -
    - ${this._hostData.features.includes("reboot") - ? html` - - ${this.hass.localize( - "ui.panel.config.hardware.reboot_host" - )} - - ` - : ""} - ${this._hostData.features.includes("shutdown") - ? html` - - ${this.hass.localize( - "ui.panel.config.hardware.shutdown_host" - )} - - ` - : ""} -
    - - - - ${this.hass.localize( - "ui.panel.config.hardware.available_hardware.title" - )} - - + ${this._hostData.features.includes("reboot") + ? html` + + ${this.hass.localize( + "ui.panel.config.hardware.reboot_host" + )} + + ` + : ""} + ${this._hostData.features.includes("shutdown") + ? html` + + ${this.hass.localize( + "ui.panel.config.hardware.shutdown_host" + )} + + ` + : ""}
    @@ -241,10 +242,6 @@ class HaConfigHardware extends LitElement { justify-content: space-between; align-items: center; } - .buttons { - display: flex; - align-items: center; - } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index c84157181d..64db711526 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1519,7 +1519,7 @@ "caption": "Hardware", "available_hardware": { "failed_to_get": "Failed to get available hardware", - "title": "Available hardware", + "title": "All Hardware", "subsystem": "Subsystem", "device_path": "Device path", "id": "ID", From 1f3c23de2905541a7c9a9f85495c76b227ce9506 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Apr 2022 15:43:00 -0500 Subject: [PATCH 136/181] Change Restart to be a button, update dialogs (#12499) --- .../core/ha-config-system-navigation.ts | 70 +++++++++---------- .../developer-yaml-config.ts | 8 ++- src/translations/en.json | 9 +-- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 548cc51e6c..a665215626 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -1,5 +1,3 @@ -import { ActionDetail } from "@material/mwc-list"; -import { mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { canShowPage } from "../../../common/config/can_show_page"; @@ -44,22 +42,18 @@ class HaConfigSystemNavigation extends LitElement { back-path="/config" .header=${this.hass.localize("ui.panel.config.dashboard.system.main")} > - - - - ${this.hass.localize( - "ui.panel.config.system_dashboard.restart_homeassistant" - )} - - + class="warning" + .label=${this.narrow + ? this.hass.localize( + "ui.panel.config.system_dashboard.restart_homeassistant_short" + ) + : this.hass.localize( + "ui.panel.config.system_dashboard.restart_homeassistant" + )} + @click=${this._restart} + > ) { - switch (ev.detail.index) { - case 0: - showConfirmationDialog(this, { - text: this.hass.localize( - "ui.panel.config.system_dashboard.confirm_restart" - ), - confirm: () => { - this.hass - .callService("homeassistant", "restart") - .catch((reason) => { - showAlertDialog(this, { - title: this.hass.localize( - "ui.panel.config.system_dashboard.restart_error" - ), - text: reason.message, - }); - }); - }, + private _restart() { + showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.config.system_dashboard.confirm_restart_title" + ), + text: this.hass.localize( + "ui.panel.config.system_dashboard.confirm_restart_text" + ), + confirmText: this.hass.localize( + "ui.panel.config.system_dashboard.restart_homeassistant_short" + ), + confirm: () => { + this.hass.callService("homeassistant", "restart").catch((reason) => { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.system_dashboard.restart_error" + ), + text: reason.message, + }); }); - break; - } + }, + }); } static get styles(): CSSResultGroup { diff --git a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts index 57ca442c49..47ab1f4ff6 100644 --- a/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts +++ b/src/panels/developer-tools/yaml_configuration/developer-yaml-config.ts @@ -167,8 +167,14 @@ export class DeveloperYamlConfig extends LitElement { private _restart() { showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart_title" + ), text: this.hass.localize( - "ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart" + "ui.panel.developer-tools.tabs.yaml.section.server_management.confirm_restart_text" + ), + confirmText: this.hass.localize( + "ui.panel.developer-tools.tabs.yaml.section.server_management.restart" ), confirm: () => { this.hass.callService("homeassistant", "restart").catch((reason) => { diff --git a/src/translations/en.json b/src/translations/en.json index 64db711526..420e496340 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3170,8 +3170,10 @@ "supervisor_stats": "Supervisor Stats" }, "system_dashboard": { - "confirm_restart": "Are you sure you want to restart Home Assistant?", + "confirm_restart_text": "Restarting Home Assistant will stop all your active dashboards, automations and scripts.", + "confirm_restart_title": "Restart Home Assistant?", "restart_homeassistant": "Restart Home Assistant", + "restart_homeassistant_short": "Restart", "restart_error": "Failed to restart Home Assistant" } }, @@ -4203,10 +4205,9 @@ }, "server_management": { "heading": "Home Assistant", - "introduction": "Restarting Home Assistant will stop your dashboard and automations. After the reboot, each configuration will be reloaded.", + "confirm_restart_text": "Restarting Home Assistant will stop all your active dashboards, automations and scripts.", + "confirm_restart_title": "Restart Home Assistant?", "restart": "Restart", - "restart_home_assistant": "Restart Home Assistant", - "confirm_restart": "Are you sure you want to restart Home Assistant?", "stop": "Stop", "confirm_stop": "Are you sure you want to stop Home Assistant?", "restart_error": "Failed to restart Home Assistant" From 51f971337d4ebbcac7a9bc99244f48757ce82cd6 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Apr 2022 15:50:08 -0500 Subject: [PATCH 137/181] Bumped version to 20220428.0 (#12501) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f1f72e8597..fdd3616922 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220427.0 +version = 20220428.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From cadbe45babca349deeadbf92ae6947897e9c7b16 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 28 Apr 2022 21:23:23 -0500 Subject: [PATCH 138/181] Fix Wrap menu and remove menu title (#12505) --- src/components/ha-clickable-list-item.ts | 1 + src/components/ha-navigation-list.ts | 7 ------- src/panels/config/dashboard/ha-config-dashboard.ts | 5 ----- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/components/ha-clickable-list-item.ts b/src/components/ha-clickable-list-item.ts index 41c3734ab4..4ceb7b7e45 100644 --- a/src/components/ha-clickable-list-item.ts +++ b/src/components/ha-clickable-list-item.ts @@ -55,6 +55,7 @@ export class HaClickableListItem extends ListItemBase { align-items: center; padding-left: var(--mdc-list-side-padding, 20px); padding-right: var(--mdc-list-side-padding, 20px); + overflow: hidden; } `, ]; diff --git a/src/components/ha-navigation-list.ts b/src/components/ha-navigation-list.ts index fe1c044c35..f354e98001 100644 --- a/src/components/ha-navigation-list.ts +++ b/src/components/ha-navigation-list.ts @@ -59,13 +59,6 @@ class HaNavigationList extends LitElement { :host { --mdc-list-vertical-padding: 0; } - a { - text-decoration: none; - color: var(--primary-text-color); - position: relative; - display: block; - outline: 0; - } ha-svg-icon, ha-icon-next { color: var(--secondary-text-color); diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index ceb8c5447c..c0dd5b07a1 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -195,11 +195,6 @@ class HaConfigDashboard extends LitElement { ` : ""} - ${this.narrow && canInstallUpdates.length - ? html`
    - ${this.hass.localize("panel.config")} -
    ` - : ""} Date: Fri, 29 Apr 2022 12:50:19 +0300 Subject: [PATCH 139/181] form-string password fix (#12507) --- src/components/ha-form/ha-form-string.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/ha-form/ha-form-string.ts b/src/components/ha-form/ha-form-string.ts index f527676aaf..04cdd3d182 100644 --- a/src/components/ha-form/ha-form-string.ts +++ b/src/components/ha-form/ha-form-string.ts @@ -132,6 +132,11 @@ export class HaFormString extends LitElement implements HaFormElement { --mdc-icon-button-size: 24px; color: var(--secondary-text-color); } + + :host-context([style*="direction: rtl;"]) ha-icon-button { + right: auto; + left: 12px; + } `; } } From bf8affaf2b704e7dfd3440b6cb569abba88268b3 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 29 Apr 2022 14:41:27 +0200 Subject: [PATCH 140/181] Use media query for config menu mobile (#12510) --- .../config/core/ha-config-system-navigation.ts | 16 +++++++++------- .../config/dashboard/ha-config-dashboard.ts | 15 +++++++++------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index a665215626..85f8c857d6 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -126,13 +126,15 @@ class HaConfigSystemNavigation extends LitElement { padding-bottom: 0; } - :host([narrow]) ha-card { - border-radius: 0; - box-shadow: unset; - } - - :host([narrow]) ha-config-section { - margin-top: -42px; + @media all and (max-width: 600px) { + ha-card { + border-width: 1px 0; + border-radius: 0; + box-shadow: unset; + } + ha-config-section { + margin-top: -42px; + } } ha-navigation-list { diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index c0dd5b07a1..1ff795dafc 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -277,13 +277,16 @@ class HaConfigDashboard extends LitElement { padding: 16px; padding-bottom: 0; } - :host([narrow]) ha-card { - border-radius: 0; - box-shadow: unset; - } - :host([narrow]) ha-config-section { - margin-top: -42px; + @media all and (max-width: 600px) { + ha-card { + border-width: 1px 0; + border-radius: 0; + box-shadow: unset; + } + ha-config-section { + margin-top: -42px; + } } ha-tip { From 39f7034578859814b13dfa4fdefe9a6f9b24832a Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Fri, 29 Apr 2022 16:24:37 +0200 Subject: [PATCH 141/181] Fix incorrect 3-dot menu labels (config hardware & storage) (#12512) --- src/panels/config/hardware/ha-config-hardware.ts | 2 +- src/panels/config/storage/ha-config-section-storage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index 3430fd1ca3..50bbf8ce78 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -59,7 +59,7 @@ class HaConfigHardware extends LitElement { > diff --git a/src/panels/config/storage/ha-config-section-storage.ts b/src/panels/config/storage/ha-config-section-storage.ts index 9975166c20..d9acb79e38 100644 --- a/src/panels/config/storage/ha-config-section-storage.ts +++ b/src/panels/config/storage/ha-config-section-storage.ts @@ -46,7 +46,7 @@ class HaConfigSectionStorage extends LitElement { From 0108ec65cfdd3296cb685a4fa43207078f793129 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Fri, 29 Apr 2022 17:27:06 +0300 Subject: [PATCH 142/181] Media browser RTL fixes (#12506) --- src/components/media-player/dialog-media-manage.ts | 9 +++++++++ src/components/media-player/ha-media-manage-button.ts | 5 +++++ src/components/media-player/ha-media-upload-button.ts | 5 +++++ src/panels/media-browser/ha-panel-media-browser.ts | 5 +++-- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/components/media-player/dialog-media-manage.ts b/src/components/media-player/dialog-media-manage.ts index daee21d017..ef2af8e57e 100644 --- a/src/components/media-player/dialog-media-manage.ts +++ b/src/components/media-player/dialog-media-manage.ts @@ -302,6 +302,10 @@ class DialogMediaManage extends LitElement { --mdc-theme-primary: var(--mdc-theme-on-primary); } + mwc-list { + direction: ltr; + } + .danger { --mdc-theme-primary: var(--error-color); } @@ -310,6 +314,11 @@ class DialogMediaManage extends LitElement { vertical-align: middle; } + :host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { + margin-left: 8px !important; + margin-right: 0px !important; + } + .refresh { display: flex; height: 200px; diff --git a/src/components/media-player/ha-media-manage-button.ts b/src/components/media-player/ha-media-manage-button.ts index b67a0903b0..aeb9d7b2bc 100644 --- a/src/components/media-player/ha-media-manage-button.ts +++ b/src/components/media-player/ha-media-manage-button.ts @@ -59,6 +59,11 @@ class MediaManageButton extends LitElement { ha-circular-progress[slot="icon"] { vertical-align: middle; } + + :host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { + margin-left: 8px; + margin-right: 0px; + } `; } diff --git a/src/components/media-player/ha-media-upload-button.ts b/src/components/media-player/ha-media-upload-button.ts index 65d7b36982..413952b9a3 100644 --- a/src/components/media-player/ha-media-upload-button.ts +++ b/src/components/media-player/ha-media-upload-button.ts @@ -119,6 +119,11 @@ class MediaUploadButton extends LitElement { ha-circular-progress[slot="icon"] { vertical-align: middle; } + + :host-context([style*="direction: rtl;"]) ha-svg-icon[slot="icon"] { + margin-left: 8px; + margin-right: 0px; + } `; } diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 049fb3d5e3..1123806c3a 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -16,6 +16,7 @@ import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; import { navigate } from "../../common/navigate"; import "../../components/ha-menu-button"; import "../../components/ha-icon-button"; +import "../../components/ha-icon-button-arrow-prev"; import "../../components/media-player/ha-media-player-browse"; import "../../components/media-player/ha-media-manage-button"; import type { @@ -85,10 +86,10 @@ class PanelMediaBrowser extends LitElement { ${this._navigateIds.length > 1 ? html` - + > ` : html` Date: Fri, 29 Apr 2022 15:37:35 +0100 Subject: [PATCH 143/181] Fix `continue_on_timeout` default on `wait_template` automation visual editor (#12511) --- .../action/types/ha-automation-action-wait_template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts index 17c28ab665..06dbab6c26 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts @@ -33,7 +33,7 @@ export class HaWaitAction extends LitElement implements ActionElement { @property({ attribute: false }) public action!: WaitAction; public static get defaultConfig() { - return { wait_template: "" }; + return { wait_template: "", continue_on_timeout: true }; } protected render() { From 3438912ba59aa34ff84780da72c5b6643b7f133b Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 29 Apr 2022 16:47:44 +0200 Subject: [PATCH 144/181] Support shorthand logical operators in script sequences (#12509) --- src/data/script.ts | 2 +- .../config/automation/action/ha-automation-action-row.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/data/script.ts b/src/data/script.ts index dec65806c5..ee587f5946 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -294,7 +294,7 @@ export const getActionType = (action: Action): ActionType => { if ("wait_template" in action) { return "wait_template"; } - if ("condition" in action) { + if (["condition", "and", "or", "not"].some((key) => key in action)) { return "check_condition"; } if ("event" in action) { diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 119f22ca5c..42a4465878 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -65,6 +65,9 @@ const getType = (action: Action | undefined) => { if ("service" in action || "scene" in action) { return getActionType(action); } + if (["and", "or", "not"].some((key) => key in action)) { + return "condition"; + } return OPTIONS.find((option) => option in action); }; From f510e2a8e0c31e7109fe4ca2b66b12228495a485 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 29 Apr 2022 09:49:47 -0500 Subject: [PATCH 145/181] Only show Card Content if OS exist (#12513) --- .../config/hardware/ha-config-hardware.ts | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index 50bbf8ce78..5a080dae98 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -76,13 +76,13 @@ class HaConfigHardware extends LitElement { > ` : ""} - ${this._OSData && this._hostData + ${this._OSData || this._hostData ? html`
    -
    - ${this._OSData?.board - ? html` + ${this._OSData?.board + ? html` +
    ${BOARD_NAMES[this._OSData.board] || @@ -94,35 +94,39 @@ class HaConfigHardware extends LitElement { ${this._OSData.board}
    - ` - : ""} -
    -
    - ${this._hostData.features.includes("reboot") - ? html` - - ${this.hass.localize( - "ui.panel.config.hardware.reboot_host" - )} - - ` - : ""} - ${this._hostData.features.includes("shutdown") - ? html` - - ${this.hass.localize( - "ui.panel.config.hardware.shutdown_host" - )} - - ` - : ""} -
    +
    + ` + : ""} + ${this._hostData + ? html` +
    + ${this._hostData.features.includes("reboot") + ? html` + + ${this.hass.localize( + "ui.panel.config.hardware.reboot_host" + )} + + ` + : ""} + ${this._hostData.features.includes("shutdown") + ? html` + + ${this.hass.localize( + "ui.panel.config.hardware.shutdown_host" + )} + + ` + : ""} +
    + ` + : ""}
    ` From 98cc82db4481e2d26966123dd4020bb5b90e3f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Fri, 29 Apr 2022 17:40:03 +0200 Subject: [PATCH 146/181] Add condition shorthand to action types (#12514) --- src/data/script.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/data/script.ts b/src/data/script.ts index ee587f5946..0624095c5d 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -18,7 +18,13 @@ import { import { computeObjectId } from "../common/entity/compute_object_id"; import { navigate } from "../common/navigate"; import { HomeAssistant } from "../types"; -import { Condition, Trigger } from "./automation"; +import { + Condition, + ShorthandAndCondition, + ShorthandNotCondition, + ShorthandOrCondition, + Trigger, +} from "./automation"; import { BlueprintInput } from "./blueprint"; export const MODES = ["single", "restart", "queued", "parallel"] as const; @@ -216,6 +222,9 @@ export type Action = | DeviceAction | ServiceAction | Condition + | ShorthandAndCondition + | ShorthandOrCondition + | ShorthandNotCondition | DelayAction | SceneAction | WaitAction From c8b87b65bdf8872f4a264dc1c11b62486b6fdb48 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 29 Apr 2022 11:19:53 -0500 Subject: [PATCH 147/181] Fix for external url not logged into cloud (#12516) --- src/panels/config/network/ha-config-url-form.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/panels/config/network/ha-config-url-form.ts b/src/panels/config/network/ha-config-url-form.ts index f8af5f8781..e5fb9ef4e9 100644 --- a/src/panels/config/network/ha-config-url-form.ts +++ b/src/panels/config/network/ha-config-url-form.ts @@ -252,6 +252,8 @@ class ConfigUrlForm extends LitElement { this._cloudStatus = cloudStatus; if (cloudStatus.logged_in) { this._showCustomExternalUrl = this._externalUrlValue !== null; + } else { + this._showCustomExternalUrl = true; } }); } else { From f52e8c3392a99bec34b5442bb521d5026c0964c7 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 29 Apr 2022 12:15:43 -0500 Subject: [PATCH 148/181] Restart Home ASsistant button - Make less red and less big (#12515) --- src/panels/config/core/ha-config-system-navigation.ts | 11 +++-------- src/translations/en.json | 1 - 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 85f8c857d6..d4d3784ba1 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -44,14 +44,9 @@ class HaConfigSystemNavigation extends LitElement { > Date: Fri, 29 Apr 2022 20:51:44 +0200 Subject: [PATCH 149/181] Add actions to design gallery (#12518) * Add actions to design gallery * Update describe-action.ts --- gallery/src/pages/automation/describe-action.ts | 8 ++++++++ gallery/src/pages/automation/editor-action.ts | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 286a25dc3d..6b252c87f7 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -73,6 +73,14 @@ const ACTIONS = [ }, ], }, + { + stop: "No one is home!", + }, + { repeat: { count: 3, sequence: [{ delay: "00:00:01" }] } }, + { + if: [{ condition: "state" }], + then: [{ delay: "00:00:01" }], + }, ]; @customElement("demo-automation-describe-action") diff --git a/gallery/src/pages/automation/editor-action.ts b/gallery/src/pages/automation/editor-action.ts index 6d675f974c..4551593c44 100644 --- a/gallery/src/pages/automation/editor-action.ts +++ b/gallery/src/pages/automation/editor-action.ts @@ -21,6 +21,9 @@ import { HaWaitAction } from "../../../../src/panels/config/automation/action/ty import { Action } from "../../../../src/data/script"; import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition"; import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel"; +import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if"; +import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop"; +import { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media"; const SCHEMAS: { name: string; actions: Action[] }[] = [ { name: "Event", actions: [HaEventAction.defaultConfig] }, @@ -29,12 +32,15 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [ { name: "Condition", actions: [HaConditionAction.defaultConfig] }, { name: "Delay", actions: [HaDelayAction.defaultConfig] }, { name: "Scene", actions: [HaSceneAction.defaultConfig] }, + { name: "Play media", actions: [HaPlayMediaAction.defaultConfig] }, { name: "Wait", actions: [HaWaitAction.defaultConfig] }, { name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] }, { name: "Repeat", actions: [HaRepeatAction.defaultConfig] }, + { name: "If-Then", actions: [HaIfAction.defaultConfig] }, { name: "Choose", actions: [HaChooseAction.defaultConfig] }, { name: "Variables", actions: [{ variables: { hello: "1" } }] }, { name: "Parallel", actions: [HaParallelAction.defaultConfig] }, + { name: "Stop", actions: [HaStopAction.defaultConfig] }, ]; @customElement("demo-automation-editor-action") @@ -88,6 +94,6 @@ class DemoHaAutomationEditorAction extends LitElement { declare global { interface HTMLElementTagNameMap { - "demo-ha-automation-editor-action": DemoHaAutomationEditorAction; + "demo-automation-editor-action": DemoHaAutomationEditorAction; } } From 591b8cc5034c3770b3a699ff5c890dfa1061a5e3 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 29 Apr 2022 13:53:24 -0500 Subject: [PATCH 150/181] Move integrations to System Health (#12504) --- src/panels/config/info/ha-config-info.ts | 10 +- src/panels/config/info/integrations-card.ts | 230 +++++++----------- .../system-health/ha-config-system-health.ts | 16 +- src/translations/en.json | 6 +- 4 files changed, 104 insertions(+), 158 deletions(-) diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index 93c55db689..d5a7675fc0 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -3,12 +3,12 @@ import { property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-logo-svg"; import { - fetchHassioHostInfo, fetchHassioHassOsInfo, + fetchHassioHostInfo, HassioHassOSInfo, HassioHostInfo, } from "../../../data/hassio/host"; -import { HassioInfo, fetchHassioInfo } from "../../../data/hassio/supervisor"; +import { fetchHassioInfo, HassioInfo } from "../../../data/hassio/supervisor"; import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; @@ -153,12 +153,6 @@ class HaConfigInfo extends LitElement { : ""}

    -
    - -
    `; } diff --git a/src/panels/config/info/integrations-card.ts b/src/panels/config/info/integrations-card.ts index f4b9922e28..8a7d02aea3 100644 --- a/src/panels/config/info/integrations-card.ts +++ b/src/panels/config/info/integrations-card.ts @@ -1,16 +1,23 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "@material/mwc-list/mwc-list"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; import { customElement, property, state } from "lit/decorators"; -import memoizeOne from "memoize-one"; import "../../../components/ha-card"; +import "../../../components/ha-clickable-list-item"; import { domainToName, fetchIntegrationManifests, fetchIntegrationSetups, - integrationIssuesUrl, IntegrationManifest, IntegrationSetup, } from "../../../data/integration"; -import { HomeAssistant } from "../../../types"; +import type { HomeAssistant } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; import { documentationUrl } from "../../../util/documentation-url"; @@ -24,120 +31,69 @@ class IntegrationsCard extends LitElement { [domain: string]: IntegrationManifest; }; - @state() private _setups?: { - [domain: string]: IntegrationSetup; - }; + @state() private _setups?: IntegrationSetup[]; - private _sortedIntegrations = memoizeOne((components: string[]) => - Array.from( - new Set( - components.map((comp) => - comp.includes(".") ? comp.split(".")[1] : comp - ) - ) - ).sort() - ); - - firstUpdated(changedProps) { + protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); this._fetchManifests(); this._fetchSetups(); } protected render(): TemplateResult { + if (!this._setups) { + return html``; + } + return html` - - - - - ${!this.narrow - ? html` - - ` - : ""} - - - - - ${this._sortedIntegrations(this.hass!.config.components).map( - (domain) => { - const manifest = this._manifests && this._manifests[domain]; - const docLink = manifest - ? html`${this.hass.localize( - "ui.panel.config.info.documentation" - )}` - : ""; - const issueLink = - manifest && (manifest.is_built_in || manifest.issue_tracker) - ? html` - ${this.hass.localize( - "ui.panel.config.info.issues" - )} - ` - : ""; - const setupSeconds = - this._setups?.[domain]?.seconds?.toFixed(2); - return html` - - - - ${this.narrow - ? "" - : html` - - - - `} - - `; - } - )} - -
    ${this.hass.localize("ui.panel.config.info.setup_time")}
    - - - ${domainToName( - this.hass.localize, - domain, - manifest - )}
    - ${domain} - ${this.narrow - ? html`
    -
    ${docLink} ${issueLink}
    - ${setupSeconds ? html`${setupSeconds} s` : ""} -
    ` - : ""} -
    ${docLink}${issueLink} - ${setupSeconds ? html`${setupSeconds} s` : ""} -
    + + ${this._setups?.map((setup) => { + const manifest = this._manifests && this._manifests[setup.domain]; + const docLink = manifest + ? manifest.is_built_in + ? documentationUrl( + this.hass, + `/integrations/${manifest.domain}` + ) + : manifest.documentation + : ""; + + const setupSeconds = setup.seconds?.toFixed(2); + return html` + + + + ${domainToName(this.hass.localize, setup.domain, manifest)} + + ${setup.domain} +
    + ${setupSeconds ? html`${setupSeconds} s` : ""} +
    +
    + `; + })} +
    `; } @@ -151,54 +107,36 @@ class IntegrationsCard extends LitElement { } private async _fetchSetups() { - const setups = {}; - for (const setup of await fetchIntegrationSetups(this.hass)) { - setups[setup.domain] = setup; - } - this._setups = setups; + const setups = await fetchIntegrationSetups(this.hass); + this._setups = setups.sort((a, b) => { + if (a.seconds === b.seconds) { + return 0; + } + if (a.seconds === undefined) { + return 1; + } + if (b.seconds === undefined) { + return 1; + } + return b.seconds - a.seconds; + }); + } + + private _entryClicked(ev) { + ev.currentTarget.blur(); } static get styles(): CSSResultGroup { return css` - table { - width: 100%; - } - td, - th { - padding: 0 8px; - } - td:first-child { - padding-left: 0; - } - td.name { - padding: 8px; - } - td.setup { - text-align: right; - white-space: nowrap; - direction: ltr; - } - th { - text-align: right; - } - .domain { - color: var(--secondary-text-color); - } - .mobile-row { - display: flex; - justify-content: space-between; - } - .mobile-row a:not(:last-of-type) { - margin-right: 4px; + ha-clickable-list-item { + --mdc-list-item-meta-size: 48px; + --mdc-typography-caption-font-size: 12px; } img { display: block; max-height: 40px; max-width: 40px; } - a { - color: var(--primary-color); - } `; } } diff --git a/src/panels/config/system-health/ha-config-system-health.ts b/src/panels/config/system-health/ha-config-system-health.ts index efc357cf2c..46222a00da 100644 --- a/src/panels/config/system-health/ha-config-system-health.ts +++ b/src/panels/config/system-health/ha-config-system-health.ts @@ -30,6 +30,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; +import "../info/integrations-card"; const sortKeys = (a: string, b: string) => { if (a === "homeassistant") { @@ -317,6 +318,11 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) {
    `} + +
    `; @@ -450,12 +456,20 @@ class HaConfigSystemHealth extends SubscribeMixin(LitElement) { max-width: 1040px; margin: 0 auto; } + integrations-card { + max-width: 600px; + display: block; + max-width: 600px; + margin: 0 auto; + margin-bottom: 24px; + margin-bottom: max(24px, env(safe-area-inset-bottom)); + } ha-card { display: block; max-width: 600px; margin: 0 auto; padding-bottom: 16px; - margin-bottom: max(24px, env(safe-area-inset-bottom)); + margin-bottom: 24px; } ha-alert { display: block; diff --git a/src/translations/en.json b/src/translations/en.json index 0394f0d3af..c5be427d1b 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1551,7 +1551,6 @@ "frontend_version": "Frontend version: {version} - {type}", "custom_uis": "Custom UIs:", "system_health_error": "System Health component is not loaded. Add 'system_health:' to configuration.yaml", - "integrations": "Integrations", "documentation": "Documentation", "issues": "Issues", "setup_time": "Setup time", @@ -3164,10 +3163,11 @@ }, "system_health": { "caption": "System Health", - "cpu_usage": "Process Usage", + "cpu_usage": "Processor Usage", "ram_usage": "Memory Usage", "core_stats": "Core Stats", - "supervisor_stats": "Supervisor Stats" + "supervisor_stats": "Supervisor Stats", + "long_loading_integrations": "Long Loading Integrations" }, "system_dashboard": { "confirm_restart_text": "Restarting Home Assistant will stop all your active dashboards, automations and scripts.", From 1421df2a5aa224396caccb80d6bc69aa9b679217 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 29 Apr 2022 23:30:40 +0200 Subject: [PATCH 151/181] Add if, parallel and stop action to trace graph (#12520) --- src/components/trace/hat-script-graph.ts | 106 ++++++++++++++++++++++- src/data/trace.ts | 8 ++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 61555e01d2..7441bdebae 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -1,7 +1,11 @@ import { mdiAbTesting, + mdiAlertOctagon, + mdiArrowDecision, mdiArrowUp, mdiAsterisk, + mdiCallMissed, + mdiCallReceived, mdiCallSplit, mdiCheckboxBlankOutline, mdiCheckboxMarkedOutline, @@ -9,10 +13,12 @@ import { mdiChevronRight, mdiChevronUp, mdiClose, + mdiCloseOctagon, mdiCodeBrackets, mdiDevices, mdiExclamation, mdiRefresh, + mdiShuffleDisabled, mdiTimerOutline, mdiTrafficLight, } from "@mdi/js"; @@ -27,6 +33,8 @@ import { DelayAction, DeviceAction, EventAction, + IfAction, + ParallelAction, RepeatAction, SceneAction, ServiceAction, @@ -36,6 +44,8 @@ import { import { ChooseActionTraceStep, ConditionTraceStep, + IfActionTraceStep, + StopActionTraceStep, TraceExtended, } from "../../data/trace"; import "../ha-icon-button"; @@ -110,6 +120,9 @@ export class HatScriptGraph extends LitElement { repeat: this.render_repeat_node, choose: this.render_choose_node, device_id: this.render_device_node, + if: this.render_if_node, + stop: this.render_stop_node, + parallel: this.render_parallel_node, other: this.render_other_node, }; @@ -146,7 +159,7 @@ export class HatScriptGraph extends LitElement { > + + ${config.else + ? html`
    + ${ensureArray(config.else).map((action, j) => + this.render_action_node(action, `${path}/else/${j}`) + )} +
    ` + : html``} +
    + + ${ensureArray(config.then).map((action, j) => + this.render_action_node(action, `${path}/then/${j}`) + )} +
    + + `; + } + private render_condition_node( node: Condition, path: string, @@ -392,6 +453,49 @@ export class HatScriptGraph extends LitElement { `; } + private render_parallel_node( + node: ParallelAction, + path: string, + graphStart = false + ) { + const trace: any = this.trace.trace[path]; + return html` + + + ${ensureArray(node.parallel).map((action, i) => + this.render_action_node(action, `${path}/parallel/${i}/0`) + )} + + `; + } + + private render_stop_node(node: Action, path: string, graphStart = false) { + const trace = this.trace.trace[path] as StopActionTraceStep[] | undefined; + return html` + + `; + } + private render_other_node(node: Action, path: string, graphStart = false) { return html` Date: Fri, 29 Apr 2022 17:37:42 -0500 Subject: [PATCH 152/181] Bumped version to 20220429.0 (#12521) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index fdd3616922..9a53d83aba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220428.0 +version = 20220429.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 092dfd1e87f882236c250d55868c0066e566e80a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 30 Apr 2022 21:31:43 +0200 Subject: [PATCH 153/181] Change color of persons for real this time (#12527) --- src/panels/config/ha-panel-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index abe2a11b13..0c933e8d8b 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -93,7 +93,7 @@ export const configSections: { [name: string]: PageNavigation[] } = { path: "/config/person", translationKey: "people", iconPath: mdiAccount, - iconColor: "#832EA6", + iconColor: "#5A87FA", components: ["person", "users"], }, { From a4a0d7cf1901d824b17bcf01b7ced4cdc80650f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 30 Apr 2022 23:52:14 +0200 Subject: [PATCH 154/181] Ignore modifier keys when forwarding events to quickbar (#12525) --- hassio/src/hassio-main.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts index d4a3a450e6..68fe3e7482 100644 --- a/hassio/src/hassio-main.ts +++ b/hassio/src/hassio-main.ts @@ -74,7 +74,11 @@ export class HassioMain extends SupervisorBaseElement { }); // Forward keydown events to the main window for quickbar access - document.body.addEventListener("keydown", (ev) => { + document.body.addEventListener("keydown", (ev: KeyboardEvent) => { + if (ev.altKey || ev.ctrlKey || ev.shiftKey || ev.metaKey) { + // Ignore if modifier keys are pressed + return; + } // @ts-ignore fireEvent(mainWindow, "hass-quick-bar-trigger", ev, { bubbles: false, From f5864181afdcdb01a0456347f5f16c88eb55347b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Sat, 30 Apr 2022 23:53:43 +0200 Subject: [PATCH 155/181] Add optional repository_url to supervisor_addon my link (#12524) --- .../src/addon-view/hassio-addon-dashboard.ts | 38 +++++++++++++++++++ hassio/src/hassio-my-redirect.ts | 11 ++++++ src/data/hassio/supervisor.ts | 4 +- src/panels/my/ha-panel-my.ts | 3 ++ src/translations/en.json | 4 ++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/hassio/src/addon-view/hassio-addon-dashboard.ts b/hassio/src/addon-view/hassio-addon-dashboard.ts index 4455982ecc..22e5bb32c9 100644 --- a/hassio/src/addon-view/hassio-addon-dashboard.ts +++ b/hassio/src/addon-view/hassio-addon-dashboard.ts @@ -17,7 +17,9 @@ import { HassioAddonDetails, } from "../../../src/data/hassio/addon"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; +import { setSupervisorOption } from "../../../src/data/hassio/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; +import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box"; import "../../../src/layouts/hass-error-screen"; import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-tabs-subpage"; @@ -166,6 +168,42 @@ class HassioAddonDashboard extends LitElement { protected async firstUpdated(): Promise { if (this.route.path === "") { const requestedAddon = extractSearchParam("addon"); + const requestedAddonRepository = extractSearchParam("repository_url"); + if ( + requestedAddonRepository && + !this.supervisor.supervisor.addons_repositories.find( + (repo) => repo === requestedAddonRepository + ) + ) { + if ( + !(await showConfirmationDialog(this, { + title: this.supervisor.localize("my.add_addon_repository_title"), + text: this.supervisor.localize( + "my.add_addon_repository_description", + { addon: requestedAddon, repository: requestedAddonRepository } + ), + confirmText: this.supervisor.localize("common.add"), + dismissText: this.supervisor.localize("common.cancel"), + })) + ) { + this._error = this.supervisor.localize( + "my.error_repository_not_found" + ); + return; + } + + try { + await setSupervisorOption(this.hass, { + addons_repositories: [ + ...this.supervisor.supervisor.addons_repositories, + requestedAddonRepository, + ], + }); + } catch (err: any) { + this._error = extractApiErrorMessage(err); + } + } + if (requestedAddon) { const addonsInfo = await fetchHassioAddonsInfo(this.hass); const validAddon = addonsInfo.addons.some( diff --git a/hassio/src/hassio-my-redirect.ts b/hassio/src/hassio-my-redirect.ts index fc0e1c0808..526d6295e1 100644 --- a/hassio/src/hassio-my-redirect.ts +++ b/hassio/src/hassio-my-redirect.ts @@ -42,6 +42,9 @@ export const REDIRECTS: Redirects = { params: { addon: "string", }, + optional_params: { + repository_url: "url", + }, }, supervisor_ingress: { redirect: "/hassio/ingress", @@ -124,6 +127,14 @@ class HassioMyRedirect extends LitElement { } resultParams[key] = params[key]; }); + Object.entries(redirect.optional_params || {}).forEach(([key, type]) => { + if (params[key]) { + if (!this._checkParamType(type, params[key])) { + throw Error(); + } + resultParams[key] = params[key]; + } + }); return `?${createSearchParam(resultParams)}`; } diff --git a/src/data/hassio/supervisor.ts b/src/data/hassio/supervisor.ts index ec6434f545..55edd1cf9e 100644 --- a/src/data/hassio/supervisor.ts +++ b/src/data/hassio/supervisor.ts @@ -1,7 +1,7 @@ import { atLeastVersion } from "../../common/config/version"; import { HomeAssistant, PanelInfo } from "../../types"; import { SupervisorArch } from "../supervisor/supervisor"; -import { HassioAddonInfo, HassioAddonRepository } from "./addon"; +import { HassioAddonInfo } from "./addon"; import { hassioApiResultExtractor, HassioResponse } from "./common"; export type HassioHomeAssistantInfo = { @@ -23,7 +23,7 @@ export type HassioHomeAssistantInfo = { export type HassioSupervisorInfo = { addons: HassioAddonInfo[]; - addons_repositories: HassioAddonRepository[]; + addons_repositories: string[]; arch: SupervisorArch; channel: string; debug: boolean; diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index 836d3061f5..9883f6a20b 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -220,6 +220,9 @@ export interface Redirect { params?: { [key: string]: ParamType; }; + optional_params?: { + [key: string]: ParamType; + }; } @customElement("ha-panel-my") diff --git a/src/translations/en.json b/src/translations/en.json index c5be427d1b..e95c39cfac 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4480,6 +4480,7 @@ "cancel": "[%key:ui::common::cancel%]", "yes": "[%key:ui::common::yes%]", "no": "[%key:ui::common::no%]", + "add": "[%key:supervisor::dialog::repositories::add%]", "description": "Description", "failed_to_restart_name": "Failed to restart {name}", "failed_to_update_name": "Failed to update {name}", @@ -4549,8 +4550,11 @@ "my": { "not_supported": "[%key:ui::panel::my::not_supported%]", "faq_link": "[%key:ui::panel::my::faq_link%]", + "add_addon_repository_title": "Missing add-on repository", + "add_addon_repository_description": "The addon ''{addon}'' is a part of the add-on repository ''{repository}'', this repository is missing on your system, do you want to add that now?", "error": "[%key:ui::panel::my::error%]", "error_addon_not_found": "Add-on not found", + "error_repository_not_found": "The required repository for this Add-on was not found", "error_addon_not_started": "The requested add-on is not running. Please start it first", "error_addon_not_installed": "The requested add-on is not installed. Please install it first", "error_addon_no_ingress": "The requested add-on does not support ingress" From 1369c1ae8ced807cd8ba801296c6eccb896cdb21 Mon Sep 17 00:00:00 2001 From: Yosi Levy <37745463+yosilevy@users.noreply.github.com> Date: Sun, 1 May 2022 18:59:12 +0300 Subject: [PATCH 156/181] Calendar-card fix (#12532) --- src/panels/calendar/ha-full-calendar.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index 7ee2042304..d279ba1f28 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -11,14 +11,7 @@ import listPlugin from "@fullcalendar/list"; // @ts-ignore import listStyle from "@fullcalendar/list/main.css"; import "@material/mwc-button"; -import { - mdiChevronLeft, - mdiChevronRight, - mdiViewAgenda, - mdiViewDay, - mdiViewModule, - mdiViewWeek, -} from "@mdi/js"; +import { mdiViewAgenda, mdiViewDay, mdiViewModule, mdiViewWeek } from "@mdi/js"; import { css, CSSResultGroup, @@ -33,7 +26,6 @@ import memoize from "memoize-one"; import { useAmPm } from "../../common/datetime/use_am_pm"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-button-toggle-group"; -import "../../components/ha-icon-button"; import "../../components/ha-icon-button-prev"; import "../../components/ha-icon-button-next"; import { haStyle } from "../../resources/styles"; @@ -152,20 +144,18 @@ export class HAFullCalendar extends LitElement {

    ${this.calendar.view.title}

    - - - + - +
    From a571fb5528c65f94bc3efe60941861a9a4672f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Sun, 1 May 2022 17:59:46 +0200 Subject: [PATCH 157/181] Handle condition shorthands in trace graphs (#12533) --- src/components/trace/hat-script-graph.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 7441bdebae..45ea965b03 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -111,6 +111,9 @@ export class HatScriptGraph extends LitElement { private typeRenderers = { condition: this.render_condition_node, + and: this.render_condition_node, + or: this.render_condition_node, + not: this.render_condition_node, delay: this.render_delay_node, event: this.render_event_node, scene: this.render_scene_node, From dd49fd27889d301e286b1a46bb8911ce20f71896 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 1 May 2022 09:02:32 -0700 Subject: [PATCH 158/181] Make the "Aborted: Reauthentication successful" more user friendly (#12530) Replace the "Aborted" in the title with the integration name to make the user error messages more user friendly. The message itself ("Reauthentication successful" or "Missing configuraiton, etc) error message is descriptive enought that we can replace the title with the integration name and still preserve the meeting. The advance is that this doesn't confuse users who are surprised by it saying "Aborted" when things were successful https://github.com/home-assistant/core/issues/47135 --- src/dialogs/config-flow/dialog-data-entry-flow.ts | 1 + src/dialogs/config-flow/step-flow-abort.ts | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/dialogs/config-flow/dialog-data-entry-flow.ts b/src/dialogs/config-flow/dialog-data-entry-flow.ts index 023807a439..d0a7d28d13 100644 --- a/src/dialogs/config-flow/dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/dialog-data-entry-flow.ts @@ -312,6 +312,7 @@ class DataEntryFlowDialog extends LitElement { .flowConfig=${this._params.flowConfig} .step=${this._step} .hass=${this.hass} + .domain=${this._step.handler} > ` : this._step.type === "progress" diff --git a/src/dialogs/config-flow/step-flow-abort.ts b/src/dialogs/config-flow/step-flow-abort.ts index dfed959d90..79581e366a 100644 --- a/src/dialogs/config-flow/step-flow-abort.ts +++ b/src/dialogs/config-flow/step-flow-abort.ts @@ -15,13 +15,11 @@ class StepFlowAbort extends LitElement { @property({ attribute: false }) public step!: DataEntryFlowStepAbort; + @property({ attribute: false }) public domain!: string; + protected render(): TemplateResult { return html` -

    - ${this.hass.localize( - "ui.panel.config.integrations.config_flow.aborted" - )} -

    +

    ${this.hass.localize(`component.${this.domain}.title`)}

    ${this.flowConfig.renderAbortDescription(this.hass, this.step)}
    From 080cad0ccde4621e997ce2ad2729a878e62ce1f7 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Mon, 2 May 2022 00:21:25 +0200 Subject: [PATCH 159/181] Prevent color temp selector mired exception (#12536) --- src/components/ha-selector/ha-selector-color-temp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ha-selector/ha-selector-color-temp.ts b/src/components/ha-selector/ha-selector-color-temp.ts index e94519df24..d309e4f5d3 100644 --- a/src/components/ha-selector/ha-selector-color-temp.ts +++ b/src/components/ha-selector/ha-selector-color-temp.ts @@ -27,8 +27,8 @@ export class HaColorTempSelector extends LitElement { pin icon="hass:thermometer" .caption=${this.label || ""} - .min=${this.selector.color_temp.min_mireds ?? 153} - .max=${this.selector.color_temp.max_mireds ?? 500} + .min=${this.selector.color_temp?.min_mireds ?? 153} + .max=${this.selector.color_temp?.max_mireds ?? 500} .value=${this.value} .disabled=${this.disabled} .helper=${this.helper} From 239e71b4143e2ae5598b4ac909df404f6f91a880 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 2 May 2022 05:54:55 -0500 Subject: [PATCH 160/181] Fix some issues and feedback with About and system health (#12537) --- src/components/ha-clickable-list-item.ts | 9 ++++- .../core/ha-config-system-navigation.ts | 6 +++ src/panels/config/info/ha-config-info.ts | 38 +++++++++---------- .../system-health/ha-config-system-health.ts | 2 +- .../integrations-card.ts | 10 ++++- src/translations/en.json | 6 +-- 6 files changed, 45 insertions(+), 26 deletions(-) rename src/panels/config/{info => system-health}/integrations-card.ts (93%) diff --git a/src/components/ha-clickable-list-item.ts b/src/components/ha-clickable-list-item.ts index 4ceb7b7e45..793b9d1795 100644 --- a/src/components/ha-clickable-list-item.ts +++ b/src/components/ha-clickable-list-item.ts @@ -12,6 +12,8 @@ export class HaClickableListItem extends ListItemBase { // property used only in css @property({ type: Boolean, reflect: true }) public rtl = false; + @property({ type: Boolean, reflect: true }) public openNewTab = false; + @query("a") private _anchor!: HTMLAnchorElement; public render() { @@ -20,7 +22,12 @@ export class HaClickableListItem extends ListItemBase { return html`${this.disableHref ? html`${r}` - : html`${r}`}`; + : html`${r}`}`; } firstUpdated() { diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index d4d3784ba1..3af90288dc 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -61,6 +61,7 @@ class HaConfigSystemNavigation extends LitElement { .pages=${pages} > +
    Looking for YAML Configuration? It has moved to Developer Tools
    `; @@ -136,6 +137,11 @@ class HaConfigSystemNavigation extends LitElement { --navigation-list-item-title-font-size: 16px; --navigation-list-item-padding: 4px; } + .yaml-config { + margin-bottom: max(env(safe-area-inset-bottom), 24px); + text-align: center; + font-style: italic; + } `, ]; } diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index d5a7675fc0..b046e5ff18 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -13,7 +13,6 @@ import "../../../layouts/hass-subpage"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; -import "./integrations-card"; const JS_TYPE = __BUILD__; const JS_VERSION = __VERSION__; @@ -21,13 +20,13 @@ const JS_VERSION = __VERSION__; class HaConfigInfo extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public narrow!: boolean; + @property({ type: Boolean }) public narrow!: boolean; - @property() public isWide!: boolean; + @property({ type: Boolean }) public isWide!: boolean; - @property() public showAdvanced!: boolean; + @property({ type: Boolean }) public showAdvanced!: boolean; - @property() public route!: Route; + @property({ attribute: false }) public route!: Route; @state() private _hostInfo?: HassioHostInfo; @@ -61,18 +60,22 @@ class HaConfigInfo extends LitElement {
    -

    Home Assistant Core ${hass.connection.haVersion}

    +

    Home Assistant Core ${hass.connection.haVersion}

    ${this._hassioInfo - ? html`

    - Home Assistant Supervisor ${this._hassioInfo.supervisor} -

    ` + ? html` +

    + Home Assistant Supervisor ${this._hassioInfo.supervisor} +

    + ` : ""} ${this._osInfo?.version - ? html`

    Home Assistant OS ${this._osInfo.version}

    ` + ? html`

    Home Assistant OS ${this._osInfo.version}

    ` : ""} ${this._hostInfo - ? html`

    Kernel version ${this._hostInfo.kernel}

    -

    Agent version ${this._hostInfo.agent_version}

    ` + ? html` +

    Kernel version ${this._hostInfo.kernel}

    +

    Agent version ${this._hostInfo.agent_version}

    + ` : ""}

    ${this.hass.localize( @@ -211,18 +214,15 @@ class HaConfigInfo extends LitElement { .about a { color: var(--primary-color); } - - integrations-card { - display: block; - max-width: 600px; - margin: 0 auto; - padding-bottom: 16px; - } ha-logo-svg { padding: 12px; height: 180px; width: 180px; } + + h4 { + font-weight: 400; + } `, ]; } diff --git a/src/panels/config/system-health/ha-config-system-health.ts b/src/panels/config/system-health/ha-config-system-health.ts index 46222a00da..ba3c2862b0 100644 --- a/src/panels/config/system-health/ha-config-system-health.ts +++ b/src/panels/config/system-health/ha-config-system-health.ts @@ -30,7 +30,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; -import "../info/integrations-card"; +import "./integrations-card"; const sortKeys = (a: string, b: string) => { if (a === "homeassistant") { diff --git a/src/panels/config/info/integrations-card.ts b/src/panels/config/system-health/integrations-card.ts similarity index 93% rename from src/panels/config/info/integrations-card.ts rename to src/panels/config/system-health/integrations-card.ts index 8a7d02aea3..2188160eb7 100644 --- a/src/panels/config/info/integrations-card.ts +++ b/src/panels/config/system-health/integrations-card.ts @@ -48,7 +48,7 @@ class IntegrationsCard extends LitElement { @@ -69,6 +69,7 @@ class IntegrationsCard extends LitElement { graphic="avatar" twoline hasMeta + openNewTab @click=${this._entryClicked} href=${docLink} > @@ -129,7 +130,7 @@ class IntegrationsCard extends LitElement { static get styles(): CSSResultGroup { return css` ha-clickable-list-item { - --mdc-list-item-meta-size: 48px; + --mdc-list-item-meta-size: 64px; --mdc-typography-caption-font-size: 12px; } img { @@ -137,6 +138,11 @@ class IntegrationsCard extends LitElement { max-height: 40px; max-width: 40px; } + div[slot="meta"] { + display: flex; + justify-content: center; + align-items: center; + } `; } } diff --git a/src/translations/en.json b/src/translations/en.json index e95c39cfac..9e7565cfa1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1152,7 +1152,7 @@ }, "about": { "main": "About", - "secondary": "Version, loaded integrations and links to documentation" + "secondary": "Version information, credits and more" } }, "common": { @@ -3167,7 +3167,7 @@ "ram_usage": "Memory Usage", "core_stats": "Core Stats", "supervisor_stats": "Supervisor Stats", - "long_loading_integrations": "Long Loading Integrations" + "integration_start_time": "Integration Startup Time" }, "system_dashboard": { "confirm_restart_text": "Restarting Home Assistant will stop all your active dashboards, automations and scripts.", @@ -4155,7 +4155,7 @@ "adjust_sum": "Adjust sum" }, "yaml": { - "title": "YAML Configuration", + "title": "YAML", "section": { "validation": { "heading": "Configuration validation", From 3b6b4d76640fa73dc336a9a6875ff036fe45033f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 May 2022 17:06:55 +0200 Subject: [PATCH 161/181] Add descriptions for actions (#12541) Co-authored-by: Paulus Schoutsen --- .../src/pages/automation/describe-action.ts | 20 +++++ src/data/script.ts | 6 +- src/data/script_i18n.ts | 85 ++++++++++++++++++- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 6b252c87f7..49fa3dc7f9 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -77,9 +77,29 @@ const ACTIONS = [ stop: "No one is home!", }, { repeat: { count: 3, sequence: [{ delay: "00:00:01" }] } }, + { + repeat: { + for_each: ["bread", "butter", "cheese"], + sequence: [{ delay: "00:00:01" }], + }, + }, { if: [{ condition: "state" }], then: [{ delay: "00:00:01" }], + else: [{ delay: "00:00:05" }], + }, + { + choose: [ + { + conditions: [{ condition: "state" }], + sequence: [{ delay: "00:00:01" }], + }, + { + conditions: [{ condition: "sun" }], + sequence: [{ delay: "00:00:05" }], + }, + ], + default: [{ delay: "00:00:03" }], }, ]; diff --git a/src/data/script.ts b/src/data/script.ts index 0624095c5d..ada068d25e 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -165,7 +165,7 @@ export interface PlayMediaAction extends BaseAction { } export interface RepeatAction extends BaseAction { - repeat: CountRepeat | WhileRepeat | UntilRepeat; + repeat: CountRepeat | WhileRepeat | UntilRepeat | ForEachRepeat; } interface BaseRepeat extends BaseAction { @@ -184,6 +184,10 @@ export interface UntilRepeat extends BaseRepeat { until: Condition[]; } +export interface ForEachRepeat extends BaseRepeat { + for_each: string | any[]; +} + export interface ChooseActionChoice extends BaseAction { conditions: string | Condition[]; sequence: Action | Action[]; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index f6c1931cf9..a66c9529e0 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -8,12 +8,17 @@ import { describeCondition, describeTrigger } from "./automation_i18n"; import { ActionType, ActionTypes, + ChooseAction, DelayAction, DeviceAction, EventAction, getActionType, + IfAction, + ParallelAction, PlayMediaAction, + RepeatAction, SceneAction, + StopAction, VariablesAction, WaitForTriggerAction, } from "./script"; @@ -161,6 +166,81 @@ export const describeAction = ( return `Test ${describeCondition(action as Condition)}`; } + if (actionType === "stop") { + const config = action as StopAction; + return `Stopped${config.stop ? ` because: ${config.stop}` : ""}`; + } + + if (actionType === "if") { + const config = action as IfAction; + return `If ${ + typeof config.if === "string" + ? config.if + : ensureArray(config.if) + .map((condition) => describeCondition(condition)) + .join(", ") + } then ${ensureArray(config.then).map((thenAction) => + describeAction(hass, thenAction) + )}${ + config.else + ? ` else ${ensureArray(config.else).map((elseAction) => + describeAction(hass, elseAction) + )}` + : "" + }`; + } + + if (actionType === "choose") { + const config = action as ChooseAction; + return config.choose + ? `If ${ensureArray(config.choose) + .map( + (chooseAction) => + `${ + typeof chooseAction.conditions === "string" + ? chooseAction.conditions + : ensureArray(chooseAction.conditions) + .map((condition) => describeCondition(condition)) + .join(", ") + } then ${ensureArray(chooseAction.sequence) + .map((chooseSeq) => describeAction(hass, chooseSeq)) + .join(", ")}` + ) + .join(", else if ")}${ + config.default + ? `. If none match: ${ensureArray(config.default) + .map((dAction) => describeAction(hass, dAction)) + .join(", ")}` + : "" + }` + : "Choose"; + } + + if (actionType === "repeat") { + const config = action as RepeatAction; + return `Repeat ${ensureArray(config.repeat.sequence).map((repeatAction) => + describeAction(hass, repeatAction) + )} ${"count" in config.repeat ? `${config.repeat.count} times` : ""}${ + "while" in config.repeat + ? `while ${ensureArray(config.repeat.while) + .map((condition) => describeCondition(condition)) + .join(", ")} is true` + : "until" in config.repeat + ? `until ${ensureArray(config.repeat.until) + .map((condition) => describeCondition(condition)) + .join(", ")} is true` + : "for_each" in config.repeat + ? `for every item: ${ensureArray(config.repeat.for_each) + .map((item) => JSON.stringify(item)) + .join(", ")}` + : "" + }`; + } + + if (actionType === "check_condition") { + return `Test ${describeCondition(action as Condition)}`; + } + if (actionType === "device_action") { const config = action as DeviceAction; const stateObj = hass.states[config.entity_id as string]; @@ -170,7 +250,10 @@ export const describeAction = ( } if (actionType === "parallel") { - return "Run in parallel"; + const config = action as ParallelAction; + return `Run in parallel: ${ensureArray(config.parallel) + .map((pAction) => describeAction(hass, pAction)) + .join(", ")}`; } return actionType; From e51e3e79d51b93068c4e3e1c85f7751e80c84a6e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 May 2022 19:16:32 +0200 Subject: [PATCH 162/181] Add repeat to trace timeline (#12547) --- src/components/trace/hat-trace-timeline.ts | 64 +++++++++++++++++++--- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 291f188691..62895b3976 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -25,9 +25,11 @@ import { ChooseAction, ChooseActionChoice, getActionType, + RepeatAction, } from "../../data/script"; import { describeAction } from "../../data/script_i18n"; import { + ActionTraceStep, AutomationTraceExtended, ChooseActionTraceStep, getDataFromPath, @@ -105,7 +107,7 @@ class LogbookRenderer { } get hasNext() { - return this.curIndex !== this.logbookEntries.length; + return this.curIndex < this.logbookEntries.length; } maybeRenderItem() { @@ -201,7 +203,7 @@ class ActionRenderer { } get hasNext() { - return this.curIndex !== this.keys.length; + return this.curIndex < this.keys.length; } renderItem() { @@ -214,15 +216,31 @@ class ActionRenderer { private _renderItem( index: number, - actionType?: ReturnType + actionType?: ReturnType, + renderAllIterations?: boolean ): number { const value = this._getItem(index); - if (isTriggerPath(value[0].path)) { - return this._handleTrigger(index, value[0] as TriggerTraceStep); + if (renderAllIterations) { + let i; + value.forEach((item) => { + i = this._renderIteration(index, item, actionType); + }); + return i; + } + return this._renderIteration(index, value[0], actionType); + } + + private _renderIteration( + index: number, + value: ActionTraceStep, + actionType?: ReturnType + ) { + if (isTriggerPath(value.path)) { + return this._handleTrigger(index, value as TriggerTraceStep); } - const timestamp = new Date(value[0].timestamp); + const timestamp = new Date(value.timestamp); // Render all logbook items that are in front of this item. while ( @@ -235,7 +253,7 @@ class ActionRenderer { this.logbookRenderer.flush(); this.timeTracker.maybeRenderTime(timestamp); - const path = value[0].path; + const path = value.path; let data; try { data = getDataFromPath(this.trace.config, path); @@ -263,6 +281,10 @@ class ActionRenderer { return this._handleChoose(index); } + if (actionType === "repeat") { + return this._handleRepeat(index); + } + this._renderEntry(path, describeAction(this.hass, data, actionType)); let i = index + 1; @@ -374,6 +396,34 @@ class ActionRenderer { return i; } + private _handleRepeat(index: number): number { + const repeatPath = this.keys[index]; + const startLevel = repeatPath.split("/").length; + + const repeatConfig = this._getDataFromPath( + this.keys[index] + ) as RepeatAction; + const name = repeatConfig.alias || describeAction(this.hass, repeatConfig); + + this._renderEntry(repeatPath, name); + + let i; + + for (i = index + 1; i < this.keys.length; i++) { + const path = this.keys[i]; + const parts = path.split("/"); + + // We're done if no more sequence in current level + if (parts.length <= startLevel) { + return i; + } + + i = this._renderItem(i, getActionType(this._getDataFromPath(path)), true); + } + + return i; + } + private _renderEntry( path: string, description: string, From b2186592dfb022b79ab91776d0e199121af77335 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 2 May 2022 16:29:06 -0500 Subject: [PATCH 163/181] Change name to Settings (#12548) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 9e7565cfa1..257b9e6609 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2,7 +2,7 @@ "panel": { "energy": "Energy", "calendar": "Calendar", - "config": "Configuration", + "config": "Settings", "states": "Overview", "map": "Map", "logbook": "Logbook", From f0c72327041db251a42c791da442f79d93a04560 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 May 2022 23:47:17 +0200 Subject: [PATCH 164/181] Add trace timeline for if (#12543) --- src/components/trace/hat-trace-timeline.ts | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index 62895b3976..b8da75f381 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -25,6 +25,7 @@ import { ChooseAction, ChooseActionChoice, getActionType, + IfAction, RepeatAction, } from "../../data/script"; import { describeAction } from "../../data/script_i18n"; @@ -33,6 +34,7 @@ import { AutomationTraceExtended, ChooseActionTraceStep, getDataFromPath, + IfActionTraceStep, isTriggerPath, TriggerTraceStep, } from "../../data/trace"; @@ -285,6 +287,10 @@ class ActionRenderer { return this._handleRepeat(index); } + if (actionType === "if") { + return this._handleIf(index); + } + this._renderEntry(path, describeAction(this.hass, data, actionType)); let i = index + 1; @@ -424,6 +430,52 @@ class ActionRenderer { return i; } + private _handleIf(index: number): number { + const ifPath = this.keys[index]; + const startLevel = ifPath.split("/").length; + + const ifTrace = this._getItem(index)[0] as IfActionTraceStep; + const ifConfig = this._getDataFromPath(this.keys[index]) as IfAction; + const name = ifConfig.alias || "If"; + + if (ifTrace.result) { + const choiceConfig = this._getDataFromPath( + `${this.keys[index]}/${ifTrace.result.choice}/` + ) as any; + const choiceName = choiceConfig + ? `${choiceConfig.alias || `${ifTrace.result.choice} action executed`}` + : `Error: ${ifTrace.error}`; + this._renderEntry(ifPath, `${name}: ${choiceName}`); + } else { + this._renderEntry(ifPath, `${name}: No action taken`); + } + + let i; + + // Skip over conditions + for (i = index + 1; i < this.keys.length; i++) { + const path = this.keys[i]; + const parts = this.keys[i].split("/"); + + // We're done if no more sequence in current level + if (parts.length <= startLevel) { + return i; + } + + // We're going to skip all conditions + if ( + parts[startLevel + 1] === "condition" || + parts.length < startLevel + 2 + ) { + continue; + } + + i = this._renderItem(i, getActionType(this._getDataFromPath(path))); + } + + return i; + } + private _renderEntry( path: string, description: string, From e99143139e845581d8e6c3239b04a27268e74782 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 May 2022 23:47:43 +0200 Subject: [PATCH 165/181] Fix script graph parallel (#12545) --- src/components/trace/hat-script-graph.ts | 16 +++++++++++++++- src/data/script.ts | 2 +- src/data/trace.ts | 6 +++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index 45ea965b03..b43dd2749e 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -34,6 +34,7 @@ import { DeviceAction, EventAction, IfAction, + ManualScriptConfig, ParallelAction, RepeatAction, SceneAction, @@ -478,7 +479,20 @@ export class HatScriptGraph extends LitElement { nofocus > ${ensureArray(node.parallel).map((action, i) => - this.render_action_node(action, `${path}/parallel/${i}/0`) + "sequence" in action + ? html`

    + ${ensureArray((action as ManualScriptConfig).sequence).map( + (sAction, j) => + this.render_action_node( + sAction, + `${path}/parallel/${i}/sequence/${j}` + ) + )} +
    ` + : this.render_action_node( + action, + `${path}/parallel/${i}/sequence/0` + ) )} `; diff --git a/src/data/script.ts b/src/data/script.ts index ada068d25e..017a72cc88 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -214,7 +214,7 @@ export interface StopAction extends BaseAction { } export interface ParallelAction extends BaseAction { - parallel: Action | Action[]; + parallel: ManualScriptConfig | Action | (ManualScriptConfig | Action)[]; } interface UnknownAction extends BaseAction { diff --git a/src/data/trace.ts b/src/data/trace.ts index ae9a4b0738..600d6c9466 100644 --- a/src/data/trace.ts +++ b/src/data/trace.ts @@ -185,7 +185,11 @@ export const getDataFromPath = ( const asNumber = Number(raw); if (isNaN(asNumber)) { - result = result[raw]; + const tempResult = result[raw]; + if (!tempResult && raw === "sequence") { + continue; + } + result = tempResult; continue; } From 3a305a44b6f6e5a2cf58d16f9bfde97192b2e884 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 2 May 2022 23:48:28 +0200 Subject: [PATCH 166/181] Handle if in repeat (#12544) --- src/components/trace/hat-script-graph.ts | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index b43dd2749e..cab2f87c33 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -215,7 +215,19 @@ export class HatScriptGraph extends LitElement { private render_if_node(config: IfAction, path: string, graphStart = false) { const trace = this.trace.trace[path] as IfActionTraceStep[] | undefined; - const result = trace?.[0].result?.choice; + let trackThen = false; + let trackElse = false; + for (const trc of trace || []) { + if (!trackThen && trc.result?.choice === "then") { + trackThen = true; + } + if ((!trackElse && trc.result?.choice === "else") || !trc.result) { + trackElse = true; + } + if (trackElse && trackThen) { + break; + } + } return html` ${config.else - ? html`
    + ? html`
    ` - : html``} -
    + : html``} +
    From 43e80f1a2eee87983015c93636caac9c5ca24aa2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 3 May 2022 00:07:01 +0200 Subject: [PATCH 167/181] Add parallel action to trace timeline (#12549) --- src/components/trace/hat-trace-timeline.ts | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/components/trace/hat-trace-timeline.ts b/src/components/trace/hat-trace-timeline.ts index b8da75f381..0573549cd7 100644 --- a/src/components/trace/hat-trace-timeline.ts +++ b/src/components/trace/hat-trace-timeline.ts @@ -26,6 +26,7 @@ import { ChooseActionChoice, getActionType, IfAction, + ParallelAction, RepeatAction, } from "../../data/script"; import { describeAction } from "../../data/script_i18n"; @@ -291,6 +292,10 @@ class ActionRenderer { return this._handleIf(index); } + if (actionType === "parallel") { + return this._handleParallel(index); + } + this._renderEntry(path, describeAction(this.hass, data, actionType)); let i = index + 1; @@ -476,6 +481,35 @@ class ActionRenderer { return i; } + private _handleParallel(index: number): number { + const parallelPath = this.keys[index]; + const startLevel = parallelPath.split("/").length; + + const parallelConfig = this._getDataFromPath( + this.keys[index] + ) as ParallelAction; + + const name = parallelConfig.alias || "Execute in parallel"; + + this._renderEntry(parallelPath, name); + + let i; + + for (i = index + 1; i < this.keys.length; i++) { + const path = this.keys[i]; + const parts = path.split("/"); + + // We're done if no more sequence in current level + if (parts.length <= startLevel) { + return i; + } + + i = this._renderItem(i, getActionType(this._getDataFromPath(path))); + } + + return i; + } + private _renderEntry( path: string, description: string, From ba8621fa2c40d942e52b1358e4cec05a34e877a0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 3 May 2022 00:07:36 +0200 Subject: [PATCH 168/181] Indicate things are disabled in trace graph (#12550) * Indicate things are disabled in trace graph * Update hat-script-graph.ts --- src/components/trace/hat-graph-node.ts | 5 +- src/components/trace/hat-script-graph.ts | 129 +++++++++++++++++++---- 2 files changed, 111 insertions(+), 23 deletions(-) diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index 203419657b..1350479e3c 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -19,6 +19,8 @@ export class HatGraphNode extends LitElement { @property({ reflect: true, type: Boolean }) disabled?: boolean; + @property({ reflect: true, type: Boolean }) notEnabled = false; + @property({ reflect: true, type: Boolean }) graphStart?: boolean; @property({ type: Boolean, attribute: "nofocus" }) noFocus = false; @@ -114,7 +116,8 @@ export class HatGraphNode extends LitElement { --stroke-clr: var(--hover-clr); --icon-clr: var(--default-icon-clr); } - :host([disabled]) circle { + :host([notEnabled]) circle, + :host([notEnabled]) path.connector { stroke: var(--disabled-clr); } svg { diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index cab2f87c33..432e8a4f98 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -96,6 +96,7 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(config, path)} ?active=${this.selected === path} .iconPath=${mdiAsterisk} + .notEnabled=${config.enabled === false} tabindex=${track ? "0" : "-1"} > `; @@ -130,20 +131,31 @@ export class HatScriptGraph extends LitElement { other: this.render_other_node, }; - private render_action_node(node: Action, path: string, graphStart = false) { + private render_action_node( + node: Action, + path: string, + graphStart = false, + disabled = false + ) { const type = Object.keys(this.typeRenderers).find((key) => key in node) || "other"; this.renderedNodes[path] = { config: node, path }; if (this.trace && path in this.trace.trace) { this.trackedNodes[path] = this.renderedNodes[path]; } - return this.typeRenderers[type].bind(this)(node, path, graphStart); + return this.typeRenderers[type].bind(this)( + node, + path, + graphStart, + disabled + ); } private render_choose_node( config: ChooseAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { const trace = this.trace.trace[path] as ChooseActionTraceStep[] | undefined; const trace_path = trace @@ -160,12 +172,14 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(config, path)} ?track=${trace !== undefined} ?active=${this.selected === path} + .notEnabled=${disabled || config.enabled === false} > @@ -188,12 +202,15 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(config, branch_path)} ?track=${track_this} ?active=${this.selected === branch_path} + .notEnabled=${disabled || config.enabled === false} > ${branch.sequence !== null ? ensureArray(branch.sequence).map((action, j) => this.render_action_node( action, - `${branch_path}/sequence/${j}` + `${branch_path}/sequence/${j}`, + false, + disabled || config.enabled === false ) ) : ""} @@ -205,7 +222,12 @@ export class HatScriptGraph extends LitElement { ${config.default !== null ? ensureArray(config.default)?.map((action, i) => - this.render_action_node(action, `${path}/default/${i}`) + this.render_action_node( + action, + `${path}/default/${i}`, + false, + disabled || config.enabled === false + ) ) : ""}
    @@ -213,7 +235,12 @@ export class HatScriptGraph extends LitElement { `; } - private render_if_node(config: IfAction, path: string, graphStart = false) { + private render_if_node( + config: IfAction, + path: string, + graphStart = false, + disabled = false + ) { const trace = this.trace.trace[path] as IfActionTraceStep[] | undefined; let trackThen = false; let trackElse = false; @@ -234,12 +261,14 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(config, path)} ?track=${trace !== undefined} ?active=${this.selected === path} + .notEnabled=${disabled || config.enabled === false} > @@ -249,10 +278,16 @@ export class HatScriptGraph extends LitElement { .iconPath=${mdiCallMissed} ?track=${trackElse} ?active=${this.selected === path} + .notEnabled=${disabled || config.enabled === false} nofocus >${ensureArray(config.else).map((action, j) => - this.render_action_node(action, `${path}/else/${j}`) + this.render_action_node( + action, + `${path}/else/${j}`, + false, + disabled || config.enabled === false + ) )}
    ` : html``} @@ -261,10 +296,16 @@ export class HatScriptGraph extends LitElement { .iconPath=${mdiCallReceived} ?track=${trackThen} ?active=${this.selected === path} + .notEnabled=${disabled || config.enabled === false} nofocus > ${ensureArray(config.then).map((action, j) => - this.render_action_node(action, `${path}/then/${j}`) + this.render_action_node( + action, + `${path}/then/${j}`, + false, + disabled || config.enabled === false + ) )}
    @@ -274,7 +315,8 @@ export class HatScriptGraph extends LitElement { private render_condition_node( node: Condition, path: string, - graphStart = false + graphStart = false, + disabled = false ) { const trace = this.trace.trace[path] as ConditionTraceStep[] | undefined; let track = false; @@ -300,6 +342,7 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(node, path)} ?track=${track} ?active=${this.selected === path} + .notEnabled=${disabled || node.enabled === false} tabindex=${trace === undefined ? "-1" : "0"} short > @@ -308,6 +351,7 @@ export class HatScriptGraph extends LitElement { slot="head" ?track=${track} ?active=${this.selected === path} + .notEnabled=${disabled || node.enabled === false} .iconPath=${mdiAbTesting} nofocus > @@ -322,6 +366,7 @@ export class HatScriptGraph extends LitElement { nofocus ?track=${trackFailed} ?active=${this.selected === path} + .notEnabled=${disabled || node.enabled === false} > `; @@ -330,7 +375,8 @@ export class HatScriptGraph extends LitElement { private render_delay_node( node: DelayAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { return html` `; @@ -347,7 +394,8 @@ export class HatScriptGraph extends LitElement { private render_device_node( node: DeviceAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { return html` `; @@ -364,7 +413,8 @@ export class HatScriptGraph extends LitElement { private render_event_node( node: EventAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { return html` `; @@ -381,7 +432,8 @@ export class HatScriptGraph extends LitElement { private render_repeat_node( node: RepeatAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { const trace: any = this.trace.trace[path]; const repeats = this.trace?.trace[`${path}/repeat/sequence/0`]?.length; @@ -391,12 +443,14 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(node, path)} ?track=${path in this.trace.trace} ?active=${this.selected === path} + .notEnabled=${disabled || node.enabled === false} > @@ -404,12 +458,18 @@ export class HatScriptGraph extends LitElement { .iconPath=${mdiArrowUp} ?track=${repeats > 1} ?active=${this.selected === path} + .notEnabled=${disabled || node.enabled === false} nofocus .badge=${repeats > 1 ? repeats : undefined} >
    ${ensureArray(node.repeat.sequence).map((action, i) => - this.render_action_node(action, `${path}/repeat/sequence/${i}`) + this.render_action_node( + action, + `${path}/repeat/sequence/${i}`, + false, + disabled || node.enabled === false + ) )}
    @@ -419,7 +479,8 @@ export class HatScriptGraph extends LitElement { private render_scene_node( node: SceneAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { return html` `; @@ -436,7 +498,8 @@ export class HatScriptGraph extends LitElement { private render_service_node( node: ServiceAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { return html` `; @@ -453,7 +517,8 @@ export class HatScriptGraph extends LitElement { private render_wait_node( node: WaitAction | WaitForTriggerAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { return html` `; @@ -470,7 +536,8 @@ export class HatScriptGraph extends LitElement { private render_parallel_node( node: ParallelAction, path: string, - graphStart = false + graphStart = false, + disabled = false ) { const trace: any = this.trace.trace[path]; return html` @@ -479,12 +546,14 @@ export class HatScriptGraph extends LitElement { @focus=${this.selectNode(node, path)} ?track=${path in this.trace.trace} ?active=${this.selected === path} + .notEnabled=${disabled || node.enabled === false} > @@ -495,20 +564,29 @@ export class HatScriptGraph extends LitElement { (sAction, j) => this.render_action_node( sAction, - `${path}/parallel/${i}/sequence/${j}` + `${path}/parallel/${i}/sequence/${j}`, + false, + disabled || node.enabled === false ) )}
    ` : this.render_action_node( action, - `${path}/parallel/${i}/sequence/0` + `${path}/parallel/${i}/sequence/0`, + false, + disabled || node.enabled === false ) )}
    `; } - private render_stop_node(node: Action, path: string, graphStart = false) { + private render_stop_node( + node: Action, + path: string, + graphStart = false, + disabled = false + ) { const trace = this.trace.trace[path] as StopActionTraceStep[] | undefined; return html` `; } - private render_other_node(node: Action, path: string, graphStart = false) { + private render_other_node( + node: Action, + path: string, + graphStart = false, + disabled = false + ) { return html` `; } From 85a37e2d2fef700715b5f118f7cb811b7b1759a6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 2 May 2022 15:08:01 -0700 Subject: [PATCH 169/181] Bumped version to 20220502.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9a53d83aba..8cecd08e3b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220429.0 +version = 20220502.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 10aa0a8829b901e54e6ec45d5607b5b881caa5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Tue, 3 May 2022 13:13:20 +0200 Subject: [PATCH 170/181] Add add-on logs to log selector (#12556) --- src/data/hassio/supervisor.ts | 5 +++- src/panels/config/logs/ha-config-logs.ts | 29 ++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/data/hassio/supervisor.ts b/src/data/hassio/supervisor.ts index 55edd1cf9e..e08600098b 100644 --- a/src/data/hassio/supervisor.ts +++ b/src/data/hassio/supervisor.ts @@ -179,7 +179,10 @@ export const fetchHassioInfo = async ( }; export const fetchHassioLogs = async (hass: HomeAssistant, provider: string) => - hass.callApi("GET", `hassio/${provider}/logs`); + hass.callApi( + "GET", + `hassio/${provider.includes("_") ? `addons/${provider}` : provider}/logs` + ); export const setSupervisorOption = async ( hass: HomeAssistant, diff --git a/src/panels/config/logs/ha-config-logs.ts b/src/panels/config/logs/ha-config-logs.ts index 803cc7e3f0..1981a08a25 100644 --- a/src/panels/config/logs/ha-config-logs.ts +++ b/src/panels/config/logs/ha-config-logs.ts @@ -6,6 +6,7 @@ import { extractSearchParam } from "../../../common/url/search-params"; import "../../../components/ha-button-menu"; import "../../../components/search-input"; import { LogProvider } from "../../../data/error_log"; +import { fetchHassioSupervisorInfo } from "../../../data/hassio/supervisor"; import "../../../layouts/hass-subpage"; import "../../../layouts/hass-tabs-subpage"; import { haStyle } from "../../../resources/styles"; @@ -59,6 +60,8 @@ export class HaConfigLogs extends LitElement { @state() private _selectedLogProvider = "core"; + @state() private _logProviders = logProviders; + public connectedCallback() { super.connectedCallback(); if (this.systemLog && this.systemLog.loaded) { @@ -66,6 +69,13 @@ export class HaConfigLogs extends LitElement { } } + protected firstUpdated(changedProps): void { + super.firstUpdated(changedProps); + if (isComponentLoaded(this.hass, "hassio")) { + this._getInstalledAddons(); + } + } + private async _filterChanged(ev) { this._filter = ev.detail.value; } @@ -107,7 +117,7 @@ export class HaConfigLogs extends LitElement { p.key === this._selectedLogProvider )!.name} > @@ -116,7 +126,7 @@ export class HaConfigLogs extends LitElement { .path=${mdiChevronDown} > - ${logProviders.map( + ${this._logProviders.map( (provider) => html` ({ + key: addon.slug, + name: addon.name, + })), + ]; + } catch (err) { + // Ignore, nothing the user can do anyway + } + } + static get styles(): CSSResultGroup { return [ haStyle, From e8da203fe105b12039d8d85d00fc12f09af8f955 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 3 May 2022 06:17:02 -0500 Subject: [PATCH 171/181] Fix Webhook Overflow (#12551) --- src/panels/config/cloud/account/cloud-account.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts index b53e60449a..8f0f1857b8 100644 --- a/src/panels/config/cloud/account/cloud-account.ts +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -1,26 +1,28 @@ import "@material/mwc-button"; -import "@material/mwc-list/mwc-list-item"; import type { ActionDetail } from "@material/mwc-list"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; -import { LitElement, css, html, PropertyValues } from "lit"; +import "@polymer/paper-item/paper-item-body"; +import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { formatDateTime } from "../../../../common/datetime/format_date_time"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeRTLDirection } from "../../../../common/util/compute_rtl"; +import { debounce } from "../../../../common/util/debounce"; import "../../../../components/buttons/ha-call-api-button"; -import "../../../../components/ha-card"; import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; +import "../../../../components/ha-card"; import "../../../../components/ha-icon-button"; -import { debounce } from "../../../../common/util/debounce"; import { cloudLogout, CloudStatusLoggedIn, fetchCloudSubscriptionInfo, SubscriptionInfo, } from "../../../../data/cloud"; +import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import "../../../../layouts/hass-subpage"; +import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../../types"; import "../../ha-config-section"; import "./cloud-alexa-pref"; @@ -28,8 +30,6 @@ import "./cloud-google-pref"; import "./cloud-remote-pref"; import "./cloud-tts-pref"; import "./cloud-webhooks"; -import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; -import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; @customElement("cloud-account") export class CloudAccount extends SubscribeMixin(LitElement) { @@ -210,6 +210,7 @@ export class CloudAccount extends SubscribeMixin(LitElement) { From a5411f7ac4cf205241cc76aac39e9686cbfa876a Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 3 May 2022 06:17:47 -0500 Subject: [PATCH 172/181] Search in Overflow on Mobile (#12552) --- src/panels/lovelace/hui-root.ts | 34 +++++++++++++++++++++++++++++---- src/translations/en.json | 1 + 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 0f92dcec8e..83e2ebdfd6 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -266,10 +266,14 @@ class HUIRoot extends LitElement { ` : html`
    ${this.config.title}
    `} - + ${!this.narrow + ? html` + + ` + : ""} ${!this.narrow && this._conversation(this.hass.config.components) ? html` @@ -292,6 +296,28 @@ class HUIRoot extends LitElement { )} .path=${mdiDotsVertical} > + + ${this.narrow + ? html` + + ${this.hass!.localize( + "ui.panel.lovelace.menu.search" + )} + + + ` + : ""} ${this.narrow && this._conversation(this.hass.config.components) ? html` diff --git a/src/translations/en.json b/src/translations/en.json index 257b9e6609..f6c1e3d728 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3314,6 +3314,7 @@ "menu": { "configure_ui": "Edit Dashboard", "help": "Help", + "search": "Search", "start_conversation": "Start conversation", "reload_resources": "Reload resources", "exit_edit_mode": "Done", From 02e67d114692cf4458491226f6c5bfac6bce3cc4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 3 May 2022 13:22:48 +0200 Subject: [PATCH 173/181] Use ha-tip for yaml move tip (#12559) --- .../config/core/ha-config-system-navigation.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/panels/config/core/ha-config-system-navigation.ts b/src/panels/config/core/ha-config-system-navigation.ts index 3af90288dc..a71e398685 100644 --- a/src/panels/config/core/ha-config-system-navigation.ts +++ b/src/panels/config/core/ha-config-system-navigation.ts @@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { canShowPage } from "../../../common/config/can_show_page"; import "../../../components/ha-card"; +import "../../../components/ha-tip"; import "../../../components/ha-navigation-list"; import { CloudStatus } from "../../../data/cloud"; import { @@ -61,7 +62,12 @@ class HaConfigSystemNavigation extends LitElement { .pages=${pages} > -
    Looking for YAML Configuration? It has moved to Developer Tools
    + ${this.hass.userData?.showAdvanced + ? html` + Looking for YAML Configuration? It has moved to + Developer Tools + ` + : ""} `; @@ -137,10 +143,8 @@ class HaConfigSystemNavigation extends LitElement { --navigation-list-item-title-font-size: 16px; --navigation-list-item-padding: 4px; } - .yaml-config { - margin-bottom: max(env(safe-area-inset-bottom), 24px); - text-align: center; - font-style: italic; + ha-tip { + margin-bottom: max(env(safe-area-inset-bottom), 8px); } `, ]; From a35a380ec72254eed59cd5d8f6a4df462f470aba Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 3 May 2022 06:25:46 -0500 Subject: [PATCH 174/181] Update Quickbar Section Logic to include all (#12553) --- src/dialogs/quick-bar/ha-quick-bar.ts | 20 ++++++++------------ src/translations/en.json | 14 +++++++++++--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 8e7fbefd74..f78dc5e081 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -610,20 +610,14 @@ export class QuickBar extends LitElement { if (!canShowPage(this.hass, page)) { continue; } - if (!page.component) { - continue; - } + const info = this._getNavigationInfoFromConfig(page); if (!info) { continue; } // Add to list, but only if we do not already have an entry for the same path and component - if ( - items.some( - (e) => e.path === info.path && e.component === info.component - ) - ) { + if (items.some((e) => e.path === info.path)) { continue; } @@ -637,11 +631,13 @@ export class QuickBar extends LitElement { private _getNavigationInfoFromConfig( page: PageNavigation ): NavigationInfo | undefined { - if (!page.component) { - return undefined; - } + const path = page.path.substring(1); + + let name = path.substring(path.indexOf("/") + 1); + name = name.indexOf("/") > -1 ? name.substring(0, name.indexOf("/")) : name; + const caption = this.hass.localize( - `ui.dialogs.quick-bar.commands.navigation.${page.component}` + `ui.dialogs.quick-bar.commands.navigation.${name}` ); if (page.translationKey && caption) { diff --git a/src/translations/en.json b/src/translations/en.json index f6c1e3d728..96e7a8d8eb 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -676,18 +676,26 @@ "areas": "[%key:ui::panel::config::areas::caption%]", "scene": "[%key:ui::panel::config::scene::caption%]", "helpers": "[%key:ui::panel::config::helpers::caption%]", - "tag": "[%key:ui::panel::config::tag::caption%]", + "tags": "[%key:ui::panel::config::tag::caption%]", "person": "[%key:ui::panel::config::person::caption%]", "devices": "[%key:ui::panel::config::devices::caption%]", "entities": "[%key:ui::panel::config::entities::caption%]", "energy": "Energy Configuration", "lovelace": "[%key:ui::panel::config::lovelace::caption%]", - "core": "[%key:ui::panel::config::core::caption%]", "zone": "[%key:ui::panel::config::zone::caption%]", "users": "[%key:ui::panel::config::users::caption%]", "info": "[%key:ui::panel::config::info::caption%]", + "network": "[%key:ui::panel::config::network::caption%]", + "updates": "[%key:ui::panel::config::updates::caption%]", + "hardware": "[%key:ui::panel::config::hardware::caption%]", + "storage": "[%key:ui::panel::config::storage::caption%]", + "general": "[%key:ui::panel::config::core::caption%]", + "backups": "[%key:ui::panel::config::backup::caption%]", + "backup": "[%key:ui::panel::config::backup::caption%]", + "analytics": "[%key:ui::panel::config::analytics::caption%]", + "system_health": "[%key:ui::panel::config::system_health::caption%]", "blueprint": "[%key:ui::panel::config::blueprint::caption%]", - "server_control": "[%key:ui::panel::developer-tools::tabs::yaml::title%]" + "system": "[%key:ui::panel::config::dashboard::system::main%]" } }, "filter_placeholder": "Entity Filter", From 45c7e0eeeb66813188ac6507b734452f6439715a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 3 May 2022 13:44:55 +0200 Subject: [PATCH 175/181] Use outline for cards on config pages (#12558) --- hassio/src/addon-store/hassio-addon-repository.ts | 1 + hassio/src/addon-view/config/hassio-addon-audio.ts | 1 + hassio/src/addon-view/config/hassio-addon-config.ts | 2 +- hassio/src/addon-view/config/hassio-addon-network.ts | 1 + .../documentation/hassio-addon-documentation-tab.ts | 2 +- hassio/src/addon-view/info/hassio-addon-info.ts | 4 ++-- hassio/src/addon-view/log/hassio-addon-logs.ts | 2 +- hassio/src/dashboard/hassio-addons.ts | 8 ++++++-- src/panels/config/areas/ha-config-area-page.ts | 5 +++++ .../automation/action/ha-automation-action-row.ts | 2 +- .../config/automation/action/ha-automation-action.ts | 2 +- .../action/types/ha-automation-action-choose.ts | 2 +- .../config/automation/blueprint-automation-editor.ts | 3 ++- .../condition/ha-automation-condition-editor.ts | 1 - .../condition/ha-automation-condition-row.ts | 2 +- .../automation/condition/ha-automation-condition.ts | 2 +- src/panels/config/automation/dialog-new-automation.ts | 1 - src/panels/config/automation/ha-automation-editor.ts | 8 ++++---- .../config/automation/manual-automation-editor.ts | 2 +- .../automation/trigger/ha-automation-trigger-row.ts | 2 +- .../config/automation/trigger/ha-automation-trigger.ts | 2 +- src/panels/config/cloud/account/cloud-account.ts | 1 + src/panels/config/cloud/account/cloud-alexa-pref.ts | 1 + src/panels/config/cloud/account/cloud-google-pref.ts | 1 + src/panels/config/cloud/account/cloud-remote-pref.ts | 2 ++ src/panels/config/cloud/account/cloud-tts-pref.ts | 1 + src/panels/config/cloud/account/cloud-webhooks.ts | 1 + src/panels/config/cloud/alexa/cloud-alexa.ts | 2 +- .../cloud/forgot-password/cloud-forgot-password.ts | 1 + .../cloud/google-assistant/cloud-google-assistant.ts | 2 +- src/panels/config/cloud/login/cloud-login.ts | 3 ++- src/panels/config/cloud/register/cloud-register.ts | 1 + src/panels/config/core/ha-config-section-updates.ts | 2 +- .../devices/device-detail/ha-device-actions-card.ts | 1 - .../devices/device-detail/ha-device-automation-card.ts | 1 - .../devices/device-detail/ha-device-conditions-card.ts | 1 - .../devices/device-detail/ha-device-entities-card.ts | 4 ++-- .../devices/device-detail/ha-device-info-card.ts | 8 ++++++++ src/panels/config/devices/ha-config-device-page.ts | 6 +++--- .../energy/components/ha-energy-battery-settings.ts | 2 +- .../energy/components/ha-energy-device-settings.ts | 2 +- .../config/energy/components/ha-energy-gas-settings.ts | 2 +- .../energy/components/ha-energy-grid-settings.ts | 2 +- .../energy/components/ha-energy-solar-settings.ts | 2 +- src/panels/config/person/ha-config-person.ts | 4 ++-- src/panels/config/scene/ha-scene-editor.ts | 7 +++++-- src/panels/config/script/blueprint-script-editor.ts | 2 +- src/panels/config/script/ha-script-editor.ts | 10 +++++----- src/panels/config/tags/ha-config-tags.ts | 1 - src/panels/config/zone/ha-config-zone.ts | 3 +-- 50 files changed, 79 insertions(+), 52 deletions(-) diff --git a/hassio/src/addon-store/hassio-addon-repository.ts b/hassio/src/addon-store/hassio-addon-repository.ts index 43e9822df2..8d55dbcb8c 100644 --- a/hassio/src/addon-store/hassio-addon-repository.ts +++ b/hassio/src/addon-store/hassio-addon-repository.ts @@ -68,6 +68,7 @@ class HassioAddonRepositoryEl extends LitElement { ${addons.map( (addon) => html`
    diff --git a/hassio/src/addon-view/config/hassio-addon-config.ts b/hassio/src/addon-view/config/hassio-addon-config.ts index e99aa61aa4..196d345bba 100644 --- a/hassio/src/addon-view/config/hassio-addon-config.ts +++ b/hassio/src/addon-view/config/hassio-addon-config.ts @@ -162,7 +162,7 @@ class HassioAddonConfig extends LitElement { ); return html`

    ${this.addon.name}

    - +

    ${this.supervisor.localize("addon.configuration.options.header")} diff --git a/hassio/src/addon-view/config/hassio-addon-network.ts b/hassio/src/addon-view/config/hassio-addon-network.ts index 66c4853551..2d3b11c5ad 100644 --- a/hassio/src/addon-view/config/hassio-addon-network.ts +++ b/hassio/src/addon-view/config/hassio-addon-network.ts @@ -58,6 +58,7 @@ class HassioAddonNetwork extends LitElement { return html` - + ${this._error ? html`${this._error}` : ""} diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 47e6087e36..71a0a6f225 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -166,7 +166,7 @@ class HassioAddonInfo extends LitElement { ` : ""} - +
    ${!this.narrow ? this.addon.name : ""} @@ -649,7 +649,7 @@ class HassioAddonInfo extends LitElement { ${this.addon.long_description ? html` - +
    ${this.addon.name}

    - + ${this._error ? html`${this._error}` : ""} diff --git a/hassio/src/dashboard/hassio-addons.ts b/hassio/src/dashboard/hassio-addons.ts index 06741a9aa3..4e685ef361 100644 --- a/hassio/src/dashboard/hassio-addons.ts +++ b/hassio/src/dashboard/hassio-addons.ts @@ -26,7 +26,7 @@ class HassioAddons extends LitElement {
    ${!this.supervisor.supervisor.addons?.length ? html` - +