From c3718ff7ddfd1580fd6ccdcc62552455c3f61ec5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Oct 2020 10:31:14 +0100 Subject: [PATCH] Add Grid card (#7476) --- demo/src/configs/teachingbirds/lovelace.ts | 342 ++++++++---------- src/panels/lovelace/cards/hui-grid-card.ts | 75 ++++ src/panels/lovelace/cards/hui-stack-card.ts | 8 +- src/panels/lovelace/cards/types.ts | 5 + .../create-element/create-card-element.ts | 1 + src/panels/lovelace/editor/lovelace-cards.ts | 4 + 6 files changed, 250 insertions(+), 185 deletions(-) create mode 100644 src/panels/lovelace/cards/hui-grid-card.ts diff --git a/demo/src/configs/teachingbirds/lovelace.ts b/demo/src/configs/teachingbirds/lovelace.ts index f7684fde6d..135dde6a0b 100644 --- a/demo/src/configs/teachingbirds/lovelace.ts +++ b/demo/src/configs/teachingbirds/lovelace.ts @@ -7,205 +7,183 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ cards: [ { type: "custom:ha-demo-card" }, { + type: "grid", + columns: 4, cards: [ { - cards: [ + image: "/assets/teachingbirds/isa_square.jpg", + type: "picture-entity", + show_name: false, + tap_action: { + action: "more-info", + }, + entity: "sensor.presence_isa", + }, + { + image: "/assets/teachingbirds/Stefan_square.jpg", + type: "picture-entity", + show_name: false, + tap_action: { + action: "more-info", + }, + entity: "sensor.presence_stefan", + }, + { + image: "/assets/teachingbirds/background_square.png", + elements: [ { - image: "/assets/teachingbirds/isa_square.jpg", - type: "picture-entity", - show_name: false, + state_image: { + on: "/assets/teachingbirds/radiator_on.jpg", + off: "/assets/teachingbirds/radiator_off.jpg", + }, + type: "image", + style: { + width: "100%", + top: "50%", + left: "50%", + }, tap_action: { action: "more-info", }, - entity: "sensor.presence_isa", + entity: "switch.stefan_radiator_3", }, { - image: "/assets/teachingbirds/Stefan_square.jpg", - type: "picture-entity", - show_name: false, - tap_action: { - action: "more-info", + style: { + top: "90%", + left: "50%", }, - entity: "sensor.presence_stefan", - }, - { - image: "/assets/teachingbirds/background_square.png", - elements: [ - { - state_image: { - on: "/assets/teachingbirds/radiator_on.jpg", - off: "/assets/teachingbirds/radiator_off.jpg", - }, - type: "image", - style: { - width: "100%", - top: "50%", - left: "50%", - }, - tap_action: { - action: "more-info", - }, - entity: "switch.stefan_radiator_3", - }, - { - style: { - top: "90%", - left: "50%", - }, - type: "state-label", - entity: "sensor.temperature_stefan", - }, - ], - type: "picture-elements", - }, - { - image: "/assets/teachingbirds/background_square.png", - elements: [ - { - style: { - "--mdc-icon-size": "100%", - top: "50%", - left: "50%", - }, - type: "icon", - tap_action: { - action: "navigate", - navigation_path: "/lovelace/home_info", - }, - icon: "mdi:car", - }, - ], - type: "picture-elements", - }, - ], - type: "horizontal-stack", - }, - { - cards: [ - { - show_name: false, - type: "picture-entity", - name: "Alarm", - image: "/assets/teachingbirds/House_square.jpg", - entity: "alarm_control_panel.house", - }, - { - name: "Roomba", - image: "/assets/teachingbirds/roomba_square.jpg", - show_name: false, - type: "picture-entity", - state_image: { - "Not Today": "/assets/teachingbirds/roomba_bw_square.jpg", - }, - entity: "input_select.roomba_mode", - }, - { - show_name: false, - type: "picture-entity", - state_image: { - Mail: "/assets/teachingbirds/mailbox_square.jpg", - "Package and mail": - "/assets/teachingbirds/mailbox_square.jpg", - Empty: "/assets/teachingbirds/mailbox_bw_square.jpg", - Package: "/assets/teachingbirds/mailbox_square.jpg", - }, - entity: "sensor.mailbox", - }, - { - show_name: false, - state_image: { - "Put out": "/assets/teachingbirds/trash_square.jpg", - "Take in": "/assets/teachingbirds/trash_square.jpg", - }, - type: "picture-entity", - image: "/assets/teachingbirds/trash_bear_bw_square.jpg", - entity: "sensor.trash_status", - }, - ], - type: "horizontal-stack", - }, - { - cards: [ - { - state_image: { - Idle: "/assets/teachingbirds/washer_square.jpg", - Running: "/assets/teachingbirds/laundry_running_square.jpg", - Clean: "/assets/teachingbirds/laundry_clean_2_square.jpg", - }, - entity: "input_select.washing_machine_status", - type: "picture-entity", - show_name: false, - name: "Washer", - }, - { - state_image: { - Idle: "/assets/teachingbirds/dryer_square.jpg", - Running: "/assets/teachingbirds/clothes_drying_square.jpg", - Clean: "/assets/teachingbirds/folded_clothes_square.jpg", - }, - entity: "input_select.dryer_status", - type: "picture-entity", - show_name: false, - name: "Dryer", - }, - { - image: "/assets/teachingbirds/guests_square.jpg", - type: "picture-entity", - show_name: false, - tap_action: { - action: "toggle", - }, - entity: "input_boolean.guest_mode", - }, - { - image: "/assets/teachingbirds/cleaning_square.jpg", - type: "picture-entity", - show_name: false, - tap_action: { - action: "toggle", - }, - entity: "input_boolean.cleaning_day", - }, - ], - type: "horizontal-stack", - }, - ], - type: "vertical-stack", - }, - { - type: "vertical-stack", - cards: [ - { - cards: [ - { - graph: "line", - type: "sensor", - entity: "sensor.temperature_bedroom", - }, - { - graph: "line", - type: "sensor", - name: "S's room", + type: "state-label", entity: "sensor.temperature_stefan", }, ], - type: "horizontal-stack", + type: "picture-elements", }, { - cards: [ + image: "/assets/teachingbirds/background_square.png", + elements: [ { - graph: "line", - type: "sensor", - entity: "sensor.temperature_passage", - }, - { - graph: "line", - type: "sensor", - name: "Laundry", - entity: "sensor.temperature_downstairs_bathroom", + style: { + "--mdc-icon-size": "100%", + top: "50%", + left: "50%", + }, + type: "icon", + tap_action: { + action: "navigate", + navigation_path: "/lovelace/home_info", + }, + icon: "mdi:car", }, ], - type: "horizontal-stack", + type: "picture-elements", + }, + + { + show_name: false, + type: "picture-entity", + name: "Alarm", + image: "/assets/teachingbirds/House_square.jpg", + entity: "alarm_control_panel.house", + }, + { + name: "Roomba", + image: "/assets/teachingbirds/roomba_square.jpg", + show_name: false, + type: "picture-entity", + state_image: { + "Not Today": "/assets/teachingbirds/roomba_bw_square.jpg", + }, + entity: "input_select.roomba_mode", + }, + { + show_name: false, + type: "picture-entity", + state_image: { + Mail: "/assets/teachingbirds/mailbox_square.jpg", + "Package and mail": "/assets/teachingbirds/mailbox_square.jpg", + Empty: "/assets/teachingbirds/mailbox_bw_square.jpg", + Package: "/assets/teachingbirds/mailbox_square.jpg", + }, + entity: "sensor.mailbox", + }, + { + show_name: false, + state_image: { + "Put out": "/assets/teachingbirds/trash_square.jpg", + "Take in": "/assets/teachingbirds/trash_square.jpg", + }, + type: "picture-entity", + image: "/assets/teachingbirds/trash_bear_bw_square.jpg", + entity: "sensor.trash_status", + }, + + { + state_image: { + Idle: "/assets/teachingbirds/washer_square.jpg", + Running: "/assets/teachingbirds/laundry_running_square.jpg", + Clean: "/assets/teachingbirds/laundry_clean_2_square.jpg", + }, + entity: "input_select.washing_machine_status", + type: "picture-entity", + show_name: false, + name: "Washer", + }, + { + state_image: { + Idle: "/assets/teachingbirds/dryer_square.jpg", + Running: "/assets/teachingbirds/clothes_drying_square.jpg", + Clean: "/assets/teachingbirds/folded_clothes_square.jpg", + }, + entity: "input_select.dryer_status", + type: "picture-entity", + show_name: false, + name: "Dryer", + }, + { + image: "/assets/teachingbirds/guests_square.jpg", + type: "picture-entity", + show_name: false, + tap_action: { + action: "toggle", + }, + entity: "input_boolean.guest_mode", + }, + { + image: "/assets/teachingbirds/cleaning_square.jpg", + type: "picture-entity", + show_name: false, + tap_action: { + action: "toggle", + }, + entity: "input_boolean.cleaning_day", + }, + ], + }, + { + type: "grid", + columns: 2, + cards: [ + { + graph: "line", + type: "sensor", + entity: "sensor.temperature_bedroom", + }, + { + graph: "line", + type: "sensor", + name: "S's room", + entity: "sensor.temperature_stefan", + }, + { + graph: "line", + type: "sensor", + entity: "sensor.temperature_passage", + }, + { + graph: "line", + type: "sensor", + name: "Laundry", + entity: "sensor.temperature_downstairs_bathroom", }, ], }, diff --git a/src/panels/lovelace/cards/hui-grid-card.ts b/src/panels/lovelace/cards/hui-grid-card.ts new file mode 100644 index 0000000000..03f06998c2 --- /dev/null +++ b/src/panels/lovelace/cards/hui-grid-card.ts @@ -0,0 +1,75 @@ +import { css, CSSResult } from "lit-element"; +import { computeCardSize } from "../common/compute-card-size"; +import { HuiStackCard } from "./hui-stack-card"; +import { GridCardConfig } from "./types"; + +const DEFAULT_COLUMNS = 3; + +class HuiGridCard extends HuiStackCard { + public async getCardSize(): Promise { + if (!this._cards || !this._config) { + return 0; + } + + const promises: Array | number> = []; + + for (const element of this._cards) { + promises.push(computeCardSize(element)); + } + + const results = await Promise.all(promises); + + const maxCardSize = Math.max(...results); + + return maxCardSize * (this._cards.length / this.columns); + } + + get columns() { + return this._config!.columns || DEFAULT_COLUMNS; + } + + setConfig(config: GridCardConfig) { + super.setConfig(config); + this.style.setProperty("--grid-card-column-count", String(this.columns)); + this.toggleAttribute("square", config.square !== false); + } + + static get styles(): CSSResult[] { + return [ + super.sharedStyles, + css` + #root { + display: grid; + grid-template-columns: repeat( + var(--grid-card-column-count, ${DEFAULT_COLUMNS}), + minmax(0, 1fr) + ); + grid-gap: var(--grid-card-gap, 8px); + } + :host([square]) #root { + grid-auto-rows: 1fr; + } + :host([square]) #root::before { + content: ""; + width: 0; + padding-bottom: 100%; + grid-row: 1 / 1; + grid-column: 1 / 1; + } + + :host([square]) #root > *:first-child { + grid-row: 1 / 1; + grid-column: 1 / 1; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-grid-card": HuiGridCard; + } +} + +customElements.define("hui-grid-card", HuiGridCard); diff --git a/src/panels/lovelace/cards/hui-stack-card.ts b/src/panels/lovelace/cards/hui-stack-card.ts index dfe7a0ec6f..728cd51075 100644 --- a/src/panels/lovelace/cards/hui-stack-card.ts +++ b/src/panels/lovelace/cards/hui-stack-card.ts @@ -14,7 +14,9 @@ import { createCardElement } from "../create-element/create-card-element"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { StackCardConfig } from "./types"; -export abstract class HuiStackCard extends LitElement implements LovelaceCard { +export abstract class HuiStackCard + extends LitElement + implements LovelaceCard { public static async getConfigElement(): Promise { await import( /* webpackChunkName: "hui-stack-card-editor" */ "../editor/config-elements/hui-stack-card-editor" @@ -32,13 +34,13 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard { @property() protected _cards?: LovelaceCard[]; - @internalProperty() private _config?: StackCardConfig; + @internalProperty() protected _config?: T; public getCardSize(): number | Promise { return 1; } - public setConfig(config: StackCardConfig): void { + public setConfig(config: T): void { if (!config || !config.cards || !Array.isArray(config.cards)) { throw new Error("Card config incorrect"); } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index f7e173e72f..2c3c9cb6d8 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -288,6 +288,11 @@ export interface StackCardConfig extends LovelaceCardConfig { title?: string; } +export interface GridCardConfig extends StackCardConfig { + columns?: number; + square?: boolean; +} + export interface ThermostatCardConfig extends LovelaceCardConfig { entity: string; theme?: string; diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index bf62823dbb..a10781a209 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -37,6 +37,7 @@ const LAZY_LOAD_TYPES = { "alarm-panel": () => import("../cards/hui-alarm-panel-card"), error: () => import("../cards/hui-error-card"), "empty-state": () => import("../cards/hui-empty-state-card"), + grid: () => import("../cards/hui-grid-card"), starting: () => import("../cards/hui-starting-card"), "entity-filter": () => import("../cards/hui-entity-filter-card"), humidifier: () => import("../cards/hui-humidifier-card"), diff --git a/src/panels/lovelace/editor/lovelace-cards.ts b/src/panels/lovelace/editor/lovelace-cards.ts index 13c099a791..6da45eef00 100644 --- a/src/panels/lovelace/editor/lovelace-cards.ts +++ b/src/panels/lovelace/editor/lovelace-cards.ts @@ -29,6 +29,10 @@ export const coreCards: Card[] = [ type: "glance", showElement: true, }, + { + type: "grid", + showElement: true, + }, { type: "history-graph", showElement: true,