Entities Card: Entity Row Editor (#7134)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Zack Barett 2020-09-30 09:20:10 -05:00 committed by GitHub
parent d43b5d3337
commit a2ec878ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 902 additions and 334 deletions

View File

@ -93,6 +93,8 @@ export class HaEntityPicker extends LitElement {
@property() public entityFilter?: HaEntityPickerEntityFilterFunc; @property() public entityFilter?: HaEntityPickerEntityFilterFunc;
@property({ type: Boolean }) public hideClearIcon = false;
@property({ type: Boolean }) private _opened = false; @property({ type: Boolean }) private _opened = false;
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement; @query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
@ -204,7 +206,7 @@ export class HaEntityPicker extends LitElement {
autocorrect="off" autocorrect="off"
spellcheck="false" spellcheck="false"
> >
${this.value ${this.value && !this.hideClearIcon
? html` ? html`
<ha-icon-button <ha-icon-button
aria-label=${this.hass.localize( aria-label=${this.hass.localize(

View File

@ -3,8 +3,8 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
LitElement,
internalProperty, internalProperty,
LitElement,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
@ -13,19 +13,23 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { computeCardSize } from "../common/compute-card-size";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { processConfigEntities } from "../common/process-config-entities"; import { processConfigEntities } from "../common/process-config-entities";
import "../components/hui-entities-toggle"; import "../components/hui-entities-toggle";
import { createHeaderFooterElement } from "../create-element/create-header-footer-element"; import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
import { createRowElement } from "../create-element/create-row-element"; import { createRowElement } from "../create-element/create-row-element";
import { LovelaceRow } from "../entity-rows/types"; import {
EntityConfig,
LovelaceRow,
LovelaceRowConfig,
} from "../entity-rows/types";
import { import {
LovelaceCard, LovelaceCard,
LovelaceCardEditor, LovelaceCardEditor,
LovelaceHeaderFooter, LovelaceHeaderFooter,
} from "../types"; } from "../types";
import { EntitiesCardConfig, EntitiesCardEntityConfig } from "./types"; import { EntitiesCardConfig } from "./types";
import { computeCardSize } from "../common/compute-card-size";
@customElement("hui-entities-card") @customElement("hui-entities-card")
class HuiEntitiesCard extends LitElement implements LovelaceCard { class HuiEntitiesCard extends LitElement implements LovelaceCard {
@ -57,7 +61,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
private _hass?: HomeAssistant; private _hass?: HomeAssistant;
private _configEntities?: EntitiesCardEntityConfig[]; private _configEntities?: LovelaceRowConfig[];
private _showHeaderToggle?: boolean; private _showHeaderToggle?: boolean;
@ -115,7 +119,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
// Default value is show toggle if we can at least toggle 2 entities. // Default value is show toggle if we can at least toggle 2 entities.
let toggleable = 0; let toggleable = 0;
for (const rowConf of entities) { for (const rowConf of entities) {
if (!rowConf.entity) { if (!("entity" in rowConf)) {
continue; continue;
} }
toggleable += Number(DOMAINS_TOGGLE.has(computeDomain(rowConf.entity))); toggleable += Number(DOMAINS_TOGGLE.has(computeDomain(rowConf.entity)));
@ -188,7 +192,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
? html` ? html`
<ha-icon <ha-icon
class="icon" class="icon"
.icon="${this._config.icon}" .icon=${this._config.icon}
></ha-icon> ></ha-icon>
` `
: ""} : ""}
@ -198,10 +202,10 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
? html`` ? html``
: html` : html`
<hui-entities-toggle <hui-entities-toggle
.hass="${this._hass}" .hass=${this._hass}
.entities="${this._configEntities!.map( .entities=${(this._configEntities!.filter(
(conf) => conf.entity (conf) => "type" in conf
)}" ) as EntityConfig[]).map((conf) => conf.entity)}
></hui-entities-toggle> ></hui-entities-toggle>
`} `}
</div> </div>
@ -285,20 +289,20 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
`; `;
} }
private renderEntity(entityConf: EntitiesCardEntityConfig): TemplateResult { private renderEntity(entityConf: LovelaceRowConfig): TemplateResult {
const element = createRowElement( const element = createRowElement(
this._config!.state_color !("type" in entityConf) && this._config!.state_color
? { ? ({
state_color: true, state_color: true,
...entityConf, ...(entityConf as EntityConfig),
} } as EntityConfig)
: entityConf : entityConf
); );
if (this._hass) { if (this._hass) {
element.hass = this._hass; element.hass = this._hass;
} }
return html` <div>${element}</div> `; return html`<div>${element}</div>`;
} }
} }

View File

@ -3,7 +3,11 @@ import { FullCalendarView } from "../../../types";
import { Condition } from "../common/validate-condition"; import { Condition } from "../common/validate-condition";
import { HuiImage } from "../components/hui-image"; import { HuiImage } from "../components/hui-image";
import { LovelaceElementConfig } from "../elements/types"; import { LovelaceElementConfig } from "../elements/types";
import { EntityConfig, EntityFilterEntityConfig } from "../entity-rows/types"; import {
EntityConfig,
EntityFilterEntityConfig,
LovelaceRowConfig,
} from "../entity-rows/types";
import { LovelaceHeaderFooterConfig } from "../header-footer/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types";
export interface AlarmPanelCardConfig extends LovelaceCardConfig { export interface AlarmPanelCardConfig extends LovelaceCardConfig {
@ -60,7 +64,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
type: "entities"; type: "entities";
show_header_toggle?: boolean; show_header_toggle?: boolean;
title?: string; title?: string;
entities: Array<EntitiesCardEntityConfig | string>; entities: Array<LovelaceRowConfig | string>;
theme?: string; theme?: string;
icon?: string; icon?: string;
header?: LovelaceHeaderFooterConfig; header?: LovelaceHeaderFooterConfig;

View File

@ -56,6 +56,7 @@ export function hasConfigOrEntitiesChanged(
return entities.some( return entities.some(
(entity) => (entity) =>
"entity" in entity &&
oldHass.states[entity.entity] !== element.hass!.states[entity.entity] oldHass.states[entity.entity] !== element.hass!.states[entity.entity]
); );
} }

View File

@ -1,8 +1,10 @@
// Parse array of entity objects from config // Parse array of entity objects from config
import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
export const processConfigEntities = <T extends EntityConfig>( export const processConfigEntities = <
T extends EntityConfig | LovelaceRowConfig
>(
entities: Array<T | string> entities: Array<T | string>
): T[] => { ): T[] => {
if (!entities || !Array.isArray(entities)) { if (!entities || !Array.isArray(entities)) {
@ -24,7 +26,7 @@ export const processConfigEntities = <T extends EntityConfig>(
if (typeof entityConf === "string") { if (typeof entityConf === "string") {
config = { entity: entityConf } as T; config = { entity: entityConf } as T;
} else if (typeof entityConf === "object" && !Array.isArray(entityConf)) { } else if (typeof entityConf === "object" && !Array.isArray(entityConf)) {
if (!entityConf.entity) { if (!("entity" in entityConf)) {
throw new Error( throw new Error(
`Entity object at position ${index} is missing entity field.` `Entity object at position ${index} is missing entity field.`
); );
@ -34,9 +36,11 @@ export const processConfigEntities = <T extends EntityConfig>(
throw new Error(`Invalid entity specified at position ${index}.`); throw new Error(`Invalid entity specified at position ${index}.`);
} }
if (!isValidEntityId(config.entity)) { if (!isValidEntityId((config as EntityConfig).entity!)) {
throw new Error( throw new Error(
`Invalid entity ID at position ${index}: ${config.entity}` `Invalid entity ID at position ${index}: ${
(config as EntityConfig).entity
}`
); );
} }

View File

@ -16,6 +16,7 @@ import {
LovelaceCard, LovelaceCard,
LovelaceCardConstructor, LovelaceCardConstructor,
LovelaceHeaderFooter, LovelaceHeaderFooter,
LovelaceRowConstructor,
} from "../types"; } from "../types";
const TIMEOUT = 2000; const TIMEOUT = 2000;
@ -39,7 +40,7 @@ interface CreateElementConfigTypes {
row: { row: {
config: LovelaceRowConfig; config: LovelaceRowConfig;
element: LovelaceRow; element: LovelaceRow;
constructor: unknown; constructor: LovelaceRowConstructor;
}; };
"header-footer": { "header-footer": {
config: LovelaceHeaderFooterConfig; config: LovelaceHeaderFooterConfig;

View File

@ -4,11 +4,14 @@ import "../entity-rows/hui-script-entity-row";
import "../entity-rows/hui-sensor-entity-row"; import "../entity-rows/hui-sensor-entity-row";
import "../entity-rows/hui-text-entity-row"; import "../entity-rows/hui-text-entity-row";
import "../entity-rows/hui-toggle-entity-row"; import "../entity-rows/hui-toggle-entity-row";
import { EntityConfig } from "../entity-rows/types"; import { LovelaceRowConfig } from "../entity-rows/types";
import "../special-rows/hui-attribute-row"; import "../special-rows/hui-attribute-row";
import "../special-rows/hui-button-row"; import "../special-rows/hui-button-row";
import "../special-rows/hui-call-service-row"; import "../special-rows/hui-call-service-row";
import { createLovelaceElement } from "./create-element-base"; import {
createLovelaceElement,
getLovelaceElementClass,
} from "./create-element-base";
const ALWAYS_LOADED_TYPES = new Set([ const ALWAYS_LOADED_TYPES = new Set([
"media-player-entity", "media-player-entity",
@ -74,7 +77,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
weather: "weather", weather: "weather",
}; };
export const createRowElement = (config: EntityConfig) => export const createRowElement = (config: LovelaceRowConfig) =>
createLovelaceElement( createLovelaceElement(
"row", "row",
config, config,
@ -83,3 +86,12 @@ export const createRowElement = (config: EntityConfig) =>
DOMAIN_TO_ELEMENT_TYPE, DOMAIN_TO_ELEMENT_TYPE,
undefined undefined
); );
export const getRowElementClass = (type: string) => {
return getLovelaceElementClass(
type,
"row",
ALWAYS_LOADED_TYPES,
LAZY_LOAD_TYPES
);
};

View File

@ -1,42 +1,42 @@
import { mdiHelpCircle } from "@mdi/js";
import deepFreeze from "deep-freeze"; import deepFreeze from "deep-freeze";
import { import {
css, css,
CSSResultArray, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty, PropertyValues,
query, query,
TemplateResult, TemplateResult,
PropertyValues,
} from "lit-element"; } from "lit-element";
import { mdiHelpCircle } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import { haStyleDialog } from "../../../../resources/styles";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { addCard, replaceCard } from "../config-util";
import { getCardDocumentationURL } from "../get-card-documentation-url";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../../types";
import type { GUIModeChangedEvent } from "../types";
import type { ConfigChangedEvent, HuiCardEditor } from "./hui-card-editor";
import type { EditCardDialogParams } from "./show-edit-card-dialog";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import type { HASSDomEvent } from "../../../../common/dom/fire_event"; import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-circular-progress";
import "../../../../components/ha-dialog";
import "../../../../components/ha-header-bar";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceViewConfig, LovelaceViewConfig,
} from "../../../../data/lovelace"; } from "../../../../data/lovelace";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import "./hui-card-editor"; import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
import { addCard, replaceCard } from "../config-util";
import { getCardDocumentationURL } from "../get-card-documentation-url";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import type { GUIModeChangedEvent } from "../types";
import "./hui-card-preview"; import "./hui-card-preview";
import "../../../../components/ha-dialog"; import type { EditCardDialogParams } from "./show-edit-card-dialog";
import "../../../../components/ha-header-bar";
import "../../../../components/ha-circular-progress";
declare global { declare global {
// for fire event // for fire event
@ -65,7 +65,7 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@internalProperty() private _guiModeAvailable? = true; @internalProperty() private _guiModeAvailable? = true;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
@internalProperty() private _GUImode = true; @internalProperty() private _GUImode = true;
@ -183,14 +183,14 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
</div> </div>
<div class="content"> <div class="content">
<div class="element-editor"> <div class="element-editor">
<hui-card-editor <hui-element-editor
.hass=${this.hass} .hass=${this.hass}
.lovelace=${this._params.lovelaceConfig} .lovelace=${this._params.lovelaceConfig}
.value=${this._cardConfig} .value=${this._cardConfig}
@config-changed=${this._handleConfigChanged} @config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged} @GUImode-changed=${this._handleGUIModeChanged}
@editor-save=${this._save} @editor-save=${this._save}
></hui-card-editor> ></hui-element-editor>
</div> </div>
<div class="element-preview"> <div class="element-preview">
<hui-card-preview <hui-card-preview
@ -364,6 +364,8 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@media all and (min-width: 850px) { @media all and (min-width: 850px) {
ha-dialog { ha-dialog {
--mdc-dialog-min-width: 845px; --mdc-dialog-min-width: 845px;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
} }
} }
@ -402,6 +404,9 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
ha-dialog { ha-dialog {
--mdc-dialog-max-width: calc(100% - 32px); --mdc-dialog-max-width: calc(100% - 32px);
--mdc-dialog-min-width: 1000px; --mdc-dialog-min-width: 1000px;
--dialog-surface-position: fixed;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
} }
.content { .content {

View File

@ -1,7 +1,6 @@
import { html } from "lit-element"; import { css } from "lit-element";
export const configElementStyle = html` export const configElementStyle = css`
<style>
ha-switch { ha-switch {
padding: 16px 0; padding: 16px 0;
} }
@ -15,5 +14,4 @@ export const configElementStyle = html`
.suffix { .suffix {
margin: 0 8px; margin: 0 8px;
} }
</style>
`; `;

View File

@ -3,14 +3,15 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { array, assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
@ -20,7 +21,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, object, string, optional, array } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -68,7 +68,6 @@ export class HuiAlarmPanelCardEditor extends LitElement
const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"]; const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -128,8 +127,10 @@ export class HuiAlarmPanelCardEditor extends LitElement
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.states { .states {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -143,7 +144,8 @@ export class HuiAlarmPanelCardEditor extends LitElement
ha-icon { ha-icon {
padding-top: 12px; padding-top: 12px;
} }
`; `,
];
} }
private _stateRemoved(ev: EntitiesEditorEvent): void { private _stateRemoved(ev: EntitiesEditorEvent): void {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -109,7 +110,6 @@ export class HuiButtonCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!); const dir = computeRTLDirection(this.hass!);
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -293,6 +293,10 @@ export class HuiButtonCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: newConfig }); fireEvent(this, "config-changed", { config: newConfig });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,4 +1,5 @@
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -68,7 +69,6 @@ export class HuiCalendarCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<div class="side-by-side"> <div class="side-by-side">
<paper-input <paper-input
@ -174,6 +174,10 @@ export class HuiCalendarCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -2,28 +2,30 @@ import "@polymer/paper-tabs";
import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tab";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { any, array, assert, object, optional, string } from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { LovelaceConfig } from "../../../../data/lovelace"; import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ConditionalCardConfig } from "../../cards/types"; import { ConditionalCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import {
ConfigChangedEvent,
HuiCardEditor,
} from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import { GUIModeChangedEvent } from "../types"; import { GUIModeChangedEvent } from "../types";
import { string, any, object, optional, array, assert } from "superstruct"; import { configElementStyle } from "./config-elements-style";
const conditionStruct = object({ const conditionStruct = object({
entity: string(), entity: string(),
@ -51,7 +53,7 @@ export class HuiConditionalCardEditor extends LitElement
@internalProperty() private _cardTab = false; @internalProperty() private _cardTab = false;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: ConditionalCardConfig): void { public setConfig(config: ConditionalCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -106,13 +108,13 @@ export class HuiConditionalCardEditor extends LitElement
)}</mwc-button )}</mwc-button
> >
</div> </div>
<hui-card-editor <hui-element-editor
.hass=${this.hass} .hass=${this.hass}
.value=${this._config.card} .value=${this._config.card}
.lovelace=${this.lovelace} .lovelace=${this.lovelace}
@config-changed=${this._handleCardChanged} @config-changed=${this._handleCardChanged}
@GUImode-changed=${this._handleGUIModeChanged} @GUImode-changed=${this._handleGUIModeChanged}
></hui-card-editor> ></hui-element-editor>
` `
: html` : html`
<hui-card-picker <hui-card-picker
@ -227,7 +229,10 @@ export class HuiConditionalCardEditor extends LitElement
if (!this._config) { if (!this._config) {
return; return;
} }
this._config = { ...this._config, card: ev.detail.config }; this._config = {
...this._config,
card: ev.detail.config as LovelaceCardConfig,
};
this._guiModeAvailable = ev.detail.guiModeAvailable; this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -292,8 +297,10 @@ export class HuiConditionalCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
paper-tabs { paper-tabs {
--paper-tabs-selection-bar-color: var(--primary-color); --paper-tabs-selection-bar-color: var(--primary-color);
--paper-tab-ink: var(--primary-color); --paper-tab-ink: var(--primary-color);
@ -337,7 +344,8 @@ export class HuiConditionalCardEditor extends LitElement
.gui-mode-button { .gui-mode-button {
margin-right: auto; margin-right: auto;
} }
`; `,
];
} }
} }

View File

@ -2,11 +2,14 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
css,
CSSResultArray,
customElement, customElement,
html, html,
internalProperty, internalProperty,
LitElement, LitElement,
property, property,
query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { import {
@ -18,7 +21,7 @@ import {
string, string,
union, union,
} from "superstruct"; } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge"; import "../../../../components/entity/state-badge";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
@ -26,22 +29,33 @@ import "../../../../components/ha-formfield";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-switch"; import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { import { EntitiesCardConfig } from "../../cards/types";
EntitiesCardConfig,
EntitiesCardEntityConfig,
} from "../../cards/types";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceRowConfig } from "../../entity-rows/types";
import { headerFooterConfigStructs } from "../../header-footer/types"; import { headerFooterConfigStructs } from "../../header-footer/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import "../hui-detail-editor-base";
import { HuiElementEditor } from "../hui-element-editor";
import "../hui-entities-card-row-editor"; import "../hui-entities-card-row-editor";
import { processEditorEntities } from "../process-editor-entities"; import { processEditorEntities } from "../process-editor-entities";
import { import {
EditorTarget, EditorTarget,
entitiesConfigStruct, entitiesConfigStruct,
EntitiesEditorEvent, EntitiesEditorEvent,
GUIModeChangedEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
interface EditRowEvent {
index: number;
}
declare global {
interface HASSDomEvents {
"edit-row": EditRowEvent;
}
}
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
title: optional(union([string(), boolean()])), title: optional(union([string(), boolean()])),
@ -60,7 +74,17 @@ export class HuiEntitiesCardEditor extends LitElement
@internalProperty() private _config?: EntitiesCardConfig; @internalProperty() private _config?: EntitiesCardConfig;
@internalProperty() private _configEntities?: EntitiesCardEntityConfig[]; @internalProperty() private _configEntities?: LovelaceRowConfig[];
@internalProperty() private _editRowConfig?: LovelaceRowConfig;
@internalProperty() private _editRowIndex?: number;
@internalProperty() private _editRowGuiModeAvailable? = true;
@internalProperty() private _editRowGuiMode? = true;
@query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: EntitiesCardConfig): void { public setConfig(config: EntitiesCardConfig): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -81,8 +105,32 @@ export class HuiEntitiesCardEditor extends LitElement
return html``; return html``;
} }
if (this._editRowConfig) {
return html`
<hui-detail-editor-base
.hass=${this.hass}
.guiModeAvailable=${this._editRowGuiModeAvailable}
.guiMode=${this._editRowGuiMode}
@toggle-gui-mode=${this._toggleMode}
@go-back=${this._goBack}
>
<span slot="title"
>${this.hass.localize(
"ui.panel.lovelace.editor.card.entities.entity_row_editor"
)}</span
>
<hui-element-editor
.hass=${this.hass}
.value=${this._editRowConfig}
elementType="row"
@config-changed=${this._handleEntityRowConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-element-editor>
</hui-detail-editor-base>
`;
}
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -127,11 +175,11 @@ export class HuiEntitiesCardEditor extends LitElement
</ha-formfield> </ha-formfield>
</div> </div>
</div> </div>
<hui-entities-card-row-editor <hui-entities-card-row-editor
.hass=${this.hass} .hass=${this.hass}
.entities=${this._configEntities} .entities=${this._configEntities}
@entities-changed=${this._valueChanged} @entities-changed=${this._valueChanged}
@edit-row=${this._editRow}
></hui-entities-card-row-editor> ></hui-entities-card-row-editor>
`; `;
} }
@ -169,6 +217,65 @@ export class HuiEntitiesCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
private _editRow(ev: HASSDomEvent<EditRowEvent>): void {
this._editRowIndex = ev.detail.index;
this._editRowConfig = this._configEntities![this._editRowIndex];
}
private _goBack(): void {
this._editRowIndex = undefined;
this._editRowConfig = undefined;
this._editRowGuiModeAvailable = true;
this._editRowGuiMode = true;
}
private _toggleMode(): void {
this._cardEditorEl?.toggleMode();
}
private _handleEntityRowConfigChanged(ev: CustomEvent): void {
ev.stopPropagation();
const value = ev.detail.config as LovelaceRowConfig;
this._editRowGuiModeAvailable = ev.detail.guiModeAvailable;
const newConfigEntities = this._configEntities!.concat();
if (!value) {
newConfigEntities.splice(this._editRowIndex!, 1);
this._goBack();
} else {
newConfigEntities[this._editRowIndex!] = value;
}
this._editRowConfig = value;
this._config = { ...this._config!, entities: newConfigEntities };
this._configEntities = processEditorEntities(this._config!.entities);
fireEvent(this, "config-changed", { config: this._config! });
}
private _handleGUIModeChanged(ev: HASSDomEvent<GUIModeChangedEvent>): void {
ev.stopPropagation();
this._editRowGuiMode = ev.detail.guiMode;
this._editRowGuiModeAvailable = ev.detail.guiModeAvailable;
}
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
.edit-entity-row-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
}
`,
];
}
} }
declare global { declare global {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -75,7 +76,6 @@ export class HuiEntityCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -186,6 +186,10 @@ export class HuiEntityCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -81,7 +81,6 @@ export class HuiGaugeCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -197,8 +196,10 @@ export class HuiGaugeCardEditor extends LitElement
`; `;
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.severity { .severity {
display: none; display: none;
width: 100%; width: 100%;
@ -213,7 +214,8 @@ export class HuiGaugeCardEditor extends LitElement
ha-switch[checked] ~ .severity { ha-switch[checked] ~ .severity {
display: flex; display: flex;
} }
`; `,
];
} }
private _toggleSeverity(ev: EntitiesEditorEvent): void { private _toggleSeverity(ev: EntitiesEditorEvent): void {

View File

@ -0,0 +1,173 @@
import "@polymer/paper-input/paper-input";
import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { assert } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { stateIcon } from "../../../../common/entity/state_icon";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon-input";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { EntitiesCardEntityConfig } from "../../cards/types";
import "../../components/hui-action-editor";
import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor";
import { LovelaceRowEditor } from "../../types";
import {
EditorTarget,
entitiesConfigStruct,
EntitiesEditorEvent,
} from "../types";
import { configElementStyle } from "./config-elements-style";
const SecondaryInfoValues: { [key: string]: { domains?: string[] } } = {
"entity-id": {},
"last-changed": {},
"last-triggered": { domains: ["automation", "script"] },
position: { domains: ["cover"] },
"tilt-position": { domains: ["cover"] },
brightness: { domains: ["light"] },
};
@customElement("hui-generic-entity-row-editor")
export class HuiGenericEntityRowEditor extends LitElement
implements LovelaceRowEditor {
@property({ attribute: false }) public hass?: HomeAssistant;
@internalProperty() private _config?: EntitiesCardEntityConfig;
public setConfig(config: EntitiesCardEntityConfig): void {
assert(config, entitiesConfigStruct);
this._config = config;
}
get _entity(): string {
return this._config!.entity || "";
}
get _name(): string {
return this._config!.name || "";
}
get _icon(): string {
return this._config!.icon || "";
}
get _secondary_info(): string {
return this._config!.secondary_info || "";
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
const domain = computeDomain(this._config.entity);
return html`
<div class="card-config">
<ha-entity-picker
allow-custom-entity
.hass=${this.hass}
.value=${this._config.entity}
.configValue=${"entity"}
@change=${this._valueChanged}
></ha-entity-picker>
<div class="side-by-side">
<paper-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.name"
)}
.value=${this._config.name}
.configValue=${"name"}
@value-changed=${this._valueChanged}
></paper-input>
<ha-icon-input
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.card.generic.icon"
)}
.value=${this._config.icon}
.placeholder=${stateIcon(this.hass!.states[this._config.entity])}
.configValue=${"icon"}
@value-changed=${this._valueChanged}
></ha-icon-input>
</div>
<paper-dropdown-menu .label=${"Secondary Info"}>
<paper-listbox
slot="dropdown-content"
attr-for-selected="value"
.selected=${this._config.secondary_info || "none"}
.configValue=${"secondary_info"}
@iron-select=${this._valueChanged}
>
<paper-item value=""
>${this.hass!.localize(
"ui.panel.lovelace.editor.card.entities.secondary_info_values.none"
)}</paper-item
>
${Object.keys(SecondaryInfoValues).map((info) => {
if (
!("domains" in SecondaryInfoValues[info]) ||
("domains" in SecondaryInfoValues[info] &&
SecondaryInfoValues[info].domains!.includes(domain))
) {
return html`
<paper-item .value=${info}
>${this.hass!.localize(
`ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}`
)}</paper-item
>
`;
}
return "";
})}
</paper-listbox>
</paper-dropdown-menu>
</div>
`;
}
private _valueChanged(ev: EntitiesEditorEvent): void {
if (!this._config || !this.hass) {
return;
}
const target = ev.target! as EditorTarget;
const value = target.value || ev.detail?.item?.value;
if (this[`_${target.configValue}`] === value) {
return;
}
if (target.configValue) {
if (value === "" || !value) {
this._config = { ...this._config };
delete this._config[target.configValue!];
} else {
this._config = {
...this._config,
[target.configValue!]: value,
};
}
}
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResult {
return configElementStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-generic-entity-row-editor": HuiGenericEntityRowEditor;
}
}

View File

@ -2,19 +2,31 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import {
array,
assert,
boolean,
number,
object,
optional,
string,
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge"; import "../../../../components/entity/state-badge";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-switch"; import "../../../../components/ha-switch";
import "../../../../components/ha-formfield";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ConfigEntity, GlanceCardConfig } from "../../cards/types"; import { ConfigEntity, GlanceCardConfig } from "../../cards/types";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
@ -27,17 +39,6 @@ import {
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
union,
object,
optional,
number,
boolean,
assert,
array,
} from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -102,7 +103,6 @@ export class HuiGlanceCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!); const dir = computeRTLDirection(this.hass!);
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -234,6 +234,10 @@ export class HuiGlanceCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,31 +1,32 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import {
array,
assert,
number,
object,
optional,
string,
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { HistoryGraphCardConfig } from "../../cards/types"; import { HistoryGraphCardConfig } from "../../cards/types";
import { EntityId } from "../../common/structs/is-entity-id";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import { EntityConfig } from "../../entity-rows/types"; import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { processEditorEntities } from "../process-editor-entities"; import { processEditorEntities } from "../process-editor-entities";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import {
assert,
union,
optional,
string,
object,
array,
number,
} from "superstruct";
import { EntityId } from "../../common/structs/is-entity-id";
const entitiesConfigStruct = union([ const entitiesConfigStruct = union([
object({ object({
@ -80,7 +81,6 @@ export class HuiHistoryGraphCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -156,6 +156,10 @@ export class HuiHistoryGraphCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,12 +1,14 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -15,7 +17,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -56,7 +57,6 @@ export class HuiHumidifierCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -110,6 +110,10 @@ export class HuiHumidifierCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,19 +1,20 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { IframeCardConfig } from "../../cards/types"; import { IframeCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -52,7 +53,6 @@ export class HuiIframeCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -110,6 +110,10 @@ export class HuiIframeCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -84,7 +85,6 @@ export class HuiLightCardEditor extends LitElement
]; ];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -183,6 +183,10 @@ export class HuiLightCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,15 +1,27 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import {
array,
assert,
boolean,
number,
object,
optional,
string,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield";
import "../../../../components/ha-switch";
import { PolymerChangedEvent } from "../../../../polymer-types"; import { PolymerChangedEvent } from "../../../../polymer-types";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MapCardConfig } from "../../cards/types"; import { MapCardConfig } from "../../cards/types";
@ -23,19 +35,7 @@ import {
entitiesConfigStruct, entitiesConfigStruct,
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import "../../../../components/ha-switch";
import "../../../../components/ha-formfield";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
optional,
object,
number,
boolean,
array,
assert,
} from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -94,7 +94,6 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -216,12 +215,15 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.geo_location_sources { .geo_location_sources {
padding-left: 20px; padding-left: 20px;
} }
`; `,
];
} }
} }

View File

@ -1,13 +1,15 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-input/paper-textarea";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MarkdownCardConfig } from "../../cards/types"; import { MarkdownCardConfig } from "../../cards/types";
@ -15,7 +17,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -54,7 +55,6 @@ export class HuiMarkdownCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -116,6 +116,10 @@ export class HuiMarkdownCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -62,7 +63,6 @@ export class HuiPictureCardEditor extends LitElement
const actions = ["navigate", "url", "call-service", "none"]; const actions = ["navigate", "url", "call-service", "none"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -133,6 +133,10 @@ export class HuiPictureCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -3,6 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -108,7 +109,6 @@ export class HuiPictureEntityCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!); const dir = computeRTLDirection(this.hass!);
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -293,6 +293,10 @@ export class HuiPictureEntityCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -3,6 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -111,7 +112,6 @@ export class HuiPictureGlanceCardEditor extends LitElement
const views = ["auto", "live"]; const views = ["auto", "live"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -257,6 +257,10 @@ export class HuiPictureGlanceCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,12 +1,14 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
@ -16,7 +18,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, object, string, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -57,7 +58,6 @@ export class HuiPlantStatusCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -113,6 +113,10 @@ export class HuiPlantStatusCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -3,6 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty, internalProperty,
@ -90,7 +91,6 @@ export class HuiSensorCardEditor extends LitElement
const graphs = ["line", "none"]; const graphs = ["line", "none"];
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -238,6 +238,10 @@ export class HuiSensorCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,4 +1,3 @@
import "../../../../components/ha-icon-button";
import "@polymer/paper-tabs"; import "@polymer/paper-tabs";
import "@polymer/paper-tabs/paper-tab"; import "@polymer/paper-tabs/paper-tab";
import { import {
@ -6,24 +5,26 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { any, array, assert, object, optional, string } from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { LovelaceConfig } from "../../../../data/lovelace"; import "../../../../components/ha-icon-button";
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { StackCardConfig } from "../../cards/types"; import { StackCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import {
ConfigChangedEvent,
HuiCardEditor,
} from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import { GUIModeChangedEvent } from "../types"; import { GUIModeChangedEvent } from "../types";
import { assert, object, string, array, any, optional } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -46,7 +47,7 @@ export class HuiStackCardEditor extends LitElement
@internalProperty() private _guiModeAvailable? = true; @internalProperty() private _guiModeAvailable? = true;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: Readonly<StackCardConfig>): void { public setConfig(config: Readonly<StackCardConfig>): void {
assert(config, cardConfigStruct); assert(config, cardConfigStruct);
@ -128,13 +129,13 @@ export class HuiStackCardEditor extends LitElement
></ha-icon-button> ></ha-icon-button>
</div> </div>
<hui-card-editor <hui-element-editor
.hass=${this.hass} .hass=${this.hass}
.value=${this._config.cards[selected]} .value=${this._config.cards[selected]}
.lovelace=${this.lovelace} .lovelace=${this.lovelace}
@config-changed=${this._handleConfigChanged} @config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged} @GUImode-changed=${this._handleGUIModeChanged}
></hui-card-editor> ></hui-element-editor>
` `
: html` : html`
<hui-card-picker <hui-card-picker
@ -164,7 +165,7 @@ export class HuiStackCardEditor extends LitElement
return; return;
} }
const cards = [...this._config.cards]; const cards = [...this._config.cards];
cards[this._selectedCard] = ev.detail.config; cards[this._selectedCard] = ev.detail.config as LovelaceCardConfig;
this._config = { ...this._config, cards }; this._config = { ...this._config, cards };
this._guiModeAvailable = ev.detail.guiModeAvailable; this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });

View File

@ -1,12 +1,14 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -15,7 +17,6 @@ import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { object, string, optional, assert } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -56,7 +57,6 @@ export class HuiThermostatCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -110,6 +110,10 @@ export class HuiThermostatCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -1,23 +1,24 @@
import { import {
CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { assert, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { WeatherForecastCardConfig } from "../../cards/types"; import { WeatherForecastCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { object, string, optional, boolean, assert } from "superstruct";
const cardConfigStruct = object({ const cardConfigStruct = object({
type: string(), type: string(),
@ -68,7 +69,6 @@ export class HuiWeatherForecastCardEditor extends LitElement
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<ha-entity-picker <ha-entity-picker
.label="${this.hass.localize( .label="${this.hass.localize(
@ -151,6 +151,10 @@ export class HuiWeatherForecastCardEditor extends LitElement
} }
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -0,0 +1,86 @@
import "@material/mwc-button";
import "@material/mwc-icon-button";
import { mdiArrowLeft } from "@mdi/js";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-svg-icon";
import { HomeAssistant } from "../../../types";
declare global {
interface HASSDomEvents {
"go-back": undefined;
"toggle-gui-mode": undefined;
}
}
@customElement("hui-detail-editor-base")
export class HuiDetailEditorBase extends LitElement {
public hass!: HomeAssistant;
@property({ type: Boolean }) public guiModeAvailable? = true;
@property({ type: Boolean }) public guiMode? = true;
protected render(): TemplateResult {
return html`
<div class="header">
<div class="back-title">
<mwc-icon-button @click=${this._goBack}>
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
</mwc-icon-button>
<slot name="title"></slot>
</div>
<mwc-button
slot="secondaryAction"
class="gui-mode-button"
.disabled=${!this.guiModeAvailable}
@click=${this._toggleMode}
>
${this.hass.localize(
this.guiMode
? "ui.panel.lovelace.editor.edit_card.show_code_editor"
: "ui.panel.lovelace.editor.edit_card.show_visual_editor"
)}
</mwc-button>
</div>
<slot></slot>
`;
}
private _goBack(): void {
fireEvent(this, "go-back");
}
private _toggleMode(): void {
fireEvent(this, "toggle-gui-mode");
}
static get styles(): CSSResult {
return css`
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.back-title {
display: flex;
align-items: center;
font-size: 18px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-detail-editor-base": HuiDetailEditorBase;
}
}

View File

@ -11,26 +11,33 @@ import {
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeRTL } from "../../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { deepEqual } from "../../../../common/util/deep-equal"; import { deepEqual } from "../../../common/util/deep-equal";
import "../../../../components/ha-circular-progress"; import "../../../components/ha-circular-progress";
import "../../../../components/ha-code-editor"; import "../../../components/ha-code-editor";
import type { HaCodeEditor } from "../../../../components/ha-code-editor"; import type { HaCodeEditor } from "../../../components/ha-code-editor";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceConfig, LovelaceConfig,
} from "../../../../data/lovelace"; } from "../../../data/lovelace";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../types";
import { handleStructError } from "../../common/structs/handle-errors"; import { handleStructError } from "../common/structs/handle-errors";
import { getCardElementClass } from "../../create-element/create-card-element"; import { getCardElementClass } from "../create-element/create-card-element";
import type { LovelaceRowConfig } from "../../entity-rows/types"; import { getRowElementClass } from "../create-element/create-row-element";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceRowConfig } from "../entity-rows/types";
import { GUISupportError } from "../gui-support-error"; import type {
import type { GUIModeChangedEvent } from "../types"; LovelaceCardConstructor,
LovelaceCardEditor,
LovelaceRowConstructor,
LovelaceRowEditor,
} from "../types";
import "./config-elements/hui-generic-entity-row-editor";
import { GUISupportError } from "./gui-support-error";
import { GUIModeChangedEvent } from "./types";
export interface ConfigChangedEvent { export interface ConfigChangedEvent {
config: LovelaceCardConfig; config: LovelaceCardConfig | LovelaceRowConfig;
error?: string; error?: string;
guiModeAvailable?: boolean; guiModeAvailable?: boolean;
} }
@ -47,21 +54,27 @@ declare global {
export interface UIConfigChangedEvent extends Event { export interface UIConfigChangedEvent extends Event {
detail: { detail: {
config: LovelaceCardConfig; config: LovelaceCardConfig | LovelaceRowConfig;
}; };
} }
@customElement("hui-card-editor") const GENERIC_ROW_TYPE = "generic-row";
export class HuiCardEditor extends LitElement {
@customElement("hui-element-editor")
export class HuiElementEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: LovelaceConfig; @property({ attribute: false }) public lovelace?: LovelaceConfig;
@property() public elementType: "row" | "card" = "card";
@internalProperty() private _yaml?: string; @internalProperty() private _yaml?: string;
@internalProperty() private _config?: LovelaceCardConfig; @internalProperty() private _config?: LovelaceCardConfig | LovelaceRowConfig;
@internalProperty() private _configElement?: LovelaceCardEditor; @internalProperty() private _configElement?:
| LovelaceCardEditor
| LovelaceRowEditor;
@internalProperty() private _configElType?: string; @internalProperty() private _configElType?: string;
@ -95,11 +108,11 @@ export class HuiCardEditor extends LitElement {
this._setConfig(); this._setConfig();
} }
public get value(): LovelaceCardConfig | undefined { public get value(): LovelaceCardConfig | LovelaceRowConfig | undefined {
return this._config; return this._config;
} }
public set value(config: LovelaceCardConfig | undefined) { public set value(config: LovelaceCardConfig | LovelaceRowConfig | undefined) {
if (this._config && deepEqual(config, this._config)) { if (this._config && deepEqual(config, this._config)) {
return; return;
} }
@ -220,7 +233,11 @@ export class HuiCardEditor extends LitElement {
if (this._configElement && changedProperties.has("hass")) { if (this._configElement && changedProperties.has("hass")) {
this._configElement.hass = this.hass; this._configElement.hass = this.hass;
} }
if (this._configElement && changedProperties.has("lovelace")) { if (
this._configElement &&
"lovelace" in this._configElement &&
changedProperties.has("lovelace")
) {
this._configElement.lovelace = this.lovelace; this._configElement.lovelace = this.lovelace;
} }
} }
@ -244,37 +261,61 @@ export class HuiCardEditor extends LitElement {
return; return;
} }
const cardType = this.value.type; let type: string;
if (
this.elementType === "row" &&
!this.value.type &&
"entity" in this.value
) {
type = GENERIC_ROW_TYPE;
} else {
type = this.value.type!;
}
let configElement = this._configElement; let configElement = this._configElement;
try { try {
this._error = undefined; this._error = undefined;
this._warnings = undefined; this._warnings = undefined;
if (this._configElType !== cardType) { if (this._configElType !== type) {
// If the card type has changed, we need to load a new GUI editor // If the type has changed, we need to load a new GUI editor
if (!this.value.type) { if (!type) {
throw new Error("No card type defined"); throw new Error(`No ${this.elementType} type defined`);
} }
const elClass = await getCardElementClass(cardType); let elClass:
| LovelaceCardConstructor
| LovelaceRowConstructor
| undefined;
if (this.elementType === "card") {
elClass = await getCardElementClass(type);
} else if (this.elementType === "row" && type !== GENERIC_ROW_TYPE) {
elClass = await getRowElementClass(type);
}
this._loading = true; this._loading = true;
// Check if a GUI editor exists // Check if a GUI editor exists
if (elClass && elClass.getConfigElement) { if (elClass && elClass.getConfigElement) {
configElement = await elClass.getConfigElement(); configElement = await elClass.getConfigElement();
} else if (this.elementType === "row" && type === GENERIC_ROW_TYPE) {
configElement = document.createElement(
"hui-generic-entity-row-editor"
);
} else { } else {
configElement = undefined; configElement = undefined;
throw new GUISupportError( throw new GUISupportError(`No visual editor available for: ${type}`);
`No visual editor available for: ${cardType}`
);
} }
this._configElement = configElement; this._configElement = configElement;
this._configElType = cardType; this._configElType = type;
// Perform final setup // Perform final setup
this._configElement.hass = this.hass; this._configElement.hass = this.hass;
if ("lovelace" in this._configElement) {
this._configElement.lovelace = this.lovelace; this._configElement.lovelace = this.lovelace;
}
this._configElement.addEventListener("config-changed", (ev) => this._configElement.addEventListener("config-changed", (ev) =>
this._handleUIConfigChanged(ev as UIConfigChangedEvent) this._handleUIConfigChanged(ev as UIConfigChangedEvent)
); );
@ -282,6 +323,7 @@ export class HuiCardEditor extends LitElement {
// Setup GUI editor and check that it can handle the current config // Setup GUI editor and check that it can handle the current config
try { try {
// @ts-ignore
this._configElement!.setConfig(this.value); this._configElement!.setConfig(this.value);
} catch (err) { } catch (err) {
throw new GUISupportError( throw new GUISupportError(
@ -340,6 +382,6 @@ export class HuiCardEditor extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hui-card-editor": HuiCardEditor; "hui-element-editor": HuiElementEditor;
} }
} }

View File

@ -1,4 +1,5 @@
import { mdiClose, mdiDrag } from "@mdi/js"; import "@material/mwc-icon-button";
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
@ -19,7 +20,7 @@ import Sortable, {
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker"; import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker"; import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon";
import { sortableStyles } from "../../../resources/ha-sortable-style"; import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types"; import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
@ -85,26 +86,38 @@ export class HuiEntitiesCardRowEditor extends LitElement {
)}</span )}</span
> >
</div> </div>
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.clear"
)}
.index=${index}
@click=${this._removeSpecialRow}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</div> </div>
` `
: html` : html`
<ha-entity-picker <ha-entity-picker
allow-custom-entity allow-custom-entity
hideClearIcon
.hass=${this.hass} .hass=${this.hass}
.value=${(entityConf as EntityConfig).entity} .value=${(entityConf as EntityConfig).entity}
.index=${index} .index=${index}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
></ha-entity-picker> ></ha-entity-picker>
`} `}
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.clear"
)}
class="remove-icon"
.index=${index}
@click=${this._removeRow}
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button
aria-label=${this.hass!.localize(
"ui.components.entity.entity-picker.edit"
)}
class="edit-icon"
.index=${index}
@click=${this._editRow}
>
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
</mwc-icon-button>
</div> </div>
`; `;
}) })
@ -164,7 +177,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
animation: 150, animation: 150,
fallbackClass: "sortable-fallback", fallbackClass: "sortable-fallback",
handle: ".handle", handle: ".handle",
onEnd: async (evt: SortableEvent) => this._entityMoved(evt), onEnd: async (evt: SortableEvent) => this._rowMoved(evt),
}); });
} }
@ -180,7 +193,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities }); fireEvent(this, "entities-changed", { entities: newConfigEntities });
} }
private _entityMoved(ev: SortableEvent): void { private _rowMoved(ev: SortableEvent): void {
if (ev.oldIndex === ev.newIndex) { if (ev.oldIndex === ev.newIndex) {
return; return;
} }
@ -192,7 +205,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newEntities }); fireEvent(this, "entities-changed", { entities: newEntities });
} }
private _removeSpecialRow(ev: CustomEvent): void { private _removeRow(ev: CustomEvent): void {
const index = (ev.currentTarget as any).index; const index = (ev.currentTarget as any).index;
const newConfigEntities = this.entities!.concat(); const newConfigEntities = this.entities!.concat();
@ -218,6 +231,12 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newConfigEntities }); fireEvent(this, "entities-changed", { entities: newConfigEntities });
} }
private _editRow(ev: CustomEvent): void {
fireEvent(this, "edit-row", {
index: (ev.currentTarget as any).index,
});
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
sortableStyles, sortableStyles,
@ -226,13 +245,16 @@ export class HuiEntitiesCardRowEditor extends LitElement {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.entity .handle { .entity .handle {
padding-right: 8px; padding-right: 8px;
cursor: move; cursor: move;
} }
.entity ha-entity-picker { .entity ha-entity-picker {
flex-grow: 1; flex-grow: 1;
} }
.special-row { .special-row {
height: 60px; height: 60px;
font-size: 16px; font-size: 16px;
@ -247,7 +269,8 @@ export class HuiEntitiesCardRowEditor extends LitElement {
flex-direction: column; flex-direction: column;
} }
.special-row mwc-icon-button { .remove-icon,
.edit-icon {
--mdc-icon-button-size: 36px; --mdc-icon-button-size: 36px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }

View File

@ -1,5 +1,6 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
CSSResult,
customElement, customElement,
html, html,
LitElement, LitElement,
@ -35,7 +36,6 @@ export class HuiLovelaceEditor extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label=${this.hass.localize( .label=${this.hass.localize(
@ -71,6 +71,10 @@ export class HuiLovelaceEditor extends LitElement {
fireEvent(this, "lovelace-config-changed", { config: newConfig }); fireEvent(this, "lovelace-config-changed", { config: newConfig });
} }
static get styles(): CSSResult {
return configElementStyle;
}
} }
declare global { declare global {

View File

@ -2,6 +2,7 @@ import {
any, any,
array, array,
boolean, boolean,
number,
object, object,
optional, optional,
string, string,
@ -13,8 +14,6 @@ import {
LovelaceViewConfig, LovelaceViewConfig,
ShowViewConfig, ShowViewConfig,
} from "../../../data/lovelace"; } from "../../../data/lovelace";
import { EntityId } from "../common/structs/is-entity-id";
import { Icon } from "../common/structs/is-icon";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
export interface YamlChangedEvent extends Event { export interface YamlChangedEvent extends Event {
@ -51,6 +50,7 @@ export interface ConfigError {
export interface EntitiesEditorEvent { export interface EntitiesEditorEvent {
detail?: { detail?: {
entities?: EntityConfig[]; entities?: EntityConfig[];
item?: any;
}; };
target?: EventTarget; target?: EventTarget;
} }
@ -95,19 +95,19 @@ const buttonEntitiesRowConfigStruct = object({
const castEntitiesRowConfigStruct = object({ const castEntitiesRowConfigStruct = object({
type: string(), type: string(),
view: string(), view: union([string(), number()]),
dashboard: optional(string()), dashboard: optional(string()),
name: optional(string()), name: optional(string()),
icon: optional(string()), icon: optional(string()),
hide_if_unavailable: optional(string()), hide_if_unavailable: optional(boolean()),
}); });
const callServiceEntitiesRowConfigStruct = object({ const callServiceEntitiesRowConfigStruct = object({
type: string(), type: string(),
name: string(), name: string(),
service: string(),
icon: optional(string()), icon: optional(string()),
action_name: optional(string()), action_name: optional(string()),
service: string(),
service_data: optional(any()), service_data: optional(any()),
}); });
@ -150,7 +150,7 @@ const buttonsEntitiesRowConfigStruct = object({
image: optional(string()), image: optional(string()),
name: optional(string()), name: optional(string()),
}), }),
EntityId, string(),
]) ])
), ),
}); });
@ -166,9 +166,9 @@ const attributeEntitiesRowConfigStruct = object({
export const entitiesConfigStruct = union([ export const entitiesConfigStruct = union([
object({ object({
entity: EntityId, entity: string(),
name: optional(string()), name: optional(string()),
icon: optional(Icon), icon: optional(string()),
image: optional(string()), image: optional(string()),
secondary_info: optional(string()), secondary_info: optional(string()),
format: optional(string()), format: optional(string()),
@ -177,7 +177,7 @@ export const entitiesConfigStruct = union([
hold_action: optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct),
}), }),
EntityId, string(),
buttonEntitiesRowConfigStruct, buttonEntitiesRowConfigStruct,
castEntitiesRowConfigStruct, castEntitiesRowConfigStruct,
conditionalEntitiesRowConfigStruct, conditionalEntitiesRowConfigStruct,

View File

@ -1,25 +1,25 @@
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResult, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { slugify } from "../../../../common/string/slugify"; import { slugify } from "../../../../common/string/slugify";
import "../../../../components/ha-switch"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import "../../../../components/ha-icon-input"; import "../../../../components/ha-icon-input";
import "../../../../components/ha-switch";
import { LovelaceViewConfig } from "../../../../data/lovelace"; import { LovelaceViewConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { configElementStyle } from "../config-elements/config-elements-style"; import { configElementStyle } from "../config-elements/config-elements-style";
import { EditorTarget } from "../types"; import { EditorTarget } from "../types";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -84,7 +84,6 @@ export class HuiViewEditor extends LitElement {
} }
return html` return html`
${configElementStyle}
<div class="card-config"> <div class="card-config">
<paper-input <paper-input
.label="${this.hass.localize( .label="${this.hass.localize(
@ -182,13 +181,16 @@ export class HuiViewEditor extends LitElement {
fireEvent(this, "view-config-changed", { config }); fireEvent(this, "view-config-changed", { config });
} }
static get styles(): CSSResult { static get styles(): CSSResultArray {
return css` return [
configElementStyle,
css`
.panel { .panel {
color: var(--secondary-text-color); color: var(--secondary-text-color);
display: block; display: block;
} }
`; `,
];
} }
} }

View File

@ -4,6 +4,7 @@ import {
LovelaceConfig, LovelaceConfig,
} from "../../data/lovelace"; } from "../../data/lovelace";
import { Constructor, HomeAssistant } from "../../types"; import { Constructor, HomeAssistant } from "../../types";
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
import { LovelaceHeaderFooterConfig } from "./header-footer/types"; import { LovelaceHeaderFooterConfig } from "./header-footer/types";
declare global { declare global {
@ -48,6 +49,10 @@ export interface LovelaceCardConstructor extends Constructor<LovelaceCard> {
getConfigElement?: () => LovelaceCardEditor; getConfigElement?: () => LovelaceCardEditor;
} }
export interface LovelaceRowConstructor extends Constructor<LovelaceRow> {
getConfigElement?: () => LovelaceRowEditor;
}
export interface LovelaceHeaderFooter extends HTMLElement { export interface LovelaceHeaderFooter extends HTMLElement {
hass?: HomeAssistant; hass?: HomeAssistant;
getCardSize(): number | Promise<number>; getCardSize(): number | Promise<number>;
@ -60,3 +65,9 @@ export interface LovelaceCardEditor extends HTMLElement {
setConfig(config: LovelaceCardConfig): void; setConfig(config: LovelaceCardConfig): void;
refreshYamlEditor?: (focus: boolean) => void; refreshYamlEditor?: (focus: boolean) => void;
} }
export interface LovelaceRowEditor extends HTMLElement {
hass?: HomeAssistant;
setConfig(config: LovelaceRowConfig): void;
refreshYamlEditor?: (focus: boolean) => void;
}

View File

@ -2331,6 +2331,16 @@
"description": "The Entities card is the most common type of card. It groups items together into lists.", "description": "The Entities card is the most common type of card. It groups items together into lists.",
"special_row": "special row", "special_row": "special row",
"edit_special_row": "Edit row using the code editor", "edit_special_row": "Edit row using the code editor",
"entity_row_editor": "Entity Row Editor",
"secondary_info_values": {
"none": "No Secondary Info",
"entity-id": "Entity ID",
"last-changed": "Last Changed",
"last-triggered": "Last Triggered",
"position": "Position",
"tilt-position": "Tilt Position",
"brightness": "Brightness"
},
"entity_row": { "entity_row": {
"divider": "Divider", "divider": "Divider",
"call-service": "Call Service", "call-service": "Call Service",