mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-03 17:34:21 +00:00
Compare commits
1 Commits
keyboard-i
...
refactor-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb95d617a1 |
@@ -38,14 +38,6 @@ export interface HASSDomEvent<T> extends Event {
|
||||
detail: T;
|
||||
}
|
||||
|
||||
export type HASSDomTargetEvent<T extends EventTarget> = Event & {
|
||||
target: T;
|
||||
};
|
||||
|
||||
export type HASSDomCurrentTargetEvent<T extends EventTarget> = Event & {
|
||||
currentTarget: T;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches a custom event with an optional detail value.
|
||||
*
|
||||
|
||||
@@ -17,10 +17,6 @@ import memoizeOne from "memoize-one";
|
||||
import { STRINGS_SEPARATOR_DOT } from "../../common/const";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
HASSDomCurrentTargetEvent,
|
||||
HASSDomTargetEvent,
|
||||
} from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
@@ -107,7 +103,6 @@ export interface DataTableRowData {
|
||||
export type SortableColumnContainer = Record<string, ClonedDataTableColumnData>;
|
||||
|
||||
const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
|
||||
const AUTO_FOCUS_ALLOWED_ACTIVE_TAGS = ["BODY", "HTML", "HOME-ASSISTANT"];
|
||||
|
||||
@customElement("ha-data-table")
|
||||
export class HaDataTable extends LitElement {
|
||||
@@ -171,10 +166,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
|
||||
@query(".mdc-data-table__header-row") private _headerRow?: HTMLDivElement;
|
||||
|
||||
@query("lit-virtualizer") private _scroller?: HTMLElement;
|
||||
|
||||
@state() private _collapsedGroups: string[] = [];
|
||||
|
||||
@state() private _lastSelectedRowId: string | null = null;
|
||||
@@ -189,8 +180,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
private _lastUpdate = 0;
|
||||
|
||||
private _didAutoFocusScroller = false;
|
||||
|
||||
// @ts-ignore
|
||||
@restoreScroll(".scroller") private _savedScrollPos?: number;
|
||||
|
||||
@@ -253,26 +242,16 @@ export class HaDataTable extends LitElement {
|
||||
this.updateComplete.then(() => this._calcTableHeight());
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (!this._headerRow) {
|
||||
protected updated() {
|
||||
const header = this.renderRoot.querySelector(".mdc-data-table__header-row");
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._headerRow.scrollWidth > this._headerRow.clientWidth) {
|
||||
this.style.setProperty(
|
||||
"--table-row-width",
|
||||
`${this._headerRow.scrollWidth}px`
|
||||
);
|
||||
if (header.scrollWidth > header.clientWidth) {
|
||||
this.style.setProperty("--table-row-width", `${header.scrollWidth}px`);
|
||||
} else {
|
||||
this.style.removeProperty("--table-row-width");
|
||||
}
|
||||
|
||||
this._focusTableOnLoad();
|
||||
|
||||
// Refocus on toggle checkbox changes
|
||||
if (changedProps.has("selectable")) {
|
||||
this._focusScroller();
|
||||
}
|
||||
}
|
||||
|
||||
public willUpdate(properties: PropertyValues) {
|
||||
@@ -538,7 +517,6 @@ export class HaDataTable extends LitElement {
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
class="mdc-data-table__content scroller ha-scrollbar"
|
||||
tabindex=${ifDefined(!this.autoHeight ? "0" : undefined)}
|
||||
@scroll=${this._saveScrollPos}
|
||||
.items=${this._groupData(
|
||||
this._filteredData,
|
||||
@@ -851,10 +829,8 @@ export class HaDataTable extends LitElement {
|
||||
): Promise<DataTableRowData[]> => filterData(data, columns, filter)
|
||||
);
|
||||
|
||||
private _handleHeaderClick(
|
||||
ev: HASSDomCurrentTargetEvent<HTMLElement & { columnId: string }>
|
||||
) {
|
||||
const columnId = ev.currentTarget.columnId;
|
||||
private _handleHeaderClick(ev: Event) {
|
||||
const columnId = (ev.currentTarget as any).columnId;
|
||||
if (!this.columns[columnId].sortable) {
|
||||
return;
|
||||
}
|
||||
@@ -872,12 +848,11 @@ export class HaDataTable extends LitElement {
|
||||
column: columnId,
|
||||
direction: this.sortDirection,
|
||||
});
|
||||
|
||||
this._focusScroller();
|
||||
}
|
||||
|
||||
private _handleHeaderRowCheckboxClick(ev: HASSDomTargetEvent<HaCheckbox>) {
|
||||
if (ev.target.checked) {
|
||||
private _handleHeaderRowCheckboxClick(ev: Event) {
|
||||
const checkbox = ev.target as HaCheckbox;
|
||||
if (checkbox.checked) {
|
||||
this.selectAll();
|
||||
} else {
|
||||
this._checkedRows = [];
|
||||
@@ -886,10 +861,9 @@ export class HaDataTable extends LitElement {
|
||||
this._lastSelectedRowId = null;
|
||||
}
|
||||
|
||||
private _handleRowCheckboxClicked = (
|
||||
ev: HASSDomCurrentTargetEvent<HaCheckbox & { rowId: string }>
|
||||
) => {
|
||||
const rowId = ev.currentTarget.rowId;
|
||||
private _handleRowCheckboxClicked = (ev: Event) => {
|
||||
const checkbox = ev.currentTarget as HaCheckbox;
|
||||
const rowId = (checkbox as any).rowId;
|
||||
|
||||
const groupedData = this._groupData(
|
||||
this._filteredData,
|
||||
@@ -926,7 +900,7 @@ export class HaDataTable extends LitElement {
|
||||
...this._selectRange(groupedData, lastSelectedRowIndex, rowIndex),
|
||||
];
|
||||
}
|
||||
} else if (!ev.currentTarget.checked) {
|
||||
} else if (!checkbox.checked) {
|
||||
if (!this._checkedRows.includes(rowId)) {
|
||||
this._checkedRows = [...this._checkedRows, rowId];
|
||||
}
|
||||
@@ -964,9 +938,7 @@ export class HaDataTable extends LitElement {
|
||||
return checkedRows;
|
||||
}
|
||||
|
||||
private _handleRowClick = (
|
||||
ev: HASSDomCurrentTargetEvent<HTMLElement & { rowId: string }>
|
||||
) => {
|
||||
private _handleRowClick = (ev: Event) => {
|
||||
if (
|
||||
ev
|
||||
.composedPath()
|
||||
@@ -982,13 +954,14 @@ export class HaDataTable extends LitElement {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const rowId = ev.currentTarget.rowId;
|
||||
const rowId = (ev.currentTarget as any).rowId;
|
||||
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
||||
};
|
||||
|
||||
private _setTitle(ev: HASSDomCurrentTargetEvent<HTMLElement>) {
|
||||
if (ev.currentTarget.scrollWidth > ev.currentTarget.offsetWidth) {
|
||||
ev.currentTarget.setAttribute("title", ev.currentTarget.innerText);
|
||||
private _setTitle(ev: Event) {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
if (target.scrollWidth > target.offsetWidth) {
|
||||
target.setAttribute("title", target.innerText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,27 +983,6 @@ export class HaDataTable extends LitElement {
|
||||
this._debounceSearch((ev.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
private _focusTableOnLoad() {
|
||||
if (
|
||||
this._didAutoFocusScroller ||
|
||||
this.autoHeight ||
|
||||
(document.activeElement &&
|
||||
!AUTO_FOCUS_ALLOWED_ACTIVE_TAGS.includes(
|
||||
document.activeElement.tagName
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._focusScroller();
|
||||
}
|
||||
|
||||
private _focusScroller(): void {
|
||||
this._scroller?.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async _calcTableHeight() {
|
||||
if (this.autoHeight) {
|
||||
return;
|
||||
@@ -1040,27 +992,23 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _saveScrollPos(e: HASSDomTargetEvent<HTMLDivElement>) {
|
||||
this._savedScrollPos = e.target.scrollTop;
|
||||
private _saveScrollPos(e: Event) {
|
||||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||
|
||||
if (this._headerRow) {
|
||||
this._headerRow.scrollLeft = e.target.scrollLeft;
|
||||
}
|
||||
this.renderRoot.querySelector(".mdc-data-table__header-row")!.scrollLeft = (
|
||||
e.target as HTMLDivElement
|
||||
).scrollLeft;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _scrollContent(e: HASSDomTargetEvent<HTMLDivElement>) {
|
||||
if (!this._scroller) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._scroller.scrollLeft = e.target.scrollLeft;
|
||||
private _scrollContent(e: Event) {
|
||||
this.renderRoot.querySelector("lit-virtualizer")!.scrollLeft = (
|
||||
e.target as HTMLDivElement
|
||||
).scrollLeft;
|
||||
}
|
||||
|
||||
private _collapseGroup = (
|
||||
ev: HASSDomCurrentTargetEvent<HTMLElement & { group: string }>
|
||||
) => {
|
||||
const groupName = ev.currentTarget.group;
|
||||
private _collapseGroup = (ev: Event) => {
|
||||
const groupName = (ev.currentTarget as any).group;
|
||||
if (this._collapsedGroups.includes(groupName)) {
|
||||
this._collapsedGroups = this._collapsedGroups.filter(
|
||||
(grp) => grp !== groupName
|
||||
@@ -1483,11 +1431,6 @@ export class HaDataTable extends LitElement {
|
||||
contain: size layout !important;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
lit-virtualizer:focus,
|
||||
lit-virtualizer:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -18,13 +18,11 @@ import {
|
||||
mdiStopCircleOutline,
|
||||
mdiWrench,
|
||||
} from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import "../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
@@ -34,22 +32,16 @@ import {
|
||||
fetchApplicationCredentialsConfigEntry,
|
||||
} from "../../../data/application_credential";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type {
|
||||
ConfigEntry,
|
||||
DisableConfigEntryResult,
|
||||
SubEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import type { DisableConfigEntryResult } from "../../../data/config_entries";
|
||||
import {
|
||||
deleteConfigEntry,
|
||||
disableConfigEntry,
|
||||
enableConfigEntry,
|
||||
ERROR_STATES,
|
||||
getSubEntries,
|
||||
RECOVERABLE_STATES,
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
} from "../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import type { DiagnosticInfo } from "../../../data/diagnostics";
|
||||
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
@@ -74,6 +66,7 @@ import {
|
||||
showPromptDialog,
|
||||
} from "../../lovelace/custom-card-helpers";
|
||||
import "./ha-config-entry-device-row";
|
||||
import type { ConfigEntryData } from "./ha-config-integration-page";
|
||||
import { renderConfigEntryError } from "./ha-config-integration-page";
|
||||
import "./ha-config-sub-entry-row";
|
||||
|
||||
@@ -89,22 +82,14 @@ export class HaConfigEntryRow extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public entities!: EntityRegistryEntry[];
|
||||
|
||||
@property({ attribute: false }) public entry!: ConfigEntry;
|
||||
@property({ attribute: false }) public data!: ConfigEntryData;
|
||||
|
||||
@state() private _expanded = true;
|
||||
|
||||
@state() private _devicesExpanded = true;
|
||||
|
||||
@state() private _subEntries?: SubEntry[];
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("entry")) {
|
||||
this._fetchSubEntries();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const item = this.entry;
|
||||
const item = this.data.entry;
|
||||
|
||||
let stateText: Parameters<typeof this.hass.localize> | undefined;
|
||||
let stateTextExtra: TemplateResult | string | undefined;
|
||||
@@ -127,15 +112,17 @@ export class HaConfigEntryRow extends LitElement {
|
||||
stateTextExtra = renderConfigEntryError(this.hass, item);
|
||||
}
|
||||
|
||||
const devices = this._getDevices();
|
||||
const services = this._getServices();
|
||||
const entities = this._getEntities();
|
||||
const ownDevices = [...this.data.devices, ...this.data.services];
|
||||
|
||||
const ownDevices = [...devices, ...services].filter(
|
||||
(device) =>
|
||||
!device.config_entries_subentries[item.entry_id].length ||
|
||||
device.config_entries_subentries[item.entry_id][0] === null
|
||||
);
|
||||
const allDevices = [
|
||||
...this.data.devices,
|
||||
...this.data.subEntries.flatMap((s) => s.devices),
|
||||
];
|
||||
const allServices = [
|
||||
...this.data.services,
|
||||
...this.data.subEntries.flatMap((s) => s.services),
|
||||
];
|
||||
|
||||
const statusLine: (TemplateResult | string)[] = [];
|
||||
|
||||
@@ -157,7 +144,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
)}.`);
|
||||
}
|
||||
} else if (!devices.length && !services.length && entities.length) {
|
||||
} else if (!allDevices.length && !allServices.length && entities.length) {
|
||||
statusLine.push(
|
||||
html`<a
|
||||
href=${`/config/entities/?historyBack=1&config_entry=${item.entry_id}`}
|
||||
@@ -171,7 +158,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
|
||||
const configPanel = this._configPanel(item.domain, this.hass.panels);
|
||||
|
||||
const subEntries = this._subEntries || [];
|
||||
const subEntries = this.data.subEntries;
|
||||
|
||||
return html`<ha-md-list>
|
||||
<ha-md-list-item
|
||||
@@ -245,29 +232,29 @@ export class HaConfigEntryRow extends LitElement {
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${devices.length
|
||||
${allDevices.length
|
||||
? html`
|
||||
<a
|
||||
href=${devices.length === 1
|
||||
? `/config/devices/device/${devices[0].id}`
|
||||
href=${allDevices.length === 1
|
||||
? `/config/devices/device/${allDevices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-dropdown-item value="devices">
|
||||
<ha-svg-icon .path=${mdiDevices} slot="icon"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.devices`,
|
||||
{ count: devices.length }
|
||||
{ count: allDevices.length }
|
||||
)}
|
||||
<ha-icon-next slot="details"></ha-icon-next>
|
||||
</ha-dropdown-item>
|
||||
</a>
|
||||
`
|
||||
: nothing}
|
||||
${services.length
|
||||
${allServices.length
|
||||
? html`
|
||||
<a
|
||||
href=${services.length === 1
|
||||
? `/config/devices/device/${services[0].id}`
|
||||
href=${allServices.length === 1
|
||||
? `/config/devices/device/${allServices[0].id}`
|
||||
: `/config/devices/dashboard?historyBack=1&config_entry=${item.entry_id}`}
|
||||
>
|
||||
<ha-dropdown-item value="services">
|
||||
@@ -277,7 +264,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.services`,
|
||||
{ count: services.length }
|
||||
{ count: allServices.length }
|
||||
)}
|
||||
<ha-icon-next slot="details"></ha-icon-next>
|
||||
</ha-dropdown-item>
|
||||
@@ -449,7 +436,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
</ha-md-list>`
|
||||
: nothing}
|
||||
${subEntries.map(
|
||||
(subEntry) => html`
|
||||
(subEntryData) => html`
|
||||
<ha-config-sub-entry-row
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@@ -457,7 +444,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
.diagnosticHandler=${this.diagnosticHandler}
|
||||
.entities=${this.entities}
|
||||
.entry=${item}
|
||||
.subEntry=${subEntry}
|
||||
.data=${subEntryData}
|
||||
data-entry-id=${item.entry_id}
|
||||
></ha-config-sub-entry-row>
|
||||
`
|
||||
@@ -478,18 +465,6 @@ export class HaConfigEntryRow extends LitElement {
|
||||
</ha-md-list>`;
|
||||
}
|
||||
|
||||
private async _fetchSubEntries() {
|
||||
this._subEntries = this.entry.num_subentries
|
||||
? (await getSubEntries(this.hass, this.entry.entry_id)).sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.title,
|
||||
b.title,
|
||||
this.hass.locale.language
|
||||
)
|
||||
)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private _configPanel = memoizeOne(
|
||||
(domain: string, panels: HomeAssistant["panels"]): string | undefined =>
|
||||
Object.values(panels).find(
|
||||
@@ -499,39 +474,9 @@ export class HaConfigEntryRow extends LitElement {
|
||||
|
||||
private _getEntities = (): EntityRegistryEntry[] =>
|
||||
this.entities.filter(
|
||||
(entity) => entity.config_entry_id === this.entry.entry_id
|
||||
(entity) => entity.config_entry_id === this.data.entry.entry_id
|
||||
);
|
||||
|
||||
private _getDevices = (): DeviceRegistryEntry[] =>
|
||||
Object.values(this.hass.devices)
|
||||
.filter(
|
||||
(device) =>
|
||||
device.config_entries.includes(this.entry.entry_id) &&
|
||||
device.entry_type !== "service"
|
||||
)
|
||||
.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
computeDeviceNameDisplay(a, this.hass),
|
||||
computeDeviceNameDisplay(b, this.hass),
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
|
||||
private _getServices = (): DeviceRegistryEntry[] =>
|
||||
Object.values(this.hass.devices)
|
||||
.filter(
|
||||
(device) =>
|
||||
device.config_entries.includes(this.entry.entry_id) &&
|
||||
device.entry_type === "service"
|
||||
)
|
||||
.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
computeDeviceNameDisplay(a, this.hass),
|
||||
computeDeviceNameDisplay(b, this.hass),
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
|
||||
private _toggleExpand() {
|
||||
this._expanded = !this._expanded;
|
||||
}
|
||||
@@ -583,7 +528,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
};
|
||||
|
||||
private _showOptions() {
|
||||
showOptionsFlowDialog(this, this.entry, { manifest: this.manifest });
|
||||
showOptionsFlowDialog(this, this.data.entry, { manifest: this.manifest });
|
||||
}
|
||||
|
||||
// Return an application credentials id for this config entry to prompt the
|
||||
@@ -648,7 +593,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
}
|
||||
|
||||
private _handleReload = async () => {
|
||||
const result = await reloadConfigEntry(this.hass, this.entry.entry_id);
|
||||
const result = await reloadConfigEntry(this.hass, this.data.entry.entry_id);
|
||||
const locale_key = result.require_restart
|
||||
? "reload_restart_confirm"
|
||||
: "reload_confirm";
|
||||
@@ -661,16 +606,19 @@ export class HaConfigEntryRow extends LitElement {
|
||||
|
||||
private _handleReconfigure = async () => {
|
||||
showConfigFlowDialog(this, {
|
||||
startFlowHandler: this.entry.domain,
|
||||
startFlowHandler: this.data.entry.domain,
|
||||
showAdvanced: this.hass.userData?.showAdvanced,
|
||||
manifest: await fetchIntegrationManifest(this.hass, this.entry.domain),
|
||||
entryId: this.entry.entry_id,
|
||||
manifest: await fetchIntegrationManifest(
|
||||
this.hass,
|
||||
this.data.entry.domain
|
||||
),
|
||||
entryId: this.data.entry.entry_id,
|
||||
navigateToResult: true,
|
||||
});
|
||||
};
|
||||
|
||||
private _handleCopy = async () => {
|
||||
await copyToClipboard(this.entry.entry_id);
|
||||
await copyToClipboard(this.data.entry.entry_id);
|
||||
showToast(this, {
|
||||
message:
|
||||
this.hass?.localize("ui.common.copied_clipboard") ||
|
||||
@@ -681,7 +629,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
private _handleRename = async () => {
|
||||
const newName = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
|
||||
defaultValue: this.entry.title,
|
||||
defaultValue: this.data.entry.title,
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.integrations.rename_input_label"
|
||||
),
|
||||
@@ -689,7 +637,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
if (newName === null) {
|
||||
return;
|
||||
}
|
||||
await updateConfigEntry(this.hass, this.entry.entry_id, {
|
||||
await updateConfigEntry(this.hass, this.data.entry.entry_id, {
|
||||
title: newName,
|
||||
});
|
||||
};
|
||||
@@ -705,12 +653,12 @@ export class HaConfigEntryRow extends LitElement {
|
||||
}
|
||||
|
||||
private _handleDisable = async () => {
|
||||
const entryId = this.entry.entry_id;
|
||||
const entryId = this.data.entry.entry_id;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_confirm_title",
|
||||
{ title: this.entry.title }
|
||||
{ title: this.data.entry.title }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_confirm_text"
|
||||
@@ -745,7 +693,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
};
|
||||
|
||||
private _handleEnable = async () => {
|
||||
const entryId = this.entry.entry_id;
|
||||
const entryId = this.data.entry.entry_id;
|
||||
|
||||
let result: DisableConfigEntryResult;
|
||||
try {
|
||||
@@ -770,7 +718,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
};
|
||||
|
||||
private _handleDelete = async () => {
|
||||
const entryId = this.entry.entry_id;
|
||||
const entryId = this.data.entry.entry_id;
|
||||
|
||||
const applicationCredentialsId =
|
||||
await this._applicationCredentialForRemove(entryId);
|
||||
@@ -778,7 +726,7 @@ export class HaConfigEntryRow extends LitElement {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_title",
|
||||
{ title: this.entry.title }
|
||||
{ title: this.data.entry.title }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_text"
|
||||
@@ -807,14 +755,14 @@ export class HaConfigEntryRow extends LitElement {
|
||||
|
||||
private _handleSystemOptions = () => {
|
||||
showConfigEntrySystemOptionsDialog(this, {
|
||||
entry: this.entry,
|
||||
entry: this.data.entry,
|
||||
manifest: this.manifest,
|
||||
});
|
||||
};
|
||||
|
||||
private _addSubEntry = (flowType: string) => {
|
||||
showSubConfigFlowDialog(this, this.entry, flowType, {
|
||||
startFlowHandler: this.entry.entry_id,
|
||||
showSubConfigFlowDialog(this, this.data.entry, flowType, {
|
||||
startFlowHandler: this.data.entry.entry_id,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -29,8 +29,12 @@ import "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type { ConfigEntry } from "../../../data/config_entries";
|
||||
import { ERROR_STATES, getConfigEntries } from "../../../data/config_entries";
|
||||
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
ERROR_STATES,
|
||||
getConfigEntries,
|
||||
getSubEntries,
|
||||
} from "../../../data/config_entries";
|
||||
import { ATTENTION_SOURCES } from "../../../data/config_flow";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import type { DiagnosticInfo } from "../../../data/diagnostics";
|
||||
@@ -69,6 +73,19 @@ import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||
import { showPickConfigEntryDialog } from "./show-pick-config-entry-dialog";
|
||||
|
||||
export interface SubEntryData {
|
||||
subEntry: SubEntry;
|
||||
devices: DeviceRegistryEntry[];
|
||||
services: DeviceRegistryEntry[];
|
||||
}
|
||||
|
||||
export interface ConfigEntryData {
|
||||
entry: ConfigEntry;
|
||||
devices: DeviceRegistryEntry[];
|
||||
services: DeviceRegistryEntry[];
|
||||
subEntries: SubEntryData[];
|
||||
}
|
||||
|
||||
export const renderConfigEntryError = (
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry
|
||||
@@ -132,6 +149,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
window.location.hash.substring(1)
|
||||
);
|
||||
|
||||
@state() private _subEntries: Record<string, SubEntry[]> = {};
|
||||
|
||||
@state() private _domainEntities: Record<string, string[]> = {};
|
||||
|
||||
@queryAll("ha-config-entry-row")
|
||||
@@ -190,10 +209,21 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
this.hass.loadBackendTranslation("config", [this.domain]);
|
||||
this.hass.loadBackendTranslation("config_subentries", [this.domain]);
|
||||
this._extraConfigEntries = undefined;
|
||||
this._subEntries = {};
|
||||
this._fetchManifest();
|
||||
this._fetchDiagnostics();
|
||||
this._fetchEntitySources();
|
||||
}
|
||||
if (
|
||||
changedProperties.has("configEntries") ||
|
||||
changedProperties.has("_extraConfigEntries")
|
||||
) {
|
||||
const entries = this._domainConfigEntries(
|
||||
this.domain,
|
||||
this._extraConfigEntries || this.configEntries
|
||||
);
|
||||
this._fetchAllSubEntries(entries);
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchEntitySources() {
|
||||
@@ -280,6 +310,19 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
});
|
||||
|
||||
const normalData = this._buildEntryData(
|
||||
normalEntries,
|
||||
this.hass.devices,
|
||||
this._subEntries,
|
||||
this.hass.locale.language
|
||||
);
|
||||
const attentionData = this._buildEntryData(
|
||||
attentionEntries,
|
||||
this.hass.devices,
|
||||
this._subEntries,
|
||||
this.hass.locale.language
|
||||
);
|
||||
|
||||
const devicesRegs = this._getDevices(configEntries, this.hass.devices);
|
||||
const entities = this._getEntities(configEntries, this._entities);
|
||||
let numberOfEntities = entities.length;
|
||||
@@ -632,7 +675,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${attentionFlows.length || attentionEntries.length
|
||||
${attentionFlows.length || attentionData.length
|
||||
? html`
|
||||
<div class="section">
|
||||
<h3 class="section-header">
|
||||
@@ -672,8 +715,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
})}
|
||||
</ha-md-list>`
|
||||
: nothing}
|
||||
${attentionEntries.map(
|
||||
(item) =>
|
||||
${attentionData.map(
|
||||
(data) =>
|
||||
html`<ha-config-entry-row
|
||||
class="attention"
|
||||
.hass=${this.hass}
|
||||
@@ -681,8 +724,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
.manifest=${this._manifest}
|
||||
.diagnosticHandler=${this._diagnosticHandler}
|
||||
.entities=${this._entities}
|
||||
.entry=${item}
|
||||
data-entry-id=${item.entry_id}
|
||||
.data=${data}
|
||||
data-entry-id=${data.entry.entry_id}
|
||||
></ha-config-entry-row>`
|
||||
)}
|
||||
</div>
|
||||
@@ -699,7 +742,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
`ui.panel.config.integrations.integration_page.entries`
|
||||
)}
|
||||
</h3>
|
||||
${normalEntries.length === 0
|
||||
${normalData.length === 0
|
||||
? html`<div class="card-content no-entries">
|
||||
${this._manifest &&
|
||||
!this._manifest.config_flow &&
|
||||
@@ -714,16 +757,16 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>`
|
||||
: html`
|
||||
${normalEntries.map(
|
||||
(item) =>
|
||||
${normalData.map(
|
||||
(data) =>
|
||||
html`<ha-config-entry-row
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.manifest=${this._manifest}
|
||||
.diagnosticHandler=${this._diagnosticHandler}
|
||||
.entities=${this._entities}
|
||||
.entry=${item}
|
||||
data-entry-id=${item.entry_id}
|
||||
.data=${data}
|
||||
data-entry-id=${data.entry.entry_id}
|
||||
></ha-config-entry-row>`
|
||||
)}
|
||||
`}
|
||||
@@ -797,6 +840,102 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchAllSubEntries(entries: ConfigEntry[]) {
|
||||
const entriesWithSubs = entries.filter((e) => e.num_subentries > 0);
|
||||
if (!entriesWithSubs.length) {
|
||||
this._subEntries = {};
|
||||
return;
|
||||
}
|
||||
const results: Record<string, SubEntry[]> = {};
|
||||
await Promise.all(
|
||||
entriesWithSubs.map(async (entry) => {
|
||||
try {
|
||||
results[entry.entry_id] = (
|
||||
await getSubEntries(this.hass, entry.entry_id)
|
||||
).sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.title,
|
||||
b.title,
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
} catch {
|
||||
results[entry.entry_id] = [];
|
||||
}
|
||||
})
|
||||
);
|
||||
this._subEntries = results;
|
||||
}
|
||||
|
||||
private _buildEntryData = memoizeOne(
|
||||
(
|
||||
entries: ConfigEntry[],
|
||||
devices: HomeAssistant["devices"],
|
||||
subEntries: Record<string, SubEntry[]>,
|
||||
language: string
|
||||
): ConfigEntryData[] => {
|
||||
const sortDevices = (a: DeviceRegistryEntry, b: DeviceRegistryEntry) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.name_by_user || a.name || "",
|
||||
b.name_by_user || b.name || "",
|
||||
language
|
||||
);
|
||||
|
||||
return entries.map((entry) => {
|
||||
const allDevices = Object.values(devices).filter((device) =>
|
||||
device.config_entries.includes(entry.entry_id)
|
||||
);
|
||||
|
||||
const entrySubs = (subEntries[entry.entry_id] || []).map(
|
||||
(sub): SubEntryData => {
|
||||
const subDevs = allDevices
|
||||
.filter(
|
||||
(d) =>
|
||||
d.config_entries_subentries[entry.entry_id]?.includes(
|
||||
sub.subentry_id
|
||||
) && d.entry_type !== "service"
|
||||
)
|
||||
.sort(sortDevices);
|
||||
const subServices = allDevices
|
||||
.filter(
|
||||
(d) =>
|
||||
d.config_entries_subentries[entry.entry_id]?.includes(
|
||||
sub.subentry_id
|
||||
) && d.entry_type === "service"
|
||||
)
|
||||
.sort(sortDevices);
|
||||
return { subEntry: sub, devices: subDevs, services: subServices };
|
||||
}
|
||||
);
|
||||
|
||||
const ownDevices = allDevices
|
||||
.filter(
|
||||
(d) =>
|
||||
(!d.config_entries_subentries[entry.entry_id]?.length ||
|
||||
d.config_entries_subentries[entry.entry_id][0] === null) &&
|
||||
d.entry_type !== "service"
|
||||
)
|
||||
.sort(sortDevices);
|
||||
|
||||
const ownServices = allDevices
|
||||
.filter(
|
||||
(d) =>
|
||||
(!d.config_entries_subentries[entry.entry_id]?.length ||
|
||||
d.config_entries_subentries[entry.entry_id][0] === null) &&
|
||||
d.entry_type === "service"
|
||||
)
|
||||
.sort(sortDevices);
|
||||
|
||||
return {
|
||||
entry,
|
||||
devices: ownDevices,
|
||||
services: ownServices,
|
||||
subEntries: entrySubs,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private async _handleEnableDebugLogging() {
|
||||
const integration = this.domain;
|
||||
await setIntegrationLogLevel(
|
||||
|
||||
@@ -13,12 +13,12 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
|
||||
import type { ConfigEntry } from "../../../data/config_entries";
|
||||
import { deleteSubEntry, updateSubEntry } from "../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import type { DiagnosticInfo } from "../../../data/diagnostics";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity/entity_registry";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import type { SubEntryData } from "./ha-config-integration-page";
|
||||
import { showSubConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-sub-config-flow";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
@@ -41,16 +41,16 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public entry!: ConfigEntry;
|
||||
|
||||
@property({ attribute: false }) public subEntry!: SubEntry;
|
||||
@property({ attribute: false }) public data!: SubEntryData;
|
||||
|
||||
@state() private _expanded = true;
|
||||
|
||||
protected render() {
|
||||
const subEntry = this.subEntry;
|
||||
const subEntry = this.data.subEntry;
|
||||
const configEntry = this.entry;
|
||||
|
||||
const devices = this._getDevices();
|
||||
const services = this._getServices();
|
||||
const devices = this.data.devices;
|
||||
const services = this.data.services;
|
||||
const entities = this._getEntities();
|
||||
|
||||
return html`<ha-md-list>
|
||||
@@ -202,30 +202,19 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
|
||||
private _getEntities = (): EntityRegistryEntry[] =>
|
||||
this.entities.filter(
|
||||
(entity) => entity.config_subentry_id === this.subEntry.subentry_id
|
||||
);
|
||||
|
||||
private _getDevices = (): DeviceRegistryEntry[] =>
|
||||
Object.values(this.hass.devices).filter(
|
||||
(device) =>
|
||||
device.config_entries_subentries[this.entry.entry_id]?.includes(
|
||||
this.subEntry.subentry_id
|
||||
) && device.entry_type !== "service"
|
||||
);
|
||||
|
||||
private _getServices = (): DeviceRegistryEntry[] =>
|
||||
Object.values(this.hass.devices).filter(
|
||||
(device) =>
|
||||
device.config_entries_subentries[this.entry.entry_id]?.includes(
|
||||
this.subEntry.subentry_id
|
||||
) && device.entry_type === "service"
|
||||
(entity) => entity.config_subentry_id === this.data.subEntry.subentry_id
|
||||
);
|
||||
|
||||
private async _handleReconfigureSub(): Promise<void> {
|
||||
showSubConfigFlowDialog(this, this.entry, this.subEntry.subentry_type, {
|
||||
startFlowHandler: this.entry.entry_id,
|
||||
subEntryId: this.subEntry.subentry_id,
|
||||
});
|
||||
showSubConfigFlowDialog(
|
||||
this,
|
||||
this.entry,
|
||||
this.data.subEntry.subentry_type,
|
||||
{
|
||||
startFlowHandler: this.entry.entry_id,
|
||||
subEntryId: this.data.subEntry.subentry_id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _handleMenuAction = (ev: CustomEvent) => {
|
||||
@@ -244,7 +233,7 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
private _handleRenameSub = async (): Promise<void> => {
|
||||
const newName = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.common.rename"),
|
||||
defaultValue: this.subEntry.title,
|
||||
defaultValue: this.data.subEntry.title,
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.integrations.rename_input_label"
|
||||
),
|
||||
@@ -255,7 +244,7 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
await updateSubEntry(
|
||||
this.hass,
|
||||
this.entry.entry_id,
|
||||
this.subEntry.subentry_id,
|
||||
this.data.subEntry.subentry_id,
|
||||
{ title: newName }
|
||||
);
|
||||
};
|
||||
@@ -264,7 +253,7 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_title",
|
||||
{ title: this.subEntry.title }
|
||||
{ title: this.data.subEntry.title }
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.delete_confirm_text"
|
||||
@@ -280,7 +269,7 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
await deleteSubEntry(
|
||||
this.hass,
|
||||
this.entry.entry_id,
|
||||
this.subEntry.subentry_id
|
||||
this.data.subEntry.subentry_id
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user