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,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import memoizeOne from "memoize-one";
import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style";
@ -16,14 +17,40 @@ import {
HassioAddonRepository,
} from "../../../src/data/hassio";
import { navigate } from "../../../src/common/navigate";
import { filterAndSort } from "../components/hassio-filter-addons";
class HassioAddonRepositoryEl extends LitElement {
@property() public hass!: HomeAssistant;
@property() public repo!: HassioAddonRepository;
@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 {
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`
<div class="card-group">
<div class="title">
@ -34,31 +61,27 @@ class HassioAddonRepositoryEl extends LitElement {
</div>
</div>
${this.addons
.sort((a, b) =>
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
)
.map(
(addon) => html`
<paper-card
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this.addonTapped}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${addon.name}
.description=${addon.description}
.available=${addon.available}
.icon=${this.computeIcon(addon)}
.iconTitle=${this.computeIconTitle(addon)}
.iconClass=${this.computeIconClass(addon)}
></hassio-card-content>
</div>
</paper-card>
`
)}
${addons.map(
(addon) => html`
<paper-card
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this.addonTapped}
>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${addon.name}
.description=${addon.description}
.available=${addon.available}
.icon=${this.computeIcon(addon)}
.iconTitle=${this.computeIconTitle(addon)}
.iconClass=${this.computeIconClass(addon)}
></hassio-card-content>
</div>
</paper-card>
`
)}
</div>
`;
}

View File

@ -16,6 +16,7 @@ import {
reloadHassioAddons,
} from "../../../src/data/hassio";
import "../../../src/layouts/loading-screen";
import "../components/hassio-search-input";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
if (a.slug === "local") {
@ -37,10 +38,12 @@ class HassioAddonStore extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _addons?: HassioAddonInfo[];
@property() private _repos?: HassioAddonRepository[];
@property() private _filter?: string;
public async refreshData() {
this._repos = undefined;
this._addons = undefined;
this._filter = undefined;
await reloadHassioAddons(this.hass);
await this._loadData();
}
@ -67,6 +70,7 @@ class HassioAddonStore extends LitElement {
.hass=${this.hass}
.repo=${repo}
.addons=${addons}
.filter=${this._filter}
></hassio-addon-repository>
`);
}
@ -77,6 +81,11 @@ class HassioAddonStore extends LitElement {
.repos=${this._repos}
></hassio-repositories-editor>
<hassio-search-input
.filter=${this._filter}
@value-changed=${this._filterChanged}
></hassio-search-input>
${repos}
`;
}
@ -104,6 +113,10 @@ class HassioAddonStore extends LitElement {
}
}
private async _filterChanged(e) {
this._filter = e.detail.value;
}
static get styles(): CSSResult {
return css`
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",
"es6-object-assign": "^1.1.0",
"fecha": "^3.0.2",
"fuse.js": "^3.4.4",
"hls.js": "^0.12.4",
"home-assistant-js-websocket": "^4.1.2",
"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"
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:
version "2.0.2"
resolved "https://registry.yarnpkg.com/g-status/-/g-status-2.0.2.tgz#270fd32119e8fc9496f066fe5fe88e0a6bc78b97"