@matter-server/dashboard 0.7.0-alpha.0-20260512-b404bea → 0.7.0

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.
Files changed (171) hide show
  1. package/dist/esm/client/client-context.d.ts +3 -0
  2. package/dist/esm/client/client-context.d.ts.map +1 -1
  3. package/dist/esm/client/client-context.js +3 -1
  4. package/dist/esm/client/client-context.js.map +1 -1
  5. package/dist/esm/client/models/descriptions.js +45 -45
  6. package/dist/esm/components/avsum-ptz-strip.d.ts +41 -0
  7. package/dist/esm/components/avsum-ptz-strip.d.ts.map +1 -0
  8. package/dist/esm/components/avsum-ptz-strip.js +379 -0
  9. package/dist/esm/components/avsum-ptz-strip.js.map +6 -0
  10. package/dist/esm/components/dialogs/binding/node-binding-dialog.js +1 -1
  11. package/dist/esm/components/dialogs/binding/show-node-binding-dialog.d.ts +2 -2
  12. package/dist/esm/components/dialogs/binding/show-node-binding-dialog.d.ts.map +1 -1
  13. package/dist/esm/components/dialogs/binding/show-node-binding-dialog.js +1 -2
  14. package/dist/esm/components/dialogs/binding/show-node-binding-dialog.js.map +1 -1
  15. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-dialog.d.ts +1 -0
  16. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-dialog.d.ts.map +1 -1
  17. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-dialog.js +12 -3
  18. package/dist/esm/components/dialogs/commission-node-dialog/commission-node-dialog.js.map +1 -1
  19. package/dist/esm/components/dialogs/commission-node-dialog/show-commission-node-dialog.d.ts +1 -2
  20. package/dist/esm/components/dialogs/commission-node-dialog/show-commission-node-dialog.d.ts.map +1 -1
  21. package/dist/esm/components/dialogs/commission-node-dialog/show-commission-node-dialog.js +1 -2
  22. package/dist/esm/components/dialogs/commission-node-dialog/show-commission-node-dialog.js.map +1 -1
  23. package/dist/esm/components/dialogs/settings/log-level-dialog.d.ts +0 -2
  24. package/dist/esm/components/dialogs/settings/log-level-dialog.d.ts.map +1 -1
  25. package/dist/esm/components/dialogs/settings/log-level-dialog.js +2 -8
  26. package/dist/esm/components/dialogs/settings/log-level-dialog.js.map +1 -1
  27. package/dist/esm/components/dialogs/settings/log-level-section.d.ts +2 -1
  28. package/dist/esm/components/dialogs/settings/log-level-section.d.ts.map +1 -1
  29. package/dist/esm/components/dialogs/settings/log-level-section.js +10 -2
  30. package/dist/esm/components/dialogs/settings/log-level-section.js.map +1 -1
  31. package/dist/esm/components/dialogs/settings/settings-dialog.d.ts +1 -0
  32. package/dist/esm/components/dialogs/settings/settings-dialog.d.ts.map +1 -1
  33. package/dist/esm/components/dialogs/settings/settings-dialog.js +8 -2
  34. package/dist/esm/components/dialogs/settings/settings-dialog.js.map +1 -1
  35. package/dist/esm/components/dialogs/settings/show-log-level-dialog.d.ts +1 -2
  36. package/dist/esm/components/dialogs/settings/show-log-level-dialog.d.ts.map +1 -1
  37. package/dist/esm/components/dialogs/settings/show-log-level-dialog.js +1 -2
  38. package/dist/esm/components/dialogs/settings/show-log-level-dialog.js.map +1 -1
  39. package/dist/esm/components/dialogs/settings/show-settings-dialog.d.ts +1 -2
  40. package/dist/esm/components/dialogs/settings/show-settings-dialog.d.ts.map +1 -1
  41. package/dist/esm/components/dialogs/settings/show-settings-dialog.js +1 -2
  42. package/dist/esm/components/dialogs/settings/show-settings-dialog.js.map +1 -1
  43. package/dist/esm/components/webrtc-stream-view.d.ts +97 -0
  44. package/dist/esm/components/webrtc-stream-view.d.ts.map +1 -0
  45. package/dist/esm/components/webrtc-stream-view.js +878 -0
  46. package/dist/esm/components/webrtc-stream-view.js.map +6 -0
  47. package/dist/esm/pages/camera-overlay.d.ts +63 -0
  48. package/dist/esm/pages/camera-overlay.d.ts.map +1 -0
  49. package/dist/esm/pages/camera-overlay.js +546 -0
  50. package/dist/esm/pages/camera-overlay.js.map +6 -0
  51. package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts +1 -0
  52. package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts.map +1 -1
  53. package/dist/esm/pages/cluster-commands/base-cluster-commands.js +11 -1
  54. package/dist/esm/pages/cluster-commands/base-cluster-commands.js.map +1 -1
  55. package/dist/esm/pages/cluster-commands/clusters/avsum-commands.d.ts +38 -0
  56. package/dist/esm/pages/cluster-commands/clusters/avsum-commands.d.ts.map +1 -0
  57. package/dist/esm/pages/cluster-commands/clusters/avsum-commands.js +534 -0
  58. package/dist/esm/pages/cluster-commands/clusters/avsum-commands.js.map +6 -0
  59. package/dist/esm/pages/cluster-commands/clusters/chime-commands.d.ts +32 -0
  60. package/dist/esm/pages/cluster-commands/clusters/chime-commands.d.ts.map +1 -0
  61. package/dist/esm/pages/cluster-commands/clusters/chime-commands.js +261 -0
  62. package/dist/esm/pages/cluster-commands/clusters/chime-commands.js.map +6 -0
  63. package/dist/esm/pages/cluster-commands/index.d.ts +2 -0
  64. package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -1
  65. package/dist/esm/pages/cluster-commands/index.js +2 -0
  66. package/dist/esm/pages/cluster-commands/index.js.map +1 -1
  67. package/dist/esm/pages/components/header.d.ts +1 -0
  68. package/dist/esm/pages/components/header.d.ts.map +1 -1
  69. package/dist/esm/pages/components/header.js +10 -3
  70. package/dist/esm/pages/components/header.js.map +1 -1
  71. package/dist/esm/pages/components/node-details.d.ts +3 -0
  72. package/dist/esm/pages/components/node-details.d.ts.map +1 -1
  73. package/dist/esm/pages/components/node-details.js +34 -4
  74. package/dist/esm/pages/components/node-details.js.map +1 -1
  75. package/dist/esm/pages/components/server-details.d.ts +1 -0
  76. package/dist/esm/pages/components/server-details.d.ts.map +1 -1
  77. package/dist/esm/pages/components/server-details.js +11 -2
  78. package/dist/esm/pages/components/server-details.js.map +1 -1
  79. package/dist/esm/pages/matter-cluster-view.d.ts +1 -0
  80. package/dist/esm/pages/matter-cluster-view.d.ts.map +1 -1
  81. package/dist/esm/pages/matter-cluster-view.js +15 -8
  82. package/dist/esm/pages/matter-cluster-view.js.map +1 -1
  83. package/dist/esm/pages/matter-dashboard-app.d.ts +3 -1
  84. package/dist/esm/pages/matter-dashboard-app.d.ts.map +1 -1
  85. package/dist/esm/pages/matter-dashboard-app.js +12 -16
  86. package/dist/esm/pages/matter-dashboard-app.js.map +1 -1
  87. package/dist/esm/pages/matter-endpoint-view.d.ts +2 -1
  88. package/dist/esm/pages/matter-endpoint-view.d.ts.map +1 -1
  89. package/dist/esm/pages/matter-endpoint-view.js +17 -3
  90. package/dist/esm/pages/matter-endpoint-view.js.map +1 -1
  91. package/dist/esm/pages/matter-network-view.d.ts.map +1 -1
  92. package/dist/esm/pages/matter-network-view.js +5 -1
  93. package/dist/esm/pages/matter-network-view.js.map +1 -1
  94. package/dist/esm/pages/matter-node-view.d.ts +1 -0
  95. package/dist/esm/pages/matter-node-view.d.ts.map +1 -1
  96. package/dist/esm/pages/matter-node-view.js +15 -7
  97. package/dist/esm/pages/matter-node-view.js.map +1 -1
  98. package/dist/esm/pages/matter-server-view.d.ts +1 -0
  99. package/dist/esm/pages/matter-server-view.d.ts.map +1 -1
  100. package/dist/esm/pages/matter-server-view.js +13 -2
  101. package/dist/esm/pages/matter-server-view.js.map +1 -1
  102. package/dist/esm/pages/network/network-details.d.ts.map +1 -1
  103. package/dist/esm/pages/network/network-details.js +0 -3
  104. package/dist/esm/pages/network/network-details.js.map +1 -1
  105. package/dist/esm/pages/network/update-connections-dialog.d.ts +1 -0
  106. package/dist/esm/pages/network/update-connections-dialog.d.ts.map +1 -1
  107. package/dist/esm/pages/network/update-connections-dialog.js +7 -1
  108. package/dist/esm/pages/network/update-connections-dialog.js.map +1 -1
  109. package/dist/esm/util/attribute-shapes.d.ts +11 -0
  110. package/dist/esm/util/attribute-shapes.d.ts.map +1 -0
  111. package/dist/esm/util/attribute-shapes.js +37 -0
  112. package/dist/esm/util/attribute-shapes.js.map +6 -0
  113. package/dist/esm/util/avsum.d.ts +69 -0
  114. package/dist/esm/util/avsum.d.ts.map +1 -0
  115. package/dist/esm/util/avsum.js +150 -0
  116. package/dist/esm/util/avsum.js.map +6 -0
  117. package/dist/esm/util/chime.d.ts +20 -0
  118. package/dist/esm/util/chime.d.ts.map +1 -0
  119. package/dist/esm/util/chime.js +60 -0
  120. package/dist/esm/util/chime.js.map +6 -0
  121. package/dist/web/js/{attribute-write-dialog-DzMWN_T3.js → attribute-write-dialog-DLRP2hlx.js} +1 -1
  122. package/dist/web/js/{command-invoke-dialog-BhAOXzjX.js → command-invoke-dialog-BcCfT1sn.js} +1 -1
  123. package/dist/web/js/{commission-node-dialog-DF87YIqR.js → commission-node-dialog-BfldkUxD.js} +16 -8
  124. package/dist/web/js/{commission-node-existing-DLcWvJTL.js → commission-node-existing-omW-An0J.js} +2 -2
  125. package/dist/web/js/{commission-node-thread-m2fqED-2.js → commission-node-thread-BZpJIxlI.js} +2 -2
  126. package/dist/web/js/{commission-node-wifi-PCsot-CX.js → commission-node-wifi-HA2pYTW2.js} +2 -2
  127. package/dist/web/js/{dialog-box-DiqYXM8c.js → dialog-box-BjDID9j4.js} +1 -1
  128. package/dist/web/js/{fire_event-BnpND_gK.js → fire_event-CChLgSiV.js} +1 -1
  129. package/dist/web/js/main.js +39 -1
  130. package/dist/web/js/{matter-dashboard-app-BZOhBELR.js → matter-dashboard-app-XUWBcruM.js} +8265 -2035
  131. package/dist/web/js/{node-binding-dialog-B3lVN-FU.js → node-binding-dialog-_yHtf7WP.js} +2 -2
  132. package/dist/web/js/settings-dialog-BFPfOSOI.js +607 -0
  133. package/package.json +4 -4
  134. package/src/client/client-context.ts +1 -0
  135. package/src/client/models/descriptions.ts +45 -45
  136. package/src/components/avsum-ptz-strip.ts +388 -0
  137. package/src/components/dialogs/binding/node-binding-dialog.ts +1 -1
  138. package/src/components/dialogs/binding/show-node-binding-dialog.ts +2 -3
  139. package/src/components/dialogs/commission-node-dialog/commission-node-dialog.ts +10 -4
  140. package/src/components/dialogs/commission-node-dialog/show-commission-node-dialog.ts +1 -4
  141. package/src/components/dialogs/settings/log-level-dialog.ts +2 -9
  142. package/src/components/dialogs/settings/log-level-section.ts +11 -4
  143. package/src/components/dialogs/settings/settings-dialog.ts +8 -3
  144. package/src/components/dialogs/settings/show-log-level-dialog.ts +1 -4
  145. package/src/components/dialogs/settings/show-settings-dialog.ts +1 -4
  146. package/src/components/webrtc-stream-view.ts +999 -0
  147. package/src/pages/camera-overlay.ts +562 -0
  148. package/src/pages/cluster-commands/base-cluster-commands.ts +7 -1
  149. package/src/pages/cluster-commands/clusters/avsum-commands.ts +562 -0
  150. package/src/pages/cluster-commands/clusters/chime-commands.ts +272 -0
  151. package/src/pages/cluster-commands/index.ts +2 -0
  152. package/src/pages/components/header.ts +7 -3
  153. package/src/pages/components/node-details.ts +33 -3
  154. package/src/pages/components/server-details.ts +8 -2
  155. package/src/pages/matter-cluster-view.ts +13 -9
  156. package/src/pages/matter-dashboard-app.ts +11 -16
  157. package/src/pages/matter-endpoint-view.ts +13 -5
  158. package/src/pages/matter-network-view.ts +3 -1
  159. package/src/pages/matter-node-view.ts +9 -7
  160. package/src/pages/matter-server-view.ts +7 -2
  161. package/src/pages/network/network-details.ts +0 -3
  162. package/src/pages/network/update-connections-dialog.ts +6 -1
  163. package/src/util/attribute-shapes.ts +34 -0
  164. package/src/util/avsum.ts +212 -0
  165. package/src/util/chime.ts +82 -0
  166. package/dist/esm/util/clone_class.d.ts +0 -7
  167. package/dist/esm/util/clone_class.d.ts.map +0 -1
  168. package/dist/esm/util/clone_class.js +0 -10
  169. package/dist/esm/util/clone_class.js.map +0 -6
  170. package/dist/web/js/settings-dialog-D75IAPKe.js +0 -4039
  171. package/src/util/clone_class.ts +0 -7
