diff --git a/src/panels/lovelace/cards/hui-home-summary-card.ts b/src/panels/lovelace/cards/hui-home-summary-card.ts index 55b447e611..518d173a36 100644 --- a/src/panels/lovelace/cards/hui-home-summary-card.ts +++ b/src/panels/lovelace/cards/hui-home-summary-card.ts @@ -4,6 +4,9 @@ import { computeCssColor } from "../../../common/color/compute-color"; import { computeDomain } from "../../../common/entity/compute_domain"; import { generateEntityFilter } from "../../../common/entity/entity_filter"; import { formatNumber } from "../../../common/number/format_number"; +import "../../../components/ha-icon"; +import "../../../components/tile/ha-tile-icon"; +import "../../../components/tile/ha-tile-info"; import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; import type { HomeAssistant } from "../../../types"; import { handleAction } from "../common/handle-action"; @@ -229,29 +232,25 @@ export class HuiHomeSummaryCard extends LitElement implements LovelaceCard { const color = computeCssColor(COLORS[this._config.summary]); const secondary = this._computeSummaryState(); - const tileConfig = { - name: label, - icon, - vertical: this._config.vertical, - tapAction: this._config.tap_action, - holdAction: this._config.hold_action, - doubleTapAction: this._config.double_tap_action, - }; - - const tileState = { - active: false, // Home summary cards don't have active state - color, - stateDisplay: html`${secondary}`, - }; - return html` + .tapAction=${this._config.tap_action} + .holdAction=${this._config.hold_action} + .doubleTapAction=${this._config.double_tap_action} + > + + + + + ${label} + ${secondary} + + `; } } diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 1071aebab0..e5e3e0b3e7 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -1,6 +1,9 @@ import type { HassEntity } from "home-assistant-js-websocket"; -import { LitElement, html, nothing } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { ifDefined } from "lit/directives/if-defined"; +import memoizeOne from "memoize-one"; import { computeCssColor } from "../../../common/color/compute-color"; import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color"; import { DOMAINS_TOGGLE } from "../../../common/const"; @@ -22,6 +25,7 @@ import type { LovelaceCardEditor, LovelaceGridOptions, } from "../types"; +import { renderTileBadge } from "./tile/badges/tile-badge"; import type { TileCardConfig } from "./types"; export const getEntityDefaultTileIconAction = (entityId: string) => { @@ -216,6 +220,23 @@ export class HuiTileCard extends LitElement implements LovelaceCard { ); } + private _featurePosition = memoizeOne((config: TileCardConfig) => { + if (config.vertical) { + return "bottom"; + } + return config.features_position || "bottom"; + }); + + private _displayedFeatures = memoizeOne((config: TileCardConfig) => { + const features = config.features || []; + const featurePosition = this._featurePosition(config); + + if (featurePosition === "inline") { + return features.slice(0, 1); + } + return features; + }); + protected render() { if (!this._config || !this.hass) { return nothing; @@ -232,7 +253,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard { } const name = this._config.name || computeStateName(stateObj); - const active = stateActive(stateObj); const color = this._computeStateColor(stateObj, this._config.color); const domain = computeDomain(stateObj.entity_id); @@ -252,46 +272,102 @@ export class HuiTileCard extends LitElement implements LovelaceCard { ? this._getImageUrl(stateObj) : undefined; - const tileConfig = { - name, - stateContent: this._config.state_content, - hideState: this._config.hide_state, - icon: this._config.icon, - color: this._config.color, - showEntityPicture: this._config.show_entity_picture, - vertical: this._config.vertical, - features: this._config.features, - featuresPosition: this._config.features_position, - tapAction: this._config.tap_action, - holdAction: this._config.hold_action, - doubleTapAction: this._config.double_tap_action, - iconTapAction: this._config.icon_tap_action, - iconHoldAction: this._config.icon_hold_action, - iconDoubleTapAction: this._config.icon_double_tap_action, - }; - - const tileState = { - active, - color, - imageUrl, - stateDisplay, - }; + const features = this._displayedFeatures(this._config); return html` + .tapAction=${this._config.tap_action} + .holdAction=${this._config.hold_action} + .doubleTapAction=${this._config.double_tap_action} + .iconTapAction=${this._config.icon_tap_action} + .iconHoldAction=${this._config.icon_hold_action} + .iconDoubleTapAction=${this._config.icon_double_tap_action} + .featurePosition=${this._featurePosition(this._config)} + > + + + ${renderTileBadge(stateObj, this.hass)} + + + ${name} + ${stateDisplay + ? html`${stateDisplay}` + : nothing} + + ${features.length + ? html` + + ` + : nothing} + `; } + + static styles = css` + ha-tile-icon[data-domain="alarm_control_panel"][data-state="pending"], + ha-tile-icon[data-domain="alarm_control_panel"][data-state="arming"], + ha-tile-icon[data-domain="alarm_control_panel"][data-state="triggered"], + ha-tile-icon[data-domain="lock"][data-state="jammed"] { + animation: pulse 1s infinite; + } + + /* Make sure we display the whole image */ + ha-tile-icon.image[data-domain="update"] { + --tile-icon-border-radius: 0; + } + /* Make sure we display the almost the whole image but it often use text */ + ha-tile-icon.image[data-domain="media_player"] { + --tile-icon-border-radius: min( + var(--ha-tile-icon-border-radius, var(--ha-border-radius-sm)), + var(--ha-border-radius-sm) + ); + } + + ha-tile-badge { + position: absolute; + top: 3px; + right: 3px; + inset-inline-end: 3px; + inset-inline-start: initial; + } + + @keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + `; } declare global { diff --git a/src/panels/lovelace/components/hui-tile.ts b/src/panels/lovelace/components/hui-tile.ts index c928d6054b..38e024bd64 100644 --- a/src/panels/lovelace/components/hui-tile.ts +++ b/src/panels/lovelace/components/hui-tile.ts @@ -1,63 +1,20 @@ -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, css, html } from "lit"; import { customElement, property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import "../../../components/ha-card"; import "../../../components/ha-ripple"; -import "../../../components/ha-state-icon"; -import "../../../components/ha-svg-icon"; -import "../../../components/tile/ha-tile-badge"; -import "../../../components/tile/ha-tile-icon"; -import "../../../components/tile/ha-tile-info"; import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler"; -import "../../../state-display/state-display"; -import type { HomeAssistant } from "../../../types"; -import "../card-features/hui-card-features"; -import type { LovelaceCardFeatureContext } from "../card-features/types"; import { actionHandler } from "../common/directives/action-handler-directive"; import { hasAction } from "../common/has-action"; -import { renderTileBadge } from "../cards/tile/badges/tile-badge"; - -export interface TileConfig { - name: string; - stateContent?: any; - hideState?: boolean; - icon?: string; - color?: string; - showEntityPicture?: boolean; - vertical?: boolean; - features?: any[]; - featuresPosition?: string; - tapAction?: any; - holdAction?: any; - doubleTapAction?: any; - iconTapAction?: any; - iconHoldAction?: any; - iconDoubleTapAction?: any; -} - -export interface TileState { - active: boolean; - color?: string; - imageUrl?: string; - stateDisplay?: any; -} +import type { LovelaceCardFeaturePosition } from "../card-features/types"; @customElement("hui-tile") export class HuiTile extends LitElement { - @property({ attribute: false }) public hass?: HomeAssistant; + @property({ type: Boolean }) public vertical = false; - @property({ attribute: false }) public config?: TileConfig; - - @property({ attribute: false }) public state?: TileState; - - @property({ attribute: false }) - public featureContext?: LovelaceCardFeatureContext; - - @property({ attribute: false }) public domain?: string; - - @property({ attribute: false }) public entityId?: string; + @property() public color?: string; @property({ attribute: false, type: Boolean }) public hasCardAction = false; @@ -71,62 +28,43 @@ export class HuiTile extends LitElement { ev: CustomEvent ) => void; - private get _featurePosition() { - if (this.config?.vertical) { - return "bottom"; - } - return this.config?.featuresPosition || "bottom"; - } + @property({ attribute: false }) public tapAction?: any; - private get _displayedFeatures() { - const features = this.config?.features || []; - const featurePosition = this._featurePosition; + @property({ attribute: false }) public holdAction?: any; - if (featurePosition === "inline") { - return features.slice(0, 1); - } - return features; - } + @property({ attribute: false }) public doubleTapAction?: any; + + @property({ attribute: false }) public iconTapAction?: any; + + @property({ attribute: false }) public iconHoldAction?: any; + + @property({ attribute: false }) public iconDoubleTapAction?: any; + + @property({ attribute: false }) + public featurePosition?: LovelaceCardFeaturePosition; private _handleAction(ev: ActionHandlerEvent) { this.onAction?.(ev); } - private _handleIconAction(ev: CustomEvent) { - this.onIconAction?.(ev); - } - protected render() { - if (!this.config || !this.state || !this.hass) { - return nothing; - } - - const contentClasses = { vertical: Boolean(this.config.vertical) }; - const name = this.config.name; - const active = this.state.active; - const color = this.state.color; - const stateDisplay = this.config.hideState - ? nothing - : this.state.stateDisplay; + const contentClasses = { vertical: Boolean(this.vertical) }; const style = { - "--tile-color": color, + "--tile-color": this.color, }; - const imageUrl = this.state.imageUrl; - const featurePosition = this._featurePosition; - const features = this._displayedFeatures; const containerOrientationClass = - featurePosition === "inline" ? "horizontal" : ""; + this.featurePosition === "inline" ? "horizontal" : ""; return html` - +
- - - ${this.entityId - ? renderTileBadge(this.hass.states[this.entityId], this.hass) - : nothing} - - - ${name} - ${stateDisplay - ? html`${stateDisplay}` - : nothing} - + +
- ${features.length > 0 - ? html` - - ` - : nothing} +
`; @@ -259,71 +154,33 @@ export class HuiTile extends LitElement { text-align: center; justify-content: center; } - .vertical ha-tile-info { + .vertical ::slotted(ha-tile-info) { width: 100%; flex: none; } - ha-tile-icon { + ::slotted(ha-tile-icon) { --tile-icon-color: var(--tile-color); position: relative; padding: 6px; margin: -6px; } - ha-tile-badge { - position: absolute; - top: 3px; - right: 3px; - inset-inline-end: 3px; - inset-inline-start: initial; - } - ha-tile-info { + ::slotted(ha-tile-info) { position: relative; min-width: 0; transition: background-color 180ms ease-in-out; box-sizing: border-box; } - hui-card-features { + ::slotted(features) { --feature-color: var(--tile-color); padding: 0 12px 12px 12px; } - .container.horizontal hui-card-features { + .container.horizontal ::slotted(hui-card-features) { width: calc(50% - var(--column-gap, 0px) / 2 - 12px); flex: none; --feature-height: 36px; padding: 0 12px; padding-inline-start: 0; } - - ha-tile-icon[data-domain="alarm_control_panel"][data-state="pending"], - ha-tile-icon[data-domain="alarm_control_panel"][data-state="arming"], - ha-tile-icon[data-domain="alarm_control_panel"][data-state="triggered"], - ha-tile-icon[data-domain="lock"][data-state="jammed"] { - animation: pulse 1s infinite; - } - - /* Make sure we display the whole image */ - ha-tile-icon.image[data-domain="update"] { - --tile-icon-border-radius: 0; - } - /* Make sure we display the almost the whole image but it often use text */ - ha-tile-icon.image[data-domain="media_player"] { - --tile-icon-border-radius: min( - var(--ha-tile-icon-border-radius, var(--ha-border-radius-sm)), - var(--ha-border-radius-sm) - ); - } - - @keyframes pulse { - 0% { - opacity: 1; - } - 50% { - opacity: 0; - } - 100% { - opacity: 1; - } - } `; }