@matter-server/dashboard 0.2.6 → 0.2.7-alpha.0-20260118-993a1c7
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/README.md +16 -0
- package/dist/esm/client/models/descriptions.js +1754 -1754
- package/dist/esm/components/dialogs/binding/node-binding-dialog.d.ts.map +1 -1
- package/dist/esm/components/dialogs/binding/node-binding-dialog.js +8 -4
- package/dist/esm/components/dialogs/binding/node-binding-dialog.js.map +1 -1
- package/dist/esm/entrypoint/main.d.ts +1 -1
- package/dist/esm/entrypoint/main.d.ts.map +1 -1
- package/dist/esm/entrypoint/main.js +1 -0
- package/dist/esm/entrypoint/main.js.map +1 -1
- package/dist/esm/pages/components/header.d.ts +8 -0
- package/dist/esm/pages/components/header.d.ts.map +1 -1
- package/dist/esm/pages/components/header.js +56 -6
- package/dist/esm/pages/components/header.js.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.d.ts +6 -1
- package/dist/esm/pages/matter-dashboard-app.d.ts.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.js +119 -24
- package/dist/esm/pages/matter-dashboard-app.js.map +1 -1
- package/dist/esm/util/theme-service.d.ts +27 -0
- package/dist/esm/util/theme-service.d.ts.map +1 -0
- package/dist/esm/util/theme-service.js +71 -0
- package/dist/esm/util/theme-service.js.map +6 -0
- package/dist/web/index.html +35 -0
- package/dist/web/js/{commission-node-dialog--19-sX9D.js → commission-node-dialog-DGw5qDgH.js} +5 -5
- package/dist/web/js/{commission-node-existing-DY6SnsHb.js → commission-node-existing-CHyyeC8y.js} +4 -4
- package/dist/web/js/{commission-node-thread-CXquVvK5.js → commission-node-thread-iRDSlidy.js} +4 -4
- package/dist/web/js/{commission-node-wifi-VQGVOrr7.js → commission-node-wifi-C4YNR3bG.js} +4 -4
- package/dist/web/js/{dialog-box-qX-alVZJ.js → dialog-box-ag-xOaYh.js} +2 -2
- package/dist/web/js/{fire_event-B13DcOc9.js → fire_event-BeiEbHcE.js} +1 -1
- package/dist/web/js/main.js +140 -8
- package/dist/web/js/{matter-dashboard-app-CU3-L2nl.js → matter-dashboard-app-BxQ4W_uT.js} +13183 -13045
- package/dist/web/js/{node-binding-dialog-D4rr_G9I.js → node-binding-dialog-ClziphM0.js} +11 -7
- package/dist/web/js/{outlined-text-field-CtlEkpbk.js → outlined-text-field-B-CiqgEJ.js} +2 -2
- package/dist/web/js/{prevent_default-Dw7ifAL-.js → prevent_default-Bs2sUnny.js} +1 -1
- package/package.json +3 -3
- package/src/client/models/descriptions.ts +1754 -1754
- package/src/components/dialogs/binding/node-binding-dialog.ts +8 -4
- package/src/entrypoint/main.ts +1 -0
- package/src/pages/components/header.ts +57 -8
- package/src/pages/matter-dashboard-app.ts +123 -26
- package/src/util/theme-service.ts +98 -0
|
@@ -306,7 +306,7 @@ export class NodeBindingDialog extends LitElement {
|
|
|
306
306
|
<md-list style="padding-bottom:18px;">
|
|
307
307
|
${Object.values(bindings).map(
|
|
308
308
|
(entry, index) => html`
|
|
309
|
-
<md-list-item
|
|
309
|
+
<md-list-item class="binding-item">
|
|
310
310
|
<div style="display:flex;gap:10px;">
|
|
311
311
|
<div>node:${entry["node"]}</div>
|
|
312
312
|
<div>endpoint:${entry["endpoint"]}</div>
|
|
@@ -376,9 +376,13 @@ export class NodeBindingDialog extends LitElement {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
static override styles = css`
|
|
379
|
+
.binding-item {
|
|
380
|
+
background: var(--md-sys-color-surface-container-high);
|
|
381
|
+
}
|
|
382
|
+
|
|
379
383
|
.inline-group {
|
|
380
384
|
display: flex;
|
|
381
|
-
border: 2px solid
|
|
385
|
+
border: 2px solid var(--md-sys-color-primary);
|
|
382
386
|
padding: 1px;
|
|
383
387
|
border-radius: 8px;
|
|
384
388
|
position: relative;
|
|
@@ -404,8 +408,8 @@ export class NodeBindingDialog extends LitElement {
|
|
|
404
408
|
position: absolute;
|
|
405
409
|
left: 15px;
|
|
406
410
|
top: -12px;
|
|
407
|
-
background:
|
|
408
|
-
color:
|
|
411
|
+
background: var(--md-sys-color-primary);
|
|
412
|
+
color: var(--md-sys-color-on-primary);
|
|
409
413
|
padding: 3px 15px;
|
|
410
414
|
border-radius: 4px;
|
|
411
415
|
}
|
package/src/entrypoint/main.ts
CHANGED
|
@@ -10,10 +10,11 @@ import "@material/web/iconbutton/icon-button";
|
|
|
10
10
|
import "@material/web/list/list";
|
|
11
11
|
import "@material/web/list/list-item";
|
|
12
12
|
import { MatterClient } from "@matter-server/ws-client";
|
|
13
|
-
import { mdiArrowLeft, mdiLogout } from "@mdi/js";
|
|
13
|
+
import { mdiArrowLeft, mdiBrightnessAuto, mdiLogout, mdiWeatherNight, mdiWeatherSunny } from "@mdi/js";
|
|
14
14
|
import { LitElement, css, html, nothing } from "lit";
|
|
15
|
-
import { customElement, property } from "lit/decorators.js";
|
|
15
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
16
16
|
import "../../components/ha-svg-icon";
|
|
17
|
+
import { EffectiveTheme, ThemePreference, ThemeService } from "../../util/theme-service.js";
|
|
17
18
|
|
|
18
19
|
interface HeaderAction {
|
|
19
20
|
label: string;
|
|
@@ -28,6 +29,50 @@ export class DashboardHeader extends LitElement {
|
|
|
28
29
|
|
|
29
30
|
public client?: MatterClient;
|
|
30
31
|
|
|
32
|
+
@state() private _themePreference: ThemePreference = ThemeService.preference;
|
|
33
|
+
@state() private _effectiveTheme: EffectiveTheme = ThemeService.effectiveTheme;
|
|
34
|
+
|
|
35
|
+
private _unsubscribeTheme?: () => void;
|
|
36
|
+
|
|
37
|
+
override connectedCallback() {
|
|
38
|
+
super.connectedCallback();
|
|
39
|
+
this._unsubscribeTheme = ThemeService.subscribe(theme => {
|
|
40
|
+
this._effectiveTheme = theme;
|
|
41
|
+
this._themePreference = ThemeService.preference;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override disconnectedCallback() {
|
|
46
|
+
super.disconnectedCallback();
|
|
47
|
+
this._unsubscribeTheme?.();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private _cycleTheme() {
|
|
51
|
+
ThemeService.cycleTheme();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private _getThemeIcon(): string {
|
|
55
|
+
switch (this._themePreference) {
|
|
56
|
+
case "light":
|
|
57
|
+
return mdiWeatherSunny;
|
|
58
|
+
case "dark":
|
|
59
|
+
return mdiWeatherNight;
|
|
60
|
+
case "system":
|
|
61
|
+
return mdiBrightnessAuto;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private _getThemeTooltip(): string {
|
|
66
|
+
switch (this._themePreference) {
|
|
67
|
+
case "light":
|
|
68
|
+
return "Theme: Light";
|
|
69
|
+
case "dark":
|
|
70
|
+
return "Theme: Dark";
|
|
71
|
+
case "system":
|
|
72
|
+
return `Theme: System (${this._effectiveTheme})`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
31
76
|
protected override render() {
|
|
32
77
|
return html`
|
|
33
78
|
<div class="header">
|
|
@@ -50,14 +95,18 @@ export class DashboardHeader extends LitElement {
|
|
|
50
95
|
</md-icon-button>
|
|
51
96
|
`;
|
|
52
97
|
})}
|
|
53
|
-
<!--
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
98
|
+
<!-- theme toggle button -->
|
|
99
|
+
<md-icon-button @click=${this._cycleTheme} .title=${this._getThemeTooltip()}>
|
|
100
|
+
<ha-svg-icon .path=${this._getThemeIcon()}></ha-svg-icon>
|
|
101
|
+
</md-icon-button>
|
|
102
|
+
<!-- optional logout button (only when client exists and not in production) -->
|
|
103
|
+
${this.client && !this.client.isProduction
|
|
104
|
+
? html`
|
|
105
|
+
<md-icon-button @click=${this.client.disconnect}>
|
|
58
106
|
<ha-svg-icon .path=${mdiLogout}></ha-svg-icon>
|
|
59
107
|
</md-icon-button>
|
|
60
|
-
`
|
|
108
|
+
`
|
|
109
|
+
: nothing}
|
|
61
110
|
</div>
|
|
62
111
|
</div>
|
|
63
112
|
`;
|
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
import { ContextProvider } from "@lit/context";
|
|
8
8
|
import { MatterClient, MatterError } from "@matter-server/ws-client";
|
|
9
|
-
import {
|
|
9
|
+
import { mdiRefresh } from "@mdi/js";
|
|
10
|
+
import { LitElement, PropertyValueMap, css, html } from "lit";
|
|
10
11
|
import { customElement, state } from "lit/decorators.js";
|
|
11
12
|
import { clientContext } from "../client/client-context.js";
|
|
13
|
+
import "../components/ha-svg-icon";
|
|
12
14
|
import { clone } from "../util/clone_class.js";
|
|
13
15
|
import type { Route } from "../util/routing.js";
|
|
16
|
+
import "./components/header";
|
|
14
17
|
import "./matter-cluster-view";
|
|
15
18
|
import "./matter-endpoint-view";
|
|
16
19
|
import "./matter-node-view";
|
|
@@ -34,31 +37,11 @@ class MatterDashboardApp extends LitElement {
|
|
|
34
37
|
@state()
|
|
35
38
|
private _state: "connecting" | "connected" | "error" | "disconnected" = "connecting";
|
|
36
39
|
|
|
37
|
-
private _error: string | undefined;
|
|
38
|
-
|
|
39
40
|
private provider = new ContextProvider(this, { context: clientContext, initialValue: this.client });
|
|
40
41
|
|
|
41
42
|
protected override firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
|
42
43
|
super.firstUpdated(_changedProperties);
|
|
43
|
-
this.
|
|
44
|
-
() => {
|
|
45
|
-
this._state = "connected";
|
|
46
|
-
this.client.addEventListener("nodes_changed", () => {
|
|
47
|
-
this.requestUpdate();
|
|
48
|
-
this.provider.setValue(clone(this.client));
|
|
49
|
-
});
|
|
50
|
-
this.client.addEventListener("server_info_updated", () => {
|
|
51
|
-
this.provider.setValue(clone(this.client));
|
|
52
|
-
});
|
|
53
|
-
this.client.addEventListener("connection_lost", () => {
|
|
54
|
-
this._state = "disconnected";
|
|
55
|
-
});
|
|
56
|
-
},
|
|
57
|
-
(err: MatterError) => {
|
|
58
|
-
this._state = "error";
|
|
59
|
-
this._error = err.message;
|
|
60
|
-
},
|
|
61
|
-
);
|
|
44
|
+
this._connect();
|
|
62
45
|
|
|
63
46
|
// Handle history changes
|
|
64
47
|
const updateRoute = () => {
|
|
@@ -72,17 +55,73 @@ class MatterDashboardApp extends LitElement {
|
|
|
72
55
|
updateRoute();
|
|
73
56
|
}
|
|
74
57
|
|
|
58
|
+
private _connect() {
|
|
59
|
+
this.client.startListening().then(
|
|
60
|
+
() => {
|
|
61
|
+
this._state = "connected";
|
|
62
|
+
this._setupEventListeners();
|
|
63
|
+
},
|
|
64
|
+
(_err: MatterError) => {
|
|
65
|
+
this._state = "error";
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private _setupEventListeners() {
|
|
71
|
+
this.client.addEventListener("nodes_changed", () => {
|
|
72
|
+
this.requestUpdate();
|
|
73
|
+
this.provider.setValue(clone(this.client));
|
|
74
|
+
});
|
|
75
|
+
this.client.addEventListener("server_info_updated", () => {
|
|
76
|
+
this.provider.setValue(clone(this.client));
|
|
77
|
+
});
|
|
78
|
+
this.client.addEventListener("connection_lost", () => {
|
|
79
|
+
this._state = "disconnected";
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private _reconnect = () => {
|
|
84
|
+
this._state = "connecting";
|
|
85
|
+
this._connect();
|
|
86
|
+
};
|
|
87
|
+
|
|
75
88
|
override render() {
|
|
76
89
|
if (this._state === "connecting") {
|
|
77
|
-
return html
|
|
90
|
+
return html`
|
|
91
|
+
<dashboard-header title="Matter Server"></dashboard-header>
|
|
92
|
+
<div class="status-page">
|
|
93
|
+
<p class="status-message">Connecting...</p>
|
|
94
|
+
</div>
|
|
95
|
+
`;
|
|
78
96
|
}
|
|
79
97
|
if (this._state === "disconnected") {
|
|
80
|
-
return html
|
|
98
|
+
return html`
|
|
99
|
+
<dashboard-header title="Matter Server"></dashboard-header>
|
|
100
|
+
<div class="status-page">
|
|
101
|
+
<p class="status-message error">Connection lost</p>
|
|
102
|
+
<p class="status-hint">
|
|
103
|
+
The connection to the Matter Server was lost. Please check if the server is running.
|
|
104
|
+
</p>
|
|
105
|
+
<button class="retry-button" @click=${this._reconnect}>
|
|
106
|
+
<ha-svg-icon .path=${mdiRefresh}></ha-svg-icon>
|
|
107
|
+
Reconnect
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
`;
|
|
81
111
|
}
|
|
82
112
|
if (this._state === "error") {
|
|
83
113
|
return html`
|
|
84
|
-
<
|
|
85
|
-
<
|
|
114
|
+
<dashboard-header title="Matter Server"></dashboard-header>
|
|
115
|
+
<div class="status-page">
|
|
116
|
+
<p class="status-message error">No connection</p>
|
|
117
|
+
<p class="status-hint">
|
|
118
|
+
Unable to connect to the Matter Server. Please check if the server is running.
|
|
119
|
+
</p>
|
|
120
|
+
<button class="retry-button" @click=${this._reconnect}>
|
|
121
|
+
<ha-svg-icon .path=${mdiRefresh}></ha-svg-icon>
|
|
122
|
+
Reconnect
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
86
125
|
`;
|
|
87
126
|
}
|
|
88
127
|
if (this._route.prefix === "node" && this._route.path.length == 3) {
|
|
@@ -122,4 +161,62 @@ class MatterDashboardApp extends LitElement {
|
|
|
122
161
|
.route=${this._route}
|
|
123
162
|
></matter-server-view>`;
|
|
124
163
|
}
|
|
164
|
+
|
|
165
|
+
static override styles = css`
|
|
166
|
+
:host {
|
|
167
|
+
display: block;
|
|
168
|
+
min-height: 100vh;
|
|
169
|
+
background-color: var(--md-sys-color-background);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.status-page {
|
|
173
|
+
display: flex;
|
|
174
|
+
flex-direction: column;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
padding: 48px 24px;
|
|
178
|
+
text-align: center;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.status-message {
|
|
182
|
+
font-size: 1.5rem;
|
|
183
|
+
color: var(--md-sys-color-on-background);
|
|
184
|
+
margin: 0 0 16px 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.status-message.error {
|
|
188
|
+
color: var(--danger-color);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.status-hint {
|
|
192
|
+
font-size: 1rem;
|
|
193
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
194
|
+
margin: 0;
|
|
195
|
+
max-width: 400px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.retry-button {
|
|
199
|
+
display: inline-flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: 8px;
|
|
202
|
+
margin-top: 24px;
|
|
203
|
+
padding: 12px 24px;
|
|
204
|
+
font-size: 1rem;
|
|
205
|
+
background-color: var(--md-sys-color-primary);
|
|
206
|
+
color: var(--md-sys-color-on-primary);
|
|
207
|
+
--icon-primary-color: var(--md-sys-color-on-primary);
|
|
208
|
+
border: none;
|
|
209
|
+
border-radius: 4px;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.retry-button:hover {
|
|
214
|
+
opacity: 0.9;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.retry-button ha-svg-icon {
|
|
218
|
+
width: 20px;
|
|
219
|
+
height: 20px;
|
|
220
|
+
}
|
|
221
|
+
`;
|
|
125
222
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Theme service for managing dark/light mode preferences.
|
|
9
|
+
* Supports three modes: light, dark, and system (auto-detect from OS).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type ThemePreference = "light" | "dark" | "system";
|
|
13
|
+
export type EffectiveTheme = "light" | "dark";
|
|
14
|
+
|
|
15
|
+
const STORAGE_KEY = "matterTheme";
|
|
16
|
+
|
|
17
|
+
class ThemeServiceImpl {
|
|
18
|
+
private _preference: ThemePreference = "system";
|
|
19
|
+
private _mediaQuery: MediaQueryList;
|
|
20
|
+
private _listeners: Set<(theme: EffectiveTheme) => void> = new Set();
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
this._mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
24
|
+
this._mediaQuery.addEventListener("change", () => this._applyTheme());
|
|
25
|
+
this._loadPreference();
|
|
26
|
+
this._applyTheme();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get preference(): ThemePreference {
|
|
30
|
+
return this._preference;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get effectiveTheme(): EffectiveTheme {
|
|
34
|
+
if (this._preference === "system") {
|
|
35
|
+
return this._mediaQuery.matches ? "dark" : "light";
|
|
36
|
+
}
|
|
37
|
+
return this._preference;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setPreference(pref: ThemePreference): void {
|
|
41
|
+
this._preference = pref;
|
|
42
|
+
localStorage.setItem(STORAGE_KEY, pref);
|
|
43
|
+
this._applyTheme();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
cycleTheme(): ThemePreference {
|
|
47
|
+
const cycle: ThemePreference[] = ["light", "dark", "system"];
|
|
48
|
+
const currentIndex = cycle.indexOf(this._preference);
|
|
49
|
+
const nextIndex = (currentIndex + 1) % cycle.length;
|
|
50
|
+
this.setPreference(cycle[nextIndex]);
|
|
51
|
+
return this._preference;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
subscribe(callback: (theme: EffectiveTheme) => void): () => void {
|
|
55
|
+
this._listeners.add(callback);
|
|
56
|
+
return () => this._listeners.delete(callback);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _loadPreference(): void {
|
|
60
|
+
// Check for query parameter override (e.g., ?theme=dark)
|
|
61
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
62
|
+
const themeParam = urlParams.get("theme") as ThemePreference | null;
|
|
63
|
+
if (themeParam && ["light", "dark", "system"].includes(themeParam)) {
|
|
64
|
+
// Use query parameter value and save to localStorage
|
|
65
|
+
this._preference = themeParam;
|
|
66
|
+
localStorage.setItem(STORAGE_KEY, themeParam);
|
|
67
|
+
// Remove the query parameter from URL without reload
|
|
68
|
+
urlParams.delete("theme");
|
|
69
|
+
const newUrl = urlParams.toString()
|
|
70
|
+
? `${window.location.pathname}?${urlParams.toString()}${window.location.hash}`
|
|
71
|
+
: `${window.location.pathname}${window.location.hash}`;
|
|
72
|
+
history.replaceState({}, "", newUrl);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Fall back to localStorage
|
|
77
|
+
const stored = localStorage.getItem(STORAGE_KEY) as ThemePreference | null;
|
|
78
|
+
if (stored && ["light", "dark", "system"].includes(stored)) {
|
|
79
|
+
this._preference = stored;
|
|
80
|
+
}
|
|
81
|
+
// Default is "system" if nothing stored
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private _applyTheme(): void {
|
|
85
|
+
const effective = this.effectiveTheme;
|
|
86
|
+
document.documentElement.classList.toggle("dark-theme", effective === "dark");
|
|
87
|
+
|
|
88
|
+
// Update meta theme-color for mobile browsers
|
|
89
|
+
const metaThemeColor = document.querySelector('meta[name="theme-color"]');
|
|
90
|
+
if (metaThemeColor) {
|
|
91
|
+
metaThemeColor.setAttribute("content", effective === "dark" ? "#1e1e1e" : "#03a9f4");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this._listeners.forEach(cb => cb(effective));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const ThemeService = new ThemeServiceImpl();
|