Add search to integrations 🔍 (#5593)

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Aidan Timson 2020-04-27 19:42:37 +01:00 committed by GitHub
parent 01e5dfc9b3
commit 88217473f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 197 additions and 35 deletions

View File

@ -745,6 +745,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
position: relative;
top: 2px;
}
.search-toolbar search-input {
margin-left: 8px;
top: 1px;
}
.search-toolbar {
display: flex;
justify-content: space-between;

View File

@ -10,9 +10,14 @@ import {
PropertyValues,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import * as Fuse from "fuse.js";
import { compare } from "../../../common/string/compare";
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/ha-card";
import "../../../components/ha-fab";
@ -29,7 +34,7 @@ import {
localizeConfigFlowTitle,
subscribeConfigFlowInProgress,
} from "../../../data/config_flow";
import { DataEntryFlowProgress } from "../../../data/data_entry_flow";
import type { DataEntryFlowProgress } from "../../../data/data_entry_flow";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
@ -52,6 +57,15 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
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")
class HaConfigIntegrations extends SubscribeMixin(LitElement) {
@ -65,9 +79,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
@property() public route!: Route;
@property() private _configEntries: ConfigEntry[] = [];
@property() private _configEntries: ConfigEntryExtended[] = [];
@property() private _configEntriesInProgress: DataEntryFlowProgress[] = [];
@property()
private _configEntriesInProgress: DataEntryFlowProgressExtended[] = [];
@property() private _entityRegistryEntries: EntityRegistryEntry[] = [];
@ -79,6 +94,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
window.location.hash.substring(1)
);
@property() private _filter?: string;
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection, (entries) => {
@ -87,18 +104,72 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._deviceRegistryEntries = entries;
}),
subscribeConfigFlowInProgress(this.hass, (flowsInProgress) => {
this._configEntriesInProgress = flowsInProgress;
for (const flow of flowsInProgress) {
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
const translationsPromisses: Promise<void>[] = [];
flowsInProgress.forEach((flow) => {
// To render 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) {
super.firstUpdated(changed);
this._loadConfigEntries();
@ -126,6 +197,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
}
protected render(): TemplateResult {
const configEntries = this._filterConfigEntries(
this._configEntries,
this._filter
);
const configEntriesInProgress = this._filterConfigEntriesInProgress(
this._configEntriesInProgress,
this._filter
);
return html`
<hass-tabs-subpage
.hass=${this.hass}
@ -134,6 +214,21 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
.route=${this.route}
.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
close-on-activate
no-animations
@ -146,11 +241,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
slot="dropdown-trigger"
alt="menu"
></paper-icon-button>
<paper-listbox
slot="dropdown-content"
role="listbox"
selected="{{selectedItem}}"
>
<paper-listbox slot="dropdown-content" role="listbox">
<paper-item @tap=${this._toggleShowIgnored}>
${this.hass.localize(
this._showIgnored
@ -161,12 +252,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
</paper-listbox>
</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">
${this._showIgnored
? this._configEntries
? configEntries
.filter((item) => item.source === "ignore")
.map(
(item: ConfigEntry) => html`
(item: ConfigEntryExtended) => html`
<ha-card class="ignored">
<div class="header">
${this.hass.localize(
@ -183,7 +287,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
/>
</div>
<h2>
${domainToName(this.hass.localize, item.domain)}
${item.localized_domain_name}
</h2>
<mwc-button
@click=${this._removeIgnoredIntegration}
@ -200,9 +304,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
`
)
: ""}
${this._configEntriesInProgress.length
? this._configEntriesInProgress.map(
(flow) => html`
${configEntriesInProgress.length
? configEntriesInProgress.map(
(flow: DataEntryFlowProgressExtended) => html`
<ha-card class="discovered">
<div class="header">
${this.hass.localize(
@ -219,7 +323,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
/>
</div>
<h2>
${localizeConfigFlowTitle(this.hass.localize, flow)}
${flow.localized_title}
</h2>
<mwc-button
unelevated
@ -248,14 +352,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
`
)
: ""}
${this._configEntries.length
? this._configEntries.map((item: any) => {
${configEntries.length
? configEntries.map((item: ConfigEntryExtended) => {
const devices = this._getDevices(item);
const entities = this._getEntities(item);
const integrationName = domainToName(
this.hass.localize,
item.domain
);
return item.source === "ignore"
? ""
: html`
@ -274,10 +374,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
/>
</div>
<h1>
${integrationName}
${item.localized_domain_name}
</h1>
<h2>
${integrationName === item.title
${item.localized_domain_name === item.title
? html`&nbsp;`
: item.title}
</h2>
@ -365,7 +465,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
</ha-card>
`;
})
: html`
: !this._configEntries.length
? html`
<ha-card>
<div class="card-content">
<h1>
@ -383,7 +484,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
>
</div>
</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>
<ha-fab
icon="hass:plus"
@ -400,8 +521,18 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
private _loadConfigEntries() {
getConfigEntries(this.hass).then((configEntries) => {
this._configEntries = configEntries.sort((conf1, conf2) =>
this._configEntries = configEntries
.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[] {
return [
haStyle,
@ -573,7 +708,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 16px 16px;
padding: 16px;
padding: 8px 16px 16px;
margin-bottom: 64px;
}
ha-card {
@ -630,6 +765,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
margin-bottom: 16px;
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 {
max-height: 60px;
max-width: 90%;

View File

@ -1351,6 +1351,8 @@
"home_assistant_website": "Home Assistant website",
"configure": "Configure",
"none": "Nothing configured yet",
"none_found": "No integrations found",
"none_found_detail": "Adjust your search criteria.",
"integration_not_found": "Integration not found.",
"details": "Integration details",
"rename_dialog": "Edit the name of this config entry",