Align layout of all cards (#8931)

* Align layout of all cards

* Make ignore card have normal button
This commit is contained in:
Paulus Schoutsen 2021-04-16 12:27:01 -07:00 committed by GitHub
parent 25b3bb1285
commit 179767e9f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 263 additions and 252 deletions

View File

@ -44,9 +44,10 @@ const createConfigEntry = (
const createManifest = (
isCustom: boolean,
isCloud: boolean
isCloud: boolean,
name = "ESPHome"
): IntegrationManifest => ({
name: "ESPHome",
name,
domain: "esphome",
is_built_in: !isCustom,
config_flow: false,
@ -103,7 +104,7 @@ const configFlows: DataEntryFlowProgressExtended[] = [
},
},
step_id: "discovery_confirm",
localized_title: "Roku: Living room Roku",
localized_title: "Living room Roku",
},
{
flow_id: "adbb401329d8439ebb78ef29837826a8",
@ -234,7 +235,8 @@ export class DemoIntegrationCard extends LitElement {
.flow=${flow}
.manifest=${createManifest(
this.isCustomIntegration,
this.isCloud
this.isCloud,
flow.handler === "roku" ? "Roku" : "Philips Hue"
)}
></ha-config-flow-card>
`

View File

@ -1,75 +0,0 @@
import { mdiPackageVariant, mdiCloud } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { css, html } from "lit-element";
import { IntegrationManifest } from "../../../data/integration";
import { HomeAssistant } from "../../../types";
export const haConfigIntegrationsStyles = css`
.banner {
background-color: var(--state-color);
color: var(--text-on-state-color);
text-align: center;
padding: 8px;
}
.icons {
position: absolute;
top: 0px;
right: 16px;
color: var(--text-on-state-color, var(--secondary-text-color));
background-color: var(--state-color, #e0e0e0);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 1px 4px 2px;
}
.icons ha-svg-icon {
width: 20px;
height: 20px;
}
paper-tooltip {
white-space: nowrap;
}
`;
export const haConfigIntegrationRenderIcons = (
hass: HomeAssistant,
manifest?: IntegrationManifest
) => {
const icons: [string, string][] = [];
if (manifest) {
if (!manifest.is_built_in) {
icons.push([
mdiPackageVariant,
hass.localize(
"ui.panel.config.integrations.config_entry.provided_by_custom_integration"
),
]);
}
if (manifest.iot_class && manifest.iot_class.startsWith("cloud_")) {
icons.push([
mdiCloud,
hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
),
]);
}
}
return icons.length === 0
? ""
: html`
<div class="icons">
${icons.map(
([icon, description]) => html`
<span>
<ha-svg-icon .path=${icon}></ha-svg-icon>
<paper-tooltip animation-delay="0"
>${description}</paper-tooltip
>
</span>
`
)}
</div>
`;
};

View File

@ -31,6 +31,7 @@ export class HaIgnoredConfigEntryCard extends LitElement {
"ui.panel.config.integrations.ignore.ignored"
)}
.domain=${this.entry.domain}
.localizedDomainName=${this.entry.localized_domain_name}
.label=${this.entry.title === "Ignored"
? // In 2020.2 we added support for entry.title. All ignored entries before
// that have title "Ignored" so we fallback to localized domain name.
@ -38,7 +39,6 @@ export class HaIgnoredConfigEntryCard extends LitElement {
: this.entry.title}
>
<mwc-button
unelevated
@click=${this._removeIgnoredIntegration}
.label=${this.hass.localize(
"ui.panel.config.integrations.ignore.stop_ignore"

View File

@ -1,18 +1,14 @@
import {
TemplateResult,
html,
customElement,
LitElement,
property,
CSSResult,
css,
} from "lit-element";
import { TemplateResult, html } from "lit-html";
import { IntegrationManifest } from "../../../data/integration";
import { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import {
haConfigIntegrationRenderIcons,
haConfigIntegrationsStyles,
} from "./ha-config-integrations-common";
import type { IntegrationManifest } from "../../../data/integration";
import type { HomeAssistant } from "../../../types";
import "./ha-integration-header";
@customElement("ha-integration-action-card")
export class HaIntegrationActionCard extends LitElement {
@ -20,6 +16,8 @@ export class HaIntegrationActionCard extends LitElement {
@property() public banner!: string;
@property() public localizedDomainName?: string;
@property() public domain!: string;
@property() public label!: string;
@ -29,38 +27,21 @@ export class HaIntegrationActionCard extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card outlined>
<div class="banner">
${this.banner}
</div>
<div class="content">
${haConfigIntegrationRenderIcons(this.hass, this.manifest)}
<div class="image">
<img
src=${brandsUrl(this.domain, "logo")}
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
</div>
<h2>${this.label}</h2>
</div>
<ha-integration-header
.hass=${this.hass}
.banner=${this.banner}
.domain=${this.domain}
.label=${this.label}
.localizedDomainName=${this.localizedDomainName}
.manifest=${this.manifest}
></ha-integration-header>
<div class="filler"></div>
<div class="actions"><slot></slot></div>
</ha-card>
`;
}
private _onImageLoad(ev) {
ev.target.style.visibility = "initial";
}
private _onImageError(ev) {
ev.target.style.visibility = "hidden";
}
static get styles(): CSSResult[] {
return [
haConfigIntegrationsStyles,
css`
static styles = css`
ha-card {
display: flex;
flex-direction: column;
@ -68,25 +49,9 @@ export class HaIntegrationActionCard extends LitElement {
--ha-card-border-color: var(--state-color);
--mdc-theme-primary: var(--state-color);
}
.content {
position: relative;
.filler {
flex: 1;
}
.image {
height: 60px;
margin-top: 16px;
display: flex;
align-items: center;
justify-content: space-around;
}
img {
max-width: 90%;
max-height: 100%;
}
h2 {
text-align: center;
margin: 16px 8px 0;
}
.attention {
--state-color: var(--error-color);
--text-on-state-color: var(--text-primary-color);
@ -102,9 +67,7 @@ export class HaIntegrationActionCard extends LitElement {
padding: 8px 6px 0;
height: 48px;
}
`,
];
}
`;
}
declare global {

View File

@ -31,7 +31,7 @@ import {
} from "../../../data/config_entries";
import type { DeviceRegistryEntry } from "../../../data/device_registry";
import type { EntityRegistryEntry } from "../../../data/entity_registry";
import { domainToName, IntegrationManifest } from "../../../data/integration";
import type { IntegrationManifest } from "../../../data/integration";
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
import {
@ -40,13 +40,9 @@ import {
showPromptDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { ConfigEntryExtended } from "./ha-config-integrations";
import {
haConfigIntegrationRenderIcons,
haConfigIntegrationsStyles,
} from "./ha-config-integrations-common";
import type { HomeAssistant } from "../../../types";
import type { ConfigEntryExtended } from "./ha-config-integrations";
import "./ha-integration-header";
const ERROR_STATES: ConfigEntry["state"][] = [
"migration_error",
@ -92,18 +88,6 @@ export class HaIntegrationCard extends LitElement {
);
}
let primary: string;
let secondary: string | undefined;
if (item) {
primary = item.title || item.localized_domain_name || this.domain;
if (primary !== item.localized_domain_name) {
secondary = item.localized_domain_name;
}
} else {
primary = domainToName(this.hass.localize, this.domain, this.manifest);
}
const hasItem = item !== undefined;
return html`
@ -120,18 +104,23 @@ export class HaIntegrationCard extends LitElement {
})}"
.configEntry=${item}
>
${this.disabled
? html`
<div class="banner">
${this.hass.localize(
<ha-integration-header
.hass=${this.hass}
.banner=${this.disabled
? this.hass.localize(
"ui.panel.config.integrations.config_entry.disable.disabled"
)}
</div>
`
: ""}
)
: undefined}
.domain=${this.domain}
.label=${item
? item.title || item.localized_domain_name || this.domain
: undefined}
.localizedDomainName=${item ? item.localized_domain_name : undefined}
.manifest=${this.manifest}
>
${this.items.length > 1
? html`
<div class="back-btn">
<div class="back-btn" slot="above-header">
<ha-icon-button
icon="hass:chevron-left"
@click=${this._back}
@ -139,19 +128,8 @@ export class HaIntegrationCard extends LitElement {
</div>
`
: ""}
<div class="header">
<img
src=${brandsUrl(this.domain, "icon")}
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
<div class="info">
<div class="primary">${primary}</div>
${secondary ? html`<div class="secondary">${secondary}</div>` : ""}
</div>
${haConfigIntegrationRenderIcons(this.hass, this.manifest)}
</div>
</ha-integration-header>
${item
? this._renderSingleEntry(item)
: this._renderGroupedIntegration()}
@ -441,14 +419,6 @@ export class HaIntegrationCard extends LitElement {
);
}
private _onImageLoad(ev) {
ev.target.style.visibility = "initial";
}
private _onImageError(ev) {
ev.target.style.visibility = "hidden";
}
private _showOptions(ev) {
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
}
@ -605,7 +575,6 @@ export class HaIntegrationCard extends LitElement {
static get styles(): CSSResult[] {
return [
haStyle,
haConfigIntegrationsStyles,
css`
ha-card {
display: flex;
@ -627,7 +596,7 @@ export class HaIntegrationCard extends LitElement {
--state-message-color: var(--primary-text-color);
}
:host(.highlight) ha-card {
--state-color: var(--accent-color);
--state-color: var(--primary-color);
--text-on-state-color: var(--text-primary-color);
}
@ -645,36 +614,6 @@ export class HaIntegrationCard extends LitElement {
height: 0px;
}
.header {
display: flex;
position: relative;
align-items: center;
padding: 16px 8px 8px 16px;
}
.header img {
margin-right: 16px;
width: 40px;
height: 40px;
}
.header .info div,
paper-item-body {
word-wrap: break-word;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.primary {
font-size: 16px;
font-weight: 400;
color: var(--primary-text-color);
}
.secondary {
font-size: 14px;
color: var(--secondary-text-color);
}
.message {
font-weight: bold;
padding-bottom: 16px;
@ -724,6 +663,14 @@ export class HaIntegrationCard extends LitElement {
cursor: pointer;
min-height: 35px;
}
paper-item-body {
word-wrap: break-word;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}

View File

@ -0,0 +1,174 @@
import { mdiPackageVariant, mdiCloud } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
html,
customElement,
property,
LitElement,
TemplateResult,
} from "lit-element";
import { domainToName, IntegrationManifest } from "../../../data/integration";
import { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
@customElement("ha-integration-header")
export class HaIntegrationHeader extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public banner!: string;
@property() public localizedDomainName?: string;
@property() public domain!: string;
@property() public label!: string;
@property() public manifest?: IntegrationManifest;
protected render(): TemplateResult {
let primary: string;
let secondary: string | undefined;
const domainName =
this.localizedDomainName ||
domainToName(this.hass.localize, this.domain, this.manifest);
if (this.label) {
primary = this.label;
secondary = primary === domainName ? undefined : domainName;
} else {
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.provided_by_custom_integration"
),
]);
}
if (
this.manifest.iot_class &&
this.manifest.iot_class.startsWith("cloud_")
) {
icons.push([
mdiCloud,
this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud"
),
]);
}
}
return html`
${!this.banner
? ""
: html`<div class="banner">
${this.banner}
</div>`}
<slot name="above-header"></slot>
<div class="header">
<img
src=${brandsUrl(this.domain, "icon")}
referrerpolicy="no-referrer"
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
<div class="info">
<div class="primary">${primary}</div>
${secondary ? html`<div class="secondary">${secondary}</div>` : ""}
</div>
${icons.length === 0
? ""
: html`
<div class="icons">
${icons.map(
([icon, description]) => html`
<span>
<ha-svg-icon .path=${icon}></ha-svg-icon>
<paper-tooltip animation-delay="0"
>${description}</paper-tooltip
>
</span>
`
)}
</div>
`}
</div>
`;
}
private _onImageLoad(ev) {
ev.target.style.visibility = "initial";
}
private _onImageError(ev) {
ev.target.style.visibility = "hidden";
}
static styles = css`
.banner {
background-color: var(--state-color);
color: var(--text-on-state-color);
text-align: center;
padding: 8px;
}
.header {
display: flex;
position: relative;
align-items: center;
padding: 16px 8px 8px 16px;
}
.header img {
margin-right: 16px;
width: 40px;
height: 40px;
}
.header .info div {
word-wrap: break-word;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.primary {
font-size: 16px;
font-weight: 400;
color: var(--primary-text-color);
}
.secondary {
font-size: 14px;
color: var(--secondary-text-color);
}
.icons {
position: absolute;
top: 0px;
right: 16px;
color: var(--text-on-state-color, var(--secondary-text-color));
background-color: var(--state-color, #e0e0e0);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 1px 4px 2px;
}
.icons ha-svg-icon {
width: 20px;
height: 20px;
}
paper-tooltip {
white-space: nowrap;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-integration-header": HaIntegrationHeader;
}
}