From 5d72e3e2e54ab2419e7c6733e41ed0a32fe841c3 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 5 Mar 2024 22:26:59 +0100 Subject: [PATCH] Add grid section support (#1385) * Add grid support to climate card * Add grid support to light card * Add grid support to media_player card * Add grid support to template card * Add logic in base card * Add grid support to vacuum card * Refactor stateObj * Add grid support to select card * Add grid support to template card * Default set config * Add grid support to person card * Add grid support to number card * Add grid support to media player card * Add grid support to lock card * Add grid support to humidifier card * Add grid support to fan card * Add grid support to cover card * Add grid support to alarm control panel card * Clean stateObj * Prettier * Fix missing condition * Fix missing condition * Improve get card size * fix light card action * Improve get card size --- .../alarm-control-panel-card.ts | 41 ++++----- .../chips/conditional-chip-editor-legacy.ts | 15 ++- .../chips/conditional-chip-editor.ts | 15 ++- .../chips-card/chips/conditional-chip.ts | 2 +- src/cards/climate-card/climate-card.ts | 72 +++++++-------- src/cards/cover-card/cover-card.ts | 47 ++++++---- src/cards/entity-card/entity-card.ts | 25 +---- .../controls/fan-oscillate-control.ts | 4 +- src/cards/fan-card/fan-card.ts | 26 +++--- src/cards/humidifier-card/humidifier-card.ts | 18 ++-- src/cards/light-card/light-card.ts | 86 ++++++++---------- .../controls/lock-buttons-control.ts | 2 +- src/cards/lock-card/lock-card.ts | 23 +---- .../controls/media-player-media-control.ts | 2 +- .../controls/media-player-volume-control.ts | 2 +- .../media-player-card/media-player-card.ts | 91 +++++++++---------- src/cards/number-card/number-card.ts | 30 ++---- src/cards/person-card/person-card.ts | 25 +---- src/cards/select-card/select-card.ts | 23 +---- src/cards/template-card/template-card.ts | 31 ++++++- src/cards/update-card/update-card.ts | 30 +++--- .../controls/vacuum-commands-control.ts | 76 ++++++++++------ src/cards/vacuum-card/vacuum-card.ts | 32 +++---- src/shared/card.ts | 3 +- src/shared/editor/layout-picker.ts | 13 ++- src/utils/base-card.ts | 73 ++++++++++++++- src/utils/card-styles.ts | 6 ++ src/utils/theme.ts | 2 +- 28 files changed, 408 insertions(+), 407 deletions(-) diff --git a/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts b/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts index 9cd4dc462..b0d3bfc3c 100644 --- a/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts +++ b/src/cards/alarm-control-panel-card/alarm-control-panel-card.ts @@ -62,7 +62,10 @@ const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"]; */ @customElement(ALARM_CONTROl_PANEL_CARD_NAME) -export class AlarmControlPanelCard extends MushroomBaseCard implements LovelaceCard { +export class AlarmControlPanelCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./alarm-control-panel-card-editor"); return document.createElement(ALARM_CONTROl_PANEL_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -80,24 +83,21 @@ export class AlarmControlPanelCard extends MushroomBaseCard implements LovelaceC }; } - @state() private _config?: AlarmControlPanelCardConfig; - - @query("#alarmCode") private _input?: HaTextField; + protected get hasControls(): boolean { + return Boolean(this._config?.states?.length); + } - getCardSize(): number | Promise { - return 1; + public getGridSize() { + if (this._config?.show_keypad) { + return [4, 1] as [number, number]; + } + return super.getGridSize(); } + @query("#alarmCode") private _input?: HaTextField; + setConfig(config: AlarmControlPanelCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; + super.setConfig(config); this.loadComponents(); } @@ -109,9 +109,7 @@ export class AlarmControlPanelCard extends MushroomBaseCard implements LovelaceC } async loadComponents() { - if (!this._config || !this.hass || !this._config.entity) return; - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (stateObj && hasCode(stateObj)) { void import("../../shared/form/mushroom-textfield"); @@ -144,9 +142,7 @@ export class AlarmControlPanelCard extends MushroomBaseCard implements LovelaceC } private get _hasCode(): boolean { - const entityId = this._config?.entity; - if (!entityId) return false; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) return false; return hasCode(stateObj) && Boolean(this._config?.show_keypad); } @@ -156,8 +152,7 @@ export class AlarmControlPanelCard extends MushroomBaseCard implements LovelaceC return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/chips-card/chips/conditional-chip-editor-legacy.ts b/src/cards/chips-card/chips/conditional-chip-editor-legacy.ts index bce8dd058..7a7ab0dbe 100644 --- a/src/cards/chips-card/chips/conditional-chip-editor-legacy.ts +++ b/src/cards/chips-card/chips/conditional-chip-editor-legacy.ts @@ -102,14 +102,13 @@ export class ConditionalChipEditor extends LitElement implements LovelaceChipEdi naturalMenuWidth > ${CHIP_LIST.map( - (chip) => - html` - - ${customLocalize( - `editor.chip.chip-picker.types.${chip}` - )} - - ` + (chip) => html` + + ${customLocalize( + `editor.chip.chip-picker.types.${chip}` + )} + + ` )} `} diff --git a/src/cards/chips-card/chips/conditional-chip-editor.ts b/src/cards/chips-card/chips/conditional-chip-editor.ts index d9016546c..9cc5cfaaa 100644 --- a/src/cards/chips-card/chips/conditional-chip-editor.ts +++ b/src/cards/chips-card/chips/conditional-chip-editor.ts @@ -106,14 +106,13 @@ export class ConditionalChipEditor extends LitElement implements LovelaceChipEdi naturalMenuWidth > ${CHIP_LIST.map( - (chip) => - html` - - ${customLocalize( - `editor.chip.chip-picker.types.${chip}` - )} - - ` + (chip) => html` + + ${customLocalize( + `editor.chip.chip-picker.types.${chip}` + )} + + ` )} `} diff --git a/src/cards/chips-card/chips/conditional-chip.ts b/src/cards/chips-card/chips/conditional-chip.ts index 847564328..6ad9f9a6e 100644 --- a/src/cards/chips-card/chips/conditional-chip.ts +++ b/src/cards/chips-card/chips/conditional-chip.ts @@ -28,7 +28,7 @@ export const setupConditionChipComponent = async () => { }); } const HuiConditionalBase = await loadCustomElement("hui-conditional-base"); - + // @ts-ignore class ConditionalChip extends HuiConditionalBase implements LovelaceChip { public static async getConfigElement(): Promise { diff --git a/src/cards/climate-card/climate-card.ts b/src/cards/climate-card/climate-card.ts index 72b2f4c7f..8b9ef7498 100644 --- a/src/cards/climate-card/climate-card.ts +++ b/src/cards/climate-card/climate-card.ts @@ -51,7 +51,10 @@ registerCustomCard({ }); @customElement(CLIMATE_CARD_NAME) -export class ClimateCard extends MushroomBaseCard implements LovelaceCard { +export class ClimateCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./climate-card-editor"); return document.createElement(CLIMATE_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -66,23 +69,33 @@ export class ClimateCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: ClimateCardConfig; - @state() private _activeControl?: ClimateCardControl; - @state() private _controls: ClimateCardControl[] = []; + private get _controls(): ClimateCardControl[] { + if (!this._config || !this._stateObj) return []; + + const stateObj = this._stateObj; + const controls: ClimateCardControl[] = []; + if (isTemperatureControlVisible(stateObj) && this._config.show_temperature_control) { + controls.push("temperature_control"); + } + if (isHvacModesVisible(stateObj, this._config.hvac_modes)) { + controls.push("hvac_mode_control"); + } + return controls; + } + + protected get hasControls(): boolean { + return this._controls.length > 0; + } _onControlTap(ctrl, e): void { e.stopPropagation(); this._activeControl = ctrl; } - getCardSize(): number | Promise { - return 1; - } - setConfig(config: ClimateCardConfig): void { - this._config = { + super.setConfig({ tap_action: { action: "toggle", }, @@ -90,40 +103,22 @@ export class ClimateCard extends MushroomBaseCard implements LovelaceCard { action: "more-info", }, ...config, - }; - this.updateControls(); + }); + this.updateActiveControl(); } protected updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (this.hass && changedProperties.has("hass")) { - this.updateControls(); + this.updateActiveControl(); } } - updateControls() { - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as ClimateEntity | undefined; - - if (!stateObj) return; - - const controls: ClimateCardControl[] = []; - if (!this._config.collapsible_controls || isActive(stateObj)) { - if (isTemperatureControlVisible(stateObj) && this._config.show_temperature_control) { - controls.push("temperature_control"); - } - if (isHvacModesVisible(stateObj, this._config.hvac_modes)) { - controls.push("hvac_mode_control"); - } - } - - this._controls = controls; + updateActiveControl() { const isActiveControlSupported = this._activeControl - ? controls.includes(this._activeControl) + ? this._controls.includes(this._activeControl) : false; - this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; + this._activeControl = isActiveControlSupported ? this._activeControl : this._controls[0]; } private _handleAction(ev: ActionHandlerEvent) { @@ -135,8 +130,7 @@ export class ClimateCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as ClimateEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); @@ -166,6 +160,9 @@ export class ClimateCard extends MushroomBaseCard implements LovelaceCard { } const rtl = computeRTL(this.hass); + const isControlVisible = + (!this._config.collapsible_controls || isActive(stateObj)) && this._controls.length; + return html` @@ -182,10 +179,11 @@ export class ClimateCard extends MushroomBaseCard implements LovelaceCard { ${this.renderBadge(stateObj)} ${this.renderStateInfo(stateObj, appearance, name, stateDisplay)}; - ${this._controls.length > 0 + ${isControlVisible ? html`
- ${this.renderActiveControl(stateObj)}${this.renderOtherControls()} + ${this.renderActiveControl(stateObj)} + ${this.renderOtherControls()}
` : nothing} diff --git a/src/cards/cover-card/cover-card.ts b/src/cards/cover-card/cover-card.ts index d647d1bb4..b4e1bfd22 100644 --- a/src/cards/cover-card/cover-card.ts +++ b/src/cards/cover-card/cover-card.ts @@ -51,7 +51,10 @@ registerCustomCard({ }); @customElement(COVER_CARD_NAME) -export class CoverCard extends MushroomBaseCard implements LovelaceCard { +export class CoverCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./cover-card-editor"); return document.createElement(COVER_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -66,12 +69,12 @@ export class CoverCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: CoverCardConfig; + protected get hasControls(): boolean { + return this._controls.length > 0; + } @state() private _activeControl?: CoverCardControl; - @state() private _controls: CoverCardControl[] = []; - get _nextControl(): CoverCardControl | undefined { if (this._activeControl) { return ( @@ -91,7 +94,7 @@ export class CoverCard extends MushroomBaseCard implements LovelaceCard { } setConfig(config: CoverCardConfig): void { - this._config = { + super.setConfig({ tap_action: { action: "toggle", }, @@ -99,26 +102,38 @@ export class CoverCard extends MushroomBaseCard implements LovelaceCard { action: "more-info", }, ...config, - }; + }); + this.updateActiveControl(); + this.updatePosition(); + } + + private get _controls(): CoverCardControl[] { + if (!this._config || !this._stateObj) return []; const controls: CoverCardControl[] = []; - if (this._config?.show_buttons_control) { + if (this._config.show_buttons_control) { controls.push("buttons_control"); } - if (this._config?.show_position_control) { + if (this._config.show_position_control) { controls.push("position_control"); } - if (this._config?.show_tilt_position_control) { + if (this._config.show_tilt_position_control) { controls.push("tilt_position_control"); } - this._controls = controls; - this._activeControl = controls[0]; - this.updatePosition(); + return controls; + } + + updateActiveControl() { + const isActiveControlSupported = this._activeControl + ? this._controls.includes(this._activeControl) + : false; + this._activeControl = isActiveControlSupported ? this._activeControl : this._controls[0]; } protected updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (this.hass && changedProperties.has("hass")) { this.updatePosition(); + this.updateActiveControl(); } } @@ -127,10 +142,7 @@ export class CoverCard extends MushroomBaseCard implements LovelaceCard { updatePosition() { this.position = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as CoverEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) return; this.position = getPosition(stateObj); @@ -151,8 +163,7 @@ export class CoverCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as CoverEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/entity-card/entity-card.ts b/src/cards/entity-card/entity-card.ts index 81dd1b609..da9cc9cca 100644 --- a/src/cards/entity-card/entity-card.ts +++ b/src/cards/entity-card/entity-card.ts @@ -1,6 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, nothing, TemplateResult } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { styleMap } from "lit/directives/style-map.js"; import { @@ -36,7 +36,7 @@ registerCustomCard({ }); @customElement(ENTITY_CARD_NAME) -export class EntityCard extends MushroomBaseCard implements LovelaceCard { +export class EntityCard extends MushroomBaseCard implements LovelaceCard { public static async getConfigElement(): Promise { await import("./entity-card-editor"); return document.createElement(ENTITY_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -50,24 +50,6 @@ export class EntityCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: EntityCardConfig; - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: EntityCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; - } - private _handleAction(ev: ActionHandlerEvent) { handleAction(this, this.hass!, this._config!, ev.detail.action!); } @@ -77,8 +59,7 @@ export class EntityCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/fan-card/controls/fan-oscillate-control.ts b/src/cards/fan-card/controls/fan-oscillate-control.ts index fc928fe98..8ec149f6e 100644 --- a/src/cards/fan-card/controls/fan-oscillate-control.ts +++ b/src/cards/fan-card/controls/fan-oscillate-control.ts @@ -33,9 +33,7 @@ export class FanPercentageControl extends LitElement { .disabled=${!active} > `; diff --git a/src/cards/fan-card/fan-card.ts b/src/cards/fan-card/fan-card.ts index 92edea9b1..44a9f3ebf 100644 --- a/src/cards/fan-card/fan-card.ts +++ b/src/cards/fan-card/fan-card.ts @@ -41,7 +41,7 @@ registerCustomCard({ }); @customElement(FAN_CARD_NAME) -export class FanCard extends MushroomBaseCard implements LovelaceCard { +export class FanCard extends MushroomBaseCard implements LovelaceCard { public static async getConfigElement(): Promise { await import("./fan-card-editor"); return document.createElement(FAN_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -56,14 +56,15 @@ export class FanCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: FanCardConfig; - - getCardSize(): number | Promise { - return 1; + protected get hasControls(): boolean { + return ( + Boolean(this._config?.show_percentage_control) || + Boolean(this._config?.show_oscillate_control) + ); } setConfig(config: FanCardConfig): void { - this._config = { + super.setConfig({ tap_action: { action: "toggle", }, @@ -71,7 +72,7 @@ export class FanCard extends MushroomBaseCard implements LovelaceCard { action: "more-info", }, ...config, - }; + }); this.updatePercentage(); } @@ -87,12 +88,8 @@ export class FanCard extends MushroomBaseCard implements LovelaceCard { updatePercentage() { this.percentage = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; - - if (!stateObj) return; + const stateObj = this._stateObj; + if (!this._config || !this.hass || !stateObj) return; this.percentage = getPercentage(stateObj); } @@ -111,8 +108,7 @@ export class FanCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/humidifier-card/humidifier-card.ts b/src/cards/humidifier-card/humidifier-card.ts index c21e70643..a415d9ff4 100644 --- a/src/cards/humidifier-card/humidifier-card.ts +++ b/src/cards/humidifier-card/humidifier-card.ts @@ -42,7 +42,10 @@ registerCustomCard({ }); @customElement(HUMIDIFIER_CARD_NAME) -export class HumidifierCard extends MushroomBaseCard implements LovelaceCard { +export class HumidifierCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./humidifier-card-editor"); return document.createElement(HUMIDIFIER_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -59,16 +62,14 @@ export class HumidifierCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: HumidifierCardConfig; - @state() private humidity?: number; - getCardSize(): number | Promise { - return 1; + protected get hasControls(): boolean { + return Boolean(this._config?.show_target_humidity_control); } setConfig(config: HumidifierCardConfig): void { - this._config = { + super.setConfig({ tap_action: { action: "toggle", }, @@ -76,7 +77,7 @@ export class HumidifierCard extends MushroomBaseCard implements LovelaceCard { action: "more-info", }, ...config, - }; + }); } private _handleAction(ev: ActionHandlerEvent) { @@ -94,8 +95,7 @@ export class HumidifierCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HumidifierEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/light-card/light-card.ts b/src/cards/light-card/light-card.ts index a2153d801..89b270ae0 100644 --- a/src/cards/light-card/light-card.ts +++ b/src/cards/light-card/light-card.ts @@ -59,7 +59,10 @@ registerCustomCard({ }); @customElement(LIGHT_CARD_NAME) -export class LightCard extends MushroomBaseCard implements LovelaceCard { +export class LightCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./light-card-editor"); return document.createElement(LIGHT_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -74,23 +77,33 @@ export class LightCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: LightCardConfig; - @state() private _activeControl?: LightCardControl; - @state() private _controls: LightCardControl[] = []; + @state() private brightness?: number; - _onControlTap(ctrl, e): void { - e.stopPropagation(); - this._activeControl = ctrl; + private get _controls(): LightCardControl[] { + if (!this._config || !this._stateObj) return []; + + const stateObj = this._stateObj; + const controls: LightCardControl[] = []; + if (this._config.show_brightness_control && supportsBrightnessControl(stateObj)) { + controls.push("brightness_control"); + } + if (this._config.show_color_temp_control && supportsColorTempControl(stateObj)) { + controls.push("color_temp_control"); + } + if (this._config.show_color_control && supportsColorControl(stateObj)) { + controls.push("color_control"); + } + return controls; } - getCardSize(): number | Promise { - return 1; + protected get hasControls(): boolean { + return this._controls.length > 0; } setConfig(config: LightCardConfig): void { - this._config = { + super.setConfig({ tap_action: { action: "toggle", }, @@ -98,28 +111,27 @@ export class LightCard extends MushroomBaseCard implements LovelaceCard { action: "more-info", }, ...config, - }; - this.updateControls(); + }); + this.updateActiveControl(); this.updateBrightness(); } + _onControlTap(ctrl, e): void { + e.stopPropagation(); + this._activeControl = ctrl; + } + protected updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (this.hass && changedProperties.has("hass")) { - this.updateControls(); + this.updateActiveControl(); this.updateBrightness(); } } - @state() - private brightness?: number; - updateBrightness() { this.brightness = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as LightEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) return; this.brightness = getBrightness(stateObj); @@ -131,31 +143,11 @@ export class LightCard extends MushroomBaseCard implements LovelaceCard { } } - updateControls() { - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as LightEntity | undefined; - - if (!stateObj) return; - - const controls: LightCardControl[] = []; - if (!this._config.collapsible_controls || isActive(stateObj)) { - if (this._config.show_brightness_control && supportsBrightnessControl(stateObj)) { - controls.push("brightness_control"); - } - if (this._config.show_color_temp_control && supportsColorTempControl(stateObj)) { - controls.push("color_temp_control"); - } - if (this._config.show_color_control && supportsColorControl(stateObj)) { - controls.push("color_control"); - } - } - this._controls = controls; + updateActiveControl() { const isActiveControlSupported = this._activeControl - ? controls.includes(this._activeControl) + ? this._controls.includes(this._activeControl) : false; - this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; + this._activeControl = isActiveControlSupported ? this._activeControl : this._controls[0]; } private _handleAction(ev: ActionHandlerEvent) { @@ -167,8 +159,7 @@ export class LightCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as LightEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); @@ -194,6 +185,9 @@ export class LightCard extends MushroomBaseCard implements LovelaceCard { const rtl = computeRTL(this.hass); + const isControlVisible = + (!this._config.collapsible_controls || isActive(stateObj)) && this._controls.length; + return html` @@ -210,7 +204,7 @@ export class LightCard extends MushroomBaseCard implements LovelaceCard { ${this.renderBadge(stateObj)} ${this.renderStateInfo(stateObj, appearance, name, stateDisplay)}; - ${this._controls.length > 0 + ${isControlVisible ? html`
${this.renderActiveControl(stateObj)} diff --git a/src/cards/lock-card/controls/lock-buttons-control.ts b/src/cards/lock-card/controls/lock-buttons-control.ts index d21bab6c6..708a9f0d7 100644 --- a/src/cards/lock-card/controls/lock-buttons-control.ts +++ b/src/cards/lock-card/controls/lock-buttons-control.ts @@ -54,7 +54,7 @@ export class LockButtonsControl extends LitElement { @property({ attribute: false }) public entity!: LockEntity; - @property({ type: Boolean }) public fill: boolean = false; + @property({ type: Boolean }) public fill: boolean = false; private callService(e: CustomEvent) { e.stopPropagation(); diff --git a/src/cards/lock-card/lock-card.ts b/src/cards/lock-card/lock-card.ts index fc1d9ade5..5c2f711a9 100644 --- a/src/cards/lock-card/lock-card.ts +++ b/src/cards/lock-card/lock-card.ts @@ -37,7 +37,7 @@ registerCustomCard({ }); @customElement(LOCK_CARD_NAME) -export class LockCard extends MushroomBaseCard implements LovelaceCard { +export class LockCard extends MushroomBaseCard implements LovelaceCard { public static async getConfigElement(): Promise { await import("./lock-card-editor"); return document.createElement(LOCK_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -52,22 +52,8 @@ export class LockCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: LockCardConfig; - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: LockCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; + protected get hasControls(): boolean { + return true; } private _handleAction(ev: ActionHandlerEvent) { @@ -79,8 +65,7 @@ export class LockCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as LockEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/media-player-card/controls/media-player-media-control.ts b/src/cards/media-player-card/controls/media-player-media-control.ts index 5466c7433..8e8b3a5f9 100644 --- a/src/cards/media-player-card/controls/media-player-media-control.ts +++ b/src/cards/media-player-card/controls/media-player-media-control.ts @@ -17,7 +17,7 @@ export class MediaPlayerMediaControls extends LitElement { @property({ attribute: false }) public controls!: MediaPlayerMediaControl[]; - @property({ type: Boolean }) public fill: boolean = false; + @property({ type: Boolean }) public fill: boolean = false; private _handleClick(e: MouseEvent): void { e.stopPropagation(); diff --git a/src/cards/media-player-card/controls/media-player-volume-control.ts b/src/cards/media-player-card/controls/media-player-volume-control.ts index 9a047baaa..5c9f4ca24 100644 --- a/src/cards/media-player-card/controls/media-player-volume-control.ts +++ b/src/cards/media-player-card/controls/media-player-volume-control.ts @@ -31,7 +31,7 @@ export class MediaPlayerVolumeControls extends LitElement { @property({ attribute: false }) public entity!: MediaPlayerEntity; - @property({ type: Boolean }) public fill: boolean = false; + @property({ type: Boolean }) public fill: boolean = false; @property({ attribute: false }) public controls!: MediaPlayerVolumeControl[]; diff --git a/src/cards/media-player-card/media-player-card.ts b/src/cards/media-player-card/media-player-card.ts index 3526058e9..6e92db76f 100644 --- a/src/cards/media-player-card/media-player-card.ts +++ b/src/cards/media-player-card/media-player-card.ts @@ -55,7 +55,10 @@ registerCustomCard({ }); @customElement(MEDIA_PLAYER_CARD_NAME) -export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { +export class MediaPlayerCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./media-player-card-editor"); return document.createElement(MEDIA_PLAYER_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -72,39 +75,45 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: MediaPlayerCardConfig; - @state() private _activeControl?: MediaPlayerCardControl; - @state() private _controls: MediaPlayerCardControl[] = []; + protected get hasControls(): boolean { + return ( + Boolean(this._config?.media_controls?.length) || + Boolean(this._config?.volume_controls?.length) + ); + } + + private get _controls(): MediaPlayerCardControl[] { + if (!this._config || !this._stateObj) return []; + + const stateObj = this._stateObj; + + const controls: MediaPlayerCardControl[] = []; + if (isMediaControlVisible(stateObj, this._config.media_controls)) { + controls.push("media_control"); + } + if (isVolumeControlVisible(stateObj, this._config.volume_controls)) { + controls.push("volume_control"); + } + return controls; + } _onControlTap(ctrl, e): void { e.stopPropagation(); this._activeControl = ctrl; } - getCardSize(): number | Promise { - return 1; - } - setConfig(config: MediaPlayerCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; - this.updateControls(); + super.setConfig(config); + this.updateActiveControl(); this.updateVolume(); } protected updated(changedProperties: PropertyValues) { super.updated(changedProperties); if (this.hass && changedProperties.has("hass")) { - this.updateControls(); + this.updateActiveControl(); this.updateVolume(); } } @@ -114,10 +123,7 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { updateVolume() { this.volume = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as MediaPlayerEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) return; const volume = getVolumeLevel(stateObj); @@ -130,29 +136,11 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { } } - updateControls() { - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as MediaPlayerEntity | undefined; - - if (!stateObj) return; - - const controls: MediaPlayerCardControl[] = []; - if (!this._config.collapsible_controls || isActive(stateObj)) { - if (isMediaControlVisible(stateObj, this._config?.media_controls)) { - controls.push("media_control"); - } - if (isVolumeControlVisible(stateObj, this._config.volume_controls)) { - controls.push("volume_control"); - } - } - - this._controls = controls; + updateActiveControl() { const isActiveControlSupported = this._activeControl - ? controls.includes(this._activeControl) + ? this._controls.includes(this._activeControl) : false; - this._activeControl = isActiveControlSupported ? this._activeControl : controls[0]; + this._activeControl = isActiveControlSupported ? this._activeControl : this._controls[0]; } private _handleAction(ev: ActionHandlerEvent) { @@ -164,8 +152,7 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as MediaPlayerEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); @@ -184,8 +171,16 @@ export class MediaPlayerCard extends MushroomBaseCard implements LovelaceCard { const rtl = computeRTL(this.hass); + const isControlVisible = + (!this._config.collapsible_controls || isActive(stateObj)) && this._controls.length; + return html` - + - ${this._controls.length > 0 + ${isControlVisible ? html`
${this.renderActiveControl(stateObj, appearance.layout)} diff --git a/src/cards/number-card/number-card.ts b/src/cards/number-card/number-card.ts index a653f70e5..588887f65 100644 --- a/src/cards/number-card/number-card.ts +++ b/src/cards/number-card/number-card.ts @@ -42,7 +42,7 @@ registerCustomCard({ }); @customElement(NUMBER_CARD_NAME) -export class NumberCard extends MushroomBaseCard implements LovelaceCard { +export class NumberCard extends MushroomBaseCard implements LovelaceCard { public static async getConfigElement(): Promise { await import("./number-card-editor"); return document.createElement(NUMBER_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -57,25 +57,11 @@ export class NumberCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: NumberCardConfig; - - @state() private value?: number; - - getCardSize(): number | Promise { - return 1; + protected get hasControls(): boolean { + return true; } - setConfig(config: NumberCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; - } + @state() private value?: number; private _handleAction(ev: ActionHandlerEvent) { handleAction(this, this.hass!, this._config!, ev.detail.action!); @@ -96,10 +82,7 @@ export class NumberCard extends MushroomBaseCard implements LovelaceCard { updateValue() { this.value = undefined; - if (!this._config || !this.hass || !this._config.entity) return; - - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj || Number.isNaN(stateObj.state)) return; this.value = Number(stateObj.state); @@ -110,8 +93,7 @@ export class NumberCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/person-card/person-card.ts b/src/cards/person-card/person-card.ts index 969bab5e2..8cef3d0ba 100644 --- a/src/cards/person-card/person-card.ts +++ b/src/cards/person-card/person-card.ts @@ -1,6 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, nothing } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { styleMap } from "lit/directives/style-map.js"; import { @@ -36,7 +36,7 @@ registerCustomCard({ }); @customElement(PERSON_CARD_NAME) -export class PersonCard extends MushroomBaseCard implements LovelaceCard { +export class PersonCard extends MushroomBaseCard implements LovelaceCard { public static async getConfigElement(): Promise { await import("./person-card-editor"); return document.createElement(PERSON_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -51,24 +51,6 @@ export class PersonCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: PersonCardConfig; - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: PersonCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; - } - private _handleAction(ev: ActionHandlerEvent) { handleAction(this, this.hass!, this._config!, ev.detail.action!); } @@ -78,8 +60,7 @@ export class PersonCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/select-card/select-card.ts b/src/cards/select-card/select-card.ts index 416ae4abd..91cc83f5d 100644 --- a/src/cards/select-card/select-card.ts +++ b/src/cards/select-card/select-card.ts @@ -37,7 +37,7 @@ registerCustomCard({ }); @customElement(SELECT_CARD_NAME) -export class SelectCard extends MushroomBaseCard implements LovelaceCard { +export class SelectCard extends MushroomBaseCard implements LovelaceCard { public static async getConfigElement(): Promise { await import("./select-card-editor"); return document.createElement(SELECT_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -52,22 +52,8 @@ export class SelectCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: SelectCardConfig; - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: SelectCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; + protected get hasControls(): boolean { + return true; } private _handleAction(ev: ActionHandlerEvent) { @@ -79,8 +65,7 @@ export class SelectCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as HassEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/template-card/template-card.ts b/src/cards/template-card/template-card.ts index 86233c856..c5274a0f6 100644 --- a/src/cards/template-card/template-card.ts +++ b/src/cards/template-card/template-card.ts @@ -1,6 +1,6 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, nothing, PropertyValues, TemplateResult } from "lit"; -import { customElement, state } from "lit/decorators.js"; +import { customElement, property, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { styleMap } from "lit/directives/style-map.js"; import { @@ -69,8 +69,33 @@ export class TemplateCard extends MushroomBaseElement implements LovelaceCard { @state() private _unsubRenderTemplates: Map> = new Map(); - getCardSize(): number | Promise { - return 1; + @property({ attribute: "in-grid", reflect: true, type: Boolean }) + protected _inGrid = false; + + public getCardSize(): number | Promise { + let height = 1; + if (!this._config) return height; + const appearance = computeAppearance(this._config); + if (appearance.layout === "vertical") { + height += 1; + } + return height; + } + + public getGridSize(): [number, number] { + this._inGrid = true; + let column = 2; + let row = 1; + if (!this._config) return [column, row]; + + const appearance = computeAppearance(this._config); + if (appearance.layout === "vertical") { + row += 1; + } + if (appearance.layout === "horizontal") { + column = 4; + } + return [column, row]; } setConfig(config: TemplateCardConfig): void { diff --git a/src/cards/update-card/update-card.ts b/src/cards/update-card/update-card.ts index e7b0f863f..fe98c3cd1 100644 --- a/src/cards/update-card/update-card.ts +++ b/src/cards/update-card/update-card.ts @@ -40,7 +40,10 @@ registerCustomCard({ }); @customElement(UPDATE_CARD_NAME) -export class UpdateCard extends MushroomBaseCard implements LovelaceCard { +export class UpdateCard + extends MushroomBaseCard + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./update-card-editor"); return document.createElement(UPDATE_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -55,22 +58,12 @@ export class UpdateCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: UpdateCardConfig; - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: UpdateCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; + protected get hasControls() { + if (!this._stateObj || !this._config) return false; + return ( + Boolean(this._config.show_buttons_control) && + supportsFeature(this._stateObj, UPDATE_SUPPORT_INSTALL) + ); } private _handleAction(ev: ActionHandlerEvent) { @@ -82,8 +75,7 @@ export class UpdateCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as UpdateEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/cards/vacuum-card/controls/vacuum-commands-control.ts b/src/cards/vacuum-card/controls/vacuum-commands-control.ts index ee689eca8..f937aefd3 100644 --- a/src/cards/vacuum-card/controls/vacuum-commands-control.ts +++ b/src/cards/vacuum-card/controls/vacuum-commands-control.ts @@ -22,88 +22,102 @@ import { VacuumCommand } from "../vacuum-card-config"; interface VacuumButton { icon: string; serviceName: string; - isVisible: (entity: VacuumEntity, commands: VacuumCommand[]) => boolean; + command: VacuumCommand; + isSupported: (entity: VacuumEntity) => boolean; + isVisible?: (entity: VacuumEntity) => boolean; isDisabled: (entity: VacuumEntity) => boolean; } +export const isButtonVisible = ( + entity: VacuumEntity, + button: VacuumButton, + commands: VacuumCommand[] +) => isButtonSupported(entity, button, commands) && (!button.isVisible || button.isVisible(entity)); + +export const isButtonSupported = ( + entity: VacuumEntity, + button: VacuumButton, + commands: VacuumCommand[] +) => button.isSupported(entity) && commands.includes(button.command); + export const isCommandsControlVisible = (entity: VacuumEntity, commands: VacuumCommand[]) => - VACUUM_BUTTONS.some((item) => item.isVisible(entity, commands)); + VACUUM_BUTTONS.some((button) => isButtonVisible(entity, button, commands)); + +export const isCommandsControlSupported = (entity: VacuumEntity, commands: VacuumCommand[]) => + VACUUM_BUTTONS.some((button) => isButtonSupported(entity, button, commands)); export const VACUUM_BUTTONS: VacuumButton[] = [ { icon: "mdi:power", serviceName: "turn_on", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_TURN_ON) && - commands.includes("on_off") && - !isActive(entity), + command: "on_off", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_TURN_ON), + isVisible: (entity) => !isActive(entity), isDisabled: () => false, }, { icon: "mdi:power", serviceName: "turn_off", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_TURN_OFF) && - commands.includes("on_off") && - isActive(entity), + command: "on_off", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_TURN_OFF), + isVisible: (entity) => isActive(entity), isDisabled: () => false, }, { icon: "mdi:play", serviceName: "start", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_START) && - commands.includes("start_pause") && - !isCleaning(entity), + command: "start_pause", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_START), + isVisible: (entity) => !isCleaning(entity), isDisabled: () => false, }, { icon: "mdi:pause", serviceName: "pause", - isVisible: (entity, commands) => + command: "start_pause", + isSupported: (entity) => // We need also to check if Start is supported because if not we show play-pause supportsFeature(entity, VACUUM_SUPPORT_START) && - supportsFeature(entity, VACUUM_SUPPORT_PAUSE) && - commands.includes("start_pause") && - isCleaning(entity), + supportsFeature(entity, VACUUM_SUPPORT_PAUSE), + isVisible: (entity) => isCleaning(entity), isDisabled: () => false, }, { icon: "mdi:play-pause", serviceName: "start_pause", - isVisible: (entity, commands) => + command: "start_pause", + isSupported: (entity) => // If start is supported, we don't show this button !supportsFeature(entity, VACUUM_SUPPORT_START) && - supportsFeature(entity, VACUUM_SUPPORT_PAUSE) && - commands.includes("start_pause"), + supportsFeature(entity, VACUUM_SUPPORT_PAUSE), isDisabled: () => false, }, { icon: "mdi:stop", serviceName: "stop", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_STOP) && commands.includes("stop"), + command: "stop", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_STOP), isDisabled: (entity) => isStopped(entity), }, { icon: "mdi:target-variant", serviceName: "clean_spot", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_CLEAN_SPOT) && commands.includes("clean_spot"), + command: "clean_spot", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_CLEAN_SPOT), isDisabled: () => false, }, { icon: "mdi:map-marker", serviceName: "locate", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_LOCATE) && commands.includes("locate"), + command: "locate", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_LOCATE), isDisabled: (entity) => isReturningHome(entity), }, { icon: "mdi:home-map-marker", serviceName: "return_to_base", - isVisible: (entity, commands) => - supportsFeature(entity, VACUUM_SUPPORT_RETURN_HOME) && commands.includes("return_home"), + command: "return_home", + isSupported: (entity) => supportsFeature(entity, VACUUM_SUPPORT_RETURN_HOME), isDisabled: () => false, }, ]; @@ -131,7 +145,9 @@ export class CoverButtonsControl extends LitElement { return html` - ${VACUUM_BUTTONS.filter((item) => item.isVisible(this.entity, this.commands)).map( + ${VACUUM_BUTTONS.filter((item) => + isButtonVisible(this.entity, item, this.commands) + ).map( (item) => html` + implements LovelaceCard +{ public static async getConfigElement(): Promise { await import("./vacuum-card-editor"); return document.createElement(VACUUM_CARD_EDITOR_NAME) as LovelaceCardEditor; @@ -53,22 +59,9 @@ export class VacuumCard extends MushroomBaseCard implements LovelaceCard { }; } - @state() private _config?: VacuumCardConfig; - - getCardSize(): number | Promise { - return 1; - } - - setConfig(config: VacuumCardConfig): void { - this._config = { - tap_action: { - action: "more-info", - }, - hold_action: { - action: "more-info", - }, - ...config, - }; + protected get hasControls() { + if (!this._stateObj || !this._config) return false; + return isCommandsControlSupported(this._stateObj, this._config.commands ?? []); } private _handleAction(ev: ActionHandlerEvent) { @@ -80,8 +73,7 @@ export class VacuumCard extends MushroomBaseCard implements LovelaceCard { return nothing; } - const entityId = this._config.entity; - const stateObj = this.hass.states[entityId] as VacuumEntity | undefined; + const stateObj = this._stateObj; if (!stateObj) { return this.renderNotFound(this._config); diff --git a/src/shared/card.ts b/src/shared/card.ts index d8adb442b..cca62383b 100644 --- a/src/shared/card.ts +++ b/src/shared/card.ts @@ -32,7 +32,8 @@ export class Card extends LitElement { flex-shrink: 0; flex-grow: 0; box-sizing: border-box; - justify-content: center; + justify-content: space-between; + height: 100%; } .container > ::slotted(*:not(:last-child)) { margin-bottom: var(--spacing); diff --git a/src/shared/editor/layout-picker.ts b/src/shared/editor/layout-picker.ts index 6d5a24f81..803daa8bd 100644 --- a/src/shared/editor/layout-picker.ts +++ b/src/shared/editor/layout-picker.ts @@ -54,13 +54,12 @@ export class LayoutPicker extends LitElement { > ${LAYOUTS.map( - (layout) => - html` - - ${customLocalize(`editor.form.layout_picker.values.${layout}`)} - - - ` + (layout) => html` + + ${customLocalize(`editor.form.layout_picker.values.${layout}`)} + + + ` )} `; diff --git a/src/utils/base-card.ts b/src/utils/base-card.ts index 02ffd7bbe..982a8534a 100644 --- a/src/utils/base-card.ts +++ b/src/utils/base-card.ts @@ -1,5 +1,6 @@ import { HassEntity } from "home-assistant-js-websocket"; import { html, nothing, TemplateResult } from "lit"; +import { property, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { computeRTL, computeStateDisplay, HomeAssistant, isActive, isAvailable } from "../ha"; import setupCustomlocalize from "../localize"; @@ -21,7 +22,77 @@ export function computeDarkMode(hass?: HomeAssistant): boolean { if (!hass) return false; return (hass.themes as any).darkMode as boolean; } -export class MushroomBaseCard extends MushroomBaseElement { +export class MushroomBaseCard< + T extends BaseConfig = BaseConfig, + E extends HassEntity = HassEntity, +> extends MushroomBaseElement { + @state() protected _config?: T; + + @property({ attribute: "in-grid", reflect: true, type: Boolean }) + protected _inGrid = false; + + protected get _stateObj(): E | undefined { + if (!this._config || !this.hass || !this._config.entity) return undefined; + + const entityId = this._config.entity; + return this.hass.states[entityId] as E; + } + + protected get hasControls(): boolean { + return false; + } + + setConfig(config: T): void { + this._config = { + tap_action: { + action: "more-info", + }, + hold_action: { + action: "more-info", + }, + ...config, + }; + } + + public getGridSize(): [number, number] { + this._inGrid = true; + let column = 2; + let row = 1; + if (!this._config) return [column, row]; + const appearance = computeAppearance(this._config); + if (appearance.layout === "vertical") { + row += 1; + } + if (appearance.layout === "horizontal") { + column = 4; + } + if ( + appearance?.layout !== "horizontal" && + this.hasControls && + !("collapsible_controls" in this._config && this._config?.collapsible_controls) + ) { + row += 1; + } + return [column, row]; + } + + public getCardSize(): number | Promise { + let height = 1; + if (!this._config) return height; + const appearance = computeAppearance(this._config); + if (appearance.layout === "vertical") { + height += 1; + } + if ( + appearance?.layout !== "horizontal" && + this.hasControls && + !("collapsible_controls" in this._config && this._config?.collapsible_controls) + ) { + height += 1; + } + return height; + } + protected renderPicture(picture: string): TemplateResult { return html`