diff --git a/src/common/string/slugify.ts b/src/common/string/slugify.ts
index 05c6f3352c..a5f0c134cb 100644
--- a/src/common/string/slugify.ts
+++ b/src/common/string/slugify.ts
@@ -1,19 +1,19 @@
// https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1
-export const slugify = (value: string) => {
+export const slugify = (value: string, delimiter = "-") => {
const a =
"àáäâãåăæąçćčđďèéěėëêęğǵḧìíïîįłḿǹńňñòóöôœøṕŕřßşśšșťțùúüûǘůűūųẃẍÿýźžż·/_,:;";
- const b =
- "aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz------";
+ const b = `aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}${delimiter}`;
const p = new RegExp(a.split("").join("|"), "g");
return value
.toString()
.toLowerCase()
- .replace(/\s+/g, "-") // Replace spaces with -
+ .replace(/\s+/g, delimiter) // Replace spaces with delimiter
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
- .replace(/&/g, "-and-") // Replace & with 'and'
+ .replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
.replace(/[^\w-]+/g, "") // Remove all non-word characters
- .replace(/--+/g, "-") // Replace multiple - with single -
- .replace(/^-+/, "") // Trim - from start of text
- .replace(/-+$/, ""); // Trim - from end of text
+ .replace(/-/, delimiter) // Replace - with delimiter
+ .replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single delimiter
+ .replace(new RegExp(`/^${delimiter}+/`), "") // Trim delimiter from start of text
+ .replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
};
diff --git a/src/data/script.ts b/src/data/script.ts
index 81f8292f24..2a2cf1912c 100644
--- a/src/data/script.ts
+++ b/src/data/script.ts
@@ -22,6 +22,7 @@ export interface ScriptEntity extends HassEntityBase {
export interface ScriptConfig {
alias: string;
sequence: Action[];
+ icon?: string;
mode?: "single" | "restart" | "queued" | "parallel";
max?: number;
}
diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts
index 68f334383a..4617f1a01b 100644
--- a/src/panels/config/script/ha-script-editor.ts
+++ b/src/panels/config/script/ha-script-editor.ts
@@ -10,12 +10,14 @@ import {
property,
PropertyValues,
TemplateResult,
+ internalProperty,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { computeObjectId } from "../../../common/entity/compute_object_id";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-card";
+import "../../../components/ha-icon-input";
import "@material/mwc-fab";
import {
Action,
@@ -36,6 +38,7 @@ import { configSections } from "../ha-panel-config";
import "../../../components/ha-svg-icon";
import { mdiContentSave } from "@mdi/js";
import { PaperListboxElement } from "@polymer/paper-listbox";
+import { slugify } from "../../../common/string/slugify";
export class HaScriptEditor extends LitElement {
@property() public hass!: HomeAssistant;
@@ -48,11 +51,15 @@ export class HaScriptEditor extends LitElement {
@property() public narrow!: boolean;
- @property() private _config?: ScriptConfig;
+ @internalProperty() private _config?: ScriptConfig;
- @property() private _dirty?: boolean;
+ @internalProperty() private _entityId?: string;
- @property() private _errors?: string;
+ @internalProperty() private _idError = false;
+
+ @internalProperty() private _dirty?: boolean;
+
+ @internalProperty() private _errors?: string;
protected render(): TemplateResult {
return html`
@@ -107,9 +114,32 @@ export class HaScriptEditor extends LitElement {
name="alias"
.value=${this._config.alias}
@value-changed=${this._valueChanged}
+ @change=${this._aliasChanged}
>
-
+
${this.hass.localize(
"ui.panel.config.script.editor.modes.description",
@@ -283,6 +313,30 @@ export class HaScriptEditor extends LitElement {
this._dirty = true;
}
+ private _aliasChanged(ev: CustomEvent) {
+ if (this.scriptEntityId || this._entityId) {
+ return;
+ }
+ const aliasSlugify = slugify((ev.target as any).value, "_");
+ let id = aliasSlugify;
+ let i = 2;
+ while (this.hass.states[`script.${id}`]) {
+ id = `${aliasSlugify}_${i}`;
+ i++;
+ }
+ this._entityId = id;
+ }
+
+ private _idChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ this._entityId = (ev.target as any).value;
+ if (this.hass.states[`script.${this._entityId}`]) {
+ this._idError = true;
+ } else {
+ this._idError = false;
+ }
+ }
+
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
const target = ev.target as any;
@@ -337,9 +391,15 @@ export class HaScriptEditor extends LitElement {
}
private _saveScript(): void {
+ if (this._idError) {
+ this._errors = this.hass.localize(
+ "ui.panel.config.script.editor.id_already_exists_save_error"
+ );
+ return;
+ }
const id = this.scriptEntityId
? computeObjectId(this.scriptEntityId)
- : Date.now();
+ : this._entityId || Date.now();
this.hass!.callApi("POST", "config/script/config/" + id, this._config).then(
() => {
this._dirty = false;
diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts
index 8fba4004ba..7135a80db0 100644
--- a/src/panels/config/script/ha-script-picker.ts
+++ b/src/panels/config/script/ha-script-picker.ts
@@ -25,6 +25,7 @@ import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
+import { stateIcon } from "../../../common/entity/state_icon";
@customElement("ha-script-picker")
class HaScriptPicker extends LitElement {
@@ -43,6 +44,7 @@ class HaScriptPicker extends LitElement {
return {
...script,
name: computeStateName(script),
+ icon: stateIcon(script),
};
});
});
@@ -65,6 +67,11 @@ class HaScriptPicker extends LitElement {
>
`,
},
+ icon: {
+ title: "",
+ type: "icon",
+ template: (icon) => html`