diff --git a/src/common/const.ts b/src/common/const.ts index 12f3a6eb74ab..66c14e64e140 100644 --- a/src/common/const.ts +++ b/src/common/const.ts @@ -203,6 +203,7 @@ export const DOMAINS_WITH_CARD = [ "select", "timer", "text", + "update", "vacuum", "water_heater", ]; diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 2216a65ec354..70a41a589371 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -2,10 +2,6 @@ import { HassConfig, HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { EntityRegistryDisplayEntry } from "../../data/entity_registry"; import { FrontendLocaleData, TimeZone } from "../../data/translation"; -import { - UPDATE_SUPPORT_PROGRESS, - updateIsInstallingFromAttributes, -} from "../../data/update"; import { HomeAssistant } from "../../types"; import { UNIT_TO_MILLISECOND_CONVERT, @@ -19,10 +15,9 @@ import { getNumberFormatOptions, isNumericFromAttributes, } from "../number/format_number"; +import { blankBeforeUnit } from "../translations/blank_before_unit"; import { LocalizeFunc } from "../translations/localize"; import { computeDomain } from "./compute_domain"; -import { supportsFeatureFromAttributes } from "./supports-feature"; -import { blankBeforeUnit } from "../translations/blank_before_unit"; export const computeStateDisplaySingleEntity = ( localize: LocalizeFunc, @@ -208,27 +203,6 @@ export const computeStateDisplayFromEntityAttributes = ( } } - if (domain === "update") { - // When updating, and entity does not support % show "Installing" - // When updating, and entity does support % show "Installing (xx%)" - // When update available, show the version - // When the latest version is skipped, show the latest version - // When update is not available, show "Up-to-date" - // When update is not available and there is no latest_version show "Unavailable" - return state === "on" - ? updateIsInstallingFromAttributes(attributes) - ? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) && - typeof attributes.in_progress === "number" - ? localize("ui.card.update.installing_with_progress", { - progress: attributes.in_progress, - }) - : localize("ui.card.update.installing") - : attributes.latest_version - : attributes.skipped_version === attributes.latest_version - ? attributes.latest_version ?? localize("state.default.unavailable") - : localize("ui.card.update.up_to_date"); - } - return ( (entity?.translation_key && localize( diff --git a/src/data/update.ts b/src/data/update.ts index 9392ad0162dc..ba9df3a555d8 100644 --- a/src/data/update.ts +++ b/src/data/update.ts @@ -7,10 +7,7 @@ import type { import { BINARY_STATE_ON } from "../common/const"; import { computeDomain } from "../common/entity/compute_domain"; import { computeStateDomain } from "../common/entity/compute_state_domain"; -import { - supportsFeature, - supportsFeatureFromAttributes, -} from "../common/entity/supports-feature"; +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"; @@ -38,13 +35,8 @@ export interface UpdateEntity extends HassEntityBase { } export const updateUsesProgress = (entity: UpdateEntity): boolean => - updateUsesProgressFromAttributes(entity.attributes); - -export const updateUsesProgressFromAttributes = (attributes: { - [key: string]: any; -}): boolean => - supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) && - typeof attributes.in_progress === "number"; + supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) && + typeof entity.attributes.in_progress === "number"; export const updateCanInstall = ( entity: UpdateEntity, @@ -57,11 +49,6 @@ export const updateCanInstall = ( export const updateIsInstalling = (entity: UpdateEntity): boolean => updateUsesProgress(entity) || !!entity.attributes.in_progress; -export const updateIsInstallingFromAttributes = (attributes: { - [key: string]: any; -}): boolean => - updateUsesProgressFromAttributes(attributes) || !!attributes.in_progress; - export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) => hass.callWS({ type: "update/release_notes", @@ -162,3 +149,47 @@ export const checkForEntityUpdates = async ( }); } }; + +// When updating, and entity does not support % show "Installing" +// When updating, and entity does support % show "Installing (xx%)" +// When update available, show the version +// When the latest version is skipped, show the latest version +// When update is not available, show "Up-to-date" +// When update is not available and there is no latest_version show "Unavailable" +export const computeUpdateStateDisplay = ( + stateObj: UpdateEntity, + hass: HomeAssistant +): string => { + const state = stateObj.state; + const attributes = stateObj.attributes; + + if (state === "off") { + const isSkipped = + attributes.latest_version && + attributes.skipped_version === attributes.latest_version; + if (isSkipped) { + return attributes.latest_version!; + } + return hass.formatEntityState(stateObj); + } + + if (state === "on") { + if (updateIsInstalling(stateObj)) { + const supportsProgress = + supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS) && + typeof typeof attributes.in_progress === "number"; + if (supportsProgress) { + return hass.localize("ui.card.update.installing_with_progress", { + progress: attributes.in_progress, + }); + } + return hass.localize("ui.card.update.installing"); + } + + if (attributes.latest_version) { + return attributes.latest_version; + } + } + + return hass.formatEntityState(stateObj); +}; diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index bf9b563a81e5..83bf90945ae0 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -63,8 +63,9 @@ class MoreInfoUpdate extends LitElement { : ""}
- ${this.hass.localize( - "ui.dialogs.more_info_control.update.installed_version" + ${this.hass.formatEntityAttributeName( + this.stateObj, + "installed_version" )}
@@ -74,8 +75,9 @@ class MoreInfoUpdate extends LitElement {
- ${this.hass.localize( - "ui.dialogs.more_info_control.update.latest_version" + ${this.hass.formatEntityAttributeName( + this.stateObj, + "latest_version" )}
diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 6b9465360f88..1b613333225f 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -47,6 +47,7 @@ import "../tile-features/hui-tile-features"; import type { LovelaceCard, LovelaceCardEditor } from "../types"; import { computeTileBadge } from "./tile/badges/tile-badge"; import type { ThermostatCardConfig, TileCardConfig } from "./types"; +import { UpdateEntity, computeUpdateStateDisplay } from "../../../data/update"; const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"]; @@ -260,6 +261,13 @@ export class HuiTileCard extends LitElement implements LovelaceCard { ]); } + if (domain === "update") { + return html`${computeUpdateStateDisplay( + stateObj as UpdateEntity, + this.hass! + )}`; + } + return this._renderStateContent(stateObj, "state"); } diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index 3c79dc1bf897..ed39e06c93e6 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -48,6 +48,7 @@ const LAZY_LOAD_TYPES = { "text-entity": () => import("../entity-rows/hui-text-entity-row"), "time-entity": () => import("../entity-rows/hui-time-entity-row"), "timer-entity": () => import("../entity-rows/hui-timer-entity-row"), + "update-entity": () => import("../entity-rows/hui-update-entity-row"), conditional: () => import("../special-rows/hui-conditional-row"), "weather-entity": () => import("../entity-rows/hui-weather-entity-row"), divider: () => import("../special-rows/hui-divider-row"), @@ -73,6 +74,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { humidifier: "humidifier", input_boolean: "toggle", input_button: "input-button", + input_datetime: "input-datetime", input_number: "input-number", input_select: "input-select", input_text: "input-text", @@ -90,11 +92,11 @@ const DOMAIN_TO_ELEMENT_TYPE = { text: "text", time: "time", timer: "timer", + update: "update", vacuum: "toggle", // Temporary. Once climate is rewritten, // water heater should get its own row. water_heater: "climate", - input_datetime: "input-datetime", weather: "weather", }; diff --git a/src/panels/lovelace/entity-rows/hui-update-entity-row.ts b/src/panels/lovelace/entity-rows/hui-update-entity-row.ts new file mode 100644 index 000000000000..6a8b8d81ee9f --- /dev/null +++ b/src/panels/lovelace/entity-rows/hui-update-entity-row.ts @@ -0,0 +1,75 @@ +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { UpdateEntity, computeUpdateStateDisplay } from "../../../data/update"; +import { HomeAssistant } from "../../../types"; +import { EntitiesCardEntityConfig } from "../cards/types"; +import { hasConfigOrEntityChanged } from "../common/has-changed"; +import "../components/hui-generic-entity-row"; +import { createEntityNotFoundWarning } from "../components/hui-warning"; +import { LovelaceRow } from "./types"; + +@customElement("hui-update-entity-row") +class HuiUpdateEntityRow extends LitElement implements LovelaceRow { + @property({ attribute: false }) public hass?: HomeAssistant; + + @state() private _config?: EntitiesCardEntityConfig; + + public setConfig(config: EntitiesCardEntityConfig): void { + if (!config) { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + protected shouldUpdate(changedProps: PropertyValues): boolean { + return hasConfigOrEntityChanged(this, changedProps); + } + + protected render() { + if (!this._config || !this.hass) { + return nothing; + } + + const stateObj = this.hass.states[this._config.entity] as + | UpdateEntity + | undefined; + + if (!stateObj) { + return html` + + ${createEntityNotFoundWarning(this.hass, this._config.entity)} + + `; + } + + return html` + + ${computeUpdateStateDisplay(stateObj, this.hass)} + + `; + } + + static get styles(): CSSResultGroup { + return css` + div { + text-align: right; + } + .pointer { + cursor: pointer; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-update-entity-row": HuiUpdateEntityRow; + } +} diff --git a/src/state-summary/state-card-content.js b/src/state-summary/state-card-content.js index c4d2e1820654..a0f3b450d6b9 100644 --- a/src/state-summary/state-card-content.js +++ b/src/state-summary/state-card-content.js @@ -24,6 +24,7 @@ import "./state-card-select"; import "./state-card-text"; import "./state-card-timer"; import "./state-card-toggle"; +import "./state-card-update"; import "./state-card-vacuum"; import "./state-card-water_heater"; diff --git a/src/state-summary/state-card-update.ts b/src/state-summary/state-card-update.ts new file mode 100755 index 000000000000..fc307cb37227 --- /dev/null +++ b/src/state-summary/state-card-update.ts @@ -0,0 +1,56 @@ +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../components/entity/state-info"; +import { computeUpdateStateDisplay, UpdateEntity } from "../data/update"; +import { haStyle } from "../resources/styles"; +import type { HomeAssistant } from "../types"; + +@customElement("state-card-update") +export class StateCardUpdate extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj!: UpdateEntity; + + @property({ type: Boolean }) public inDialog = false; + + protected render(): TemplateResult { + return html` +
+ + +
+ ${computeUpdateStateDisplay(this.stateObj, this.hass)} +
+
+ `; + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + css` + state-info { + flex: 0 1 fit-content; + min-width: 120px; + } + .state { + color: var(--primary-text-color); + margin-inline-start: 16px; + margin-inline-end: initial; + text-align: var(--float-end, right); + min-width: 50px; + flex: 0 1 fit-content; + word-break: break-word; + display: flex; + align-items: center; + direction: ltr; + justify-content: flex-end; + } + `, + ]; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index aded614d0273..a13b970ba8fd 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -936,8 +936,6 @@ "setting": "Setting" }, "update": { - "installed_version": "Installed version", - "latest_version": "Latest version", "release_announcement": "Read release announcement", "skip": "Skip", "clear_skipped": "Clear skipped",