Skip to content

Commit 74d3616

Browse files
authored
Feat/tags (#255)
This pull request introduces support for editable fields in the `TextInfo` component and adds a tagging system for devices in the network visualization. The most significant changes include updates to the `TextInfo` class to allow inline editing of certain fields, the addition of a `tag` property to devices, and the use of device tags as identifiers in various components. ### Editable Fields in `TextInfo`: * [`src/graphics/basic_components/text_info.ts`](diffhunk://#diff-d8873354465d98b848d2fc3b34957af25885e7e1e77dedbf6fdbcd98e750b765R17-R18): Added `editable` and `onEdit` properties to `InfoField` and updated the `addField` method to support these properties. Modified the `createDetailElement` method to render an input field for editable fields and handle user edits. [[1]](diffhunk://#diff-d8873354465d98b848d2fc3b34957af25885e7e1e77dedbf6fdbcd98e750b765R17-R18) [[2]](diffhunk://#diff-d8873354465d98b848d2fc3b34957af25885e7e1e77dedbf6fdbcd98e750b765L45-R52) [[3]](diffhunk://#diff-d8873354465d98b848d2fc3b34957af25885e7e1e77dedbf6fdbcd98e750b765L66-R85) [[4]](diffhunk://#diff-d8873354465d98b848d2fc3b34957af25885e7e1e77dedbf6fdbcd98e750b765L119-R139) [[5]](diffhunk://#diff-d8873354465d98b848d2fc3b34957af25885e7e1e77dedbf6fdbcd98e750b765L138-R162) * [`src/styles/info.css`](diffhunk://#diff-491a767362663fa382b6668f53918a33594c6cff6f34f616a22ae85b4dfbac53R55-R77): Added styles for the new `.editable-field` class to style editable input fields. ### Tagging System for Devices: * [`src/types/data-devices/dDevice.ts`](diffhunk://#diff-96da61e6f9b86c4b8e4877068e5896132265c26e24436c5fe0ccb670597cf240R15): Introduced a `tag` property to `DataDevice` and added methods to get and set the tag. [[1]](diffhunk://#diff-96da61e6f9b86c4b8e4877068e5896132265c26e24436c5fe0ccb670597cf240R15) [[2]](diffhunk://#diff-96da61e6f9b86c4b8e4877068e5896132265c26e24436c5fe0ccb670597cf240R40) [[3]](diffhunk://#diff-96da61e6f9b86c4b8e4877068e5896132265c26e24436c5fe0ccb670597cf240R58-R67) * [`src/types/view-devices/vDevice.ts`](diffhunk://#diff-2bb090dfab27a6fdd9154c9d52e4bb771254dcb3685cea653537c1266720bd4eR65): Updated `ViewDevice` to include a `tag` property, use it as a label if available, and synchronize it with the data graph. Added methods for managing tags and retrieving device identifiers. [[1]](diffhunk://#diff-2bb090dfab27a6fdd9154c9d52e4bb771254dcb3685cea653537c1266720bd4eR65) [[2]](diffhunk://#diff-2bb090dfab27a6fdd9154c9d52e4bb771254dcb3685cea653537c1266720bd4eR107-R112) [[3]](diffhunk://#diff-2bb090dfab27a6fdd9154c9d52e4bb771254dcb3685cea653537c1266720bd4eL135-R138) [[4]](diffhunk://#diff-2bb090dfab27a6fdd9154c9d52e4bb771254dcb3685cea653537c1266720bd4eL249-R288) * Updated all `ViewDevice` subclasses (`ViewHost`, `ViewRouter`, `ViewSwitch`, etc.) to pass the `tag` property during initialization. [[1]](diffhunk://#diff-7887bc08a95b49d0d793156a43696b2d0124f450833dcd46e9ac1cd287e2f104R51) [[2]](diffhunk://#diff-7887bc08a95b49d0d793156a43696b2d0124f450833dcd46e9ac1cd287e2f104R63) [[3]](diffhunk://#diff-8b60a0a40123dfb655fb928b7de3e711094e1b2d393b1c80306aa9bebd6c13c7R36-R40) [[4]](diffhunk://#diff-221c2585585eef7ff9eafd84467d1c0bcef658af348a8673d2d00f752d06e35aR43) [[5]](diffhunk://#diff-221c2585585eef7ff9eafd84467d1c0bcef658af348a8673d2d00f752d06e35aR57) [[6]](diffhunk://#diff-273582e218624a21744b6795e7b5c241816fd31a72ae8b5c0bea983aecf72adfR33) [[7]](diffhunk://#diff-273582e218624a21744b6795e7b5c241816fd31a72ae8b5c0bea983aecf72adfR43) ### Identifier Updates Across Components: * Updated various components to use the new `getIdentifier` method (which prioritizes tags) instead of raw device IDs. This includes changes in `EdgeInfo`, `PacketInfo`, and dropdown labels in `program_info.ts`. [[1]](diffhunk://#diff-b2ff09f0ed37556256344e68555579178a82eced6c6e055568aa8aefbaf18dfdL55-R60) [[2]](diffhunk://#diff-b2ff09f0ed37556256344e68555579178a82eced6c6e055568aa8aefbaf18dfdL97-R97) [[3]](diffhunk://#diff-b2ff09f0ed37556256344e68555579178a82eced6c6e055568aa8aefbaf18dfdL122-R122) [[4]](diffhunk://#diff-dad6a431ca480d540d428efc2a8a75565208ed4e64ed84388af2b429357970b5L41-R46) [[5]](diffhunk://#diff-dad6a431ca480d540d428efc2a8a75565208ed4e64ed84388af2b429357970b5L61-R66) Ff392e25L48R48, [[6]](diffhunk://#diff-b8494f225cac1cb8b3c9735dac84eb3d2fa18c0fb6b32469b70ba095c0b87d69L86-R93) These changes collectively enhance the user experience by enabling inline editing of information fields and providing a more user-friendly way to identify and label devices. close #64
1 parent 4a76015 commit 74d3616

File tree

15 files changed

+143
-33
lines changed

15 files changed

+143
-33
lines changed

src/graphics/basic_components/text_info.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export interface InfoField {
1414
key: string;
1515
value: string | number | object;
1616
tooltip?: string;
17+
editable?: boolean;
18+
onEdit?: (newValue: string) => void;
1719
}
1820

1921
export class TextInfo {
@@ -42,10 +44,12 @@ export class TextInfo {
4244
// Add a field to the info and update the UI dynamically
4345
addField(
4446
key: string,
45-
value: string | number | object,
47+
value: string | number | object | null,
4648
tooltip?: string,
49+
editable?: boolean,
50+
onEdit?: (newValue: string) => void,
4751
): void {
48-
const field: InfoField = { key, value, tooltip };
52+
const field: InfoField = { key, value, tooltip, editable, onEdit };
4953

5054
// Create and append the new field dynamically
5155
const listItem = this.createListItem(field);
@@ -63,17 +67,22 @@ export class TextInfo {
6367
return this.container;
6468
}
6569

66-
// Create a list item dynamically based on the field type
6770
private createListItem(field: InfoField): HTMLLIElement {
68-
const { key, value, tooltip } = field;
71+
const { key, value, tooltip, editable, onEdit } = field;
6972

7073
if (key === TCP_FLAGS_KEY) {
7174
const flags = value as Flags;
7275
return this.createFlagsElement(flags, tooltip);
7376
} else if (typeof value === "object" && value !== null) {
7477
return this.createPayloadElement(key, value, tooltip);
7578
} else {
76-
return this.createDetailElement(key, value as string, tooltip);
79+
return this.createDetailElement(
80+
key,
81+
value as string,
82+
tooltip,
83+
editable,
84+
onEdit,
85+
);
7786
}
7887
}
7988

@@ -116,17 +125,18 @@ export class TextInfo {
116125
return listItem;
117126
}
118127

119-
// Create a regular detail element
120128
private createDetailElement(
121129
key: string,
122130
value: string,
123131
tooltip?: string,
132+
editable = false,
133+
onEdit?: (newValue: string) => void,
124134
): HTMLLIElement {
125135
const listItem = document.createElement("li");
126-
listItem.classList.add(CSS_CLASSES.DETAIL_ITEM); // Apply styling
136+
listItem.classList.add(CSS_CLASSES.DETAIL_ITEM);
127137

128138
const container = document.createElement("div");
129-
container.classList.add(CSS_CLASSES.DETAIL_CONTAINER); // Apply styling
139+
container.classList.add(CSS_CLASSES.DETAIL_CONTAINER);
130140

131141
const keyElement = document.createElement("span");
132142
keyElement.textContent = `${key}`;
@@ -135,9 +145,21 @@ export class TextInfo {
135145
attachTooltip(keyElement, tooltip);
136146
}
137147

138-
const valueElement = document.createElement("span");
139-
valueElement.textContent = value;
140-
valueElement.classList.add(CSS_CLASSES.DETAIL_VALUE);
148+
let valueElement: HTMLElement;
149+
if (editable) {
150+
const input = document.createElement("input");
151+
input.type = "text";
152+
input.value = value;
153+
input.classList.add(CSS_CLASSES.DETAIL_VALUE, "editable-field");
154+
input.addEventListener("change", () => {
155+
if (onEdit) onEdit(input.value);
156+
});
157+
valueElement = input;
158+
} else {
159+
valueElement = document.createElement("span");
160+
valueElement.textContent = value;
161+
valueElement.classList.add(CSS_CLASSES.DETAIL_VALUE);
162+
}
141163

142164
container.appendChild(keyElement);
143165
container.appendChild(valueElement);

src/graphics/renderables/device_info.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ export class DeviceInfo extends BaseInfo {
3535
const connections = this.device.viewgraph.getVisibleConnectedDeviceIds(id);
3636

3737
this.information.addField(TOOLTIP_KEYS.ID, id.toString(), TOOLTIP_KEYS.ID);
38+
this.information.addField(
39+
TOOLTIP_KEYS.TAG,
40+
this.device.getTag(),
41+
TOOLTIP_KEYS.TAG,
42+
true,
43+
(newValue: string) => this.device.setTag(newValue),
44+
);
3845
this.information.addListField(
3946
TOOLTIP_KEYS.CONNECTED_DEVICES,
4047
connections,

src/graphics/renderables/edge_info.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ export class EdgeInfo extends BaseInfo {
5353
);
5454
// Add MAC addresses as separate fields
5555
this.information.addField(
56-
`MAC Address iface (Device ${from})`,
56+
`MAC Address iface (${this.edge.viewgraph.getDevice(from).getIdentifier()})`,
5757
fromInterface.mac.toString(),
5858
TOOLTIP_KEYS.MAC_ADDRESS_IFACE,
5959
);
6060
this.information.addField(
61-
`MAC Address iface (Device ${to})`,
61+
`MAC Address iface (${this.edge.viewgraph.getDevice(to).getIdentifier()})`,
6262
toInterface.mac.toString(),
6363
TOOLTIP_KEYS.MAC_ADDRESS_IFACE,
6464
);
@@ -95,7 +95,7 @@ export class EdgeInfo extends BaseInfo {
9595

9696
// Dropdown for selecting the interface for "from" device
9797
const fromIfaceDropdown = new Dropdown({
98-
label: `Interface (Device ${from})`,
98+
label: `Interface (${this.edge.viewgraph.getDevice(from).getIdentifier()})`,
9999
tooltip: TOOLTIP_KEYS.IFACE_EDITOR,
100100
options: [
101101
{
@@ -120,7 +120,7 @@ export class EdgeInfo extends BaseInfo {
120120

121121
// Dropdown for selecting the interface for "to" device
122122
const toIfaceDropdown = new Dropdown({
123-
label: `Interface (Device ${to})`,
123+
label: `Interface (${this.edge.viewgraph.getDevice(to).getIdentifier()})`,
124124
tooltip: TOOLTIP_KEYS.IFACE_EDITOR,
125125
options: [
126126
{

src/graphics/renderables/packet_info.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ export class PacketInfo extends BaseInfo {
3939

4040
this.information.addField(
4141
TOOLTIP_KEYS.SOURCE_MAC_ADDRESS,
42-
`${this.packet.rawPacket.source.toString()}${srcDevice ? " (Device " + srcDevice.id + ")" : ""}`,
42+
`${this.packet.rawPacket.source.toString()}${srcDevice ? " (" + srcDevice.getIdentifier() + ")" : ""}`,
4343
TOOLTIP_KEYS.SOURCE_MAC_ADDRESS,
4444
);
4545
this.information.addField(
4646
TOOLTIP_KEYS.DESTINATION_MAC_ADDRESS,
47-
`${this.packet.rawPacket.destination.toString()}${dstDevice ? " (Device " + dstDevice.id + ")" : ""}`,
47+
`${this.packet.rawPacket.destination.toString()}${dstDevice ? " (" + dstDevice.getIdentifier() + ")" : ""}`,
4848
TOOLTIP_KEYS.DESTINATION_MAC_ADDRESS,
4949
);
5050
}
@@ -59,12 +59,12 @@ export class PacketInfo extends BaseInfo {
5959

6060
this.information.addField(
6161
TOOLTIP_KEYS.SOURCE_IP_ADDRESS,
62-
`${framePayload.sourceAddress.toString()}${srcDevice ? " (Device " + srcDevice.id + ")" : ""}`,
62+
`${framePayload.sourceAddress.toString()}${srcDevice ? " (" + srcDevice.getIdentifier() + ")" : ""}`,
6363
TOOLTIP_KEYS.SOURCE_IP_ADDRESS,
6464
);
6565
this.information.addField(
6666
TOOLTIP_KEYS.DESTINATION_IP_ADDRESS,
67-
`${framePayload.destinationAddress.toString()}${dstDevice ? " (Device " + dstDevice.id + ")" : ""}`,
67+
`${framePayload.destinationAddress.toString()}${dstDevice ? " (" + dstDevice.getIdentifier() + ")" : ""}`,
6868
TOOLTIP_KEYS.DESTINATION_IP_ADDRESS,
6969
);
7070
}

src/graphics/renderables/program_info.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ function otherDevices(viewgraph: ViewGraph, srcId: DeviceId) {
6868
return viewgraph
6969
.getLayerDeviceIds()
7070
.filter((id) => id !== srcId)
71-
.map((id) => ({ value: id.toString(), text: `Device ${id}`, id }));
71+
.map((id) => ({
72+
value: id.toString(),
73+
text: `${viewgraph.getDevice(id).getIdentifier()}`,
74+
id,
75+
}));
7276
}
7377

7478
function otherDevicesIp(
@@ -89,7 +93,7 @@ function otherDevicesIp(
8993
.flatMap((device) => {
9094
return device.interfaces.map((iface) => ({
9195
value: iface.ip.toString(),
92-
text: `${iface.ip.toString()} (Device ${device.id})`,
96+
text: `${iface.ip.toString()} (${device.getIdentifier()})`,
9397
}));
9498
});
9599
}

src/styles/info.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,29 @@
5252
cursor: default;
5353
}
5454

55+
.editable-field {
56+
color: #2b2b2b;
57+
text-align: right;
58+
flex-grow: 1;
59+
font-size: 1em;
60+
border: 1px solid #bfbfbf;
61+
border-radius: 0.25em;
62+
background: #fafbfc;
63+
padding: 0.15em 0.4em;
64+
margin-left: 0.5em;
65+
min-width: 2.5em; /* Mucho más corto */
66+
max-width: 10em; /* Limita el ancho máximo */
67+
width: 100%;
68+
box-sizing: border-box;
69+
transition: border-color 0.2s;
70+
}
71+
72+
.editable-field:focus {
73+
border-color: #1976d2;
74+
outline: none;
75+
background: #fff;
76+
}
77+
5578
.info-title {
5679
text-align: center; /* Centers the text horizontally */
5780
font-weight: bold; /* Highlights the title */

src/types/data-devices/dDevice.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export abstract class DataDevice {
1212
y: number;
1313
datagraph: DataGraph;
1414
interfaces: NetworkInterface[] = [];
15+
tag: string;
1516

1617
private static setIdCounter(id: number): void {
1718
if (id >= DataDevice.idCounter) {
@@ -36,6 +37,7 @@ export abstract class DataDevice {
3637
});
3738
});
3839
this.datagraph = datagraph;
40+
this.tag = graphData.tag;
3941
}
4042

4143
static initializedIdCounter() {
@@ -53,11 +55,16 @@ export abstract class DataDevice {
5355
mac: iface.mac.toString(),
5456
ip: iface.ip !== undefined ? iface.ip.toString() : undefined,
5557
})),
58+
tag: this.getTag(),
5659
};
5760
}
5861

5962
abstract getType(): DeviceType;
6063

64+
getTag(): string {
65+
return this.tag;
66+
}
67+
6168
ownMac(mac: MacAddress): boolean {
6269
return this.interfaces.some((iface) => iface.mac.equals(mac));
6370
}

src/types/graphs/datagraph.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface CommonDataNode {
3333
type: DeviceType;
3434
// TODO: remove this
3535
interfaces: NetworkInterfaceData[];
36+
tag?: string;
3637
}
3738

3839
export interface NetworkInterfaceData {

src/types/view-devices/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function createViewDevice(
2828
packetQueueSize = deviceInfo.packetQueueSize;
2929
timePerByte = deviceInfo.timePerByte;
3030
}
31-
const { id, interfaces } = deviceInfo;
31+
const { id, interfaces, tag } = deviceInfo;
3232

3333
switch (deviceInfo.type) {
3434
case DeviceType.Router:
@@ -38,13 +38,14 @@ export function createViewDevice(
3838
ctx,
3939
position,
4040
interfaces,
41+
tag,
4142
mask,
4243
packetQueueSize,
4344
timePerByte,
4445
);
4546
case DeviceType.Host:
46-
return new ViewHost(id, viewgraph, ctx, position, interfaces, mask);
47+
return new ViewHost(id, viewgraph, ctx, position, interfaces, tag, mask);
4748
case DeviceType.Switch:
48-
return new ViewSwitch(id, viewgraph, ctx, position, interfaces);
49+
return new ViewSwitch(id, viewgraph, ctx, position, interfaces, tag);
4950
}
5051
}

src/types/view-devices/vDevice.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export function layerFromType(type: DeviceType) {
6363
export abstract class ViewDevice extends Container {
6464
private sprite: Sprite;
6565
private tooltip: Text | null = null; // Tooltip como un Text de PIXI.js
66+
private tag: string | null = null; // Tag for the device
6667
private isDragCircle = false;
6768
private circleGraphic?: Graphics;
6869
private idLabel?: Text;
@@ -101,10 +102,12 @@ export abstract class ViewDevice extends Container {
101102
ctx: GlobalContext,
102103
position: Position,
103104
interfaces: NetworkInterfaceData[],
105+
tag: string | null,
104106
) {
105107
super();
106108

107109
this.id = id;
110+
this.tag = tag;
108111
this.viewgraph = viewgraph;
109112
this.ctx = ctx;
110113

@@ -129,7 +132,7 @@ export abstract class ViewDevice extends Container {
129132
this.zIndex = ZIndexLevels.Device;
130133

131134
// Add device ID label using the helper function
132-
this.addDeviceIdLabel();
135+
this.setTag(tag);
133136
this.updateVisibility();
134137

135138
// Set up tooltip behavior
@@ -249,18 +252,43 @@ export abstract class ViewDevice extends Container {
249252

250253
// Function to add the ID label to the device
251254
addDeviceIdLabel() {
255+
// Remove previous label if exists
256+
if (this.idLabel) {
257+
this.removeChild(this.idLabel);
258+
this.idLabel.destroy();
259+
}
252260
const textStyle = new TextStyle({
253261
fontSize: 12,
254262
fill: Colors.Black,
255263
align: "center",
256264
fontWeight: "bold",
257265
});
258-
const idText = new Text({ text: `ID: ${this.id}`, style: textStyle });
259-
idText.anchor.set(0.5);
260-
idText.y = this.height * 0.8;
261-
idText.zIndex = ZIndexLevels.Label;
262-
this.idLabel = idText;
263-
this.addChild(idText); // Add the ID text as a child of the device
266+
const labelText = this.tag
267+
? `ID: ${this.id} - ${this.tag}`
268+
: `ID: ${this.id}`;
269+
this.idLabel = new Text({ text: labelText, style: textStyle });
270+
this.idLabel.anchor.set(0.5);
271+
this.idLabel.y = this.height * 0.8;
272+
this.idLabel.zIndex = ZIndexLevels.Label;
273+
this.addChild(this.idLabel);
274+
}
275+
276+
setTag(tag: string | null) {
277+
this.tag = tag && tag.trim() !== "" ? tag : null;
278+
this.viewgraph.getDataGraph().modifyDevice(this.id, (device) => {
279+
if (device) {
280+
device.tag = this.tag;
281+
}
282+
});
283+
this.addDeviceIdLabel();
284+
}
285+
286+
getTag(): string | null {
287+
return this.tag;
288+
}
289+
290+
getIdentifier(): string {
291+
return this.tag ?? `Device ${this.id.toString()}`;
264292
}
265293

266294
getPosition(): Position {

0 commit comments

Comments
 (0)