From 901f736d5f1edbfd481ead75aae0faa61583adba Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 29 Oct 2024 09:16:02 +0100 Subject: [PATCH] Improve more info update release note display (#22502) * Fix ha-settings-row * Improve update more info and update available card * Set actions at the bottom on mobile * Use update instead of install * Improve markdown loaded --- .../update-available/update-available-card.ts | 131 ++++--- src/components/ha-markdown.ts | 5 + src/components/ha-settings-row.ts | 15 +- src/dialogs/more-info/const.ts | 3 + .../more-info/controls/more-info-update.ts | 344 +++++++++++------- src/dialogs/more-info/ha-more-info-info.ts | 5 +- src/translations/en.json | 1 + 7 files changed, 306 insertions(+), 198 deletions(-) diff --git a/hassio/src/update-available/update-available-card.ts b/hassio/src/update-available/update-available-card.ts index d2c22ac0e1..e00035b987 100644 --- a/hassio/src/update-available/update-available-card.ts +++ b/hassio/src/update-available/update-available-card.ts @@ -1,11 +1,10 @@ -import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement, - PropertyValues, nothing, + PropertyValues, } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -16,12 +15,12 @@ import "../../../src/components/ha-button-menu"; import "../../../src/components/ha-card"; import "../../../src/components/ha-checkbox"; import "../../../src/components/ha-faded"; -import "../../../src/components/ha-formfield"; import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-markdown"; import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-switch"; +import type { HaSwitch } from "../../../src/components/ha-switch"; import { fetchHassioAddonChangelog, fetchHassioAddonInfo, @@ -42,6 +41,7 @@ import { updateCore } from "../../../src/data/supervisor/core"; import { StoreAddon } from "../../../src/data/supervisor/store"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; +import { haStyle } from "../../../src/resources/styles"; import { HomeAssistant, Route } from "../../../src/types"; import { addonArchIsSupported, extractChangelog } from "../util/addon"; @@ -149,7 +149,7 @@ class UpdateAvailableCard extends LitElement { ` - : ""} + : nothing}

${this.supervisor.localize( @@ -164,15 +164,17 @@ class UpdateAvailableCard extends LitElement {

${["core", "addon"].includes(this._updateType) ? html` - - - +
+ + + ${this.supervisor.localize( + "update_available.create_backup" + )} + + + ` - : ""} + : nothing} ` : html` ${changelog - ? html` - - - ` - : ""} + ? html` + + + + + ` + : nothing} - + ${this.supervisor.localize("common.update")} ` - : ""} + : nothing} `; } @@ -242,9 +246,11 @@ class UpdateAvailableCard extends LitElement { if (this._updateType && !["core", "addon"].includes(this._updateType)) { return false; } - const checkbox = this.shadowRoot?.querySelector("ha-checkbox"); - if (checkbox) { - return checkbox.checked; + const createBackupSwitch = this.shadowRoot?.getElementById( + "create-backup" + ) as HaSwitch; + if (createBackupSwitch) { + return createBackupSwitch.checked; } return true; } @@ -397,41 +403,50 @@ class UpdateAvailableCard extends LitElement { } static get styles(): CSSResultGroup { - return css` - :host { - display: block; - } - ha-card { - margin: auto; - } - a { - text-decoration: none; - color: var(--primary-text-color); - } - ha-settings-row { - padding: 0; - } - .card-actions { - display: flex; - justify-content: space-between; - border-top: none; - padding: 0 8px 8px; - } + return [ + haStyle, + css` + :host { + display: block; + } + ha-card { + margin: auto; + } + a { + text-decoration: none; + color: var(--primary-text-color); + } + .card-actions { + display: flex; + justify-content: space-between; + } - ha-circular-progress { - display: block; - margin: 32px; - text-align: center; - } + ha-circular-progress { + display: block; + margin: 32px; + text-align: center; + } - .progress-text { - text-align: center; - } + .progress-text { + text-align: center; + } - ha-markdown { - padding-bottom: 8px; - } - `; + ha-markdown { + padding-bottom: 8px; + } + + ha-settings-row { + padding: 0; + margin-bottom: -16px; + } + + hr { + border-color: var(--divider-color); + border-bottom: none; + margin: 16px 0 0 0; + } + `, + ]; } } diff --git a/src/components/ha-markdown.ts b/src/components/ha-markdown.ts index d8f0ba2270..f66037776b 100644 --- a/src/components/ha-markdown.ts +++ b/src/components/ha-markdown.ts @@ -86,6 +86,11 @@ export class HaMarkdown extends LitElement { font-size: 1.5em; font-weight: bold; } + hr { + border-color: var(--divider-color); + border-bottom: none; + margin: 16px 0; + } `; } } diff --git a/src/components/ha-settings-row.ts b/src/components/ha-settings-row.ts index 4ba42277e3..722eae4705 100644 --- a/src/components/ha-settings-row.ts +++ b/src/components/ha-settings-row.ts @@ -42,14 +42,17 @@ export class HaSettingsRow extends LitElement { padding-bottom: 8px; padding-left: 0; padding-inline-start: 0; - padding-right: 16x; + padding-right: 16px; padding-inline-end: 16px; overflow: hidden; - display: var(--layout-vertical_-_display); - flex-direction: var(--layout-vertical_-_flex-direction); - justify-content: var(--layout-center-justified_-_justify-content); - flex: var(--layout-flex_-_flex); - flex-basis: var(--layout-flex_-_flex-basis); + display: var(--layout-vertical_-_display, flex); + flex-direction: var(--layout-vertical_-_flex-direction, column); + justify-content: var( + --layout-center-justified_-_justify-content, + center + ); + flex: var(--layout-flex_-_flex, 1); + flex-basis: var(--layout-flex_-_flex-basis, 0.000000001px); } .body[three-line] { min-height: var(--paper-item-body-three-line-min-height, 88px); diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts index e558910779..ef6ef2b28c 100644 --- a/src/dialogs/more-info/const.ts +++ b/src/dialogs/more-info/const.ts @@ -31,6 +31,9 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [ "valve", "water_heater", ]; +/** Domains with full height more info dialog */ +export const DOMAINS_FULL_HEIGHT_MORE_INFO = ["update"]; + /** Domains with separate more info dialog. */ export const DOMAINS_WITH_MORE_INFO = [ "alarm_control_panel", diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index 1e7199e2c9..6bea428f80 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -1,15 +1,18 @@ -import "@material/mwc-button/mwc-button"; import "@material/mwc-linear-progress/mwc-linear-progress"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { BINARY_STATE_OFF } from "../../../common/const"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-alert"; +import "../../../components/ha-button"; import "../../../components/ha-checkbox"; import "../../../components/ha-circular-progress"; import "../../../components/ha-faded"; import "../../../components/ha-formfield"; import "../../../components/ha-markdown"; +import "../../../components/ha-settings-row"; +import "../../../components/ha-switch"; +import type { HaSwitch } from "../../../components/ha-switch"; import { isUnavailableState } from "../../../data/entity"; import { UpdateEntity, @@ -30,6 +33,8 @@ class MoreInfoUpdate extends LitElement { @state() private _error?: string; + @state() private _markdownLoading = true; + protected render() { if ( !this.hass || @@ -45,137 +50,174 @@ class MoreInfoUpdate extends LitElement { this.stateObj.attributes.latest_version; return html` - ${this.stateObj.attributes.in_progress - ? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) && - this.stateObj.attributes.update_percentage !== null - ? html`` - : html`` - : ""} -

