diff --git a/src/common/integrations/protocolIntegrationPicked.ts b/src/common/integrations/protocolIntegrationPicked.ts index dfc3a89f7a..558bee552d 100644 --- a/src/common/integrations/protocolIntegrationPicked.ts +++ b/src/common/integrations/protocolIntegrationPicked.ts @@ -130,8 +130,9 @@ export const protocolIntegrationPicked = async ( text: hass.localize( "ui.panel.config.integrations.config_flow.missing_matter", { + integration: "Matter", brand: options?.brand || options?.domain || "Matter", - link: html` { - if (["zha", "zwave_js"].includes(integration.supported_by!)) { + if ( + (PROTOCOL_INTEGRATIONS as ReadonlyArray).includes( + integration.supported_by! + ) + ) { protocolIntegrationPicked( this, this.hass, diff --git a/src/panels/config/integrations/integration-panels/matter/matter-add-device.ts b/src/panels/config/integrations/integration-panels/matter/matter-add-device.ts new file mode 100644 index 0000000000..76a5bbadf2 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-add-device.ts @@ -0,0 +1,22 @@ +import { customElement } from "lit/decorators"; +import { navigate } from "../../../../../common/navigate"; +import { HomeAssistant } from "../../../../../types"; +import { showMatterAddDeviceDialog } from "./show-dialog-add-matter-device"; + +@customElement("matter-add-device") +export class MatterAddDevice extends HTMLElement { + public hass!: HomeAssistant; + + connectedCallback() { + showMatterAddDeviceDialog(this); + navigate(`/config/devices`, { + replace: true, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "matter-add-device": MatterAddDevice; + } +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-config-dashboard.ts b/src/panels/config/integrations/integration-panels/matter/matter-config-dashboard.ts new file mode 100644 index 0000000000..29e4a62650 --- /dev/null +++ b/src/panels/config/integrations/integration-panels/matter/matter-config-dashboard.ts @@ -0,0 +1,212 @@ +import "@material/mwc-button"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { isComponentLoaded } from "../../../../../common/config/is_component_loaded"; +import "../../../../../components/ha-alert"; +import "../../../../../components/ha-card"; +import { + acceptSharedMatterDevice, + canCommissionMatterExternal, + commissionMatterDevice, + matterSetThread, + matterSetWifi, + redirectOnNewMatterDevice, + startExternalCommissioning, +} from "../../../../../data/matter"; +import { showPromptDialog } from "../../../../../dialogs/generic/show-dialog-box"; +import "../../../../../layouts/hass-subpage"; +import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; + +@customElement("matter-config-dashboard") +export class MatterConfigDashboard extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow!: boolean; + + @state() private _error?: string; + + private _unsub?: UnsubscribeFunc; + + disconnectedCallback() { + super.disconnectedCallback(); + this._stopRedirect(); + } + + protected render(): TemplateResult { + return html` + + ${isComponentLoaded(this.hass, "otbr") + ? html` + + Visit Thread Panel + + ` + : ""} +
+ + Matter is still in the early phase of development, it is not + meant to be used in production. This panel is for development + only. +
+ ${this._error + ? html`${this._error}` + : ""} + You can add Matter devices by commissing them if they are not + setup yet, or share them from another controller and enter the + share code. +
+
+ ${canCommissionMatterExternal(this.hass) + ? html`Commission device with mobile app` + : ""} + Commission device + Add shared device + Set WiFi Credentials + Set Thread Credentials +
+
+
+ + `; + } + + private _redirectOnNewMatterDevice() { + if (this._unsub) { + return; + } + this._unsub = redirectOnNewMatterDevice(this.hass, () => { + this._unsub = undefined; + }); + } + + private _stopRedirect() { + this._unsub?.(); + this._unsub = undefined; + } + + private _startMobileCommissioning() { + this._redirectOnNewMatterDevice(); + startExternalCommissioning(this.hass); + } + + private async _setWifi(): Promise { + this._error = undefined; + const networkName = await showPromptDialog(this, { + title: "Network name", + inputLabel: "Network name", + inputType: "string", + confirmText: "Continue", + }); + if (!networkName) { + return; + } + const psk = await showPromptDialog(this, { + title: "Passcode", + inputLabel: "Code", + inputType: "password", + confirmText: "Set Wifi", + }); + if (!psk) { + return; + } + try { + await matterSetWifi(this.hass, networkName, psk); + } catch (err: any) { + this._error = err.message; + } + } + + private async _commission(): Promise { + const code = await showPromptDialog(this, { + title: "Commission device", + inputLabel: "Code", + inputType: "string", + confirmText: "Commission", + }); + if (!code) { + return; + } + this._error = undefined; + this._redirectOnNewMatterDevice(); + try { + await commissionMatterDevice(this.hass, code); + } catch (err: any) { + this._error = err.message; + this._stopRedirect(); + } + } + + private async _acceptSharedDevice(): Promise { + const code = await showPromptDialog(this, { + title: "Add shared device", + inputLabel: "Pin", + inputType: "number", + confirmText: "Accept device", + }); + if (!code) { + return; + } + this._error = undefined; + this._redirectOnNewMatterDevice(); + try { + await acceptSharedMatterDevice(this.hass, Number(code)); + } catch (err: any) { + this._error = err.message; + this._stopRedirect(); + } + } + + private async _setThread(): Promise { + const code = await showPromptDialog(this, { + title: "Set Thread operation", + inputLabel: "Dataset", + inputType: "string", + confirmText: "Set Thread", + }); + if (!code) { + return; + } + this._error = undefined; + try { + await matterSetThread(this.hass, code); + } catch (err: any) { + this._error = err.message; + } + } + + static styles = [ + haStyle, + css` + ha-alert[alert-type="warning"] { + position: relative; + top: -16px; + } + .content { + padding: 24px 0 32px; + max-width: 600px; + margin: 0 auto; + direction: ltr; + } + ha-card:first-child { + margin-bottom: 16px; + } + a[slot="toolbar-icon"] { + text-decoration: none; + } + `, + ]; +} diff --git a/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts b/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts index 20fcc722f9..e6aa5b102f 100644 --- a/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/matter/matter-config-panel.ts @@ -1,212 +1,58 @@ -import "@material/mwc-button"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { css, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import { isComponentLoaded } from "../../../../../common/config/is_component_loaded"; -import "../../../../../components/ha-alert"; -import "../../../../../components/ha-card"; +import { mdiMathLog, mdiServerNetwork } from "@mdi/js"; +import { customElement, property } from "lit/decorators"; import { - acceptSharedMatterDevice, - canCommissionMatterExternal, - commissionMatterDevice, - matterSetThread, - matterSetWifi, - redirectOnNewMatterDevice, - startExternalCommissioning, -} from "../../../../../data/matter"; -import { showPromptDialog } from "../../../../../dialogs/generic/show-dialog-box"; -import "../../../../../layouts/hass-subpage"; -import { haStyle } from "../../../../../resources/styles"; + HassRouterPage, + RouterOptions, +} from "../../../../../layouts/hass-router-page"; +import { PageNavigation } from "../../../../../layouts/hass-tabs-subpage"; import { HomeAssistant } from "../../../../../types"; +export const configTabs: PageNavigation[] = [ + { + translationKey: "ui.panel.config.zwave_js.navigation.network", + path: `/config/zwave_js/dashboard`, + iconPath: mdiServerNetwork, + }, + { + translationKey: "ui.panel.config.zwave_js.navigation.logs", + path: `/config/zwave_js/logs`, + iconPath: mdiMathLog, + }, +]; + @customElement("matter-config-panel") -export class MatterConfigPanel extends LitElement { +class MatterConfigRouter extends HassRouterPage { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ type: Boolean }) public narrow!: boolean; + @property() public isWide!: boolean; - @state() private _error?: string; + @property() public narrow!: boolean; - private _unsub?: UnsubscribeFunc; + protected routerOptions: RouterOptions = { + defaultPage: "dashboard", + showLoading: true, + routes: { + dashboard: { + tag: "matter-config-dashboard", + load: () => import("./matter-config-dashboard"), + }, + add: { + tag: "matter-add-device", + load: () => import("./matter-add-device"), + }, + }, + }; - disconnectedCallback() { - super.disconnectedCallback(); - this._stopRedirect(); + protected updatePageEl(el): void { + el.route = this.routeTail; + el.hass = this.hass; + el.isWide = this.isWide; + el.narrow = this.narrow; + } +} + +declare global { + interface HTMLElementTagNameMap { + "matter-config-panel": MatterConfigRouter; } - - protected render(): TemplateResult { - return html` - - ${isComponentLoaded(this.hass, "otbr") - ? html` - - Visit Thread Panel - - ` - : ""} -
- - Matter is still in the early phase of development, it is not - meant to be used in production. This panel is for development - only. -
- ${this._error - ? html`${this._error}` - : ""} - You can add Matter devices by commissing them if they are not - setup yet, or share them from another controller and enter the - share code. -
-
- ${canCommissionMatterExternal(this.hass) - ? html`Commission device with mobile app` - : ""} - Commission device - Add shared device - Set WiFi Credentials - Set Thread Credentials -
-
-
-
- `; - } - - private _redirectOnNewMatterDevice() { - if (this._unsub) { - return; - } - this._unsub = redirectOnNewMatterDevice(this.hass, () => { - this._unsub = undefined; - }); - } - - private _stopRedirect() { - this._unsub?.(); - this._unsub = undefined; - } - - private _startMobileCommissioning() { - this._redirectOnNewMatterDevice(); - startExternalCommissioning(this.hass); - } - - private async _setWifi(): Promise { - this._error = undefined; - const networkName = await showPromptDialog(this, { - title: "Network name", - inputLabel: "Network name", - inputType: "string", - confirmText: "Continue", - }); - if (!networkName) { - return; - } - const psk = await showPromptDialog(this, { - title: "Passcode", - inputLabel: "Code", - inputType: "password", - confirmText: "Set Wifi", - }); - if (!psk) { - return; - } - try { - await matterSetWifi(this.hass, networkName, psk); - } catch (err: any) { - this._error = err.message; - } - } - - private async _commission(): Promise { - const code = await showPromptDialog(this, { - title: "Commission device", - inputLabel: "Code", - inputType: "string", - confirmText: "Commission", - }); - if (!code) { - return; - } - this._error = undefined; - this._redirectOnNewMatterDevice(); - try { - await commissionMatterDevice(this.hass, code); - } catch (err: any) { - this._error = err.message; - this._stopRedirect(); - } - } - - private async _acceptSharedDevice(): Promise { - const code = await showPromptDialog(this, { - title: "Add shared device", - inputLabel: "Pin", - inputType: "number", - confirmText: "Accept device", - }); - if (!code) { - return; - } - this._error = undefined; - this._redirectOnNewMatterDevice(); - try { - await acceptSharedMatterDevice(this.hass, Number(code)); - } catch (err: any) { - this._error = err.message; - this._stopRedirect(); - } - } - - private async _setThread(): Promise { - const code = await showPromptDialog(this, { - title: "Set Thread operation", - inputLabel: "Dataset", - inputType: "string", - confirmText: "Set Thread", - }); - if (!code) { - return; - } - this._error = undefined; - try { - await matterSetThread(this.hass, code); - } catch (err: any) { - this._error = err.message; - } - } - - static styles = [ - haStyle, - css` - ha-alert[alert-type="warning"] { - position: relative; - top: -16px; - } - .content { - padding: 24px 0 32px; - max-width: 600px; - margin: 0 auto; - direction: ltr; - } - ha-card:first-child { - margin-bottom: 16px; - } - a[slot="toolbar-icon"] { - text-decoration: none; - } - `, - ]; } diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index 7960d31dba..609aba96f2 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -2,7 +2,10 @@ import { sanitizeUrl } from "@braintree/sanitize-url"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../common/config/is_component_loaded"; -import { protocolIntegrationPicked } from "../../common/integrations/protocolIntegrationPicked"; +import { + protocolIntegrationPicked, + PROTOCOL_INTEGRATIONS, +} from "../../common/integrations/protocolIntegrationPicked"; import { navigate } from "../../common/navigate"; import { createSearchParam, @@ -87,6 +90,10 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ component: "zwave_js", redirect: "/config/zwave_js/add", }, + add_matter_device: { + component: "matter", + redirect: "/config/matter/add", + }, config_energy: { component: "energy", redirect: "/config/energy/dashboard", @@ -310,9 +317,11 @@ class HaPanelMy extends LitElement { ) { this.hass.loadBackendTranslation("title", this._redirect.component); this._error = "no_component"; - if (["add_zwave_device", "add_zigbee_device"].includes(path)) { + const component = this._redirect.component; + if ( + (PROTOCOL_INTEGRATIONS as ReadonlyArray).includes(component) + ) { const params = extractSearchParamsObject(); - const component = this._redirect.component; this.hass .loadFragmentTranslation("config") .then()