mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
Add filtering and zoom to node to the ZHA network visualization (#7913)
This commit is contained in:
parent
607eb6d130
commit
30997dbc88
@ -15,6 +15,12 @@ import { fetchDevices, ZHADevice } from "../../../../../data/zha";
|
|||||||
import "../../../../../layouts/hass-subpage";
|
import "../../../../../layouts/hass-subpage";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { Network, Edge, Node, EdgeOptions } from "vis-network";
|
import { Network, Edge, Node, EdgeOptions } from "vis-network";
|
||||||
|
import "../../../../../common/search/search-input";
|
||||||
|
import "../../../../../components/ha-button-menu";
|
||||||
|
import "../../../../../components/device/ha-device-picker";
|
||||||
|
import "../../../../../components/ha-svg-icon";
|
||||||
|
import { formatAsPaddedHex } from "./functions";
|
||||||
|
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||||
|
|
||||||
@customElement("zha-network-visualization-page")
|
@customElement("zha-network-visualization-page")
|
||||||
export class ZHANetworkVisualizationPage extends LitElement {
|
export class ZHANetworkVisualizationPage extends LitElement {
|
||||||
@ -28,9 +34,21 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
@internalProperty()
|
@internalProperty()
|
||||||
private _devices: Map<string, ZHADevice> = new Map();
|
private _devices: Map<string, ZHADevice> = new Map();
|
||||||
|
|
||||||
|
@internalProperty()
|
||||||
|
private _devicesByDeviceId: Map<string, ZHADevice> = new Map();
|
||||||
|
|
||||||
|
@internalProperty()
|
||||||
|
private _nodes: Node[] = [];
|
||||||
|
|
||||||
@internalProperty()
|
@internalProperty()
|
||||||
private _network?: Network;
|
private _network?: Network;
|
||||||
|
|
||||||
|
@internalProperty()
|
||||||
|
private _filter?: string;
|
||||||
|
|
||||||
|
@internalProperty()
|
||||||
|
private _zoomedDeviceId?: string;
|
||||||
|
|
||||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
@ -91,6 +109,27 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
"ui.panel.config.zha.visualization.header"
|
"ui.panel.config.zha.visualization.header"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<div class="table-header">
|
||||||
|
<search-input
|
||||||
|
no-label-float
|
||||||
|
no-underline
|
||||||
|
@value-changed=${this._handleSearchChange}
|
||||||
|
.filter=${this._filter}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.zha.visualization.highlight_label"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</search-input>
|
||||||
|
<ha-device-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._zoomedDeviceId}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.zha.visualization.zoom_label"
|
||||||
|
)}
|
||||||
|
.includeDomains="['zha']"
|
||||||
|
@value-changed=${this._zoomToDevice}
|
||||||
|
></ha-device-picker>
|
||||||
|
</div>
|
||||||
<div id="visualization"></div>
|
<div id="visualization"></div>
|
||||||
</hass-subpage>
|
</hass-subpage>
|
||||||
`;
|
`;
|
||||||
@ -101,15 +140,18 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
this._devices = new Map(
|
this._devices = new Map(
|
||||||
devices.map((device: ZHADevice) => [device.ieee, device])
|
devices.map((device: ZHADevice) => [device.ieee, device])
|
||||||
);
|
);
|
||||||
|
this._devicesByDeviceId = new Map(
|
||||||
|
devices.map((device: ZHADevice) => [device.device_reg_id, device])
|
||||||
|
);
|
||||||
this._updateDevices(devices);
|
this._updateDevices(devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateDevices(devices: ZHADevice[]) {
|
private _updateDevices(devices: ZHADevice[]) {
|
||||||
const nodes: Node[] = [];
|
this._nodes = [];
|
||||||
const edges: Edge[] = [];
|
const edges: Edge[] = [];
|
||||||
|
|
||||||
devices.forEach((device) => {
|
devices.forEach((device) => {
|
||||||
nodes.push({
|
this._nodes.push({
|
||||||
id: device.ieee,
|
id: device.ieee,
|
||||||
label: this._buildLabel(device),
|
label: this._buildLabel(device),
|
||||||
shape: this._getShape(device),
|
shape: this._getShape(device),
|
||||||
@ -137,7 +179,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._network?.setData({ nodes: nodes, edges: edges });
|
this._network?.setData({ nodes: this._nodes, edges: edges });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLQI(lqi: number): EdgeOptions["color"] {
|
private _getLQI(lqi: number): EdgeOptions["color"] {
|
||||||
@ -181,7 +223,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
label += `<b>IEEE: </b>${device.ieee}`;
|
label += `<b>IEEE: </b>${device.ieee}`;
|
||||||
label += `\n<b>Device Type: </b>${device.device_type.replace("_", " ")}`;
|
label += `\n<b>Device Type: </b>${device.device_type.replace("_", " ")}`;
|
||||||
if (device.nwk != null) {
|
if (device.nwk != null) {
|
||||||
label += `\n<b>NWK: </b>${device.nwk}`;
|
label += `\n<b>NWK: </b>${formatAsPaddedHex(device.nwk)}`;
|
||||||
}
|
}
|
||||||
if (device.manufacturer != null && device.model != null) {
|
if (device.manufacturer != null && device.model != null) {
|
||||||
label += `\n<b>Device: </b>${device.manufacturer} ${device.model}`;
|
label += `\n<b>Device: </b>${device.manufacturer} ${device.model}`;
|
||||||
@ -194,6 +236,56 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
|
this._filter = ev.detail.value;
|
||||||
|
const filterText = this._filter!.toLowerCase();
|
||||||
|
if (!this._network) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._filter) {
|
||||||
|
const filteredNodeIds: (string | number)[] = [];
|
||||||
|
this._nodes.forEach((node) => {
|
||||||
|
if (node.label && node.label.toLowerCase().includes(filterText)) {
|
||||||
|
filteredNodeIds.push(node.id!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._zoomedDeviceId = "";
|
||||||
|
this._zoomOut();
|
||||||
|
this._network.selectNodes(filteredNodeIds, true);
|
||||||
|
} else {
|
||||||
|
this._network.unselectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _zoomToDevice(event: PolymerChangedEvent<string>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this._zoomedDeviceId = event.detail.value;
|
||||||
|
if (!this._network) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._filter = "";
|
||||||
|
if (!this._zoomedDeviceId) {
|
||||||
|
this._zoomOut();
|
||||||
|
} else {
|
||||||
|
const device: ZHADevice | undefined = this._devicesByDeviceId.get(
|
||||||
|
this._zoomedDeviceId
|
||||||
|
);
|
||||||
|
if (device) {
|
||||||
|
this._network.fit({
|
||||||
|
nodes: [device.ieee],
|
||||||
|
animation: { duration: 500, easingFunction: "easeInQuad" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _zoomOut() {
|
||||||
|
this._network!.fit({
|
||||||
|
nodes: [],
|
||||||
|
animation: { duration: 500, easingFunction: "easeOutQuad" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
@ -208,6 +300,30 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
|||||||
line-height: var(--paper-font-display1_-_line-height);
|
line-height: var(--paper-font-display1_-_line-height);
|
||||||
opacity: var(--dark-primary-opacity);
|
opacity: var(--dark-primary-opacity);
|
||||||
}
|
}
|
||||||
|
.table-header {
|
||||||
|
border-bottom: 1px solid --divider-color;
|
||||||
|
padding: 0 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: var(--header-height);
|
||||||
|
}
|
||||||
|
.search-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
search-input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
search-input.header {
|
||||||
|
left: -8px;
|
||||||
|
}
|
||||||
|
ha-device-picker {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -2271,7 +2271,9 @@
|
|||||||
},
|
},
|
||||||
"visualization": {
|
"visualization": {
|
||||||
"header": "Network Visualization",
|
"header": "Network Visualization",
|
||||||
"caption": "Visualization"
|
"caption": "Visualization",
|
||||||
|
"highlight_label": "Highlight Devices",
|
||||||
|
"zoom_label": "Zoom To Device"
|
||||||
},
|
},
|
||||||
"group_binding": {
|
"group_binding": {
|
||||||
"header": "Group Binding",
|
"header": "Group Binding",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user