Refactor integration card (#17061)

This commit is contained in:
Bram Kragten 2023-06-28 14:09:02 +02:00 committed by GitHub
parent 8945650b62
commit cd3bec08f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 160 additions and 144 deletions

View File

@ -1,19 +1,29 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-button"; import "@material/mwc-button";
import "@material/mwc-list"; import "@material/mwc-list";
import "@material/mwc-ripple";
import type { Ripple } from "@material/mwc-ripple";
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import { mdiCloud, mdiPackageVariant } from "@mdi/js";
import { import {
mdiCogOutline, CSSResultGroup,
mdiDevices, LitElement,
mdiHandExtendedOutline, TemplateResult,
mdiPuzzleOutline, css,
mdiShapeOutline, html,
} from "@mdi/js"; nothing,
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; } from "lit";
import { customElement, property } from "lit/decorators"; import {
customElement,
eventOptions,
property,
queryAsync,
state,
} from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import "../../../components/ha-list-item"; import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -47,8 +57,12 @@ export class HaIntegrationCard extends LitElement {
@property() public logInfo?: IntegrationLogInfo; @property() public logInfo?: IntegrationLogInfo;
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
@state() private _shouldRenderRipple = false;
protected render(): TemplateResult { protected render(): TemplateResult {
const state = this._getState(this.items); const entryState = this._getState(this.items);
const debugLoggingEnabled = const debugLoggingEnabled =
this.logInfo && this.logInfo.level === LogSeverity.DEBUG; this.logInfo && this.logInfo.level === LogSeverity.DEBUG;
@ -57,22 +71,35 @@ export class HaIntegrationCard extends LitElement {
<ha-card <ha-card
outlined outlined
class=${classMap({ class=${classMap({
"state-loaded": state === "loaded", "state-loaded": entryState === "loaded",
"state-not-loaded": state === "not_loaded", "state-not-loaded": entryState === "not_loaded",
"state-failed-unload": state === "failed_unload", "state-failed-unload": entryState === "failed_unload",
"state-setup": state === "setup_in_progress", "state-setup": entryState === "setup_in_progress",
"state-error": ERROR_STATES.includes(state), "state-error": ERROR_STATES.includes(entryState),
"debug-logging": Boolean(debugLoggingEnabled), "debug-logging": Boolean(debugLoggingEnabled),
})} })}
> >
<a href=${`/config/integrations/integration/${this.domain}`}> <a
href=${`/config/integrations/integration/${this.domain}`}
class="ripple-anchor"
@focus=${this.handleRippleFocus}
@blur=${this.handleRippleBlur}
@mouseenter=${this.handleRippleMouseEnter}
@mouseleave=${this.handleRippleMouseLeave}
@mousedown=${this.handleRippleActivate}
@mouseup=${this.handleRippleDeactivate}
@touchstart=${this.handleRippleActivate}
@touchend=${this.handleRippleDeactivate}
@touchcancel=${this.handleRippleDeactivate}
>
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
<ha-integration-header <ha-integration-header
.hass=${this.hass} .hass=${this.hass}
.domain=${this.domain} .domain=${this.domain}
.localizedDomainName=${this.items[0].localized_domain_name} .localizedDomainName=${this.items[0].localized_domain_name}
.banner=${state !== "loaded" .banner=${entryState !== "loaded"
? this.hass.localize( ? this.hass.localize(
`ui.panel.config.integrations.config_entry.state.${state}` `ui.panel.config.integrations.config_entry.state.${entryState}`
) )
: debugLoggingEnabled : debugLoggingEnabled
? this.hass.localize( ? this.hass.localize(
@ -81,13 +108,12 @@ export class HaIntegrationCard extends LitElement {
: undefined} : undefined}
.manifest=${this.manifest} .manifest=${this.manifest}
> >
<ha-icon-button <ha-icon-next
slot="header-button" slot="header-button"
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.panel.config.integrations.config_entry.configure" "ui.panel.config.integrations.config_entry.configure"
)} )}
.path=${mdiCogOutline} ></ha-icon-next>
></ha-icon-button>
</ha-integration-header> </ha-integration-header>
</a> </a>
@ -105,18 +131,14 @@ export class HaIntegrationCard extends LitElement {
const services = !devices.some((device) => device.entry_type !== "service"); const services = !devices.some((device) => device.entry_type !== "service");
return html` return html`
<div class="content"> <div class="card-actions">
${devices.length > 0 ${devices.length > 0
? html`<a ? html`<a
href=${devices.length === 1 href=${devices.length === 1
? `/config/devices/device/${devices[0].id}` ? `/config/devices/device/${devices[0].id}`
: `/config/devices/dashboard?historyBack=1&domain=${this.domain}`} : `/config/devices/dashboard?historyBack=1&domain=${this.domain}`}
> >
<ha-list-item hasMeta graphic="icon"> <ha-button>
<ha-svg-icon
.path=${services ? mdiHandExtendedOutline : mdiDevices}
slot="graphic"
></ha-svg-icon>
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.integrations.config_entry.${ `ui.panel.config.integrations.config_entry.${
services ? "services" : "devices" services ? "services" : "devices"
@ -124,40 +146,57 @@ export class HaIntegrationCard extends LitElement {
"count", "count",
devices.length devices.length
)} )}
<ha-icon-next slot="meta"></ha-icon-next> </ha-button>
</ha-list-item>
</a>` </a>`
: entities.length > 0 : entities.length > 0
? html`<a ? html`<a
href=${`/config/entities?historyBack=1&domain=${this.domain}`} href=${`/config/entities?historyBack=1&domain=${this.domain}`}
> >
<ha-list-item hasMeta graphic="icon"> <ha-button>
<ha-svg-icon
.path=${mdiShapeOutline}
slot="graphic"
></ha-svg-icon>
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.integrations.config_entry.entities`, `ui.panel.config.integrations.config_entry.entities`,
"count", "count",
entities.length entities.length
)} )}
<ha-icon-next slot="meta"></ha-icon-next> </ha-button>
</ha-list-item>
</a>` </a>`
: html`<a href=${`/config/integrations/integration/${this.domain}`}> : html`<a href=${`/config/integrations/integration/${this.domain}`}>
<ha-list-item hasMeta graphic="icon"> <ha-button>
<ha-svg-icon
.path=${mdiPuzzleOutline}
slot="graphic"
></ha-svg-icon>
${this.hass.localize( ${this.hass.localize(
`ui.panel.config.integrations.config_entry.entries`, `ui.panel.config.integrations.config_entry.entries`,
"count", "count",
this.items.length this.items.length
)} )}
<ha-icon-next slot="meta"></ha-icon-next> </ha-button>
</ha-list-item>
</a>`} </a>`}
<div class="icons">
${this.manifest && !this.manifest.is_built_in
? html`<span class="icon custom">
<ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon>
<simple-tooltip
animation-delay="0"
.position=${computeRTL(this.hass) ? "right" : "left"}
offset="4"
>${this.hass.localize(
"ui.panel.config.integrations.config_entry.custom_integration"
)}</simple-tooltip
>
</span>`
: nothing}
${this.manifest && this.manifest.iot_class?.startsWith("cloud_")
? html`<div class="icon cloud">
<ha-svg-icon .path=${mdiCloud}></ha-svg-icon>
<simple-tooltip
animation-delay="0"
.position=${computeRTL(this.hass) ? "right" : "left"}
offset="4"
>${this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
)}</simple-tooltip
>
</div>`
: nothing}
</div>
</div> </div>
`; `;
} }
@ -167,14 +206,14 @@ export class HaIntegrationCard extends LitElement {
if (configEntry.length === 1) { if (configEntry.length === 1) {
return configEntry[0].state; return configEntry[0].state;
} }
let state: ConfigEntry["state"]; let entryState: ConfigEntry["state"];
for (const entry of configEntry) { for (const entry of configEntry) {
if (ERROR_STATES.includes(entry.state)) { if (ERROR_STATES.includes(entry.state)) {
return entry.state; return entry.state;
} }
state = entry.state; entryState = entry.state;
} }
return state!; return entryState!;
} }
); );
@ -209,6 +248,36 @@ export class HaIntegrationCard extends LitElement {
} }
); );
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
this._shouldRenderRipple = true;
return this._ripple;
});
@eventOptions({ passive: true })
private handleRippleActivate(evt?: Event) {
this._rippleHandlers.startPress(evt);
}
private handleRippleDeactivate() {
this._rippleHandlers.endPress();
}
private handleRippleFocus() {
this._rippleHandlers.startFocus();
}
private handleRippleBlur() {
this._rippleHandlers.endFocus();
}
protected handleRippleMouseEnter() {
this._rippleHandlers.startHover();
}
protected handleRippleMouseLeave() {
this._rippleHandlers.endHover();
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
@ -216,12 +285,25 @@ export class HaIntegrationCard extends LitElement {
ha-card { ha-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
--state-color: var(--divider-color, #e0e0e0); --state-color: var(--divider-color, #e0e0e0);
--ha-card-border-color: var(--state-color); --ha-card-border-color: var(--state-color);
--state-message-color: var(--state-color); --state-message-color: var(--state-color);
} }
.ripple-anchor {
flex-grow: 1;
position: relative;
}
ha-integration-header {
height: 100%;
}
.card-actions {
display: flex;
align-items: center;
justify-content: space-between;
}
.debug-logging { .debug-logging {
--state-color: var(--warning-color); --state-color: var(--warning-color);
--text-on-state-color: var(--primary-text-color); --text-on-state-color: var(--primary-text-color);
@ -254,9 +336,32 @@ export class HaIntegrationCard extends LitElement {
text-decoration: none; text-decoration: none;
color: var(--primary-text-color); color: var(--primary-text-color);
} }
a ha-icon-button { a ha-icon-next {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.icons {
display: flex;
}
.icon {
border-radius: 50%;
color: var(--text-primary-color);
padding: 4px;
margin-left: 8px;
}
.icon.cloud {
background: var(--info-color);
}
.icon.custom {
background: var(--warning-color);
}
.icon ha-svg-icon {
width: 16px;
height: 16px;
display: block;
}
simple-tooltip {
white-space: nowrap;
}
`, `,
]; ];
} }

View File

@ -1,11 +1,7 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import { LitElement, TemplateResult, css, html } from "lit";
import { mdiCloud, mdiPackageVariant } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { domainToName, IntegrationManifest } from "../../../data/integration"; import { IntegrationManifest, domainToName } from "../../../data/integration";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url"; import { brandsUrl } from "../../../util/brands-url";
@ -23,8 +19,6 @@ export class HaIntegrationHeader extends LitElement {
@property({ attribute: false }) public manifest?: IntegrationManifest; @property({ attribute: false }) public manifest?: IntegrationManifest;
@property({ attribute: false }) public debugLoggingEnabled?: boolean;
protected render(): TemplateResult { protected render(): TemplateResult {
let primary: string; let primary: string;
let secondary: string | undefined; let secondary: string | undefined;
@ -43,31 +37,8 @@ export class HaIntegrationHeader extends LitElement {
primary = domainName; primary = domainName;
} }
const icons: [string, string][] = [];
if (this.manifest) {
if (!this.manifest.is_built_in) {
icons.push([
mdiPackageVariant,
this.hass.localize(
"ui.panel.config.integrations.config_entry.custom_integration"
),
]);
}
if (this.manifest.iot_class?.startsWith("cloud_")) {
icons.push([
mdiCloud,
this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
),
]);
}
}
return html` return html`
${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`} ${!this.banner ? "" : html`<div class="banner">${this.banner}</div>`}
<slot name="above-header"></slot>
<div class="header"> <div class="header">
<img <img
alt="" alt=""
@ -80,32 +51,6 @@ export class HaIntegrationHeader extends LitElement {
@error=${this._onImageError} @error=${this._onImageError}
@load=${this._onImageLoad} @load=${this._onImageLoad}
/> />
${icons.length === 0
? ""
: html`
<div
class="icons ${classMap({
double: icons.length > 1,
cloud: Boolean(
this.manifest?.iot_class?.startsWith("cloud_")
),
})}"
>
${icons.map(
([icon, description]) => html`
<span>
<ha-svg-icon .path=${icon}></ha-svg-icon>
<simple-tooltip
animation-delay="0"
.position=${computeRTL(this.hass) ? "left" : "right"}
offset="4"
>${description}</simple-tooltip
>
</span>
`
)}
</div>
`}
<div class="info"> <div class="info">
<div class="primary" role="heading">${primary}</div> <div class="primary" role="heading">${primary}</div>
<div class="secondary">${secondary}</div> <div class="secondary">${secondary}</div>
@ -139,14 +84,13 @@ export class HaIntegrationHeader extends LitElement {
.header { .header {
display: flex; display: flex;
position: relative; position: relative;
padding-top: 0px; padding-top: 16px;
padding-bottom: 8px; padding-bottom: 16px;
padding-inline-start: 16px; padding-inline-start: 16px;
padding-inline-end: 8px; padding-inline-end: 8px;
direction: var(--direction); direction: var(--direction);
} }
.header img { .header img {
margin-top: 16px;
margin-inline-start: initial; margin-inline-start: initial;
margin-inline-end: 16px; margin-inline-end: 16px;
width: 40px; width: 40px;
@ -166,11 +110,13 @@ export class HaIntegrationHeader extends LitElement {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.header-button { .header-button {
margin-top: 8px; display: flex;
align-items: center;
justify-content: center;
width: 36px;
} }
.primary { .primary {
font-size: 16px; font-size: 16px;
margin-top: 16px;
font-weight: 400; font-weight: 400;
word-break: break-word; word-break: break-word;
color: var(--primary-text-color); color: var(--primary-text-color);
@ -179,41 +125,6 @@ export class HaIntegrationHeader extends LitElement {
font-size: 14px; font-size: 14px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.icons {
background: var(--warning-color);
border: 1px solid var(--card-background-color);
border-radius: 14px;
color: var(--text-primary-color);
position: absolute;
left: 40px;
top: 40px;
display: flex;
padding: 4px;
inset-inline-start: 40px;
inset-inline-end: initial;
}
.icons.cloud {
background: var(--info-color);
}
.icons.double {
background: var(--warning-color);
left: 28px;
inset-inline-start: 28px;
inset-inline-end: initial;
}
.icons ha-svg-icon {
width: 16px;
height: 16px;
display: block;
}
.icons span:not(:first-child) ha-svg-icon {
margin-left: 4px;
margin-inline-start: 4px;
margin-inline-end: inherit;
}
simple-tooltip {
white-space: nowrap;
}
`; `;
} }