ha-frontend/src/dialogs/config-flow/step-flow-pick-handler.ts

224 lines
6.0 KiB
TypeScript

import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import Fuse from "fuse.js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import "../../common/search/search-input";
import { LocalizeFunc } from "../../common/translations/localize";
import "../../components/ha-icon-next";
import { domainToName } from "../../data/integration";
import { HomeAssistant } from "../../types";
import { brandsUrl } from "../../util/brands-url";
import { documentationUrl } from "../../util/documentation-url";
import { configFlowContentStyles } from "./styles";
interface HandlerObj {
name: string;
slug: string;
}
declare global {
// for fire event
interface HASSDomEvents {
"handler-picked": {
handler: string;
};
}
}
@customElement("step-flow-pick-handler")
class StepFlowPickHandler extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public handlers!: string[];
@state() private _filter?: string;
private _width?: number;
private _height?: number;
private _getHandlers = memoizeOne(
(h: string[], filter?: string, _localize?: LocalizeFunc) => {
const handlers: HandlerObj[] = h.map((handler) => ({
name: domainToName(this.hass.localize, handler),
slug: handler,
}));
if (filter) {
const options: Fuse.IFuseOptions<HandlerObj> = {
keys: ["name", "slug"],
isCaseSensitive: false,
minMatchCharLength: 2,
threshold: 0.2,
};
const fuse = new Fuse(handlers, options);
return fuse.search(filter).map((result) => result.item);
}
return handlers.sort((a, b) =>
a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
);
}
);
protected render(): TemplateResult {
const handlers = this._getHandlers(
this.handlers,
this._filter,
this.hass.localize
);
return html`
<h2>${this.hass.localize("ui.panel.config.integrations.new")}</h2>
<search-input
autofocus
.filter=${this._filter}
@value-changed=${this._filterChanged}
.label=${this.hass.localize("ui.panel.config.integrations.search")}
></search-input>
<div
style=${styleMap({
width: `${this._width}px`,
height: `${this._height}px`,
})}
>
${handlers.length
? handlers.map(
(handler: HandlerObj) =>
html`
<paper-icon-item
@click=${this._handlerPicked}
.handler=${handler}
>
<img
slot="item-icon"
loading="lazy"
src=${brandsUrl({
domain: handler.slug,
type: "icon",
useFallback: true,
darkOptimized: this.hass.selectedTheme?.dark,
})}
referrerpolicy="no-referrer"
/>
<paper-item-body> ${handler.name} </paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-icon-item>
`
)
: html`
<p>
${this.hass.localize(
"ui.panel.config.integrations.note_about_integrations"
)}<br />
${this.hass.localize(
"ui.panel.config.integrations.note_about_website_reference"
)}<a
href=${documentationUrl(
this.hass,
`/integrations/${
this._filter ? `#search/${this._filter}` : ""
}`
)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.integrations.home_assistant_website"
)}</a
>.
</p>
`}
</div>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
setTimeout(
() => this.shadowRoot!.querySelector("search-input")!.focus(),
0
);
}
protected updated(changedProps) {
super.updated(changedProps);
// Store the width and height so that when we search, box doesn't jump
const div = this.shadowRoot!.querySelector("div")!;
if (!this._width) {
const width = div.clientWidth;
if (width) {
this._width = width;
}
}
if (!this._height) {
const height = div.clientHeight;
if (height) {
this._height = height;
}
}
}
private async _filterChanged(e) {
this._filter = e.detail.value;
}
private async _handlerPicked(ev) {
fireEvent(this, "handler-picked", {
handler: ev.currentTarget.handler.slug,
});
}
static get styles(): CSSResultGroup {
return [
configFlowContentStyles,
css`
img {
width: 40px;
height: 40px;
}
search-input {
display: block;
margin: -12px 16px 0;
}
ha-icon-next {
margin-right: 8px;
}
div {
overflow: auto;
max-height: 600px;
}
h2 {
padding-right: 66px;
}
@media all and (max-height: 900px) {
div {
max-height: calc(100vh - 134px);
}
}
paper-icon-item {
cursor: pointer;
margin-bottom: 4px;
}
p {
text-align: center;
padding: 16px;
margin: 0;
}
p > a {
color: var(--primary-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"step-flow-pick-handler": StepFlowPickHandler;
}
}