mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 20:56:36 +00:00
Add search to integrations 🔍 (#5593)
Co-Authored-By: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
01e5dfc9b3
commit
88217473f7
@ -745,6 +745,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
.search-toolbar search-input {
|
||||||
|
margin-left: 8px;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
.search-toolbar {
|
.search-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -10,9 +10,14 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import * as Fuse from "fuse.js";
|
||||||
import { compare } from "../../../common/string/compare";
|
import { compare } from "../../../common/string/compare";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
import { afterNextRender } from "../../../common/util/render-status";
|
import {
|
||||||
|
afterNextRender,
|
||||||
|
nextRender,
|
||||||
|
} from "../../../common/util/render-status";
|
||||||
import "../../../components/entity/ha-state-icon";
|
import "../../../components/entity/ha-state-icon";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
@ -29,7 +34,7 @@ import {
|
|||||||
localizeConfigFlowTitle,
|
localizeConfigFlowTitle,
|
||||||
subscribeConfigFlowInProgress,
|
subscribeConfigFlowInProgress,
|
||||||
} from "../../../data/config_flow";
|
} from "../../../data/config_flow";
|
||||||
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
import type { DataEntryFlowProgress } from "../../../data/data_entry_flow";
|
||||||
import {
|
import {
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
subscribeDeviceRegistry,
|
subscribeDeviceRegistry,
|
||||||
@ -52,6 +57,15 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
|
import "../../../common/search/search-input";
|
||||||
|
|
||||||
|
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
||||||
|
localized_title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfigEntryExtended extends ConfigEntry {
|
||||||
|
localized_domain_name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("ha-config-integrations")
|
@customElement("ha-config-integrations")
|
||||||
class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||||
@ -65,9 +79,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
|
|
||||||
@property() private _configEntries: ConfigEntry[] = [];
|
@property() private _configEntries: ConfigEntryExtended[] = [];
|
||||||
|
|
||||||
@property() private _configEntriesInProgress: DataEntryFlowProgress[] = [];
|
@property()
|
||||||
|
private _configEntriesInProgress: DataEntryFlowProgressExtended[] = [];
|
||||||
|
|
||||||
@property() private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
@property() private _entityRegistryEntries: EntityRegistryEntry[] = [];
|
||||||
|
|
||||||
@ -79,6 +94,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
window.location.hash.substring(1)
|
window.location.hash.substring(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@property() private _filter?: string;
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
return [
|
return [
|
||||||
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
subscribeEntityRegistry(this.hass.connection, (entries) => {
|
||||||
@ -87,18 +104,72 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
subscribeDeviceRegistry(this.hass.connection, (entries) => {
|
||||||
this._deviceRegistryEntries = entries;
|
this._deviceRegistryEntries = entries;
|
||||||
}),
|
}),
|
||||||
subscribeConfigFlowInProgress(this.hass, (flowsInProgress) => {
|
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
|
||||||
this._configEntriesInProgress = flowsInProgress;
|
const translationsPromisses: Promise<void>[] = [];
|
||||||
for (const flow of flowsInProgress) {
|
flowsInProgress.forEach((flow) => {
|
||||||
// To render title placeholders
|
// To render title placeholders
|
||||||
if (flow.context.title_placeholders) {
|
if (flow.context.title_placeholders) {
|
||||||
this.hass.loadBackendTranslation("config", flow.handler);
|
translationsPromisses.push(
|
||||||
|
this.hass.loadBackendTranslation("config", flow.handler)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
await Promise.all(translationsPromisses);
|
||||||
|
await nextRender();
|
||||||
|
this._configEntriesInProgress = flowsInProgress.map((flow) => {
|
||||||
|
return {
|
||||||
|
...flow,
|
||||||
|
localized_title: localizeConfigFlowTitle(this.hass.localize, flow),
|
||||||
|
};
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _filterConfigEntries = memoizeOne(
|
||||||
|
(
|
||||||
|
configEntries: ConfigEntryExtended[],
|
||||||
|
filter?: string
|
||||||
|
): ConfigEntryExtended[] => {
|
||||||
|
if (!filter) {
|
||||||
|
return configEntries;
|
||||||
|
}
|
||||||
|
const options: Fuse.FuseOptions<ConfigEntryExtended> = {
|
||||||
|
keys: ["domain", "localized_domain_name", "title"],
|
||||||
|
caseSensitive: false,
|
||||||
|
minMatchCharLength: 2,
|
||||||
|
threshold: 0.2,
|
||||||
|
};
|
||||||
|
const fuse = new Fuse(configEntries, options);
|
||||||
|
return fuse.search(filter);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _filterConfigEntriesInProgress = memoizeOne(
|
||||||
|
(
|
||||||
|
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
||||||
|
filter?: string
|
||||||
|
): DataEntryFlowProgressExtended[] => {
|
||||||
|
configEntriesInProgress = configEntriesInProgress.map(
|
||||||
|
(flow: DataEntryFlowProgressExtended) => ({
|
||||||
|
...flow,
|
||||||
|
title: localizeConfigFlowTitle(this.hass.localize, flow),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (!filter) {
|
||||||
|
return configEntriesInProgress;
|
||||||
|
}
|
||||||
|
const options: Fuse.FuseOptions<DataEntryFlowProgressExtended> = {
|
||||||
|
keys: ["handler", "localized_title"],
|
||||||
|
caseSensitive: false,
|
||||||
|
minMatchCharLength: 2,
|
||||||
|
threshold: 0.2,
|
||||||
|
};
|
||||||
|
const fuse = new Fuse(configEntriesInProgress, options);
|
||||||
|
return fuse.search(filter);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
protected firstUpdated(changed: PropertyValues) {
|
protected firstUpdated(changed: PropertyValues) {
|
||||||
super.firstUpdated(changed);
|
super.firstUpdated(changed);
|
||||||
this._loadConfigEntries();
|
this._loadConfigEntries();
|
||||||
@ -126,6 +197,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
const configEntries = this._filterConfigEntries(
|
||||||
|
this._configEntries,
|
||||||
|
this._filter
|
||||||
|
);
|
||||||
|
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||||
|
this._configEntriesInProgress,
|
||||||
|
this._filter
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -134,6 +214,21 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.integrations}
|
.tabs=${configSections.integrations}
|
||||||
>
|
>
|
||||||
|
${this.narrow
|
||||||
|
? html`
|
||||||
|
<div slot="header">
|
||||||
|
<slot name="header">
|
||||||
|
<search-input
|
||||||
|
.filter=${this._filter}
|
||||||
|
class="header"
|
||||||
|
no-label-float
|
||||||
|
no-underline
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
></search-input>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
<paper-menu-button
|
<paper-menu-button
|
||||||
close-on-activate
|
close-on-activate
|
||||||
no-animations
|
no-animations
|
||||||
@ -146,11 +241,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
slot="dropdown-trigger"
|
slot="dropdown-trigger"
|
||||||
alt="menu"
|
alt="menu"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
<paper-listbox
|
<paper-listbox slot="dropdown-content" role="listbox">
|
||||||
slot="dropdown-content"
|
|
||||||
role="listbox"
|
|
||||||
selected="{{selectedItem}}"
|
|
||||||
>
|
|
||||||
<paper-item @tap=${this._toggleShowIgnored}>
|
<paper-item @tap=${this._toggleShowIgnored}>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
this._showIgnored
|
this._showIgnored
|
||||||
@ -161,12 +252,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
</paper-listbox>
|
</paper-listbox>
|
||||||
</paper-menu-button>
|
</paper-menu-button>
|
||||||
|
|
||||||
|
${!this.narrow
|
||||||
|
? html`
|
||||||
|
<div class="search">
|
||||||
|
<search-input
|
||||||
|
no-label-float
|
||||||
|
no-underline
|
||||||
|
.filter=${this._filter}
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
></search-input>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
${this._showIgnored
|
${this._showIgnored
|
||||||
? this._configEntries
|
? configEntries
|
||||||
.filter((item) => item.source === "ignore")
|
.filter((item) => item.source === "ignore")
|
||||||
.map(
|
.map(
|
||||||
(item: ConfigEntry) => html`
|
(item: ConfigEntryExtended) => html`
|
||||||
<ha-card class="ignored">
|
<ha-card class="ignored">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@ -183,7 +287,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2>
|
<h2>
|
||||||
${domainToName(this.hass.localize, item.domain)}
|
${item.localized_domain_name}
|
||||||
</h2>
|
</h2>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@click=${this._removeIgnoredIntegration}
|
@click=${this._removeIgnoredIntegration}
|
||||||
@ -200,9 +304,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
: ""}
|
: ""}
|
||||||
${this._configEntriesInProgress.length
|
${configEntriesInProgress.length
|
||||||
? this._configEntriesInProgress.map(
|
? configEntriesInProgress.map(
|
||||||
(flow) => html`
|
(flow: DataEntryFlowProgressExtended) => html`
|
||||||
<ha-card class="discovered">
|
<ha-card class="discovered">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@ -219,7 +323,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2>
|
<h2>
|
||||||
${localizeConfigFlowTitle(this.hass.localize, flow)}
|
${flow.localized_title}
|
||||||
</h2>
|
</h2>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
unelevated
|
unelevated
|
||||||
@ -248,14 +352,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
: ""}
|
: ""}
|
||||||
${this._configEntries.length
|
${configEntries.length
|
||||||
? this._configEntries.map((item: any) => {
|
? configEntries.map((item: ConfigEntryExtended) => {
|
||||||
const devices = this._getDevices(item);
|
const devices = this._getDevices(item);
|
||||||
const entities = this._getEntities(item);
|
const entities = this._getEntities(item);
|
||||||
const integrationName = domainToName(
|
|
||||||
this.hass.localize,
|
|
||||||
item.domain
|
|
||||||
);
|
|
||||||
return item.source === "ignore"
|
return item.source === "ignore"
|
||||||
? ""
|
? ""
|
||||||
: html`
|
: html`
|
||||||
@ -274,10 +374,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h1>
|
<h1>
|
||||||
${integrationName}
|
${item.localized_domain_name}
|
||||||
</h1>
|
</h1>
|
||||||
<h2>
|
<h2>
|
||||||
${integrationName === item.title
|
${item.localized_domain_name === item.title
|
||||||
? html` `
|
? html` `
|
||||||
: item.title}
|
: item.title}
|
||||||
</h2>
|
</h2>
|
||||||
@ -365,7 +465,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
})
|
})
|
||||||
: html`
|
: !this._configEntries.length
|
||||||
|
? html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<h1>
|
<h1>
|
||||||
@ -383,7 +484,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`}
|
`
|
||||||
|
: ""}
|
||||||
|
${this._filter &&
|
||||||
|
!configEntriesInProgress.length &&
|
||||||
|
!configEntries.length &&
|
||||||
|
this._configEntries.length
|
||||||
|
? html`
|
||||||
|
<div class="none-found">
|
||||||
|
<h1>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.none_found"
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.none_found_detail"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
icon="hass:plus"
|
icon="hass:plus"
|
||||||
@ -400,9 +521,19 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _loadConfigEntries() {
|
private _loadConfigEntries() {
|
||||||
getConfigEntries(this.hass).then((configEntries) => {
|
getConfigEntries(this.hass).then((configEntries) => {
|
||||||
this._configEntries = configEntries.sort((conf1, conf2) =>
|
this._configEntries = configEntries
|
||||||
compare(conf1.domain + conf1.title, conf2.domain + conf2.title)
|
.sort((conf1, conf2) =>
|
||||||
);
|
compare(conf1.domain + conf1.title, conf2.domain + conf2.title)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(entry: ConfigEntry): ConfigEntryExtended => ({
|
||||||
|
...entry,
|
||||||
|
localized_domain_name: domainToName(
|
||||||
|
this.hass.localize,
|
||||||
|
entry.domain
|
||||||
|
),
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,6 +696,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
|
this._filter = ev.detail.value;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@ -573,7 +708,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
grid-gap: 16px 16px;
|
grid-gap: 16px 16px;
|
||||||
padding: 16px;
|
padding: 8px 16px 16px;
|
||||||
margin-bottom: 64px;
|
margin-bottom: 64px;
|
||||||
}
|
}
|
||||||
ha-card {
|
ha-card {
|
||||||
@ -630,6 +765,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
.none-found {
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
search-input.header {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
left: -8px;
|
||||||
|
top: -7px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
.search {
|
||||||
|
padding: 0 16px;
|
||||||
|
background: var(--sidebar-background-color);
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
.search search-input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
img {
|
img {
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
|
@ -1351,6 +1351,8 @@
|
|||||||
"home_assistant_website": "Home Assistant website",
|
"home_assistant_website": "Home Assistant website",
|
||||||
"configure": "Configure",
|
"configure": "Configure",
|
||||||
"none": "Nothing configured yet",
|
"none": "Nothing configured yet",
|
||||||
|
"none_found": "No integrations found",
|
||||||
|
"none_found_detail": "Adjust your search criteria.",
|
||||||
"integration_not_found": "Integration not found.",
|
"integration_not_found": "Integration not found.",
|
||||||
"details": "Integration details",
|
"details": "Integration details",
|
||||||
"rename_dialog": "Edit the name of this config entry",
|
"rename_dialog": "Edit the name of this config entry",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user