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.scriptEntityId + ? html` + ` + : ""}

${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` `, + }, name: { title: this.hass.localize( "ui.panel.config.script.picker.headers.name" diff --git a/src/translations/en.json b/src/translations/en.json index 9010307a42..cd2f1eb8a9 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1080,6 +1080,10 @@ }, "editor": { "alias": "Name", + "icon": "Icon", + "id": "Entity ID", + "id_already_exists_save_error": "You can't save this script because the ID is not unique, pick another ID or leave it blank to automatically generate one.", + "id_already_exists": "This ID already exists", "introduction": "Use scripts to execute a sequence of actions.", "header": "Script: {name}", "default_name": "New Script",