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
This commit is contained in:
Zack Arnett 2020-03-03 14:53:55 -05:00 committed by GitHub
parent f3445d99cf
commit aa2e632df3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 679 additions and 91 deletions

View File

@ -24,6 +24,8 @@ import {
import { AlarmPanelCardConfig } from "./types"; import { AlarmPanelCardConfig } from "./types";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { findEntities } from "../common/find-entites";
import { LovelaceConfig } from "../../../data/lovelace";
const ICONS = { const ICONS = {
armed_away: "hass:shield-lock", armed_away: "hass:shield-lock",
@ -46,8 +48,27 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
return document.createElement("hui-alarm-panel-card-editor"); return document.createElement("hui-alarm-panel-card-editor");
} }
public static getStubConfig() { public static getStubConfig(
return { states: ["arm_home", "arm_away"], entity: "" }; 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; @property() public hass?: HomeAssistant;

View File

@ -30,9 +30,10 @@ import { ButtonCardConfig } from "./types";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { hasAction } from "../common/has-action"; import { hasAction } from "../common/has-action";
import { handleAction } from "../common/handle-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 { computeActiveState } from "../../../common/entity/compute_active_state";
import { iconColorCSS } from "../../../common/style/icon_color_css"; import { iconColorCSS } from "../../../common/style/icon_color_css";
import { findEntities } from "../common/find-entites";
@customElement("hui-button-card") @customElement("hui-button-card")
export class HuiButtonCard extends LitElement implements LovelaceCard { 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"); 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 { return {
tap_action: { action: "toggle" }, tap_action: { action: "toggle" },
hold_action: { action: "more-info" }, hold_action: { action: "more-info" },
show_icon: true, show_icon: true,
show_name: true, show_name: true,
state_color: true, state_color: true,
entity: foundEntities[0] || "",
}; };
} }

View File

