From b2bfb1fdcbf790b991a35b5c1621c0f97663cbbd Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 7 Feb 2023 15:18:00 +0100 Subject: [PATCH] Update create automation dialog (#15286) --- src/components/ha-blueprint-picker.ts | 4 +- src/data/blueprint.ts | 17 ++ .../automation/dialog-new-automation.ts | 204 ++++++++++++++---- src/translations/en.json | 23 +- 4 files changed, 197 insertions(+), 51 deletions(-) diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index 0dfc64c3a9..c1f8a82af3 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -41,9 +41,9 @@ class HaBluePrintPicker extends LitElement { return []; } const result = Object.entries(blueprints) - .filter(([_path, blueprint]) => !("error" in blueprint)) + .filter((entry): entry is [string, Blueprint] => !("error" in entry[1])) .map(([path, blueprint]) => ({ - ...(blueprint as Blueprint).metadata, + ...blueprint.metadata, path, })); return result.sort((a, b) => diff --git a/src/data/blueprint.ts b/src/data/blueprint.ts index 3f2b826953..315aa53044 100644 --- a/src/data/blueprint.ts +++ b/src/data/blueprint.ts @@ -16,6 +16,7 @@ export interface BlueprintMetaData { input?: Record; description?: string; source_url?: string; + author?: string; } export interface BlueprintInput { @@ -63,3 +64,19 @@ export const deleteBlueprint = ( domain, path, }); + +export type BlueprintSourceType = "local" | "community" | "homeassistant"; + +export const getBlueprintSourceType = ( + blueprint: Blueprint +): BlueprintSourceType => { + const sourceUrl = blueprint.metadata.source_url; + + if (!sourceUrl) { + return "local"; + } + if (sourceUrl.includes("github.com/home-assistant")) { + return "homeassistant"; + } + return "community"; +}; diff --git a/src/panels/config/automation/dialog-new-automation.ts b/src/panels/config/automation/dialog-new-automation.ts index d755f09a7e..c91ad599f9 100644 --- a/src/panels/config/automation/dialog-new-automation.ts +++ b/src/panels/config/automation/dialog-new-automation.ts @@ -1,17 +1,40 @@ -import "@material/mwc-button"; +import "@material/mwc-list/mwc-list"; +import { + mdiAccount, + mdiFile, + mdiHomeAssistant, + mdiOpenInNew, + mdiPencilOutline, + mdiWeb, +} from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; -import "../../../components/ha-blueprint-picker"; -import "../../../components/ha-circular-progress"; +import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; +import { stringCompare } from "../../../common/string/compare"; import { createCloseHeading } from "../../../components/ha-dialog"; +import "../../../components/ha-icon-next"; +import "../../../components/ha-list-item"; +import "../../../components/ha-tip"; import { showAutomationEditor } from "../../../data/automation"; +import { + Blueprint, + Blueprints, + BlueprintSourceType, + fetchBlueprints, + getBlueprintSourceType, +} from "../../../data/blueprint"; import { HassDialog } from "../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; -import "@material/mwc-list/mwc-list-item"; -import "../../../components/ha-icon-next"; -import "@material/mwc-list/mwc-list"; +import { documentationUrl } from "../../../util/documentation-url"; + +const SOURCE_TYPE_ICONS: Record = { + local: mdiFile, + community: mdiAccount, + homeassistant: mdiHomeAssistant, +}; @customElement("ha-dialog-new-automation") class DialogNewAutomation extends LitElement implements HassDialog { @@ -19,8 +42,13 @@ class DialogNewAutomation extends LitElement implements HassDialog { @state() private _opened = false; + @state() public blueprints?: Blueprints; + public showDialog(): void { this._opened = true; + fetchBlueprints(this.hass!, "automation").then((blueprints) => { + this.blueprints = blueprints; + }); } public closeDialog(): void { @@ -30,10 +58,33 @@ class DialogNewAutomation extends LitElement implements HassDialog { this._opened = false; } + private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => { + if (!blueprints) { + return []; + } + const result = Object.entries(blueprints) + .filter((entry): entry is [string, Blueprint] => !("error" in entry[1])) + .map(([path, blueprint]) => { + const sourceType = getBlueprintSourceType(blueprint); + + return { + ...blueprint.metadata, + sourceType, + path, + }; + }); + return result.sort((a, b) => + stringCompare(a.name, b.name, this.hass!.locale.language) + ); + }); + protected render(): TemplateResult { if (!this._opened) { return html``; } + + const processedBlueprints = this._processedBlueprints(this.blueprints); + return html` - - + + + ${this.hass.localize( - "ui.panel.config.automation.dialog_new.blueprint.use_blueprint" - )} - - - - -
  • - - ${this.hass.localize( - "ui.panel.config.automation.dialog_new.start_empty" + "ui.panel.config.automation.dialog_new.create_empty" )} ${this.hass.localize( - "ui.panel.config.automation.dialog_new.start_empty_description" + "ui.panel.config.automation.dialog_new.create_empty_description" )} - + +
  • + ${processedBlueprints.map( + (blueprint) => html` + + + ${blueprint.name} + + ${blueprint.author + ? this.hass.localize( + `ui.panel.config.automation.dialog_new.blueprint_source.author`, + { author: blueprint.author } + ) + : this.hass.localize( + `ui.panel.config.automation.dialog_new.blueprint_source.${blueprint.sourceType}` + )} + + + + ` + )} + ${processedBlueprints.length === 0 + ? html` + + + + ${this.hass.localize( + "ui.panel.config.automation.dialog_new.create_blueprint" + )} + + ${this.hass.localize( + "ui.panel.config.automation.dialog_new.create_blueprint_description" + )} + + + + + ` + : html` + + + ${this.hass.localize( + "ui.panel.config.automation.dialog_new.discover_blueprint_tip" + )} + + + `}
    `; } - private async _blueprintPicked(ev: CustomEvent) { + private async _blueprint(ev) { + if (!shouldHandleRequestSelectedEvent(ev)) { + return; + } + const path = (ev.currentTarget! as any).path; this.closeDialog(); - showAutomationEditor({ use_blueprint: { path: ev.detail.value } }); + showAutomationEditor({ use_blueprint: { path } }); } - private async _blueprint() { - this.shadowRoot!.querySelector("ha-blueprint-picker")!.open(); - } - - private async _blank() { + private async _blank(ev) { + if (!shouldHandleRequestSelectedEvent(ev)) { + return; + } this.closeDialog(); showAutomationEditor(); } @@ -92,14 +212,24 @@ class DialogNewAutomation extends LitElement implements HassDialog { haStyle, haStyleDialog, css` - mwc-list-item.blueprint { - height: 110px; - } - ha-blueprint-picker { - margin-top: 8px; - } ha-dialog { --dialog-content-padding: 0; + --mdc-dialog-max-height: 60vh; + } + @media all and (min-width: 550px) { + ha-dialog { + --mdc-dialog-min-width: 500px; + } + } + ha-icon-next { + width: 24px; + } + ha-tip { + margin-top: 8px; + margin-bottom: 4px; + } + a.item { + text-decoration: unset; } `, ]; diff --git a/src/translations/en.json b/src/translations/en.json index 79f5a7ebaa..176cd12b34 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1931,19 +1931,18 @@ } }, "dialog_new": { - "header": "Create a new automation", - "how": "How do you want to create your new automation?", - "blueprint": { - "use_blueprint": "Use a blueprint" + "header": "Create automation", + "create_empty": "Create new automation", + "create_empty_description": "Start with an empty automation from scratch", + "create_blueprint": "Create from blueprint", + "create_blueprint_description": "Discover community blueprints", + "blueprint_source": { + "author": "By {author}", + "local": "By you", + "community": "By the community", + "homeassistant": "By Home Assistant" }, - "thingtalk": { - "header": "Describe the automation you want to create", - "intro": "And we will try to create it for you. For example: Turn the lights off when I leave.", - "input_label": "What should this automation do?", - "create": "Create" - }, - "start_empty": "Start with an empty automation", - "start_empty_description": "Create a new automation from scratch" + "discover_blueprint_tip": "Discover more community blueprints" }, "editor": { "enable": "[%key:ui::common::enable%]",