mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 15:26:36 +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" })
|
@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}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
`;
|
`;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user