Add Search to Card Picker (#5497)

* Add Search to Card Picker

* Force focus

* Remove autofocus

* Fix from rebase

* Commit suggestion

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Flip autofocus

* Cache cards

* Make cards a property

* Add missing custom cards

* Set cards to render elements

* Commit suggestion

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/lovelace/editor/card-editor/hui-card-picker.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Make card preview max width match columns

* Typo

* Add autofocus where wanted

* Update src/common/search/search-input.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/dialogs/config-flow/step-flow-pick-handler.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Aidan Timson 2020-04-17 22:29:49 +01:00 committed by GitHub
parent 97c454aa0d
commit f57754212c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 118 additions and 58 deletions

View File

@ -22,6 +22,9 @@ class SearchInput extends LitElement {
@property({ type: Boolean, attribute: "no-underline" }) @property({ type: Boolean, attribute: "no-underline" })
public noUnderline = false; public noUnderline = false;
@property({ type: Boolean })
public autofocus = false;
public focus() { public focus() {
this.shadowRoot!.querySelector("paper-input")!.focus(); this.shadowRoot!.querySelector("paper-input")!.focus();
} }
@ -38,7 +41,7 @@ class SearchInput extends LitElement {
</style> </style>
<paper-input <paper-input
class=${classMap({ "no-underline": this.noUnderline })} class=${classMap({ "no-underline": this.noUnderline })}
autofocus .autofocus=${this.autofocus}
label="Search" label="Search"
.value=${this.filter} .value=${this.filter}
@value-changed=${this._filterInputChanged} @value-changed=${this._filterInputChanged}

View File

@ -69,6 +69,7 @@ class StepFlowPickHandler extends LitElement {
return html` return html`
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2> <h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
<search-input <search-input
autofocus
.filter=${this.filter} .filter=${this.filter}
@value-changed=${this._filterChanged} @value-changed=${this._filterChanged}
></search-input> ></search-input>

View File

@ -10,12 +10,18 @@ import {
} from "lit-element"; } from "lit-element";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { until } from "lit-html/directives/until"; 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 { fireEvent } from "../../../../common/dom/fire_event";
import { UNAVAILABLE_STATES } from "../../../../data/entity"; import { UNAVAILABLE_STATES } from "../../../../data/entity";
import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace";
import { import {
customCards,
CUSTOM_TYPE_PREFIX, CUSTOM_TYPE_PREFIX,
CustomCardEntry,
customCards,
getCustomCardEntry, getCustomCardEntry,
} from "../../../../data/lovelace_custom_cards"; } from "../../../../data/lovelace_custom_cards";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -24,9 +30,22 @@ import {
computeUsedEntities, computeUsedEntities,
} from "../../common/compute-unused-entities"; } from "../../common/compute-unused-entities";
import { createCardElement } from "../../create-element/create-card-element"; import { createCardElement } from "../../create-element/create-card-element";
import { LovelaceCard } from "../../types";
import { getCardStubConfig } from "../get-card-stub-config"; 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[] = [ const previewCards: string[] = [
"alarm-panel", "alarm-panel",
@ -63,14 +82,40 @@ const nonPreviewCards: string[] = [
export class HuiCardPicker extends LitElement { export class HuiCardPicker extends LitElement {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() private _cards: CardElement[] = [];
public lovelace?: LovelaceConfig; public lovelace?: LovelaceConfig;
public cardPicked?: (cardConf: LovelaceCardConfig) => void; public cardPicked?: (cardConf: LovelaceCardConfig) => void;
private _filter?: string;
private _unusedEntities?: string[]; private _unusedEntities?: string[];
private _usedEntities?: 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<Card> = {
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 { protected render(): TemplateResult {
if ( if (
!this.hass || !this.hass ||
@ -82,50 +127,16 @@ export class HuiCardPicker extends LitElement {
} }
return html` return html`
<search-input
.filter=${this._filter}
no-label-float
@value-changed=${this._handleSearchChange}
></search-input>
<div class="cards-container"> <div class="cards-container">
${previewCards.map((type: string) => { ${this._filterCards(this._cards, this._filter).map(
return html` (cardElement: CardElement) => cardElement.element
${until( )}
this._renderCardElement(type),
html`
<div class="card spinner">
<paper-spinner active alt="Loading"></paper-spinner>
</div>
`
)}
`;
})}
${nonPreviewCards.map((type: string) => {
return html`
${until(
this._renderCardElement(type, true),
html`
<div class="card spinner">
<paper-spinner active alt="Loading"></paper-spinner>
</div>
`
)}
`;
})}
</div> </div>
${customCards.length
? html`
<div class="cards-container">
${customCards.map((card) => {
return html`
${until(
this._renderCardElement(card.type, true, true),
html`
<div class="card spinner">
<paper-spinner active alt="Loading"></paper-spinner>
</div>
`
)}
`;
})}
</div>
`
: ""}
<div class="cards-container"> <div class="cards-container">
<div <div
class="card" class="card"
@ -179,6 +190,56 @@ export class HuiCardPicker extends LitElement {
!UNAVAILABLE_STATES.includes(this.hass!.states[eid].state) !UNAVAILABLE_STATES.includes(this.hass!.states[eid].state)
); );
this._loadCards();
}
private _loadCards() {
let cards: Card[] = previewCards
.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`
),
}))
.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`
<div class="card spinner">
<paper-spinner active alt="Loading"></paper-spinner>
</div>
`
)}`,
}));
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
this.requestUpdate(); this.requestUpdate();
} }
@ -194,6 +255,7 @@ export class HuiCardPicker extends LitElement {
.card { .card {
height: 100%; height: 100%;
max-width: 500px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 4px; border-radius: 4px;
@ -279,11 +341,9 @@ export class HuiCardPicker extends LitElement {
return element; return element;
} }
private async _renderCardElement( private async _renderCardElement(card: Card): Promise<TemplateResult> {
type: string, let { type } = card;
noElement = false, const { noElement, isCustom, name, description } = card;
isCustom = false
): Promise<TemplateResult> {
const customCard = isCustom ? getCustomCardEntry(type) : undefined; const customCard = isCustom ? getCustomCardEntry(type) : undefined;
if (isCustom) { if (isCustom) {
type = `${CUSTOM_TYPE_PREFIX}${type}`; type = `${CUSTOM_TYPE_PREFIX}${type}`;
@ -324,18 +384,14 @@ export class HuiCardPicker extends LitElement {
this.hass!.localize( this.hass!.localize(
`ui.panel.lovelace.editor.cardpicker.no_description` `ui.panel.lovelace.editor.cardpicker.no_description`
) )
: this.hass!.localize( : description}
`ui.panel.lovelace.editor.card.${cardConfig.type}.description`
)}
</div> </div>
<div class="card-header"> <div class="card-header">
${customCard ${customCard
? `${this.hass!.localize( ? `${this.hass!.localize(
"ui.panel.lovelace.editor.cardpicker.custom_card" "ui.panel.lovelace.editor.cardpicker.custom_card"
)}: ${customCard.name || customCard.type}` )}: ${customCard.name || customCard.type}`
: this.hass!.localize( : name}
`ui.panel.lovelace.editor.card.${cardConfig.type}.name`
)}
</div> </div>
</div> </div>
`; `;