mirror of
https://github.com/home-assistant/frontend.git
synced 2026-09-24 05:54:48 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34b892d178 |
@@ -123,8 +123,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||
|
||||
@property({ type: Boolean }) public loading = false;
|
||||
|
||||
@property({ type: Boolean }) public selectable = false;
|
||||
|
||||
@property({ type: Boolean }) public clickable = false;
|
||||
@@ -167,9 +165,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@state() private _filteredData?: DataTableRowData[];
|
||||
|
||||
// Row count of the data that _filteredData was computed from
|
||||
@state() private _filteredDataSourceLength = 0;
|
||||
|
||||
@state() private _headerHeight = 0;
|
||||
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
@@ -528,9 +523,7 @@ export class HaDataTable extends LitElement {
|
||||
role="cell"
|
||||
>
|
||||
${
|
||||
this.loading ||
|
||||
!this._filteredData ||
|
||||
(this.data.length && !this._filteredDataSourceLength)
|
||||
!this._filteredData
|
||||
? this._i18n?.localize?.("ui.common.loading") ||
|
||||
"Loading"
|
||||
: this.data.length
|
||||
@@ -728,11 +721,10 @@ export class HaDataTable extends LitElement {
|
||||
!this._lastUpdate ||
|
||||
(timeBetweenUpdate > 500 && timeBetweenRequest < 500);
|
||||
|
||||
const sourceData = this.data;
|
||||
let filteredData = sourceData;
|
||||
let filteredData = this.data;
|
||||
if (this._filter) {
|
||||
filteredData = await this._memFilterData(
|
||||
sourceData,
|
||||
this.data,
|
||||
this._sortColumns,
|
||||
this._filter.trim()
|
||||
);
|
||||
@@ -768,13 +760,8 @@ export class HaDataTable extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (startTime < this._lastUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastUpdate = startTime;
|
||||
this._filteredData = data;
|
||||
this._filteredDataSourceLength = sourceData.length;
|
||||
}
|
||||
|
||||
private _groupData = memoizeOne(
|
||||
|
||||
@@ -154,12 +154,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
*/
|
||||
@property({ type: Boolean }) public empty = false;
|
||||
|
||||
/**
|
||||
* Show a loading state instead of the empty message until data is ready.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
@property({ type: Boolean }) public loading = false;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
/**
|
||||
@@ -498,7 +492,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this.empty && !this.loading
|
||||
this.empty
|
||||
? html`<div class="center">
|
||||
<slot name="empty">${this.noDataText}</slot>
|
||||
</div>`
|
||||
@@ -520,7 +514,6 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
.narrow=${this.narrow}
|
||||
.columns=${this.columns}
|
||||
.data=${this.data}
|
||||
.loading=${this.loading}
|
||||
.noDataText=${this.noDataText}
|
||||
.filter=${this.filter}
|
||||
.selectable=${this._selectMode}
|
||||
|
||||
@@ -35,12 +35,9 @@ class HaConfigDevices extends HassRouterPage {
|
||||
|
||||
@state() private _manifests: IntegrationManifest[] = [];
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._loadData();
|
||||
}
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
protected updatePageEl(pageEl) {
|
||||
@@ -58,14 +55,8 @@ class HaConfigDevices extends HassRouterPage {
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
await Promise.all([
|
||||
getConfigEntries(this.hass).then((configEntries) => {
|
||||
this._configEntries = configEntries;
|
||||
}),
|
||||
fetchIntegrationManifests(this.hass).then((manifests) => {
|
||||
this._manifests = manifests;
|
||||
}),
|
||||
]);
|
||||
this._configEntries = await getConfigEntries(this.hass);
|
||||
this._manifests = await fetchIntegrationManifests(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from "../../../data/config_flow";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showMatterAddDeviceDialog } from "./integration-panels/matter/show-dialog-add-matter-device";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -182,6 +183,16 @@ export class HaConfigFlowCard extends LitElement {
|
||||
});
|
||||
return;
|
||||
}
|
||||
// A discovered Matter device is commissioned through the Matter dialog;
|
||||
// other steps of a Bluetooth sourced flow set up the integration itself.
|
||||
if (
|
||||
this.flow.handler === "matter" &&
|
||||
this.flow.context.source === "bluetooth" &&
|
||||
this.flow.step_id === "bluetooth_confirm"
|
||||
) {
|
||||
showMatterAddDeviceDialog(this, { discoveryFlowId: this.flow.flow_id });
|
||||
return;
|
||||
}
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: this.flow.flow_id,
|
||||
navigateToResult: true,
|
||||
|
||||
+33
-4
@@ -12,10 +12,13 @@ import "../../../../../components/ha-icon-button-arrow-prev";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import type { MatterCommissionFinish } from "../../../../../external_app/external_messaging";
|
||||
import { handleConfigFlowStep } from "../../../../../data/config_flow";
|
||||
import {
|
||||
canCommissionMatterExternal,
|
||||
commissionMatterDevice,
|
||||
watchForNewMatterDevice,
|
||||
} from "../../../../../data/matter";
|
||||
import type { MatterAddDeviceDialogParams } from "./show-dialog-add-matter-device";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
@@ -37,6 +40,7 @@ import "./matter-add-device/matter-add-device-main";
|
||||
import "./matter-add-device/matter-add-device-new";
|
||||
import "./matter-add-device/matter-add-device-commissioning";
|
||||
import "./matter-add-device/matter-add-device-device-added";
|
||||
import "./matter-add-device/matter-add-device-discovered";
|
||||
import { showToast } from "../../../../../util/toast";
|
||||
|
||||
export type MatterAddDeviceStep =
|
||||
@@ -48,7 +52,8 @@ export type MatterAddDeviceStep =
|
||||
| "apple_home"
|
||||
| "generic"
|
||||
| "commissioning"
|
||||
| "device_added";
|
||||
| "device_added"
|
||||
| "discovered";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -68,6 +73,7 @@ const BACK_STEP: Record<MatterAddDeviceStep, MatterAddDeviceStep | undefined> =
|
||||
generic: "existing",
|
||||
commissioning: undefined,
|
||||
device_added: undefined,
|
||||
discovered: undefined,
|
||||
};
|
||||
|
||||
@customElement("dialog-matter-add-device")
|
||||
@@ -104,8 +110,15 @@ class DialogMatterAddDevice extends LitElement {
|
||||
|
||||
private _unsub?: UnsubscribeFunc;
|
||||
|
||||
public showDialog(): void {
|
||||
private _discoveryFlowId?: string;
|
||||
|
||||
public showDialog(params: MatterAddDeviceDialogParams): void {
|
||||
this._discoveryFlowId = params.discoveryFlowId;
|
||||
this._open = true;
|
||||
// A discovered device only needs its code, unless the app can scan it.
|
||||
if (this._discoveryFlowId && !canCommissionMatterExternal(this.hass)) {
|
||||
this._step = "discovered";
|
||||
}
|
||||
this._unsub = watchForNewMatterDevice(this.hass, (device) => {
|
||||
// make sure a refresh of the page will navigate to the device page, old iOS apps will refresh the webview when commissioning is done
|
||||
setRefreshUrl(`/config/devices/device/${device.id}`);
|
||||
@@ -211,6 +224,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
this._open = false;
|
||||
this._step = "main";
|
||||
this._pairingCode = "";
|
||||
this._discoveryFlowId = undefined;
|
||||
this._newDevice = undefined;
|
||||
this._mainEntity = undefined;
|
||||
this._mainEntityFetched = false;
|
||||
@@ -283,7 +297,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
const savedStep = this._step;
|
||||
try {
|
||||
this._step = "commissioning";
|
||||
await commissionMatterDevice(this.hass, code, true);
|
||||
await this._commission(code);
|
||||
} catch (_err) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
@@ -296,6 +310,20 @@ class DialogMatterAddDevice extends LitElement {
|
||||
// On success, keep showing commissioning spinner until watchForNewMatterDevice fires
|
||||
}
|
||||
|
||||
private async _commission(code: string): Promise<void> {
|
||||
if (!this._discoveryFlowId) {
|
||||
await commissionMatterDevice(this.hass, code, true);
|
||||
return;
|
||||
}
|
||||
// The discovery flow commissions over Bluetooth and ends itself on success.
|
||||
const step = await handleConfigFlowStep(this.hass, this._discoveryFlowId, {
|
||||
code,
|
||||
});
|
||||
if (step.type !== "abort" || step.reason !== "commission_successful") {
|
||||
throw new Error("Commissioning failed");
|
||||
}
|
||||
}
|
||||
|
||||
private async _finishDeviceAdded(): Promise<void> {
|
||||
const device = this._newDevice!;
|
||||
const { name, area, deviceClass, hasPendingUpdates } =
|
||||
@@ -377,7 +405,8 @@ class DialogMatterAddDevice extends LitElement {
|
||||
if (
|
||||
this._step === "apple_home" ||
|
||||
this._step === "google_home_fallback" ||
|
||||
this._step === "generic"
|
||||
this._step === "generic" ||
|
||||
this._step === "discovered"
|
||||
) {
|
||||
return html`
|
||||
<ha-button
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
|
||||
@customElement("matter-add-device-discovered")
|
||||
class MatterAddDeviceDiscovered extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _code = "";
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.matter-add-device.discovered.code_instructions"
|
||||
)}
|
||||
</p>
|
||||
<ha-input
|
||||
label=${this.hass.localize(
|
||||
"ui.dialogs.matter-add-device.discovered.setup_code"
|
||||
)}
|
||||
.value=${this._code}
|
||||
@input=${this._onCodeChanged}
|
||||
></ha-input>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _onCodeChanged(ev: InputEvent) {
|
||||
const value = (ev.target as HTMLInputElement).value;
|
||||
this._code = value;
|
||||
fireEvent(this, "pairing-code-changed", { code: value });
|
||||
}
|
||||
|
||||
static styles = [sharedStyles];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"matter-add-device-discovered": MatterAddDeviceDiscovered;
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -1,11 +1,19 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface MatterAddDeviceDialogParams {
|
||||
/** Bluetooth discovery flow to commission through, if any. */
|
||||
discoveryFlowId?: string;
|
||||
}
|
||||
|
||||
export const loadAddDeviceDialog = () => import("./dialog-matter-add-device");
|
||||
|
||||
export const showMatterAddDeviceDialog = (element: HTMLElement): void => {
|
||||
export const showMatterAddDeviceDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: MatterAddDeviceDialogParams = {}
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-matter-add-device",
|
||||
dialogImport: loadAddDeviceDialog,
|
||||
dialogParams: {},
|
||||
dialogParams,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -108,8 +108,6 @@ export class HaConfigLabels extends LitElement {
|
||||
|
||||
@state() private _labels: LabelRegistryEntry[] = [];
|
||||
|
||||
@state() private _loading = true;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
storage: "sessionStorage",
|
||||
@@ -260,7 +258,6 @@ export class HaConfigLabels extends LitElement {
|
||||
.tabs=${configSections.areas}
|
||||
.columns=${this._columns(this.hass.localize, this.narrow)}
|
||||
.data=${this._data(this._labels)}
|
||||
.loading=${this._loading}
|
||||
.noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")}
|
||||
has-fab
|
||||
.initialSorting=${this._activeSorting}
|
||||
@@ -324,11 +321,7 @@ export class HaConfigLabels extends LitElement {
|
||||
}
|
||||
|
||||
private async _fetchLabels() {
|
||||
try {
|
||||
this._labels = await fetchLabelRegistry(this.hass.connection);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
this._labels = await fetchLabelRegistry(this.hass.connection);
|
||||
}
|
||||
|
||||
private _addLabel() {
|
||||
|
||||
@@ -178,7 +178,6 @@ class DialogSystemLogDetail extends LitElement {
|
||||
<span slot="headerTitle">${title}</span>
|
||||
<ha-icon-button
|
||||
id="copy"
|
||||
autofocus
|
||||
@click=${this._copyLog}
|
||||
slot="headerActionItems"
|
||||
.label=${this._i18n.localize("ui.panel.config.logs.copy")}
|
||||
@@ -238,7 +237,7 @@ class DialogSystemLogDetail extends LitElement {
|
||||
}
|
||||
</ha-alert>`
|
||||
}
|
||||
<div class="contents">
|
||||
<div class="contents" tabindex="-1" autofocus>
|
||||
<p>
|
||||
${this._i18n.localize("ui.panel.config.logs.detail.logger")}:
|
||||
${item.name}<br />
|
||||
|
||||
@@ -10,7 +10,7 @@ import { clearStatistics, getStatisticLabel } from "../../../../data/recorder";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { showAlertDialog } from "../../../lovelace/custom-card-helpers";
|
||||
import type { DialogStatisticsFixParams } from "./show-dialog-statistics-fix";
|
||||
|
||||
@customElement("dialog-statistics-fix")
|
||||
|
||||
@@ -13,14 +13,7 @@ import {
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import {
|
||||
css,
|
||||
type CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
type PropertyValues,
|
||||
} from "lit";
|
||||
import { css, type CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type {
|
||||
@@ -74,7 +67,7 @@ import { getAreaTableColumn } from "../../common/data-table-columns";
|
||||
import { KeyboardShortcutMixin } from "../../../../mixins/keyboard-shortcut-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistantRegistries } from "../../../../types";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../lovelace/custom-card-helpers";
|
||||
import { fixStatisticsIssue } from "./fix-statistics";
|
||||
import { showStatisticsAdjustSumDialog } from "./show-dialog-statistics-adjust-sum";
|
||||
|
||||
@@ -115,8 +108,6 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _data: StatisticData[] = [] as StatisticsMetaData[];
|
||||
|
||||
@state() private _loading = true;
|
||||
|
||||
@state() private filter = "";
|
||||
|
||||
@state() private _selected: string[] = [];
|
||||
@@ -155,12 +146,8 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@query("ha-input-search") private _searchInput!: HaInputSearch;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._validateStatistics();
|
||||
}
|
||||
protected firstUpdated() {
|
||||
this._validateStatistics();
|
||||
}
|
||||
|
||||
private _displayData = memoizeOne(
|
||||
@@ -567,7 +554,6 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
<ha-data-table
|
||||
.narrow=${this.narrow}
|
||||
.loading=${this._loading}
|
||||
.columns=${columns}
|
||||
.data=${this._displayData(
|
||||
this._data,
|
||||
@@ -736,42 +722,38 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _validateStatistics() {
|
||||
try {
|
||||
const [statisticIds, issues] = await Promise.all([
|
||||
getStatisticIds(this._api),
|
||||
validateStatistics(this._api),
|
||||
]);
|
||||
const [statisticIds, issues] = await Promise.all([
|
||||
getStatisticIds(this._api),
|
||||
validateStatistics(this._api),
|
||||
]);
|
||||
|
||||
updateStatisticsIssues(this._api);
|
||||
updateStatisticsIssues(this._api);
|
||||
|
||||
const statsIds = new Set();
|
||||
const statsIds = new Set();
|
||||
|
||||
this._data = statisticIds.map((statistic) => {
|
||||
statsIds.add(statistic.statistic_id);
|
||||
return {
|
||||
...statistic,
|
||||
state: this._states[statistic.statistic_id],
|
||||
issues: issues[statistic.statistic_id],
|
||||
};
|
||||
});
|
||||
this._data = statisticIds.map((statistic) => {
|
||||
statsIds.add(statistic.statistic_id);
|
||||
return {
|
||||
...statistic,
|
||||
state: this._states[statistic.statistic_id],
|
||||
issues: issues[statistic.statistic_id],
|
||||
};
|
||||
});
|
||||
|
||||
Object.keys(issues).forEach((statisticId) => {
|
||||
if (!statsIds.has(statisticId)) {
|
||||
this._data.push({
|
||||
statistic_id: statisticId,
|
||||
statistics_unit_of_measurement: "",
|
||||
source: "",
|
||||
state: this._states[statisticId],
|
||||
issues: issues[statisticId],
|
||||
mean_type: StatisticMeanType.NONE,
|
||||
has_sum: false,
|
||||
unit_class: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
Object.keys(issues).forEach((statisticId) => {
|
||||
if (!statsIds.has(statisticId)) {
|
||||
this._data.push({
|
||||
statistic_id: statisticId,
|
||||
statistics_unit_of_measurement: "",
|
||||
source: "",
|
||||
state: this._states[statisticId],
|
||||
issues: issues[statisticId],
|
||||
mean_type: StatisticMeanType.NONE,
|
||||
has_sum: false,
|
||||
unit_class: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _clearSelected = async () => {
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
export const loadVirtualizer = async () => {
|
||||
// The default flow layout is otherwise only fetched once there are items.
|
||||
await Promise.all([
|
||||
import("@lit-labs/virtualizer"),
|
||||
import("@lit-labs/virtualizer/layouts/flow.js"),
|
||||
]);
|
||||
await import("@lit-labs/virtualizer");
|
||||
};
|
||||
|
||||
@@ -2514,6 +2514,11 @@
|
||||
"code_instructions": "Search for the sharing mode in the app of your controller, and activate it. You will get a setup code, enter that below.",
|
||||
"setup_code": "Setup code"
|
||||
},
|
||||
"discovered": {
|
||||
"header": "Add discovered device",
|
||||
"code_instructions": "This device was found nearby over Bluetooth. Enter the setup code printed on the device or its packaging, or the text of its QR code.",
|
||||
"setup_code": "[%key:ui::dialogs::matter-add-device::generic::setup_code%]"
|
||||
},
|
||||
"device_added": {
|
||||
"header": "Device added",
|
||||
"finish": "Finish",
|
||||
|
||||
Reference in New Issue
Block a user