Bump superstruct, add struct to automation action (#6436)

Co-authored-by: Zack Arnett <arnett.zackary@gmail.com>
This commit is contained in:
Bram Kragten 2020-07-21 12:42:07 +02:00 committed by GitHub
parent 235fd5603f
commit 16473c9177
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 506 additions and 346 deletions

View File

@ -108,7 +108,7 @@
"regenerator-runtime": "^0.13.2", "regenerator-runtime": "^0.13.2",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"superstruct": "^0.6.1", "superstruct": "^0.10.12",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1", "vue2-daterange-picker": "^0.5.1",

View File

@ -46,8 +46,8 @@ export interface MqttTrigger {
export interface GeoLocationTrigger { export interface GeoLocationTrigger {
platform: "geo_location"; platform: "geo_location";
source: "string"; source: string;
zone: "string"; zone: string;
event: "enter" | "leave"; event: "enter" | "leave";
} }

View File

@ -29,6 +29,8 @@ import "./types/ha-automation-action-event";
import "./types/ha-automation-action-scene"; import "./types/ha-automation-action-scene";
import "./types/ha-automation-action-service"; import "./types/ha-automation-action-service";
import "./types/ha-automation-action-wait_template"; import "./types/ha-automation-action-wait_template";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
const OPTIONS = [ const OPTIONS = [
"condition", "condition",
@ -87,12 +89,16 @@ export default class HaAutomationActionRow extends LitElement {
@property() public totalActions!: number; @property() public totalActions!: number;
@internalProperty() private _warnings?: string[];
@internalProperty() private _uiModeAvailable = true;
@internalProperty() private _yamlMode = false; @internalProperty() private _yamlMode = false;
protected render() { protected render() {
const type = getType(this.action); const type = getType(this.action);
const selected = type ? OPTIONS.indexOf(type) : -1; const selected = type ? OPTIONS.indexOf(type) : -1;
const yamlMode = this._yamlMode || selected === -1; const yamlMode = this._yamlMode;
return html` return html`
<ha-card> <ha-card>
@ -137,7 +143,7 @@ export default class HaAutomationActionRow extends LitElement {
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item <mwc-list-item
@request-selected=${this._switchYamlMode} @request-selected=${this._switchYamlMode}
.disabled=${selected === -1} .disabled=${!this._uiModeAvailable}
> >
${yamlMode ${yamlMode
? this.hass.localize( ? this.hass.localize(
@ -159,6 +165,16 @@ export default class HaAutomationActionRow extends LitElement {
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>
</div> </div>
${this._warnings
? html`<div class="warning">
UI editor is not supported for this config:
<br />
<ul>
${this._warnings.map((warning) => html`<li>${warning}</li>`)}
</ul>
You can still edit your config in yaml.
</div>`
: ""}
${yamlMode ${yamlMode
? html` ? html`
<div style="margin-right: 24px;"> <div style="margin-right: 24px;">
@ -200,7 +216,7 @@ export default class HaAutomationActionRow extends LitElement {
)} )}
</paper-listbox> </paper-listbox>
</paper-dropdown-menu-light> </paper-dropdown-menu-light>
<div> <div @ui-mode-not-available=${this._handleUiModeNotAvailable}>
${dynamicElement(`ha-automation-action-${type}`, { ${dynamicElement(`ha-automation-action-${type}`, {
hass: this.hass, hass: this.hass,
action: this.action, action: this.action,
@ -212,6 +228,13 @@ export default class HaAutomationActionRow extends LitElement {
`; `;
} }
private _handleUiModeNotAvailable(ev: CustomEvent) {
this._warnings = handleStructError(ev.detail);
if (!this._yamlMode) {
this._yamlMode = true;
}
}
private _moveUp() { private _moveUp() {
fireEvent(this, "move-action", { direction: "up" }); fireEvent(this, "move-action", { direction: "up" });
} }
@ -241,6 +264,11 @@ export default class HaAutomationActionRow extends LitElement {
return; return;
} }
this._uiModeAvailable = OPTIONS.includes(type);
if (!this._uiModeAvailable && !this._yamlMode) {
this._yamlMode = false;
}
if (type !== getType(this.action)) { if (type !== getType(this.action)) {
const elClass = customElements.get(`ha-automation-action-${type}`); const elClass = customElements.get(`ha-automation-action-${type}`);
@ -260,7 +288,10 @@ export default class HaAutomationActionRow extends LitElement {
fireEvent(this, "value-changed", { value: ev.detail.value }); fireEvent(this, "value-changed", { value: ev.detail.value });
} }
private _switchYamlMode() { private _switchYamlMode(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "interaction") {
return;
}
this._yamlMode = !this._yamlMode; this._yamlMode = !this._yamlMode;
} }
@ -283,6 +314,13 @@ export default class HaAutomationActionRow extends LitElement {
mwc-list-item[disabled] { mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color); --mdc-theme-text-primary-on-background: var(--disabled-text-color);
} }
.warning {
color: var(--warning-color);
margin-bottom: 8px;
}
.warning ul {
margin: 4px 0;
}
`; `;
} }
} }

View File

@ -19,12 +19,20 @@ import { ServiceAction } from "../../../../../data/script";
import type { PolymerChangedEvent } from "../../../../../polymer-types"; import type { PolymerChangedEvent } from "../../../../../polymer-types";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
import { assert, optional, object, string } from "superstruct";
import { EntityId } from "../../../../lovelace/common/structs/is-entity-id";
const actionStruct = object({
service: optional(string()),
entity_id: optional(EntityId),
data: optional(object()),
});
@customElement("ha-automation-action-service") @customElement("ha-automation-action-service")
export class HaServiceAction extends LitElement implements ActionElement { export class HaServiceAction extends LitElement implements ActionElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public action!: ServiceAction; @property({ attribute: false }) public action!: ServiceAction;
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
@ -60,6 +68,11 @@ export class HaServiceAction extends LitElement implements ActionElement {
if (!changedProperties.has("action")) { if (!changedProperties.has("action")) {
return; return;
} }
try {
assert(this.action, actionStruct);
} catch (error) {
fireEvent(this, "ui-mode-not-available", error);
}
if (this._actionData && this._actionData !== this.action.data) { if (this._actionData && this._actionData !== this.action.data) {
if (this._yamlEditor) { if (this._yamlEditor) {
this._yamlEditor.setValue(this.action.data); this._yamlEditor.setValue(this.action.data);

View File

@ -18,6 +18,7 @@ import { Condition } from "../../../../data/automation";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-editor"; import "./ha-automation-condition-editor";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
export interface ConditionElement extends LitElement { export interface ConditionElement extends LitElement {
condition: Condition; condition: Condition;
@ -115,7 +116,10 @@ export default class HaAutomationConditionRow extends LitElement {
}); });
} }
private _switchYamlMode() { private _switchYamlMode(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "interaction") {
return;
}
this._yamlMode = !this._yamlMode; this._yamlMode = !this._yamlMode;
} }

View File

@ -50,6 +50,13 @@ import { PaperListboxElement } from "@polymer/paper-listbox";
const MODES = ["single", "restart", "queued", "parallel"]; const MODES = ["single", "restart", "queued", "parallel"];
const MODES_MAX = ["queued", "parallel"]; const MODES_MAX = ["queued", "parallel"];
declare global {
// for fire event
interface HASSDomEvents {
"ui-mode-not-available": Error;
}
}
export class HaAutomationEditor extends LitElement { export class HaAutomationEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;

View File

@ -34,6 +34,7 @@ import "./types/ha-automation-trigger-time";
import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-time_pattern";
import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-webhook";
import "./types/ha-automation-trigger-zone"; import "./types/ha-automation-trigger-zone";
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
const OPTIONS = [ const OPTIONS = [
"device", "device",
@ -218,7 +219,10 @@ export default class HaAutomationTriggerRow extends LitElement {
fireEvent(this, "value-changed", { value: ev.detail.value }); fireEvent(this, "value-changed", { value: ev.detail.value });
} }
private _switchYamlMode() { private _switchYamlMode(ev: CustomEvent<RequestSelectedDetail>) {
if (ev.detail.source !== "interaction") {
return;
}
this._yamlMode = !this._yamlMode; this._yamlMode = !this._yamlMode;
} }

View File

@ -0,0 +1,24 @@
import { StructError } from "superstruct";
export const handleStructError = (err: Error): string[] => {
if (!(err instanceof StructError)) {
return [err.message];
}
const errors: string[] = [];
for (const failure of err.failures()) {
if (failure.type === "never") {
errors.push(
`Key "${failure.path[0]}" is not supported by the UI editor.`
);
} else {
errors.push(
`The value of "${failure.path.join(
"."
)}" is not supported by the UI editor, we support "${
failure.type
}" but received "${JSON.stringify(failure.value)}".`
);
}
}
return errors;
};

View File

@ -1,9 +1,17 @@
export function isEntityId(value: any): string | boolean { import { StructResult, StructContext, struct } from "superstruct";
const isEntityId = (value: unknown, context: StructContext): StructResult => {
if (typeof value !== "string") { if (typeof value !== "string") {
return "entity id should be a string"; return [context.fail({ type: "string" })];
} }
if (!value.includes(".")) { if (!value.includes(".")) {
return "entity id should be in the format 'domain.entity'"; return [
context.fail({
type: "entity id should be in the format 'domain.entity'",
}),
];
} }
return true; return true;
} };
export const EntityId = struct("entity-id", isEntityId);

View File

@ -1,9 +1,17 @@
export function isIcon(value: any): string | boolean { import { StructContext, StructResult, struct } from "superstruct";
const isIcon = (value: unknown, context: StructContext): StructResult => {
if (typeof value !== "string") { if (typeof value !== "string") {
return "icon should be a string"; return [context.fail({ type: "string" })];
} }
if (!value.includes(":")) { if (!value.includes(":")) {
return "icon should be in the format 'mdi:icon'"; return [
context.fail({
type: "icon should be in the format 'mdi:icon'",
}),
];
} }
return true; return true;
} };
export const Icon = struct("icon", isIcon);

View File

@ -1,10 +0,0 @@
import { superstruct } from "superstruct";
import { isEntityId } from "./is-entity-id";
import { isIcon } from "./is-icon";
export const struct = superstruct({
types: {
"entity-id": isEntityId,
icon: isIcon,
},
});

View File

@ -26,6 +26,8 @@ import type { LovelaceCardEditor } from "../../types";
import type { GUIModeChangedEvent } from "../types"; import type { GUIModeChangedEvent } from "../types";
import "../../../../components/ha-circular-progress"; import "../../../../components/ha-circular-progress";
import { deepEqual } from "../../../../common/util/deep-equal"; import { deepEqual } from "../../../../common/util/deep-equal";
import { handleStructError } from "../../common/structs/handle-errors";
import { GUISupportError } from "../gui-support-error";
export interface ConfigChangedEvent { export interface ConfigChangedEvent {
config: LovelaceCardConfig; config: LovelaceCardConfig;
@ -69,7 +71,7 @@ export class HuiCardEditor extends LitElement {
@internalProperty() private _error?: string; @internalProperty() private _error?: string;
// Warning: GUI editor can't handle configuration - ok to save // Warning: GUI editor can't handle configuration - ok to save
@internalProperty() private _warning?: string; @internalProperty() private _warnings?: string[];
@internalProperty() private _loading = false; @internalProperty() private _loading = false;
@ -121,7 +123,7 @@ export class HuiCardEditor extends LitElement {
} }
public get hasWarning(): boolean { public get hasWarning(): boolean {
return this._warning !== undefined; return this._warnings !== undefined;
} }
public get hasError(): boolean { public get hasError(): boolean {
@ -194,10 +196,15 @@ export class HuiCardEditor extends LitElement {
</div> </div>
` `
: ""} : ""}
${this._warning ${this._warnings
? html` ? html`
<div class="warning"> <div class="warning">
${this._warning} UI editor is not supported for this config:
<br />
<ul>
${this._warnings.map((warning) => html`<li>${warning}</li>`)}
</ul>
You can still edit your config in yaml.
</div> </div>
` `
: ""} : ""}
@ -238,7 +245,7 @@ export class HuiCardEditor extends LitElement {
let configElement = this._configElement; let configElement = this._configElement;
try { try {
this._error = undefined; this._error = undefined;
this._warning = undefined; this._warnings = undefined;
if (this._configElType !== cardType) { if (this._configElType !== cardType) {
// If the card type has changed, we need to load a new GUI editor // If the card type has changed, we need to load a new GUI editor
@ -254,7 +261,9 @@ export class HuiCardEditor extends LitElement {
configElement = await elClass.getConfigElement(); configElement = await elClass.getConfigElement();
} else { } else {
configElement = undefined; configElement = undefined;
throw Error(`WARNING: No visual editor available for: ${cardType}`); throw new GUISupportError(
`No visual editor available for: ${cardType}`
);
} }
this._configElement = configElement; this._configElement = configElement;
@ -272,11 +281,14 @@ export class HuiCardEditor extends LitElement {
try { try {
this._configElement!.setConfig(this.value); this._configElement!.setConfig(this.value);
} catch (err) { } catch (err) {
throw Error(`WARNING: ${err.message}`); throw new GUISupportError(
"Config is not supported",
handleStructError(err)
);
} }
} catch (err) { } catch (err) {
if (err.message.startsWith("WARNING:")) { if (err instanceof GUISupportError) {
this._warning = err.message.substr(8); this._warnings = err.warnings ?? [err.message];
} else { } else {
this._error = err; this._error = err;
} }
@ -312,6 +324,9 @@ export class HuiCardEditor extends LitElement {
.warning { .warning {
color: var(--warning-color); color: var(--warning-color);
} }
.warning ul {
margin: 4px 0;
}
ha-circular-progress { ha-circular-progress {
display: block; display: block;
margin: auto; margin: auto;

View File

@ -16,18 +16,18 @@ import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { AlarmPanelCardConfig } from "../../cards/types"; import { AlarmPanelCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, object, string, optional, array } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string?", entity: optional(string()),
name: "string?", name: optional(string()),
states: "array?", states: optional(array()),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["alarm_control_panel"]; const includeDomains = ["alarm_control_panel"];
@ -40,7 +40,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
@internalProperty() private _config?: AlarmPanelCardConfig; @internalProperty() private _config?: AlarmPanelCardConfig;
public setConfig(config: AlarmPanelCardConfig): void { public setConfig(config: AlarmPanelCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -194,6 +194,7 @@ export class HuiAlarmPanelCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -13,7 +13,6 @@ import "../../../../components/ha-icon-input";
import { ActionConfig } from "../../../../data/lovelace"; import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ButtonCardConfig } from "../../cards/types"; import { ButtonCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-action-editor"; import "../../components/hui-action-editor";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
@ -27,19 +26,20 @@ import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { assert, object, string, optional, boolean } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string?", entity: optional(string()),
name: "string?", name: optional(string()),
show_name: "boolean?", show_name: optional(boolean()),
icon: "string?", icon: optional(string()),
show_icon: "boolean?", show_icon: optional(boolean()),
icon_height: "string?", icon_height: optional(string()),
tap_action: struct.optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: struct.optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
theme: "string?", theme: optional(string()),
show_state: "boolean?", show_state: optional(boolean()),
}); });
@customElement("hui-button-card-editor") @customElement("hui-button-card-editor")
@ -50,7 +50,7 @@ export class HuiButtonCardEditor extends LitElement
@internalProperty() private _config?: ButtonCardConfig; @internalProperty() private _config?: ButtonCardConfig;
public setConfig(config: ButtonCardConfig): void { public setConfig(config: ButtonCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -258,6 +258,7 @@ export class HuiButtonCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
let newValue: string | undefined; let newValue: string | undefined;

View File

@ -16,7 +16,6 @@ import "../../../../components/entity/ha-entity-picker";
import { LovelaceConfig } from "../../../../data/lovelace"; import { LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ConditionalCardConfig } from "../../cards/types"; import { ConditionalCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { import {
ConfigChangedEvent, ConfigChangedEvent,
@ -24,16 +23,17 @@ import {
} from "../card-editor/hui-card-editor"; } from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import { GUIModeChangedEvent } from "../types"; import { GUIModeChangedEvent } from "../types";
import { string, any, object, optional, array, assert } from "superstruct";
const conditionStruct = struct({ const conditionStruct = object({
entity: "string", entity: string(),
state: "string?", state: optional(string()),
state_not: "string?", state_not: optional(string()),
}); });
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
card: "any", card: any(),
conditions: struct.optional([conditionStruct]), conditions: optional(array(conditionStruct)),
}); });
@customElement("hui-conditional-card-editor") @customElement("hui-conditional-card-editor")
@ -54,7 +54,8 @@ export class HuiConditionalCardEditor extends LitElement
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-card-editor") private _cardEditorEl?: HuiCardEditor;
public setConfig(config: ConditionalCardConfig): void { public setConfig(config: ConditionalCardConfig): void {
this._config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config;
} }
public refreshYamlEditor(focus) { public refreshYamlEditor(focus) {
@ -217,7 +218,7 @@ export class HuiConditionalCardEditor extends LitElement
} }
this._setMode(true); this._setMode(true);
this._guiModeAvailable = true; this._guiModeAvailable = true;
this._config.card = ev.detail.config; this._config = { ...this._config, card: ev.detail.config };
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -226,7 +227,7 @@ export class HuiConditionalCardEditor extends LitElement
if (!this._config) { if (!this._config) {
return; return;
} }
this._config.card = ev.detail.config; this._config = { ...this._config, card: ev.detail.config };
this._guiModeAvailable = ev.detail.guiModeAvailable; this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -236,7 +237,8 @@ export class HuiConditionalCardEditor extends LitElement
return; return;
} }
// @ts-ignore // @ts-ignore
this._config.card = {}; this._config = { ...this._config, card: {} };
// @ts-ignore
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -245,10 +247,12 @@ export class HuiConditionalCardEditor extends LitElement
if (target.value === "" || !this._config) { if (target.value === "" || !this._config) {
return; return;
} }
this._config.conditions.push({ const conditions = [...this._config.conditions];
conditions.push({
entity: target.value, entity: target.value,
state: "", state: "",
}); });
this._config = { ...this._config, conditions };
target.value = ""; target.value = "";
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -258,10 +262,11 @@ export class HuiConditionalCardEditor extends LitElement
if (!this._config || !target) { if (!this._config || !target) {
return; return;
} }
const conditions = [...this._config.conditions];
if (target.configValue === "entity" && target.value === "") { if (target.configValue === "entity" && target.value === "") {
this._config.conditions.splice(target.index, 1); conditions.splice(target.index, 1);
} else { } else {
const condition = this._config.conditions[target.index]; const condition = { ...conditions[target.index] };
if (target.configValue === "entity") { if (target.configValue === "entity") {
condition.entity = target.value; condition.entity = target.value;
} else if (target.configValue === "state") { } else if (target.configValue === "state") {
@ -281,8 +286,9 @@ export class HuiConditionalCardEditor extends LitElement
delete condition.state_not; delete condition.state_not;
} }
} }
this._config.conditions[target.index] = condition; conditions[target.index] = condition;
} }
this._config = { ...this._config, conditions };
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }

View File

@ -20,7 +20,6 @@ import {
EntitiesCardConfig, EntitiesCardConfig,
EntitiesCardEntityConfig, EntitiesCardEntityConfig,
} from "../../cards/types"; } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { headerFooterConfigStructs } from "../../header-footer/types"; import { headerFooterConfigStructs } from "../../header-footer/types";
@ -33,15 +32,24 @@ import {
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
optional,
object,
boolean,
array,
union,
assert,
} from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string|number?", title: optional(union([string(), boolean()])),
theme: "string?", theme: optional(string()),
show_header_toggle: "boolean?", show_header_toggle: optional(boolean()),
entities: [entitiesConfigStruct], entities: array(entitiesConfigStruct),
header: struct.optional(headerFooterConfigStructs), header: optional(headerFooterConfigStructs),
footer: struct.optional(headerFooterConfigStructs), footer: optional(headerFooterConfigStructs),
}); });
@customElement("hui-entities-card-editor") @customElement("hui-entities-card-editor")
@ -54,7 +62,7 @@ export class HuiEntitiesCardEditor extends LitElement
@internalProperty() private _configEntities?: EntitiesCardEntityConfig[]; @internalProperty() private _configEntities?: EntitiesCardEntityConfig[];
public setConfig(config: EntitiesCardConfig): void { public setConfig(config: EntitiesCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
this._configEntities = processEditorEntities(config.entities); this._configEntities = processEditorEntities(config.entities);
} }
@ -131,6 +139,7 @@ export class HuiEntitiesCardEditor extends LitElement
this._configEntities = processEditorEntities(this._config.entities); this._configEntities = processEditorEntities(this._config.entities);
} else if (target.configValue) { } else if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -12,7 +12,6 @@ import { stateIcon } from "../../../../common/entity/state_icon";
import "../../../../components/ha-icon-input"; import "../../../../components/ha-icon-input";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { EntityCardConfig } from "../../cards/types"; import { EntityCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-action-editor"; import "../../components/hui-action-editor";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
@ -20,16 +19,17 @@ import { headerFooterConfigStructs } from "../../header-footer/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string?", entity: optional(string()),
name: "string?", name: optional(string()),
icon: "string?", icon: optional(string()),
attribute: "string?", attribute: optional(string()),
unit: "string?", unit: optional(string()),
theme: "string?", theme: optional(string()),
footer: struct.optional(headerFooterConfigStructs), footer: optional(headerFooterConfigStructs),
}); });
@customElement("hui-entity-card-editor") @customElement("hui-entity-card-editor")
@ -40,7 +40,7 @@ export class HuiEntityCardEditor extends LitElement
@internalProperty() private _config?: EntityCardConfig; @internalProperty() private _config?: EntityCardConfig;
public setConfig(config: EntityCardConfig): void { public setConfig(config: EntityCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -158,6 +158,7 @@ export class HuiEntityCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
let newValue: string | undefined; let newValue: string | undefined;

View File

@ -14,23 +14,23 @@ import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { GaugeCardConfig, SeverityConfig } from "../../cards/types"; import { GaugeCardConfig, SeverityConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { assert, object, string, optional, number } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
name: "string?", name: optional(string()),
entity: "string?", entity: optional(string()),
unit: "string?", unit: optional(string()),
min: "number?", min: optional(number()),
max: "number?", max: optional(number()),
severity: "object?", severity: optional(object()),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["sensor"]; const includeDomains = ["sensor"];
@ -43,7 +43,7 @@ export class HuiGaugeCardEditor extends LitElement
@internalProperty() private _config?: GaugeCardConfig; @internalProperty() private _config?: GaugeCardConfig;
public setConfig(config: GaugeCardConfig): void { public setConfig(config: GaugeCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -260,6 +260,7 @@ export class HuiGaugeCardEditor extends LitElement
target.value === "" || target.value === "" ||
(target.type === "number" && isNaN(Number(target.value))) (target.type === "number" && isNaN(Number(target.value)))
) { ) {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
let value: any = target.value; let value: any = target.value;

View File

@ -17,7 +17,6 @@ import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ConfigEntity, GlanceCardConfig } from "../../cards/types"; import { ConfigEntity, GlanceCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
@ -29,16 +28,26 @@ import {
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
union,
object,
optional,
number,
boolean,
assert,
array,
} from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string|number?", title: optional(union([string(), number()])),
theme: "string?", theme: optional(string()),
columns: "number?", columns: optional(number()),
show_name: "boolean?", show_name: optional(boolean()),
show_state: "boolean?", show_state: optional(boolean()),
show_icon: "boolean?", show_icon: optional(boolean()),
entities: [entitiesConfigStruct], entities: array(entitiesConfigStruct),
}); });
@customElement("hui-glance-card-editor") @customElement("hui-glance-card-editor")
@ -51,7 +60,7 @@ export class HuiGlanceCardEditor extends LitElement
@internalProperty() private _configEntities?: ConfigEntity[]; @internalProperty() private _configEntities?: ConfigEntity[];
public setConfig(config: GlanceCardConfig): void { public setConfig(config: GlanceCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
this._configEntities = processEditorEntities(config.entities); this._configEntities = processEditorEntities(config.entities);
} }
@ -191,6 +200,7 @@ export class HuiGlanceCardEditor extends LitElement
target.value === "" || target.value === "" ||
(target.type === "number" && isNaN(Number(target.value))) (target.type === "number" && isNaN(Number(target.value)))
) { ) {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
let value: any = target.value; let value: any = target.value;

View File

@ -10,28 +10,37 @@ import {
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { HistoryGraphCardConfig } from "../../cards/types"; import { HistoryGraphCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import { EntityConfig } from "../../entity-rows/types"; import { EntityConfig } from "../../entity-rows/types";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { processEditorEntities } from "../process-editor-entities"; import { processEditorEntities } from "../process-editor-entities";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import {
assert,
union,
optional,
string,
object,
array,
number,
} from "superstruct";
import { EntityId } from "../../common/structs/is-entity-id";
const entitiesConfigStruct = struct.union([ const entitiesConfigStruct = union([
{ object({
entity: "entity-id", entity: EntityId,
name: "string?", name: optional(string()),
}, }),
"entity-id", EntityId,
]); ]);
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entities: [entitiesConfigStruct], entities: array(entitiesConfigStruct),
title: "string?", title: optional(string()),
hours_to_show: "number?", hours_to_show: optional(number()),
refresh_interval: "number?", refresh_interval: optional(number()),
}); });
@customElement("hui-history-graph-card-editor") @customElement("hui-history-graph-card-editor")
@ -44,7 +53,7 @@ export class HuiHistoryGraphCardEditor extends LitElement
@internalProperty() private _configEntities?: EntityConfig[]; @internalProperty() private _configEntities?: EntityConfig[];
public setConfig(config: HistoryGraphCardConfig): void { public setConfig(config: HistoryGraphCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
this._configEntities = processEditorEntities(config.entities); this._configEntities = processEditorEntities(config.entities);
} }
@ -131,6 +140,7 @@ export class HuiHistoryGraphCardEditor extends LitElement
this._configEntities = processEditorEntities(this._config.entities); this._configEntities = processEditorEntities(this._config.entities);
} else if (target.configValue) { } else if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
let value: any = target.value; let value: any = target.value;

View File

@ -11,17 +11,17 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { HumidifierCardConfig } from "../../cards/types"; import { HumidifierCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string", entity: string(),
name: "string?", name: optional(string()),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["humidifier"]; const includeDomains = ["humidifier"];
@ -34,7 +34,7 @@ export class HuiHumidifierCardEditor extends LitElement
@internalProperty() private _config?: HumidifierCardConfig; @internalProperty() private _config?: HumidifierCardConfig;
public setConfig(config: HumidifierCardConfig): void { public setConfig(config: HumidifierCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -102,6 +102,7 @@ export class HuiHumidifierCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { ...this._config, [target.configValue!]: target.value }; this._config = { ...this._config, [target.configValue!]: target.value };

View File

@ -10,16 +10,16 @@ import {
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { IframeCardConfig } from "../../cards/types"; import { IframeCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string?", title: optional(string()),
url: "string?", url: optional(string()),
aspect_ratio: "string?", aspect_ratio: optional(string()),
}); });
@customElement("hui-iframe-card-editor") @customElement("hui-iframe-card-editor")
@ -30,7 +30,7 @@ export class HuiIframeCardEditor extends LitElement
@internalProperty() private _config?: IframeCardConfig; @internalProperty() private _config?: IframeCardConfig;
public setConfig(config: IframeCardConfig): void { public setConfig(config: IframeCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -102,6 +102,7 @@ export class HuiIframeCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (value === "") { if (value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { ...this._config, [target.configValue!]: value }; this._config = { ...this._config, [target.configValue!]: value };

View File

@ -13,7 +13,6 @@ import "../../../../components/ha-icon-input";
import { ActionConfig } from "../../../../data/lovelace"; import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LightCardConfig } from "../../cards/types"; import { LightCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-action-editor"; import "../../components/hui-action-editor";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
@ -24,15 +23,16 @@ import {
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
name: "string?", name: optional(string()),
entity: "string?", entity: optional(string()),
theme: "string?", theme: optional(string()),
icon: "string?", icon: optional(string()),
hold_action: struct.optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: struct.optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct),
}); });
const includeDomains = ["light"]; const includeDomains = ["light"];
@ -45,7 +45,8 @@ export class HuiLightCardEditor extends LitElement
@internalProperty() private _config?: LightCardConfig; @internalProperty() private _config?: LightCardConfig;
public setConfig(config: LightCardConfig): void { public setConfig(config: LightCardConfig): void {
this._config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config;
} }
get _name(): string { get _name(): string {
@ -177,6 +178,7 @@ export class HuiLightCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -13,7 +13,6 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../../../polymer-types"; import { PolymerChangedEvent } from "../../../../polymer-types";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MapCardConfig } from "../../cards/types"; import { MapCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-input-list-editor"; import "../../components/hui-input-list-editor";
import { EntityConfig } from "../../entity-rows/types"; import { EntityConfig } from "../../entity-rows/types";
@ -28,16 +27,25 @@ import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import {
string,
optional,
object,
number,
boolean,
array,
assert,
} from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string?", title: optional(string()),
aspect_ratio: "string?", aspect_ratio: optional(string()),
default_zoom: "number?", default_zoom: optional(number()),
dark_mode: "boolean?", dark_mode: optional(boolean()),
entities: [entitiesConfigStruct], entities: array(entitiesConfigStruct),
hours_to_show: "number?", hours_to_show: optional(number()),
geo_location_sources: "array?", geo_location_sources: optional(array()),
}); });
@customElement("hui-map-card-editor") @customElement("hui-map-card-editor")
@ -49,7 +57,7 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
@internalProperty() private _configEntities?: EntityConfig[]; @internalProperty() private _configEntities?: EntityConfig[];
public setConfig(config: MapCardConfig): void { public setConfig(config: MapCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
this._configEntities = config.entities this._configEntities = config.entities
? processEditorEntities(config.entities) ? processEditorEntities(config.entities)
@ -195,6 +203,7 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
value = Number(value); value = Number(value);
} }
if (target.value === "" || (target.type === "number" && isNaN(value))) { if (target.value === "" || (target.type === "number" && isNaN(value))) {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else if (target.configValue) { } else if (target.configValue) {
this._config = { this._config = {

View File

@ -11,17 +11,17 @@ import {
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MarkdownCardConfig } from "../../cards/types"; import { MarkdownCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string?", title: optional(string()),
content: "string", content: string(),
theme: "string?", theme: optional(string()),
}); });
@customElement("hui-markdown-card-editor") @customElement("hui-markdown-card-editor")
@ -32,7 +32,7 @@ export class HuiMarkdownCardEditor extends LitElement
@internalProperty() private _config?: MarkdownCardConfig; @internalProperty() private _config?: MarkdownCardConfig;
public setConfig(config: MarkdownCardConfig): void { public setConfig(config: MarkdownCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -100,6 +100,7 @@ export class HuiMarkdownCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "" && target.configValue !== "content") { if (target.value === "" && target.configValue !== "content") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -10,13 +10,13 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { MediaControlCardConfig } from "../../cards/types"; import { MediaControlCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { assert, object, string, optional } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string?", entity: optional(string()),
}); });
const includeDomains = ["media_player"]; const includeDomains = ["media_player"];
@ -29,7 +29,7 @@ export class HuiMediaControlCardEditor extends LitElement
@internalProperty() private _config?: MediaControlCardConfig; @internalProperty() private _config?: MediaControlCardConfig;
public setConfig(config: MediaControlCardConfig): void { public setConfig(config: MediaControlCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -71,6 +71,7 @@ export class HuiMediaControlCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -11,7 +11,6 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { ActionConfig } from "../../../../data/lovelace"; import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { PictureCardConfig } from "../../cards/types"; import { PictureCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-action-editor"; import "../../components/hui-action-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
@ -21,13 +20,14 @@ import {
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, object, optional, assert } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
image: "string?", image: optional(string()),
tap_action: struct.optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: struct.optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
theme: "string?", theme: optional(string()),
}); });
@customElement("hui-picture-card-editor") @customElement("hui-picture-card-editor")
@ -38,7 +38,7 @@ export class HuiPictureCardEditor extends LitElement
@internalProperty() private _config?: PictureCardConfig; @internalProperty() private _config?: PictureCardConfig;
public setConfig(config: PictureCardConfig): void { public setConfig(config: PictureCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -128,6 +128,7 @@ export class HuiPictureCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -16,7 +16,6 @@ import "../../../../components/ha-formfield";
import { ActionConfig } from "../../../../data/lovelace"; import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { PictureEntityCardConfig } from "../../cards/types"; import { PictureEntityCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-action-editor"; import "../../components/hui-action-editor";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
@ -28,20 +27,21 @@ import {
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { assert, object, string, optional, boolean } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string", entity: string(),
image: "string?", image: optional(string()),
name: "string?", name: optional(string()),
camera_image: "string?", camera_image: optional(string()),
camera_view: "string?", camera_view: optional(string()),
aspect_ratio: "string?", aspect_ratio: optional(string()),
tap_action: struct.optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: struct.optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
show_name: "boolean?", show_name: optional(boolean()),
show_state: "boolean?", show_state: optional(boolean()),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["camera"]; const includeDomains = ["camera"];
@ -54,7 +54,7 @@ export class HuiPictureEntityCardEditor extends LitElement
@internalProperty() private _config?: PictureEntityCardConfig; @internalProperty() private _config?: PictureEntityCardConfig;
public setConfig(config: PictureEntityCardConfig): void { public setConfig(config: PictureEntityCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -275,6 +275,7 @@ export class HuiPictureEntityCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (value === "") { if (value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -15,7 +15,6 @@ import "../../../../components/entity/ha-entity-picker";
import { ActionConfig } from "../../../../data/lovelace"; import { ActionConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { PictureGlanceCardConfig } from "../../cards/types"; import { PictureGlanceCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-action-editor"; import "../../components/hui-action-editor";
import "../../components/hui-entity-editor"; import "../../components/hui-entity-editor";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
@ -29,19 +28,20 @@ import {
EntitiesEditorEvent, EntitiesEditorEvent,
} from "../types"; } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, string, object, optional, array } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string?", title: optional(string()),
entity: "string?", entity: optional(string()),
image: "string?", image: optional(string()),
camera_image: "string?", camera_image: optional(string()),
camera_view: "string?", camera_view: optional(string()),
aspect_ratio: "string?", aspect_ratio: optional(string()),
tap_action: struct.optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: struct.optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
entities: [entitiesConfigStruct], entities: array(entitiesConfigStruct),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["camera"]; const includeDomains = ["camera"];
@ -56,7 +56,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
@internalProperty() private _configEntities?: EntityConfig[]; @internalProperty() private _configEntities?: EntityConfig[];
public setConfig(config: PictureGlanceCardConfig): void { public setConfig(config: PictureGlanceCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
this._configEntities = processEditorEntities(config.entities); this._configEntities = processEditorEntities(config.entities);
} }
@ -262,6 +262,7 @@ export class HuiPictureGlanceCardEditor extends LitElement
} }
if (value === "") { if (value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -12,17 +12,17 @@ import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { PlantStatusCardConfig } from "../../cards/types"; import { PlantStatusCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { assert, object, string, optional } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string", entity: string(),
name: "string?", name: optional(string()),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["plant"]; const includeDomains = ["plant"];
@ -35,7 +35,7 @@ export class HuiPlantStatusCardEditor extends LitElement
@internalProperty() private _config?: PlantStatusCardConfig; @internalProperty() private _config?: PlantStatusCardConfig;
public setConfig(config: PlantStatusCardConfig): void { public setConfig(config: PlantStatusCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -102,6 +102,7 @@ export class HuiPlantStatusCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -16,22 +16,22 @@ import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-icon-input"; import "../../../../components/ha-icon-input";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { SensorCardConfig } from "../../cards/types"; import { SensorCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { string, assert, object, optional, number } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string?", entity: optional(string()),
name: "string?", name: optional(string()),
icon: "string?", icon: optional(string()),
graph: "string?", graph: optional(string()),
unit: "string?", unit: optional(string()),
detail: "number?", detail: optional(number()),
theme: "string?", theme: optional(string()),
hours_to_show: "number?", hours_to_show: optional(number()),
}); });
const includeDomains = ["sensor"]; const includeDomains = ["sensor"];
@ -44,7 +44,7 @@ export class HuiSensorCardEditor extends LitElement
@internalProperty() private _config?: SensorCardConfig; @internalProperty() private _config?: SensorCardConfig;
public setConfig(config: SensorCardConfig): void { public setConfig(config: SensorCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -205,6 +205,7 @@ export class HuiSensorCardEditor extends LitElement
target.value === "" || target.value === "" ||
(target.type === "number" && isNaN(Number(target.value))) (target.type === "number" && isNaN(Number(target.value)))
) { ) {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
let value: any = target.value; let value: any = target.value;

View File

@ -13,15 +13,15 @@ import { isComponentLoaded } from "../../../../common/config/is_component_loaded
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ShoppingListCardConfig } from "../../cards/types"; import { ShoppingListCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { string, assert, object, optional } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
title: "string?", title: optional(string()),
theme: "string?", theme: optional(string()),
}); });
@customElement("hui-shopping-list-card-editor") @customElement("hui-shopping-list-card-editor")
@ -32,7 +32,7 @@ export class HuiShoppingListEditor extends LitElement
@internalProperty() private _config?: ShoppingListCardConfig; @internalProperty() private _config?: ShoppingListCardConfig;
public setConfig(config: ShoppingListCardConfig): void { public setConfig(config: ShoppingListCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -91,6 +91,7 @@ export class HuiShoppingListEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -16,7 +16,6 @@ import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { LovelaceConfig } from "../../../../data/lovelace"; import { LovelaceConfig } from "../../../../data/lovelace";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { StackCardConfig } from "../../cards/types"; import { StackCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { import {
ConfigChangedEvent, ConfigChangedEvent,
@ -24,11 +23,12 @@ import {
} from "../card-editor/hui-card-editor"; } from "../card-editor/hui-card-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import { GUIModeChangedEvent } from "../types"; import { GUIModeChangedEvent } from "../types";
import { assert, object, string, array, any, optional } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
cards: ["any"], cards: array(any()),
title: "string?", title: optional(string()),
}); });
@customElement("hui-stack-card-editor") @customElement("hui-stack-card-editor")
@ -48,8 +48,9 @@ export class HuiStackCardEditor extends LitElement
@query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; @query("hui-card-editor") private _cardEditorEl?: HuiCardEditor;
public setConfig(config: StackCardConfig): void { public setConfig(config: Readonly<StackCardConfig>): void {
this._config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config;
} }
public refreshYamlEditor(focus) { public refreshYamlEditor(focus) {
@ -162,7 +163,9 @@ export class HuiStackCardEditor extends LitElement
if (!this._config) { if (!this._config) {
return; return;
} }
this._config.cards[this._selectedCard] = ev.detail.config; const cards = [...this._config.cards];
cards[this._selectedCard] = ev.detail.config;
this._config = { ...this._config, cards };
this._guiModeAvailable = ev.detail.guiModeAvailable; this._guiModeAvailable = ev.detail.guiModeAvailable;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -173,7 +176,8 @@ export class HuiStackCardEditor extends LitElement
return; return;
} }
const config = ev.detail.config; const config = ev.detail.config;
this._config.cards.push(config); const cards = [...this._config.cards, config];
this._config = { ...this._config, cards };
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -181,7 +185,9 @@ export class HuiStackCardEditor extends LitElement
if (!this._config) { if (!this._config) {
return; return;
} }
this._config.cards.splice(this._selectedCard, 1); const cards = [...this._config.cards];
cards.splice(this._selectedCard, 1);
this._config = { ...this._config, cards };
this._selectedCard = Math.max(0, this._selectedCard - 1); this._selectedCard = Math.max(0, this._selectedCard - 1);
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -192,8 +198,13 @@ export class HuiStackCardEditor extends LitElement
} }
const source = this._selectedCard; const source = this._selectedCard;
const target = ev.target.id === "move-before" ? source - 1 : source + 1; const target = ev.target.id === "move-before" ? source - 1 : source + 1;
const card = this._config.cards.splice(this._selectedCard, 1)[0]; const cards = [...this._config.cards];
this._config.cards.splice(target, 0, card); const card = cards.splice(this._selectedCard, 1)[0];
cards.splice(target, 0, card);
this._config = {
...this._config,
cards,
};
this._selectedCard = target; this._selectedCard = target;
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }

View File

@ -11,17 +11,17 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entity-picker"; import "../../../../components/entity/ha-entity-picker";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { ThermostatCardConfig } from "../../cards/types"; import { ThermostatCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { object, string, optional, assert } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string", entity: string(),
name: "string?", name: optional(string()),
theme: "string?", theme: optional(string()),
}); });
const includeDomains = ["climate"]; const includeDomains = ["climate"];
@ -34,7 +34,7 @@ export class HuiThermostatCardEditor extends LitElement
@internalProperty() private _config?: ThermostatCardConfig; @internalProperty() private _config?: ThermostatCardConfig;
public setConfig(config: ThermostatCardConfig): void { public setConfig(config: ThermostatCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -102,6 +102,7 @@ export class HuiThermostatCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { ...this._config, [target.configValue!]: target.value }; this._config = { ...this._config, [target.configValue!]: target.value };

View File

@ -12,20 +12,20 @@ import "../../../../components/ha-switch";
import "../../../../components/ha-formfield"; import "../../../../components/ha-formfield";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { WeatherForecastCardConfig } from "../../cards/types"; import { WeatherForecastCardConfig } from "../../cards/types";
import { struct } from "../../common/structs/struct";
import "../../components/hui-theme-select-editor"; import "../../components/hui-theme-select-editor";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { EditorTarget, EntitiesEditorEvent } from "../types"; import { EditorTarget, EntitiesEditorEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { object, string, optional, boolean, assert } from "superstruct";
const cardConfigStruct = struct({ const cardConfigStruct = object({
type: "string", type: string(),
entity: "string?", entity: optional(string()),
name: "string?", name: optional(string()),
theme: "string?", theme: optional(string()),
show_forecast: "boolean?", show_forecast: optional(boolean()),
secondary_info_attribute: "string?", secondary_info_attribute: optional(string()),
}); });
const includeDomains = ["weather"]; const includeDomains = ["weather"];
@ -38,7 +38,7 @@ export class HuiWeatherForecastCardEditor extends LitElement
@internalProperty() private _config?: WeatherForecastCardConfig; @internalProperty() private _config?: WeatherForecastCardConfig;
public setConfig(config: WeatherForecastCardConfig): void { public setConfig(config: WeatherForecastCardConfig): void {
config = cardConfigStruct(config); assert(config, cardConfigStruct);
this._config = config; this._config = config;
} }
@ -139,6 +139,7 @@ export class HuiWeatherForecastCardEditor extends LitElement
} }
if (target.configValue) { if (target.configValue) {
if (target.value === "") { if (target.value === "") {
this._config = { ...this._config };
delete this._config[target.configValue!]; delete this._config[target.configValue!];
} else { } else {
this._config = { this._config = {

View File

@ -0,0 +1,9 @@
export class GUISupportError extends Error {
public warnings?: string[] = [];
constructor(message: string, warnings?: string[]) {
super(message);
this.name = "GUISupportError";
this.warnings = warnings;
}
}

View File

@ -4,8 +4,10 @@ import {
LovelaceViewConfig, LovelaceViewConfig,
ShowViewConfig, ShowViewConfig,
} from "../../../data/lovelace"; } from "../../../data/lovelace";
import { struct } from "../common/structs/struct";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
import { optional, string, object, union } from "superstruct";
import { EntityId } from "../common/structs/is-entity-id";
import { Icon } from "../common/structs/is-icon";
export interface YamlChangedEvent extends Event { export interface YamlChangedEvent extends Event {
detail: { detail: {
@ -66,19 +68,19 @@ export interface CardPickTarget extends EventTarget {
config: LovelaceCardConfig; config: LovelaceCardConfig;
} }
export const actionConfigStruct = struct({ export const actionConfigStruct = object({
action: "string", action: string(),
navigation_path: "string?", navigation_path: optional(string()),
url_path: "string?", url_path: optional(string()),
service: "string?", service: optional(string()),
service_data: "object?", service_data: optional(object()),
}); });
export const entitiesConfigStruct = struct.union([ export const entitiesConfigStruct = union([
{ object({
entity: "entity-id", entity: EntityId,
name: "string?", name: optional(string()),
icon: "icon?", icon: optional(Icon),
}, }),
"entity-id", EntityId,
]); ]);

View File

@ -1,5 +1,5 @@
import { ActionConfig } from "../../../data/lovelace"; import { ActionConfig } from "../../../data/lovelace";
import { struct } from "../common/structs/struct"; import { object, optional, union, string, number, array } from "superstruct";
import { actionConfigStruct, entitiesConfigStruct } from "../editor/types"; import { actionConfigStruct, entitiesConfigStruct } from "../editor/types";
import { EntityConfig } from "../entity-rows/types"; import { EntityConfig } from "../entity-rows/types";
@ -24,27 +24,27 @@ export interface PictureHeaderFooterConfig extends LovelaceHeaderFooterConfig {
double_tap_action?: ActionConfig; double_tap_action?: ActionConfig;
} }
export const pictureHeaderFooterConfigStruct = struct({ export const pictureHeaderFooterConfigStruct = object({
type: "string", type: string(),
image: "string", image: string(),
tap_action: struct.optional(actionConfigStruct), tap_action: optional(actionConfigStruct),
hold_action: struct.optional(actionConfigStruct), hold_action: optional(actionConfigStruct),
double_tap_action: struct.optional(actionConfigStruct), double_tap_action: optional(actionConfigStruct),
}); });
export const buttonsHeaderFooterConfigStruct = struct({ export const buttonsHeaderFooterConfigStruct = object({
type: "string", type: string(),
entities: [entitiesConfigStruct], entities: array(entitiesConfigStruct),
}); });
export const graphHeaderFooterConfigStruct = struct({ export const graphHeaderFooterConfigStruct = object({
type: "string", type: string(),
entity: "string", entity: string(),
detail: "number?", detail: optional(string()),
hours_to_show: "number?", hours_to_show: optional(number()),
}); });
export const headerFooterConfigStructs = struct.union([ export const headerFooterConfigStructs = union([
pictureHeaderFooterConfigStruct, pictureHeaderFooterConfigStruct,
buttonsHeaderFooterConfigStruct, buttonsHeaderFooterConfigStruct,
graphHeaderFooterConfigStruct, graphHeaderFooterConfigStruct,

View File

@ -27,12 +27,12 @@ import {
} from "../../dialogs/generic/show-dialog-box"; } from "../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../resources/styles"; import { haStyle } from "../../resources/styles";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { struct } from "./common/structs/struct";
import type { Lovelace } from "./types"; import type { Lovelace } from "./types";
import { optional, array, string, object, type, assert } from "superstruct";
const lovelaceStruct = struct.interface({ const lovelaceStruct = type({
title: "string?", title: optional(string()),
views: ["object"], views: array(object()),
}); });
@customElement("hui-editor") @customElement("hui-editor")
@ -251,7 +251,7 @@ class LovelaceFullConfigEditor extends LitElement {
return; return;
} }
try { try {
config = lovelaceStruct(config); assert(config, lovelaceStruct);
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
text: this.hass.localize( text: this.hass.localize(

View File

@ -34,7 +34,7 @@ documentContainer.innerHTML = `<custom-style>
--scrollbar-thumb-color: rgb(194, 194, 194); --scrollbar-thumb-color: rgb(194, 194, 194);
--error-color: #db4437; --error-color: #db4437;
--warning-color: #f4b400; --warning-color: #FF9800;
--success-color: #0f9d58; --success-color: #0f9d58;
--info-color: #4285f4; --info-color: #4285f4;

View File

@ -4411,16 +4411,6 @@ clone-buffer@^1.0.0:
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
clone-deep@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
dependencies:
for-own "^1.0.0"
is-plain-object "^2.0.4"
kind-of "^6.0.0"
shallow-clone "^1.0.0"
clone-stats@^1.0.0: clone-stats@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
@ -6163,11 +6153,6 @@ follow-redirects@^1.0.0:
dependencies: dependencies:
debug "^3.2.6" debug "^3.2.6"
for-in@^0.1.3:
version "0.1.8"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
for-in@^1.0.1, for-in@^1.0.2: for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -7706,7 +7691,7 @@ kind-of@^5.0.0, kind-of@^5.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
kind-of@^6.0.0, kind-of@^6.0.1, kind-of@^6.0.2: kind-of@^6.0.0, kind-of@^6.0.2:
version "6.0.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
@ -8628,14 +8613,6 @@ mixin-deep@^1.2.0:
for-in "^1.0.2" for-in "^1.0.2"
is-extendable "^1.0.1" is-extendable "^1.0.1"
mixin-object@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
dependencies:
for-in "^0.1.3"
is-extendable "^0.1.1"
mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1:
version "0.5.1" version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@ -10785,15 +10762,6 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1" inherits "^2.0.1"
safe-buffer "^5.0.1" safe-buffer "^5.0.1"
shallow-clone@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
dependencies:
is-extendable "^0.1.1"
kind-of "^5.0.0"
mixin-object "^2.0.1"
shebang-command@^1.2.0: shebang-command@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@ -11300,13 +11268,10 @@ subarg@^1.0.0:
dependencies: dependencies:
minimist "^1.1.0" minimist "^1.1.0"
superstruct@^0.6.1: superstruct@^0.10.12:
version "0.6.1" version "0.10.12"
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.6.1.tgz#148fc3d627bb59fcfe24aa1bd2a1b8c51b1db072" resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.10.12.tgz#7b2c8adaf61b75257265eac3b588f30017f996f0"
integrity sha512-LDbOKL5sNbOJ00Q36iYRhSexKIptZje0/mhNznnz04wT9CmsPDZg/K/UV1dgYuCwNMuOBHTbVROZsGB9EhhK4w== integrity sha512-FiNhfegyytDI0QxrrEoeGknFM28SnoHqCBpkWewUm8jRNj74NVxLpiiePvkOo41Ze/aKMSHa/twWjNF81mKaQQ==
dependencies:
clone-deep "^2.0.1"
kind-of "^6.0.1"
supports-color@6.0.0: supports-color@6.0.0:
version "6.0.0" version "6.0.0"