Skip to content

Commit 4c74067

Browse files
authored
Refactor/routing-arp-switching tables (#258)
This pull request introduces significant enhancements to the `Table` component and its usage across various files. The changes include adding support for row-level metadata, enabling dynamic row addition, and refining callback methods for editing and deleting rows. These updates improve flexibility and maintainability for tables in the application. ### Enhancements to the `Table` Component (`src/graphics/basic_components/table.ts`): * Introduced the `TableRow` interface to encapsulate row data and metadata (e.g., `edited` status). Updated the `rows` property in `TableOptions` to use `TableRow[]` instead of `string[][]`. * Added support for dynamically adding rows via the `onAddRow` callback and a new "Add Row" button in the table UI. * Improved row rendering by applying a CSS class (`edited-row`) for rows marked as edited and updated the logic for handling editable cells. [[1]](diffhunk://#diff-94a19a6c54d3f3da90efed3af1c8c06201e699a1eaa13c9db24624a4b7ea444aL83-R111) [[2]](diffhunk://#diff-94a19a6c54d3f3da90efed3af1c8c06201e699a1eaa13c9db24624a4b7ea444aL155-L190) * Added a utility method `getRowHash` to generate a key-value mapping of a row's data based on table headers. This is used for callbacks like `onEdit`. ### Updates to ARP Table (`src/graphics/renderables/arp_table.ts`): * Refactored ARP table rows to use the `TableRow` interface, enabling metadata tracking (e.g., `edited` status). * Enhanced callbacks for editing and deleting rows to work with row keys (e.g., IP address) instead of indices. Added support for adding new rows. * Replaced direct calls to ARP table methods with utility functions (e.g., `getArpTable`, `removeArpTableEntry`) for improved modularity. ### Updates to Device Info (`src/graphics/renderables/device_info.ts`): * Updated methods for adding ARP, routing, and switching tables to use `TableRow` for rows and track metadata like `edited` status. [[1]](diffhunk://#diff-965a7810b34f8fb22a13101c89a873c18aa7d3c9c0dc67f0c79f178859084809L127-R135) [[2]](diffhunk://#diff-965a7810b34f8fb22a13101c89a873c18aa7d3c9c0dc67f0c79f178859084809L191-R198) [[3]](diffhunk://#diff-965a7810b34f8fb22a13101c89a873c18aa7d3c9c0dc67f0c79f178859084809L217-R228) * Replaced direct method calls with utility functions for fetching table data (e.g., `getRoutingTable`, `getSwitchingTable`). [[1]](diffhunk://#diff-965a7810b34f8fb22a13101c89a873c18aa7d3c9c0dc67f0c79f178859084809L127-R135) [[2]](diffhunk://#diff-965a7810b34f8fb22a13101c89a873c18aa7d3c9c0dc67f0c79f178859084809L217-R228) ### Updates to Program Runner Info (`src/graphics/renderables/program_runner_info.ts`): * Refactored rows in the program runner table to use `TableRow` for consistency and added metadata support. Updated the `onDelete` callback to work with row keys (e.g., PID) instead of indices. These changes collectively improve the extensibility and usability of tables across the application while ensuring better alignment with modern design patterns. close #150
1 parent fac06e8 commit 4c74067

32 files changed

+1461
-868
lines changed

src/graphics/basic_components/table.ts

Lines changed: 113 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import { CSS_CLASSES } from "../../utils/constants/css_constants";
2+
import { TOOLTIP_KEYS } from "../../utils/constants/tooltips_constants";
23
import { attachTooltip } from "../renderables/tooltip_manager";
34
import { Button } from "./button";
45

6+
export interface TableRow {
7+
values: string[];
8+
edited?: boolean;
9+
}
10+
511
export interface TableOptions {
612
headers: Record<string, string | HTMLElement>; // Custom elements for headers
713
fieldsPerRow: number; // Number of fields per row
8-
rows: string[][]; // Table rows
14+
rows: TableRow[]; // Table rows
915
editableColumns?: boolean[]; // Array indicating which columns are editable
10-
onEdit?: (row: number, col: number, newValue: string) => boolean; // Callback for editing cells
11-
onDelete?: (row: number) => boolean; // Callback for deleting rows
16+
onEdit?: (
17+
col: number,
18+
newValue: string,
19+
rowHash: Record<string, string>,
20+
) => boolean;
21+
onDelete?: (key: string) => boolean; // Callback for deleting rows by key
22+
onAddRow?: (values: string[]) => boolean; // Callback for adding rows
1223
tableClasses?: string[]; // Additional CSS classes for the table
1324
}
1425

@@ -80,21 +91,25 @@ export class Table {
8091
}
8192

8293
private createRows(): void {
83-
const { rows, fieldsPerRow, onEdit, onDelete } = this.options;
94+
const { rows, fieldsPerRow, onEdit, onDelete, onAddRow } = this.options;
8495

8596
rows.forEach((row, rowIndex) => {
8697
const tr = document.createElement("tr");
8798

99+
if (row.edited) {
100+
tr.classList.add("edited-row");
101+
}
102+
88103
// Add cells for the row data
89-
row.forEach((cellData, colIndex) => {
104+
row.values.forEach((cellData, colIndex) => {
90105
const td = this.createCell(cellData, rowIndex, colIndex, onEdit);
91106
tr.appendChild(td);
92107
});
93108

94109
// Add empty cells if the row has fewer fields than fieldsPerRow
95-
const emptyCellsNeeded = fieldsPerRow - row.length;
110+
const emptyCellsNeeded = fieldsPerRow - row.values.length;
96111
for (let i = 0; i < emptyCellsNeeded; i++) {
97-
const td = this.createCell("", rowIndex, row.length + i, onEdit);
112+
const td = this.createCell("", rowIndex, row.values.length + i, onEdit);
98113
tr.appendChild(td);
99114
}
100115

@@ -107,14 +122,65 @@ export class Table {
107122
this.tbody.appendChild(tr);
108123
});
109124

125+
if (onAddRow) {
126+
const addTr = this.createAddRow(fieldsPerRow, onAddRow);
127+
this.tbody.appendChild(addTr);
128+
}
129+
110130
this.table.appendChild(this.tbody);
111131
}
112132

133+
private createAddRow(
134+
fieldsPerRow: number,
135+
onAddRow: (values: string[]) => boolean,
136+
): HTMLTableRowElement {
137+
const addTr = document.createElement("tr");
138+
addTr.classList.add("add-row");
139+
140+
const addCells: HTMLTableCellElement[] = [];
141+
142+
for (let i = 0; i < fieldsPerRow; i++) {
143+
const td = document.createElement("td");
144+
td.classList.add("editable-cell");
145+
td.contentEditable = "true";
146+
147+
td.addEventListener("keydown", (event) => {
148+
if (event.key === "Delete" || event.key === "Backspace") {
149+
event.stopPropagation();
150+
}
151+
});
152+
addTr.appendChild(td);
153+
addCells.push(td);
154+
}
155+
156+
const addTd = document.createElement("td");
157+
const addButton = new Button({
158+
text: "➕",
159+
classList: [CSS_CLASSES.TABLE_BUTTON],
160+
tooltip: TOOLTIP_KEYS.ADD_ENTRY_BUTTON,
161+
onClick: () => {
162+
const values = addCells.map((cell) => cell.textContent?.trim() || "");
163+
const added = onAddRow(values);
164+
if (added) {
165+
addCells.forEach((cell) => (cell.textContent = ""));
166+
}
167+
},
168+
});
169+
addTd.appendChild(addButton.toHTML());
170+
addTr.appendChild(addTd);
171+
172+
return addTr;
173+
}
174+
113175
private createCell(
114176
cellData: string,
115177
rowIndex: number,
116178
colIndex: number,
117-
onEdit?: (row: number, col: number, newValue: string) => boolean,
179+
onEdit?: (
180+
col: number,
181+
newValue: string,
182+
rowHash: Record<string, string>,
183+
) => boolean,
118184
): HTMLTableCellElement {
119185
const td = document.createElement("td");
120186
td.textContent = cellData;
@@ -131,15 +197,16 @@ export class Table {
131197

132198
private createDeleteCell(
133199
tr: HTMLTableRowElement,
134-
onDelete: (row: number) => boolean,
200+
onDelete: (key: string) => boolean,
135201
): HTMLTableCellElement {
136202
const deleteTd = document.createElement("td");
137203
const deleteButton = new Button({
138204
text: "🗑️",
139-
classList: [CSS_CLASSES.TRASH_BUTTON],
205+
classList: [CSS_CLASSES.TABLE_BUTTON],
206+
tooltip: TOOLTIP_KEYS.DELETE_ENTRY_BUTTON,
140207
onClick: () => {
141-
const index = Array.from(this.tbody.rows).indexOf(tr); // Calculate the index of the row
142-
if (index !== -1 && onDelete(index)) {
208+
const key = tr.cells[0]?.textContent?.trim() || "";
209+
if (key && onDelete(key)) {
143210
this.tbody.removeChild(tr);
144211
}
145212
},
@@ -152,42 +219,50 @@ export class Table {
152219
cell: HTMLTableCellElement,
153220
rowIndex: number,
154221
colIndex: number,
155-
onEdit: (row: number, col: number, newValue: string) => boolean,
222+
onEdit: (
223+
col: number,
224+
newValue: string,
225+
rowHash: Record<string, string>,
226+
) => boolean,
156227
) {
157228
cell.classList.add("editable-cell");
158229
cell.contentEditable = "true";
159230
const originalContent = cell.textContent;
160231

161-
// Prevent deleting the row while editing
232+
let originalRowHash: Record<string, string> = {};
233+
234+
cell.addEventListener("focus", () => {
235+
originalRowHash = this.getRowHash(rowIndex);
236+
});
237+
162238
cell.addEventListener("keydown", (event) => {
163239
if (event.key === "Delete" || event.key === "Backspace") {
164240
event.stopPropagation();
165241
}
242+
if (event.key === "C" || event.key === "c") {
243+
event.stopPropagation();
244+
}
166245
});
167246

168-
// Handle edits
169247
cell.addEventListener("blur", () => {
170248
const newValue = cell.textContent?.trim() || "";
171249

172-
const isValid = onEdit(rowIndex, colIndex, newValue);
250+
const isValid = onEdit(colIndex, newValue, originalRowHash);
173251

174252
if (!isValid) {
175-
console.warn(`Invalid input for column ${colIndex}: ${newValue}`);
176-
cell.textContent = originalContent; // Revert change if invalid
253+
console.warn(
254+
`Invalid or same input for column ${colIndex}: ${newValue}`,
255+
);
256+
cell.textContent = originalContent;
177257
return;
178258
}
179-
180-
console.log(
181-
`Updated cell at row ${rowIndex}, column ${colIndex} with value: ${newValue}`,
182-
);
183259
});
184260
}
185261

186262
// New method to update rows dynamically
187-
updateRows(newRows: string[][]): void {
263+
updateRows(newRows: TableRow[]): void {
188264
this.clearTableRows(); // Clear existing rows
189265
this.options.rows = newRows; // Update the rows in options
190-
console.log("Updated rows:", this.options.rows); // Log the updated rows
191266
this.createRows();
192267
}
193268

@@ -198,4 +273,18 @@ export class Table {
198273
toHTML(): HTMLElement {
199274
return this.tableWrapper;
200275
}
276+
277+
getRowHash(rowIndex: number): Record<string, string> {
278+
const row = this.tbody.rows[rowIndex];
279+
const hash: Record<string, string> = {};
280+
const headerKeys = Object.keys(this.options.headers);
281+
if (!row) return hash;
282+
Array.from(row.cells).forEach((cell, i) => {
283+
if (!cell.querySelector("button")) {
284+
const key = headerKeys[i] ?? `col${i}`;
285+
hash[key] = cell.textContent?.trim() || "";
286+
}
287+
});
288+
return hash;
289+
}
201290
}

src/graphics/basic_components/text_info.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export class TextInfo {
214214
{} as Record<string, string>,
215215
),
216216
fieldsPerRow: Object.keys(flags).length,
217-
rows: [statusRow, valueRow],
217+
rows: [{ values: statusRow }, { values: valueRow }],
218218
tableClasses: [CSS_CLASSES.TABLE, CSS_CLASSES.RIGHT_BAR_TABLE],
219219
});
220220

0 commit comments

Comments
 (0)