mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
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:
parent
13761a20c5
commit
a89f0bd1cd
@ -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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
13
hassio/src/components/hassio-filter-addons.ts
Normal file
13
hassio/src/components/hassio-filter-addons.ts
Normal 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);
|
||||||
|
}
|
82
hassio/src/components/hassio-search-input.ts
Normal file
82
hassio/src/components/hassio-search-input.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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",
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user