Add Search to Hassio add-on store (#3108)

*  Add search to hassio add-ons

* 👕 Fix linter error

* 👕 Lint fixes

* 🔥 Remove search from dashboard for this PR

* 🔥 Remove search from dasboard in this PR

* 🔨 Suggested changes

* 🔨 Change to fireEvent

* 🔨 Convert definition

* 🔥 Fix imports

* 🔥 Revert styling test

* 🔨 Fix search

* 🔨 CSS fix

* 🔨 Add smaller message to show no results found in repo

* 🔨 Fixes

* 🔨 CSS fixes

* 🔨 Add types

* 🎨 Max width

* 🔨 Fix margin jump

* 🔨 Add working memoizeOne

* 👕 Fix linting / error on build
This commit is contained in:
Timmo 2019-05-12 10:13:16 +01:00 committed by Pascal Vizeli
parent 13761a20c5
commit a89f0bd1cd
6 changed files with 162 additions and 25 deletions

View File

@ -7,6 +7,7 @@ import {
CSSResultArray, CSSResultArray,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-card/paper-card"; import "@polymer/paper-card/paper-card";
import memoizeOne from "memoize-one";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@ -16,14 +17,40 @@ import {
HassioAddonRepository, HassioAddonRepository,
} from "../../../src/data/hassio"; } from "../../../src/data/hassio";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { filterAndSort } from "../components/hassio-filter-addons";
class HassioAddonRepositoryEl extends LitElement { class HassioAddonRepositoryEl extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public repo!: HassioAddonRepository; @property() public repo!: HassioAddonRepository;
@property() public addons!: HassioAddonInfo[]; @property() public addons!: HassioAddonInfo[];
@property() public filter!: string;
private _getAddons = memoizeOne(
(addons: HassioAddonInfo[], filter?: string) => {
if (filter) {
return filterAndSort(addons, filter);
}
return addons.sort((a, b) =>
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
);
}
);
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
const repo = this.repo; const repo = this.repo;
const addons = this._getAddons(this.addons, this.filter);
if (this.filter && addons.length < 1) {
return html`
<div class="card-group">
<div class="title">
<div class="description">
No results found in "${repo.name}"
</div>
</div>
</div>
`;
}
return html` return html`
<div class="card-group"> <div class="card-group">
<div class="title"> <div class="title">
@ -34,31 +61,27 @@ class HassioAddonRepositoryEl extends LitElement {
</div> </div>
</div> </div>
${this.addons ${addons.map(
.sort((a, b) => (addon) => html`
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1 <paper-card
) .addon=${addon}
.map( class=${addon.available ? "" : "not_available"}
(addon) => html` @click=${this.addonTapped}
<paper-card >
.addon=${addon} <div class="card-content">
class=${addon.available ? "" : "not_available"} <hassio-card-content
@click=${this.addonTapped} .hass=${this.hass}
> .title=${addon.name}
<div class="card-content"> .description=${addon.description}
<hassio-card-content .available=${addon.available}
.hass=${this.hass} .icon=${this.computeIcon(addon)}
.title=${addon.name} .iconTitle=${this.computeIconTitle(addon)}
.description=${addon.description} .iconClass=${this.computeIconClass(addon)}
.available=${addon.available} ></hassio-card-content>
.icon=${this.computeIcon(addon)} </div>
.iconTitle=${this.computeIconTitle(addon)} </paper-card>
.iconClass=${this.computeIconClass(addon)} `
></hassio-card-content> )}
</div>
</paper-card>
`
)}
</div> </div>
`; `;
} }

View File

@ -16,6 +16,7 @@ import {
reloadHassioAddons, reloadHassioAddons,
} from "../../../src/data/hassio"; } from "../../../src/data/hassio";
import "../../../src/layouts/loading-screen"; import "../../../src/layouts/loading-screen";
import "../components/hassio-search-input";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => { const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
if (a.slug === "local") { if (a.slug === "local") {
@ -37,10 +38,12 @@ class HassioAddonStore extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() private _addons?: HassioAddonInfo[]; @property() private _addons?: HassioAddonInfo[];
@property() private _repos?: HassioAddonRepository[]; @property() private _repos?: HassioAddonRepository[];
@property() private _filter?: string;
public async refreshData() { public async refreshData() {
this._repos = undefined; this._repos = undefined;
this._addons = undefined; this._addons = undefined;
this._filter = undefined;
await reloadHassioAddons(this.hass); await reloadHassioAddons(this.hass);
await this._loadData(); await this._loadData();
} }
@ -67,6 +70,7 @@ class HassioAddonStore extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.repo=${repo} .repo=${repo}
.addons=${addons} .addons=${addons}
.filter=${this._filter}
></hassio-addon-repository> ></hassio-addon-repository>
`); `);
} }
@ -77,6 +81,11 @@ class HassioAddonStore extends LitElement {
.repos=${this._repos} .repos=${this._repos}
></hassio-repositories-editor> ></hassio-repositories-editor>
<hassio-search-input
.filter=${this._filter}
@value-changed=${this._filterChanged}
></hassio-search-input>
${repos} ${repos}
`; `;
} }
@ -104,6 +113,10 @@ class HassioAddonStore extends LitElement {
} }
} }
private async _filterChanged(e) {
this._filter = e.detail.value;
}
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
hassio-addon-repository { hassio-addon-repository {

View File

@ -0,0 +1,13 @@
import { HassioAddonInfo } from "../../../src/data/hassio";
import * as Fuse from "fuse.js";
export function filterAndSort(addons: HassioAddonInfo[], filter: string) {
const options: Fuse.FuseOptions<HassioAddonInfo> = {
keys: ["name", "description", "slug"],
caseSensitive: false,
minMatchCharLength: 2,
threshold: 0.2,
};
const fuse = new Fuse(addons, options);
return fuse.search(filter);
}

View File

@ -0,0 +1,82 @@
import { TemplateResult, html } from "lit-html";
import {
css,
CSSResult,
customElement,
LitElement,
property,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-icon-button/paper-icon-button";
import "@material/mwc-button";
@customElement("hassio-search-input")
class HassioSearchInput extends LitElement {
@property() private filter?: string;
protected render(): TemplateResult | void {
return html`
<div class="search-container">
<paper-input
label="Search"
.value=${this.filter}
@value-changed=${this._filterInputChanged}
>
<iron-icon
icon="hassio:magnify"
slot="prefix"
class="prefix"
></iron-icon>
${this.filter &&
html`
<paper-icon-button
slot="suffix"
class="suffix"
@click=${this._clearSearch}
icon="hassio:close"
alt="Clear"
title="Clear"
></paper-icon-button>
`}
</paper-input>
</div>
`;
}
private async _filterChanged(value: string) {
fireEvent(this, "value-changed", { value: String(value) });
}
private async _filterInputChanged(e) {
this._filterChanged(e.target.value);
}
private async _clearSearch() {
this._filterChanged("");
}
static get styles(): CSSResult {
return css`
paper-input {
flex: 1 1 auto;
margin: 0 16px;
}
.search-container {
display: inline-flex;
width: 100%;
align-items: center;
}
.prefix {
margin: 8px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-search-input": HassioSearchInput;
}
}

View File

@ -74,6 +74,7 @@
"deep-clone-simple": "^1.1.1", "deep-clone-simple": "^1.1.1",
"es6-object-assign": "^1.1.0", "es6-object-assign": "^1.1.0",
"fecha": "^3.0.2", "fecha": "^3.0.2",
"fuse.js": "^3.4.4",
"hls.js": "^0.12.4", "hls.js": "^0.12.4",
"home-assistant-js-websocket": "^4.1.2", "home-assistant-js-websocket": "^4.1.2",
"intl-messageformat": "^2.2.0", "intl-messageformat": "^2.2.0",

View File

@ -6489,6 +6489,11 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
fuse.js@^3.4.4:
version "3.4.4"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.4.tgz#f98f55fcb3b595cf6a3e629c5ffaf10982103e95"
integrity sha512-pyLQo/1oR5Ywf+a/tY8z4JygnIglmRxVUOiyFAbd11o9keUDpUJSMGRWJngcnkURj30kDHPmhoKY8ChJiz3EpQ==
g-status@^2.0.2: g-status@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/g-status/-/g-status-2.0.2.tgz#270fd32119e8fc9496f066fe5fe88e0a6bc78b97" resolved "https://registry.yarnpkg.com/g-status/-/g-status-2.0.2.tgz#270fd32119e8fc9496f066fe5fe88e0a6bc78b97"