mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-02 00:27:49 +01:00
Enhance create new automation/script dialog with search and adaptive dialog (#30188)
* Enhance create new automation/script dialog with search and adaptive dialog * Always show tip * Address review comments * Use multiTermSearch
This commit is contained in:
committed by
GitHub
parent
14615191f4
commit
fe53656c7e
@@ -5,6 +5,7 @@ import {
|
||||
mdiPencilOutline,
|
||||
mdiWeb,
|
||||
} from "@mdi/js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -12,11 +13,13 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import "../../../components/ha-adaptive-dialog";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-tip";
|
||||
import "../../../components/search-input";
|
||||
import { showAutomationEditor } from "../../../data/automation";
|
||||
import type {
|
||||
Blueprint,
|
||||
@@ -28,6 +31,10 @@ import {
|
||||
fetchBlueprints,
|
||||
getBlueprintSourceType,
|
||||
} from "../../../data/blueprint";
|
||||
import {
|
||||
type FuseWeightedKey,
|
||||
multiTermSearch,
|
||||
} from "../../../resources/fuseMultiTerm";
|
||||
import { showScriptEditor } from "../../../data/script";
|
||||
import { mdiHomeAssistant } from "../../../resources/home-assistant-logo-svg";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
@@ -41,6 +48,13 @@ const SOURCE_TYPE_ICONS: Record<BlueprintSourceType, string> = {
|
||||
homeassistant: mdiHomeAssistant,
|
||||
};
|
||||
|
||||
const BLUEPRINT_SEARCH_KEYS: FuseWeightedKey[] = [
|
||||
{ name: "name", weight: 10 },
|
||||
{ name: "description", weight: 7 },
|
||||
{ name: "author", weight: 5 },
|
||||
{ name: "sourceType", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-dialog-new-automation")
|
||||
class DialogNewAutomation extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -53,14 +67,25 @@ class DialogNewAutomation extends LitElement {
|
||||
|
||||
@state() public blueprints?: Blueprints;
|
||||
|
||||
@state() private _loadingBlueprints = false;
|
||||
|
||||
@state() private _filter = "";
|
||||
|
||||
public showDialog(params: NewAutomationDialogParams): void {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
this._mode = params?.mode || "automation";
|
||||
this._filter = "";
|
||||
this.blueprints = undefined;
|
||||
this._loadingBlueprints = true;
|
||||
|
||||
fetchBlueprints(this.hass!, this._mode).then((blueprints) => {
|
||||
this.blueprints = blueprints;
|
||||
});
|
||||
fetchBlueprints(this.hass!, this._mode)
|
||||
.then((blueprints) => {
|
||||
this.blueprints = blueprints;
|
||||
})
|
||||
.finally(() => {
|
||||
this._loadingBlueprints = false;
|
||||
});
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -70,6 +95,8 @@ class DialogNewAutomation extends LitElement {
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
this.blueprints = undefined;
|
||||
this._loadingBlueprints = false;
|
||||
this._filter = "";
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -93,117 +120,182 @@ class DialogNewAutomation extends LitElement {
|
||||
);
|
||||
});
|
||||
|
||||
private _blueprintFuseIndex = memoizeOne(
|
||||
(blueprints: ReturnType<DialogNewAutomation["_processedBlueprints"]>) =>
|
||||
Fuse.createIndex(BLUEPRINT_SEARCH_KEYS, blueprints)
|
||||
);
|
||||
|
||||
private _filteredBlueprints = memoizeOne(
|
||||
(
|
||||
blueprints: ReturnType<DialogNewAutomation["_processedBlueprints"]>,
|
||||
filter: string
|
||||
) =>
|
||||
multiTermSearch(
|
||||
blueprints,
|
||||
filter,
|
||||
BLUEPRINT_SEARCH_KEYS,
|
||||
this._blueprintFuseIndex(blueprints)
|
||||
)
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const processedBlueprints = this._processedBlueprints(this.blueprints);
|
||||
const filteredBlueprints = this._filteredBlueprints(
|
||||
processedBlueprints,
|
||||
this._filter
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
flexcontent
|
||||
header-title=${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.header`
|
||||
)}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-list
|
||||
innerRole="listbox"
|
||||
itemRoles="option"
|
||||
innerAriaLabel=${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.header`
|
||||
)}
|
||||
rootTabbable
|
||||
autofocus
|
||||
>
|
||||
<ha-list-item
|
||||
hasmeta
|
||||
twoline
|
||||
graphic="icon"
|
||||
@request-selected=${this._blank}
|
||||
>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPencilOutline}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_empty`
|
||||
)}
|
||||
<span slot="secondary">
|
||||
<div class="content-wrapper">
|
||||
<search-input
|
||||
autofocus
|
||||
.hass=${this.hass}
|
||||
.filter=${this._filter}
|
||||
.label=${this.hass.localize("ui.common.search")}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
></search-input>
|
||||
<ha-list>
|
||||
<ha-list-item
|
||||
hasmeta
|
||||
twoline
|
||||
graphic="icon"
|
||||
@request-selected=${this._blank}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPencilOutline}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_empty_description`
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_empty`
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
<li divider role="separator"></li>
|
||||
${processedBlueprints.map(
|
||||
(blueprint) => html`
|
||||
<ha-list-item
|
||||
hasmeta
|
||||
twoline
|
||||
graphic="icon"
|
||||
@request-selected=${this._blueprint}
|
||||
.path=${blueprint.path}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${SOURCE_TYPE_ICONS[blueprint.sourceType]}
|
||||
></ha-svg-icon>
|
||||
${blueprint.name}
|
||||
<span slot="secondary">
|
||||
${blueprint.author
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.blueprint_source.author`,
|
||||
{ author: blueprint.author }
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.blueprint_source.${blueprint.sourceType}`
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_empty_description`
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</ha-list>
|
||||
<div class="blueprints-container">
|
||||
<div class="blueprints-list ha-scrollbar">
|
||||
${this._loadingBlueprints
|
||||
? html`<div class="spinner">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>`
|
||||
: html`
|
||||
<ha-list>
|
||||
${filteredBlueprints.map(
|
||||
(blueprint) => html`
|
||||
<ha-list-item
|
||||
hasmeta
|
||||
twoline
|
||||
graphic="icon"
|
||||
@request-selected=${this._blueprint}
|
||||
.path=${blueprint.path}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${SOURCE_TYPE_ICONS[blueprint.sourceType]}
|
||||
></ha-svg-icon>
|
||||
${blueprint.name}
|
||||
<span slot="secondary">
|
||||
${blueprint.author
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.blueprint_source.author`,
|
||||
{ author: blueprint.author }
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.blueprint_source.${blueprint.sourceType}`
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</span>
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
${processedBlueprints.length === 0
|
||||
? html`
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
class="item"
|
||||
>
|
||||
<ha-list-item hasmeta twoline graphic="icon">
|
||||
<ha-svg-icon slot="graphic" .path=${mdiWeb}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_blueprint`
|
||||
)}
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_blueprint_description`
|
||||
)}
|
||||
</span>
|
||||
<ha-svg-icon slot="meta" path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
: html`
|
||||
<ha-tip .hass=${this.hass}>
|
||||
<a
|
||||
href=${documentationUrl(this.hass, "/get-blueprints")}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.discover_blueprint_tip`
|
||||
)}
|
||||
</a>
|
||||
</ha-tip>
|
||||
`}
|
||||
</ha-list>
|
||||
</ha-dialog>
|
||||
</ha-list>
|
||||
${processedBlueprints.length === 0
|
||||
? html`
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/get-blueprints"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
class="item"
|
||||
>
|
||||
<ha-list-item hasmeta twoline graphic="icon">
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiWeb}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_blueprint`
|
||||
)}
|
||||
<span slot="secondary">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.create_blueprint_description`
|
||||
)}
|
||||
</span>
|
||||
<ha-svg-icon
|
||||
slot="meta"
|
||||
path=${mdiOpenInNew}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
: filteredBlueprints.length === 0
|
||||
? html`
|
||||
<div class="empty-search">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.no_blueprints_match_search`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${processedBlueprints.length > 0
|
||||
? html`
|
||||
<ha-tip .hass=${this.hass}>
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/get-blueprints"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.${this._mode}.dialog_new.discover_blueprint_tip`
|
||||
)}
|
||||
</a>
|
||||
</ha-tip>
|
||||
`
|
||||
: nothing}
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ha-adaptive-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _blueprint(ev) {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@@ -232,22 +324,63 @@ class DialogNewAutomation extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
ha-adaptive-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
--mdc-dialog-max-height: 60vh;
|
||||
--mdc-dialog-max-height: 60dvh;
|
||||
--ha-dialog-min-height: min(
|
||||
720px,
|
||||
calc(
|
||||
100dvh - max(
|
||||
var(--safe-area-inset-bottom),
|
||||
var(--ha-space-4)
|
||||
) - max(var(--safe-area-inset-top), var(--ha-space-4))
|
||||
)
|
||||
);
|
||||
--ha-dialog-max-height: var(--ha-dialog-min-height);
|
||||
}
|
||||
:host {
|
||||
--ha-bottom-sheet-height: min(85vh, 85dvh);
|
||||
--ha-bottom-sheet-max-height: min(85vh, 85dvh);
|
||||
}
|
||||
@media all and (min-width: 550px) {
|
||||
ha-dialog {
|
||||
ha-adaptive-dialog {
|
||||
--mdc-dialog-min-width: 500px;
|
||||
}
|
||||
}
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
.blueprints-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
}
|
||||
.blueprints-list {
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
padding-bottom: var(--ha-space-2);
|
||||
}
|
||||
.spinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--ha-space-8) 0;
|
||||
}
|
||||
ha-icon-next {
|
||||
width: 24px;
|
||||
}
|
||||
ha-tip {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
margin: var(--ha-space-2) var(--ha-space-4);
|
||||
}
|
||||
.empty-search {
|
||||
color: var(--secondary-text-color);
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
a.item {
|
||||
text-decoration: unset;
|
||||
|
||||
@@ -4743,6 +4743,7 @@
|
||||
"header": "Create automation",
|
||||
"create_empty": "Create new automation",
|
||||
"create_empty_description": "Start with an empty automation from scratch",
|
||||
"no_blueprints_match_search": "No matching blueprints",
|
||||
"create_blueprint": "Create from blueprint",
|
||||
"create_blueprint_description": "Discover community blueprints",
|
||||
"blueprint_source": {
|
||||
@@ -5762,6 +5763,7 @@
|
||||
"header": "Create script",
|
||||
"create_empty": "Create new script",
|
||||
"create_empty_description": "Start with an empty script from scratch",
|
||||
"no_blueprints_match_search": "[%key:ui::panel::config::automation::dialog_new::no_blueprints_match_search%]",
|
||||
"create_blueprint": "[%key:ui::panel::config::automation::dialog_new::create_blueprint%]",
|
||||
"create_blueprint_description": "[%key:ui::panel::config::automation::dialog_new::create_blueprint_description%]",
|
||||
"blueprint_source": {
|
||||
|
||||
Reference in New Issue
Block a user