From aa2e632df328231c3ba5d0f46a30eeaa1ab8bf3b Mon Sep 17 00:00:00 2001 From: Zack Arnett Date: Tue, 3 Mar 2020 14:53:55 -0500 Subject: [PATCH] Card Picker with Previews of cards (#4975) * Card Picker with Previews of cards * Getting Preview Async - Using dialogs entities * Create generic getElement - filter entities before * lint * Add entities back to Picker. Set Qualifier * Style Updates * Move setup of filtered cards to connected * style updates * Dont pull entities if noEntity config * Move all config logic to getConfig * Style Update - Remove Manual process * lint * Accounting for ll-rebuild for async cards * Style Updates - Use GetStubConfig for most * Lint * Filter entities with function - style - no preview * Iframe rename and description * Move getstubconfig to helper - update spinner * Style for themes * Move entities to be calc once * Should update * oops * TSC * Comments --- .../lovelace/cards/hui-alarm-panel-card.ts | 25 +- src/panels/lovelace/cards/hui-button-card.ts | 20 +- .../lovelace/cards/hui-entities-card.ts | 20 +- src/panels/lovelace/cards/hui-gauge-card.ts | 29 +- src/panels/lovelace/cards/hui-glance-card.ts | 23 +- .../lovelace/cards/hui-history-graph-card.ts | 22 +- src/panels/lovelace/cards/hui-light-card.ts | 23 +- src/panels/lovelace/cards/hui-map-card.ts | 22 +- .../lovelace/cards/hui-media-control-card.ts | 22 +- .../cards/hui-picture-elements-card.ts | 30 ++ .../lovelace/cards/hui-picture-entity-card.ts | 22 +- .../lovelace/cards/hui-picture-glance-card.ts | 22 +- .../lovelace/cards/hui-plant-status-card.ts | 22 +- src/panels/lovelace/cards/hui-sensor-card.ts | 31 +- .../lovelace/cards/hui-thermostat-card.ts | 22 +- .../cards/hui-weather-forecast-card.ts | 23 +- .../common/compute-unused-entities.ts | 4 +- src/panels/lovelace/common/find-entites.ts | 59 ++++ .../editor/card-editor/hui-card-picker.ts | 268 +++++++++++++++--- .../card-editor/hui-dialog-edit-card.ts | 3 +- .../lovelace/editor/get-card-stub-config.ts | 28 ++ src/panels/lovelace/editor/types.ts | 2 +- src/panels/lovelace/types.ts | 7 +- src/translations/en.json | 21 +- 24 files changed, 679 insertions(+), 91 deletions(-) create mode 100644 src/panels/lovelace/common/find-entites.ts create mode 100644 src/panels/lovelace/editor/get-card-stub-config.ts diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts index ec368ae2ab..5c171ddb6b 100644 --- a/src/panels/lovelace/cards/hui-alarm-panel-card.ts +++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts @@ -24,6 +24,8 @@ import { import { AlarmPanelCardConfig } from "./types"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { findEntities } from "../common/find-entites"; +import { LovelaceConfig } from "../../../data/lovelace"; const ICONS = { armed_away: "hass:shield-lock", @@ -46,8 +48,27 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { return document.createElement("hui-alarm-panel-card-editor"); } - public static getStubConfig() { - return { states: ["arm_home", "arm_away"], entity: "" }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ) { + const includeDomains = ["alarm_control_panel"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { + states: ["arm_home", "arm_away"], + entity: foundEntities[0] || "", + }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index ba4df01747..6d38ae561d 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -30,9 +30,10 @@ import { ButtonCardConfig } from "./types"; import { actionHandler } from "../common/directives/action-handler-directive"; import { hasAction } from "../common/has-action"; import { handleAction } from "../common/handle-action"; -import { ActionHandlerEvent } from "../../../data/lovelace"; +import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace"; import { computeActiveState } from "../../../common/entity/compute_active_state"; import { iconColorCSS } from "../../../common/style/icon_color_css"; +import { findEntities } from "../common/find-entites"; @customElement("hui-button-card") export class HuiButtonCard extends LitElement implements LovelaceCard { @@ -43,13 +44,28 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { return document.createElement("hui-button-card-editor"); } - public static getStubConfig(): object { + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill + ); + return { tap_action: { action: "toggle" }, hold_action: { action: "more-info" }, show_icon: true, show_name: true, state_color: true, + entity: foundEntities[0] || "", }; } diff --git a/src/panels/lovelace/cards/hui-entities-card.ts b/src/panels/lovelace/cards/hui-entities-card.ts index 645b94452c..072e6eb1ba 100644 --- a/src/panels/lovelace/cards/hui-entities-card.ts +++ b/src/panels/lovelace/cards/hui-entities-card.ts @@ -27,6 +27,8 @@ import { createHeaderFooterElement } from "../create-element/create-header-foote import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { DOMAINS_TOGGLE } from "../../../common/const"; import { computeDomain } from "../../../common/entity/compute_domain"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; @customElement("hui-entities-card") class HuiEntitiesCard extends LitElement implements LovelaceCard { @@ -37,8 +39,22 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { return document.createElement("hui-entities-card-editor"); } - public static getStubConfig(): object { - return { entities: [] }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ) { + const maxEntities = 3; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill + ); + + return { entities: foundEntities }; } @property() private _config?: EntitiesCardConfig; diff --git a/src/panels/lovelace/cards/hui-gauge-card.ts b/src/panels/lovelace/cards/hui-gauge-card.ts index 06f0ada6ec..def7bf77f8 100644 --- a/src/panels/lovelace/cards/hui-gauge-card.ts +++ b/src/panels/lovelace/cards/hui-gauge-card.ts @@ -22,6 +22,9 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { GaugeCardConfig } from "./types"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; export const severityMap = { red: "var(--label-badge-red)", @@ -38,8 +41,30 @@ class HuiGaugeCard extends LitElement implements LovelaceCard { ); return document.createElement("hui-gauge-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["sensor"]; + const maxEntities = 1; + const entityFilter = (stateObj: HassEntity): boolean => { + return !isNaN(Number(stateObj.state)); + }; + + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains, + entityFilter + ); + + return { entity: foundEntities[0] || "" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-glance-card.ts b/src/panels/lovelace/cards/hui-glance-card.ts index 994216c2cc..b1288f4276 100644 --- a/src/panels/lovelace/cards/hui-glance-card.ts +++ b/src/panels/lovelace/cards/hui-glance-card.ts @@ -27,10 +27,11 @@ import { processConfigEntities } from "../common/process-config-entities"; import { GlanceCardConfig, GlanceConfigEntity } from "./types"; import { actionHandler } from "../common/directives/action-handler-directive"; import { hasAction } from "../common/has-action"; -import { ActionHandlerEvent } from "../../../data/lovelace"; +import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace"; import { handleAction } from "../common/handle-action"; import { computeDomain } from "../../../common/entity/compute_domain"; import { UNAVAILABLE, UNKNOWN } from "../../../data/entity"; +import { findEntities } from "../common/find-entites"; @customElement("hui-glance-card") export class HuiGlanceCard extends LitElement implements LovelaceCard { @@ -41,8 +42,24 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard { return document.createElement("hui-glance-card-editor"); } - public static getStubConfig(): object { - return { entities: [] }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["sensor"]; + const maxEntities = 3; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entities: foundEntities }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-history-graph-card.ts b/src/panels/lovelace/cards/hui-history-graph-card.ts index 02a895ac99..25922076fc 100644 --- a/src/panels/lovelace/cards/hui-history-graph-card.ts +++ b/src/panels/lovelace/cards/hui-history-graph-card.ts @@ -20,6 +20,8 @@ import { HomeAssistant } from "../../../types"; import { HistoryGraphCardConfig } from "./types"; import { LovelaceCard } from "../types"; import { EntityConfig } from "../entity-rows/types"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; @customElement("hui-history-graph-card") export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { @@ -30,8 +32,24 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { return document.createElement("hui-history-graph-card-editor"); } - public static getStubConfig() { - return { entities: [] }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["sensor"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entities: foundEntities }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts index 0f3eb52e7f..29a7791afc 100644 --- a/src/panels/lovelace/cards/hui-light-card.ts +++ b/src/panels/lovelace/cards/hui-light-card.ts @@ -29,6 +29,8 @@ import { toggleEntity } from "../common/entity/toggle-entity"; import { LightCardConfig } from "./types"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { SUPPORT_BRIGHTNESS } from "../../../data/light"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; import { UNAVAILABLE } from "../../../data/entity"; @customElement("hui-light-card") @@ -39,8 +41,25 @@ export class HuiLightCard extends LitElement implements LovelaceCard { ); return document.createElement("hui-light-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["light"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entity: foundEntities[0] || "" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-map-card.ts b/src/panels/lovelace/cards/hui-map-card.ts index fa9355018d..269b0deebf 100644 --- a/src/panels/lovelace/cards/hui-map-card.ts +++ b/src/panels/lovelace/cards/hui-map-card.ts @@ -30,6 +30,8 @@ import { EntityConfig } from "../entity-rows/types"; import { processConfigEntities } from "../common/process-config-entities"; import { MapCardConfig } from "./types"; import { classMap } from "lit-html/directives/class-map"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; @customElement("hui-map-card") class HuiMapCard extends LitElement implements LovelaceCard { @@ -40,8 +42,24 @@ class HuiMapCard extends LitElement implements LovelaceCard { return document.createElement("hui-map-card-editor"); } - public static getStubConfig() { - return { entities: [] }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["device_tracker"]; + const maxEntities = 2; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entities: foundEntities }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-media-control-card.ts b/src/panels/lovelace/cards/hui-media-control-card.ts index d596f0befe..4071b8dc7b 100644 --- a/src/panels/lovelace/cards/hui-media-control-card.ts +++ b/src/panels/lovelace/cards/hui-media-control-card.ts @@ -34,6 +34,8 @@ import { LovelaceCard, LovelaceCardEditor } from "../types"; import { fireEvent } from "../../../common/dom/fire_event"; import { MediaControlCardConfig } from "./types"; import { UNAVAILABLE } from "../../../data/entity"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; @customElement("hui-media-control-card") export class HuiMediaControlCard extends LitElement implements LovelaceCard { @@ -44,8 +46,24 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard { return document.createElement("hui-media-control-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["media_player"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entity: foundEntities[0] || "" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-picture-elements-card.ts b/src/panels/lovelace/cards/hui-picture-elements-card.ts index 7db1aff9a8..d8408bb55f 100644 --- a/src/panels/lovelace/cards/hui-picture-elements-card.ts +++ b/src/panels/lovelace/cards/hui-picture-elements-card.ts @@ -15,9 +15,39 @@ import { HomeAssistant } from "../../../types"; import { LovelaceElementConfig, LovelaceElement } from "../elements/types"; import { PictureElementsCardConfig } from "./types"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; @customElement("hui-picture-elements-card") class HuiPictureElementsCard extends LitElement implements LovelaceCard { + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill + ); + + return { + elements: [ + { + type: "state-badge", + entity: foundEntities[0] || "", + style: "position: absolute, transform: translate(-50%, -50%)", + }, + ], + image: + "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", + }; + } + @property() private _config?: PictureElementsCardConfig; private _hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-picture-entity-card.ts b/src/panels/lovelace/cards/hui-picture-entity-card.ts index 187da7ec35..047cc84e6d 100644 --- a/src/panels/lovelace/cards/hui-picture-entity-card.ts +++ b/src/panels/lovelace/cards/hui-picture-entity-card.ts @@ -27,8 +27,9 @@ import { PictureEntityCardConfig } from "./types"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { actionHandler } from "../common/directives/action-handler-directive"; import { hasAction } from "../common/has-action"; -import { ActionHandlerEvent } from "../../../data/lovelace"; +import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace"; import { handleAction } from "../common/handle-action"; +import { findEntities } from "../common/find-entites"; @customElement("hui-picture-entity-card") class HuiPictureEntityCard extends LitElement implements LovelaceCard { @@ -38,9 +39,24 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard { ); return document.createElement("hui-picture-entity-card-editor"); } - public static getStubConfig(): object { + + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill + ); + return { - entity: "", + entity: foundEntities[0] || "", image: "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", }; diff --git a/src/panels/lovelace/cards/hui-picture-glance-card.ts b/src/panels/lovelace/cards/hui-picture-glance-card.ts index 6317397e50..77da2de6f9 100644 --- a/src/panels/lovelace/cards/hui-picture-glance-card.ts +++ b/src/panels/lovelace/cards/hui-picture-glance-card.ts @@ -29,8 +29,9 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { actionHandler } from "../common/directives/action-handler-directive"; import { hasAction } from "../common/has-action"; -import { ActionHandlerEvent } from "../../../data/lovelace"; +import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace"; import { handleAction } from "../common/handle-action"; +import { findEntities } from "../common/find-entites"; const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]); @@ -42,11 +43,26 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard { ); return document.createElement("hui-picture-glance-card-editor"); } - public static getStubConfig(): object { + + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const maxEntities = 2; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill + ); + return { image: "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", - entities: [], + entities: foundEntities, }; } diff --git a/src/panels/lovelace/cards/hui-plant-status-card.ts b/src/panels/lovelace/cards/hui-plant-status-card.ts index a2ec8b0dae..9b7fd8a86d 100644 --- a/src/panels/lovelace/cards/hui-plant-status-card.ts +++ b/src/panels/lovelace/cards/hui-plant-status-card.ts @@ -22,6 +22,8 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import { PlantStatusCardConfig, PlantAttributeTarget } from "./types"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { actionHandler } from "../common/directives/action-handler-directive"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; const SENSORS = { moisture: "hass:water", @@ -40,8 +42,24 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard { return document.createElement("hui-plant-status-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["plant"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entity: foundEntities[0] || "" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-sensor-card.ts b/src/panels/lovelace/cards/hui-sensor-card.ts index 40f0c92cae..adfbb5da2f 100644 --- a/src/panels/lovelace/cards/hui-sensor-card.ts +++ b/src/panels/lovelace/cards/hui-sensor-card.ts @@ -26,6 +26,9 @@ import { fetchRecent } from "../../../data/history"; import { SensorCardConfig } from "./types"; import { hasConfigOrEntityChanged } from "../common/has-changed"; import { actionHandler } from "../common/directives/action-handler-directive"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; +import { HassEntity } from "home-assistant-js-websocket/dist/types"; const strokeWidth = 5; @@ -174,8 +177,32 @@ class HuiSensorCard extends LitElement implements LovelaceCard { return document.createElement("hui-sensor-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["sensor"]; + const maxEntities = 1; + const entityFilter = (stateObj: HassEntity): boolean => { + return ( + !isNaN(Number(stateObj.state)) && + !!stateObj.attributes.unit_of_measurement + ); + }; + + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains, + entityFilter + ); + + return { entity: foundEntities[0] || "", graph: "line" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-thermostat-card.ts b/src/panels/lovelace/cards/hui-thermostat-card.ts index 6b51051906..0570af89b2 100644 --- a/src/panels/lovelace/cards/hui-thermostat-card.ts +++ b/src/panels/lovelace/cards/hui-thermostat-card.ts @@ -34,6 +34,8 @@ import { } from "../../../data/climate"; import { HassEntity } from "home-assistant-js-websocket"; import { actionHandler } from "../common/directives/action-handler-directive"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; import { UNAVAILABLE } from "../../../data/entity"; const modeIcons: { [mode in HvacMode]: string } = { @@ -55,8 +57,24 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard { return document.createElement("hui-thermostat-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["climate"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entity: foundEntities[0] || "" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/cards/hui-weather-forecast-card.ts b/src/panels/lovelace/cards/hui-weather-forecast-card.ts index 41a0ae738a..ceb3ec0dd8 100644 --- a/src/panels/lovelace/cards/hui-weather-forecast-card.ts +++ b/src/panels/lovelace/cards/hui-weather-forecast-card.ts @@ -24,6 +24,8 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { toggleAttribute } from "../../../common/dom/toggle_attribute"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { actionHandler } from "../common/directives/action-handler-directive"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { findEntities } from "../common/find-entites"; const cardinalDirections = [ "N", @@ -71,8 +73,25 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard { ); return document.createElement("hui-weather-forecast-card-editor"); } - public static getStubConfig(): object { - return { entity: "" }; + + public static getStubConfig( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ): object { + const includeDomains = ["weather"]; + const maxEntities = 1; + const foundEntities = findEntities( + hass, + lovelaceConfig, + maxEntities, + entities, + entitiesFill, + includeDomains + ); + + return { entity: foundEntities[0] || "" }; } @property() public hass?: HomeAssistant; diff --git a/src/panels/lovelace/common/compute-unused-entities.ts b/src/panels/lovelace/common/compute-unused-entities.ts index dc9ad15182..af91701e78 100755 --- a/src/panels/lovelace/common/compute-unused-entities.ts +++ b/src/panels/lovelace/common/compute-unused-entities.ts @@ -1,7 +1,7 @@ import { LovelaceConfig, ActionConfig } from "../../../data/lovelace"; import { HomeAssistant } from "../../../types"; -const EXCLUDED_DOMAINS = ["zone", "persistent_notification"]; +export const EXCLUDED_DOMAINS = ["zone", "persistent_notification"]; const addFromAction = (entities: Set, actionConfig: ActionConfig) => { if ( @@ -61,7 +61,7 @@ const addEntities = (entities: Set, obj) => { } }; -const computeUsedEntities = (config) => { +export const computeUsedEntities = (config) => { const entities = new Set(); config.views.forEach((view) => addEntities(entities, view)); return entities; diff --git a/src/panels/lovelace/common/find-entites.ts b/src/panels/lovelace/common/find-entites.ts new file mode 100644 index 0000000000..b9c00ecd44 --- /dev/null +++ b/src/panels/lovelace/common/find-entites.ts @@ -0,0 +1,59 @@ +import { + computeUnusedEntities, + computeUsedEntities, +} from "./compute-unused-entities"; +import { HomeAssistant } from "../../../types"; +import { LovelaceConfig } from "../../../data/lovelace"; +import { computeDomain } from "../../../common/entity/compute_domain"; +import { HassEntity } from "home-assistant-js-websocket"; + +export const findEntities = ( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + maxEntities: number, + entities?: string[], + entitiesFill?: string[], + includeDomains?: string[], + entityFilter?: (stateObj: HassEntity) => boolean +) => { + let entityIds: string[]; + + entityIds = !entities?.length + ? computeUnusedEntities(hass, lovelaceConfig) + : entities; + + if (includeDomains && includeDomains.length) { + entityIds = entityIds.filter((eid) => + includeDomains!.includes(computeDomain(eid)) + ); + } + + if (entityFilter) { + entityIds = entityIds.filter( + (eid) => hass.states[eid] && entityFilter(hass.states[eid]) + ); + } + + if (entityIds.length < (maxEntities || 1)) { + let fillEntityIds = + entitiesFill && entitiesFill.length + ? entitiesFill + : [...computeUsedEntities(lovelaceConfig)]; + + if (includeDomains && includeDomains.length) { + fillEntityIds = fillEntityIds.filter((eid) => + includeDomains!.includes(computeDomain(eid)) + ); + } + + if (entityFilter) { + fillEntityIds = fillEntityIds.filter( + (eid) => hass.states[eid] && entityFilter(hass.states[eid]) + ); + } + + entityIds = [...entityIds, ...fillEntityIds]; + } + + return entityIds.slice(0, maxEntities); +}; diff --git a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts index a035237067..6a7d89f2bf 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts @@ -5,26 +5,31 @@ import { TemplateResult, CSSResult, customElement, + property, + PropertyValues, } from "lit-element"; -import "@material/mwc-button"; +import { until } from "lit-html/directives/until"; +import { classMap } from "lit-html/directives/class-map"; -import { HomeAssistant } from "../../../../types"; -import { LovelaceCardConfig } from "../../../../data/lovelace"; import { CardPickTarget } from "../types"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCard } from "../../types"; +import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { getCardElementClass } from "../../create-element/create-card-element"; +import { createCardElement } from "../../create-element/create-card-element"; +import { getCardStubConfig } from "../get-card-stub-config"; +import { + computeUnusedEntities, + computeUsedEntities, +} from "../../common/compute-unused-entities"; -const cards: string[] = [ +const previewCards: string[] = [ "alarm-panel", - "conditional", "entities", "button", - "entity-filter", "gauge", "glance", "history-graph", - "horizontal-stack", - "iframe", "light", "map", "markdown", @@ -35,78 +40,247 @@ const cards: string[] = [ "picture-glance", "plant-status", "sensor", - "shopping-list", "thermostat", - "vertical-stack", "weather-forecast", ]; +const nonPreviewCards: string[] = [ + "conditional", + "entity-filter", + "horizontal-stack", + "iframe", + "vertical-stack", + "shopping-list", +]; + @customElement("hui-card-picker") export class HuiCardPicker extends LitElement { - public hass?: HomeAssistant; - + @property() public hass?: HomeAssistant; + public lovelace?: LovelaceConfig; public cardPicked?: (cardConf: LovelaceCardConfig) => void; + private _unusedEntities?: string[]; + private _usedEntities?: string[]; protected render(): TemplateResult { + if ( + !this.hass || + !this.lovelace || + !this._unusedEntities || + !this._usedEntities + ) { + return html``; + } + return html`
- ${cards.map((card: string) => { + ${previewCards.map((type: string) => { return html` - - ${this.hass!.localize( - `ui.panel.lovelace.editor.card.${card}.name` - )} - + ${until( + this._renderCardElement(type), + html` +
+ +
+ ` + )} + `; + })} + ${nonPreviewCards.map((type: string) => { + return html` + ${until( + this._renderCardElement(type, true), + html` +
+ +
+ ` + )} `; })}
- MANUAL CARD +
+
+ ${this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.manual_description` + )} +
+
+ ${this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.manual` + )} +
+
`; } + protected shouldUpdate(changedProps: PropertyValues): boolean { + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + if (!oldHass) { + return true; + } + + if (oldHass.language !== this.hass!.language) { + return true; + } + + return false; + } + + protected firstUpdated(): void { + if (!this.hass || !this.lovelace) { + return; + } + + this._unusedEntities = computeUnusedEntities(this.hass, this.lovelace); + this._usedEntities = [...computeUsedEntities(this.lovelace)]; + this.requestUpdate(); + } + static get styles(): CSSResult[] { return [ css` .cards-container { - display: flex; - flex-wrap: wrap; - margin-bottom: 10px; - } - .cards-container mwc-button { - flex: 1 0 25%; - margin: 4px; + display: grid; + grid-gap: 8px 8px; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + margin-top: 20px; } - @media all and (max-width: 450px), all and (max-height: 500px) { - .cards-container mwc-button { - flex: 1 0 33%; - } + .card { + height: 100%; + display: flex; + flex-direction: column; + border-radius: 4px; + border: 1px solid var(--divider-color); + background: var(--primary-background-color, #fafafa); + cursor: pointer; + box-sizing: border-box; + } + + .card-header { + color: var(--ha-card-header-color, --primary-text-color); + font-family: var(--ha-card-header-font-family, inherit); + font-size: 16px; + letter-spacing: -0.012em; + line-height: 20px; + padding: 12px 16px; + display: block; + text-align: center; + background: var( + --ha-card-background, + var(--paper-card-background-color, white) + ); + border-radius: 0 0 4px 4px; + border-top: 1px solid var(--divider-color); + } + + .preview { + pointer-events: none; + margin: 20px; + flex: 1 1 0; + display: flex; + align-items: center; + justify-content: center; + } + + .preview > :first-child { + zoom: 0.6; + -moz-transform: scale(0.6); /* Firefox */ + -moz-transform-origin: 0 0; + -o-transform: scale(0.6); /* Opera */ + -o-transform-origin: 0 0; + display: block; + width: 100%; + } + + .description { + text-align: center; + } + + .spinner { + align-items: center; + justify-content: center; } `, ]; } - private _manualPicked(): void { - fireEvent(this, "config-changed", { - config: { type: "" }, - }); - } - - private async _cardPicked(ev: Event): Promise { - const type = (ev.currentTarget! as CardPickTarget).type; - - const elClass = await getCardElementClass(type); - let config: LovelaceCardConfig = { type }; - - if (elClass && elClass.getStubConfig) { - const cardConfig = elClass.getStubConfig(this.hass!); - config = { ...config, ...cardConfig }; - } + private _cardPicked(ev: Event): void { + const config: LovelaceCardConfig = (ev.currentTarget! as CardPickTarget) + .config; fireEvent(this, "config-changed", { config }); } + + private _createCardElement(cardConfig: LovelaceCardConfig) { + const element = createCardElement(cardConfig) as LovelaceCard; + element.hass = this.hass; + element.addEventListener( + "ll-rebuild", + (ev) => { + ev.stopPropagation(); + element.parentElement!.replaceChild( + this._createCardElement(cardConfig), + element + ); + }, + { once: true } + ); + return element; + } + + private async _renderCardElement( + type: string, + noElement: boolean = false + ): Promise { + let element: LovelaceCard | undefined; + let cardConfig: LovelaceCardConfig = { type }; + + if (this.hass && this.lovelace) { + cardConfig = await getCardStubConfig( + this.hass, + this.lovelace, + type, + this._unusedEntities, + this._usedEntities + ); + + if (!noElement) { + element = this._createCardElement(cardConfig); + } + } + + return html` +
+
+ ${!element || element.tagName === "HUI-ERROR-CARD" + ? html` + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.${cardConfig.type}.description` + )} + ` + : html` + ${element} + `} +
+
+ ${this.hass!.localize( + `ui.panel.lovelace.editor.card.${cardConfig.type}.name` + )} +
+
+ `; + } } declare global { diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index 08f60fd5d8..779d746b0a 100755 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -99,6 +99,7 @@ export class HuiDialogEditCard extends LitElement { ${this._cardConfig === undefined ? html` @@ -254,7 +255,7 @@ export class HuiDialogEditCard extends LitElement { private _handleCardPicked(ev) { const config = ev.detail.config; - if (this._params!.entities && this._params!.entities.length > 0) { + if (this._params!.entities && this._params!.entities.length) { if (Object.keys(config).includes("entities")) { config.entities = this._params!.entities; } else if (Object.keys(config).includes("entity")) { diff --git a/src/panels/lovelace/editor/get-card-stub-config.ts b/src/panels/lovelace/editor/get-card-stub-config.ts new file mode 100644 index 0000000000..e8a1feff92 --- /dev/null +++ b/src/panels/lovelace/editor/get-card-stub-config.ts @@ -0,0 +1,28 @@ +import { HomeAssistant } from "../../../types"; +import { LovelaceCardConfig, LovelaceConfig } from "../../../data/lovelace"; +import { getCardElementClass } from "../create-element/create-card-element"; + +export const getCardStubConfig = async ( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + type: string, + entities?: string[], + entitiesFill?: string[] +): Promise => { + let cardConfig: LovelaceCardConfig = { type }; + + const elClass = await getCardElementClass(type); + + if (elClass && elClass.getStubConfig) { + const classStubConfig = elClass.getStubConfig( + hass, + lovelaceConfig, + entities, + entitiesFill + ); + + cardConfig = { ...cardConfig, ...classStubConfig }; + } + + return cardConfig; +}; diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index 42a04fd5dc..c7228422dd 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -46,7 +46,7 @@ export interface EditorTarget extends EventTarget { } export interface CardPickTarget extends EventTarget { - type: string; + config: LovelaceCardConfig; } export const actionConfigStruct = struct({ diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index 71b36c01e2..ef99341213 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -38,7 +38,12 @@ export interface LovelaceCard extends HTMLElement { } export interface LovelaceCardConstructor extends Constructor { - getStubConfig?: (hass: HomeAssistant) => LovelaceCardConfig; + getStubConfig?: ( + hass: HomeAssistant, + lovelaceConfig: LovelaceConfig, + entities?: string[], + entitiesFill?: string[] + ) => LovelaceCardConfig; getConfigElement?: () => LovelaceCardEditor; } diff --git a/src/translations/en.json b/src/translations/en.json index 551b7a4c2f..9f75a5feda 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1956,7 +1956,8 @@ "available_states": "Available States" }, "conditional": { - "name": "Conditional" + "name": "Conditional", + "description": "Displays another card based on entity states." }, "config": { "required": "Required", @@ -1971,7 +1972,8 @@ "name": "Button" }, "entity-filter": { - "name": "Entity Filter" + "name": "Entity Filter", + "description": "This card allows you to define a list of entities that you want to track only when in a certain state." }, "gauge": { "name": "Gauge", @@ -1990,10 +1992,12 @@ "name": "History Graph" }, "horizontal-stack": { - "name": "Horizontal Stack" + "name": "Horizontal Stack", + "description": "Horizontal stack card allows you to stack together multiple cards, so they always sit next to each other in the space of one column." }, "iframe": { - "name": "iFrame" + "name": "Webpage", + "description": "Embed your favorite webpage right into Home Assistant" }, "light": { "name": "Light" @@ -2010,6 +2014,8 @@ "icon_height": "Icon Height", "image": "Image Path", "maximum": "Maximum", + "manual": "Manual", + "manual_description": "Need to add a custom card or just want to manually write the yaml?", "minimum": "Minimum", "name": "Name", "refresh_interval": "Refresh Interval", @@ -2049,7 +2055,8 @@ "name": "Picture Glance" }, "plant-status": { - "name": "Plant Status" + "name": "Plant Status", + "description": "A card for all the lovely botanists out there." }, "sensor": { "name": "Sensor", @@ -2058,13 +2065,15 @@ }, "shopping-list": { "name": "Shopping List", + "description": "The Shopping List Card allows you to add, edit, check-off, and clear items from your shopping list.", "integration_not_loaded": "This card requires the `shopping_list` integration to be set up." }, "thermostat": { "name": "Thermostat" }, "vertical-stack": { - "name": "Vertical Stack" + "name": "Vertical Stack", + "description": "Vertical stack allows you to group multiple cards so they always sit in the same column." }, "weather-forecast": { "name": "Weather Forecast"