mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Improve element editor and migrate heading-entity editor (#22034)
* Extract load config element * Improve error by using ha-alert * Create hui-hase-editor * Migrate heading entity form to its own editor * Rename editor * Rename * Rename * Move heading entity to its own component * Fix default action for heading entity
This commit is contained in:
parent
a759767d79
commit
c4a700a55c
113
src/panels/lovelace/cards/heading/hui-heading-entity.ts
Normal file
113
src/panels/lovelace/cards/heading/hui-heading-entity.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-icon";
|
||||||
|
import "../../../../components/ha-icon-next";
|
||||||
|
import "../../../../components/ha-state-icon";
|
||||||
|
import { ActionHandlerEvent } from "../../../../data/lovelace/action_handler";
|
||||||
|
import "../../../../state-display/state-display";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { actionHandler } from "../../common/directives/action-handler-directive";
|
||||||
|
import { handleAction } from "../../common/handle-action";
|
||||||
|
import { hasAction } from "../../common/has-action";
|
||||||
|
import type { HeadingEntityConfig } from "../types";
|
||||||
|
|
||||||
|
@customElement("hui-heading-entity")
|
||||||
|
export class HuiHeadingEntity extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public config!: HeadingEntityConfig | string;
|
||||||
|
|
||||||
|
private _handleAction(ev: ActionHandlerEvent) {
|
||||||
|
const config: HeadingEntityConfig = {
|
||||||
|
tap_action: {
|
||||||
|
action: "none",
|
||||||
|
},
|
||||||
|
...this._config(this.config),
|
||||||
|
};
|
||||||
|
handleAction(this, this.hass!, config, ev.detail.action!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _config = memoizeOne(
|
||||||
|
(configOrString: HeadingEntityConfig | string): HeadingEntityConfig => {
|
||||||
|
const config =
|
||||||
|
typeof configOrString === "string"
|
||||||
|
? { entity: configOrString }
|
||||||
|
: configOrString;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tap_action: {
|
||||||
|
action: "none",
|
||||||
|
},
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const config = this._config(this.config);
|
||||||
|
|
||||||
|
const stateObj = this.hass!.states[config.entity];
|
||||||
|
|
||||||
|
if (!stateObj) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionable = hasAction(config.tap_action);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="entity"
|
||||||
|
@action=${this._handleAction}
|
||||||
|
.actionHandler=${actionHandler()}
|
||||||
|
role=${ifDefined(actionable ? "button" : undefined)}
|
||||||
|
tabindex=${ifDefined(actionable ? "0" : undefined)}
|
||||||
|
>
|
||||||
|
<ha-state-icon
|
||||||
|
.hass=${this.hass}
|
||||||
|
.icon=${config.icon}
|
||||||
|
.stateObj=${stateObj}
|
||||||
|
></ha-state-icon>
|
||||||
|
<state-display
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${stateObj}
|
||||||
|
.content=${config.content || "state"}
|
||||||
|
></state-display>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.entity {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-family: Roboto;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
--mdc-icon-size: 14px;
|
||||||
|
}
|
||||||
|
.entity ha-state-icon {
|
||||||
|
--ha-icon-display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-heading-entity": HuiHeadingEntity;
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,8 @@ import type {
|
|||||||
LovelaceCardEditor,
|
LovelaceCardEditor,
|
||||||
LovelaceLayoutOptions,
|
LovelaceLayoutOptions,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import type { HeadingCardConfig, HeadingCardEntityConfig } from "./types";
|
import "./heading/hui-heading-entity";
|
||||||
|
import type { HeadingCardConfig } from "./types";
|
||||||
|
|
||||||
@customElement("hui-heading-card")
|
@customElement("hui-heading-card")
|
||||||
export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
||||||
@ -91,8 +92,11 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
|||||||
${this._config.entities?.length
|
${this._config.entities?.length
|
||||||
? html`
|
? html`
|
||||||
<div class="entities">
|
<div class="entities">
|
||||||
${this._config.entities.map((config) =>
|
${this._config.entities.map(
|
||||||
this._renderEntity(config)
|
(config) => html`
|
||||||
|
<hui-heading-entity .config=${config} .hass=${this.hass}>
|
||||||
|
</hui-heading-entity>
|
||||||
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -102,54 +106,6 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleEntityAction(ev: ActionHandlerEvent) {
|
|
||||||
const config = {
|
|
||||||
tap_action: {
|
|
||||||
action: "none",
|
|
||||||
},
|
|
||||||
...(ev.currentTarget as any).config,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleAction(this, this.hass!, config, ev.detail.action!);
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderEntity(entityConfig: string | HeadingCardEntityConfig) {
|
|
||||||
const config =
|
|
||||||
typeof entityConfig === "string"
|
|
||||||
? { entity: entityConfig }
|
|
||||||
: entityConfig;
|
|
||||||
|
|
||||||
const stateObj = this.hass!.states[config.entity];
|
|
||||||
|
|
||||||
if (!stateObj) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionable = hasAction(config.tap_action || { action: "none" });
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
.config=${config}
|
|
||||||
class="entity"
|
|
||||||
@action=${this._handleEntityAction}
|
|
||||||
.actionHandler=${actionHandler()}
|
|
||||||
role=${ifDefined(actionable ? "button" : undefined)}
|
|
||||||
tabindex=${ifDefined(actionable ? "0" : undefined)}
|
|
||||||
>
|
|
||||||
<ha-state-icon
|
|
||||||
.hass=${this.hass}
|
|
||||||
.icon=${config.icon}
|
|
||||||
.stateObj=${stateObj}
|
|
||||||
></ha-state-icon>
|
|
||||||
<state-display
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateObj=${stateObj}
|
|
||||||
.content=${config.content || "state"}
|
|
||||||
></state-display>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
@ -231,24 +187,6 @@ export class HuiHeadingCard extends LitElement implements LovelaceCard {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 4px 10px;
|
gap: 4px 10px;
|
||||||
}
|
}
|
||||||
.entities .entity {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
white-space: nowrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: 3px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
font-family: Roboto;
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: 0.1px;
|
|
||||||
--mdc-icon-size: 14px;
|
|
||||||
}
|
|
||||||
.entities .entity ha-state-icon {
|
|
||||||
--ha-icon-display: block;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,7 +503,7 @@ export interface TileCardConfig extends LovelaceCardConfig {
|
|||||||
features?: LovelaceCardFeatureConfig[];
|
features?: LovelaceCardFeatureConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HeadingCardEntityConfig {
|
export interface HeadingEntityConfig {
|
||||||
entity: string;
|
entity: string;
|
||||||
content?: string | string[];
|
content?: string | string[];
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@ -515,5 +515,5 @@ export interface HeadingCardConfig extends LovelaceCardConfig {
|
|||||||
heading?: string;
|
heading?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
tap_action?: ActionConfig;
|
tap_action?: ActionConfig;
|
||||||
entities?: (string | HeadingCardEntityConfig)[];
|
entities?: (string | HeadingEntityConfig)[];
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,13 @@ import { customElement, state } from "lit/decorators";
|
|||||||
import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||||
import { getBadgeElementClass } from "../../create-element/create-badge-element";
|
import { getBadgeElementClass } from "../../create-element/create-badge-element";
|
||||||
import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types";
|
import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
import "./hui-badge-visibility-editor";
|
import "./hui-badge-visibility-editor";
|
||||||
|
|
||||||
const tabs = ["config", "visibility"] as const;
|
const tabs = ["config", "visibility"] as const;
|
||||||
|
|
||||||
@customElement("hui-badge-element-editor")
|
@customElement("hui-badge-element-editor")
|
||||||
export class HuiBadgeElementEditor extends HuiElementEditor<LovelaceBadgeConfig> {
|
export class HuiBadgeElementEditor extends HuiTypedElementEditor<LovelaceBadgeConfig> {
|
||||||
@state() private _currTab: (typeof tabs)[number] = tabs[0];
|
@state() private _currTab: (typeof tabs)[number] = tabs[0];
|
||||||
|
|
||||||
protected async getConfigElement(): Promise<LovelaceCardEditor | undefined> {
|
protected async getConfigElement(): Promise<LovelaceCardEditor | undefined> {
|
||||||
@ -88,7 +88,7 @@ export class HuiBadgeElementEditor extends HuiElementEditor<LovelaceBadgeConfig>
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
HuiElementEditor.styles,
|
HuiTypedElementEditor.styles,
|
||||||
css`
|
css`
|
||||||
mwc-tab-bar {
|
mwc-tab-bar {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -5,7 +5,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||||
import { getCardElementClass } from "../../create-element/create-card-element";
|
import { getCardElementClass } from "../../create-element/create-card-element";
|
||||||
import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types";
|
import type { LovelaceCardEditor, LovelaceConfigForm } from "../../types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
import "./hui-card-layout-editor";
|
import "./hui-card-layout-editor";
|
||||||
import "./hui-card-visibility-editor";
|
import "./hui-card-visibility-editor";
|
||||||
import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
|
import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
|
||||||
@ -13,7 +13,7 @@ import { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"
|
|||||||
const tabs = ["config", "visibility", "layout"] as const;
|
const tabs = ["config", "visibility", "layout"] as const;
|
||||||
|
|
||||||
@customElement("hui-card-element-editor")
|
@customElement("hui-card-element-editor")
|
||||||
export class HuiCardElementEditor extends HuiElementEditor<LovelaceCardConfig> {
|
export class HuiCardElementEditor extends HuiTypedElementEditor<LovelaceCardConfig> {
|
||||||
@property({ type: Boolean, attribute: "show-visibility-tab" })
|
@property({ type: Boolean, attribute: "show-visibility-tab" })
|
||||||
public showVisibilityTab = false;
|
public showVisibilityTab = false;
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ export class HuiCardElementEditor extends HuiElementEditor<LovelaceCardConfig> {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
HuiElementEditor.styles,
|
HuiTypedElementEditor.styles,
|
||||||
css`
|
css`
|
||||||
mwc-tab-bar {
|
mwc-tab-bar {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
@ -8,11 +8,10 @@ import {
|
|||||||
array,
|
array,
|
||||||
assert,
|
assert,
|
||||||
assign,
|
assign,
|
||||||
literal,
|
enums,
|
||||||
object,
|
object,
|
||||||
optional,
|
optional,
|
||||||
string,
|
string,
|
||||||
union,
|
|
||||||
} from "superstruct";
|
} from "superstruct";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
@ -24,17 +23,14 @@ import type {
|
|||||||
} from "../../../../components/ha-form/types";
|
} from "../../../../components/ha-form/types";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import type {
|
import type { HeadingCardConfig, HeadingEntityConfig } from "../../cards/types";
|
||||||
HeadingCardConfig,
|
|
||||||
HeadingCardEntityConfig,
|
|
||||||
} from "../../cards/types";
|
|
||||||
import { UiAction } from "../../components/hui-action-editor";
|
import { UiAction } from "../../components/hui-action-editor";
|
||||||
import type { LovelaceCardEditor } from "../../types";
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
import "../hui-sub-form-editor";
|
import "../hui-sub-element-editor";
|
||||||
import { processEditorEntities } from "../process-editor-entities";
|
import { processEditorEntities } from "../process-editor-entities";
|
||||||
import { actionConfigStruct } from "../structs/action-struct";
|
import { actionConfigStruct } from "../structs/action-struct";
|
||||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
import { SubFormEditorData } from "../types";
|
import { SubElementEditorConfig } from "../types";
|
||||||
import { configElementStyle } from "./config-elements-style";
|
import { configElementStyle } from "./config-elements-style";
|
||||||
import "./hui-entities-editor";
|
import "./hui-entities-editor";
|
||||||
|
|
||||||
@ -43,7 +39,7 @@ const actions: UiAction[] = ["navigate", "url", "perform-action", "none"];
|
|||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
object({
|
object({
|
||||||
heading_style: optional(union([literal("title"), literal("subtitle")])),
|
heading_style: optional(enums(["title", "subtitle"])),
|
||||||
heading: optional(string()),
|
heading: optional(string()),
|
||||||
icon: optional(string()),
|
icon: optional(string()),
|
||||||
tap_action: optional(actionConfigStruct),
|
tap_action: optional(actionConfigStruct),
|
||||||
@ -51,13 +47,6 @@ const cardConfigStruct = assign(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const entityConfigStruct = object({
|
|
||||||
entity: string(),
|
|
||||||
content: optional(union([string(), array(string())])),
|
|
||||||
icon: optional(string()),
|
|
||||||
tap_action: optional(actionConfigStruct),
|
|
||||||
});
|
|
||||||
|
|
||||||
@customElement("hui-heading-card-editor")
|
@customElement("hui-heading-card-editor")
|
||||||
export class HuiHeadingCardEditor
|
export class HuiHeadingCardEditor
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -68,17 +57,13 @@ export class HuiHeadingCardEditor
|
|||||||
@state() private _config?: HeadingCardConfig;
|
@state() private _config?: HeadingCardConfig;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private _entityFormEditorData?: SubFormEditorData<HeadingCardEntityConfig>;
|
private _subElementEditorConfig?: SubElementEditorConfig;
|
||||||
|
|
||||||
public setConfig(config: HeadingCardConfig): void {
|
public setConfig(config: HeadingCardConfig): void {
|
||||||
assert(config, cardConfigStruct);
|
assert(config, cardConfigStruct);
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public _assertEntityConfig(config: HeadingCardEntityConfig): void {
|
|
||||||
assert(config, entityConfigStruct);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _schema = memoizeOne(
|
private _schema = memoizeOne(
|
||||||
(localize: LocalizeFunc) =>
|
(localize: LocalizeFunc) =>
|
||||||
[
|
[
|
||||||
@ -123,68 +108,27 @@ export class HuiHeadingCardEditor
|
|||||||
] as const satisfies readonly HaFormSchema[]
|
] as const satisfies readonly HaFormSchema[]
|
||||||
);
|
);
|
||||||
|
|
||||||
private _entitySchema = memoizeOne(
|
|
||||||
() =>
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: "entity",
|
|
||||||
selector: { entity: {} },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "icon",
|
|
||||||
selector: { icon: {} },
|
|
||||||
context: { icon_entity: "entity" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "content",
|
|
||||||
selector: { ui_state_content: {} },
|
|
||||||
context: { filter_entity: "entity" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "interactions",
|
|
||||||
type: "expandable",
|
|
||||||
flatten: true,
|
|
||||||
iconPath: mdiGestureTap,
|
|
||||||
schema: [
|
|
||||||
{
|
|
||||||
name: "tap_action",
|
|
||||||
selector: {
|
|
||||||
ui_action: {
|
|
||||||
default_action: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const satisfies readonly HaFormSchema[]
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache(
|
return cache(
|
||||||
this._entityFormEditorData ? this._renderEntityForm() : this._renderForm()
|
this._subElementEditorConfig
|
||||||
|
? this._renderEntityForm()
|
||||||
|
: this._renderForm()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderEntityForm() {
|
private _renderEntityForm() {
|
||||||
const schema = this._entitySchema();
|
|
||||||
return html`
|
return html`
|
||||||
<hui-sub-form-editor
|
<hui-sub-element-editor
|
||||||
.label=${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.entities.form-label"
|
|
||||||
)}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${this._entityFormEditorData!.data}
|
.config=${this._subElementEditorConfig}
|
||||||
@go-back=${this._goBack}
|
@go-back=${this._goBack}
|
||||||
@value-changed=${this._subFormChanged}
|
@config-changed=${this._subElementChanged}
|
||||||
.schema=${schema}
|
|
||||||
.assertConfig=${this._assertEntityConfig}
|
|
||||||
.computeLabel=${this._computeEntityLabelCallback}
|
|
||||||
>
|
>
|
||||||
</hui-sub-form-editor>
|
</hui-sub-element-editor>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +183,7 @@ export class HuiHeadingCardEditor
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
...this._config,
|
...this._config,
|
||||||
entities: ev.detail.entities as HeadingCardEntityConfig[],
|
entities: ev.detail.entities as HeadingEntityConfig[],
|
||||||
};
|
};
|
||||||
|
|
||||||
fireEvent(this, "config-changed", { config });
|
fireEvent(this, "config-changed", { config });
|
||||||
@ -256,30 +200,30 @@ export class HuiHeadingCardEditor
|
|||||||
fireEvent(this, "config-changed", { config });
|
fireEvent(this, "config-changed", { config });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _subFormChanged(ev: CustomEvent): void {
|
private _subElementChanged(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = ev.detail.value;
|
const value = ev.detail.config;
|
||||||
|
|
||||||
const newEntities = this._config!.entities
|
const newConfigEntities = this._config!.entities
|
||||||
? [...this._config!.entities]
|
? [...this._config!.entities]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
newEntities.splice(this._entityFormEditorData!.index!, 1);
|
newConfigEntities.splice(this._subElementEditorConfig!.index!, 1);
|
||||||
this._goBack();
|
this._goBack();
|
||||||
} else {
|
} else {
|
||||||
newEntities[this._entityFormEditorData!.index!] = value;
|
newConfigEntities[this._subElementEditorConfig!.index!] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._config = { ...this._config!, entities: newEntities };
|
this._config = { ...this._config!, entities: newConfigEntities };
|
||||||
|
|
||||||
this._entityFormEditorData = {
|
this._subElementEditorConfig = {
|
||||||
...this._entityFormEditorData!,
|
...this._subElementEditorConfig!,
|
||||||
data: value,
|
elementConfig: value,
|
||||||
};
|
};
|
||||||
|
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
@ -287,31 +231,17 @@ export class HuiHeadingCardEditor
|
|||||||
|
|
||||||
private _editEntity(ev: HASSDomEvent<{ index: number }>): void {
|
private _editEntity(ev: HASSDomEvent<{ index: number }>): void {
|
||||||
const entities = this._entities(this._config!.entities);
|
const entities = this._entities(this._config!.entities);
|
||||||
this._entityFormEditorData = {
|
this._subElementEditorConfig = {
|
||||||
data: entities[ev.detail.index],
|
elementConfig: entities[ev.detail.index],
|
||||||
index: ev.detail.index,
|
index: ev.detail.index,
|
||||||
|
type: "heading-entity",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _goBack(): void {
|
private _goBack(): void {
|
||||||
this._entityFormEditorData = undefined;
|
this._subElementEditorConfig = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeEntityLabelCallback = (
|
|
||||||
schema: SchemaUnion<ReturnType<typeof this._entitySchema>>
|
|
||||||
) => {
|
|
||||||
switch (schema.name) {
|
|
||||||
case "content":
|
|
||||||
return this.hass!.localize(
|
|
||||||
`ui.panel.lovelace.editor.card.heading.entity_config.${schema.name}`
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return this.hass!.localize(
|
|
||||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private _computeLabelCallback = (
|
private _computeLabelCallback = (
|
||||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
) => {
|
) => {
|
||||||
|
@ -2,10 +2,10 @@ import { customElement } from "lit/decorators";
|
|||||||
import { LovelaceDashboardStrategyConfig } from "../../../../data/lovelace/config/types";
|
import { LovelaceDashboardStrategyConfig } from "../../../../data/lovelace/config/types";
|
||||||
import { getLovelaceStrategy } from "../../strategies/get-strategy";
|
import { getLovelaceStrategy } from "../../strategies/get-strategy";
|
||||||
import { LovelaceStrategyEditor } from "../../strategies/types";
|
import { LovelaceStrategyEditor } from "../../strategies/types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
|
|
||||||
@customElement("hui-dashboard-strategy-element-editor")
|
@customElement("hui-dashboard-strategy-element-editor")
|
||||||
export class HuiDashboardStrategyElementEditor extends HuiElementEditor<LovelaceDashboardStrategyConfig> {
|
export class HuiDashboardStrategyElementEditor extends HuiTypedElementEditor<LovelaceDashboardStrategyConfig> {
|
||||||
protected async getConfigElement(): Promise<
|
protected async getConfigElement(): Promise<
|
||||||
LovelaceStrategyEditor | undefined
|
LovelaceStrategyEditor | undefined
|
||||||
> {
|
> {
|
||||||
|
@ -2,12 +2,12 @@ import { customElement } from "lit/decorators";
|
|||||||
import { getRowElementClass } from "../../create-element/create-row-element";
|
import { getRowElementClass } from "../../create-element/create-row-element";
|
||||||
import { LovelaceRowConfig } from "../../entity-rows/types";
|
import { LovelaceRowConfig } from "../../entity-rows/types";
|
||||||
import type { LovelaceRowEditor } from "../../types";
|
import type { LovelaceRowEditor } from "../../types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
|
|
||||||
const GENERIC_ROW_TYPE = "generic-row";
|
const GENERIC_ROW_TYPE = "generic-row";
|
||||||
|
|
||||||
@customElement("hui-row-element-editor")
|
@customElement("hui-row-element-editor")
|
||||||
export class HuiRowElementEditor extends HuiElementEditor<LovelaceRowConfig> {
|
export class HuiRowElementEditor extends HuiTypedElementEditor<LovelaceRowConfig> {
|
||||||
protected get configElementType(): string | undefined {
|
protected get configElementType(): string | undefined {
|
||||||
if (!this.value?.type && "entity" in this.value!) {
|
if (!this.value?.type && "entity" in this.value!) {
|
||||||
return GENERIC_ROW_TYPE;
|
return GENERIC_ROW_TYPE;
|
||||||
|
@ -8,10 +8,10 @@ import type {
|
|||||||
LovelaceConfigForm,
|
LovelaceConfigForm,
|
||||||
LovelaceCardFeatureEditor,
|
LovelaceCardFeatureEditor,
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
|
|
||||||
@customElement("hui-card-feature-element-editor")
|
@customElement("hui-card-feature-element-editor")
|
||||||
export class HuiCardFeatureElementEditor extends HuiElementEditor<
|
export class HuiCardFeatureElementEditor extends HuiTypedElementEditor<
|
||||||
LovelaceCardFeatureConfig,
|
LovelaceCardFeatureConfig,
|
||||||
LovelaceCardFeatureContext
|
LovelaceCardFeatureContext
|
||||||
> {
|
> {
|
||||||
|
@ -2,10 +2,10 @@ import { customElement } from "lit/decorators";
|
|||||||
import { getHeaderFooterElementClass } from "../../create-element/create-header-footer-element";
|
import { getHeaderFooterElementClass } from "../../create-element/create-header-footer-element";
|
||||||
import type { LovelaceHeaderFooterConfig } from "../../header-footer/types";
|
import type { LovelaceHeaderFooterConfig } from "../../header-footer/types";
|
||||||
import type { LovelaceHeaderFooterEditor } from "../../types";
|
import type { LovelaceHeaderFooterEditor } from "../../types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
|
|
||||||
@customElement("hui-headerfooter-element-editor")
|
@customElement("hui-headerfooter-element-editor")
|
||||||
export class HuiHeaderFooterElementEditor extends HuiElementEditor<LovelaceHeaderFooterConfig> {
|
export class HuiHeaderFooterElementEditor extends HuiTypedElementEditor<LovelaceHeaderFooterConfig> {
|
||||||
protected async getConfigElement(): Promise<
|
protected async getConfigElement(): Promise<
|
||||||
LovelaceHeaderFooterEditor | undefined
|
LovelaceHeaderFooterEditor | undefined
|
||||||
> {
|
> {
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
import { mdiGestureTap } from "@mdi/js";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { array, assert, object, optional, string, union } from "superstruct";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-form/ha-form";
|
||||||
|
import type {
|
||||||
|
HaFormSchema,
|
||||||
|
SchemaUnion,
|
||||||
|
} from "../../../../components/ha-form/types";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import type { HeadingCardConfig, HeadingEntityConfig } from "../../cards/types";
|
||||||
|
import type { LovelaceGenericElementEditor } from "../../types";
|
||||||
|
import { configElementStyle } from "../config-elements/config-elements-style";
|
||||||
|
import { actionConfigStruct } from "../structs/action-struct";
|
||||||
|
|
||||||
|
const entityConfigStruct = object({
|
||||||
|
entity: string(),
|
||||||
|
content: optional(union([string(), array(string())])),
|
||||||
|
icon: optional(string()),
|
||||||
|
tap_action: optional(actionConfigStruct),
|
||||||
|
});
|
||||||
|
|
||||||
|
@customElement("hui-heading-entity-editor")
|
||||||
|
export class HuiHeadingEntityEditor
|
||||||
|
extends LitElement
|
||||||
|
implements LovelaceGenericElementEditor
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _config?: HeadingEntityConfig;
|
||||||
|
|
||||||
|
public setConfig(config: HeadingEntityConfig): void {
|
||||||
|
assert(config, entityConfigStruct);
|
||||||
|
this._config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _schema = memoizeOne(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "entity",
|
||||||
|
selector: { entity: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
selector: { icon: {} },
|
||||||
|
context: { icon_entity: "entity" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
selector: { ui_state_content: {} },
|
||||||
|
context: { filter_entity: "entity" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "interactions",
|
||||||
|
type: "expandable",
|
||||||
|
flatten: true,
|
||||||
|
iconPath: mdiGestureTap,
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: "tap_action",
|
||||||
|
selector: {
|
||||||
|
ui_action: {
|
||||||
|
default_action: "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as const satisfies readonly HaFormSchema[]
|
||||||
|
);
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass || !this._config) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = this._schema();
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this._config}
|
||||||
|
.schema=${schema}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!this._config || !this.hass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = ev.detail.value as HeadingCardConfig;
|
||||||
|
|
||||||
|
fireEvent(this, "config-changed", { config });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||||
|
) => {
|
||||||
|
switch (schema.name) {
|
||||||
|
case "content":
|
||||||
|
return this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.heading.entity_config.${schema.name}`
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
configElementStyle,
|
||||||
|
css`
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
ha-form {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-heading-entity-editor": HuiHeadingEntityEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import { HeadingEntityConfig } from "../../cards/types";
|
||||||
|
import { HuiElementEditor } from "../hui-element-editor";
|
||||||
|
import type { HuiHeadingEntityEditor } from "./hui-heading-entity-editor";
|
||||||
|
|
||||||
|
@customElement("hui-heading-entity-element-editor")
|
||||||
|
export class HuiHeadingEntityElementEditor extends HuiElementEditor<HeadingEntityConfig> {
|
||||||
|
protected async getConfigElement(): Promise<
|
||||||
|
HuiHeadingEntityEditor | undefined
|
||||||
|
> {
|
||||||
|
await import("./hui-heading-entity-editor");
|
||||||
|
return document.createElement("hui-heading-entity-editor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"hui-heading-entity-element-editor": HuiHeadingEntityElementEditor;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { dump, load } from "js-yaml";
|
import { dump, load } from "js-yaml";
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -7,6 +6,7 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
css,
|
css,
|
||||||
html,
|
html,
|
||||||
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { property, query, state } from "lit/decorators";
|
import { property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
@ -16,34 +16,18 @@ import "../../../components/ha-alert";
|
|||||||
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 { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
|
||||||
import { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
|
||||||
import { LovelaceConfig } from "../../../data/lovelace/config/types";
|
import { LovelaceConfig } from "../../../data/lovelace/config/types";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { LovelaceCardFeatureConfig } from "../card-features/types";
|
|
||||||
import type { LovelaceRowConfig } from "../entity-rows/types";
|
|
||||||
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
|
||||||
import { LovelaceElementConfig } from "../elements/types";
|
|
||||||
import type {
|
import type {
|
||||||
LovelaceConfigForm,
|
LovelaceConfigForm,
|
||||||
LovelaceGenericElementEditor,
|
LovelaceGenericElementEditor,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import "./card-editor/hui-card-visibility-editor";
|
|
||||||
import type { HuiFormEditor } from "./config-elements/hui-form-editor";
|
import type { HuiFormEditor } from "./config-elements/hui-form-editor";
|
||||||
import "./config-elements/hui-generic-entity-row-editor";
|
|
||||||
import { GUISupportError } from "./gui-support-error";
|
import { GUISupportError } from "./gui-support-error";
|
||||||
import { EditSubElementEvent, GUIModeChangedEvent } from "./types";
|
import { EditSubElementEvent, GUIModeChangedEvent } from "./types";
|
||||||
import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
|
||||||
|
|
||||||
export interface ConfigChangedEvent {
|
export interface ConfigChangedEvent<T extends object = object> {
|
||||||
config:
|
config: T;
|
||||||
| LovelaceCardConfig
|
|
||||||
| LovelaceRowConfig
|
|
||||||
| LovelaceHeaderFooterConfig
|
|
||||||
| LovelaceCardFeatureConfig
|
|
||||||
| LovelaceStrategyConfig
|
|
||||||
| LovelaceElementConfig
|
|
||||||
| LovelaceBadgeConfig;
|
|
||||||
error?: string;
|
error?: string;
|
||||||
guiModeAvailable?: boolean;
|
guiModeAvailable?: boolean;
|
||||||
}
|
}
|
||||||
@ -56,17 +40,16 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UIConfigChangedEvent extends Event {
|
export interface UIConfigChangedEvent<T extends object = object> extends Event {
|
||||||
detail: {
|
detail: {
|
||||||
config:
|
config: T;
|
||||||
| LovelaceCardConfig
|
|
||||||
| LovelaceRowConfig
|
|
||||||
| LovelaceHeaderFooterConfig
|
|
||||||
| LovelaceCardFeatureConfig;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
export abstract class HuiElementEditor<
|
||||||
|
T extends object = object,
|
||||||
|
C = any,
|
||||||
|
> 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;
|
||||||
@ -79,8 +62,6 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
|||||||
|
|
||||||
@state() private _configElement?: LovelaceGenericElementEditor;
|
@state() private _configElement?: LovelaceGenericElementEditor;
|
||||||
|
|
||||||
@state() private _configElementType?: string;
|
|
||||||
|
|
||||||
@state() private _guiMode = true;
|
@state() private _guiMode = true;
|
||||||
|
|
||||||
// Error: Configuration broken - do not save
|
// Error: Configuration broken - do not save
|
||||||
@ -199,10 +180,6 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get configElementType(): string | undefined {
|
|
||||||
return this.value ? (this.value as any).type : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderConfigElement(): TemplateResult {
|
protected renderConfigElement(): TemplateResult {
|
||||||
return html`${this._configElement}`;
|
return html`${this._configElement}`;
|
||||||
}
|
}
|
||||||
@ -239,45 +216,51 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
|||||||
></ha-code-editor>
|
></ha-code-editor>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
${this._guiSupported === false && this.configElementType
|
${this._guiSupported === false && this._loading === false
|
||||||
? html`
|
? html`
|
||||||
<div class="info">
|
<ha-alert
|
||||||
${this.hass.localize("ui.errors.config.editor_not_available", {
|
alert-type="info"
|
||||||
type: this.configElementType,
|
.title=${this.hass.localize(
|
||||||
})}
|
"ui.errors.config.visual_editor_not_supported"
|
||||||
</div>
|
)}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.errors.config.visual_editor_not_supported_reason_type"
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
||||||
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
${this.hasError
|
${this.hasError
|
||||||
? html`
|
? html`
|
||||||
<div class="error">
|
<ha-alert
|
||||||
${this.hass.localize("ui.errors.config.error_detected")}:
|
alert-type="error"
|
||||||
<br />
|
.title=${this.hass.localize(
|
||||||
|
"ui.errors.config.configuration_error"
|
||||||
|
)}
|
||||||
|
>
|
||||||
<ul>
|
<ul>
|
||||||
${this._errors!.map((error) => html`<li>${error}</li>`)}
|
${this._errors!.map((error) => html`<li>${error}</li>`)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
${this.hasWarning
|
${this.hasWarning
|
||||||
? html`
|
? html`
|
||||||
<ha-alert
|
<ha-alert
|
||||||
alert-type="warning"
|
alert-type="warning"
|
||||||
.title="${this.hass.localize(
|
.title=${this.hass.localize(
|
||||||
"ui.errors.config.editor_not_supported"
|
"ui.errors.config.visual_editor_not_supported"
|
||||||
)}:"
|
)}
|
||||||
>
|
>
|
||||||
${this._warnings!.length > 0 && this._warnings![0] !== undefined
|
<ul>
|
||||||
? html` <ul>
|
${this._warnings!.map((warning) => html`<li>${warning}</li>`)}
|
||||||
${this._warnings!.map(
|
</ul>
|
||||||
(warning) => html`<li>${warning}</li>`
|
|
||||||
)}
|
|
||||||
</ul>`
|
|
||||||
: ""}
|
|
||||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
||||||
</ha-alert>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -300,7 +283,7 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
|
private _handleUIConfigChanged(ev: UIConfigChangedEvent<T>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const config = ev.detail.config;
|
const config = ev.detail.config;
|
||||||
Object.keys(config).forEach((key) => {
|
Object.keys(config).forEach((key) => {
|
||||||
@ -319,66 +302,62 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async unloadConfigElement(): Promise<void> {
|
||||||
|
this._configElement = undefined;
|
||||||
|
this._guiSupported = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async loadConfigElement(): Promise<void> {
|
||||||
|
if (this._configElement) return;
|
||||||
|
|
||||||
|
let configElement = await this.getConfigElement();
|
||||||
|
|
||||||
|
if (!configElement) {
|
||||||
|
const form = await this.getConfigForm();
|
||||||
|
if (form) {
|
||||||
|
await import("./config-elements/hui-form-editor");
|
||||||
|
configElement = document.createElement("hui-form-editor");
|
||||||
|
const { schema, assertConfig, computeLabel, computeHelper } = form;
|
||||||
|
(configElement as HuiFormEditor).schema = schema;
|
||||||
|
if (computeLabel) {
|
||||||
|
(configElement as HuiFormEditor).computeLabel = computeLabel;
|
||||||
|
}
|
||||||
|
if (computeHelper) {
|
||||||
|
(configElement as HuiFormEditor).computeHelper = computeHelper;
|
||||||
|
}
|
||||||
|
if (assertConfig) {
|
||||||
|
(configElement as HuiFormEditor).assertConfig = assertConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configElement) {
|
||||||
|
configElement.hass = this.hass;
|
||||||
|
if ("lovelace" in configElement) {
|
||||||
|
configElement.lovelace = this.lovelace;
|
||||||
|
}
|
||||||
|
configElement.context = this.context;
|
||||||
|
configElement.addEventListener("config-changed", (ev) =>
|
||||||
|
this._handleUIConfigChanged(ev as UIConfigChangedEvent<T>)
|
||||||
|
);
|
||||||
|
this._guiSupported = true;
|
||||||
|
} else {
|
||||||
|
this._guiSupported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._configElement = configElement;
|
||||||
|
}
|
||||||
|
|
||||||
private async _updateConfigElement(): Promise<void> {
|
private async _updateConfigElement(): Promise<void> {
|
||||||
if (!this.value) {
|
if (!this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let configElement: LovelaceGenericElementEditor | undefined;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._errors = undefined;
|
this._errors = undefined;
|
||||||
this._warnings = undefined;
|
this._warnings = undefined;
|
||||||
|
|
||||||
if (this._configElementType !== this.configElementType) {
|
await this.loadConfigElement();
|
||||||
// If the type has changed, we need to load a new GUI editor
|
|
||||||
this._guiSupported = undefined;
|
|
||||||
this._configElement = undefined;
|
|
||||||
|
|
||||||
if (!this.configElementType) {
|
|
||||||
throw new Error(
|
|
||||||
this.hass.localize("ui.errors.config.no_type_provided")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._configElementType = this.configElementType;
|
|
||||||
|
|
||||||
this._loading = true;
|
|
||||||
configElement = await this.getConfigElement();
|
|
||||||
|
|
||||||
if (!configElement) {
|
|
||||||
const form = await this.getConfigForm();
|
|
||||||
if (form) {
|
|
||||||
await import("./config-elements/hui-form-editor");
|
|
||||||
configElement = document.createElement("hui-form-editor");
|
|
||||||
const { schema, assertConfig, computeLabel, computeHelper } = form;
|
|
||||||
(configElement as HuiFormEditor).schema = schema;
|
|
||||||
if (computeLabel) {
|
|
||||||
(configElement as HuiFormEditor).computeLabel = computeLabel;
|
|
||||||
}
|
|
||||||
if (computeHelper) {
|
|
||||||
(configElement as HuiFormEditor).computeHelper = computeHelper;
|
|
||||||
}
|
|
||||||
if (assertConfig) {
|
|
||||||
(configElement as HuiFormEditor).assertConfig = assertConfig;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configElement) {
|
|
||||||
configElement.hass = this.hass;
|
|
||||||
if ("lovelace" in configElement) {
|
|
||||||
configElement.lovelace = this.lovelace;
|
|
||||||
}
|
|
||||||
configElement.context = this.context;
|
|
||||||
configElement.addEventListener("config-changed", (ev) =>
|
|
||||||
this._handleUIConfigChanged(ev as UIConfigChangedEvent)
|
|
||||||
);
|
|
||||||
|
|
||||||
this._configElement = configElement;
|
|
||||||
this._guiSupported = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._configElement) {
|
if (this._configElement) {
|
||||||
// Setup GUI editor and check that it can handle the current config
|
// Setup GUI editor and check that it can handle the current config
|
||||||
@ -428,26 +407,6 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
|
|||||||
ha-code-editor {
|
ha-code-editor {
|
||||||
--code-mirror-max-height: calc(100vh - 245px);
|
--code-mirror-max-height: calc(100vh - 245px);
|
||||||
}
|
}
|
||||||
.error,
|
|
||||||
.warning,
|
|
||||||
.info {
|
|
||||||
word-break: break-word;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
.warning {
|
|
||||||
color: var(--warning-color);
|
|
||||||
}
|
|
||||||
.warning ul,
|
|
||||||
.error ul {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
.warning li,
|
|
||||||
.error li {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { mdiCodeBraces, mdiListBoxOutline } from "@mdi/js";
|
import { mdiCodeBraces, mdiListBoxOutline } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -13,14 +12,13 @@ import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-icon-button-prev";
|
import "../../../components/ha-icon-button-prev";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import type { LovelaceRowConfig } from "../entity-rows/types";
|
|
||||||
import type { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
|
||||||
import "./entity-row-editor/hui-row-element-editor";
|
import "./entity-row-editor/hui-row-element-editor";
|
||||||
import "./header-footer-editor/hui-header-footer-element-editor";
|
|
||||||
import type { HuiElementEditor } from "./hui-element-editor";
|
|
||||||
import "./feature-editor/hui-card-feature-element-editor";
|
import "./feature-editor/hui-card-feature-element-editor";
|
||||||
import type { GUIModeChangedEvent, SubElementEditorConfig } from "./types";
|
import "./header-footer-editor/hui-header-footer-element-editor";
|
||||||
|
import "./heading-entity/hui-heading-entity-element-editor";
|
||||||
|
import type { HuiElementEditor } from "./hui-element-editor";
|
||||||
import "./picture-element-editor/hui-picture-element-element-editor";
|
import "./picture-element-editor/hui-picture-element-element-editor";
|
||||||
|
import type { GUIModeChangedEvent, SubElementEditorConfig } from "./types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -40,11 +38,14 @@ export class HuiSubElementEditor extends LitElement {
|
|||||||
|
|
||||||
@state() private _guiMode = true;
|
@state() private _guiMode = true;
|
||||||
|
|
||||||
@query(".editor") private _editorElement?: HuiElementEditor<
|
@query(".editor") private _editorElement?: HuiElementEditor;
|
||||||
LovelaceRowConfig | LovelaceHeaderFooterConfig
|
|
||||||
>;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
const elementType =
|
||||||
|
this.config.elementConfig && "type" in this.config.elementConfig
|
||||||
|
? this.config.elementConfig.type
|
||||||
|
: "";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="back-title">
|
<div class="back-title">
|
||||||
@ -52,21 +53,21 @@ export class HuiSubElementEditor extends LitElement {
|
|||||||
.label=${this.hass!.localize("ui.common.back")}
|
.label=${this.hass!.localize("ui.common.back")}
|
||||||
@click=${this._goBack}
|
@click=${this._goBack}
|
||||||
></ha-icon-button-prev>
|
></ha-icon-button-prev>
|
||||||
<span slot="title"
|
<span slot="title">
|
||||||
>${this.config?.type === "element"
|
${this.config?.type === "element"
|
||||||
? this.hass.localize(
|
? this.hass.localize(
|
||||||
`ui.panel.lovelace.editor.sub-element-editor.types.element_type`,
|
`ui.panel.lovelace.editor.sub-element-editor.types.element_type`,
|
||||||
{
|
{
|
||||||
type:
|
type:
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
`ui.panel.lovelace.editor.card.picture-elements.element_types.${this.config?.elementConfig?.type}`
|
`ui.panel.lovelace.editor.card.picture-elements.element_types.${elementType}`
|
||||||
) || this.config?.elementConfig?.type,
|
) || elementType,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: this.hass.localize(
|
: this.hass.localize(
|
||||||
`ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}`
|
`ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}`
|
||||||
)}</span
|
)}
|
||||||
>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class="gui-mode-button"
|
class="gui-mode-button"
|
||||||
@ -80,54 +81,74 @@ export class HuiSubElementEditor extends LitElement {
|
|||||||
.path=${this._guiMode ? mdiCodeBraces : mdiListBoxOutline}
|
.path=${this._guiMode ? mdiCodeBraces : mdiListBoxOutline}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</div>
|
</div>
|
||||||
${this.config.type === "row"
|
${this._renderEditor()}
|
||||||
? html`
|
|
||||||
<hui-row-element-editor
|
|
||||||
class="editor"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this.config.elementConfig}
|
|
||||||
.context=${this.context}
|
|
||||||
@config-changed=${this._handleConfigChanged}
|
|
||||||
@GUImode-changed=${this._handleGUIModeChanged}
|
|
||||||
></hui-row-element-editor>
|
|
||||||
`
|
|
||||||
: this.config.type === "header" || this.config.type === "footer"
|
|
||||||
? html`
|
|
||||||
<hui-headerfooter-element-editor
|
|
||||||
class="editor"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this.config.elementConfig}
|
|
||||||
.context=${this.context}
|
|
||||||
@config-changed=${this._handleConfigChanged}
|
|
||||||
@GUImode-changed=${this._handleGUIModeChanged}
|
|
||||||
></hui-headerfooter-element-editor>
|
|
||||||
`
|
|
||||||
: this.config.type === "feature"
|
|
||||||
? html`
|
|
||||||
<hui-card-feature-element-editor
|
|
||||||
class="editor"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this.config.elementConfig}
|
|
||||||
.context=${this.context}
|
|
||||||
@config-changed=${this._handleConfigChanged}
|
|
||||||
@GUImode-changed=${this._handleGUIModeChanged}
|
|
||||||
></hui-card-feature-element-editor>
|
|
||||||
`
|
|
||||||
: this.config.type === "element"
|
|
||||||
? html`
|
|
||||||
<hui-picture-element-element-editor
|
|
||||||
class="editor"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this.config.elementConfig}
|
|
||||||
.context=${this.context}
|
|
||||||
@config-changed=${this._handleConfigChanged}
|
|
||||||
@GUImode-changed=${this._handleGUIModeChanged}
|
|
||||||
></hui-picture-element-element-editor>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderEditor() {
|
||||||
|
const type = this.config.type;
|
||||||
|
switch (type) {
|
||||||
|
case "row":
|
||||||
|
return html`
|
||||||
|
<hui-row-element-editor
|
||||||
|
class="editor"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.config.elementConfig}
|
||||||
|
.context=${this.context}
|
||||||
|
@config-changed=${this._handleConfigChanged}
|
||||||
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
|
></hui-row-element-editor>
|
||||||
|
`;
|
||||||
|
case "header":
|
||||||
|
case "footer":
|
||||||
|
return html`
|
||||||
|
<hui-headerfooter-element-editor
|
||||||
|
class="editor"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.config.elementConfig}
|
||||||
|
.context=${this.context}
|
||||||
|
@config-changed=${this._handleConfigChanged}
|
||||||
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
|
></hui-headerfooter-element-editor>
|
||||||
|
`;
|
||||||
|
case "element":
|
||||||
|
return html`
|
||||||
|
<hui-picture-element-element-editor
|
||||||
|
class="editor"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.config.elementConfig}
|
||||||
|
.context=${this.context}
|
||||||
|
@config-changed=${this._handleConfigChanged}
|
||||||
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
|
></hui-picture-element-element-editor>
|
||||||
|
`;
|
||||||
|
case "feature":
|
||||||
|
return html`
|
||||||
|
<hui-card-feature-element-editor
|
||||||
|
class="editor"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.config.elementConfig}
|
||||||
|
.context=${this.context}
|
||||||
|
@config-changed=${this._handleConfigChanged}
|
||||||
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
|
></hui-card-feature-element-editor>
|
||||||
|
`;
|
||||||
|
case "heading-entity":
|
||||||
|
return html`
|
||||||
|
<hui-heading-entity-element-editor
|
||||||
|
class="editor"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.config.elementConfig}
|
||||||
|
.context=${this.context}
|
||||||
|
@config-changed=${this._handleConfigChanged}
|
||||||
|
@GUImode-changed=${this._handleGUIModeChanged}
|
||||||
|
></hui-heading-entity-element-editor>
|
||||||
|
`;
|
||||||
|
default:
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _goBack(): void {
|
private _goBack(): void {
|
||||||
fireEvent(this, "go-back");
|
fireEvent(this, "go-back");
|
||||||
}
|
}
|
||||||
|
@ -1,190 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { mdiCodeBraces, mdiListBoxOutline } from "@mdi/js";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
nothing,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import "../../../components/ha-form/ha-form";
|
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "../../../components/ha-icon-button-prev";
|
|
||||||
import "../../../components/ha-yaml-editor";
|
|
||||||
import "../../../components/ha-alert";
|
|
||||||
import type { HomeAssistant } from "../../../types";
|
|
||||||
import type { LovelaceConfigForm } from "../types";
|
|
||||||
import type { EditSubFormEvent } from "./types";
|
|
||||||
import { handleStructError } from "../../../common/structs/handle-errors";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"go-back": undefined;
|
|
||||||
"edit-sub-form": EditSubFormEvent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("hui-sub-form-editor")
|
|
||||||
export class HuiSubFormEditor<T = any> extends LitElement {
|
|
||||||
public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public label?: string;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public data!: T;
|
|
||||||
|
|
||||||
public schema!: LovelaceConfigForm["schema"];
|
|
||||||
|
|
||||||
public assertConfig?: (config: T) => void;
|
|
||||||
|
|
||||||
public computeLabel?: LovelaceConfigForm["computeLabel"];
|
|
||||||
|
|
||||||
public computeHelper?: LovelaceConfigForm["computeHelper"];
|
|
||||||
|
|
||||||
@state() public _yamlMode = false;
|
|
||||||
|
|
||||||
@state() private _errors?: string[];
|
|
||||||
|
|
||||||
@state() private _warnings?: string[];
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
const uiAvailable = !this.hasWarning && !this.hasError;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="header">
|
|
||||||
<div class="back-title">
|
|
||||||
<ha-icon-button-prev
|
|
||||||
.label=${this.hass!.localize("ui.common.back")}
|
|
||||||
@click=${this._goBack}
|
|
||||||
></ha-icon-button-prev>
|
|
||||||
<span slot="title">${this.label}</span>
|
|
||||||
</div>
|
|
||||||
<ha-icon-button
|
|
||||||
class="gui-mode-button"
|
|
||||||
@click=${this._toggleMode}
|
|
||||||
.disabled=${!uiAvailable}
|
|
||||||
.label=${this.hass!.localize(
|
|
||||||
this._yamlMode
|
|
||||||
? "ui.panel.lovelace.editor.edit_card.show_visual_editor"
|
|
||||||
: "ui.panel.lovelace.editor.edit_card.show_code_editor"
|
|
||||||
)}
|
|
||||||
.path=${this._yamlMode ? mdiListBoxOutline : mdiCodeBraces}
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
${this._yamlMode
|
|
||||||
? html`
|
|
||||||
<ha-yaml-editor
|
|
||||||
.hass=${this.hass}
|
|
||||||
.defaultValue=${this.data}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-yaml-editor>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<ha-form
|
|
||||||
.hass=${this.hass}
|
|
||||||
.schema=${this.schema}
|
|
||||||
.computeLabel=${this.computeLabel}
|
|
||||||
.computeHelper=${this.computeHelper}
|
|
||||||
.data=${this.data}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
>
|
|
||||||
</ha-form>
|
|
||||||
`}
|
|
||||||
${this.hasError
|
|
||||||
? html`
|
|
||||||
<ha-alert alert-type="error">
|
|
||||||
${this.hass.localize("ui.errors.config.error_detected")}:
|
|
||||||
<br />
|
|
||||||
<ul>
|
|
||||||
${this._errors!.map((error) => html`<li>${error}</li>`)}
|
|
||||||
</ul>
|
|
||||||
</ha-alert>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${this.hasWarning
|
|
||||||
? html`
|
|
||||||
<ha-alert
|
|
||||||
alert-type="warning"
|
|
||||||
.title="${this.hass.localize(
|
|
||||||
"ui.errors.config.editor_not_supported"
|
|
||||||
)}:"
|
|
||||||
>
|
|
||||||
${this._warnings!.length > 0 && this._warnings![0] !== undefined
|
|
||||||
? html`
|
|
||||||
<ul>
|
|
||||||
${this._warnings!.map(
|
|
||||||
(warning) => html`<li>${warning}</li>`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${this.hass.localize("ui.errors.config.edit_in_yaml_supported")}
|
|
||||||
</ha-alert>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
|
||||||
if (changedProperties.has("data")) {
|
|
||||||
if (this.assertConfig) {
|
|
||||||
try {
|
|
||||||
this.assertConfig(this.data);
|
|
||||||
this._warnings = undefined;
|
|
||||||
this._errors = undefined;
|
|
||||||
} catch (err: any) {
|
|
||||||
const msgs = handleStructError(this.hass, err);
|
|
||||||
this._warnings = msgs.warnings ?? [err.message];
|
|
||||||
this._errors = msgs.errors || undefined;
|
|
||||||
this._yamlMode = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get hasWarning(): boolean {
|
|
||||||
return this._warnings !== undefined && this._warnings.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get hasError(): boolean {
|
|
||||||
return this._errors !== undefined && this._errors.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _goBack(): void {
|
|
||||||
fireEvent(this, "go-back");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _toggleMode(): void {
|
|
||||||
this._yamlMode = !this._yamlMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const value = (ev.detail.value ?? (ev.target as any).value ?? {}) as T;
|
|
||||||
fireEvent(this, "value-changed", { value });
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
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-sub-form-editor": HuiSubFormEditor;
|
|
||||||
}
|
|
||||||
}
|
|
30
src/panels/lovelace/editor/hui-typed-element-editor.ts
Normal file
30
src/panels/lovelace/editor/hui-typed-element-editor.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { state } from "lit/decorators";
|
||||||
|
import { HuiElementEditor } from "./hui-element-editor";
|
||||||
|
|
||||||
|
export abstract class HuiTypedElementEditor<
|
||||||
|
T extends object,
|
||||||
|
C = any,
|
||||||
|
> extends HuiElementEditor<T, C> {
|
||||||
|
@state() private _configElementType?: string;
|
||||||
|
|
||||||
|
protected get configElementType(): string | undefined {
|
||||||
|
return this.value ? (this.value as any).type : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async loadConfigElement(): Promise<void> {
|
||||||
|
// If the type has changed, we need to unload the current editor and load the new one
|
||||||
|
if (this._configElementType !== this.configElementType) {
|
||||||
|
this.unloadConfigElement();
|
||||||
|
|
||||||
|
if (!this.configElementType) {
|
||||||
|
throw new Error(
|
||||||
|
this.hass.localize("ui.errors.config.no_type_provided")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._configElementType = this.configElementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.loadConfigElement();
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { LovelaceElementConfig } from "../../elements/types";
|
import { LovelaceElementConfig } from "../../elements/types";
|
||||||
import type { LovelacePictureElementEditor } from "../../types";
|
import type { LovelacePictureElementEditor } from "../../types";
|
||||||
import { HuiElementEditor } from "../hui-element-editor";
|
import { HuiTypedElementEditor } from "../hui-typed-element-editor";
|
||||||
import { getPictureElementClass } from "../../create-element/create-picture-element";
|
import { getPictureElementClass } from "../../create-element/create-picture-element";
|
||||||
|
|
||||||
@customElement("hui-picture-element-element-editor")
|
@customElement("hui-picture-element-element-editor")
|
||||||
export class HuiPictureElementElementEditor extends HuiElementEditor<LovelaceElementConfig> {
|
export class HuiPictureElementElementEditor extends HuiTypedElementEditor<LovelaceElementConfig> {
|
||||||
protected get configElementType(): string | undefined {
|
protected get configElementType(): string | undefined {
|
||||||
return this.value?.type === "action-button"
|
return this.value?.type === "action-button"
|
||||||
? "service-button"
|
? "service-button"
|
||||||
|
@ -9,6 +9,7 @@ import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
|||||||
import { LovelaceCardFeatureConfig } from "../card-features/types";
|
import { LovelaceCardFeatureConfig } from "../card-features/types";
|
||||||
import { LovelaceElementConfig } from "../elements/types";
|
import { LovelaceElementConfig } from "../elements/types";
|
||||||
import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
import { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
|
||||||
|
import { HeadingEntityConfig } from "../cards/types";
|
||||||
|
|
||||||
export interface YamlChangedEvent extends Event {
|
export interface YamlChangedEvent extends Event {
|
||||||
detail: {
|
detail: {
|
||||||
@ -95,19 +96,11 @@ export interface SubElementEditorConfig {
|
|||||||
| LovelaceRowConfig
|
| LovelaceRowConfig
|
||||||
| LovelaceHeaderFooterConfig
|
| LovelaceHeaderFooterConfig
|
||||||
| LovelaceCardFeatureConfig
|
| LovelaceCardFeatureConfig
|
||||||
| LovelaceElementConfig;
|
| LovelaceElementConfig
|
||||||
type: "header" | "footer" | "row" | "feature" | "element";
|
| HeadingEntityConfig;
|
||||||
|
type: "header" | "footer" | "row" | "feature" | "element" | "heading-entity";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditSubElementEvent {
|
export interface EditSubElementEvent {
|
||||||
subElementConfig: SubElementEditorConfig;
|
subElementConfig: SubElementEditorConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubFormEditorData<T = any> {
|
|
||||||
index?: number;
|
|
||||||
data?: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditSubFormEvent<T = any> {
|
|
||||||
subFormData: SubFormEditorData<T>;
|
|
||||||
}
|
|
||||||
|
@ -1796,11 +1796,13 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"config": {
|
"config": {
|
||||||
|
"visual_editor_not_supported": "Visual editor not supported",
|
||||||
|
"visual_editor_not_supported_reason_type": "The visual editor is not available for this type of element.",
|
||||||
|
"edit_in_yaml_supported": "You can still edit your config using YAML.",
|
||||||
|
"configuration_error": "Configuration error",
|
||||||
|
"configuration_error_reason": "The configuration is not valid. Check the logs for more information.",
|
||||||
"no_type_provided": "No type provided.",
|
"no_type_provided": "No type provided.",
|
||||||
"error_detected": "Configuration errors detected",
|
|
||||||
"editor_not_available": "No visual editor available for type ''{type}''.",
|
|
||||||
"editor_not_supported": "Visual editor is not supported for this configuration",
|
"editor_not_supported": "Visual editor is not supported for this configuration",
|
||||||
"edit_in_yaml_supported": "You can still edit your config in YAML.",
|
|
||||||
"key_missing": "Required key ''{key}'' is missing.",
|
"key_missing": "Required key ''{key}'' is missing.",
|
||||||
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
|
"key_not_expected": "Key ''{key}'' is not expected or not supported by the visual editor.",
|
||||||
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
|
"key_wrong_type": "The provided value for ''{key}'' is not supported by the visual editor. We support ({type_correct}) but received ({type_wrong}).",
|
||||||
@ -6379,6 +6381,7 @@
|
|||||||
"row": "Entity row editor",
|
"row": "Entity row editor",
|
||||||
"feature": "Feature editor",
|
"feature": "Feature editor",
|
||||||
"element": "Element editor",
|
"element": "Element editor",
|
||||||
|
"heading-entity": "Entity editor",
|
||||||
"element_type": "{type} element editor"
|
"element_type": "{type} element editor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user