@@ -0,0 +1,388 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { consume } from "@lit/context";
8
+ import "@material/web/iconbutton/icon-button";
9
+ import type { MatterClient, MatterNode } from "@matter-server/ws-client";
10
+ import { mdiArrowDown, mdiArrowLeft, mdiArrowRight, mdiArrowUp, mdiMinus, mdiPlus } from "@mdi/js";
11
+ import { css, html, LitElement, nothing } from "lit";
12
+ import { customElement, property, state } from "lit/decorators.js";
13
+ import { clientContext } from "../client/client-context.js";
14
+ import { handleAsync, handleAsyncEvent } from "../util/async-handler.js";
15
+ import {
16
+ dptzRelativeMove,
17
+ hasAvsumOnEndpoint,
18
+ moveToPreset,
19
+ readFeatures,
20
+ readMovementState,
21
+ readPosition,
22
+ readPresets,
23
+ relativeMove,
24
+ } from "../util/avsum.js";
25
+ import "./ha-svg-icon.js";
26
+
27
+ type MoveMode = "mech" | "digital";
28
+
29
+ @customElement("avsum-ptz-strip")
30
+ export class AvsumPtzStrip extends LitElement {
31
+ @consume({ context: clientContext, subscribe: true })
32
+ @property({ attribute: false })
33
+ client?: MatterClient;
34
+
35
+ @property({ attribute: false }) nodeId!: number | bigint;
36
+ @property({ type: Number }) endpointId!: number;
37
+ /** Active VideoStreamID for DPTZ moves; required when DPTZ is selected mode. */
38
+ @property({ type: Number, attribute: false }) activeVideoStreamId: number | null = null;
39
+ @property({ type: Object, attribute: false }) sensorSize: { width: number; height: number } | null = null;
40
+
41
+ @state() private _mode: MoveMode = "mech";
42
+ @state() private _toast: string | null = null;
43
+ private _unsubscribeNodes?: () => void;
44
+ private _toastTimer?: ReturnType<typeof setTimeout>;
45
+
46
+ private get _node(): MatterNode | null {
47
+ if (!this.client) return null;
48
+ return this.client.nodes[String(this.nodeId)] ?? null;
49
+ }
50
+
51
+ override connectedCallback() {
52
+ super.connectedCallback();
53
+ if (this.client) {
54
+ this._unsubscribeNodes = this.client.addEventListener("nodes_changed", () => this.requestUpdate());
55
+ }
56
+ }
57
+
58
+ override disconnectedCallback() {
59
+ super.disconnectedCallback();
60
+ this._unsubscribeNodes?.();
61
+ if (this._toastTimer) {
62
+ clearTimeout(this._toastTimer);
63
+ this._toastTimer = undefined;
64
+ }
65
+ }
66
+
67
+ override render() {
68
+ const node = this._node;
69
+ if (!node || !hasAvsumOnEndpoint(node, this.endpointId)) return nothing;
70
+ const features = readFeatures(node, this.endpointId);
71
+ if (!features.mPan && !features.mTilt && !features.mZoom && !features.dptz && !features.mPresets)
72
+ return nothing;
73
+
74
+ const hasMech = features.mPan || features.mTilt || features.mZoom;
75
+ const effective: MoveMode = hasMech && features.dptz ? this._mode : hasMech ? "mech" : "digital";
76
+
77
+ const pos = readPosition(node, this.endpointId);
78
+ const movement = readMovementState(node, this.endpointId);
79
+ const presets = features.mPresets ? readPresets(node, this.endpointId).items : [];
80
+
81
+ return html`
82
+ <div class="strip">
83
+ ${hasMech || features.dptz
84
+ ? html`<div class="group movement">
85
+ <div class="dpad">
86
+ <span></span>
87
+ <md-icon-button
88
+ ?disabled=${this._isDisabled(effective, "tilt", features, movement)}
89
+ title="Tilt up"
90
+ @click=${handleAsyncEvent((e: MouseEvent) =>
91
+ this._move(effective, "tilt", this._step(e, 10)),
92
+ )}
93
+ >
94
+ <ha-svg-icon .path=${mdiArrowUp}></ha-svg-icon>
95
+ </md-icon-button>
96
+ <span></span>
97
+ <md-icon-button
98
+ ?disabled=${this._isDisabled(effective, "pan", features, movement)}
99
+ title="Pan left"
100
+ @click=${handleAsyncEvent((e: MouseEvent) =>
101
+ this._move(effective, "pan", this._step(e, -10)),
102
+ )}
103
+ >
104
+ <ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
105
+ </md-icon-button>
106
+ <span></span>
107
+ <md-icon-button
108
+ ?disabled=${this._isDisabled(effective, "pan", features, movement)}
109
+ title="Pan right"
110
+ @click=${handleAsyncEvent((e: MouseEvent) =>
111
+ this._move(effective, "pan", this._step(e, 10)),
112
+ )}
113
+ >
114
+ <ha-svg-icon .path=${mdiArrowRight}></ha-svg-icon>
115
+ </md-icon-button>
116
+ <span></span>
117
+ <md-icon-button
118
+ ?disabled=${this._isDisabled(effective, "tilt", features, movement)}
119
+ title="Tilt down"
120
+ @click=${handleAsyncEvent((e: MouseEvent) =>
121
+ this._move(effective, "tilt", this._step(e, -10)),
122
+ )}
123
+ >
124
+ <ha-svg-icon .path=${mdiArrowDown}></ha-svg-icon>
125
+ </md-icon-button>
126
+ <span></span>
127
+ </div>
128
+ <div class="zoom">
129
+ <md-icon-button
130
+ ?disabled=${this._isDisabled(effective, "zoom", features, movement)}
131
+ title="Zoom in"
132
+ @click=${handleAsyncEvent((e: MouseEvent) =>
133
+ this._move(effective, "zoom", this._step(e, 10)),
134
+ )}
135
+ >
136
+ <ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
137
+ </md-icon-button>
138
+ <md-icon-button
139
+ ?disabled=${this._isDisabled(effective, "zoom", features, movement)}
140
+ title="Zoom out"
141
+ @click=${handleAsyncEvent((e: MouseEvent) =>
142
+ this._move(effective, "zoom", this._step(e, -10)),
143
+ )}
144
+ >
145
+ <ha-svg-icon .path=${mdiMinus}></ha-svg-icon>
146
+ </md-icon-button>
147
+ </div>
148
+ ${hasMech && features.dptz
149
+ ? html`<div class="mode-toggle">
150
+ <button
151
+ class=${this._mode === "mech" ? "active" : ""}
152
+ @click=${() => (this._mode = "mech")}
153
+ >
154
+ Mech
155
+ </button>
156
+ <button
157
+ class=${this._mode === "digital" ? "active" : ""}
158
+ @click=${() => (this._mode = "digital")}
159
+ >
160
+ Digital
161
+ </button>
162
+ </div>`
163
+ : nothing}
164
+ ${effective === "digital" && this.activeVideoStreamId === null
165
+ ? html`<span class="mode-hint">Start stream to enable DPTZ</span>`
166
+ : nothing}
167
+ </div>`
168
+ : nothing}
169
+ ${features.mPresets && presets.length > 0
170
+ ? html`<div class="group presets">
171
+ ${presets.map(
172
+ p => html`<button
173
+ class="chip"
174
+ title="p=${p.settings.pan ?? 0}° · t=${p.settings.tilt ?? 0}° · z=${p.settings.zoom ??
175
+ 1}×"
176
+ @click=${handleAsync(() => this._handleGoPreset(p.presetId))}
177
+ >
178
+ ${p.name}
179
+ </button>`,
180
+ )}
181
+ </div>`
182
+ : nothing}
183
+ <div class="group readout">
184
+ ${features.mPan && pos.pan !== null ? html`<span>Pan ${this._fmt(pos.pan)}°</span>` : nothing}
185
+ ${features.mTilt && pos.tilt !== null ? html`<span>Tilt ${this._fmt(pos.tilt)}°</span>` : nothing}
186
+ ${features.mZoom && pos.zoom !== null ? html`<span>Zoom ${pos.zoom}×</span>` : nothing}
187
+ </div>
188
+ ${this._toast ? html`<div class="toast">${this._toast}</div>` : nothing}
189
+ </div>
190
+ `;
191
+ }
192
+
193
+ private _fmt(v: number): string {
194
+ return v >= 0 ? `+${v}` : `${v}`;
195
+ }
196
+
197
+ private _step(e: MouseEvent, base: number): number {
198
+ return e.shiftKey ? Math.sign(base) : base;
199
+ }
200
+
201
+ private _isDisabled(
202
+ mode: MoveMode,
203
+ axis: "pan" | "tilt" | "zoom",
204
+ f: ReturnType<typeof readFeatures>,
205
+ movement: ReturnType<typeof readMovementState>,
206
+ ): boolean {
207
+ if (mode === "mech") {
208
+ if (movement === "moving") return true;
209
+ if (axis === "pan") return !f.mPan;
210
+ if (axis === "tilt") return !f.mTilt;
211
+ return !f.mZoom;
212
+ }
213
+ if (!f.dptz) return true;
214
+ if (this.activeVideoStreamId === null) return true;
215
+ return false;
216
+ }
217
+
218
+ private async _move(mode: MoveMode, axis: "pan" | "tilt" | "zoom", step: number) {
219
+ if (!this.client) return;
220
+ try {
221
+ if (mode === "mech") {
222
+ const delta: { panDelta?: number; tiltDelta?: number; zoomDelta?: number } = {};
223
+ if (axis === "pan") delta.panDelta = step;
224
+ else if (axis === "tilt") delta.tiltDelta = step;
225
+ else delta.zoomDelta = step;
226
+ await relativeMove(this.client, this.nodeId, this.endpointId, delta);
227
+ } else {
228
+ if (this.activeVideoStreamId === null) return;
229
+ // step magnitude (10 or 1) scales the sensor-relative pixel delta so Shift gives 1% nudges.
230
+ const scale = step / 10;
231
+ const dx = this.sensorSize ? Math.round(this.sensorSize.width * 0.1 * scale) : Math.round(100 * scale);
232
+ const dy = this.sensorSize ? Math.round(this.sensorSize.height * 0.1 * scale) : Math.round(100 * scale);
233
+ const delta: { deltaX?: number; deltaY?: number; zoomDelta?: number } = {};
234
+ if (axis === "pan") delta.deltaX = dx;
235
+ else if (axis === "tilt") delta.deltaY = dy;
236
+ else delta.zoomDelta = step;
237
+ await dptzRelativeMove(this.client, this.nodeId, this.endpointId, this.activeVideoStreamId, delta);
238
+ }
239
+ } catch (err) {
240
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
241
+ if (msg.includes("busy")) {
242
+ this._toast = "Camera busy";
243
+ } else {
244
+ console.warn("PTZ move failed:", err);
245
+ this._toast = "Move failed";
246
+ }
247
+ this._scheduleToastClear();
248
+ }
249
+ }
250
+
251
+ private async _handleGoPreset(presetId: number) {
252
+ if (!this.client) return;
253
+ try {
254
+ await moveToPreset(this.client, this.nodeId, this.endpointId, presetId);
255
+ } catch (err) {
256
+ const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
257
+ if (msg.includes("busy")) {
258
+ this._toast = "Camera busy";
259
+ } else if (msg.includes("not_found")) {
260
+ this._toast = "Preset removed";
261
+ } else {
262
+ console.warn("Preset move failed:", err);
263
+ this._toast = "Move failed";
264
+ }
265
+ this._scheduleToastClear();
266
+ }
267
+ }
268
+
269
+ private _scheduleToastClear() {
270
+ if (this._toastTimer) clearTimeout(this._toastTimer);
271
+ this._toastTimer = setTimeout(() => {
272
+ this._toast = null;
273
+ }, 2000);
274
+ }
275
+
276
+ static override styles = css`
277
+ :host {
278
+ display: block;
279
+ color: white;
280
+ }
281
+ .strip {
282
+ background: rgba(0, 0, 0, 0.6);
283
+ padding: 8px 12px;
284
+ display: flex;
285
+ gap: 16px;
286
+ align-items: center;
287
+ flex-wrap: wrap;
288
+ }
289
+ .group {
290
+ display: flex;
291
+ align-items: center;
292
+ gap: 8px;
293
+ padding-right: 16px;
294
+ border-right: 1px solid rgba(255, 255, 255, 0.15);
295
+ }
296
+ .group:last-child {
297
+ border-right: none;
298
+ }
299
+ .movement {
300
+ gap: 12px;
301
+ }
302
+ .dpad {
303
+ display: grid;
304
+ grid-template-columns: 32px 32px 32px;
305
+ grid-template-rows: 32px 32px 32px;
306
+ gap: 2px;
307
+ }
308
+ .zoom {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 2px;
312
+ }
313
+ md-icon-button {
314
+ --md-icon-button-icon-color: white;
315
+ --md-icon-button-state-layer-color: white;
316
+ width: 32px;
317
+ height: 32px;
318
+ }
319
+ md-icon-button[disabled] {
320
+ opacity: 0.55;
321
+ }
322
+ .mode-toggle {
323
+ display: flex;
324
+ border: 1px solid rgba(255, 255, 255, 0.3);
325
+ border-radius: 4px;
326
+ overflow: hidden;
327
+ }
328
+ .mode-toggle button {
329
+ background: transparent;
330
+ border: none;
331
+ padding: 4px 10px;
332
+ cursor: pointer;
333
+ color: rgba(255, 255, 255, 0.7);
334
+ font-size: 0.75rem;
335
+ font-family: inherit;
336
+ }
337
+ .mode-toggle button.active {
338
+ background: rgba(255, 255, 255, 0.2);
339
+ color: white;
340
+ }
341
+ .mode-hint {
342
+ color: rgba(255, 255, 255, 0.6);
343
+ font-size: 0.7rem;
344
+ font-style: italic;
345
+ }
346
+ .presets {
347
+ flex-wrap: wrap;
348
+ }
349
+ .chip {
350
+ padding: 4px 12px;
351
+ border-radius: 12px;
352
+ background: rgba(255, 255, 255, 0.15);
353
+ color: white;
354
+ border: none;
355
+ cursor: pointer;
356
+ font-size: 0.8rem;
357
+ font-family: inherit;
358
+ }
359
+ .chip:hover {
360
+ background: rgba(255, 255, 255, 0.3);
361
+ }
362
+ .readout {
363
+ color: rgba(255, 255, 255, 0.85);
364
+ font-family: var(--monospace-font, monospace);
365
+ font-size: 0.8rem;
366
+ gap: 12px;
367
+ }
368
+ .toast {
369
+ background: rgba(0, 0, 0, 0.85);
370
+ color: white;
371
+ padding: 4px 10px;
372
+ border-radius: 4px;
373
+ font-size: 0.8rem;
374
+ margin-left: auto;
375
+ }
376
+ @media (max-width: 900px) {
377
+ .readout {
378
+ display: none;
379
+ }
380
+ }
381
+ `;
382
+ }
383
+
384
+ declare global {
385
+ interface HTMLElementTagNameMap {
386
+ "avsum-ptz-strip": AvsumPtzStrip;
387
+ }
388
+ }
@@ -285,7 +285,7 @@ export class NodeBindingDialog extends LitElement {
285
285
 
286
286
  // Note: fabricIndex is assigned by the server based on the device's fabric table
287
287
  const acl_entry: AccessControlEntryStruct = {
288
- privilege: 5,
288
+ privilege: 3,
289
289
  authMode: 2,
290
290
  subjects: [this.node!.node_id],
291
291
  targets: [targets],
@@ -4,12 +4,11 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { MatterClient, MatterNode } from "@matter-server/ws-client";
7
+ import { MatterNode } from "@matter-server/ws-client";
8
8
 
9
- export const showNodeBindingDialog = async (client: MatterClient, node: MatterNode, endpoint: number) => {
9
+ export const showNodeBindingDialog = async (node: MatterNode, endpoint: number) => {
10
10
  await import("./node-binding-dialog.js");
11
11
  const dialog = document.createElement("node-binding-dialog");
12
- dialog.client = client;
13
12
  dialog.node = node;
14
13
  dialog.endpoint = endpoint;
15
14
  document.querySelector("matter-dashboard-app")?.renderRoot.appendChild(dialog);
@@ -6,17 +6,23 @@
6
6
 
7
7
  import "@material/web/button/text-button";
8
8
  import "@material/web/dialog/dialog";
9
- import type { MdDialog } from "@material/web/dialog/dialog.js";
9
+ import { consume } from "@lit/context";
10
10
  import "@material/web/list/list";
11
11
  import "@material/web/list/list-item";
12
+ import type { MdDialog } from "@material/web/dialog/dialog.js";
12
13
  import { MatterClient, MatterNode } from "@matter-server/ws-client";
13
14
  import { html, LitElement } from "lit";
14
- import { customElement, property, state } from "lit/decorators.js";
15
+ import { customElement, state } from "lit/decorators.js";
16
+ import { clientContext, tickContext } from "../../../client/client-context.js";
15
17
  import { preventDefault } from "../../../util/prevent_default.js";
16
18
 
17
19
  @customElement("commission-node-dialog")
18
20
  export class ComissionNodeDialog extends LitElement {
19
- @property({ attribute: false }) public client!: MatterClient;
21
+ @consume({ context: clientContext })
22
+ public client!: MatterClient;
23
+
24
+ @consume({ context: tickContext, subscribe: true })
25
+ protected _tick = 0;
20
26
 
21
27
  @state() private _mode?: "wifi" | "thread" | "existing";
22
28
 
@@ -88,7 +94,7 @@ export class ComissionNodeDialog extends LitElement {
88
94
 
89
95
  private _requestSettings() {
90
96
  import("../settings/show-settings-dialog.js").then(({ showSettingsDialog }) => {
91
- showSettingsDialog(this.client, "network-credentials");
97
+ showSettingsDialog("network-credentials");
92
98
  });
93
99
  this._close();
94
100
  }
@@ -4,11 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { MatterClient } from "@matter-server/ws-client";
8
-
9
- export const showCommissionNodeDialog = async (client: MatterClient) => {
7
+ export const showCommissionNodeDialog = async () => {
10
8
  await import("./commission-node-dialog.js");
11
9
  const dialog = document.createElement("commission-node-dialog");
12
- dialog.client = client;
13
10
  document.querySelector("matter-dashboard-app")?.renderRoot.appendChild(dialog);
14
11
  };
@@ -7,17 +7,13 @@
7
7
  import "@material/web/button/text-button";
8
8
  import "@material/web/dialog/dialog";
9
9
  import type { MdDialog } from "@material/web/dialog/dialog.js";
10
- import { MatterClient } from "@matter-server/ws-client";
11
10
  import { css, html, LitElement } from "lit";
12
- import { customElement, property } from "lit/decorators.js";
11
+ import { customElement } from "lit/decorators.js";
13
12
  import { preventDefault } from "../../../util/prevent_default.js";
14
13
  import "./log-level-section.js";
15
14
 
16
15
  @customElement("log-level-dialog")
17
16
  export class LogLevelDialog extends LitElement {
18
- @property({ attribute: false })
19
- public client!: MatterClient;
20
-
21
17
  private _close() {
22
18
  this.shadowRoot!.querySelector<MdDialog>("md-dialog")!.close();
23
19
  }
@@ -35,10 +31,7 @@ export class LogLevelDialog extends LitElement {
35
31
  <md-dialog open @cancel=${preventDefault} @closed=${this._handleClosed}>
36
32
  <div slot="headline">Server Log Settings</div>
37
33
  <div slot="content">
38
- <log-level-section
39
- .client=${this.client}
40
- @log-level-applied=${this._handleApplied}
41
- ></log-level-section>
34
+ <log-level-section @log-level-applied=${this._handleApplied}></log-level-section>
42
35
  </div>
43
36
  <div slot="actions">
44
37
  <md-text-button @click=${this._close}>Close</md-text-button>
@@ -6,11 +6,13 @@
6
6
 
7
7
  import "@material/web/button/text-button";
8
8
  import "@material/web/select/outlined-select";
9
- import type { MdOutlinedSelect } from "@material/web/select/outlined-select.js";
9
+ import { consume } from "@lit/context";
10
10
  import "@material/web/select/select-option";
11
+ import type { MdOutlinedSelect } from "@material/web/select/outlined-select.js";
11
12
  import { LogLevelString, MatterClient } from "@matter-server/ws-client";
12
13
  import { css, html, LitElement, nothing } from "lit";
13
- import { customElement, property, query, state } from "lit/decorators.js";
14
+ import { customElement, query, state } from "lit/decorators.js";
15
+ import { clientContext, tickContext } from "../../../client/client-context.js";
14
16
  import { fireAndForget, handleAsync } from "../../../util/async-handler.js";
15
17
  import { showAlertDialog } from "../../dialog-box/show-dialog-box.js";
16
18
 
@@ -28,8 +30,11 @@ const LOG_LEVELS: { value: LogLevelString; label: string }[] = [
28
30
  */
29
31
  @customElement("log-level-section")
30
32
  export class LogLevelSection extends LitElement {
31
- @property({ attribute: false })
32
- public client!: MatterClient;
33
+ @consume({ context: clientContext })
34
+ public client?: MatterClient;
35
+
36
+ @consume({ context: tickContext, subscribe: true })
37
+ protected _tick = 0;
33
38
 
34
39
  @state() private _consoleLevel: LogLevelString = "info";
35
40
  @state() private _fileLevel: LogLevelString | null = null;
@@ -48,6 +53,7 @@ export class LogLevelSection extends LitElement {
48
53
  }
49
54
 
50
55
  private async _loadLogLevels() {
56
+ if (!this.client) return;
51
57
  try {
52
58
  const result = await this.client.getLogLevel();
53
59
  this._consoleLevel = result.console_loglevel;
@@ -60,6 +66,7 @@ export class LogLevelSection extends LitElement {
60
66
  }
61
67
 
62
68
  private async _apply() {
69
+ if (!this.client) return;
63
70
  this._applying = true;
64
71
  try {
65
72
  const consoleLevel = this._consoleSelect.value as LogLevelString;
@@ -7,10 +7,11 @@
7
7
  import "@material/web/button/filled-button";
8
8
  import "@material/web/button/text-button";
9
9
  import "@material/web/dialog/dialog";
10
- import type { MdDialog } from "@material/web/dialog/dialog.js";
11
10
  import "@material/web/divider/divider";
12
11
  import "@material/web/iconbutton/icon-button";
13
12
  import "@material/web/switch/switch";
13
+ import { consume } from "@lit/context";
14
+ import type { MdDialog } from "@material/web/dialog/dialog.js";
14
15
  import type { MdSwitch } from "@material/web/switch/switch.js";
15
16
  import "@material/web/textfield/outlined-text-field";
16
17
  import type { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field.js";
@@ -18,6 +19,7 @@ import { MatterClient } from "@matter-server/ws-client";
18
19
  import { mdiAccessPoint, mdiEye, mdiEyeOff, mdiWifi } from "@mdi/js";
19
20
  import { css, html, LitElement, nothing } from "lit";
20
21
  import { customElement, property, query, state } from "lit/decorators.js";
22
+ import { clientContext, tickContext } from "../../../client/client-context.js";
21
23
  import { handleAsync } from "../../../util/async-handler.js";
22
24
  import { DevModeService } from "../../../util/dev-mode-service.js";
23
25
  import { preventDefault } from "../../../util/prevent_default.js";
@@ -27,9 +29,12 @@ import "./log-level-section.js";
27
29
 
28
30
  @customElement("settings-dialog")
29
31
  export class SettingsDialog extends LitElement {
30
- @property({ attribute: false })
32
+ @consume({ context: clientContext })
31
33
  public client!: MatterClient;
32
34
 
35
+ @consume({ context: tickContext, subscribe: true })
36
+ protected _tick = 0;
37
+
33
38
  @state() private _devMode = DevModeService.active;
34
39
 
35
40
  private _unsubscribeDev?: () => void;
@@ -206,7 +211,7 @@ export class SettingsDialog extends LitElement {
206
211
 
207
212
  <section class="section">
208
213
  <h3 class="section-title">Server log levels</h3>
209
- <log-level-section .client=${this.client}></log-level-section>
214
+ <log-level-section></log-level-section>
210
215
  </section>
211
216
 
212
217
  <md-divider></md-divider>
@@ -4,11 +4,8 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { MatterClient } from "@matter-server/ws-client";
8
-
9
- export const showLogLevelDialog = async (client: MatterClient) => {
7
+ export const showLogLevelDialog = async () => {
10
8
  await import("./log-level-dialog.js");
11
9
  const dialog = document.createElement("log-level-dialog");
12
- dialog.client = client;
13
10
  document.querySelector("matter-dashboard-app")?.renderRoot.appendChild(dialog);
14
11
  };
@@ -4,12 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { MatterClient } from "@matter-server/ws-client";
8
-
9
- export const showSettingsDialog = async (client: MatterClient, section?: string) => {
7
+ export const showSettingsDialog = async (section?: string) => {
10
8
  await import("./settings-dialog.js");
11
9
  const dialog = document.createElement("settings-dialog");
12
- dialog.client = client;
13
10
  if (section) dialog.scrollToSection = section;
14
11
  document.querySelector("matter-dashboard-app")?.renderRoot.appendChild(dialog);
15
12
  };