Add integration name information to repairs (#22006)

* Add integration name to repairs

* Improve dialog-repairs-issue aria and translations

* Fix type in dialog-repairs-issue

* Remove unused slots in dialog-repairs-issue

* Fix ha-config-repairs avoid nested css

* Fix ha-config-repairs to use ha-md-list

* Add subtitle slot to ha-dialog-header

* Move close icon to left in dialog-data-entry-flow

* Move severity and reportedBy to dialog subtitle in repair-dialog

* Add md buttons to dialog-repairs-issue

* Revert dialog-repairs-issue to use normal ha-buttons

* Revert dialog-entry-flow close icon position

* Improve buttons for dialog-repairs-issue

* Add subtitle to all show-dialog-repair-flow headers

* Fix integration names for repair dialogs

* Fix subtitle title repair dialogs

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Wendelin 2024-09-25 16:12:45 +02:00 committed by GitHub
parent cd631e8693
commit 254ee8568b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 263 additions and 145 deletions

View File

@ -10,8 +10,13 @@ export class HaDialogHeader extends LitElement {
<section class="header-navigation-icon">
<slot name="navigationIcon"></slot>
</section>
<section class="header-title">
<slot name="title"></slot>
<section class="header-content">
<div class="header-title">
<slot name="title"></slot>
</div>
<div class="header-subtitle">
<slot name="subtitle"></slot>
</div>
</section>
<section class="header-action-items">
<slot name="actionItems"></slot>
@ -39,17 +44,24 @@ export class HaDialogHeader extends LitElement {
padding: 4px;
box-sizing: border-box;
}
.header-title {
.header-content {
flex: 1;
font-size: 22px;
line-height: 28px;
font-weight: 400;
padding: 10px 4px;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header-title {
font-size: 22px;
line-height: 28px;
font-weight: 400;
}
.header-subtitle {
font-size: 14px;
line-height: 20px;
color: var(--secondary-text-color);
}
@media all and (min-width: 450px) and (min-height: 500px) {
.header-bar {
padding: 12px;

View File

@ -31,6 +31,11 @@ export interface FlowConfig {
deleteFlow(hass: HomeAssistant, flowId: string): Promise<unknown>;
renderAbortHeader?(
hass: HomeAssistant,
step: DataEntryFlowStepAbort
): TemplateResult | string;
renderAbortDescription(
hass: HomeAssistant,
step: DataEntryFlowStepAbort
@ -39,7 +44,7 @@ export interface FlowConfig {
renderShowFormStepHeader(
hass: HomeAssistant,
step: DataEntryFlowStepForm
): string;
): string | TemplateResult;
renderShowFormStepDescription(
hass: HomeAssistant,
@ -95,14 +100,17 @@ export interface FlowConfig {
renderShowFormProgressHeader(
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): string;
): string | TemplateResult;
renderShowFormProgressDescription(
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): TemplateResult | "";
renderMenuHeader(hass: HomeAssistant, step: DataEntryFlowStepMenu): string;
renderMenuHeader(
hass: HomeAssistant,
step: DataEntryFlowStepMenu
): string | TemplateResult;
renderMenuDescription(
hass: HomeAssistant,

View File

@ -31,7 +31,11 @@ class StepFlowAbort extends LitElement {
return nothing;
}
return html`
<h2>${this.hass.localize(`component.${this.domain}.title`)}</h2>
<h2>
${this.params.flowConfig.renderAbortHeader
? this.params.flowConfig.renderAbortHeader(this.hass, this.step)
: this.hass.localize(`component.${this.domain}.title`)}
</h2>
<div class="content">
${this.params.flowConfig.renderAbortDescription(this.hass, this.step)}
</div>

View File

@ -0,0 +1,63 @@
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../../types";
import { domainToName } from "../../../data/integration";
import type { RepairsIssue } from "../../../data/repairs";
@customElement("dialog-repairs-issue-subtitle")
class DialogRepairsIssueSubtitle extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Object }) public issue!: RepairsIssue;
protected firstUpdated() {
if (this.scrollWidth > this.offsetWidth) {
this.title =
(this.shadowRoot?.firstElementChild as HTMLElement)?.innerText || "";
}
}
protected render() {
const domainName = domainToName(this.hass.localize, this.issue.domain);
const reportedBy = domainName
? this.hass.localize("ui.panel.config.repairs.reported_by", {
integration: domainName,
})
: "";
const severity = this.hass.localize(
`ui.panel.config.repairs.${this.issue.severity}`
);
return html`
<span>
<span class=${this.issue.severity}> ${severity} </span>
${reportedBy}
</span>
`;
}
static styles = css`
:host {
display: block;
font-size: 14px;
margin-bottom: 8px;
color: var(--secondary-text-color);
text-overflow: ellipsis;
overflow: hidden;
}
.error,
.critical {
color: var(--error-color);
}
.warning {
color: var(--warning-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"dialog-repairs-issue-subtitle": DialogRepairsIssueSubtitle;
}
}

View File

@ -1,11 +1,14 @@
import "@material/mwc-button/mwc-button";
import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatDateNumeric } from "../../../common/datetime/format_date";
import { customElement, property, state, query } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { isNavigationClick } from "../../../common/dom/is-navigation-click";
import "../../../components/ha-alert";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../components/ha-md-dialog";
import "../../../components/ha-button";
import "../../../components/ha-dialog-header";
import "./dialog-repairs-issue-subtitle";
import "../../../components/ha-markdown";
import { ignoreRepairsIssue, RepairsIssue } from "../../../data/repairs";
import { haStyleDialog } from "../../../resources/styles";
@ -20,12 +23,14 @@ class DialogRepairsIssue extends LitElement {
@state() private _params?: RepairsIssueDialogParams;
@query("ha-md-dialog") private _dialog?: HaMdDialog;
public showDialog(params: RepairsIssueDialogParams): void {
this._params = params;
this._issue = this._params.issue;
}
public closeDialog() {
private _dialogClosed() {
if (this._params?.dialogClosedCallback) {
this._params.dialogClosedCallback();
}
@ -35,6 +40,10 @@ class DialogRepairsIssue extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
public closeDialog() {
this._dialog?.close();
}
protected render() {
if (!this._issue) {
return nothing;
@ -43,23 +52,39 @@ class DialogRepairsIssue extends LitElement {
const learnMoreUrlIsHomeAssistant =
this._issue.learn_more_url?.startsWith("homeassistant://") || false;
const dialogTitle =
this.hass.localize(
`component.${this._issue.domain}.issues.${this._issue.translation_key || this._issue.issue_id}.title`,
this._issue.translation_placeholders || {}
) || this.hass!.localize("ui.panel.config.repairs.dialog.title");
return html`
<ha-dialog
<ha-md-dialog
open
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass.localize(
`component.${this._issue.domain}.issues.${
this._issue.translation_key || this._issue.issue_id
}.title`,
this._issue.translation_placeholders || {}
) || this.hass!.localize("ui.panel.config.repairs.dialog.title")
)}
@closed=${this._dialogClosed}
aria-labelledby="dialog-repairs-issue-title"
aria-describedby="dialog-repairs-issue-description"
>
<div>
<ha-dialog-header slot="headline">
<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.dialogs.generic.close") ?? "Close"}
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
<span
slot="title"
id="dialog-repairs-issue-title"
.title=${dialogTitle}
>${dialogTitle}</span
>
<dialog-repairs-issue-subtitle
slot="subtitle"
.hass=${this.hass}
.issue=${this._issue}
></dialog-repairs-issue-subtitle>
</ha-dialog-header>
<div slot="content" class="dialog-content">
${this._issue.breaks_in_ha_version
? html`
<ha-alert alert-type="warning">
@ -71,6 +96,7 @@ class DialogRepairsIssue extends LitElement {
`
: ""}
<ha-markdown
id="dialog-repairs-issue-description"
allowsvg
breaks
@click=${this._clickHandler}
@ -92,51 +118,39 @@ class DialogRepairsIssue extends LitElement {
>
`
: ""}
<div class="secondary">
<span class=${this._issue.severity}
>${this.hass.localize(
`ui.panel.config.repairs.${this._issue.severity}`
)}
</span>
-
${this._issue.created
? formatDateNumeric(
new Date(this._issue.created),
this.hass.locale,
this.hass.config
)
: ""}
</div>
</div>
${this._issue.learn_more_url
? html`
<a
.href=${learnMoreUrlIsHomeAssistant
? this._issue.learn_more_url.replace("homeassistant://", "/")
: this._issue.learn_more_url}
.target=${learnMoreUrlIsHomeAssistant ? "" : "_blank"}
@click=${learnMoreUrlIsHomeAssistant
? this.closeDialog
: undefined}
slot="primaryAction"
rel="noopener noreferrer"
>
<mwc-button
.label=${this.hass!.localize(
"ui.panel.config.repairs.dialog.learn"
)}
></mwc-button>
</a>
`
: ""}
<mwc-button
slot="secondaryAction"
.label=${this._issue!.ignored
? this.hass!.localize("ui.panel.config.repairs.dialog.unignore")
: this.hass!.localize("ui.panel.config.repairs.dialog.ignore")}
@click=${this._ignoreIssue}
></mwc-button>
</ha-dialog>
<div slot="actions">
<ha-button @click=${this._ignoreIssue}>
${this._issue!.ignored
? this.hass!.localize("ui.panel.config.repairs.dialog.unignore")
: this.hass!.localize("ui.panel.config.repairs.dialog.ignore")}
</ha-button>
${this._issue.learn_more_url
? html`
<a
rel="noopener noreferrer"
.href=${learnMoreUrlIsHomeAssistant
? this._issue.learn_more_url.replace(
"homeassistant://",
"/"
)
: this._issue.learn_more_url}
.target=${learnMoreUrlIsHomeAssistant ? "" : "_blank"}
>
<ha-button
@click=${learnMoreUrlIsHomeAssistant
? this.closeDialog
: undefined}
>
${this.hass!.localize(
"ui.panel.config.repairs.dialog.learn"
)}
</ha-button>
</a>
`
: ""}
</div>
</ha-md-dialog>
`;
}
@ -154,28 +168,16 @@ class DialogRepairsIssue extends LitElement {
static styles: CSSResultGroup = [
haStyleDialog,
css`
.dialog-content {
padding-top: 0;
}
ha-alert {
margin-bottom: 16px;
display: block;
}
a {
text-decoration: none;
}
.dismissed {
font-style: italic;
}
.secondary {
margin-top: 8px;
text-align: right;
color: var(--secondary-text-color);
}
.error,
.critical {
color: var(--error-color);
}
.warning {
color: var(--warning-color);
}
`,
];
}

View File

@ -1,12 +1,9 @@
import "@material/mwc-list/mwc-list";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { relativeTime } from "../../../common/datetime/relative_time";
import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import "../../../components/ha-md-list";
import "../../../components/ha-md-list-item";
import { domainToName } from "../../../data/integration";
import {
fetchRepairsIssueData,
@ -48,19 +45,31 @@ class HaConfigRepairs extends LitElement {
count: this.total || this.repairsIssues.length,
})}
</div>
<mwc-list>
${issues.map(
(issue) => html`
<ha-list-item
twoline
graphic="medium"
<ha-md-list>
${issues.map((issue) => {
const domainName = domainToName(this.hass.localize, issue.domain);
const createdBy =
issue.created && domainName
? this.hass.localize("ui.panel.config.repairs.created_at_by", {
date: capitalizeFirstLetter(
relativeTime(new Date(issue.created), this.hass.locale)
),
integration: domainName,
})
: "";
return html`
<ha-md-list-item
.hasMeta=${!this.narrow}
.issue=${issue}
class=${issue.ignored ? "ignored" : ""}
@click=${this._openShowMoreDialog}
type="button"
>
<img
alt=${domainToName(this.hass.localize, issue.domain)}
slot="start"
alt=${domainName}
loading="lazy"
src=${brandsUrl({
domain: issue.issue_domain || issue.domain,
@ -68,21 +77,18 @@ class HaConfigRepairs extends LitElement {
useFallback: true,
darkOptimized: this.hass.themes?.darkMode,
})}
.title=${domainToName(this.hass.localize, issue.domain)}
.title=${domainName}
crossorigin="anonymous"
referrerpolicy="no-referrer"
slot="graphic"
/>
<span
>${this.hass.localize(
`component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id
}.title`,
<span slot="headline">
${this.hass.localize(
`component.${issue.domain}.issues.${issue.translation_key || issue.issue_id}.title`,
issue.translation_placeholders || {}
) ||
`${issue.domain}: ${issue.translation_key || issue.issue_id}`}</span
>
<span slot="secondary" class="secondary">
`${issue.domain}: ${issue.translation_key || issue.issue_id}`}
</span>
<span slot="supporting-text">
${issue.severity === "critical" || issue.severity === "error"
? html`<span class="error"
>${this.hass.localize(
@ -93,27 +99,25 @@ class HaConfigRepairs extends LitElement {
${(issue.severity === "critical" ||
issue.severity === "error") &&
issue.created
? " - "
: ""}
${issue.created
? capitalizeFirstLetter(
relativeTime(new Date(issue.created), this.hass.locale)
)
? " ⸱ "
: ""}
${createdBy
? html`<span .title=${createdBy}>${createdBy}</span>`
: nothing}
${issue.ignored
? ` - ${this.hass.localize(
? ` ${this.hass.localize(
"ui.panel.config.repairs.dialog.ignored_in_version_short",
{ version: issue.dismissed_version }
)}`
: ""}
</span>
${!this.narrow
? html`<ha-icon-next slot="meta"></ha-icon-next>`
? html`<ha-icon-next slot="end"></ha-icon-next>`
: ""}
</ha-list-item>
`
)}
</mwc-list>
</ha-md-list-item>
`;
})}
</ha-md-list>
`;
}
@ -179,9 +183,6 @@ class HaConfigRepairs extends LitElement {
.ignored {
opacity: var(--light-secondary-opacity);
}
ha-list-item {
--mdc-list-item-graphic-size: 40px;
}
button.show-more {
color: var(--primary-color);
text-align: left;
@ -198,9 +199,12 @@ class HaConfigRepairs extends LitElement {
outline: none;
text-decoration: underline;
}
ha-list-item {
cursor: pointer;
font-size: 16px;
ha-md-list-item img[slot="start"] {
width: 40px;
height: 40px;
}
ha-md-list-item span[slot="supporting-text"] {
white-space: nowrap;
}
.error {
color: var(--error-color);

View File

@ -1,6 +1,7 @@
import { html } from "lit";
import { html, nothing } from "lit";
import { DataEntryFlowStep } from "../../../data/data_entry_flow";
import { domainToName } from "../../../data/integration";
import "./dialog-repairs-issue-subtitle";
import {
RepairsIssue,
createRepairsFlow,
@ -66,6 +67,16 @@ export const showRepairsFlowDialog = (
handleFlowStep: handleRepairsFlowStep,
deleteFlow: deleteRepairsFlow,
renderAbortHeader(hass) {
return html`
${hass.localize("ui.dialogs.repair_flow.form.header")}
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
},
renderAbortDescription(hass, step) {
const description = hass.localize(
`component.${issue.domain}.issues.${
@ -87,14 +98,18 @@ export const showRepairsFlowDialog = (
},
renderShowFormStepHeader(hass, step) {
return (
hass.localize(
return html`
${hass.localize(
`component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.title`,
mergePlaceholders(issue, step)
) || hass.localize("ui.dialogs.repair_flow.form.header")
);
) || hass.localize("ui.dialogs.repair_flow.form.header")}
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
},
renderShowFormStepDescription(hass, step) {
@ -113,7 +128,7 @@ export const showRepairsFlowDialog = (
.content=${description}
></ha-markdown>
`
: ""}`;
: nothing}`;
},
renderShowFormStepFieldLabel(hass, step, field, options) {
@ -135,7 +150,7 @@ export const showRepairsFlowDialog = (
return html`${renderIssueDescription(hass, issue)}
${description
? html`<ha-markdown breaks .content=${description}></ha-markdown>`
: ""}`;
: nothing}`;
},
renderShowFormStepFieldError(hass, step, error) {
@ -181,14 +196,18 @@ export const showRepairsFlowDialog = (
},
renderShowFormProgressHeader(hass, step) {
return (
hass.localize(
return html`
${hass.localize(
`component.${issue.domain}.issues.step.${
issue.translation_key || issue.issue_id
}.fix_flow.${step.step_id}.title`,
mergePlaceholders(issue, step)
) || hass.localize(`component.${issue.domain}.title`)
);
) || hass.localize(`component.${issue.domain}.title`)}
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
},
renderShowFormProgressDescription(hass, step) {
@ -206,18 +225,22 @@ export const showRepairsFlowDialog = (
.content=${description}
></ha-markdown>
`
: ""}`;
: nothing}`;
},
renderMenuHeader(hass, step) {
return (
hass.localize(
return html`
${hass.localize(
`component.${issue.domain}.issues.${
issue.translation_key || issue.issue_id
}.fix_flow.step.${step.step_id}.title`,
mergePlaceholders(issue, step)
) || hass.localize(`component.${issue.domain}.title`)
);
) || hass.localize(`component.${issue.domain}.title`)}
<dialog-repairs-issue-subtitle
.hass=${hass}
.issue=${issue}
></dialog-repairs-issue-subtitle>
`;
},
renderMenuDescription(hass, step) {
@ -236,7 +259,7 @@ export const showRepairsFlowDialog = (
.content=${description}
></ha-markdown>
`
: ""}`;
: nothing}`;
},
renderMenuOption(hass, step, option) {

View File

@ -1972,6 +1972,8 @@
"system_information": "System information",
"integration_startup_time": "Integration startup time",
"copy": "Copy",
"reported_by": "Reported by {integration}",
"created_at_by": "{date} by {integration}",
"dialog": {
"title": "Repair",
"fix": "Repair",