@matter-server/dashboard 0.3.2 → 0.3.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 +2 -2
- package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts.map +1 -1
- package/dist/esm/pages/cluster-commands/base-cluster-commands.js.map +1 -1
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts +36 -0
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts.map +1 -0
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js +159 -0
- package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js.map +6 -0
- package/dist/esm/pages/cluster-commands/index.d.ts +1 -0
- package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -1
- package/dist/esm/pages/cluster-commands/index.js +1 -0
- package/dist/esm/pages/cluster-commands/index.js.map +1 -1
- package/dist/esm/pages/components/footer.d.ts.map +1 -1
- package/dist/esm/pages/components/footer.js +4 -7
- package/dist/esm/pages/components/footer.js.map +1 -1
- package/dist/esm/pages/components/header.d.ts +5 -0
- package/dist/esm/pages/components/header.d.ts.map +1 -1
- package/dist/esm/pages/components/header.js +75 -0
- package/dist/esm/pages/components/header.js.map +1 -1
- package/dist/esm/pages/components/node-details.js +1 -1
- package/dist/esm/pages/components/node-details.js.map +1 -1
- package/dist/esm/pages/components/server-details.d.ts.map +1 -1
- package/dist/esm/pages/components/server-details.js +0 -1
- package/dist/esm/pages/components/server-details.js.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.d.ts +12 -0
- package/dist/esm/pages/matter-dashboard-app.d.ts.map +1 -1
- package/dist/esm/pages/matter-dashboard-app.js +84 -4
- package/dist/esm/pages/matter-dashboard-app.js.map +1 -1
- package/dist/esm/pages/matter-network-view.d.ts +52 -0
- package/dist/esm/pages/matter-network-view.d.ts.map +1 -0
- package/dist/esm/pages/matter-network-view.js +309 -0
- package/dist/esm/pages/matter-network-view.js.map +6 -0
- package/dist/esm/pages/matter-node-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-node-view.js +70 -1
- package/dist/esm/pages/matter-node-view.js.map +1 -1
- package/dist/esm/pages/matter-server-view.d.ts +4 -0
- package/dist/esm/pages/matter-server-view.d.ts.map +1 -1
- package/dist/esm/pages/matter-server-view.js +16 -1
- package/dist/esm/pages/matter-server-view.js.map +1 -1
- package/dist/esm/pages/network/base-network-graph.d.ts +74 -0
- package/dist/esm/pages/network/base-network-graph.d.ts.map +1 -0
- package/dist/esm/pages/network/base-network-graph.js +403 -0
- package/dist/esm/pages/network/base-network-graph.js.map +6 -0
- package/dist/esm/pages/network/device-icons.d.ts +52 -0
- package/dist/esm/pages/network/device-icons.d.ts.map +1 -0
- package/dist/esm/pages/network/device-icons.js +197 -0
- package/dist/esm/pages/network/device-icons.js.map +6 -0
- package/dist/esm/pages/network/device-panel.d.ts +31 -0
- package/dist/esm/pages/network/device-panel.d.ts.map +1 -0
- package/dist/esm/pages/network/device-panel.js +183 -0
- package/dist/esm/pages/network/device-panel.js.map +6 -0
- package/dist/esm/pages/network/network-details.d.ts +47 -0
- package/dist/esm/pages/network/network-details.d.ts.map +1 -0
- package/dist/esm/pages/network/network-details.js +686 -0
- package/dist/esm/pages/network/network-details.js.map +6 -0
- package/dist/esm/pages/network/network-types.d.ts +153 -0
- package/dist/esm/pages/network/network-types.d.ts.map +1 -0
- package/dist/esm/pages/network/network-types.js +19 -0
- package/dist/esm/pages/network/network-types.js.map +6 -0
- package/dist/esm/pages/network/network-utils.d.ts +170 -0
- package/dist/esm/pages/network/network-utils.d.ts.map +1 -0
- package/dist/esm/pages/network/network-utils.js +472 -0
- package/dist/esm/pages/network/network-utils.js.map +6 -0
- package/dist/esm/pages/network/thread-graph.d.ts +27 -0
- package/dist/esm/pages/network/thread-graph.d.ts.map +1 -0
- package/dist/esm/pages/network/thread-graph.js +134 -0
- package/dist/esm/pages/network/thread-graph.js.map +6 -0
- package/dist/esm/pages/network/wifi-graph.d.ts +27 -0
- package/dist/esm/pages/network/wifi-graph.d.ts.map +1 -0
- package/dist/esm/pages/network/wifi-graph.js +167 -0
- package/dist/esm/pages/network/wifi-graph.js.map +6 -0
- package/dist/web/js/{commission-node-dialog-CBSDiqRW.js → commission-node-dialog-B1_khzZb.js} +5 -5
- package/dist/web/js/{commission-node-existing-TP6s8Tez.js → commission-node-existing-RpdajrwF.js} +2 -5
- package/dist/web/js/{commission-node-thread-DOB8pu6x.js → commission-node-thread-5f2itkTG.js} +2 -5
- package/dist/web/js/{commission-node-wifi-tzavmk1j.js → commission-node-wifi-DZ_pWqsa.js} +2 -5
- package/dist/web/js/{dialog-box-Dknil_Be.js → dialog-box-DEUxM4B1.js} +2 -2
- package/dist/web/js/{fire_event-DRpOSjJR.js → fire_event-BczBMT8E.js} +1 -1
- package/dist/web/js/{log-level-dialog-TXkma-7Z.js → log-level-dialog-Cr3PfX1X.js} +2 -3
- package/dist/web/js/main.js +1 -1
- package/dist/web/js/matter-dashboard-app-BuCe_Jxf.js +29990 -0
- package/dist/web/js/{node-binding-dialog-D52FCBFP.js → node-binding-dialog-DMiHNDLA.js} +2 -4
- package/dist/web/js/{prevent_default-BPgSQsuY.js → prevent_default-D4FX_PIh.js} +2 -42
- package/package.json +5 -4
- package/src/pages/cluster-commands/base-cluster-commands.ts +2 -2
- package/src/pages/cluster-commands/clusters/basic-information-commands.ts +171 -0
- package/src/pages/cluster-commands/index.ts +1 -0
- package/src/pages/components/footer.ts +4 -7
- package/src/pages/components/header.ts +81 -0
- package/src/pages/components/node-details.ts +2 -2
- package/src/pages/components/server-details.ts +0 -1
- package/src/pages/matter-dashboard-app.ts +105 -5
- package/src/pages/matter-network-view.ts +325 -0
- package/src/pages/matter-node-view.ts +75 -1
- package/src/pages/matter-server-view.ts +17 -1
- package/src/pages/network/base-network-graph.ts +463 -0
- package/src/pages/network/device-icons.ts +283 -0
- package/src/pages/network/device-panel.ts +180 -0
- package/src/pages/network/network-details.ts +750 -0
- package/src/pages/network/network-types.ts +161 -0
- package/src/pages/network/network-utils.ts +752 -0
- package/src/pages/network/thread-graph.ts +164 -0
- package/src/pages/network/wifi-graph.ts +192 -0
- package/dist/web/js/matter-dashboard-app-B7GUghkC.js +0 -17254
- package/dist/web/js/outlined-text-field-D1DyKQY-.js +0 -968
- package/dist/web/js/validator-C735j770.js +0 -1122
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025-2026 Open Home Foundation
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MatterNode } from "@matter-server/ws-client";
|
|
8
|
+
import { LitElement, css } from "lit";
|
|
9
|
+
import { property, state } from "lit/decorators.js";
|
|
10
|
+
// @ts-expect-error vis-network doesn't have proper type declarations for standalone export
|
|
11
|
+
import { DataSet, Network } from "vis-network/standalone";
|
|
12
|
+
import { ThemeService } from "../../util/theme-service.js";
|
|
13
|
+
import type { NetworkGraphEdge, NetworkGraphNode } from "./network-types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Base class for network graph components (Thread and WiFi).
|
|
17
|
+
* Provides shared vis.js network initialization, highlighting, and theme support.
|
|
18
|
+
*/
|
|
19
|
+
export abstract class BaseNetworkGraph extends LitElement {
|
|
20
|
+
@property({ type: Object })
|
|
21
|
+
public nodes: Record<string, MatterNode> = {};
|
|
22
|
+
|
|
23
|
+
@state()
|
|
24
|
+
protected _selectedNodeId: number | string | null = null;
|
|
25
|
+
|
|
26
|
+
protected _network?: Network;
|
|
27
|
+
protected _nodesDataSet?: DataSet<NetworkGraphNode>;
|
|
28
|
+
protected _edgesDataSet?: DataSet<NetworkGraphEdge>;
|
|
29
|
+
protected _container?: HTMLDivElement;
|
|
30
|
+
protected _resizeObserver?: ResizeObserver;
|
|
31
|
+
protected _themeUnsubscribe?: () => void;
|
|
32
|
+
protected _updateDebounceTimer?: ReturnType<typeof setTimeout>;
|
|
33
|
+
|
|
34
|
+
/** Store original edge colors for restoration after highlighting */
|
|
35
|
+
private _originalEdgeColors: Map<string, { color: string; highlight: string }> = new Map();
|
|
36
|
+
|
|
37
|
+
protected _getFontColor(): string {
|
|
38
|
+
return ThemeService.effectiveTheme === "dark" ? "#e0e0e0" : "#333333";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected _getDimmedEdgeColor(): string {
|
|
42
|
+
return ThemeService.effectiveTheme === "dark" ? "#555555" : "#cccccc";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns physics options for the network. Override in subclasses for different behavior.
|
|
47
|
+
*/
|
|
48
|
+
protected _getPhysicsOptions(): any {
|
|
49
|
+
return {
|
|
50
|
+
enabled: true,
|
|
51
|
+
solver: "forceAtlas2Based",
|
|
52
|
+
forceAtlas2Based: {
|
|
53
|
+
gravitationalConstant: -70,
|
|
54
|
+
centralGravity: 0.005,
|
|
55
|
+
springLength: 130,
|
|
56
|
+
springConstant: 0.08,
|
|
57
|
+
damping: 0.4,
|
|
58
|
+
avoidOverlap: 0.6,
|
|
59
|
+
},
|
|
60
|
+
stabilization: {
|
|
61
|
+
enabled: true,
|
|
62
|
+
iterations: 250,
|
|
63
|
+
updateInterval: 25,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override updated(changedProperties: Map<string, unknown>): void {
|
|
69
|
+
super.updated(changedProperties);
|
|
70
|
+
|
|
71
|
+
// If container wasn't found in firstUpdated (empty state was rendered),
|
|
72
|
+
// try to find and observe it now that it might have appeared
|
|
73
|
+
if (!this._container && !this._resizeObserver) {
|
|
74
|
+
this._tryAttachContainer();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (changedProperties.has("nodes")) {
|
|
78
|
+
this._debouncedUpdateGraph();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Try to find and attach the graph container if it wasn't available before.
|
|
84
|
+
* This handles the case where empty state was rendered initially.
|
|
85
|
+
*/
|
|
86
|
+
private _tryAttachContainer(): void {
|
|
87
|
+
const container = this.shadowRoot?.querySelector(".graph-container") as HTMLDivElement;
|
|
88
|
+
if (container) {
|
|
89
|
+
this._container = container;
|
|
90
|
+
this._resizeObserver = new ResizeObserver(entries => {
|
|
91
|
+
const entry = entries[0];
|
|
92
|
+
if (entry && entry.contentRect.width > 0 && entry.contentRect.height > 0) {
|
|
93
|
+
if (!this._network) {
|
|
94
|
+
this._initializeNetwork();
|
|
95
|
+
} else {
|
|
96
|
+
this._network.setSize(`${entry.contentRect.width}px`, `${entry.contentRect.height}px`);
|
|
97
|
+
this._network.redraw();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
this._resizeObserver.observe(this._container);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Debounced graph update to avoid excessive redraws */
|
|
106
|
+
protected _debouncedUpdateGraph(): void {
|
|
107
|
+
if (this._updateDebounceTimer) {
|
|
108
|
+
clearTimeout(this._updateDebounceTimer);
|
|
109
|
+
}
|
|
110
|
+
this._updateDebounceTimer = setTimeout(() => {
|
|
111
|
+
this._updateGraph();
|
|
112
|
+
}, 100);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override firstUpdated(): void {
|
|
116
|
+
this._container = this.shadowRoot?.querySelector(".graph-container") as HTMLDivElement;
|
|
117
|
+
if (this._container) {
|
|
118
|
+
// Wait for container to have proper dimensions before initializing
|
|
119
|
+
this._resizeObserver = new ResizeObserver(entries => {
|
|
120
|
+
const entry = entries[0];
|
|
121
|
+
if (entry && entry.contentRect.width > 0 && entry.contentRect.height > 0) {
|
|
122
|
+
if (!this._network) {
|
|
123
|
+
this._initializeNetwork();
|
|
124
|
+
} else {
|
|
125
|
+
// Resize existing network
|
|
126
|
+
this._network.setSize(`${entry.contentRect.width}px`, `${entry.contentRect.height}px`);
|
|
127
|
+
this._network.redraw();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
this._resizeObserver.observe(this._container);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Subscribe to theme changes - refresh entire graph to update colors
|
|
135
|
+
this._themeUnsubscribe = ThemeService.subscribe(() => {
|
|
136
|
+
if (this._network) {
|
|
137
|
+
// Update font colors
|
|
138
|
+
this._network.setOptions({
|
|
139
|
+
nodes: {
|
|
140
|
+
font: {
|
|
141
|
+
color: this._getFontColor(),
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
// Regenerate node icons and edges with new theme colors
|
|
146
|
+
this._updateGraph();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
override disconnectedCallback(): void {
|
|
152
|
+
super.disconnectedCallback();
|
|
153
|
+
this._resizeObserver?.disconnect();
|
|
154
|
+
this._themeUnsubscribe?.();
|
|
155
|
+
if (this._updateDebounceTimer) {
|
|
156
|
+
clearTimeout(this._updateDebounceTimer);
|
|
157
|
+
}
|
|
158
|
+
this._nodesDataSet?.clear();
|
|
159
|
+
this._edgesDataSet?.clear();
|
|
160
|
+
this._network?.destroy();
|
|
161
|
+
this._network = undefined;
|
|
162
|
+
this._nodesDataSet = undefined;
|
|
163
|
+
this._edgesDataSet = undefined;
|
|
164
|
+
this._originalEdgeColors.clear();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected _initializeNetwork(): void {
|
|
168
|
+
if (!this._container) return;
|
|
169
|
+
|
|
170
|
+
// Get actual dimensions
|
|
171
|
+
const rect = this._container.getBoundingClientRect();
|
|
172
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
173
|
+
return; // Wait for proper dimensions
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this._nodesDataSet = new DataSet<NetworkGraphNode>();
|
|
177
|
+
this._edgesDataSet = new DataSet<NetworkGraphEdge>();
|
|
178
|
+
|
|
179
|
+
const options = {
|
|
180
|
+
width: `${rect.width}px`,
|
|
181
|
+
height: `${rect.height}px`,
|
|
182
|
+
autoResize: false, // We handle resize manually
|
|
183
|
+
nodes: {
|
|
184
|
+
shape: "image",
|
|
185
|
+
size: 30,
|
|
186
|
+
font: {
|
|
187
|
+
size: 12,
|
|
188
|
+
color: this._getFontColor(),
|
|
189
|
+
},
|
|
190
|
+
borderWidth: 2,
|
|
191
|
+
borderWidthSelected: 3,
|
|
192
|
+
},
|
|
193
|
+
edges: {
|
|
194
|
+
width: 2,
|
|
195
|
+
smooth: {
|
|
196
|
+
type: "continuous",
|
|
197
|
+
roundness: 0.5,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
physics: this._getPhysicsOptions(),
|
|
201
|
+
interaction: {
|
|
202
|
+
hover: true,
|
|
203
|
+
tooltipDelay: 200,
|
|
204
|
+
hideEdgesOnDrag: true,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this._network = new Network(
|
|
209
|
+
this._container,
|
|
210
|
+
{
|
|
211
|
+
nodes: this._nodesDataSet,
|
|
212
|
+
edges: this._edgesDataSet,
|
|
213
|
+
},
|
|
214
|
+
options,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Handle node selection
|
|
218
|
+
this._network.on("selectNode", (params: { nodes: (number | string)[] }) => {
|
|
219
|
+
if (params.nodes.length > 0) {
|
|
220
|
+
this._selectedNodeId = params.nodes[0];
|
|
221
|
+
this._highlightConnections(this._selectedNodeId);
|
|
222
|
+
this._dispatchNodeSelected(this._selectedNodeId);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
this._network.on("deselectNode", () => {
|
|
227
|
+
this._selectedNodeId = null;
|
|
228
|
+
this._clearHighlights();
|
|
229
|
+
this._dispatchNodeSelected(null);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Auto-fit after stabilization completes
|
|
233
|
+
this._network.on("stabilizationIterationsDone", () => {
|
|
234
|
+
// Fit with padding to keep nodes away from edges
|
|
235
|
+
this._network?.fit({
|
|
236
|
+
animation: {
|
|
237
|
+
duration: 500,
|
|
238
|
+
easingFunction: "easeInOutQuad",
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Also fit when physics fully stops (catches any drift after initial stabilization)
|
|
244
|
+
this._network.on("stabilized", () => {
|
|
245
|
+
this._network?.fit({
|
|
246
|
+
animation: {
|
|
247
|
+
duration: 300,
|
|
248
|
+
easingFunction: "easeInOutQuad",
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
this._updateGraph();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Returns true if the graph is initialized and ready for interaction.
|
|
258
|
+
*/
|
|
259
|
+
public isReady(): boolean {
|
|
260
|
+
return this._network !== undefined && this._nodesDataSet !== undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Fits all nodes into view.
|
|
265
|
+
*/
|
|
266
|
+
public fit(): void {
|
|
267
|
+
this._network?.fit({
|
|
268
|
+
animation: {
|
|
269
|
+
duration: 300,
|
|
270
|
+
easingFunction: "easeInOutQuad",
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Selects a node by ID and focuses on it.
|
|
277
|
+
*/
|
|
278
|
+
public selectNode(nodeId: number | string): void {
|
|
279
|
+
if (!this._network) return;
|
|
280
|
+
|
|
281
|
+
this._selectedNodeId = nodeId;
|
|
282
|
+
this._network.selectNodes([nodeId]);
|
|
283
|
+
this._highlightConnections(nodeId);
|
|
284
|
+
this._dispatchNodeSelected(nodeId);
|
|
285
|
+
|
|
286
|
+
// Focus on the selected node
|
|
287
|
+
this._network.focus(nodeId, {
|
|
288
|
+
scale: 1.2,
|
|
289
|
+
animation: {
|
|
290
|
+
duration: 500,
|
|
291
|
+
easingFunction: "easeInOutQuad",
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
protected _dispatchNodeSelected(nodeId: number | string | null): void {
|
|
297
|
+
this.dispatchEvent(
|
|
298
|
+
new CustomEvent("node-selected", {
|
|
299
|
+
detail: { nodeId },
|
|
300
|
+
bubbles: true,
|
|
301
|
+
composed: true,
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Highlights edges connected to the selected node and makes neighbor nodes more prominent.
|
|
308
|
+
*/
|
|
309
|
+
protected _highlightConnections(nodeId: number | string): void {
|
|
310
|
+
if (!this._edgesDataSet || !this._nodesDataSet) return;
|
|
311
|
+
|
|
312
|
+
// Store original colors before highlighting
|
|
313
|
+
const allEdges = this._edgesDataSet.get();
|
|
314
|
+
for (const edge of allEdges) {
|
|
315
|
+
const edgeId = String(edge.id);
|
|
316
|
+
if (!this._originalEdgeColors.has(edgeId)) {
|
|
317
|
+
const colorObj = edge.color as { color: string; highlight: string };
|
|
318
|
+
this._originalEdgeColors.set(edgeId, {
|
|
319
|
+
color: colorObj?.color ?? "#999999",
|
|
320
|
+
highlight: colorObj?.highlight ?? "#999999",
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Find all edges connected to this node
|
|
326
|
+
const connectedEdges = this._edgesDataSet.get({
|
|
327
|
+
filter: (edge: NetworkGraphEdge) => edge.from === nodeId || edge.to === nodeId,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Get neighbor node IDs
|
|
331
|
+
const neighborIds = new Set<number | string>();
|
|
332
|
+
for (const edge of connectedEdges) {
|
|
333
|
+
if (edge.from === nodeId) {
|
|
334
|
+
neighborIds.add(edge.to);
|
|
335
|
+
} else {
|
|
336
|
+
neighborIds.add(edge.from);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Update edges - make connected ones thicker
|
|
341
|
+
const dimmedColor = this._getDimmedEdgeColor();
|
|
342
|
+
const edgeUpdates = allEdges.map((edge: NetworkGraphEdge) => {
|
|
343
|
+
const isConnected = edge.from === nodeId || edge.to === nodeId;
|
|
344
|
+
const originalColor = this._originalEdgeColors.get(String(edge.id));
|
|
345
|
+
return {
|
|
346
|
+
id: edge.id,
|
|
347
|
+
width: isConnected ? 3 : 1,
|
|
348
|
+
// Dim non-connected edges
|
|
349
|
+
color: isConnected
|
|
350
|
+
? { color: originalColor?.color, highlight: originalColor?.highlight }
|
|
351
|
+
: {
|
|
352
|
+
color: dimmedColor,
|
|
353
|
+
highlight: dimmedColor,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
this._edgesDataSet.update(edgeUpdates);
|
|
358
|
+
|
|
359
|
+
// Update nodes - make neighbors more prominent
|
|
360
|
+
const allNodes = this._nodesDataSet.get();
|
|
361
|
+
const nodeUpdates = allNodes.map((node: NetworkGraphNode) => {
|
|
362
|
+
const isNeighbor = neighborIds.has(node.id);
|
|
363
|
+
const isSelected = node.id === nodeId;
|
|
364
|
+
return {
|
|
365
|
+
id: node.id,
|
|
366
|
+
size: isSelected ? 40 : isNeighbor ? 35 : 25,
|
|
367
|
+
font: {
|
|
368
|
+
size: isSelected ? 14 : isNeighbor ? 13 : 11,
|
|
369
|
+
color: this._getFontColor(),
|
|
370
|
+
bold: isSelected || isNeighbor ? { color: this._getFontColor() } : undefined,
|
|
371
|
+
},
|
|
372
|
+
opacity: isSelected || isNeighbor ? 1 : 0.5,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
this._nodesDataSet.update(nodeUpdates);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Clears all highlights and restores default styling.
|
|
380
|
+
*/
|
|
381
|
+
protected _clearHighlights(): void {
|
|
382
|
+
if (!this._edgesDataSet || !this._nodesDataSet) return;
|
|
383
|
+
|
|
384
|
+
// Restore edge widths and original colors
|
|
385
|
+
const allEdges = this._edgesDataSet.get();
|
|
386
|
+
const edgeUpdates = allEdges.map((edge: NetworkGraphEdge) => {
|
|
387
|
+
const originalColor = this._originalEdgeColors.get(String(edge.id));
|
|
388
|
+
return {
|
|
389
|
+
id: edge.id,
|
|
390
|
+
width: 2,
|
|
391
|
+
color: originalColor ?? { color: "#999999", highlight: "#999999" },
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
this._edgesDataSet.update(edgeUpdates);
|
|
395
|
+
|
|
396
|
+
// Restore node sizes
|
|
397
|
+
const allNodes = this._nodesDataSet.get();
|
|
398
|
+
const nodeUpdates = allNodes.map((node: NetworkGraphNode) => ({
|
|
399
|
+
id: node.id,
|
|
400
|
+
size: 30,
|
|
401
|
+
font: {
|
|
402
|
+
size: 12,
|
|
403
|
+
color: this._getFontColor(),
|
|
404
|
+
bold: undefined,
|
|
405
|
+
},
|
|
406
|
+
opacity: 1,
|
|
407
|
+
}));
|
|
408
|
+
this._nodesDataSet.update(nodeUpdates);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Clear stored edge colors when graph is updated (edges are recreated).
|
|
413
|
+
*/
|
|
414
|
+
protected _clearOriginalEdgeColors(): void {
|
|
415
|
+
this._originalEdgeColors.clear();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Abstract method to be implemented by subclasses for graph-specific updates.
|
|
420
|
+
*/
|
|
421
|
+
protected abstract _updateGraph(): void;
|
|
422
|
+
|
|
423
|
+
static override styles = css`
|
|
424
|
+
:host {
|
|
425
|
+
display: block;
|
|
426
|
+
width: 100%;
|
|
427
|
+
height: 100%;
|
|
428
|
+
min-height: 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.graph-container {
|
|
432
|
+
width: 100%;
|
|
433
|
+
height: 100%;
|
|
434
|
+
background-color: var(--md-sys-color-surface, #fff);
|
|
435
|
+
border-radius: 8px;
|
|
436
|
+
border: 1px solid var(--md-sys-color-outline-variant, #ccc);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.empty-state {
|
|
440
|
+
display: flex;
|
|
441
|
+
flex-direction: column;
|
|
442
|
+
align-items: center;
|
|
443
|
+
justify-content: center;
|
|
444
|
+
height: 100%;
|
|
445
|
+
color: var(--md-sys-color-on-surface-variant, #666);
|
|
446
|
+
text-align: center;
|
|
447
|
+
padding: 24px;
|
|
448
|
+
box-sizing: border-box;
|
|
449
|
+
background-color: var(--md-sys-color-surface, #fff);
|
|
450
|
+
border-radius: 8px;
|
|
451
|
+
border: 1px solid var(--md-sys-color-outline-variant, #ccc);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.empty-state p {
|
|
455
|
+
margin: 8px 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.empty-state .hint {
|
|
459
|
+
font-size: 0.875rem;
|
|
460
|
+
opacity: 0.7;
|
|
461
|
+
}
|
|
462
|
+
`;
|
|
463
|
+
}
|