@ -27,6 +27,8 @@ import { createHeaderFooterElement } from "../create-element/create-header-foote
import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types";
import { DOMAINS_TOGGLE } from "../../../common/const"; import { DOMAINS_TOGGLE } from "../../../common/const";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
@customElement("hui-entities-card") @customElement("hui-entities-card")
class HuiEntitiesCard extends LitElement implements LovelaceCard { class HuiEntitiesCard extends LitElement implements LovelaceCard {
@ -37,8 +39,22 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
return document.createElement("hui-entities-card-editor"); return document.createElement("hui-entities-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(
return { entities: [] }; 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; @property() private _config?: EntitiesCardConfig;

View File

@ -22,6 +22,9 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import { LovelaceCard, LovelaceCardEditor } from "../types";
import { GaugeCardConfig } 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 = { export const severityMap = {
red: "var(--label-badge-red)", red: "var(--label-badge-red)",
@ -38,8 +41,30 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
); );
return document.createElement("hui-gauge-card-editor"); 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; @property() public hass?: HomeAssistant;

View File

@ -27,10 +27,11 @@ import { processConfigEntities } from "../common/process-config-entities";
import { GlanceCardConfig, GlanceConfigEntity } from "./types"; import { GlanceCardConfig, GlanceConfigEntity } from "./types";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { hasAction } from "../common/has-action"; import { hasAction } from "../common/has-action";
import { ActionHandlerEvent } from "../../../data/lovelace"; import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace";
import { handleAction } from "../common/handle-action"; import { handleAction } from "../common/handle-action";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
import { findEntities } from "../common/find-entites";
@customElement("hui-glance-card") @customElement("hui-glance-card")
export class HuiGlanceCard extends LitElement implements LovelaceCard { 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"); return document.createElement("hui-glance-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(
return { entities: [] }; 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; @property() public hass?: HomeAssistant;

View File

@ -20,6 +20,8 @@ import { HomeAssistant } from "../../../types";
import { HistoryGraphCardConfig } from "./types"; import { HistoryGraphCardConfig } from "./types";
import { LovelaceCard } from "../types"; import { LovelaceCard } from "../types";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
@customElement("hui-history-graph-card") @customElement("hui-history-graph-card")
export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { 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"); return document.createElement("hui-history-graph-card-editor");
} }
public static getStubConfig() { public static getStubConfig(
return { entities: [] }; 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; @property() public hass?: HomeAssistant;

View File

@ -29,6 +29,8 @@ import { toggleEntity } from "../common/entity/toggle-entity";
import { LightCardConfig } from "./types"; import { LightCardConfig } from "./types";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import { SUPPORT_BRIGHTNESS } from "../../../data/light"; import { SUPPORT_BRIGHTNESS } from "../../../data/light";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
@customElement("hui-light-card") @customElement("hui-light-card")
@ -39,8 +41,25 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
); );
return document.createElement("hui-light-card-editor"); 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; @property() public hass?: HomeAssistant;

View File

@ -30,6 +30,8 @@ import { EntityConfig } from "../entity-rows/types";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import { MapCardConfig } from "./types"; import { MapCardConfig } from "./types";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
@customElement("hui-map-card") @customElement("hui-map-card")
class HuiMapCard extends LitElement implements LovelaceCard { class HuiMapCard extends LitElement implements LovelaceCard {
@ -40,8 +42,24 @@ class HuiMapCard extends LitElement implements LovelaceCard {
return document.createElement("hui-map-card-editor"); return document.createElement("hui-map-card-editor");
} }
public static getStubConfig() { public static getStubConfig(
return { entities: [] }; 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; @property() public hass?: HomeAssistant;

View File

@ -34,6 +34,8 @@ import { LovelaceCard, LovelaceCardEditor } from "../types";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { MediaControlCardConfig } from "./types"; import { MediaControlCardConfig } from "./types";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
@customElement("hui-media-control-card") @customElement("hui-media-control-card")
export class HuiMediaControlCard extends LitElement implements LovelaceCard { 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"); return document.createElement("hui-media-control-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(
return { entity: "" }; 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; @property() public hass?: HomeAssistant;

View File

@ -15,9 +15,39 @@ import { HomeAssistant } from "../../../types";
import { LovelaceElementConfig, LovelaceElement } from "../elements/types"; import { LovelaceElementConfig, LovelaceElement } from "../elements/types";
import { PictureElementsCardConfig } from "./types"; import { PictureElementsCardConfig } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; 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") @customElement("hui-picture-elements-card")
class HuiPictureElementsCard extends LitElement implements LovelaceCard { 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; @property() private _config?: PictureElementsCardConfig;
private _hass?: HomeAssistant; private _hass?: HomeAssistant;

View File

@ -27,8 +27,9 @@ import { PictureEntityCardConfig } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { hasAction } from "../common/has-action"; import { hasAction } from "../common/has-action";
import { ActionHandlerEvent } from "../../../data/lovelace"; import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace";
import { handleAction } from "../common/handle-action"; import { handleAction } from "../common/handle-action";
import { findEntities } from "../common/find-entites";
@customElement("hui-picture-entity-card") @customElement("hui-picture-entity-card")
class HuiPictureEntityCard extends LitElement implements LovelaceCard { class HuiPictureEntityCard extends LitElement implements LovelaceCard {
@ -38,9 +39,24 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
); );
return document.createElement("hui-picture-entity-card-editor"); 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 { return {
entity: "", entity: foundEntities[0] || "",
image: image:
"https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png",
}; };

View File

@ -29,8 +29,9 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { hasAction } from "../common/has-action"; import { hasAction } from "../common/has-action";
import { ActionHandlerEvent } from "../../../data/lovelace"; import { ActionHandlerEvent, LovelaceConfig } from "../../../data/lovelace";
import { handleAction } from "../common/handle-action"; import { handleAction } from "../common/handle-action";
import { findEntities } from "../common/find-entites";
const STATES_OFF = new Set(["closed", "locked", "not_home", "off"]); 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"); 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 { return {
image: image:
"https://www.home-assistant.io/images/merchandise/shirt-frontpage.png", "https://www.home-assistant.io/images/merchandise/shirt-frontpage.png",
entities: [], entities: foundEntities,
}; };
} }

View File

@ -22,6 +22,8 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
import { PlantStatusCardConfig, PlantAttributeTarget } from "./types"; import { PlantStatusCardConfig, PlantAttributeTarget } from "./types";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
const SENSORS = { const SENSORS = {
moisture: "hass:water", moisture: "hass:water",
@ -40,8 +42,24 @@ class HuiPlantStatusCard extends LitElement implements LovelaceCard {
return document.createElement("hui-plant-status-card-editor"); return document.createElement("hui-plant-status-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(
return { entity: "" }; 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; @property() public hass?: HomeAssistant;

View File

@ -26,6 +26,9 @@ import { fetchRecent } from "../../../data/history";
import { SensorCardConfig } from "./types"; import { SensorCardConfig } from "./types";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import { actionHandler } from "../common/directives/action-handler-directive"; 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; const strokeWidth = 5;
@ -174,8 +177,32 @@ class HuiSensorCard extends LitElement implements LovelaceCard {
return document.createElement("hui-sensor-card-editor"); return document.createElement("hui-sensor-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(
return { entity: "" }; 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; @property() public hass?: HomeAssistant;

View File

@ -34,6 +34,8 @@ import {
} from "../../../data/climate"; } from "../../../data/climate";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
const modeIcons: { [mode in HvacMode]: string } = { const modeIcons: { [mode in HvacMode]: string } = {
@ -55,8 +57,24 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
return document.createElement("hui-thermostat-card-editor"); return document.createElement("hui-thermostat-card-editor");
} }
public static getStubConfig(): object { public static getStubConfig(
return { entity: "" }; 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; @property() public hass?: HomeAssistant;

View File

@ -24,6 +24,8 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { toggleAttribute } from "../../../common/dom/toggle_attribute"; import { toggleAttribute } from "../../../common/dom/toggle_attribute";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { LovelaceConfig } from "../../../data/lovelace";
import { findEntities } from "../common/find-entites";
const cardinalDirections = [ const cardinalDirections = [
"N", "N",
@ -71,8 +73,25 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
); );
return document.createElement("hui-weather-forecast-card-editor"); 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; @property() public hass?: HomeAssistant;

View File

@ -1,7 +1,7 @@
import { LovelaceConfig, ActionConfig } from "../../../data/lovelace"; import { LovelaceConfig, ActionConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
const EXCLUDED_DOMAINS = ["zone", "persistent_notification"]; export const EXCLUDED_DOMAINS = ["zone", "persistent_notification"];
const addFromAction = (entities: Set<string>, actionConfig: ActionConfig) => { const addFromAction = (entities: Set<string>, actionConfig: ActionConfig) => {
if ( if (
@ -61,7 +61,7 @@ const addEntities = (entities: Set<string>, obj) => {
} }
}; };
const computeUsedEntities = (config) => { export const computeUsedEntities = (config) => {
const entities = new Set<string>(); const entities = new Set<string>();
config.views.forEach((view) => addEntities(entities, view)); config.views.forEach((view) => addEntities(entities, view));
return entities; return entities;

View File

@ -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);
};

View File

@ -5,26 +5,31 @@ import {
TemplateResult, TemplateResult,
CSSResult, CSSResult,
customElement, customElement,
property,
PropertyValues,
} from "lit-element"; } 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 { 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 { 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", "alarm-panel",
"conditional",
"entities", "entities",
"button", "button",
"entity-filter",
"gauge", "gauge",
"glance", "glance",
"history-graph", "history-graph",
"horizontal-stack",
"iframe",
"light", "light",
"map", "map",
"markdown", "markdown",
@ -35,78 +40,247 @@ const cards: string[] = [
"picture-glance", "picture-glance",
"plant-status", "plant-status",
"sensor", "sensor",
"shopping-list",
"thermostat", "thermostat",
"vertical-stack",
"weather-forecast", "weather-forecast",
]; ];
const nonPreviewCards: string[] = [
"conditional",
"entity-filter",
"horizontal-stack",
"iframe",
"vertical-stack",
"shopping-list",
];
@customElement("hui-card-picker") @customElement("hui-card-picker")
export class HuiCardPicker extends LitElement { export class HuiCardPicker extends LitElement {
public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
public lovelace?: LovelaceConfig;
public cardPicked?: (cardConf: LovelaceCardConfig) => void; public cardPicked?: (cardConf: LovelaceCardConfig) => void;
private _unusedEntities?: string[];
private _usedEntities?: string[];
protected render(): TemplateResult { protected render(): TemplateResult {
if (
!this.hass ||
!this.lovelace ||
!this._unusedEntities ||
!this._usedEntities
) {
return html``;
}
return html` return html`
<div class="cards-container"> <div class="cards-container">
${cards.map((card: string) => { ${previewCards.map((type: string) => {
return html` return html`
<mwc-button @click="${this._cardPicked}" .type="${card}"> ${until(
${this.hass!.localize( this._renderCardElement(type),
`ui.panel.lovelace.editor.card.${card}.name` html`
)} <div class="card spinner">
</mwc-button> <paper-spinner active alt="Loading"></paper-spinner>
</div>
`
)}
`;
})}
${nonPreviewCards.map((type: string) => {
return html`
${until(
this._renderCardElement(type, true),
html`
<div class="card spinner">
<paper-spinner active alt="Loading"></paper-spinner>
</div>
`
)}
`; `;
})} })}
</div> </div>
<div class="cards-container"> <div class="cards-container">
<mwc-button @click="${this._manualPicked}">MANUAL CARD</mwc-button> <div
class="card"
@click="${this._cardPicked}"
.config="${{ type: "" }}"
>
<div class="preview description">
${this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.manual_description`
)}
</div>
<div class="card-header">
${this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.manual`
)}
</div>
</div>
</div> </div>
`; `;
} }
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[] { static get styles(): CSSResult[] {
return [ return [
css` css`
.cards-container { .cards-container {
display: flex; display: grid;
flex-wrap: wrap; grid-gap: 8px 8px;
margin-bottom: 10px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
} margin-top: 20px;
.cards-container mwc-button {
flex: 1 0 25%;
margin: 4px;
} }
@media all and (max-width: 450px), all and (max-height: 500px) { .card {
.cards-container mwc-button { height: 100%;
flex: 1 0 33%; 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 { private _cardPicked(ev: Event): void {
fireEvent(this, "config-changed", { const config: LovelaceCardConfig = (ev.currentTarget! as CardPickTarget)
config: { type: "" }, .config;
});
}
private async _cardPicked(ev: Event): Promise<void> {
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 };
}
fireEvent(this, "config-changed", { 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<TemplateResult> {
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`
<div class="card" @click="${this._cardPicked}" .config="${cardConfig}">
<div
class="preview ${classMap({
description: !element || element.tagName === "HUI-ERROR-CARD",
})}"
>
${!element || element.tagName === "HUI-ERROR-CARD"
? html`
${this.hass!.localize(
`ui.panel.lovelace.editor.card.${cardConfig.type}.description`
)}
`
: html`
${element}
`}
</div>
<div class="card-header">
${this.hass!.localize(
`ui.panel.lovelace.editor.card.${cardConfig.type}.name`
)}
</div>
</div>
`;
}
} }
declare global { declare global {

View File

@ -99,6 +99,7 @@ export class HuiDialogEditCard extends LitElement {
${this._cardConfig === undefined ${this._cardConfig === undefined
? html` ? html`
<hui-card-picker <hui-card-picker
.lovelace="${this._params.lovelaceConfig}"
.hass=${this.hass} .hass=${this.hass}
@config-changed="${this._handleCardPicked}" @config-changed="${this._handleCardPicked}"
></hui-card-picker> ></hui-card-picker>
@ -254,7 +255,7 @@ export class HuiDialogEditCard extends LitElement {
private _handleCardPicked(ev) { private _handleCardPicked(ev) {
const config = ev.detail.config; 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")) { if (Object.keys(config).includes("entities")) {
config.entities = this._params!.entities; config.entities = this._params!.entities;
} else if (Object.keys(config).includes("entity")) { } else if (Object.keys(config).includes("entity")) {

View File

@ -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<LovelaceCardConfig> => {
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;
};

View File

@ -46,7 +46,7 @@ export interface EditorTarget extends EventTarget {
} }
export interface CardPickTarget extends EventTarget { export interface CardPickTarget extends EventTarget {
type: string; config: LovelaceCardConfig;
} }
export const actionConfigStruct = struct({ export const actionConfigStruct = struct({

View File

@ -38,7 +38,12 @@ export interface LovelaceCard extends HTMLElement {
} }
export interface LovelaceCardConstructor extends Constructor<LovelaceCard> { export interface LovelaceCardConstructor extends Constructor<LovelaceCard> {
getStubConfig?: (hass: HomeAssistant) => LovelaceCardConfig; getStubConfig?: (
hass: HomeAssistant,
lovelaceConfig: LovelaceConfig,
entities?: string[],
entitiesFill?: string[]
) => LovelaceCardConfig;
getConfigElement?: () => LovelaceCardEditor; getConfigElement?: () => LovelaceCardEditor;
} }

View File

@ -1956,7 +1956,8 @@
"available_states": "Available States" "available_states": "Available States"
}, },
"conditional": { "conditional": {
"name": "Conditional" "name": "Conditional",
"description": "Displays another card based on entity states."
}, },
"config": { "config": {
"required": "Required", "required": "Required",
@ -1971,7 +1972,8 @@
"name": "Button" "name": "Button"
}, },
"entity-filter": { "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": { "gauge": {
"name": "Gauge", "name": "Gauge",
@ -1990,10 +1992,12 @@
"name": "History Graph" "name": "History Graph"
}, },
"horizontal-stack": { "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": { "iframe": {
"name": "iFrame" "name": "Webpage",
"description": "Embed your favorite webpage right into Home Assistant"
}, },
"light": { "light": {
"name": "Light" "name": "Light"
@ -2010,6 +2014,8 @@
"icon_height": "Icon Height", "icon_height": "Icon Height",
"image": "Image Path", "image": "Image Path",
"maximum": "Maximum", "maximum": "Maximum",
"manual": "Manual",
"manual_description": "Need to add a custom card or just want to manually write the yaml?",
"minimum": "Minimum", "minimum": "Minimum",
"name": "Name", "name": "Name",
"refresh_interval": "Refresh Interval", "refresh_interval": "Refresh Interval",
@ -2049,7 +2055,8 @@
"name": "Picture Glance" "name": "Picture Glance"
}, },
"plant-status": { "plant-status": {
"name": "Plant Status" "name": "Plant Status",
"description": "A card for all the lovely botanists out there."
}, },
"sensor": { "sensor": {
"name": "Sensor", "name": "Sensor",
@ -2058,13 +2065,15 @@
}, },
"shopping-list": { "shopping-list": {
"name": "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." "integration_not_loaded": "This card requires the `shopping_list` integration to be set up."
}, },
"thermostat": { "thermostat": {
"name": "Thermostat" "name": "Thermostat"
}, },
"vertical-stack": { "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": { "weather-forecast": {
"name": "Weather Forecast" "name": "Weather Forecast"