From 88217473f727fccbd89b0f985819d580cd8f24e4 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Mon, 27 Apr 2020 19:42:37 +0100 Subject: [PATCH] =?UTF-8?q?Add=20search=20to=20integrations=20=F0=9F=94=8D?= =?UTF-8?q?=20(#5593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Bram Kragten --- .../config/entities/ha-config-entities.ts | 4 + .../integrations/ha-config-integrations.ts | 226 +++++++++++++++--- src/translations/en.json | 2 + 3 files changed, 197 insertions(+), 35 deletions(-) diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 2da4fb17d4..21585f8001 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -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; diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 806e2e74c6..f347ce34da 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -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[] = []; + 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 = { + 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 = { + 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` + ${this.narrow + ? html` +
+ + + +
+ ` + : ""} - + ${this.hass.localize( this._showIgnored @@ -161,12 +252,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { + ${!this.narrow + ? html` + + ` + : ""} +
${this._showIgnored - ? this._configEntries + ? configEntries .filter((item) => item.source === "ignore") .map( - (item: ConfigEntry) => html` + (item: ConfigEntryExtended) => html`
${this.hass.localize( @@ -183,7 +287,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { />

- ${domainToName(this.hass.localize, item.domain)} + ${item.localized_domain_name}

html` + ${configEntriesInProgress.length + ? configEntriesInProgress.map( + (flow: DataEntryFlowProgressExtended) => html`
${this.hass.localize( @@ -219,7 +323,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { />

- ${localizeConfigFlowTitle(this.hass.localize, flow)} + ${flow.localized_title}

{ + ${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) { />

- ${integrationName} + ${item.localized_domain_name}

- ${integrationName === item.title + ${item.localized_domain_name === item.title ? html` ` : item.title}

@@ -365,7 +465,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { `; }) - : html` + : !this._configEntries.length + ? html`

@@ -383,7 +484,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { >

- `} + ` + : ""} + ${this._filter && + !configEntriesInProgress.length && + !configEntries.length && + this._configEntries.length + ? html` +
+

+ ${this.hass.localize( + "ui.panel.config.integrations.none_found" + )} +

+

+ ${this.hass.localize( + "ui.panel.config.integrations.none_found_detail" + )} +

+
+ ` + : ""} { - this._configEntries = configEntries.sort((conf1, conf2) => - compare(conf1.domain + conf1.title, conf2.domain + conf2.title) - ); + 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%; diff --git a/src/translations/en.json b/src/translations/en.json index 2c959b6786..883374aea1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -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",