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" })
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 {
</style>
<paper-input
class=${classMap({ "no-underline": this.noUnderline })}
autofocus
.autofocus=${this.autofocus}
label="Search"
.value=${this.filter}
@value-changed=${this._filterInputChanged}

View File

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

View File

@ -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<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 {
if (
!this.hass ||
@ -82,50 +127,16 @@ export class HuiCardPicker extends LitElement {
}
return html`
<search-input
.filter=${this._filter}
no-label-float
@value-changed=${this._handleSearchChange}
></search-input>
<div class="cards-container">
${previewCards.map((type: string) => {
return html`
${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>
`
)}
`;
})}
${this._filterCards(this._cards, this._filter).map(
(cardElement: CardElement) => cardElement.element
)}
</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="card"
@ -179,6 +190,56 @@ export class HuiCardPicker extends LitElement {
!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();
}
@ -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<TemplateResult> {
private async _renderCardElement(card: Card): Promise<TemplateResult> {
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}
</div>
<div class="card-header">
${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}
</div>
</div>
`;