diff --git a/src/common/search/search-input.ts b/src/common/search/search-input.ts index c04395a8f5..46f3745335 100644 --- a/src/common/search/search-input.ts +++ b/src/common/search/search-input.ts @@ -22,6 +22,9 @@ class SearchInput extends LitElement { @property({ type: Boolean, attribute: "no-underline" }) public noUnderline = false; + @property({ type: Boolean }) + public autofocus = false; + public focus() { this.shadowRoot!.querySelector("paper-input")!.focus(); } @@ -38,7 +41,7 @@ class SearchInput extends LitElement { ${this.hass.localize("ui.panel.config.integrations.new")} diff --git a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts index 0c8550f39a..eb0e767a18 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts @@ -10,12 +10,18 @@ import { } from "lit-element"; import { classMap } from "lit-html/directives/class-map"; import { until } from "lit-html/directives/until"; +import memoizeOne from "memoize-one"; +import * as Fuse from "fuse.js"; + +import { CardPickTarget } from "../types"; +import { LovelaceCard } from "../../types"; +import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace"; import { fireEvent } from "../../../../common/dom/fire_event"; import { UNAVAILABLE_STATES } from "../../../../data/entity"; -import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace"; import { - customCards, CUSTOM_TYPE_PREFIX, + CustomCardEntry, + customCards, getCustomCardEntry, } from "../../../../data/lovelace_custom_cards"; import { HomeAssistant } from "../../../../types"; @@ -24,9 +30,22 @@ import { computeUsedEntities, } from "../../common/compute-unused-entities"; import { createCardElement } from "../../create-element/create-card-element"; -import { LovelaceCard } from "../../types"; import { getCardStubConfig } from "../get-card-stub-config"; -import { CardPickTarget } from "../types"; + +import "../../../../common/search/search-input"; + +interface Card { + type: string; + name?: string; + description?: string; + noElement?: boolean; + isCustom?: boolean; +} + +interface CardElement { + card: Card; + element: TemplateResult; +} const previewCards: string[] = [ "alarm-panel", @@ -63,14 +82,40 @@ const nonPreviewCards: string[] = [ export class HuiCardPicker extends LitElement { @property() public hass?: HomeAssistant; + @property() private _cards: CardElement[] = []; + public lovelace?: LovelaceConfig; public cardPicked?: (cardConf: LovelaceCardConfig) => void; + private _filter?: string; + private _unusedEntities?: string[]; private _usedEntities?: string[]; + private _filterCards = memoizeOne( + (cardElements: CardElement[], filter?: string): CardElement[] => { + if (filter) { + let cards = cardElements.map( + (cardElement: CardElement) => cardElement.card + ); + const options: Fuse.FuseOptions = { + keys: ["type", "name", "description"], + caseSensitive: false, + minMatchCharLength: 2, + threshold: 0.2, + }; + const fuse = new Fuse(cards, options); + cards = fuse.search(filter); + cardElements = cardElements.filter((cardElement: CardElement) => + cards.includes(cardElement.card) + ); + } + return cardElements; + } + ); + protected render(): TemplateResult { if ( !this.hass || @@ -82,50 +127,16 @@ export class HuiCardPicker extends LitElement { } return html` +
- ${previewCards.map((type: string) => { - return html` - ${until( - this._renderCardElement(type), - html` -
- -
- ` - )} - `; - })} - ${nonPreviewCards.map((type: string) => { - return html` - ${until( - this._renderCardElement(type, true), - html` -
- -
- ` - )} - `; - })} + ${this._filterCards(this._cards, this._filter).map( + (cardElement: CardElement) => cardElement.element + )}
- ${customCards.length - ? html` -
- ${customCards.map((card) => { - return html` - ${until( - this._renderCardElement(card.type, true, true), - html` -
- -
- ` - )} - `; - })} -
- ` - : ""}
({ + type, + name: this.hass!.localize(`ui.panel.lovelace.editor.card.${type}.name`), + description: this.hass!.localize( + `ui.panel.lovelace.editor.card.${type}.description` + ), + })) + .concat( + nonPreviewCards.map((type: string) => ({ + type, + name: this.hass!.localize( + `ui.panel.lovelace.editor.card.${type}.name` + ), + description: this.hass!.localize( + `ui.panel.lovelace.editor.card.${type}.description` + ), + noElement: true, + })) + ); + if (customCards.length > 0) { + cards = cards.concat( + customCards.map((ccard: CustomCardEntry) => ({ + type: ccard.type, + name: ccard.name, + description: ccard.description, + noElement: true, + isCustom: true, + })) + ); + } + this._cards = cards.map((card: Card) => ({ + card: card, + element: html`${until( + this._renderCardElement(card), + html` +
+ +
+ ` + )}`, + })); + } + + private _handleSearchChange(ev: CustomEvent) { + this._filter = ev.detail.value; this.requestUpdate(); } @@ -194,6 +255,7 @@ export class HuiCardPicker extends LitElement { .card { height: 100%; + max-width: 500px; display: flex; flex-direction: column; border-radius: 4px; @@ -279,11 +341,9 @@ export class HuiCardPicker extends LitElement { return element; } - private async _renderCardElement( - type: string, - noElement = false, - isCustom = false - ): Promise { + private async _renderCardElement(card: Card): Promise { + let { type } = card; + const { noElement, isCustom, name, description } = card; const customCard = isCustom ? getCustomCardEntry(type) : undefined; if (isCustom) { type = `${CUSTOM_TYPE_PREFIX}${type}`; @@ -324,18 +384,14 @@ export class HuiCardPicker extends LitElement { this.hass!.localize( `ui.panel.lovelace.editor.cardpicker.no_description` ) - : this.hass!.localize( - `ui.panel.lovelace.editor.card.${cardConfig.type}.description` - )} + : description}
${customCard ? `${this.hass!.localize( "ui.panel.lovelace.editor.cardpicker.custom_card" )}: ${customCard.name || customCard.type}` - : this.hass!.localize( - `ui.panel.lovelace.editor.card.${cardConfig.type}.name` - )} + : name}
`;