@matter-server/dashboard 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts +25 -0
- package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/base-cluster-commands.js +129 -0
- package/dist/esm/pages/cluster-commands/base-cluster-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/clusters/level-control-commands.d.ts +29 -0
- package/dist/esm/pages/cluster-commands/clusters/level-control-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/clusters/level-control-commands.js +125 -0
- package/dist/esm/pages/cluster-commands/clusters/level-control-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/clusters/on-off-commands.d.ts +25 -0
- package/dist/esm/pages/cluster-commands/clusters/on-off-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/clusters/on-off-commands.js +52 -0
- package/dist/esm/pages/cluster-commands/clusters/on-off-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/index.d.ts +14 -0
- package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/index.js +16 -0
- package/dist/esm/pages/cluster-commands/index.js.map +6 -0
- package/dist/esm/pages/cluster-commands/registry.d.ts +24 -0
- package/dist/esm/pages/cluster-commands/registry.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/registry.js +21 -0
- package/dist/esm/pages/cluster-commands/registry.js.map +6 -0
- package/dist/esm/pages/matter-cluster-view.d.ts +2 -0
- package/dist/esm/pages/matter-cluster-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-cluster-view.js +32 -1
- package/dist/esm/pages/matter-cluster-view.js.map +1 -1
- package/dist/web/js/{commission-node-dialog-BJsfA4IV.js → commission-node-dialog-B6EYNztD.js} +5 -5
- package/dist/web/js/{commission-node-existing-CzRtUgBm.js → commission-node-existing-CKKStTjd.js} +4 -4
- package/dist/web/js/{commission-node-thread-FcLFz84I.js → commission-node-thread-D_Hgo4-b.js} +4 -4
- package/dist/web/js/{commission-node-wifi-C8iGfy7c.js → commission-node-wifi-DQrWnfn9.js} +4 -4
- package/dist/web/js/{dialog-box-DN32sjfR.js → dialog-box-9i64nfcC.js} +2 -2
- package/dist/web/js/{fire_event-BlsbXpOL.js → fire_event-CGgNDbLy.js} +1 -1
- package/dist/web/js/main.js +1 -1
- package/dist/web/js/{matter-dashboard-app-5UjO1Ik8.js → matter-dashboard-app-DD29PeTW.js} +415 -80
- package/dist/web/js/{node-binding-dialog-2yitVn0R.js → node-binding-dialog-DnO2DL_u.js} +3 -3
- package/dist/web/js/{outlined-text-field-BMLYwwlc.js → outlined-text-field-tv3ZtxuK.js} +2 -2
- package/dist/web/js/{prevent_default-BsT53c0u.js → prevent_default-C72e35wq.js} +1 -1
- package/package.json +3 -3
- package/src/pages/cluster-commands/base-cluster-commands.ts +122 -0
- package/src/pages/cluster-commands/clusters/level-control-commands.ts +130 -0
- package/src/pages/cluster-commands/clusters/on-off-commands.ts +57 -0
- package/src/pages/cluster-commands/index.ts +20 -0
- package/src/pages/cluster-commands/registry.ts +40 -0
- package/src/pages/matter-cluster-view.ts +41 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as i, c, n, d as clientContext, e, i as i$1, A, b, t } from './matter-dashboard-app-
|
|
2
|
-
import { p as preventDefault } from './prevent_default-
|
|
3
|
-
import './outlined-text-field-
|
|
1
|
+
import { a as i, c, n, d as clientContext, e, i as i$1, A, b, t } from './matter-dashboard-app-DD29PeTW.js';
|
|
2
|
+
import { p as preventDefault } from './prevent_default-C72e35wq.js';
|
|
3
|
+
import './outlined-text-field-tv3ZtxuK.js';
|
|
4
4
|
import './main.js';
|
|
5
5
|
|
|
6
6
|
var _staticBlock$1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as __decorate, n as n$1, o as o$1, r as r$1, e, i as i$1, f as e$1, A, b, D, E as EASING, a as i$2, t, g as e$2, h as i$3, j as t$1, k as E, l as internals, m as mixinDelegatesAria, p as mixinElementInternals, u, q as i$4 } from './matter-dashboard-app-
|
|
2
|
-
import { r as redispatchEvent } from './prevent_default-
|
|
1
|
+
import { _ as __decorate, n as n$1, o as o$1, r as r$1, e, i as i$1, f as e$1, A, b, D, E as EASING, a as i$2, t, g as e$2, h as i$3, j as t$1, k as E, l as internals, m as mixinDelegatesAria, p as mixinElementInternals, u, q as i$4 } from './matter-dashboard-app-DD29PeTW.js';
|
|
2
|
+
import { r as redispatchEvent } from './prevent_default-C72e35wq.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @license
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as EASING, m as mixinDelegatesAria, i, _ as __decorate, n, e, r, b, f as e$1, A, a as i$1, t } from './matter-dashboard-app-
|
|
1
|
+
import { E as EASING, m as mixinDelegatesAria, i, _ as __decorate, n, e, r, b, f as e$1, A, a as i$1, t } from './matter-dashboard-app-DD29PeTW.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @license
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matter-server/dashboard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Dashboard for OHF Matter Server",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/matter-js/matterjs-server/issues"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@babel/preset-env": "^7.28.5",
|
|
26
|
-
"@matter/main": "0.16.
|
|
26
|
+
"@matter/main": "0.16.4",
|
|
27
27
|
"@rollup/plugin-babel": "^6.1.0",
|
|
28
28
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
29
29
|
"rollup-plugin-copy": "^3.5.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@lit/context": "^1.1.6",
|
|
39
39
|
"@material/web": "^2.4.1",
|
|
40
|
-
"@matter-server/ws-client": "0.2.
|
|
40
|
+
"@matter-server/ws-client": "0.2.3",
|
|
41
41
|
"@mdi/js": "^7.4.47",
|
|
42
42
|
"lit": "^3.3.1",
|
|
43
43
|
"tslib": "^2.8.1"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { MatterClient, MatterNode } from "@matter-server/ws-client";
|
|
8
|
+
import { LitElement, css } from "lit";
|
|
9
|
+
import { property } from "lit/decorators.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base class for cluster-specific command panels.
|
|
13
|
+
* Provides shared properties, styling, and helper methods for sending commands.
|
|
14
|
+
*/
|
|
15
|
+
export abstract class BaseClusterCommands extends LitElement {
|
|
16
|
+
@property({ attribute: false })
|
|
17
|
+
public client!: MatterClient;
|
|
18
|
+
|
|
19
|
+
@property({ attribute: false })
|
|
20
|
+
public node!: MatterNode;
|
|
21
|
+
|
|
22
|
+
@property({ type: Number })
|
|
23
|
+
public endpoint!: number;
|
|
24
|
+
|
|
25
|
+
@property({ type: Number })
|
|
26
|
+
public cluster!: number;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Send a command to the device.
|
|
30
|
+
* @param command - The command name (PascalCase, e.g., "On", "Off", "MoveToLevel")
|
|
31
|
+
* @param payload - Optional command payload
|
|
32
|
+
*/
|
|
33
|
+
protected async sendCommand(command: string, payload?: Record<string, unknown>): Promise<void> {
|
|
34
|
+
try {
|
|
35
|
+
await this.client.deviceCommand(this.node.node_id, this.endpoint, this.cluster, command, payload ?? {});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`Failed to send command ${command}:`, error);
|
|
38
|
+
// Could dispatch an event here for error handling in parent
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static override styles = css`
|
|
43
|
+
:host {
|
|
44
|
+
display: block;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
details.command-panel {
|
|
48
|
+
background-color: var(--md-sys-color-surface-container);
|
|
49
|
+
border-radius: 12px;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
details.command-panel summary {
|
|
54
|
+
padding: 16px;
|
|
55
|
+
font-weight: 500;
|
|
56
|
+
color: var(--md-sys-color-on-surface);
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
user-select: none;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
details.command-panel summary:hover {
|
|
65
|
+
background-color: var(--md-sys-color-surface-container-high);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
details.command-panel summary::before {
|
|
69
|
+
content: "▶";
|
|
70
|
+
font-size: 12px;
|
|
71
|
+
transition: transform 0.2s ease;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
details.command-panel[open] summary::before {
|
|
75
|
+
transform: rotate(90deg);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
details.command-panel summary::-webkit-details-marker {
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.command-content {
|
|
83
|
+
padding: 0 16px 16px 16px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.command-row {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
gap: 12px;
|
|
90
|
+
flex-wrap: wrap;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.command-row label {
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 4px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.command-row input[type="number"] {
|
|
102
|
+
width: 70px;
|
|
103
|
+
padding: 8px;
|
|
104
|
+
border: 1px solid var(--md-sys-color-outline);
|
|
105
|
+
border-radius: 4px;
|
|
106
|
+
background: var(--md-sys-color-surface);
|
|
107
|
+
color: var(--md-sys-color-on-surface);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.command-row input[type="checkbox"] {
|
|
111
|
+
width: 16px;
|
|
112
|
+
height: 16px;
|
|
113
|
+
margin: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
md-filled-button,
|
|
117
|
+
md-outlined-button {
|
|
118
|
+
--md-filled-button-container-height: 36px;
|
|
119
|
+
--md-outlined-button-container-height: 36px;
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import "@material/web/button/filled-button";
|
|
8
|
+
import { html } from "lit";
|
|
9
|
+
import { customElement, state } from "lit/decorators.js";
|
|
10
|
+
import { BaseClusterCommands } from "../base-cluster-commands.js";
|
|
11
|
+
import { registerClusterCommands } from "../registry.js";
|
|
12
|
+
|
|
13
|
+
const CLUSTER_ID = 8; // LevelControl cluster
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Command panel for LevelControl cluster (ID: 8).
|
|
17
|
+
* Provides MoveToLevel command with configurable parameters.
|
|
18
|
+
*/
|
|
19
|
+
@customElement("level-control-cluster-commands")
|
|
20
|
+
class LevelControlClusterCommands extends BaseClusterCommands {
|
|
21
|
+
@state()
|
|
22
|
+
private _targetLevel = 128;
|
|
23
|
+
|
|
24
|
+
@state()
|
|
25
|
+
private _transitionTime = 0;
|
|
26
|
+
|
|
27
|
+
@state()
|
|
28
|
+
private _executeIfOff = false;
|
|
29
|
+
|
|
30
|
+
override render() {
|
|
31
|
+
return html`
|
|
32
|
+
<details class="command-panel">
|
|
33
|
+
<summary>Level Control Commands</summary>
|
|
34
|
+
<div class="command-content">
|
|
35
|
+
<div class="command-row">
|
|
36
|
+
<label for="targetLevel">Level:</label>
|
|
37
|
+
<input
|
|
38
|
+
id="targetLevel"
|
|
39
|
+
type="number"
|
|
40
|
+
min="1"
|
|
41
|
+
max="254"
|
|
42
|
+
.value=${String(this._targetLevel)}
|
|
43
|
+
@input=${this._handleTargetLevelChange}
|
|
44
|
+
/>
|
|
45
|
+
<label for="transitionTime">Transition (0.1s):</label>
|
|
46
|
+
<input
|
|
47
|
+
id="transitionTime"
|
|
48
|
+
type="number"
|
|
49
|
+
min="0"
|
|
50
|
+
max="65535"
|
|
51
|
+
.value=${String(this._transitionTime)}
|
|
52
|
+
@input=${this._handleTransitionTimeChange}
|
|
53
|
+
/>
|
|
54
|
+
<label for="executeIfOff">
|
|
55
|
+
<input
|
|
56
|
+
id="executeIfOff"
|
|
57
|
+
type="checkbox"
|
|
58
|
+
.checked=${this._executeIfOff}
|
|
59
|
+
@change=${this._handleExecuteIfOffChange}
|
|
60
|
+
/>
|
|
61
|
+
Execute if Off
|
|
62
|
+
</label>
|
|
63
|
+
<md-outlined-button @click=${this._handleMoveToLevel}>MoveToLevel</md-outlined-button>
|
|
64
|
+
<md-outlined-button @click=${this._handleMoveToLevelWithOnOff}
|
|
65
|
+
>MoveToLevelWithOnOff</md-outlined-button
|
|
66
|
+
>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</details>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private _handleTargetLevelChange(e: Event) {
|
|
74
|
+
const input = e.target as HTMLInputElement;
|
|
75
|
+
let value = parseInt(input.value, 10);
|
|
76
|
+
if (isNaN(value)) value = 1;
|
|
77
|
+
if (value < 1) value = 1;
|
|
78
|
+
if (value > 254) value = 254;
|
|
79
|
+
this._targetLevel = value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private _handleTransitionTimeChange(e: Event) {
|
|
83
|
+
const input = e.target as HTMLInputElement;
|
|
84
|
+
let value = parseInt(input.value, 10);
|
|
85
|
+
if (isNaN(value)) value = 0;
|
|
86
|
+
if (value < 0) value = 0;
|
|
87
|
+
if (value > 65535) value = 65535;
|
|
88
|
+
this._transitionTime = value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private _handleExecuteIfOffChange(e: Event) {
|
|
92
|
+
const input = e.target as HTMLInputElement;
|
|
93
|
+
this._executeIfOff = input.checked;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async _handleMoveToLevel() {
|
|
97
|
+
// ExecuteIfOff is bit 0 of the Options bitmap
|
|
98
|
+
const optionsMask = this._executeIfOff ? { executeIfOff: true } : {};
|
|
99
|
+
const optionsOverride = this._executeIfOff ? { executeIfOff: true } : {};
|
|
100
|
+
|
|
101
|
+
await this.sendCommand("MoveToLevel", {
|
|
102
|
+
level: this._targetLevel,
|
|
103
|
+
transitionTime: this._transitionTime,
|
|
104
|
+
optionsMask,
|
|
105
|
+
optionsOverride,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async _handleMoveToLevelWithOnOff() {
|
|
110
|
+
// ExecuteIfOff is bit 0 of the Options bitmap
|
|
111
|
+
const optionsMask = this._executeIfOff ? { executeIfOff: true } : {};
|
|
112
|
+
const optionsOverride = this._executeIfOff ? { executeIfOff: true } : {};
|
|
113
|
+
|
|
114
|
+
await this.sendCommand("MoveToLevelWithOnOff", {
|
|
115
|
+
level: this._targetLevel,
|
|
116
|
+
transitionTime: this._transitionTime,
|
|
117
|
+
optionsMask,
|
|
118
|
+
optionsOverride,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Register this component for cluster ID 8
|
|
124
|
+
registerClusterCommands(CLUSTER_ID, "level-control-cluster-commands");
|
|
125
|
+
|
|
126
|
+
declare global {
|
|
127
|
+
interface HTMLElementTagNameMap {
|
|
128
|
+
"level-control-cluster-commands": LevelControlClusterCommands;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import "@material/web/button/filled-button";
|
|
8
|
+
import "@material/web/button/outlined-button";
|
|
9
|
+
import { html } from "lit";
|
|
10
|
+
import { customElement } from "lit/decorators.js";
|
|
11
|
+
import { BaseClusterCommands } from "../base-cluster-commands.js";
|
|
12
|
+
import { registerClusterCommands } from "../registry.js";
|
|
13
|
+
|
|
14
|
+
const CLUSTER_ID = 6; // OnOff cluster
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Command panel for OnOff cluster (ID: 6).
|
|
18
|
+
* Provides On, Off, and Toggle commands.
|
|
19
|
+
*/
|
|
20
|
+
@customElement("on-off-cluster-commands")
|
|
21
|
+
class OnOffClusterCommands extends BaseClusterCommands {
|
|
22
|
+
override render() {
|
|
23
|
+
return html`
|
|
24
|
+
<details class="command-panel">
|
|
25
|
+
<summary>OnOff Commands</summary>
|
|
26
|
+
<div class="command-content">
|
|
27
|
+
<div class="command-row">
|
|
28
|
+
<md-outlined-button @click=${this._handleOn}>On</md-outlined-button>
|
|
29
|
+
<md-outlined-button @click=${this._handleOff}>Off</md-outlined-button>
|
|
30
|
+
<md-outlined-button @click=${this._handleToggle}>Toggle</md-outlined-button>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</details>
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async _handleOn() {
|
|
38
|
+
await this.sendCommand("On");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async _handleOff() {
|
|
42
|
+
await this.sendCommand("Off");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async _handleToggle() {
|
|
46
|
+
await this.sendCommand("Toggle");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Register this component for cluster ID 6
|
|
51
|
+
registerClusterCommands(CLUSTER_ID, "on-off-cluster-commands");
|
|
52
|
+
|
|
53
|
+
declare global {
|
|
54
|
+
interface HTMLElementTagNameMap {
|
|
55
|
+
"on-off-cluster-commands": OnOffClusterCommands;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cluster-specific command panels.
|
|
9
|
+
* Import this file to register all cluster command components.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Registry exports
|
|
13
|
+
export { getClusterCommandsTag, hasClusterCommands, registerClusterCommands } from "./registry.js";
|
|
14
|
+
|
|
15
|
+
// Base class for creating new cluster commands
|
|
16
|
+
export { BaseClusterCommands } from "./base-cluster-commands.js";
|
|
17
|
+
|
|
18
|
+
// Cluster command components (auto-register on import)
|
|
19
|
+
import "./clusters/level-control-commands.js";
|
|
20
|
+
import "./clusters/on-off-commands.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registry for cluster-specific command panel components.
|
|
9
|
+
* Components register themselves by cluster ID and are dynamically
|
|
10
|
+
* rendered in the cluster view when viewing that cluster.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const clusterCommandRegistry = new Map<number, string>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Register a cluster command panel component.
|
|
17
|
+
* @param clusterId - The Matter cluster ID (e.g., 6 for OnOff, 8 for LevelControl)
|
|
18
|
+
* @param tagName - The custom element tag name (e.g., "on-off-cluster-commands")
|
|
19
|
+
*/
|
|
20
|
+
export function registerClusterCommands(clusterId: number, tagName: string): void {
|
|
21
|
+
clusterCommandRegistry.set(clusterId, tagName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get the registered component tag name for a cluster ID.
|
|
26
|
+
* @param clusterId - The Matter cluster ID
|
|
27
|
+
* @returns The custom element tag name, or undefined if not registered
|
|
28
|
+
*/
|
|
29
|
+
export function getClusterCommandsTag(clusterId: number): string | undefined {
|
|
30
|
+
return clusterCommandRegistry.get(clusterId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a cluster has a registered command panel.
|
|
35
|
+
* @param clusterId - The Matter cluster ID
|
|
36
|
+
* @returns true if a command panel is registered
|
|
37
|
+
*/
|
|
38
|
+
export function hasClusterCommands(clusterId: number): boolean {
|
|
39
|
+
return clusterCommandRegistry.has(clusterId);
|
|
40
|
+
}
|
|
@@ -12,11 +12,14 @@ import "@material/web/list/list-item";
|
|
|
12
12
|
import { MatterClient, MatterNode, toBigIntAwareJson } from "@matter-server/ws-client";
|
|
13
13
|
import { LitElement, css, html } from "lit";
|
|
14
14
|
import { customElement, property } from "lit/decorators.js";
|
|
15
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
15
16
|
import { clusters } from "../client/models/descriptions.js";
|
|
16
17
|
import { showAlertDialog } from "../components/dialog-box/show-dialog-box.js";
|
|
17
18
|
import "../components/ha-svg-icon";
|
|
18
19
|
import "../pages/components/node-details";
|
|
19
20
|
import { bindingContext } from "./components/context.js";
|
|
21
|
+
// Cluster command components (auto-register on import)
|
|
22
|
+
import { getClusterCommandsTag } from "./cluster-commands/index.js";
|
|
20
23
|
|
|
21
24
|
declare global {
|
|
22
25
|
interface HTMLElementTagNameMap {
|
|
@@ -27,7 +30,7 @@ declare global {
|
|
|
27
30
|
function clusterAttributes(attributes: { [key: string]: any }, endpoint: number, cluster: number) {
|
|
28
31
|
// extract unique clusters from the node attributes, as (sorted) array
|
|
29
32
|
return Object.keys(attributes)
|
|
30
|
-
.filter(key => key.startsWith(`${endpoint}/${cluster}
|
|
33
|
+
.filter(key => key.startsWith(`${endpoint}/${cluster}/`))
|
|
31
34
|
.map(key => {
|
|
32
35
|
const attributeKey = Number(key.split("/")[2]);
|
|
33
36
|
return { key: attributeKey, value: attributes[key] };
|
|
@@ -68,6 +71,9 @@ class MatterClusterView extends LitElement {
|
|
|
68
71
|
<node-details .node=${this.node} .client=${this.client}></node-details>
|
|
69
72
|
</div>
|
|
70
73
|
|
|
74
|
+
<!-- Cluster commands section (if available for this cluster) -->
|
|
75
|
+
${this._renderClusterCommands()}
|
|
76
|
+
|
|
71
77
|
<!-- Cluster attributes listing -->
|
|
72
78
|
<div class="container">
|
|
73
79
|
<md-list>
|
|
@@ -118,6 +124,40 @@ class MatterClusterView extends LitElement {
|
|
|
118
124
|
});
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
private _renderClusterCommands() {
|
|
128
|
+
if (this.cluster === undefined) return html``;
|
|
129
|
+
if (!this.node?.available) return html``; // Don't show commands when device is offline
|
|
130
|
+
|
|
131
|
+
const tagName = getClusterCommandsTag(this.cluster);
|
|
132
|
+
if (!tagName) return html``;
|
|
133
|
+
|
|
134
|
+
// Dynamically render the registered cluster command component
|
|
135
|
+
const componentHtml = `<${tagName}></${tagName}>`;
|
|
136
|
+
const element = unsafeHTML(componentHtml);
|
|
137
|
+
|
|
138
|
+
return html`
|
|
139
|
+
<div class="container">
|
|
140
|
+
<div id="cluster-commands-container">${element}</div>
|
|
141
|
+
</div>
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override updated(changedProperties: Map<string, unknown>) {
|
|
146
|
+
super.updated(changedProperties);
|
|
147
|
+
|
|
148
|
+
// After render, find and configure the cluster commands component
|
|
149
|
+
const container = this.shadowRoot?.getElementById("cluster-commands-container");
|
|
150
|
+
if (container) {
|
|
151
|
+
const commandsElement = container.firstElementChild as any;
|
|
152
|
+
if (commandsElement && this.node && this.client) {
|
|
153
|
+
commandsElement.client = this.client;
|
|
154
|
+
commandsElement.node = this.node;
|
|
155
|
+
commandsElement.endpoint = this.endpoint;
|
|
156
|
+
commandsElement.cluster = this.cluster;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
121
161
|
private _goBack() {
|
|
122
162
|
history.back();
|
|
123
163
|
}
|