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

View File

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

View File

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

View File

@ -1,8 +1,10 @@
// Parse array of entity objects from config
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>
): T[] => {
if (!entities || !Array.isArray(entities)) {
@ -24,7 +26,7 @@ export const processConfigEntities = <T extends EntityConfig>(
if (typeof entityConf === "string") {
config = { entity: entityConf } as T;
} else if (typeof entityConf === "object" && !Array.isArray(entityConf)) {
if (!entityConf.entity) {
if (!("entity" in entityConf)) {
throw new Error(
`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}.`);
}
if (!isValidEntityId(config.entity)) {
if (!isValidEntityId((config as EntityConfig).entity!)) {
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,
LovelaceCardConstructor,
LovelaceHeaderFooter,
LovelaceRowConstructor,
} from "../types";
const TIMEOUT = 2000;
@ -39,7 +40,7 @@ interface CreateElementConfigTypes {
row: {
config: LovelaceRowConfig;
element: LovelaceRow;
constructor: unknown;
constructor: LovelaceRowConstructor;
};
"header-footer": {
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-text-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-button-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([
"media-player-entity",
@ -74,7 +77,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
weather: "weather",
};
export const createRowElement = (config: EntityConfig) =>
export const createRowElement = (config: LovelaceRowConfig) =>
createLovelaceElement(
"row",
config,
@ -83,3 +86,12 @@ export const createRowElement = (config: EntityConfig) =>
DOMAIN_TO_ELEMENT_TYPE,
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 {
css,
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
query,
TemplateResult,
PropertyValues,
} 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 { 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 {
LovelaceCardConfig,
LovelaceViewConfig,
} from "../../../../data/lovelace";
import "./hui-card-editor";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
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 "../../../../components/ha-dialog";
import "../../../../components/ha-header-bar";
import "../../../../components/ha-circular-progress";
import type { EditCardDialogParams } from "./show-edit-card-dialog";
declare global {
// for fire event
@ -65,7 +65,7 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@internalProperty() private _guiModeAvailable? = true;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor;
@query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
@internalProperty() private _GUImode = true;
@ -183,14 +183,14 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
</div>
<div class="content">
<div class="element-editor">
<hui-card-editor
<hui-element-editor
.hass=${this.hass}
.lovelace=${this._params.lovelaceConfig}
.value=${this._cardConfig}
@config-changed=${this._handleConfigChanged}
@GUImode-changed=${this._handleGUIModeChanged}
@editor-save=${this._save}
></hui-card-editor>
></hui-element-editor>
</div>
<div class="element-preview">
<hui-card-preview
@ -364,6 +364,8 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
@media all and (min-width: 850px) {
ha-dialog {
--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 {
--mdc-dialog-max-width: calc(100% - 32px);
--mdc-dialog-min-width: 1000px;
--dialog-surface-position: fixed;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
}
.content {

View File

@ -1,19 +1,17 @@
import { html } from "lit-element";
import { css } from "lit-element";
export const configElementStyle = html`
<style>
ha-switch {
padding: 16px 0;
}
.side-by-side {
display: flex;
}
.side-by-side > * {
flex: 1;
padding-right: 4px;
}
.suffix {
margin: 0 8px;
}
</style>
export const configElementStyle = css`
ha-switch {
padding: 16px 0;
}
.side-by-side {
display: flex;
}
.side-by-side > * {
flex: 1;
padding-right: 4px;
}
.suffix {
margin: 0 8px;
}
`;

View File

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

View File

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

View File

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

View File

@ -2,28 +2,30 @@ import "@polymer/paper-tabs";
import "@polymer/paper-tabs/paper-tab";
import {
css,
CSSResult,
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
query,
TemplateResult,
} from "lit-element";
import { any, array, assert, object, optional, string } from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker";
import { LovelaceConfig } from "../../../../data/lovelace";
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types";
import { ConditionalCardConfig } from "../../cards/types";
import { LovelaceCardEditor } from "../../types";
import {
ConfigChangedEvent,
HuiCardEditor,
} from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker";
import "../hui-element-editor";
import type {
ConfigChangedEvent,
HuiElementEditor,
} from "../hui-element-editor";
import { GUIModeChangedEvent } from "../types";
import { string, any, object, optional, array, assert } from "superstruct";
import { configElementStyle } from "./config-elements-style";
const conditionStruct = object({
entity: string(),
@ -51,7 +53,7 @@ export class HuiConditionalCardEditor extends LitElement
@internalProperty() private _cardTab = false;
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor;
@query("hui-element-editor") private _cardEditorEl?: HuiElementEditor;
public setConfig(config: ConditionalCardConfig): void {
assert(config, cardConfigStruct);
@ -106,13 +108,13 @@ export class HuiConditionalCardEditor extends LitElement
)}</mwc-button
>
</div>
<hui-card-editor
<hui-element-editor
.hass=${this.hass}
.value=${this._config.card}
.lovelace=${this.lovelace}
@config-changed=${this._handleCardChanged}
@GUImode-changed=${this._handleGUIModeChanged}
></hui-card-editor>
></hui-element-editor>
`
: html`
<hui-card-picker
@ -227,7 +229,10 @@ export class HuiConditionalCardEditor extends LitElement
if (!this._config) {
return;
}
this._config = { ...this._config, card: ev.detail.config };
this._config = {
...this._config,
card: ev.detail.config as LovelaceCardConfig,
};
this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config });
}
@ -292,52 +297,55 @@ export class HuiConditionalCardEditor extends LitElement
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResult {
return css`
paper-tabs {
--paper-tabs-selection-bar-color: var(--primary-color);
--paper-tab-ink: var(--primary-color);
border-bottom: 1px solid var(--divider-color);
}
.conditions {
margin-top: 8px;
}
.condition {
margin-top: 8px;
border: 1px solid var(--divider-color);
padding: 12px;
}
.condition .state {
display: flex;
align-items: flex-end;
}
.condition .state paper-dropdown-menu {
margin-right: 16px;
}
.condition .state paper-input {
flex-grow: 1;
}
.card {
margin-top: 8px;
border: 1px solid var(--divider-color);
padding: 12px;
}
@media (max-width: 450px) {
.card,
.condition {
margin: 8px -12px 0;
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
paper-tabs {
--paper-tabs-selection-bar-color: var(--primary-color);
--paper-tab-ink: var(--primary-color);
border-bottom: 1px solid var(--divider-color);
}
}
.card .card-options {
display: flex;
justify-content: flex-end;
width: 100%;
}
.gui-mode-button {
margin-right: auto;
}
`;
.conditions {
margin-top: 8px;
}
.condition {
margin-top: 8px;
border: 1px solid var(--divider-color);
padding: 12px;
}
.condition .state {
display: flex;
align-items: flex-end;
}
.condition .state paper-dropdown-menu {
margin-right: 16px;
}
.condition .state paper-input {
flex-grow: 1;
}
.card {
margin-top: 8px;
border: 1px solid var(--divider-color);
padding: 12px;
}
@media (max-width: 450px) {
.card,
.condition {
margin: 8px -12px 0;
}
}
.card .card-options {
display: flex;
justify-content: flex-end;
width: 100%;
}
.gui-mode-button {
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-listbox/paper-listbox";
import {
css,
CSSResultArray,
customElement,
html,
internalProperty,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import {
@ -18,7 +21,7 @@ import {
string,
union,
} 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 "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
@ -26,22 +29,33 @@ import "../../../../components/ha-formfield";
import "../../../../components/ha-icon";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import {
EntitiesCardConfig,
EntitiesCardEntityConfig,
} from "../../cards/types";
import { EntitiesCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceRowConfig } from "../../entity-rows/types";
import { headerFooterConfigStructs } from "../../header-footer/types";
import { LovelaceCardEditor } from "../../types";
import "../hui-detail-editor-base";
import { HuiElementEditor } from "../hui-element-editor";
import "../hui-entities-card-row-editor";
import { processEditorEntities } from "../process-editor-entities";
import {
EditorTarget,
entitiesConfigStruct,
EntitiesEditorEvent,
GUIModeChangedEvent,
} from "../types";
import { configElementStyle } from "./config-elements-style";
interface EditRowEvent {
index: number;
}
declare global {
interface HASSDomEvents {
"edit-row": EditRowEvent;
}
}
const cardConfigStruct = object({
type: string(),
title: optional(union([string(), boolean()])),
@ -60,7 +74,17 @@ export class HuiEntitiesCardEditor extends LitElement
@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 {
assert(config, cardConfigStruct);
@ -81,8 +105,32 @@ export class HuiEntitiesCardEditor extends LitElement
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`
${configElementStyle}
<div class="card-config">
<paper-input
.label="${this.hass.localize(
@ -127,11 +175,11 @@ export class HuiEntitiesCardEditor extends LitElement
</ha-formfield>
</div>
</div>
<hui-entities-card-row-editor
.hass=${this.hass}
.entities=${this._configEntities}
@entities-changed=${this._valueChanged}
@edit-row=${this._editRow}
></hui-entities-card-row-editor>
`;
}
@ -169,6 +217,65 @@ export class HuiEntitiesCardEditor extends LitElement
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 {

View File

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

View File

@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
CSSResultArray,
customElement,
html,
internalProperty,
@ -81,7 +81,6 @@ export class HuiGaugeCardEditor extends LitElement
}
return html`
${configElementStyle}
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
@ -197,23 +196,26 @@ export class HuiGaugeCardEditor extends LitElement
`;
}
static get styles(): CSSResult {
return css`
.severity {
display: none;
width: 100%;
padding-left: 16px;
flex-direction: row;
flex-wrap: wrap;
}
.severity > * {
flex: 1 0 30%;
padding-right: 4px;
}
ha-switch[checked] ~ .severity {
display: flex;
}
`;
static get styles(): CSSResultArray {
return [
configElementStyle,
css`
.severity {
display: none;
width: 100%;
padding-left: 16px;
flex-direction: row;
flex-wrap: wrap;
}
.severity > * {
flex: 1 0 30%;
padding-right: 4px;
}
ha-switch[checked] ~ .severity {
display: flex;
}
`,
];
}
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-listbox/paper-listbox";
import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import {
array,
assert,
boolean,
number,
object,
optional,
string,
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
import "../../../../components/ha-formfield";
import "../../../../components/ha-icon";
import "../../../../components/ha-switch";
import "../../../../components/ha-formfield";
import { HomeAssistant } from "../../../../types";
import { ConfigEntity, GlanceCardConfig } from "../../cards/types";
import "../../components/hui-entity-editor";
@ -27,17 +39,6 @@ import {
EntitiesEditorEvent,
} from "../types";
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({
type: string(),
@ -102,7 +103,6 @@ export class HuiGlanceCardEditor extends LitElement
const dir = computeRTLDirection(this.hass!);
return html`
${configElementStyle}
<div class="card-config">
<paper-input
.label="${this.hass.localize(
@ -234,6 +234,10 @@ export class HuiGlanceCardEditor extends LitElement
}
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResult {
return configElementStyle;
}
}
declare global {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,24 @@
import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { assert, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-switch";
import "../../../../components/ha-formfield";
import "../../../../components/ha-switch";
import { HomeAssistant } from "../../../../types";
import { WeatherForecastCardConfig } from "../../cards/types";
import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { object, string, optional, boolean, assert } from "superstruct";
const cardConfigStruct = object({
type: string(),
@ -68,7 +69,6 @@ export class HuiWeatherForecastCardEditor extends LitElement
}
return html`
${configElementStyle}
<div class="card-config">
<ha-entity-picker
.label="${this.hass.localize(
@ -151,6 +151,10 @@ export class HuiWeatherForecastCardEditor extends LitElement
}
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResult {
return configElementStyle;
}
}
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,
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeRTL } from "../../../../common/util/compute_rtl";
import { deepEqual } from "../../../../common/util/deep-equal";
import "../../../../components/ha-circular-progress";
import "../../../../components/ha-code-editor";
import type { HaCodeEditor } from "../../../../components/ha-code-editor";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeRTL } from "../../../common/util/compute_rtl";
import { deepEqual } from "../../../common/util/deep-equal";
import "../../../components/ha-circular-progress";
import "../../../components/ha-code-editor";
import type { HaCodeEditor } from "../../../components/ha-code-editor";
import type {
LovelaceCardConfig,
LovelaceConfig,
} from "../../../../data/lovelace";
import type { HomeAssistant } from "../../../../types";
import { handleStructError } from "../../common/structs/handle-errors";
import { getCardElementClass } from "../../create-element/create-card-element";
import type { LovelaceRowConfig } from "../../entity-rows/types";
import type { LovelaceCardEditor } from "../../types";
import { GUISupportError } from "../gui-support-error";
import type { GUIModeChangedEvent } from "../types";
} from "../../../data/lovelace";
import type { HomeAssistant } from "../../../types";
import { handleStructError } from "../common/structs/handle-errors";
import { getCardElementClass } from "../create-element/create-card-element";
import { getRowElementClass } from "../create-element/create-row-element";
import type { LovelaceRowConfig } from "../entity-rows/types";
import type {
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 {
config: LovelaceCardConfig;
config: LovelaceCardConfig | LovelaceRowConfig;
error?: string;
guiModeAvailable?: boolean;
}
@ -47,21 +54,27 @@ declare global {
export interface UIConfigChangedEvent extends Event {
detail: {
config: LovelaceCardConfig;
config: LovelaceCardConfig | LovelaceRowConfig;
};
}
@customElement("hui-card-editor")
export class HuiCardEditor extends LitElement {
const GENERIC_ROW_TYPE = "generic-row";
@customElement("hui-element-editor")
export class HuiElementEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: LovelaceConfig;
@property() public elementType: "row" | "card" = "card";
@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;
@ -95,11 +108,11 @@ export class HuiCardEditor extends LitElement {
this._setConfig();
}
public get value(): LovelaceCardConfig | undefined {
public get value(): LovelaceCardConfig | LovelaceRowConfig | undefined {
return this._config;
}
public set value(config: LovelaceCardConfig | undefined) {
public set value(config: LovelaceCardConfig | LovelaceRowConfig | undefined) {
if (this._config && deepEqual(config, this._config)) {
return;
}
@ -220,7 +233,11 @@ export class HuiCardEditor extends LitElement {
if (this._configElement && changedProperties.has("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;
}
}
@ -244,37 +261,61 @@ export class HuiCardEditor extends LitElement {
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;
try {
this._error = undefined;
this._warnings = undefined;
if (this._configElType !== cardType) {
// If the card type has changed, we need to load a new GUI editor
if (!this.value.type) {
throw new Error("No card type defined");
if (this._configElType !== type) {
// If the type has changed, we need to load a new GUI editor
if (!type) {
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;
// Check if a GUI editor exists
if (elClass && elClass.getConfigElement) {
configElement = await elClass.getConfigElement();
} else if (this.elementType === "row" && type === GENERIC_ROW_TYPE) {
configElement = document.createElement(
"hui-generic-entity-row-editor"
);
} else {
configElement = undefined;
throw new GUISupportError(
`No visual editor available for: ${cardType}`
);
throw new GUISupportError(`No visual editor available for: ${type}`);
}
this._configElement = configElement;
this._configElType = cardType;
this._configElType = type;
// Perform final setup
this._configElement.hass = this.hass;
this._configElement.lovelace = this.lovelace;
if ("lovelace" in this._configElement) {
this._configElement.lovelace = this.lovelace;
}
this._configElement.addEventListener("config-changed", (ev) =>
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
try {
// @ts-ignore
this._configElement!.setConfig(this.value);
} catch (err) {
throw new GUISupportError(
@ -340,6 +382,6 @@ export class HuiCardEditor extends LitElement {
declare global {
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 {
css,
CSSResult,
@ -19,7 +20,7 @@ import Sortable, {
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../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 { HomeAssistant } from "../../../types";
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
@ -85,26 +86,38 @@ export class HuiEntitiesCardRowEditor extends LitElement {
)}</span
>
</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>
`
: html`
<ha-entity-picker
allow-custom-entity
hideClearIcon
.hass=${this.hass}
.value=${(entityConf as EntityConfig).entity}
.index=${index}
@value-changed=${this._valueChanged}
></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>
`;
})
@ -164,7 +177,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
animation: 150,
fallbackClass: "sortable-fallback",
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 });
}
private _entityMoved(ev: SortableEvent): void {
private _rowMoved(ev: SortableEvent): void {
if (ev.oldIndex === ev.newIndex) {
return;
}
@ -192,7 +205,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
fireEvent(this, "entities-changed", { entities: newEntities });
}
private _removeSpecialRow(ev: CustomEvent): void {
private _removeRow(ev: CustomEvent): void {
const index = (ev.currentTarget as any).index;
const newConfigEntities = this.entities!.concat();
@ -218,6 +231,12 @@ export class HuiEntitiesCardRowEditor extends LitElement {
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[] {
return [
sortableStyles,
@ -226,13 +245,16 @@ export class HuiEntitiesCardRowEditor extends LitElement {
display: flex;
align-items: center;
}
.entity .handle {
padding-right: 8px;
cursor: move;
}
.entity ha-entity-picker {
flex-grow: 1;
}
.special-row {
height: 60px;
font-size: 16px;
@ -247,7 +269,8 @@ export class HuiEntitiesCardRowEditor extends LitElement {
flex-direction: column;
}
.special-row mwc-icon-button {
.remove-icon,
.edit-icon {
--mdc-icon-button-size: 36px;
color: var(--secondary-text-color);
}

View File

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

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import {
LovelaceConfig,
} from "../../data/lovelace";
import { Constructor, HomeAssistant } from "../../types";
import { LovelaceRow, LovelaceRowConfig } from "./entity-rows/types";
import { LovelaceHeaderFooterConfig } from "./header-footer/types";
declare global {
@ -48,6 +49,10 @@ export interface LovelaceCardConstructor extends Constructor<LovelaceCard> {
getConfigElement?: () => LovelaceCardEditor;
}
export interface LovelaceRowConstructor extends Constructor<LovelaceRow> {
getConfigElement?: () => LovelaceRowEditor;
}
export interface LovelaceHeaderFooter extends HTMLElement {
hass?: HomeAssistant;
getCardSize(): number | Promise<number>;
@ -60,3 +65,9 @@ export interface LovelaceCardEditor extends HTMLElement {
setConfig(config: LovelaceCardConfig): 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.",
"special_row": "special row",
"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": {
"divider": "Divider",
"call-service": "Call Service",