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
This commit is contained in:
Paul Bottein 2024-10-29 09:16:02 +01:00 committed by GitHub
parent e55f32ae91
commit 901f736d5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 306 additions and 198 deletions

View File

@ -1,11 +1,10 @@
import "@material/mwc-list/mwc-list-item";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
html, html,
LitElement, LitElement,
PropertyValues,
nothing, nothing,
PropertyValues,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -16,12 +15,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-checkbox"; import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-faded"; import "../../../src/components/ha-faded";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-icon-button";
import "../../../src/components/ha-markdown"; import "../../../src/components/ha-markdown";
import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
import type { HaSwitch } from "../../../src/components/ha-switch";
import { import {
fetchHassioAddonChangelog, fetchHassioAddonChangelog,
fetchHassioAddonInfo, fetchHassioAddonInfo,
@ -42,6 +41,7 @@ import { updateCore } from "../../../src/data/supervisor/core";
import { StoreAddon } from "../../../src/data/supervisor/store"; import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { addonArchIsSupported, extractChangelog } from "../util/addon"; import { addonArchIsSupported, extractChangelog } from "../util/addon";
@ -149,7 +149,7 @@ class UpdateAvailableCard extends LitElement {
</ha-markdown> </ha-markdown>
</ha-faded> </ha-faded>
` `
: ""} : nothing}
<div class="versions"> <div class="versions">
<p> <p>
${this.supervisor.localize( ${this.supervisor.localize(
@ -164,15 +164,17 @@ class UpdateAvailableCard extends LitElement {
</div> </div>
${["core", "addon"].includes(this._updateType) ${["core", "addon"].includes(this._updateType)
? html` ? html`
<ha-formfield <hr />
.label=${this.supervisor.localize( <ha-settings-row>
<span slot="heading">
${this.supervisor.localize(
"update_available.create_backup" "update_available.create_backup"
)} )}
> </span>
<ha-checkbox checked></ha-checkbox> <ha-switch id="create_backup" checked></ha-switch>
</ha-formfield> </ha-settings-row>
` `
: ""} : nothing}
` `
: html`<ha-circular-progress : html`<ha-circular-progress
aria-label="Updating" aria-label="Updating"
@ -191,22 +193,24 @@ class UpdateAvailableCard extends LitElement {
? html` ? html`
<div class="card-actions"> <div class="card-actions">
${changelog ${changelog
? html`<a .href=${changelog} target="_blank" rel="noreferrer"> ? html`
<mwc-button <a href=${changelog} target="_blank" rel="noreferrer">
<ha-button
.label=${this.supervisor.localize( .label=${this.supervisor.localize(
"update_available.open_release_notes" "update_available.open_release_notes"
)} )}
> >
</mwc-button> </ha-button>
</a>` </a>
: ""} `
: nothing}
<span></span> <span></span>
<ha-progress-button @click=${this._update} raised> <ha-progress-button @click=${this._update}>
${this.supervisor.localize("common.update")} ${this.supervisor.localize("common.update")}
</ha-progress-button> </ha-progress-button>
</div> </div>
` `
: ""} : nothing}
</ha-card> </ha-card>
`; `;
} }
@ -242,9 +246,11 @@ class UpdateAvailableCard extends LitElement {
if (this._updateType && !["core", "addon"].includes(this._updateType)) { if (this._updateType && !["core", "addon"].includes(this._updateType)) {
return false; return false;
} }
const checkbox = this.shadowRoot?.querySelector("ha-checkbox"); const createBackupSwitch = this.shadowRoot?.getElementById(
if (checkbox) { "create-backup"
return checkbox.checked; ) as HaSwitch;
if (createBackupSwitch) {
return createBackupSwitch.checked;
} }
return true; return true;
} }
@ -397,7 +403,9 @@ class UpdateAvailableCard extends LitElement {
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return [
haStyle,
css`
:host { :host {
display: block; display: block;
} }
@ -408,14 +416,9 @@ class UpdateAvailableCard extends LitElement {
text-decoration: none; text-decoration: none;
color: var(--primary-text-color); color: var(--primary-text-color);
} }
ha-settings-row {
padding: 0;
}
.card-actions { .card-actions {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
border-top: none;
padding: 0 8px 8px;
} }
ha-circular-progress { ha-circular-progress {
@ -431,7 +434,19 @@ class UpdateAvailableCard extends LitElement {
ha-markdown { ha-markdown {
padding-bottom: 8px; 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;
}
`,
];
} }
} }

