mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 00:06:35 +00:00
Group config entries by integration (#5646)
This commit is contained in:
parent
462c1f94d6
commit
2abfd0392d
@ -12,7 +12,7 @@ import {
|
|||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import * as Fuse from "fuse.js";
|
import * as Fuse from "fuse.js";
|
||||||
import { compare } from "../../../common/string/compare";
|
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
import {
|
import {
|
||||||
afterNextRender,
|
afterNextRender,
|
||||||
@ -25,7 +25,6 @@ import {
|
|||||||
ConfigEntry,
|
ConfigEntry,
|
||||||
deleteConfigEntry,
|
deleteConfigEntry,
|
||||||
getConfigEntries,
|
getConfigEntries,
|
||||||
updateConfigEntry,
|
|
||||||
} from "../../../data/config_entries";
|
} from "../../../data/config_entries";
|
||||||
import {
|
import {
|
||||||
DISCOVERY_SOURCES,
|
DISCOVERY_SOURCES,
|
||||||
@ -44,29 +43,44 @@ import {
|
|||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { domainToName } from "../../../data/integration";
|
import { domainToName } from "../../../data/integration";
|
||||||
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
|
||||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import {
|
|
||||||
showAlertDialog,
|
|
||||||
showConfirmationDialog,
|
|
||||||
showPromptDialog,
|
|
||||||
} from "../../../dialogs/generic/show-dialog-box";
|
|
||||||
import "../../../layouts/hass-tabs-subpage";
|
import "../../../layouts/hass-tabs-subpage";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import "../../../common/search/search-input";
|
import "../../../common/search/search-input";
|
||||||
|
import "./ha-integration-card";
|
||||||
|
import type {
|
||||||
|
ConfigEntryRemovedEvent,
|
||||||
|
ConfigEntryUpdatedEvent,
|
||||||
|
HaIntegrationCard,
|
||||||
|
} from "./ha-integration-card";
|
||||||
|
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
||||||
localized_title?: string;
|
localized_title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigEntryExtended extends ConfigEntry {
|
export interface ConfigEntryExtended extends ConfigEntry {
|
||||||
localized_domain_name?: string;
|
localized_domain_name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupByIntegration = (
|
||||||
|
entries: ConfigEntryExtended[]
|
||||||
|
): Map<string, ConfigEntryExtended[]> => {
|
||||||
|
const result = new Map();
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (result.has(entry.domain)) {
|
||||||
|
result.get(entry.domain).push(entry);
|
||||||
|
} else {
|
||||||
|
result.set(entry.domain, [entry]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("ha-config-integrations")
|
@customElement("ha-config-integrations")
|
||||||
class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -145,6 +159,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private _filterGroupConfigEntries = memoizeOne(
|
||||||
|
(
|
||||||
|
configEntries: ConfigEntryExtended[],
|
||||||
|
filter?: string
|
||||||
|
): [Map<string, ConfigEntryExtended[]>, ConfigEntryExtended[]] => {
|
||||||
|
const filteredConfigEnties = this._filterConfigEntries(
|
||||||
|
configEntries,
|
||||||
|
filter
|
||||||
|
);
|
||||||
|
const ignored: ConfigEntryExtended[] = [];
|
||||||
|
filteredConfigEnties.forEach((item, index) => {
|
||||||
|
if (item.source === "ignore") {
|
||||||
|
ignored.push(filteredConfigEnties.splice(index, 1)[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return [groupByIntegration(filteredConfigEnties), ignored];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private _filterConfigEntriesInProgress = memoizeOne(
|
private _filterConfigEntriesInProgress = memoizeOne(
|
||||||
(
|
(
|
||||||
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
configEntriesInProgress: DataEntryFlowProgressExtended[],
|
||||||
@ -185,22 +218,32 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
this._configEntries.length
|
this._configEntries.length
|
||||||
) {
|
) {
|
||||||
afterNextRender(() => {
|
afterNextRender(() => {
|
||||||
const card = this.shadowRoot!.getElementById(
|
const entryId = this._searchParms.get("config_entry")!;
|
||||||
this._searchParms.get("config_entry")!
|
const configEntry = this._configEntries.find(
|
||||||
|
(entry) => entry.entry_id === entryId
|
||||||
);
|
);
|
||||||
|
if (!configEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const card: HaIntegrationCard = this.shadowRoot!.querySelector(
|
||||||
|
`[data-domain=${configEntry?.domain}]`
|
||||||
|
) as HaIntegrationCard;
|
||||||
if (card) {
|
if (card) {
|
||||||
card.scrollIntoView();
|
card.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
card.classList.add("highlight");
|
card.classList.add("highlight");
|
||||||
|
card.selectedConfigEntryId = entryId;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const configEntries = this._filterConfigEntries(
|
const [
|
||||||
this._configEntries,
|
groupedConfigEntries,
|
||||||
this._filter
|
ignoredConfigEntries,
|
||||||
);
|
] = this._filterGroupConfigEntries(this._configEntries, this._filter);
|
||||||
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
const configEntriesInProgress = this._filterConfigEntriesInProgress(
|
||||||
this._configEntriesInProgress,
|
this._configEntriesInProgress,
|
||||||
this._filter
|
this._filter
|
||||||
@ -265,44 +308,46 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<div class="container">
|
<div
|
||||||
|
class="container"
|
||||||
|
@entry-removed=${this._handleRemoved}
|
||||||
|
@entry-updated=${this._handleUpdated}
|
||||||
|
>
|
||||||
${this._showIgnored
|
${this._showIgnored
|
||||||
? configEntries
|
? ignoredConfigEntries.map(
|
||||||
.filter((item) => item.source === "ignore")
|
(item: ConfigEntryExtended) => html`
|
||||||
.map(
|
<ha-card class="ignored">
|
||||||
(item: ConfigEntryExtended) => html`
|
<div class="header">
|
||||||
<ha-card class="ignored">
|
${this.hass.localize(
|
||||||
<div class="header">
|
"ui.panel.config.integrations.ignore.ignored"
|
||||||
${this.hass.localize(
|
)}
|
||||||
"ui.panel.config.integrations.ignore.ignored"
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="image">
|
||||||
|
<img
|
||||||
|
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
@error=${this._onImageError}
|
||||||
|
@load=${this._onImageLoad}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2>
|
||||||
|
${item.localized_domain_name}
|
||||||
|
</h2>
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._removeIgnoredIntegration}
|
||||||
|
.entry=${item}
|
||||||
|
aria-label=${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||||
)}
|
)}
|
||||||
</div>
|
>${this.hass.localize(
|
||||||
<div class="card-content">
|
"ui.panel.config.integrations.ignore.stop_ignore"
|
||||||
<div class="image">
|
)}</mwc-button
|
||||||
<img
|
>
|
||||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
</div>
|
||||||
referrerpolicy="no-referrer"
|
</ha-card>
|
||||||
@error=${this._onImageError}
|
`
|
||||||
@load=${this._onImageLoad}
|
)
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h2>
|
|
||||||
${item.localized_domain_name}
|
|
||||||
</h2>
|
|
||||||
<mwc-button
|
|
||||||
@click=${this._removeIgnoredIntegration}
|
|
||||||
.entry=${item}
|
|
||||||
aria-label=${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
|
||||||
)}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.ignore.stop_ignore"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`
|
|
||||||
)
|
|
||||||
: ""}
|
: ""}
|
||||||
${configEntriesInProgress.length
|
${configEntriesInProgress.length
|
||||||
? configEntriesInProgress.map(
|
? configEntriesInProgress.map(
|
||||||
@ -352,119 +397,18 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
)
|
)
|
||||||
: ""}
|
: ""}
|
||||||
${configEntries.length
|
${groupedConfigEntries.size
|
||||||
? configEntries.map((item: ConfigEntryExtended) => {
|
? Array.from(groupedConfigEntries.entries()).map(
|
||||||
const devices = this._getDevices(item);
|
([domain, items]) =>
|
||||||
const entities = this._getEntities(item);
|
html`<ha-integration-card
|
||||||
return item.source === "ignore"
|
data-domain=${domain}
|
||||||
? ""
|
.hass=${this.hass}
|
||||||
: html`
|
.domain=${domain}
|
||||||
<ha-card
|
.items=${items}
|
||||||
class="integration"
|
.entityRegistryEntries=${this._entityRegistryEntries}
|
||||||
.configEntry=${item}
|
.deviceRegistryEntries=${this._deviceRegistryEntries}
|
||||||
.id=${item.entry_id}
|
></ha-integration-card>`
|
||||||
>
|
)
|
||||||
<div class="card-content">
|
|
||||||
<div class="image">
|
|
||||||
<img
|
|
||||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
@error=${this._onImageError}
|
|
||||||
@load=${this._onImageLoad}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h1>
|
|
||||||
${item.localized_domain_name}
|
|
||||||
</h1>
|
|
||||||
<h2>
|
|
||||||
${item.localized_domain_name === item.title
|
|
||||||
? html` `
|
|
||||||
: item.title}
|
|
||||||
</h2>
|
|
||||||
${devices.length || entities.length
|
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
${devices.length
|
|
||||||
? html`
|
|
||||||
<a
|
|
||||||
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.devices",
|
|
||||||
"count",
|
|
||||||
devices.length
|
|
||||||
)}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${devices.length && entities.length
|
|
||||||
? "and"
|
|
||||||
: ""}
|
|
||||||
${entities.length
|
|
||||||
? html`
|
|
||||||
<a
|
|
||||||
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.entities",
|
|
||||||
"count",
|
|
||||||
entities.length
|
|
||||||
)}</a
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<div>
|
|
||||||
<mwc-button @click=${this._editEntryName}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.rename"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
${item.supports_options
|
|
||||||
? html`
|
|
||||||
<mwc-button @click=${this._showOptions}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.options"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
<paper-menu-button
|
|
||||||
horizontal-align="right"
|
|
||||||
vertical-align="top"
|
|
||||||
vertical-offset="40"
|
|
||||||
close-on-activate
|
|
||||||
>
|
|
||||||
<paper-icon-button
|
|
||||||
icon="hass:dots-vertical"
|
|
||||||
slot="dropdown-trigger"
|
|
||||||
aria-label=${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_card.options"
|
|
||||||
)}
|
|
||||||
></paper-icon-button>
|
|
||||||
<paper-listbox slot="dropdown-content">
|
|
||||||
<paper-item @tap=${this._showSystemOptions}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.system_options"
|
|
||||||
)}</paper-item
|
|
||||||
>
|
|
||||||
<paper-item
|
|
||||||
class="warning"
|
|
||||||
@tap=${this._removeIntegration}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.delete"
|
|
||||||
)}</paper-item
|
|
||||||
>
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-menu-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
: !this._configEntries.length
|
: !this._configEntries.length
|
||||||
? html`
|
? html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
@ -488,7 +432,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
: ""}
|
: ""}
|
||||||
${this._filter &&
|
${this._filter &&
|
||||||
!configEntriesInProgress.length &&
|
!configEntriesInProgress.length &&
|
||||||
!configEntries.length &&
|
!groupedConfigEntries.size &&
|
||||||
this._configEntries.length
|
this._configEntries.length
|
||||||
? html`
|
? html`
|
||||||
<div class="none-found">
|
<div class="none-found">
|
||||||
@ -522,9 +466,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
private _loadConfigEntries() {
|
private _loadConfigEntries() {
|
||||||
getConfigEntries(this.hass).then((configEntries) => {
|
getConfigEntries(this.hass).then((configEntries) => {
|
||||||
this._configEntries = configEntries
|
this._configEntries = configEntries
|
||||||
.sort((conf1, conf2) =>
|
|
||||||
compare(conf1.domain + conf1.title, conf2.domain + conf2.title)
|
|
||||||
)
|
|
||||||
.map(
|
.map(
|
||||||
(entry: ConfigEntry): ConfigEntryExtended => ({
|
(entry: ConfigEntry): ConfigEntryExtended => ({
|
||||||
...entry,
|
...entry,
|
||||||
@ -533,10 +474,31 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
entry.domain
|
entry.domain
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
.sort((conf1, conf2) =>
|
||||||
|
caseInsensitiveCompare(
|
||||||
|
conf1.localized_domain_name + conf1.title,
|
||||||
|
conf2.localized_domain_name + conf2.title
|
||||||
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleRemoved(ev: HASSDomEvent<ConfigEntryRemovedEvent>) {
|
||||||
|
this._configEntries = this._configEntries.filter(
|
||||||
|
(entry) => entry.entry_id !== ev.detail.entryId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleUpdated(ev: HASSDomEvent<ConfigEntryUpdatedEvent>) {
|
||||||
|
const newEntry = ev.detail.entry;
|
||||||
|
this._configEntries = this._configEntries!.map((entry) =>
|
||||||
|
entry.entry_id === newEntry.entry_id
|
||||||
|
? { ...newEntry, localized_domain_name: entry.localized_domain_name }
|
||||||
|
: entry
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _createFlow() {
|
private _createFlow() {
|
||||||
showConfigFlowDialog(this, {
|
showConfigFlowDialog(this, {
|
||||||
dialogClosedCallback: () => {
|
dialogClosedCallback: () => {
|
||||||
@ -612,22 +574,8 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getEntities(configEntry: ConfigEntry): EntityRegistryEntry[] {
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
if (!this._entityRegistryEntries) {
|
this._filter = ev.detail.value;
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return this._entityRegistryEntries.filter(
|
|
||||||
(entity) => entity.config_entry_id === configEntry.entry_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getDevices(configEntry: ConfigEntry): DeviceRegistryEntry[] {
|
|
||||||
if (!this._deviceRegistryEntries) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return this._deviceRegistryEntries.filter((device) =>
|
|
||||||
device.config_entries.includes(configEntry.entry_id)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onImageLoad(ev) {
|
private _onImageLoad(ev) {
|
||||||
@ -638,68 +586,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
ev.target.style.visibility = "hidden";
|
ev.target.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showOptions(ev) {
|
|
||||||
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _showSystemOptions(ev) {
|
|
||||||
showConfigEntrySystemOptionsDialog(this, {
|
|
||||||
entry: ev.target.closest("ha-card").configEntry,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _editEntryName(ev) {
|
|
||||||
const configEntry = ev.target.closest("ha-card").configEntry;
|
|
||||||
const newName = await showPromptDialog(this, {
|
|
||||||
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
|
|
||||||
defaultValue: configEntry.title,
|
|
||||||
inputLabel: this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.rename_input_label"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
if (newName === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
|
|
||||||
title: newName,
|
|
||||||
});
|
|
||||||
this._configEntries = this._configEntries!.map((entry) =>
|
|
||||||
entry.entry_id === newEntry.entry_id ? newEntry : entry
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _removeIntegration(ev) {
|
|
||||||
const entryId = ev.target.closest("ha-card").configEntry.entry_id;
|
|
||||||
|
|
||||||
const confirmed = await showConfirmationDialog(this, {
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.delete_confirm"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteConfigEntry(this.hass, entryId).then((result) => {
|
|
||||||
this._configEntries = this._configEntries.filter(
|
|
||||||
(entry) => entry.entry_id !== entryId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.require_restart) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.panel.config.integrations.config_entry.restart_confirm"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent) {
|
|
||||||
this._filter = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@ -717,9 +603,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
ha-card.highlight {
|
|
||||||
border: 1px solid var(--accent-color);
|
|
||||||
}
|
|
||||||
.discovered {
|
.discovered {
|
||||||
border: 1px solid var(--primary-color);
|
border: 1px solid var(--primary-color);
|
||||||
}
|
}
|
||||||
@ -742,21 +625,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
ha-card.integration .card-content {
|
|
||||||
padding-bottom: 3px;
|
|
||||||
}
|
|
||||||
.card-actions {
|
|
||||||
border-top: none;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
.helper {
|
|
||||||
display: inline-block;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.image {
|
.image {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -787,11 +655,12 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
max-height: 60px;
|
max-height: 100%;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
}
|
}
|
||||||
a {
|
.none-found {
|
||||||
color: var(--primary-color);
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@ -821,10 +690,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
left: 24px;
|
left: 24px;
|
||||||
right: auto;
|
right: auto;
|
||||||
}
|
}
|
||||||
paper-menu-button {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
404
src/panels/config/integrations/ha-integration-card.ts
Normal file
404
src/panels/config/integrations/ha-integration-card.ts
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
html,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { ConfigEntryExtended } from "./ha-config-integrations";
|
||||||
|
import { domainToName } from "../../../data/integration";
|
||||||
|
import {
|
||||||
|
ConfigEntry,
|
||||||
|
updateConfigEntry,
|
||||||
|
deleteConfigEntry,
|
||||||
|
} from "../../../data/config_entries";
|
||||||
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
|
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||||
|
import { showOptionsFlowDialog } from "../../../dialogs/config-flow/show-dialog-options-flow";
|
||||||
|
import { showConfigEntrySystemOptionsDialog } from "../../../dialogs/config-entry-system-options/show-dialog-config-entry-system-options";
|
||||||
|
import {
|
||||||
|
showPromptDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
showAlertDialog,
|
||||||
|
} from "../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import "../../../components/ha-icon-next";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
|
export interface ConfigEntryUpdatedEvent {
|
||||||
|
entry: ConfigEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigEntryRemovedEvent {
|
||||||
|
entryId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// for fire event
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"entry-updated": ConfigEntryUpdatedEvent;
|
||||||
|
"entry-removed": ConfigEntryRemovedEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-integration-card")
|
||||||
|
export class HaIntegrationCard extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public domain!: string;
|
||||||
|
|
||||||
|
@property() public items!: ConfigEntryExtended[];
|
||||||
|
|
||||||
|
@property() public entityRegistryEntries!: EntityRegistryEntry[];
|
||||||
|
|
||||||
|
@property() public deviceRegistryEntries!: DeviceRegistryEntry[];
|
||||||
|
|
||||||
|
@property() public selectedConfigEntryId?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (this.items.length === 1) {
|
||||||
|
return this._renderSingleEntry(this.items[0]);
|
||||||
|
}
|
||||||
|
if (this.selectedConfigEntryId) {
|
||||||
|
const configEntry = this.items.find(
|
||||||
|
(entry) => entry.entry_id === this.selectedConfigEntryId
|
||||||
|
);
|
||||||
|
if (configEntry) {
|
||||||
|
return this._renderSingleEntry(configEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._renderGroupedIntegration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderGroupedIntegration(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-card class="group">
|
||||||
|
<div class="group-header">
|
||||||
|
<img
|
||||||
|
src="https://brands.home-assistant.io/${this.domain}/icon.png"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
@error=${this._onImageError}
|
||||||
|
@load=${this._onImageLoad}
|
||||||
|
/>
|
||||||
|
<h1>
|
||||||
|
${domainToName(this.hass.localize, this.domain)}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<paper-listbox>
|
||||||
|
${this.items.map(
|
||||||
|
(item) =>
|
||||||
|
html`<paper-item
|
||||||
|
.entryId=${item.entry_id}
|
||||||
|
@click=${this._selectConfigEntry}
|
||||||
|
><paper-item-body>${item.title}</paper-item-body
|
||||||
|
><ha-icon-next></ha-icon-next
|
||||||
|
></paper-item>`
|
||||||
|
)}
|
||||||
|
</paper-listbox>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderSingleEntry(item: ConfigEntryExtended): TemplateResult {
|
||||||
|
const devices = this._getDevices(item);
|
||||||
|
const entities = this._getEntities(item);
|
||||||
|
return html`
|
||||||
|
<ha-card
|
||||||
|
class="single integration"
|
||||||
|
.configEntry=${item}
|
||||||
|
.id=${item.entry_id}
|
||||||
|
>
|
||||||
|
${this.items.length > 1
|
||||||
|
? html`<paper-icon-button
|
||||||
|
class="back-btn"
|
||||||
|
icon="hass:chevron-left"
|
||||||
|
@click=${this._back}
|
||||||
|
></paper-icon-button>`
|
||||||
|
: ""}
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="image">
|
||||||
|
<img
|
||||||
|
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
@error=${this._onImageError}
|
||||||
|
@load=${this._onImageLoad}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1>
|
||||||
|
${item.localized_domain_name}
|
||||||
|
</h1>
|
||||||
|
<h2>
|
||||||
|
${item.localized_domain_name === item.title ? "" : item.title}
|
||||||
|
</h2>
|
||||||
|
${devices.length || entities.length
|
||||||
|
? html`
|
||||||
|
<div>
|
||||||
|
${devices.length
|
||||||
|
? html`
|
||||||
|
<a
|
||||||
|
href=${`/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.devices",
|
||||||
|
"count",
|
||||||
|
devices.length
|
||||||
|
)}</a
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${devices.length && entities.length ? "and" : ""}
|
||||||
|
${entities.length
|
||||||
|
? html`
|
||||||
|
<a
|
||||||
|
href=${`/config/entities?historyBack=1&config_entry=${item.entry_id}`}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.entities",
|
||||||
|
"count",
|
||||||
|
entities.length
|
||||||
|
)}</a
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<div>
|
||||||
|
<mwc-button @click=${this._editEntryName}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.rename"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
${item.supports_options
|
||||||
|
? html`
|
||||||
|
<mwc-button @click=${this._showOptions}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.options"
|
||||||
|
)}</mwc-button
|
||||||
|
>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<paper-menu-button
|
||||||
|
horizontal-align="right"
|
||||||
|
vertical-align="top"
|
||||||
|
vertical-offset="40"
|
||||||
|
close-on-activate
|
||||||
|
>
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:dots-vertical"
|
||||||
|
slot="dropdown-trigger"
|
||||||
|
aria-label=${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_card.options"
|
||||||
|
)}
|
||||||
|
></paper-icon-button>
|
||||||
|
<paper-listbox slot="dropdown-content">
|
||||||
|
<paper-item @tap=${this._showSystemOptions}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.system_options"
|
||||||
|
)}</paper-item
|
||||||
|
>
|
||||||
|
<paper-item class="warning" @tap=${this._removeIntegration}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.delete"
|
||||||
|
)}</paper-item
|
||||||
|
>
|
||||||
|
</paper-listbox>
|
||||||
|
</paper-menu-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _selectConfigEntry(ev: Event) {
|
||||||
|
this.selectedConfigEntryId = (ev.currentTarget as any).entryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _back() {
|
||||||
|
this.selectedConfigEntryId = undefined;
|
||||||
|
this.classList.remove("highlight");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getEntities(configEntry: ConfigEntry): EntityRegistryEntry[] {
|
||||||
|
if (!this.entityRegistryEntries) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.entityRegistryEntries.filter(
|
||||||
|
(entity) => entity.config_entry_id === configEntry.entry_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getDevices(configEntry: ConfigEntry): DeviceRegistryEntry[] {
|
||||||
|
if (!this.deviceRegistryEntries) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.deviceRegistryEntries.filter((device) =>
|
||||||
|
device.config_entries.includes(configEntry.entry_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onImageLoad(ev) {
|
||||||
|
ev.target.style.visibility = "initial";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onImageError(ev) {
|
||||||
|
ev.target.style.visibility = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showOptions(ev) {
|
||||||
|
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showSystemOptions(ev) {
|
||||||
|
showConfigEntrySystemOptionsDialog(this, {
|
||||||
|
entry: ev.target.closest("ha-card").configEntry,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _editEntryName(ev) {
|
||||||
|
const configEntry = ev.target.closest("ha-card").configEntry;
|
||||||
|
const newName = await showPromptDialog(this, {
|
||||||
|
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
|
||||||
|
defaultValue: configEntry.title,
|
||||||
|
inputLabel: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.rename_input_label"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
if (newName === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newEntry = await updateConfigEntry(this.hass, configEntry.entry_id, {
|
||||||
|
title: newName,
|
||||||
|
});
|
||||||
|
fireEvent(this, "entry-updated", { entry: newEntry });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _removeIntegration(ev) {
|
||||||
|
const entryId = ev.target.closest("ha-card").configEntry.entry_id;
|
||||||
|
|
||||||
|
const confirmed = await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.delete_confirm"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deleteConfigEntry(this.hass, entryId).then((result) => {
|
||||||
|
fireEvent(this, "entry-removed", { entryId });
|
||||||
|
|
||||||
|
if (result.require_restart) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.restart_confirm"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
ha-card.single {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
:host(.highlight) ha-card {
|
||||||
|
border: 1px solid var(--accent-color);
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
ha-card.integration .card-content {
|
||||||
|
padding-bottom: 3px;
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
border-top: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
.group-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
padding: 16px 16px 8px 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.group-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.group-header img {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.image {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 60px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.none-found {
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
min-height: 24px;
|
||||||
|
}
|
||||||
|
paper-menu-button {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
@media (min-width: 563px) {
|
||||||
|
paper-listbox {
|
||||||
|
max-height: 150px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paper-item {
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 35px;
|
||||||
|
}
|
||||||
|
.back-btn {
|
||||||
|
position: absolute;
|
||||||
|
background: #ffffffe0;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-integration-card": HaIntegrationCard;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user