mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-27 19:26:36 +00:00
Add Fields to Script UI (#18250)
This commit is contained in:
parent
7ce7cbb755
commit
80112bb662
@ -26,7 +26,7 @@ export class HaDateSelector extends LitElement {
|
||||
.label=${this.label}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.value=${this.value}
|
||||
.value=${typeof this.value === "string" ? this.value : undefined}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
>
|
||||
|
@ -30,7 +30,8 @@ export class HaDateTimeSelector extends LitElement {
|
||||
@query("ha-time-input") private _timeInput!: HaTimeInput;
|
||||
|
||||
protected render() {
|
||||
const values = this.value?.split(" ");
|
||||
const values =
|
||||
typeof this.value === "string" ? this.value.split(" ") : undefined;
|
||||
|
||||
return html`
|
||||
<div class="input">
|
||||
|
@ -38,7 +38,10 @@ export class HaNumberSelector extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const isBox = this.selector.number?.mode === "box";
|
||||
const isBox =
|
||||
this.selector.number?.mode === "box" ||
|
||||
this.selector.number?.min === undefined ||
|
||||
this.selector.number?.max === undefined;
|
||||
|
||||
return html`
|
||||
<div class="input">
|
||||
@ -67,11 +70,9 @@ export class HaNumberSelector extends LitElement {
|
||||
(this.selector.number?.step ?? 1) % 1 !== 0
|
||||
? "decimal"
|
||||
: "numeric"}
|
||||
.label=${this.selector.number?.mode !== "box"
|
||||
? undefined
|
||||
: this.label}
|
||||
.label=${!isBox ? undefined : this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
class=${classMap({ single: this.selector.number?.mode === "box" })}
|
||||
class=${classMap({ single: isBox })}
|
||||
.min=${this.selector.number?.min}
|
||||
.max=${this.selector.number?.max}
|
||||
.value=${this._valueStr ?? ""}
|
||||
@ -83,7 +84,7 @@ export class HaNumberSelector extends LitElement {
|
||||
.suffix=${this.selector.number?.unit_of_measurement}
|
||||
type="number"
|
||||
autoValidate
|
||||
?no-spinner=${this.selector.number?.mode !== "box"}
|
||||
?no-spinner=${!isBox}
|
||||
@input=${this._handleInputChange}
|
||||
>
|
||||
</ha-textfield>
|
||||
|
@ -23,7 +23,7 @@ export class HaTimeSelector extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-time-input
|
||||
.value=${this.value}
|
||||
.value=${typeof this.value === "string" ? this.value : undefined}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
|
@ -93,12 +93,27 @@ export interface ManualScriptConfig {
|
||||
icon?: string;
|
||||
mode?: (typeof MODES)[number];
|
||||
max?: number;
|
||||
fields?: Fields;
|
||||
}
|
||||
|
||||
export interface BlueprintScriptConfig extends ManualScriptConfig {
|
||||
use_blueprint: { path: string; input?: BlueprintInput };
|
||||
}
|
||||
|
||||
export interface Fields {
|
||||
[key: string]: Field;
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
name?: string;
|
||||
description?: string;
|
||||
advanced?: boolean;
|
||||
required?: boolean;
|
||||
example?: string;
|
||||
default?: any;
|
||||
selector?: any;
|
||||
}
|
||||
|
||||
interface BaseAction {
|
||||
alias?: string;
|
||||
continue_on_error?: boolean;
|
||||
|
@ -5,17 +5,18 @@ import {
|
||||
mdiContentSave,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiFormTextbox,
|
||||
mdiInformationOutline,
|
||||
mdiPlay,
|
||||
mdiTransitConnection,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
@ -38,16 +39,18 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { validateConfig } from "../../../data/config";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import {
|
||||
MODES,
|
||||
MODES_MAX,
|
||||
ScriptConfig,
|
||||
deleteScript,
|
||||
fetchScriptFileConfig,
|
||||
getScriptEditorInitData,
|
||||
getScriptStateConfig,
|
||||
isMaxMode,
|
||||
MODES,
|
||||
MODES_MAX,
|
||||
ScriptConfig,
|
||||
showScriptEditor,
|
||||
triggerScript,
|
||||
} from "../../../data/script";
|
||||
@ -60,8 +63,7 @@ import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "./blueprint-script-editor";
|
||||
import "./manual-script-editor";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { validateConfig } from "../../../data/config";
|
||||
import type { HaManualScriptEditor } from "./manual-script-editor";
|
||||
|
||||
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -92,7 +94,10 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _readOnly = false;
|
||||
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
@query("manual-script-editor")
|
||||
private _manualEditor?: HaManualScriptEditor;
|
||||
|
||||
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||
|
||||
@ -231,6 +236,19 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
|
||||
${!useBlueprint && !("fields" in this._config)
|
||||
? html`
|
||||
<mwc-list-item graphic="icon" @click=${this._addFields}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.add_fields"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFormTextbox}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
`
|
||||
: nothing}
|
||||
${this.scriptId && this.narrow
|
||||
? html`
|
||||
<a href="/config/script/trace/${this.scriptId}">
|
||||
@ -661,6 +679,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _addFields() {
|
||||
if ("fields" in this._config!) {
|
||||
return;
|
||||
}
|
||||
this._manualEditor?.addFields();
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (this._readOnly) {
|
||||
|
359
src/panels/config/script/ha-script-field-row.ts
Normal file
359
src/panels/config/script/ha-script-field-row.ts
Normal file
@ -0,0 +1,359 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiCheck, mdiDelete, mdiDotsVertical } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import { Field } from "../../../data/script";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
const preventDefault = (ev) => ev.preventDefault();
|
||||
|
||||
@customElement("ha-script-field-row")
|
||||
export default class HaScriptFieldRow extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public key!: string;
|
||||
|
||||
@property() public excludeKeys: string[] = [];
|
||||
|
||||
@property() public field!: Field;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() private _uiError?: Record<string, string>;
|
||||
|
||||
@state() private _yamlError?: undefined | "yaml_error" | "key_not_unique";
|
||||
|
||||
@state() private _yamlMode: boolean = false;
|
||||
|
||||
private _errorKey?: string;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(selector: any) =>
|
||||
[
|
||||
{
|
||||
name: "name",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "selector",
|
||||
selector: { object: {} },
|
||||
},
|
||||
{
|
||||
name: "default",
|
||||
selector: selector && typeof selector === "object" ? selector : {},
|
||||
},
|
||||
{
|
||||
name: "required",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const schema = this._schema(this.field.selector);
|
||||
const data = { ...this.field, key: this._errorKey ?? this.key };
|
||||
|
||||
const yamlValue = { [this.key]: this.field };
|
||||
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
<ha-expansion-panel leftChevron>
|
||||
<h3 slot="header">${this.key}</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
fixed
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<mwc-list-item graphic="icon">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
|
||||
${!this._yamlMode
|
||||
? html` <ha-svg-icon
|
||||
class="selected_menu_item"
|
||||
slot="graphic"
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
|
||||
<mwc-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
${this._yamlMode
|
||||
? html`<ha-svg-icon
|
||||
class="selected_menu_item"
|
||||
slot="graphic"
|
||||
.path=${mdiCheck}
|
||||
></ha-svg-icon>`
|
||||
: ``}
|
||||
</mwc-list-item>
|
||||
|
||||
<mwc-list-item
|
||||
class="warning"
|
||||
graphic="icon"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
<div
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
})}
|
||||
>
|
||||
${this._yamlMode
|
||||
? html` ${this._yamlError
|
||||
? html`<ha-alert alert-type="error">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${this._yamlError}`
|
||||
)}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${yamlValue}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>`
|
||||
: html`<ha-form
|
||||
.schema=${schema}
|
||||
.data=${data}
|
||||
.error=${this._uiError}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.computeError=${this._computeError}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>`}
|
||||
</div>
|
||||
</ha-expansion-panel>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._yamlMode = false;
|
||||
break;
|
||||
case 1:
|
||||
this._yamlMode = true;
|
||||
break;
|
||||
case 2:
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _onDelete() {
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.script.editor.field_delete_confirm_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.script.editor.field_delete_confirm_text"
|
||||
),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
confirm: () => {
|
||||
fireEvent(this, "value-changed", { value: null });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _onYamlChange(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
if (typeof value !== "object" || Object.keys(value).length !== 1) {
|
||||
this._yamlError = "yaml_error";
|
||||
return;
|
||||
}
|
||||
const key = Object.keys(value)[0];
|
||||
if (this.excludeKeys.includes(key)) {
|
||||
this._yamlError = "key_not_unique";
|
||||
return;
|
||||
}
|
||||
this._yamlError = undefined;
|
||||
|
||||
const newValue = { ...value[key], key };
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _maybeSetKey(value): void {
|
||||
const nameChanged = value.name !== this.field.name;
|
||||
const keyChanged = value.key !== this.key;
|
||||
if (!nameChanged || keyChanged) {
|
||||
return;
|
||||
}
|
||||
const slugifyName = this.field.name
|
||||
? slugify(this.field.name)
|
||||
: this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field";
|
||||
const regex = new RegExp(`^${slugifyName}(_\\d)?$`);
|
||||
if (regex.test(this.key)) {
|
||||
let key = !value.name
|
||||
? this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field"
|
||||
: slugify(value.name);
|
||||
if (this.excludeKeys.includes(key)) {
|
||||
let uniqueKey = key;
|
||||
let i = 2;
|
||||
do {
|
||||
uniqueKey = `${key}_${i}`;
|
||||
i++;
|
||||
} while (this.excludeKeys.includes(uniqueKey));
|
||||
key = uniqueKey;
|
||||
}
|
||||
value.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
this._maybeSetKey(value);
|
||||
|
||||
// Don't allow to set an empty key, or duplicate an existing key.
|
||||
if (!value.key || this.excludeKeys.includes(value.key)) {
|
||||
this._uiError = value.key
|
||||
? {
|
||||
key: "key_not_unique",
|
||||
}
|
||||
: {
|
||||
key: "key_not_null",
|
||||
};
|
||||
this._errorKey = value.key ?? "";
|
||||
return;
|
||||
}
|
||||
this._errorKey = undefined;
|
||||
this._uiError = undefined;
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
public expand() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
||||
});
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string => {
|
||||
switch (schema.name) {
|
||||
default:
|
||||
return this.hass.localize(
|
||||
`ui.panel.config.script.editor.field.${schema.name}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private _computeError = (error: string) =>
|
||||
this.hass.localize(`ui.panel.config.script.editor.field.${error}` as any) ||
|
||||
error;
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-button-menu,
|
||||
ha-icon-button {
|
||||
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
--expansion-panel-summary-padding: 0 0 0 8px;
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
.action-icon {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 870px) {
|
||||
.action-icon {
|
||||
display: inline-block;
|
||||
color: var(--secondary-text-color);
|
||||
opacity: 0.9;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
.card-content {
|
||||
padding: 16px;
|
||||
}
|
||||
.disabled-bar {
|
||||
background: var(--divider-color, #e0e0e0);
|
||||
text-align: center;
|
||||
border-top-right-radius: var(--ha-card-border-radius);
|
||||
border-top-left-radius: var(--ha-card-border-radius);
|
||||
}
|
||||
|
||||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
.warning ul {
|
||||
margin: 4px 0;
|
||||
}
|
||||
.selected_menu_item {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-script-field-row": HaScriptFieldRow;
|
||||
}
|
||||
}
|
165
src/panels/config/script/ha-script-fields.ts
Normal file
165
src/panels/config/script/ha-script-fields.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { Fields } from "../../../data/script";
|
||||
import { sortableStyles } from "../../../resources/ha-sortable-style";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./ha-script-field-row";
|
||||
import type HaScriptFieldRow from "./ha-script-field-row";
|
||||
|
||||
@customElement("ha-script-fields")
|
||||
export default class HaScriptFields extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() public fields!: Fields;
|
||||
|
||||
private _focusLastActionOnChange = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.fields
|
||||
? html`<div class="fields">
|
||||
${Object.entries(this.fields).map(
|
||||
([key, field]) => html`
|
||||
<ha-script-field-row
|
||||
.key=${key}
|
||||
.excludeKeys=${Object.keys(this.fields).filter(
|
||||
(k) => k !== key
|
||||
)}
|
||||
.field=${field}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._fieldChanged}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
</ha-script-field-row>
|
||||
`
|
||||
)}
|
||||
</div> `
|
||||
: nothing}
|
||||
<ha-button
|
||||
outlined
|
||||
@click=${this._addField}
|
||||
.disabled=${this.disabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.add_field"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||
</ha-button>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("fields") && this._focusLastActionOnChange) {
|
||||
this._focusLastActionOnChange = false;
|
||||
this.focusLastField();
|
||||
}
|
||||
}
|
||||
|
||||
public focusLastField() {
|
||||
const row = this.shadowRoot!.querySelector<HaScriptFieldRow>(
|
||||
"ha-script-field-row:last-of-type"
|
||||
)!;
|
||||
row.updateComplete.then(() => {
|
||||
row.expand();
|
||||
row.scrollIntoView();
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private _addField() {
|
||||
const key = this._getUniqueKey(
|
||||
this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field",
|
||||
this.fields || {}
|
||||
);
|
||||
const fields = {
|
||||
...(this.fields || {}),
|
||||
[key]: {
|
||||
selector: {
|
||||
text: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
this._focusLastActionOnChange = true;
|
||||
fireEvent(this, "value-changed", { value: fields });
|
||||
}
|
||||
|
||||
private _fieldChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const key = (ev.target as any).key;
|
||||
let fields: Fields = {};
|
||||
if (ev.detail.value === null) {
|
||||
fields = { ...this.fields };
|
||||
delete fields[key];
|
||||
} else {
|
||||
const newValue = { ...ev.detail.value };
|
||||
const newKey = newValue.key;
|
||||
delete newValue.key;
|
||||
const keyChanged = key !== newKey;
|
||||
|
||||
// If key is changed, recreate the object to maintain the same insertion order.
|
||||
if (keyChanged) {
|
||||
Object.entries(this.fields).forEach(([k, v]) => {
|
||||
if (k === key) {
|
||||
fields[newKey] = newValue;
|
||||
} else fields[k] = v;
|
||||
});
|
||||
} else {
|
||||
fields = { ...this.fields };
|
||||
fields[key] = newValue;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "value-changed", { value: fields });
|
||||
}
|
||||
|
||||
private _getUniqueKey(base: string, fields: Fields): string {
|
||||
let key = base;
|
||||
if (base in fields) {
|
||||
let i = 2;
|
||||
do {
|
||||
key = `${base}_${i}`;
|
||||
i++;
|
||||
} while (key in fields);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
sortableStyles,
|
||||
css`
|
||||
ha-script-field-row {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
height: 20px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-script-fields": HaScriptFields;
|
||||
}
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { Action, ScriptConfig } from "../../../data/script";
|
||||
import { Action, Fields, ScriptConfig } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../automation/action/ha-automation-action";
|
||||
import "./ha-script-fields";
|
||||
import type HaScriptFields from "./ha-script-fields";
|
||||
|
||||
@customElement("manual-script-editor")
|
||||
export class HaManualScriptEditor extends LitElement {
|
||||
@ -23,6 +25,37 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public config!: ScriptConfig;
|
||||
|
||||
@query("ha-script-fields")
|
||||
private _scriptFields?: HaScriptFields;
|
||||
|
||||
private _openFields = false;
|
||||
|
||||
public addFields() {
|
||||
this._openFields = true;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
fields: {
|
||||
[this.hass.localize("ui.panel.config.script.editor.field.field") ||
|
||||
"field"]: {
|
||||
selector: {
|
||||
text: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
if (this._openFields && changedProps.has("config")) {
|
||||
this._openFields = false;
|
||||
this._scriptFields?.updateComplete.then(
|
||||
() => this._scriptFields?.focusLastField()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.disabled
|
||||
@ -33,6 +66,40 @@ export class HaManualScriptEditor extends LitElement {
|
||||
</mwc-button>
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${this.config.fields
|
||||
? html`<div class="header">
|
||||
<h2 id="fields-heading" class="name">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.fields"
|
||||
)}
|
||||
</h2>
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/integrations/script/#fields"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<ha-icon-button
|
||||
.path=${mdiHelpCircle}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.script.editor.field.link_help_fields"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ha-script-fields
|
||||
role="region"
|
||||
aria-labelledby="fields-heading"
|
||||
.fields=${this.config.fields}
|
||||
@value-changed=${this._fieldsChanged}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
></ha-script-fields>`
|
||||
: nothing}
|
||||
|
||||
<div class="header">
|
||||
<h2 id="sequence-heading" class="name">
|
||||
${this.hass.localize("ui.panel.config.script.editor.sequence")}
|
||||
@ -63,6 +130,13 @@ export class HaManualScriptEditor extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _fieldsChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.config!, fields: ev.detail.value as Fields },
|
||||
});
|
||||
}
|
||||
|
||||
private _sequenceChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
|
@ -311,7 +311,8 @@
|
||||
"successfully_deleted": "Successfully deleted",
|
||||
"error_required": "Required",
|
||||
"copied": "Copied",
|
||||
"copied_clipboard": "Copied to clipboard"
|
||||
"copied_clipboard": "Copied to clipboard",
|
||||
"name": "Name"
|
||||
},
|
||||
"components": {
|
||||
"selectors": {
|
||||
@ -3006,6 +3007,24 @@
|
||||
"unavailable": "Script is unavailable",
|
||||
"migrate": "Migrate",
|
||||
"duplicate": "[%key:ui::common::duplicate%]",
|
||||
"field": {
|
||||
"name": "[%key:ui::common::name%]",
|
||||
"key": "Field variable key name",
|
||||
"description": "Description",
|
||||
"required": "Required",
|
||||
"default": "Default",
|
||||
"selector": "Selector",
|
||||
"yaml_error": "Field yaml has invalid format.",
|
||||
"key_not_null": "The field key must not be empty.",
|
||||
"key_not_unique": "The field key must not be the same value as another field.",
|
||||
"fields": "Fields",
|
||||
"link_help_fields": "Learn more about fields.",
|
||||
"add_fields": "Add fields",
|
||||
"add_field": "Add field",
|
||||
"field": "field"
|
||||
},
|
||||
"field_delete_confirm_title": "Delete field?",
|
||||
"field_delete_confirm_text": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm_text%]",
|
||||
"header": "Script: {name}",
|
||||
"default_name": "New Script",
|
||||
"modes": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user