View File

@ -86,6 +86,11 @@ export class HaMarkdown extends LitElement {
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
} }
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
}
`; `;
} }
} }

View File

@ -42,14 +42,17 @@ export class HaSettingsRow extends LitElement {
padding-bottom: 8px; padding-bottom: 8px;
padding-left: 0; padding-left: 0;
padding-inline-start: 0; padding-inline-start: 0;
padding-right: 16x; padding-right: 16px;
padding-inline-end: 16px; padding-inline-end: 16px;
overflow: hidden; overflow: hidden;
display: var(--layout-vertical_-_display); display: var(--layout-vertical_-_display, flex);
flex-direction: var(--layout-vertical_-_flex-direction); flex-direction: var(--layout-vertical_-_flex-direction, column);
justify-content: var(--layout-center-justified_-_justify-content); justify-content: var(
flex: var(--layout-flex_-_flex); --layout-center-justified_-_justify-content,
flex-basis: var(--layout-flex_-_flex-basis); center
);
flex: var(--layout-flex_-_flex, 1);
flex-basis: var(--layout-flex_-_flex-basis, 0.000000001px);
} }
.body[three-line] { .body[three-line] {
min-height: var(--paper-item-body-three-line-min-height, 88px); min-height: var(--paper-item-body-three-line-min-height, 88px);

View File

@ -31,6 +31,9 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
"valve", "valve",
"water_heater", "water_heater",
]; ];
/** Domains with full height more info dialog */
export const DOMAINS_FULL_HEIGHT_MORE_INFO = ["update"];
/** Domains with separate more info dialog. */ /** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [ export const DOMAINS_WITH_MORE_INFO = [
"alarm_control_panel", "alarm_control_panel",

View File

@ -1,15 +1,18 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-linear-progress/mwc-linear-progress"; import "@material/mwc-linear-progress/mwc-linear-progress";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { BINARY_STATE_OFF } from "../../../common/const"; import { BINARY_STATE_OFF } from "../../../common/const";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-alert"; import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-checkbox"; import "../../../components/ha-checkbox";
import "../../../components/ha-circular-progress"; import "../../../components/ha-circular-progress";
import "../../../components/ha-faded"; import "../../../components/ha-faded";
import "../../../components/ha-formfield"; import "../../../components/ha-formfield";
import "../../../components/ha-markdown"; 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 { isUnavailableState } from "../../../data/entity";
import { import {
UpdateEntity, UpdateEntity,
@ -30,6 +33,8 @@ class MoreInfoUpdate extends LitElement {
@state() private _error?: string; @state() private _error?: string;
@state() private _markdownLoading = true;
protected render() { protected render() {
if ( if (
!this.hass || !this.hass ||
@ -45,6 +50,7 @@ class MoreInfoUpdate extends LitElement {
this.stateObj.attributes.latest_version; this.stateObj.attributes.latest_version;
return html` return html`
<div class="content">
${this.stateObj.attributes.in_progress ${this.stateObj.attributes.in_progress
? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) && ? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
this.stateObj.attributes.update_percentage !== null this.stateObj.attributes.update_percentage !== null
@ -53,11 +59,11 @@ class MoreInfoUpdate extends LitElement {
buffer="" buffer=""
></mwc-linear-progress>` ></mwc-linear-progress>`
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>` : html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
: ""} : nothing}
<h3>${this.stateObj.attributes.title}</h3> <h3>${this.stateObj.attributes.title}</h3>
${this._error ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>` ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""} : nothing}
<div class="row"> <div class="row">
<div class="key"> <div class="key">
${this.hass.formatEntityAttributeName( ${this.hass.formatEntityAttributeName(
@ -97,48 +103,64 @@ class MoreInfoUpdate extends LitElement {
</a> </a>
</div> </div>
</div>` </div>`
: ""} : nothing}
${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) && ${supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) &&
!this._error !this._error
? this._releaseNotes === undefined ? this._releaseNotes === undefined
? html`<div class="flex center"> ? html`
<ha-circular-progress indeterminate></ha-circular-progress> <hr />
</div>` ${this._markdownLoading ? this._renderLoader() : nothing}
: html`<hr /> `
<ha-faded> : html`
<ha-markdown .content=${this._releaseNotes}></ha-markdown> <hr />
</ha-faded> `
: this.stateObj.attributes.release_summary
? html`<hr />
<ha-markdown <ha-markdown
@content-resize=${this._markdownLoaded}
.content=${this._releaseNotes}
class=${this._markdownLoading ? "hidden" : ""}
></ha-markdown>
${this._markdownLoading ? this._renderLoader() : nothing}
`
: this.stateObj.attributes.release_summary
? html`
<hr />
<ha-markdown
@content-resize=${this._markdownLoaded}
.content=${this.stateObj.attributes.release_summary} .content=${this.stateObj.attributes.release_summary}
></ha-markdown>` class=${this._markdownLoading ? "hidden" : ""}
: ""} ></ha-markdown>
${this._markdownLoading ? this._renderLoader() : nothing}
`
: nothing}
</div>
<div class="footer">
${supportsFeature(this.stateObj, UpdateEntityFeature.BACKUP) ${supportsFeature(this.stateObj, UpdateEntityFeature.BACKUP)
? html`<hr /> ? html`
<ha-formfield <ha-settings-row>
.label=${this.hass.localize( <span slot="heading">
${this.hass.localize(
"ui.dialogs.more_info_control.update.create_backup" "ui.dialogs.more_info_control.update.create_backup"
)} )}
> </span>
<ha-checkbox <ha-switch
id="create_backup"
checked checked
.disabled=${updateIsInstalling(this.stateObj)} .disabled=${updateIsInstalling(this.stateObj)}
></ha-checkbox> ></ha-switch>
</ha-formfield> ` </ha-settings-row>
: ""} `
: nothing}
<div class="actions"> <div class="actions">
${this.stateObj.state === BINARY_STATE_OFF && ${this.stateObj.state === BINARY_STATE_OFF &&
this.stateObj.attributes.skipped_version this.stateObj.attributes.skipped_version
? html` ? html`
<mwc-button @click=${this._handleClearSkipped}> <ha-button @click=${this._handleClearSkipped}>
${this.hass.localize( ${this.hass.localize(
"ui.dialogs.more_info_control.update.clear_skipped" "ui.dialogs.more_info_control.update.clear_skipped"
)} )}
</mwc-button> </ha-button>
` `
: html` : html`
<mwc-button <ha-button
@click=${this._handleSkip} @click=${this._handleSkip}
.disabled=${skippedVersion || .disabled=${skippedVersion ||
this.stateObj.state === BINARY_STATE_OFF || this.stateObj.state === BINARY_STATE_OFF ||
@ -147,35 +169,55 @@ class MoreInfoUpdate extends LitElement {
${this.hass.localize( ${this.hass.localize(
"ui.dialogs.more_info_control.update.skip" "ui.dialogs.more_info_control.update.skip"
)} )}
</mwc-button> </ha-button>
`} `}
${supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL) ${supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL)
? html` ? html`
<mwc-button <ha-button
@click=${this._handleInstall} @click=${this._handleInstall}
.disabled=${(this.stateObj.state === BINARY_STATE_OFF && .disabled=${(this.stateObj.state === BINARY_STATE_OFF &&
!skippedVersion) || !skippedVersion) ||
updateIsInstalling(this.stateObj)} updateIsInstalling(this.stateObj)}
> >
${this.hass.localize( ${this.hass.localize(
"ui.dialogs.more_info_control.update.install" "ui.dialogs.more_info_control.update.update"
)} )}
</mwc-button> </ha-button>
` `
: ""} : nothing}
</div>
</div>
`;
}
private _renderLoader() {
return html`
<div class="flex center loader">
<ha-circular-progress indeterminate></ha-circular-progress>
</div> </div>
`; `;
} }
protected firstUpdated(): void { protected firstUpdated(): void {
if (supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES)) { if (supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES)) {
updateReleaseNotes(this.hass, this.stateObj!.entity_id) this._fetchReleaseNotes();
.then((result) => { }
this._releaseNotes = result; }
})
.catch((err) => { 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; this._error = err.message;
});
} }
} }
@ -183,9 +225,11 @@ class MoreInfoUpdate extends LitElement {
if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) { if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
return null; return null;
} }
const checkbox = this.shadowRoot?.querySelector("ha-checkbox"); const createBackupSwitch = this.shadowRoot?.getElementById(
if (checkbox) { "create-backup"
return checkbox.checked; ) as HaSwitch;
if (createBackupSwitch) {
return createBackupSwitch.checked;
} }
return true; return true;
} }
@ -234,6 +278,12 @@ class MoreInfoUpdate extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
:host {
display: flex;
flex-direction: column;
flex: 1;
justify-content: space-between;
}
hr { hr {
border-color: var(--divider-color); border-color: var(--divider-color);
border-bottom: none; border-bottom: none;
@ -248,26 +298,44 @@ class MoreInfoUpdate extends LitElement {
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
} }
.actions {
.footer {
border-top: 1px solid var(--divider-color); border-top: 1px solid var(--divider-color);
background: var( background: var(
--ha-dialog-surface-background, --ha-dialog-surface-background,
var(--mdc-theme-surface, #fff) var(--mdc-theme-surface, #fff)
); );
margin: 8px 0 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
position: sticky; position: sticky;
bottom: 0; bottom: 0;
padding: 12px 0; margin: 0 -24px -24px -24px;
margin-bottom: -24px; box-sizing: border-box;
z-index: 1; display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
z-index: 10;
} }
.actions mwc-button { ha-settings-row {
margin: 0 4px 4px; 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 { a {
color: var(--primary-color); color: var(--primary-color);
} }
@ -282,6 +350,16 @@ class MoreInfoUpdate extends LitElement {
} }
ha-markdown { ha-markdown {
direction: ltr; direction: ltr;
padding-bottom: 16px;
box-sizing: border-box;
}
ha-markdown.hidden {
display: none;
}
.loader {
height: 80px;
box-sizing: border-box;
padding-bottom: 16px;
} }
`; `;
} }

View File

@ -9,6 +9,7 @@ import {
computeShowHistoryComponent, computeShowHistoryComponent,
computeShowLogBookComponent, computeShowLogBookComponent,
computeShowNewMoreInfo, computeShowNewMoreInfo,
DOMAINS_FULL_HEIGHT_MORE_INFO,
DOMAINS_NO_INFO, DOMAINS_NO_INFO,
DOMAINS_WITH_MORE_INFO, DOMAINS_WITH_MORE_INFO,
} from "./const"; } from "./const";
@ -40,6 +41,8 @@ export class MoreInfoInfo extends LitElement {
const entityRegObj = this.hass.entities[entityId]; const entityRegObj = this.hass.entities[entityId];
const domain = computeDomain(entityId); const domain = computeDomain(entityId);
const isNewMoreInfo = stateObj && computeShowNewMoreInfo(stateObj); const isNewMoreInfo = stateObj && computeShowNewMoreInfo(stateObj);
const isFullHeight =
isNewMoreInfo || DOMAINS_FULL_HEIGHT_MORE_INFO.includes(domain);
return html` return html`
<div class="container" data-domain=${domain}> <div class="container" data-domain=${domain}>
@ -89,7 +92,7 @@ export class MoreInfoInfo extends LitElement {
.entityId=${this.entityId} .entityId=${this.entityId}
></ha-more-info-logbook>`} ></ha-more-info-logbook>`}
<more-info-content <more-info-content
?full-height=${isNewMoreInfo} ?full-height=${isFullHeight}
.stateObj=${stateObj} .stateObj=${stateObj}
.hass=${this.hass} .hass=${this.hass}
.entry=${this.entry} .entry=${this.entry}

View File

@ -1191,6 +1191,7 @@
"skip": "Skip", "skip": "Skip",
"clear_skipped": "Clear skipped", "clear_skipped": "Clear skipped",
"install": "Install", "install": "Install",
"update": "Update",
"create_backup": "Create backup before updating", "create_backup": "Create backup before updating",
"auto_update_enabled_title": "Can not skip version", "auto_update_enabled_title": "Can not skip version",
"auto_update_enabled_text": "Automatic updates for this item have been enabled; skipping it is, therefore, unavailable. You can either install this update now or wait for Home Assistant to do it automatically." "auto_update_enabled_text": "Automatic updates for this item have been enabled; skipping it is, therefore, unavailable. You can either install this update now or wait for Home Assistant to do it automatically."