mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +00:00
Align layout of all cards (#8931)
* Align layout of all cards * Make ignore card have normal button
This commit is contained in:
parent
25b3bb1285
commit
179767e9f8
@ -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>
|
||||
`
|
||||
|
@ -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>
|
||||
`;
|
||||
};
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
174
src/panels/config/integrations/ha-integration-header.ts
Normal file
174
src/panels/config/integrations/ha-integration-header.ts
Normal 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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user