${this.stateObj.attributes.title}

- ${this._error - ? html`${this._error}` - : ""} -
-
- ${this.hass.formatEntityAttributeName( - this.stateObj, - "installed_version" - )} +
+ ${this.stateObj.attributes.in_progress + ? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) && + this.stateObj.attributes.update_percentage !== null + ? html`` + : html`` + : nothing} +

${this.stateObj.attributes.title}

+ ${this._error + ? html`${this._error}` + : nothing} +
+
+ ${this.hass.formatEntityAttributeName( + this.stateObj, + "installed_version" + )} +
+
+ ${this.stateObj.attributes.installed_version ?? + this.hass.localize("state.default.unavailable")} +
-
- ${this.stateObj.attributes.installed_version ?? - this.hass.localize("state.default.unavailable")} +
+
+ ${this.hass.formatEntityAttributeName( + this.stateObj, + "latest_version" + )} +
+
+ ${this.stateObj.attributes.latest_version ?? + this.hass.localize("state.default.unavailable")} +
-
-
-
- ${this.hass.formatEntityAttributeName( - this.stateObj, - "latest_version" - )} -
-
- ${this.stateObj.attributes.latest_version ?? - this.hass.localize("state.default.unavailable")} -
-
- ${this.stateObj.attributes.release_url - ? html`` - : ""} - ${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) && - !this._error - ? this._releaseNotes === undefined - ? html`
- + ${this.stateObj.attributes.release_url + ? html`` - : html`
- - - ` - : this.stateObj.attributes.release_summary - ? html`
- ` - : ""} - ${supportsFeature(this.stateObj, UpdateEntityFeature.BACKUP) - ? html`
- - - ` - : ""} -
- ${this.stateObj.state === BINARY_STATE_OFF && - this.stateObj.attributes.skipped_version + : nothing} + ${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) && + !this._error + ? this._releaseNotes === undefined + ? html` +
+ ${this._markdownLoading ? this._renderLoader() : nothing} + ` + : html` +
+ + ${this._markdownLoading ? this._renderLoader() : nothing} + ` + : this.stateObj.attributes.release_summary + ? html` +
+ + ${this._markdownLoading ? this._renderLoader() : nothing} + ` + : nothing} +
+ + `; + } + + private _renderLoader() { + return html` +
+
`; } protected firstUpdated(): void { if (supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES)) { - updateReleaseNotes(this.hass, this.stateObj!.entity_id) - .then((result) => { - this._releaseNotes = result; - }) - .catch((err) => { - this._error = err.message; - }); + this._fetchReleaseNotes(); + } + } + + private async _markdownLoaded() { + if (this._markdownLoading) { + this._markdownLoading = false; + } + } + + private async _fetchReleaseNotes() { + try { + this._releaseNotes = await updateReleaseNotes( + this.hass, + this.stateObj!.entity_id + ); + } catch (err: any) { + this._error = err.message; } } @@ -183,9 +225,11 @@ class MoreInfoUpdate extends LitElement { if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) { return null; } - const checkbox = this.shadowRoot?.querySelector("ha-checkbox"); - if (checkbox) { - return checkbox.checked; + const createBackupSwitch = this.shadowRoot?.getElementById( + "create-backup" + ) as HaSwitch; + if (createBackupSwitch) { + return createBackupSwitch.checked; } return true; } @@ -234,6 +278,12 @@ class MoreInfoUpdate extends LitElement { static get styles(): CSSResultGroup { return css` + :host { + display: flex; + flex-direction: column; + flex: 1; + justify-content: space-between; + } hr { border-color: var(--divider-color); border-bottom: none; @@ -248,26 +298,44 @@ class MoreInfoUpdate extends LitElement { flex-direction: row; justify-content: space-between; } - .actions { + + .footer { border-top: 1px solid var(--divider-color); background: var( --ha-dialog-surface-background, var(--mdc-theme-surface, #fff) ); - margin: 8px 0 0; - display: flex; - flex-wrap: wrap; - justify-content: center; position: sticky; bottom: 0; - padding: 12px 0; - margin-bottom: -24px; - z-index: 1; + margin: 0 -24px -24px -24px; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + overflow: hidden; + z-index: 10; } - .actions mwc-button { - margin: 0 4px 4px; + ha-settings-row { + width: 100%; + padding: 0 24px; + box-sizing: border-box; + margin-bottom: -16px; + margin-top: -4px; } + + .actions { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-end; + box-sizing: border-box; + padding: 12px; + z-index: 1; + gap: 8px; + } + a { color: var(--primary-color); } @@ -282,6 +350,16 @@ class MoreInfoUpdate extends LitElement { } ha-markdown { direction: ltr; + padding-bottom: 16px; + box-sizing: border-box; + } + ha-markdown.hidden { + display: none; + } + .loader { + height: 80px; + box-sizing: border-box; + padding-bottom: 16px; } `; } diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts index 99e8a95448..95394b0206 100644 --- a/src/dialogs/more-info/ha-more-info-info.ts +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -9,6 +9,7 @@ import { computeShowHistoryComponent, computeShowLogBookComponent, computeShowNewMoreInfo, + DOMAINS_FULL_HEIGHT_MORE_INFO, DOMAINS_NO_INFO, DOMAINS_WITH_MORE_INFO, } from "./const"; @@ -40,6 +41,8 @@ export class MoreInfoInfo extends LitElement { const entityRegObj = this.hass.entities[entityId]; const domain = computeDomain(entityId); const isNewMoreInfo = stateObj && computeShowNewMoreInfo(stateObj); + const isFullHeight = + isNewMoreInfo || DOMAINS_FULL_HEIGHT_MORE_INFO.includes(domain); return html`
@@ -89,7 +92,7 @@ export class MoreInfoInfo extends LitElement { .entityId=${this.entityId} >`}