Add blueprint scripts (#9504)

This commit is contained in:
Bram Kragten 2021-10-26 18:32:40 +02:00 committed by GitHub
parent 54c64c15f3
commit 371804591d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 399 additions and 118 deletions

View File

@ -21,7 +21,9 @@ export interface ScriptEntity extends HassEntityBase {
};
}
export interface ScriptConfig {
export type ScriptConfig = ManualScriptConfig | BlueprintScriptConfig;
export interface ManualScriptConfig {
alias: string;
sequence: Action | Action[];
icon?: string;
@ -29,7 +31,7 @@ export interface ScriptConfig {
max?: number;
}
export interface BlueprintScriptConfig extends ScriptConfig {
export interface BlueprintScriptConfig extends ManualScriptConfig {
use_blueprint: { path: string; input?: BlueprintInput };
}

View File

@ -29,6 +29,7 @@ import {
Blueprints,
deleteBlueprint,
} from "../../../data/blueprint";
import { showScriptEditor } from "../../../data/script";
import {
showAlertDialog,
showConfirmationDialog,
@ -52,6 +53,12 @@ const createNewFunctions = {
use_blueprint: { path: blueprintMeta.path },
});
},
script: (blueprintMeta: BlueprintMetaDataPath) => {
showScriptEditor({
alias: blueprintMeta.name,
use_blueprint: { path: blueprintMeta.path },
});
},
};
@customElement("ha-blueprint-overview")
@ -62,27 +69,38 @@ class HaBlueprintOverview extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@property() public route!: Route;
@property({ attribute: false }) public route!: Route;
@property() public blueprints!: Blueprints;
@property({ attribute: false }) public blueprints!: Record<
string,
Blueprints
>;
private _processedBlueprints = memoizeOne((blueprints: Blueprints) => {
const result = Object.entries(blueprints).map(([path, blueprint]) => {
private _processedBlueprints = memoizeOne(
(blueprints: Record<string, Blueprints>) => {
const result: any[] = [];
Object.entries(blueprints).forEach(([type, typeBlueprints]) =>
Object.entries(typeBlueprints).forEach(([path, blueprint]) => {
if ("error" in blueprint) {
return {
result.push({
name: blueprint.error,
type,
error: true,
path,
};
}
return {
});
} else {
result.push({
...blueprint.metadata,
type,
error: false,
path,
};
});
}
})
);
return result;
});
}
);
private _columns = memoizeOne(
(narrow, _language): DataTableColumnContainer => ({
@ -102,6 +120,20 @@ class HaBlueprintOverview extends LitElement {
`
: undefined,
},
type: {
title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.type"
),
template: (type: string) =>
html`${this.hass.localize(
`ui.panel.config.blueprint.overview.types.${type}`
)}`,
sortable: true,
filterable: true,
hidden: narrow,
direction: "asc",
width: "10%",
},
path: {
title: this.hass.localize(
"ui.panel.config.blueprint.overview.headers.file_name"
@ -114,25 +146,27 @@ class HaBlueprintOverview extends LitElement {
},
create: {
title: "",
width: narrow ? undefined : "20%",
type: narrow ? "icon-button" : undefined,
template: (_, blueprint: any) =>
blueprint.error
? ""
: narrow
? html` <ha-icon-button
? html`<ha-icon-button
.blueprint=${blueprint}
.label=${this.hass.localize(
"ui.panel.config.blueprint.overview.use_blueprint"
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
)}
.path=${mdiRobot}
@click=${this._createNew}
></ha-icon-button>`
.path=${mdiRobot}
>
</ha-icon-button>`
: html`<mwc-button
.blueprint=${blueprint}
@click=${this._createNew}
>
${this.hass.localize(
"ui.panel.config.blueprint.overview.use_blueprint"
`ui.panel.config.blueprint.overview.create_${blueprint.domain}`
)}
</mwc-button>`,
},

View File

@ -25,7 +25,7 @@ class HaConfigBlueprint extends HassRouterPage {
@property() public showAdvanced!: boolean;
@property() public blueprints: Blueprints = {};
@property() public blueprints: Record<string, Blueprints> = {};
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
@ -41,7 +41,11 @@ class HaConfigBlueprint extends HassRouterPage {
};
private async _getBlueprints() {
this.blueprints = await fetchBlueprints(this.hass, "automation");
const [automation, script] = await Promise.all([
fetchBlueprints(this.hass, "automation"),
fetchBlueprints(this.hass, "script"),
]);
this.blueprints = { automation, script };
}
protected firstUpdated(changedProps) {

View File

@ -0,0 +1,205 @@
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-blueprint-picker";
import "../../../components/ha-card";
import "../../../components/ha-circular-progress";
import "../../../components/ha-markdown";
import "../../../components/ha-selector/ha-selector";
import "../../../components/ha-settings-row";
import {
BlueprintOrError,
Blueprints,
fetchBlueprints,
} from "../../../data/blueprint";
import { BlueprintScriptConfig } from "../../../data/script";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
@customElement("blueprint-script-editor")
export class HaBlueprintScriptEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide!: boolean;
@property({ reflect: true, type: Boolean }) public narrow!: boolean;
@property({ attribute: false }) public config!: BlueprintScriptConfig;
@state() private _blueprints?: Blueprints;
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._getBlueprints();
}
private get _blueprint(): BlueprintOrError | undefined {
if (!this._blueprints) {
return undefined;
}
return this._blueprints[this.config.use_blueprint.path];
}
protected render() {
const blueprint = this._blueprint;
return html` <ha-config-section vertical .isWide=${this.isWide}>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.header"
)}</span
>
<ha-card>
<div class="blueprint-picker-container">
${this._blueprints
? Object.keys(this._blueprints).length
? html`
<ha-blueprint-picker
.hass=${this.hass}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.blueprint_to_use"
)}
.blueprints=${this._blueprints}
.value=${this.config.use_blueprint.path}
@value-changed=${this._blueprintChanged}
></ha-blueprint-picker>
`
: this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
</div>
${this.config.use_blueprint.path
? blueprint && "error" in blueprint
? html`<p class="warning padding">
There is an error in this Blueprint: ${blueprint.error}
</p>`
: html`${blueprint?.metadata.description
? html`<ha-markdown
class="card-content"
breaks
.content=${blueprint.metadata.description}
></ha-markdown>`
: ""}
${blueprint?.metadata?.input &&
Object.keys(blueprint.metadata.input).length
? Object.entries(blueprint.metadata.input).map(
([key, value]) =>
html`<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">${value?.name || key}</span>
<span slot="description">${value?.description}</span>
${value?.selector
? html`<ha-selector
.hass=${this.hass}
.selector=${value.selector}
.key=${key}
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
></ha-selector>`
: html`<paper-input
.key=${key}
required
.value=${(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key]) ??
value?.default}
@value-changed=${this._inputChanged}
no-label-float
></paper-input>`}
</ha-settings-row>`
)
: html`<p class="padding">
${this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_inputs"
)}
</p>`}`
: ""}
</ha-card>
</ha-config-section>`;
}
private async _getBlueprints() {
this._blueprints = await fetchBlueprints(this.hass, "script");
}
private _blueprintChanged(ev) {
ev.stopPropagation();
if (this.config.use_blueprint.path === ev.detail.value) {
return;
}
fireEvent(this, "value-changed", {
value: {
...this.config,
use_blueprint: {
path: ev.detail.value,
},
},
});
}
private _inputChanged(ev) {
ev.stopPropagation();
const target = ev.target as any;
const key = target.key;
const value = ev.detail.value;
if (
(this.config.use_blueprint.input &&
this.config.use_blueprint.input[key] === value) ||
(!this.config.use_blueprint.input && value === "")
) {
return;
}
const input = { ...this.config.use_blueprint.input, [key]: value };
if (value === "" || value === undefined) {
delete input[key];
}
fireEvent(this, "value-changed", {
value: {
...this.config,
use_blueprint: {
...this.config.use_blueprint,
input,
},
},
});
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.padding {
padding: 16px;
}
.blueprint-picker-container {
padding: 16px;
}
p {
margin-bottom: 0;
}
ha-settings-row {
--paper-time-input-justify-content: flex-end;
border-top: 1px solid var(--divider-color);
}
:host(:not([narrow])) ha-settings-row paper-input {
width: 60%;
}
:host(:not([narrow])) ha-settings-row ha-selector {
width: 60%;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"blueprint-script-editor": HaBlueprintScriptEditor;
}
}

View File

@ -38,6 +38,7 @@ import {
Action,
deleteScript,
getScriptEditorInitData,
ManualScriptConfig,
MODES,
MODES_MAX,
ScriptConfig,
@ -55,6 +56,7 @@ import "../automation/action/ha-automation-action";
import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
import "./blueprint-script-editor";
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -236,7 +238,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
>
</paper-input>`
: ""}
<p>
${"use_blueprint" in this._config
? ""
: html`<p>
${this.hass.localize(
"ui.panel.config.script.editor.modes.description",
"documentation_link",
@ -289,7 +293,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@value-changed=${this._valueChanged}
>
</paper-input>`
: html``}
: html``} `}
</div>
${this.scriptEntityId
? html`
@ -323,7 +327,18 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
</ha-card>
</ha-config-section>
<ha-config-section vertical .isWide=${this.isWide}>
${"use_blueprint" in this._config
? html`<blueprint-script-editor
.hass=${this.hass}
.narrow=${this.narrow}
.isWide=${this.isWide}
.config=${this._config}
@value-changed=${this._configChanged}
></blueprint-script-editor>`
: html`<ha-config-section
vertical
.isWide=${this.isWide}
>
<span slot="header">
${this.hass.localize(
"ui.panel.config.script.editor.sequence"
@ -353,7 +368,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@value-changed=${this._sequenceChanged}
.hass=${this.hass}
></ha-automation-action>
</ha-config-section>
</ha-config-section>`}
`
: ""}
</div>
@ -427,7 +442,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
(!oldScript || oldScript !== this.scriptEntityId)
) {
this.hass
.callApi<ScriptConfig>(
.callApi<ManualScriptConfig>(
"GET",
`config/script/config/${computeObjectId(this.scriptEntityId)}`
)
@ -466,11 +481,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
) {
const initData = getScriptEditorInitData();
this._dirty = !!initData;
this._config = {
const baseConfig: Partial<ScriptConfig> = {
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
sequence: [{ ...HaDeviceAction.defaultConfig }],
...initData,
};
if (!initData || !("use_blueprint" in initData)) {
baseConfig.sequence = [{ ...HaDeviceAction.defaultConfig }];
}
this._config = {
...baseConfig,
...initData,
} as ScriptConfig;
}
}
@ -548,6 +568,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
this._dirty = true;
}
private _configChanged(ev) {
this._config = ev.detail.value;
this._dirty = true;
}
private _sequenceChanged(ev: CustomEvent): void {
this._config = { ...this._config!, sequence: ev.detail.value as Action[] };
this._errors = undefined;
@ -749,3 +774,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
customElements.define("ha-script-editor", HaScriptEditor);
declare global {
interface HTMLElementTagNameMap {
"ha-script-editor": HaScriptEditor;
}
}

View File

@ -1794,13 +1794,18 @@
"learn_more": "Learn more about using blueprints",
"headers": {
"name": "Name",
"domain": "Domain",
"type": "Type",
"file_name": "File name"
},
"types": {
"automation": "Automation",
"script": "Script"
},
"confirm_delete_header": "Delete this blueprint?",
"confirm_delete_text": "Are you sure you want to delete this blueprint?",
"add_blueprint": "Import blueprint",
"use_blueprint": "Create automation",
"create_automation": "Create automation",
"create_script": "Create script",
"delete_blueprint": "Delete blueprint",
"share_blueprint": "Share blueprint",
"share_blueprint_no_url": "Unable to share blueprint: no source url",