mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
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:
parent
97c454aa0d
commit
f57754212c
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user