mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-14 05:20:31 +00:00
Add a sub-editor to hui-entity-editor (#27157)
* Add a sub-editor to hui-entity-editor * item styling
This commit is contained in:
@@ -283,7 +283,7 @@ export interface GlanceConfigEntity extends ConfigEntity {
|
||||
image?: string;
|
||||
show_state?: boolean;
|
||||
state_color?: boolean;
|
||||
format: TimestampRenderingFormat;
|
||||
format?: TimestampRenderingFormat;
|
||||
}
|
||||
|
||||
export interface GlanceCardConfig extends LovelaceCardConfig {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
@@ -12,6 +12,7 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-sortable";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { EntityConfig } from "../entity-rows/types";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
|
||||
@customElement("hui-entity-editor")
|
||||
export class HuiEntityEditor extends LitElement {
|
||||
@@ -24,6 +25,8 @@ export class HuiEntityEditor extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: "can-edit", type: Boolean }) public canEdit?;
|
||||
|
||||
private _entityKeys = new WeakMap<EntityConfig, string>();
|
||||
|
||||
private _getKey(action: EntityConfig) {
|
||||
@@ -34,6 +37,70 @@ export class HuiEntityEditor extends LitElement {
|
||||
return this._entityKeys.get(action)!;
|
||||
}
|
||||
|
||||
private _renderItem(item: EntityConfig, index: number) {
|
||||
const stateObj = this.hass!.states[item.entity];
|
||||
|
||||
const entityName =
|
||||
stateObj && this.hass!.formatEntityName(stateObj, "entity");
|
||||
const deviceName =
|
||||
stateObj && this.hass!.formatEntityName(stateObj, "device");
|
||||
const areaName = stateObj && this.hass!.formatEntityName(stateObj, "area");
|
||||
|
||||
const isRTL = computeRTL(this.hass!);
|
||||
|
||||
const primary = item.name || entityName || deviceName || item.entity;
|
||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(isRTL ? " ◂ " : " ▸ ");
|
||||
|
||||
return html`
|
||||
<ha-md-list-item class="item">
|
||||
<ha-svg-icon class="handle" .path=${mdiDrag} slot="start"></ha-svg-icon>
|
||||
|
||||
<div slot="headline" class="label">${primary}</div>
|
||||
${secondary
|
||||
? html`<div slot="supporting-text" class="description">
|
||||
${secondary}
|
||||
</div>`
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.item=${item}
|
||||
.index=${index}
|
||||
.label=${this.hass!.localize("ui.common.edit")}
|
||||
.path=${mdiPencil}
|
||||
@click=${this._editItem}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.index=${index}
|
||||
.label=${this.hass!.localize("ui.common.delete")}
|
||||
.path=${mdiClose}
|
||||
@click=${this._deleteItem}
|
||||
></ha-icon-button>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _editItem(ev) {
|
||||
const index = (ev.currentTarget as any).index;
|
||||
fireEvent(this, "edit-detail-element", {
|
||||
subElementConfig: {
|
||||
index,
|
||||
type: "row",
|
||||
elementConfig: this.entities![index],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _deleteItem(ev) {
|
||||
const index = ev.target.index;
|
||||
const newConfigEntities = this.entities!.slice(0, index).concat(
|
||||
this.entities!.slice(index + 1)
|
||||
);
|
||||
fireEvent(this, "entities-changed", { entities: newConfigEntities });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.entities) {
|
||||
return nothing;
|
||||
@@ -47,7 +114,26 @@ export class HuiEntityEditor extends LitElement {
|
||||
this.hass!.localize("ui.panel.lovelace.editor.card.config.required") +
|
||||
")"}
|
||||
</h3>
|
||||
<ha-sortable handle-selector=".handle" @item-moved=${this._entityMoved}>
|
||||
${this.canEdit
|
||||
? html`
|
||||
<div class="items-container">
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector=".item"
|
||||
@item-moved=${this._entityMoved}
|
||||
>
|
||||
<ha-md-list>
|
||||
${this.entities.map((item, index) =>
|
||||
this._renderItem(item, index)
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
`
|
||||
: html` <ha-sortable
|
||||
handle-selector=".handle"
|
||||
@item-moved=${this._entityMoved}
|
||||
>
|
||||
<div class="entities">
|
||||
${repeat(
|
||||
this.entities,
|
||||
@@ -69,7 +155,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
</ha-sortable>`}
|
||||
<ha-entity-picker
|
||||
class="add-entity"
|
||||
.hass=${this.hass}
|
||||
@@ -148,6 +234,35 @@ export class HuiEntityEditor extends LitElement {
|
||||
.entity ha-entity-picker {
|
||||
flex-grow: 1;
|
||||
}
|
||||
ha-md-list {
|
||||
gap: 8px;
|
||||
}
|
||||
ha-md-list-item {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 8px;
|
||||
--ha-md-list-item-gap: 0;
|
||||
--md-list-item-top-space: 0;
|
||||
--md-list-item-bottom-space: 0;
|
||||
--md-list-item-leading-space: 12px;
|
||||
--md-list-item-trailing-space: 4px;
|
||||
--md-list-item-two-line-container-height: 48px;
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
padding: 8px;
|
||||
margin-inline-start: -8px;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
ha-md-list-item .label,
|
||||
ha-md-list-item .description {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ export class HuiGenericEntityRowEditor
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public schema?;
|
||||
|
||||
@state() private _config?: EntitiesCardEntityConfig;
|
||||
|
||||
public setConfig(config: EntitiesCardEntityConfig): void {
|
||||
@@ -87,7 +89,8 @@ export class HuiGenericEntityRowEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const schema = this._schema(this._config.entity, this.hass.localize);
|
||||
const schema =
|
||||
this.schema || this._schema(this._config.entity, this.hass.localize);
|
||||
|
||||
return html`
|
||||
<ha-form
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "../hui-sub-element-editor";
|
||||
import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { ConfigEntity, GlanceCardConfig } from "../../cards/types";
|
||||
@@ -21,6 +24,7 @@ import type { LovelaceCardEditor } from "../../types";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { entitiesConfigStruct } from "../structs/entities-struct";
|
||||
import type { EntityConfig } from "../../entity-rows/types";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@@ -36,6 +40,49 @@ const cardConfigStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const SUB_SCHEMA = [
|
||||
{ name: "entity", selector: { entity: {} }, required: true },
|
||||
{
|
||||
type: "grid",
|
||||
name: "",
|
||||
schema: [
|
||||
{ name: "name", selector: { text: {} } },
|
||||
{
|
||||
name: "icon",
|
||||
selector: {
|
||||
icon: {},
|
||||
},
|
||||
context: {
|
||||
icon_entity: "entity",
|
||||
},
|
||||
},
|
||||
{ name: "show_last_changed", selector: { boolean: {} } },
|
||||
{ name: "show_state", selector: { boolean: {} }, default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "tap_action",
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "more-info",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "optional_actions",
|
||||
flatten: true,
|
||||
schema: (["hold_action", "double_tap_action"] as const).map((action) => ({
|
||||
name: action,
|
||||
selector: {
|
||||
ui_action: {
|
||||
default_action: "none" as const,
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
] as const;
|
||||
|
||||
const SCHEMA = [
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
@@ -68,6 +115,8 @@ export class HuiGlanceCardEditor
|
||||
|
||||
@state() private _config?: GlanceCardConfig;
|
||||
|
||||
@state() private _subElementEditorConfig?: SubElementEditorConfig;
|
||||
|
||||
@state() private _configEntities?: ConfigEntity[];
|
||||
|
||||
public setConfig(config: GlanceCardConfig): void {
|
||||
@@ -81,6 +130,19 @@ export class HuiGlanceCardEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (this._subElementEditorConfig) {
|
||||
return html`
|
||||
<hui-sub-element-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._subElementEditorConfig}
|
||||
.schema=${SUB_SCHEMA}
|
||||
@go-back=${this._goBack}
|
||||
@config-changed=${this._handleSubEntityChanged}
|
||||
>
|
||||
</hui-sub-element-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
const data = {
|
||||
show_name: true,
|
||||
show_icon: true,
|
||||
@@ -98,12 +160,42 @@ export class HuiGlanceCardEditor
|
||||
></ha-form>
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
can-edit
|
||||
.entities=${this._configEntities}
|
||||
@entities-changed=${this._entitiesChanged}
|
||||
@edit-detail-element=${this._editDetailElement}
|
||||
></hui-entity-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
this._subElementEditorConfig = undefined;
|
||||
}
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
|
||||
this._subElementEditorConfig = ev.detail.subElementConfig;
|
||||
}
|
||||
|
||||
private _handleSubEntityChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
const index = this._subElementEditorConfig!.index!;
|
||||
|
||||
const newEntities = this._configEntities!.concat();
|
||||
const newConfig = ev.detail.config as EntityConfig;
|
||||
this._subElementEditorConfig = {
|
||||
...this._subElementEditorConfig!,
|
||||
elementConfig: newConfig,
|
||||
};
|
||||
newEntities[index] = newConfig;
|
||||
let config = this._config!;
|
||||
config = { ...config, entities: newEntities };
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const config = ev.detail.value;
|
||||
fireEvent(this, "config-changed", { config });
|
||||
|
||||
@@ -18,6 +18,9 @@ import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HistoryGraphCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../hui-sub-element-editor";
|
||||
import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import type { EntityConfig } from "../../entity-rows/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
@@ -40,6 +43,11 @@ const cardConfigStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const SUB_SCHEMA = [
|
||||
{ name: "entity", selector: { entity: {} }, required: true },
|
||||
{ name: "name", selector: { text: {} } },
|
||||
] as const;
|
||||
|
||||
@customElement("hui-history-graph-card-editor")
|
||||
export class HuiHistoryGraphCardEditor
|
||||
extends LitElement
|
||||
@@ -49,6 +57,8 @@ export class HuiHistoryGraphCardEditor
|
||||
|
||||
@state() private _config?: HistoryGraphCardConfig;
|
||||
|
||||
@state() private _subElementEditorConfig?: SubElementEditorConfig;
|
||||
|
||||
@state() private _configEntities?: EntityConfig[];
|
||||
|
||||
public setConfig(config: HistoryGraphCardConfig): void {
|
||||
@@ -110,6 +120,19 @@ export class HuiHistoryGraphCardEditor
|
||||
return nothing;
|
||||
}
|
||||
|
||||
if (this._subElementEditorConfig) {
|
||||
return html`
|
||||
<hui-sub-element-editor
|
||||
.hass=${this.hass}
|
||||
.config=${this._subElementEditorConfig}
|
||||
.schema=${SUB_SCHEMA}
|
||||
@go-back=${this._goBack}
|
||||
@config-changed=${this._handleSubEntityChanged}
|
||||
>
|
||||
</hui-sub-element-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
const schema = this._schema(
|
||||
this._config!.min_y_axis !== undefined ||
|
||||
this._config!.max_y_axis !== undefined
|
||||
@@ -126,11 +149,41 @@ export class HuiHistoryGraphCardEditor
|
||||
<hui-entity-editor
|
||||
.hass=${this.hass}
|
||||
.entities=${this._configEntities}
|
||||
can-edit
|
||||
@entities-changed=${this._entitiesChanged}
|
||||
@edit-detail-element=${this._editDetailElement}
|
||||
></hui-entity-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _goBack(): void {
|
||||
this._subElementEditorConfig = undefined;
|
||||
}
|
||||
|
||||
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
|
||||
this._subElementEditorConfig = ev.detail.subElementConfig;
|
||||
}
|
||||
|
||||
private _handleSubEntityChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
const index = this._subElementEditorConfig!.index!;
|
||||
|
||||
const newEntities = this._configEntities!.concat();
|
||||
const newConfig = ev.detail.config as EntityConfig;
|
||||
this._subElementEditorConfig = {
|
||||
...this._subElementEditorConfig!,
|
||||
elementConfig: newConfig,
|
||||
};
|
||||
newEntities[index] = newConfig;
|
||||
let config = this._config!;
|
||||
config = { ...config, entities: newEntities };
|
||||
this._config = config;
|
||||
this._configEntities = processEditorEntities(config.entities);
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ export abstract class HuiElementEditor<
|
||||
|
||||
@property({ attribute: false }) public context?: C;
|
||||
|
||||
@property({ attribute: false }) public schema?;
|
||||
|
||||
@state() private _config?: T;
|
||||
|
||||
@state() private _configElement?: LovelaceGenericElementEditor;
|
||||
@@ -312,6 +314,9 @@ export abstract class HuiElementEditor<
|
||||
if (this._configElement && changedProperties.has("context")) {
|
||||
this._configElement.context = this.context;
|
||||
}
|
||||
if (this._configElement && changedProperties.has("schema")) {
|
||||
this._configElement.schema = this.schema;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleUIConfigChanged(ev: UIConfigChangedEvent<T>) {
|
||||
@@ -399,6 +404,7 @@ export abstract class HuiElementEditor<
|
||||
configElement.lovelace = this.lovelace;
|
||||
}
|
||||
configElement.context = this.context;
|
||||
configElement.schema = this.schema;
|
||||
configElement.addEventListener("config-changed", (ev) =>
|
||||
this._handleUIConfigChanged(ev as UIConfigChangedEvent<T>)
|
||||
);
|
||||
|
||||
@@ -27,6 +27,8 @@ export class HuiSubElementEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public config!: SubElementEditorConfig;
|
||||
|
||||
@property({ attribute: false }) public schema?;
|
||||
|
||||
@state() private _guiModeAvailable = true;
|
||||
|
||||
@state() private _guiMode = true;
|
||||
@@ -89,6 +91,7 @@ export class HuiSubElementEditor extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.config.elementConfig}
|
||||
.context=${this.config.context}
|
||||
.schema=${this.schema}
|
||||
@config-changed=${this._handleConfigChanged}
|
||||
@GUImode-changed=${this._handleGUIModeChanged}
|
||||
></hui-row-element-editor>
|
||||
|
||||
@@ -18,6 +18,8 @@ export const entitiesConfigStruct = union([
|
||||
hold_action: optional(actionConfigStruct),
|
||||
double_tap_action: optional(actionConfigStruct),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
show_last_changed: optional(boolean()),
|
||||
show_state: optional(boolean()),
|
||||
}),
|
||||
string(),
|
||||
]);
|
||||
|
||||
@@ -169,6 +169,7 @@ export interface LovelaceGenericElementEditor<C = any> extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
lovelace?: LovelaceConfig;
|
||||
context?: C;
|
||||
schema?: any;
|
||||
setConfig(config: any): void;
|
||||
focusYamlEditor?: () => void;
|
||||
}
|
||||
|
||||
@@ -7720,6 +7720,7 @@
|
||||
"show_icon": "Show icon",
|
||||
"show_name": "Show name",
|
||||
"show_state": "Show state",
|
||||
"show_last_changed": "Show last changed",
|
||||
"tap_action": "Tap behavior",
|
||||
"interactions": "Interactions",
|
||||
"title": "Title",
|
||||
|
||||
Reference in New Issue
Block a user