diff --git a/demo/src/stubs/hassio_supervisor.ts b/demo/src/stubs/hassio_supervisor.ts index 95c0d330d4..6b75b020b7 100644 --- a/demo/src/stubs/hassio_supervisor.ts +++ b/demo/src/stubs/hassio_supervisor.ts @@ -31,7 +31,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => { version_latest: "3.6.2", update_available: false, repository: "a0d7b954", - icon: true, + icon: false, logo: true, }, { diff --git a/gallery/public/api/hassio/addons/core_zwave_js/icon b/gallery/public/api/hassio/addons/core_zwave_js/icon new file mode 100644 index 0000000000..91ac9039d9 Binary files /dev/null and b/gallery/public/api/hassio/addons/core_zwave_js/icon differ diff --git a/gallery/src/pages/more-info/update.ts b/gallery/src/pages/more-info/update.ts index f23ae25780..ea531adc56 100644 --- a/gallery/src/pages/more-info/update.ts +++ b/gallery/src/pages/more-info/update.ts @@ -128,6 +128,11 @@ const ENTITIES = [ supported_features: base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES, }), + getEntity("update", "update19", "on", { + ...base_attributes, + friendly_name: "Update with auto update", + auto_update: true, + }), ]; @customElement("demo-more-info-update") diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index 5d0b71741f..718afc3869 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -32,13 +32,6 @@ interface AddonCheckboxItem extends CheckboxItem { const _computeFolders = (folders): CheckboxItem[] => { const list: CheckboxItem[] = []; - if (folders.includes("homeassistant")) { - list.push({ - slug: "homeassistant", - name: "Home Assistant configuration", - checked: false, - }); - } if (folders.includes("ssl")) { list.push({ slug: "ssl", name: "SSL", checked: false }); } @@ -100,7 +93,7 @@ export class SupervisorBackupContent extends LitElement { this.folders = _computeFolders( this.backup ? this.backup.folders - : ["homeassistant", "ssl", "share", "media", "addons/local"] + : ["ssl", "share", "media", "addons/local"] ); this.addons = _computeAddons( this.backup ? this.backup.addons : this.supervisor?.supervisor.addons diff --git a/setup.cfg b/setup.cfg index eee0210ac8..ba0f034718 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220330.0 +version = 20220401.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 diff --git a/src/components/device/ha-devices-picker.ts b/src/components/device/ha-devices-picker.ts index 9ca65e20a5..5b2e0245b9 100644 --- a/src/components/device/ha-devices-picker.ts +++ b/src/components/device/ha-devices-picker.ts @@ -68,7 +68,7 @@ class HaDevicesPicker extends LitElement { .excludeDomains=${this.excludeDomains} .includeDeviceClasses=${this.includeDeviceClasses} .label=${this.pickDeviceLabel} - .required=${this.required} + .required=${this.required && !currentDevices.length} @value-changed=${this._addDevice} > diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index 75002c51d4..f4c1a24d41 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -110,7 +110,7 @@ class HaEntitiesPickerLight extends LitElement { .includeUnitOfMeasurement=${this.includeUnitOfMeasurement} .entityFilter=${this._entityFilter} .label=${this.pickEntityLabel} - .required=${this.required} + .required=${this.required && !currentEntities.length} @value-changed=${this._addEntity} > diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index 1dd6a11cea..7a7abf13b1 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -13,9 +13,12 @@ import { HaComboBox } from "./ha-combo-box"; const rowRenderer: ComboBoxLitRenderer = ( item -) => html` +) => html` ${item.name} ${item.slug} + ${item.icon + ? html`` + : ""} `; @customElement("ha-addon-picker") diff --git a/src/components/ha-areas-picker.ts b/src/components/ha-areas-picker.ts index 24b4e4c2b0..adc8a76c7c 100644 --- a/src/components/ha-areas-picker.ts +++ b/src/components/ha-areas-picker.ts @@ -97,7 +97,7 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) { .entityFilter=${this.entityFilter} .disabled=${this.disabled} .placeholder=${this.placeholder} - .required=${this.required} + .required=${this.required && !currentAreas.length} @value-changed=${this._addArea} > diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index 9804a79004..2f79979e90 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -108,7 +108,7 @@ export class HaSelectSelector extends LitElement { .hass=${this.hass} .label=${this.label} .disabled=${this.disabled} - .required=${this.required} + .required=${this.required && !value.length} .value=${this._filter} .items=${options.filter((item) => !this.value?.includes(item.value))} @filter-changed=${this._filterChanged} diff --git a/src/data/update.ts b/src/data/update.ts index 7482412343..f6f06da0dc 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -2,6 +2,7 @@ import type { HassEntityAttributeBase, HassEntityBase, } from "home-assistant-js-websocket"; +import { BINARY_STATE_ON } from "../common/const"; import { supportsFeature } from "../common/entity/supports-feature"; import { HomeAssistant } from "../types"; @@ -12,6 +13,7 @@ export const UPDATE_SUPPORT_BACKUP = 8; export const UPDATE_SUPPORT_RELEASE_NOTES = 16; interface UpdateEntityAttributes extends HassEntityAttributeBase { + auto_update: boolean | null; current_version: string | null; in_progress: boolean | number; latest_version: string | null; @@ -30,9 +32,8 @@ export const updateUsesProgress = (entity: UpdateEntity): boolean => typeof entity.attributes.in_progress === "number"; export const updateCanInstall = (entity: UpdateEntity): boolean => - supportsFeature(entity, UPDATE_SUPPORT_INSTALL) && - entity.attributes.latest_version !== entity.attributes.current_version && - entity.attributes.latest_version !== entity.attributes.skipped_version; + entity.state === BINARY_STATE_ON && + supportsFeature(entity, UPDATE_SUPPORT_INSTALL); export const updateIsInstalling = (entity: UpdateEntity): boolean => updateUsesProgress(entity) || !!entity.attributes.in_progress; diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 1cc8653752..a1dc54f852 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -167,12 +167,18 @@ export interface ZwaveJSNodeMetadata { wakeup: string; reset: string; device_database_url: string; + comments: ZWaveJSNodeComment[]; } export interface ZWaveJSNodeConfigParams { [key: string]: ZWaveJSNodeConfigParam; } +export interface ZWaveJSNodeComment { + level: "info" | "warning" | "error"; + text: string; +} + export interface ZWaveJSNodeConfigParam { property: number; value: any; diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index 6f4b88926e..8d0a135728 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -96,6 +96,13 @@ class MoreInfoMediaPlayer extends LitElement { .path=${stateObj.attributes.is_volume_muted ? mdiVolumeOff : mdiVolumeHigh} + .label=${this.hass.localize( + `ui.card.media_player.${ + stateObj.attributes.is_volume_muted + ? "media_volume_unmute" + : "media_volume_mute" + }` + )} @click=${this._toggleMute} > ` @@ -105,11 +112,17 @@ class MoreInfoMediaPlayer extends LitElement { ` diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index 44d3d84840..8fe18779b2 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -130,14 +130,20 @@ class MoreInfoUpdate extends LitElement { : ""}
- - ${this.hass.localize("ui.dialogs.more_info_control.update.skip")} - + ${this.stateObj.attributes.auto_update + ? "" + : html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.update.skip" + )} + + `} ${supportsFeature(this.stateObj, UPDATE_SUPPORT_INSTALL) ? html` { + this._cloudStatus = cloudStatus; if (cloudStatus.logged_in) { - this._cloudStatus = cloudStatus; this._showCustomExternalUrl = this._externalUrlValue !== null; } }); diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 19e60da9ee..04209e5947 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -75,7 +75,7 @@ export class HaDeviceEntitiesCard extends LitElement { this._entityRows = []; this.entities.forEach((entry) => { - if (entry.disabled_by || entry.hidden_by) { + if (entry.disabled_by) { if (this._extDisabledEntityEntries) { hiddenEntities.push( this._extDisabledEntityEntries[entry.entity_id] || entry @@ -167,7 +167,11 @@ export class HaDeviceEntitiesCard extends LitElement { computeStateName(stateObj), this.deviceName.toLowerCase() ); - if (name) { + if (entry.hidden_by) { + config.name = `${ + name || computeStateName(stateObj) + } (${this.hass.localize("ui.panel.config.devices.entities.hidden")})`; + } else if (name) { config.name = name; } } 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 70e9d26390..bc84b9a78a 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 @@ -19,6 +19,7 @@ import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import { debounce } from "../../../../../common/util/debounce"; +import "../../../../../components/ha-alert"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-next"; import "../../../../../components/ha-select"; @@ -130,7 +131,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { >`; } - if (!this._config) { + if (!this._config || !this._nodeMetadata) { return html``; } @@ -178,20 +179,27 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {

- - ${this._config - ? html` - ${Object.entries(this._config).map( - ([id, item]) => html` - ${this._generateConfigBox(id, item)} - ` + ${this._nodeMetadata.comments.length > 0 + ? html` +
+ ${this._nodeMetadata.comments.map( + (comment) => html` + ${comment.text} + ` )} - ` - : ``} +
+ ` + : ``} + + ${Object.entries(this._config).map( + ([id, item]) => html` + ${this._generateConfigBox(id, item)} + ` + )} diff --git a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts index 5cc2ab0ee9..a169cbe565 100644 --- a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts @@ -13,6 +13,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name"; import "../../../components/ha-select"; import { UNAVAILABLE } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; +import type { InputSelectEntity } from "../../../data/input_select"; import { SelectEntity, setSelectOption } from "../../../data/select"; import { HomeAssistant } from "../../../types"; import { EntitiesCardEntityConfig } from "../cards/types"; @@ -106,9 +107,14 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { } private _selectedChanged(ev): void { - const stateObj = this.hass!.states[this._config!.entity]; + const stateObj = this.hass!.states[ + this._config!.entity + ] as InputSelectEntity; const option = ev.target.value; - if (option === stateObj.state) { + if ( + option === stateObj.state || + !stateObj.attributes.options.includes(option) + ) { return; } diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index c02fb74523..14015d2629 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -12,154 +12,172 @@ import "../../layouts/hass-error-screen"; import { HomeAssistant, Route } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; -const REDIRECTS: 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", - }, -}; +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", + }, + } as Redirects + )[path]); export type ParamType = "url" | "string"; @@ -180,12 +198,17 @@ class HaPanelMy extends LitElement { @state() public _error?: string; + private _redirect?: Redirect; + connectedCallback() { super.connectedCallback(); const path = this.route.path.substring(1); + const hasSupervisor = isComponentLoaded(this.hass, "hassio"); - if (path.startsWith("supervisor")) { - if (!isComponentLoaded(this.hass, "hassio")) { + this._redirect = getRedirect(path, hasSupervisor); + + if (path.startsWith("supervisor") && this._redirect === undefined) { + if (!hasSupervisor) { this._error = "no_supervisor"; return; } @@ -195,16 +218,14 @@ class HaPanelMy extends LitElement { return; } - const redirect = REDIRECTS[path]; - - if (!redirect) { + if (!this._redirect) { this._error = "not_supported"; return; } if ( - redirect.component && - !isComponentLoaded(this.hass, redirect.component) + this._redirect.component && + !isComponentLoaded(this.hass, this._redirect.component) ) { this._error = "no_component"; return; @@ -212,7 +233,7 @@ class HaPanelMy extends LitElement { let url: string; try { - url = this._createRedirectUrl(redirect); + url = this._createRedirectUrl(); } catch (err: any) { this._error = "url_error"; return; @@ -243,10 +264,16 @@ class HaPanelMy extends LitElement { this.hass.localize( "ui.panel.my.component_not_loaded", "integration", - domainToName( - this.hass.localize, - REDIRECTS[this.route.path.substr(1)].component! - ) + html` + ${domainToName(this.hass.localize, this._redirect!.component!)} + ` ) || "This redirect is not supported."; break; case "no_supervisor": @@ -269,18 +296,18 @@ class HaPanelMy extends LitElement { return html``; } - private _createRedirectUrl(redirect: Redirect): string { - const params = this._createRedirectParams(redirect); - return `${redirect.redirect}${params}`; + private _createRedirectUrl(): string { + const params = this._createRedirectParams(); + return `${this._redirect!.redirect}${params}`; } - private _createRedirectParams(redirect: Redirect): string { + private _createRedirectParams(): string { const params = extractSearchParamsObject(); - if (!redirect.params && !Object.keys(params).length) { + if (!this._redirect!.params && !Object.keys(params).length) { return ""; } const resultParams = {}; - Object.entries(redirect.params || {}).forEach(([key, type]) => { + Object.entries(this._redirect!.params || {}).forEach(([key, type]) => { if (!params[key] || !this._checkParamType(type, params[key])) { throw Error(); } diff --git a/src/translations/en.json b/src/translations/en.json index 86bc60cbfc..7a7dcc5032 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2463,7 +2463,8 @@ "add_entities_lovelace": "Add to dashboard", "none": "This device has no entities", "show_less": "Show less", - "hidden_entities": "+{count} {count, plural,\n one {entity}\n other {entities}\n} not shown" + "hidden_entities": "+{count} {count, plural,\n one {entity}\n other {entities}\n} not shown", + "hidden": "Hidden" }, "confirm_rename_entity_ids": "Do you also want to rename the entity IDs of your entities?", "confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, dashboards) that is currently using these entities! You will have to update them yourself to use the new entity IDs!", @@ -4049,7 +4050,8 @@ "update": "Update the historic statistic values from ''{metadata_unit}'' to ''{state_unit}''", "clear": "Delete all old statistic data for this entity" } - } + }, + "adjust_sum": "Adjust sum" } } }, diff --git a/yarn.lock b/yarn.lock index 7701804f4f..a055a2b6f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5915,9 +5915,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001259": - version: 1.0.30001261 - resolution: "caniuse-lite@npm:1.0.30001261" - checksum: d894662312ecbdd772f0a258c4a45cac93605247b127b25649052353e0b981abfd0b445f469650943b612adc236fd510ae61c1293f3e77c68af7411d1b66574a + version: 1.0.30001322 + resolution: "caniuse-lite@npm:1.0.30001322" + checksum: 48609d1808c69034a74ab6df9db8cffd847e12da6979e150f364cc8e2a4310fce1f2811382ca57b3b4111c0182f7c67edfde3cd4159c29537fc232596aecf48b languageName: node linkType: hard