Script editor updates (match automation editor) (#20791)

* Show descriptions in script editor

* Script editor updates (match automation editor)

* review feedback
This commit is contained in:
karwosts 2024-05-21 10:01:51 -07:00 committed by GitHub
parent d9bac06806
commit 9b28c7cf69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 371 additions and 489 deletions

View File

@ -138,7 +138,7 @@ class DialogAutomationMode extends LitElement implements HassDialog {
}
private _save(): void {
this._params.updateAutomation({
this._params.updateConfig({
...this._params.config,
mode: this._newMode,
max: this._newMax,

View File

@ -1,18 +1,25 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { AutomationConfig } from "../../../../data/automation";
import type { ScriptConfig } from "../../../../data/script";
export const loadAutomationModeDialog = () =>
import("./dialog-automation-mode");
export interface AutomationModeDialog {
config: AutomationConfig;
updateAutomation: (config: AutomationConfig) => void;
updateConfig: (config: AutomationConfig) => void;
onClose: () => void;
}
export interface ScriptModeDialog {
config: ScriptConfig;
updateConfig: (config: ScriptConfig) => void;
onClose: () => void;
}
export const showAutomationModeDialog = (
element: HTMLElement,
dialogParams: AutomationModeDialog
dialogParams: AutomationModeDialog | ScriptModeDialog
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-dialog-automation-mode",

View File

@ -124,7 +124,7 @@ class DialogAutomationRename extends LitElement implements HassDialog {
this._error = "Name is required";
return;
}
this._params.updateAutomation({
this._params.updateConfig({
...this._params.config,
alias: this._newName,
description: this._newDescription,

View File

@ -1,18 +1,25 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { AutomationConfig } from "../../../../data/automation";
import type { ScriptConfig } from "../../../../data/script";
export const loadAutomationRenameDialog = () =>
import("./dialog-automation-rename");
export interface AutomationRenameDialog {
config: AutomationConfig;
updateAutomation: (config: AutomationConfig) => void;
updateConfig: (config: AutomationConfig) => void;
onClose: () => void;
}
export interface ScriptRenameDialog {
config: ScriptConfig;
updateConfig: (config: ScriptConfig) => void;
onClose: () => void;
}
export const showAutomationRenameDialog = (
element: HTMLElement,
dialogParams: AutomationRenameDialog
dialogParams: AutomationRenameDialog | ScriptRenameDialog
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-dialog-automation-rename",

View File

@ -28,6 +28,7 @@ import { property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button-menu";
import "../../../components/ha-fab";
@ -117,22 +118,24 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
private _configSubscriptionsId = 1;
protected render(): TemplateResult {
protected render(): TemplateResult | typeof nothing {
if (!this._config) {
return nothing;
}
const stateObj = this._entityId
? this.hass.states[this._entityId]
: undefined;
const useBlueprint = "use_blueprint" in this._config;
return html`
<hass-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.backCallback=${this._backTapped}
.header=${!this._config
? ""
: this._config.alias ||
this.hass.localize(
"ui.panel.config.automation.editor.default_name"
)}
.header=${this._config.alias ||
this.hass.localize("ui.panel.config.automation.editor.default_name")}
>
${this._config?.id && !this.narrow
? html`
@ -171,7 +174,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</ha-list-item>
${stateObj && this._config && this.narrow
${stateObj && this.narrow
? html`<a
href="/config/automation/trace/${encodeURIComponent(
this._config.id!
@ -187,7 +190,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon>
</ha-list-item>
</a>`
: ""}
: nothing}
<ha-list-item
graphic="icon"
@ -197,8 +200,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
${this.hass.localize("ui.panel.config.automation.editor.rename")}
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</ha-list-item>
${this._config && !("use_blueprint" in this._config)
${!useBlueprint
? html`
<ha-list-item
graphic="icon"
@ -214,7 +216,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
></ha-svg-icon>
</ha-list-item>
`
: ""}
: nothing}
<ha-list-item
.disabled=${!this._readOnly && !this.automationId}
@ -288,35 +290,38 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
</ha-svg-icon>
</ha-list-item>
</ha-button-menu>
${this._config
? html`
<div
class="content ${classMap({
"yaml-mode": this._mode === "yaml",
})}"
@subscribe-automation-config=${this._subscribeAutomationConfig}
<div
class="content ${classMap({
"yaml-mode": this._mode === "yaml",
})}"
@subscribe-automation-config=${this._subscribeAutomationConfig}
>
${this._errors || stateObj?.state === UNAVAILABLE
? html`<ha-alert
alert-type="error"
.title=${stateObj?.state === UNAVAILABLE
? this.hass.localize(
"ui.panel.config.automation.editor.unavailable"
)
: undefined}
>
${this._errors || stateObj?.state === UNAVAILABLE
? html`<ha-alert
alert-type="error"
.title=${stateObj?.state === UNAVAILABLE
? this.hass.localize(
"ui.panel.config.automation.editor.unavailable"
)
: undefined}
>
${this._errors || this._validationErrors}
${stateObj?.state === UNAVAILABLE
? html`<ha-svg-icon
slot="icon"
.path=${mdiRobotConfused}
></ha-svg-icon>`
: nothing}
</ha-alert>`
: ""}
${this._mode === "gui"
? "use_blueprint" in this._config
${this._errors || this._validationErrors}
${stateObj?.state === UNAVAILABLE
? html`<ha-svg-icon
slot="icon"
.path=${mdiRobotConfused}
></ha-svg-icon>`
: nothing}
</ha-alert>`
: ""}
${this._mode === "gui"
? html`
<div
class=${classMap({
rtl: computeRTL(this.hass),
})}
>
${useBlueprint
? html`
<blueprint-automation-editor
.hass=${this.hass}
@ -340,51 +345,45 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
@value-changed=${this._valueChanged}
@duplicate=${this._duplicate}
></manual-automation-editor>
`
: this._mode === "yaml"
? html` ${this._readOnly
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.read_only"
)}
<mwc-button
slot="action"
@click=${this._duplicate}
>
${this.hass.localize(
"ui.panel.config.automation.editor.migrate"
)}
</mwc-button>
</ha-alert>`
: ""}
${stateObj?.state === "off"
? html`
<ha-alert alert-type="info">
${this.hass.localize(
"ui.panel.config.automation.editor.disabled"
)}
<mwc-button
slot="action"
@click=${this._toggle}
>
${this.hass.localize(
"ui.panel.config.automation.editor.enable"
)}
</mwc-button>
</ha-alert>
`
: ""}
<ha-yaml-editor
copyClipboard
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>`
`}
</div>
`
: this._mode === "yaml"
? html` ${this._readOnly
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.read_only"
)}
<mwc-button slot="action" @click=${this._duplicate}>
${this.hass.localize(
"ui.panel.config.automation.editor.migrate"
)}
</mwc-button>
</ha-alert>`
: nothing}
</div>
`
: ""}
${stateObj?.state === "off"
? html`
<ha-alert alert-type="info">
${this.hass.localize(
"ui.panel.config.automation.editor.disabled"
)}
<mwc-button slot="action" @click=${this._toggle}>
${this.hass.localize(
"ui.panel.config.automation.editor.enable"
)}
</mwc-button>
</ha-alert>
`
: ""}
<ha-yaml-editor
copyClipboard
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>`
: nothing}
</div>
<ha-fab
slot="fab"
class=${classMap({ dirty: this._dirty })}
@ -689,7 +688,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
return new Promise((resolve) => {
showAutomationRenameDialog(this, {
config: this._config!,
updateAutomation: (config) => {
updateConfig: (config) => {
this._config = config;
this._dirty = true;
this.requestUpdate();
@ -704,7 +703,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
return new Promise((resolve) => {
showAutomationModeDialog(this, {
config: this._config!,
updateAutomation: (config) => {
updateConfig: (config) => {
this._config = config;
this._dirty = true;
this.requestUpdate();

View File

@ -1,13 +1,16 @@
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-button";
import {
mdiCheck,
mdiContentDuplicate,
mdiContentSave,
mdiDebugStepOver,
mdiDelete,
mdiDotsVertical,
mdiFormTextbox,
mdiInformationOutline,
mdiPlay,
mdiRenameBox,
mdiRobotConfused,
mdiTransitConnection,
} from "@mdi/js";
import {
@ -21,35 +24,28 @@ import {
} from "lit";
import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import { computeRTL } from "../../../common/util/compute_rtl";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import type {
HaFormDataContainer,
SchemaUnion,
} from "../../../components/ha-form/types";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import "../../../components/ha-list-item";
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,
hasScriptFields,
isMaxMode,
showScriptEditor,
triggerScript,
} from "../../../data/script";
@ -58,8 +54,9 @@ import "../../../layouts/hass-subpage";
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
import { haStyle } from "../../../resources/styles";
import type { Entries, HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast";
import { showAutomationModeDialog } from "../automation/automation-mode-dialog/show-dialog-automation-mode";
import { showAutomationRenameDialog } from "../automation/automation-rename-dialog/show-dialog-automation-rename";
import "./blueprint-script-editor";
import "./manual-script-editor";
import type { HaManualScriptEditor } from "./manual-script-editor";
@ -72,24 +69,24 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property() public entityId: string | null = null;
@property({ attribute: false }) public route!: Route;
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@property({ type: Boolean }) public isWide = false;
@property({ type: Boolean }) public narrow = false;
@property({ attribute: false }) public entityRegistry!: EntityRegistryEntry[];
@property({ attribute: false }) public route!: Route;
@state() private _config?: ScriptConfig;
@state() private _entityId?: string;
@state() private _idError = false;
@state() private _dirty = false;
@state() private _errors?: string;
@state() private _entityId?: string;
@state() private _mode: "gui" | "yaml" = "gui";
@state() private _readOnly = false;
@ -99,72 +96,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@state() private _validationErrors?: (string | TemplateResult)[];
private _schema = memoizeOne(
(
hasID: boolean,
useBluePrint?: boolean,
currentMode?: (typeof MODES)[number]
) =>
[
{
name: "alias",
selector: {
text: {
type: "text",
},
},
},
{
name: "icon",
selector: {
icon: {},
},
},
...(!hasID
? ([
{
name: "id",
selector: {
text: {
prefix: "script.",
},
},
},
] as const)
: []),
...(!useBluePrint
? ([
{
name: "mode",
selector: {
select: {
mode: "dropdown",
options: MODES.map((mode) => ({
label: this.hass.localize(
`ui.panel.config.script.editor.modes.${mode}`
),
value: mode,
})),
},
},
},
] as const)
: []),
...(currentMode && isMaxMode(currentMode)
? ([
{
name: "max",
required: true,
selector: {
number: { mode: "box", min: 1, max: Infinity },
},
},
] as const)
: []),
] as const
);
protected render() {
protected render(): TemplateResult | typeof nothing {
if (!this._config) {
return nothing;
}
@ -174,28 +106,13 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
: undefined;
const useBlueprint = "use_blueprint" in this._config;
const schema = this._schema(
!!this.scriptId,
useBlueprint,
this._config.mode
);
const data = {
...(!this._config.mode && !useBlueprint && { mode: MODES[0] }),
icon: undefined,
max: this._config.mode && isMaxMode(this._config.mode) ? 10 : undefined,
...this._config,
id: this._entityId,
};
return html`
<hass-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
.backCallback=${this._backTapped}
.header=${!this._config?.alias ? "" : this._config.alias}
.header=${!this._config.alias ? "" : this._config.alias}
>
${this.scriptId && !this.narrow
? html`
@ -213,7 +130,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item
<ha-list-item
graphic="icon"
.disabled=${!this.scriptId}
@click=${this._showInfo}
@ -223,34 +140,21 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
<mwc-list-item
<ha-list-item
graphic="icon"
.disabled=${!this.scriptId}
@click=${this._runScript}
>
${this.hass.localize("ui.panel.config.script.picker.run_script")}
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
</mwc-list-item>
</ha-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}">
<mwc-list-item graphic="icon">
<ha-list-item graphic="icon">
${this.hass.localize(
"ui.panel.config.script.editor.show_trace"
)}
@ -258,41 +162,57 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiTransitConnection}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
</a>
`
: ""}
<li divider role="separator"></li>
<mwc-list-item graphic="icon" @click=${this._switchUiMode}>
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
${this._mode === "gui"
? html`
: nothing}
${!useBlueprint && !("fields" in this._config)
? html`
<ha-list-item
graphic="icon"
.disabled=${this._readOnly || this._mode === "yaml"}
@click=${this._addFields}
>
${this.hass.localize(
"ui.panel.config.script.editor.field.add_fields"
)}
<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
.path=${mdiFormTextbox}
></ha-svg-icon>
`
: ``}
</mwc-list-item>
<mwc-list-item graphic="icon" @click=${this._switchYamlMode}>
${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")}
${this._mode === "yaml"
? html`
</ha-list-item>
`
: nothing}
<ha-list-item
graphic="icon"
@click=${this._promptScriptAlias}
.disabled=${!this.scriptId ||
this._readOnly ||
this._mode === "yaml"}
>
${this.hass.localize("ui.panel.config.script.editor.rename")}
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
</ha-list-item>
${!useBlueprint
? html`
<ha-list-item
graphic="icon"
@click=${this._promptScriptMode}
.disabled=${this._readOnly || this._mode === "yaml"}
>
${this.hass.localize(
"ui.panel.config.script.editor.change_mode"
)}
<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
.path=${mdiDebugStepOver}
></ha-svg-icon>
`
: ``}
</mwc-list-item>
</ha-list-item>
`
: nothing}
<li divider role="separator"></li>
<mwc-list-item
<ha-list-item
.disabled=${!this._readOnly && !this.scriptId}
graphic="icon"
@click=${this._duplicate}
@ -306,9 +226,34 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
slot="graphic"
.path=${mdiContentDuplicate}
></ha-svg-icon>
</mwc-list-item>
</ha-list-item>
<mwc-list-item
<li divider role="separator"></li>
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
${this.hass.localize("ui.panel.config.automation.editor.edit_ui")}
${this._mode === "gui"
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon> `
: ``}
</ha-list-item>
<ha-list-item graphic="icon" @click=${this._switchYamlMode}>
${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")}
${this._mode === "yaml"
? html`<ha-svg-icon
class="selected_menu_item"
slot="graphic"
.path=${mdiCheck}
></ha-svg-icon>`
: ``}
</ha-list-item>
<li divider role="separator"></li>
<ha-list-item
.disabled=${this._readOnly || !this.scriptId}
class=${classMap({ warning: Boolean(this.scriptId) })}
graphic="icon"
@ -321,7 +266,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
.path=${mdiDelete}
>
</ha-svg-icon>
</mwc-list-item>
</ha-list-item>
</ha-button-menu>
<div
class="content ${classMap({
@ -329,25 +274,21 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
})}"
>
${this._errors || stateObj?.state === UNAVAILABLE
? html`
<ha-alert
alert-type="error"
.title=${stateObj?.state === UNAVAILABLE
? this.hass.localize(
"ui.panel.config.script.editor.unavailable"
)
: undefined}
>
${this._errors || this._validationErrors}
</ha-alert>
`
: ""}
${this._readOnly
? html`<ha-alert alert-type="warning">
${this.hass.localize("ui.panel.config.script.editor.read_only")}
<mwc-button slot="action" @click=${this._duplicate}>
${this.hass.localize("ui.panel.config.script.editor.migrate")}
</mwc-button>
? html`<ha-alert
alert-type="error"
.title=${stateObj?.state === UNAVAILABLE
? this.hass.localize(
"ui.panel.config.script.editor.unavailable"
)
: undefined}
>
${this._errors || this._validationErrors}
${stateObj?.state === UNAVAILABLE
? html`<ha-svg-icon
slot="icon"
.path=${mdiRobotConfused}
></ha-svg-icon>`
: nothing}
</ha-alert>`
: ""}
${this._mode === "gui"
@ -357,71 +298,63 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
rtl: computeRTL(this.hass),
})}
>
${this._config
${useBlueprint
? html`
<div class="config-container">
<ha-card outlined>
<div class="card-content">
<ha-form
.schema=${schema}
.data=${data}
.hass=${this.hass}
.disabled=${this._readOnly}
.computeLabel=${this._computeLabelCallback}
.computeHelper=${this._computeHelperCallback}
@value-changed=${this._valueChanged}
></ha-form>
</div>
</ha-card>
</div>
${useBlueprint
? html`
<blueprint-script-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.config=${this._config}
.disabled=${this._readOnly}
@duplicate=${this._duplicate}
@value-changed=${this._configChanged}
></blueprint-script-editor>
`
: html`
<manual-script-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.config=${this._config}
.disabled=${this._readOnly}
@duplicate=${this._duplicate}
@value-changed=${this._configChanged}
></manual-script-editor>
`}
<blueprint-script-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.config=${this._config}
.disabled=${this._readOnly}
@value-changed=${this._valueChanged}
@duplicate=${this._duplicate}
></blueprint-script-editor>
`
: ""}
: html`
<manual-script-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.config=${this._config}
.disabled=${this._readOnly}
@value-changed=${this._valueChanged}
@duplicate=${this._duplicate}
></manual-script-editor>
`}
</div>
`
: this._mode === "yaml"
? html` <ha-yaml-editor
copyClipboard
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>`
? html` ${this._readOnly
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.panel.config.script.editor.read_only"
)}
<mwc-button slot="action" @click=${this._duplicate}>
${this.hass.localize(
"ui.panel.config.script.editor.migrate"
)}
</mwc-button>
</ha-alert>`
: nothing}
<ha-yaml-editor
copyClipboard
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>`
: nothing}
</div>
<ha-fab
slot="fab"
class=${classMap({
dirty: this._dirty,
})}
.label=${this.hass.localize(
"ui.panel.config.script.editor.save_script"
)}
extended
@click=${this._saveScript}
class=${classMap({
dirty: this._dirty,
})}
>
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
</ha-fab>
@ -441,42 +374,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
// Only refresh config if we picked a new script. If same ID, don't fetch it.
(!oldScript || oldScript !== this.scriptId)
) {
fetchScriptFileConfig(this.hass, this.scriptId).then(
(config) => {
this._dirty = false;
this._readOnly = false;
this._config = this._normalizeConfig(config);
const entity = this.entityRegistry.find(
(ent) =>
ent.platform === "script" && ent.unique_id === this.scriptId
);
this._entityId = entity?.entity_id;
this._checkValidation();
},
(resp) => {
const entity = this.entityRegistry.find(
(ent) =>
ent.platform === "script" && ent.unique_id === this.scriptId
);
if (entity) {
navigate(`/config/script/show/${entity.entity_id}`, {
replace: true,
});
return;
}
alert(
resp.status_code === 404
? this.hass.localize(
"ui.panel.config.script.editor.load_error_not_editable"
)
: this.hass.localize(
"ui.panel.config.script.editor.load_error_unknown",
{ err_no: resp.status_code || resp.code }
)
);
history.back();
}
);
this._loadConfig();
}
if (changedProps.has("scriptId") && !this.scriptId && this.hass) {
@ -493,6 +391,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
...initData,
} as ScriptConfig;
this._readOnly = false;
this._dirty = true;
}
if (changedProps.has("entityId") && this.entityId) {
@ -512,14 +411,13 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
}
private _normalizeConfig(config: ScriptConfig): ScriptConfig {
// Normalize data: ensure sequence is a list
// Happens when people copy paste their scripts into the config
const value = config.sequence;
if (value && !Array.isArray(value)) {
config.sequence = [value];
private _setEntityId(id?: string) {
this._entityId = id;
if (this.hass.states[`script.${this._entityId}`]) {
this._idError = true;
} else {
this._idError = false;
}
return config;
}
private async _checkValidation() {
@ -546,69 +444,57 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
);
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>,
data: HaFormDataContainer
): string => {
switch (schema.name) {
case "mode":
return this.hass.localize("ui.panel.config.script.editor.modes.label");
case "max":
// Mode must be one of max modes per schema definition above
return this.hass.localize(
`ui.panel.config.script.editor.max.${
data.mode as (typeof MODES_MAX)[number]
}`
);
default:
return this.hass.localize(
`ui.panel.config.script.editor.${schema.name}`
);
private _normalizeConfig(config: ScriptConfig): ScriptConfig {
// Normalize data: ensure sequence is a list
// Happens when people copy paste their scripts into the config
const value = config.sequence;
if (value && !Array.isArray(value)) {
config.sequence = [value];
}
};
private _computeHelperCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string | undefined | TemplateResult => {
if (schema.name === "mode") {
return html`
<a
style="color: var(--secondary-text-color)"
href=${documentationUrl(
this.hass,
"/integrations/script/#script-modes"
)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.script.editor.modes.learn_more"
)}</a
>
`;
}
return undefined;
};
private async _showInfo() {
if (!this.scriptId) {
return;
}
const entity = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
);
if (!entity) {
return;
}
fireEvent(this, "hass-more-info", { entityId: entity.entity_id });
return config;
}
private async _showTrace() {
if (this.scriptId) {
const result = await this.confirmUnsavedChanged();
if (result) {
navigate(`/config/script/trace/${this.scriptId}`);
private async _loadConfig() {
fetchScriptFileConfig(this.hass, this.scriptId!).then(
(config) => {
this._dirty = false;
this._readOnly = false;
this._config = this._normalizeConfig(config);
const entity = this.entityRegistry.find(
(ent) => ent.platform === "script" && ent.unique_id === this.scriptId
);
this._entityId = entity?.entity_id;
this._checkValidation();
},
(resp) => {
const entity = this.entityRegistry.find(
(ent) => ent.platform === "script" && ent.unique_id === this.scriptId
);
if (entity) {
navigate(`/config/script/show/${entity.entity_id}`, {
replace: true,
});
return;
}
alert(
resp.status_code === 404
? this.hass.localize(
"ui.panel.config.script.editor.load_error_not_editable"
)
: this.hass.localize(
"ui.panel.config.script.editor.load_error_unknown",
{ err_no: resp.status_code || resp.code }
)
);
history.back();
}
}
);
}
private _valueChanged(ev) {
this._config = ev.detail.value;
this._errors = undefined;
this._dirty = true;
}
private async _runScript(ev: CustomEvent) {
@ -633,42 +519,39 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
const aliasSlugify = slugify(alias);
let id = aliasSlugify;
let i = 2;
while (this.hass.states[`script.${id}`]) {
while (this._idIsUsed(id)) {
id = `${aliasSlugify}_${i}`;
i++;
}
return id;
}
private _setEntityId(id?: string) {
this._entityId = id;
if (this.hass.states[`script.${this._entityId}`]) {
this._idError = true;
} else {
this._idError = false;
}
private _idIsUsed(id: string): boolean {
return (
`script.${id}` in this.hass.states ||
this.entityRegistry.some((ent) => ent.unique_id === id)
);
}
private updateEntityId(
newId: string | undefined,
newAlias: string | undefined
) {
const currentAlias = this._config?.alias ?? "";
const currentEntityId = this._entityId ?? "";
if (newId !== this._entityId) {
this._setEntityId(newId || undefined);
private async _showInfo() {
if (!this.scriptId) {
return;
}
const entity = this.entityRegistry.find(
(entry) => entry.unique_id === this.scriptId
);
if (!entity) {
return;
}
fireEvent(this, "hass-more-info", { entityId: entity.entity_id });
}
const currentComputedEntity = this._computeEntityIdFromAlias(currentAlias);
if (currentComputedEntity === currentEntityId || !this._entityId) {
const newComputedId = newAlias
? this._computeEntityIdFromAlias(newAlias)
: undefined;
this._setEntityId(newComputedId);
private async _showTrace() {
if (this.scriptId) {
const result = await this.confirmUnsavedChanged();
if (result) {
navigate(`/config/script/trace/${this.scriptId}`);
}
}
}
@ -680,53 +563,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._dirty = true;
}
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
if (this._readOnly) {
return;
}
this._errors = undefined;
const values = ev.detail.value as any;
let changed = false;
const newValues: Omit<ScriptConfig, "sequence"> = {
alias: values.alias ?? "",
icon: values.icon,
mode: values.mode,
max: isMaxMode(values.mode) ? values.max : undefined,
};
if (!this.scriptId) {
this.updateEntityId(values.id, values.alias);
}
for (const key of Object.keys(newValues)) {
const value = newValues[key];
if (value === this._config![key]) {
continue;
}
if (value === undefined) {
const newConfig = { ...this._config! };
delete newConfig![key];
this._config = newConfig;
} else {
this._config = { ...this._config!, [key]: value };
}
changed = true;
}
if (changed) {
this._dirty = true;
}
}
private _configChanged(ev) {
this._config = ev.detail.value;
this._errors = undefined;
this._dirty = true;
}
private _preprocessYaml() {
return this._config;
}
@ -795,9 +631,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
{ name: this._config?.alias }
),
confirmText: this.hass!.localize("ui.common.delete"),
destructive: true,
dismissText: this.hass!.localize("ui.common.cancel"),
confirm: () => this._delete(),
destructive: true,
});
}
@ -814,6 +650,36 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._mode = "yaml";
}
private async _promptScriptAlias(): Promise<boolean> {
return new Promise((resolve) => {
showAutomationRenameDialog(this, {
config: this._config!,
updateConfig: (config) => {
this._config = config;
this._dirty = true;
this.requestUpdate();
resolve(true);
},
onClose: () => resolve(false),
});
});
}
private async _promptScriptMode(): Promise<void> {
return new Promise((resolve) => {
showAutomationModeDialog(this, {
config: this._config!,
updateConfig: (config) => {
this._config = config;
this._dirty = true;
this.requestUpdate();
resolve();
},
onClose: () => resolve(),
});
});
}
private async _saveScript(): Promise<void> {
if (this._idError) {
showToast(this, {
@ -830,7 +696,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
return;
}
if (!this.scriptId) {
const saved = await this._promptScriptAlias();
if (!saved) {
return;
}
const entityId = this._computeEntityIdFromAlias(this._config!.alias);
this._setEntityId(entityId);
}
const id = this.scriptId || this._entityId || Date.now();
try {
await this.hass!.callApi(
"POST",
@ -860,9 +735,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
return [
haStyle,
css`
ha-card {
overflow: hidden;
}
p {
margin-bottom: 0;
}
@ -896,11 +768,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
display: flex;
flex-direction: column;
}
.yaml-mode ha-card {
overflow: initial;
--ha-card-border-radius: 0;
border-bottom: 1px solid var(--divider-color);
}
span[slot="introduction"] a {
color: var(--primary-color);
}

View File

@ -3622,6 +3622,8 @@
"introduction": "Use scripts to run a sequence of actions.",
"show_trace": "[%key:ui::panel::config::automation::editor::show_trace%]",
"show_info": "[%key:ui::panel::config::automation::editor::show_info%]",
"rename": "[%key:ui::panel::config::automation::editor::triggers::rename%]",
"change_mode": "[%key:ui::panel::config::automation::editor::change_mode%]",
"read_only": "This script cannot be edited from the UI, because it is not stored in the ''scripts.yaml'' file.",
"unavailable": "Script is unavailable",
"migrate": "Migrate",