diff --git a/src/panels/config/automation/dialog-new-automation.ts b/src/panels/config/automation/dialog-new-automation.ts index c71a909b13..63de00b99b 100644 --- a/src/panels/config/automation/dialog-new-automation.ts +++ b/src/panels/config/automation/dialog-new-automation.ts @@ -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 = { 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) => + Fuse.createIndex(BLUEPRINT_SEARCH_KEYS, blueprints) + ); + + private _filteredBlueprints = memoizeOne( + ( + blueprints: ReturnType, + 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` - - - - - ${this.hass.localize( - `ui.panel.config.${this._mode}.dialog_new.create_empty` - )} - +
+ + + + ${this.hass.localize( - `ui.panel.config.${this._mode}.dialog_new.create_empty_description` + `ui.panel.config.${this._mode}.dialog_new.create_empty` )} - - - -
  • - ${processedBlueprints.map( - (blueprint) => html` - - - ${blueprint.name} - - ${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}` + + ${this.hass.localize( + `ui.panel.config.${this._mode}.dialog_new.create_empty_description` + )} + + + +
    +
    +
    + ${this._loadingBlueprints + ? html`
    + +
    ` + : html` + + ${filteredBlueprints.map( + (blueprint) => html` + + + ${blueprint.name} + + ${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}` + )} + + + + ` )} - - - - ` - )} - ${processedBlueprints.length === 0 - ? html` - - - - ${this.hass.localize( - `ui.panel.config.${this._mode}.dialog_new.create_blueprint` - )} - - ${this.hass.localize( - `ui.panel.config.${this._mode}.dialog_new.create_blueprint_description` - )} - - - - - ` - : html` - - - ${this.hass.localize( - `ui.panel.config.${this._mode}.dialog_new.discover_blueprint_tip` - )} - - - `} - - + + ${processedBlueprints.length === 0 + ? html` + + + + ${this.hass.localize( + `ui.panel.config.${this._mode}.dialog_new.create_blueprint` + )} + + ${this.hass.localize( + `ui.panel.config.${this._mode}.dialog_new.create_blueprint_description` + )} + + + + + ` + : filteredBlueprints.length === 0 + ? html` + + ` + : nothing} + ${processedBlueprints.length > 0 + ? html` + + + ${this.hass.localize( + `ui.panel.config.${this._mode}.dialog_new.discover_blueprint_tip` + )} + + + ` + : nothing} + `} +
    +
    +
    + `; } + 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; diff --git a/src/translations/en.json b/src/translations/en.json index fbf94db9bf..34b0afea9b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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": {