Skip to content

Commit ed0c452

Browse files
authored
Refactor/renderables (#193)
### **Pull Request: Modularization and UI Enhancements** This pull request introduces a significant refactor and expansion of the codebase, focusing on modularization, UI consistency, and new features. Below is a detailed summary of the changes: closes #176 --- ### **1. Creation of Generic Basic Components** - Developed reusable and modular components to simplify UI development: - **Button**: A customizable button with support for tooltips and click events. - **Dropdown**: A flexible dropdown menu with tooltips and dynamic options. - **Label**: A simple label component with optional tooltips. - **ParameterEditor**: A parameter editor with input fields for numeric or text values. - **ProgressBar**: A progress bar with smooth animations and customizable styles. - **Slider**: A slider component for selecting values within a range. - **Table**: A table component with editable cells and row deletion functionality. - **TextInfo**: A component for displaying static text information. - **ToggleButton**: A button with toggle functionality and customizable states. --- ### **2. Creation of Composite Components** - Built higher-level components by combining the basic components: - **LabeledProgressBar**: Combines a progress bar with a label positioned above it. - **ToggleInfo**: Displays toggleable information with a title and content. - **ToggleParameterEditor**: Groups multiple parameter editors with toggle functionality. --- ### **3. Implementation of Device Information Classes** - Designed and implemented classes to display detailed information about devices and their interactions: - **DeviceInfo**: Displays general information about devices, including progress bars, routing tables, and editable parameters. - **EdgeInfo**: Provides detailed information about connections (edges) between devices. - **PacketInfo**: Displays information about packets in transit, including their source, destination, and status. --- ### **4. UI Enhancements and CSS Refactor** - Reviewed and refactored all CSS files to ensure a consistent style across the application: - Unified color schemes, font sizes, and spacing for a cohesive design. - Improved responsiveness for smaller screens using media queries. - Enhanced hover and active states for interactive elements. - Standardized button, dropdown, and tooltip styles. - Added glow effects and animations for progress bars and other interactive components. --- ### **5. Layer Selector and Context Integration** - Implemented a **LayerSelectorHandler** using the `Dropdown` component to allow users to switch between layers dynamically. - Integrated the layer selector with the `LeftBar` to update available device buttons based on the selected layer. --- ### **8. Tooltip Manager Improvements** - Enhanced the `TooltipManager` to: - Dynamically attach and detach tooltips based on element visibility. - Support delayed hiding for better user experience. - Synchronize tooltip state with global settings. --- UI Changes: ### Router Info before: ![image](https://github.com/user-attachments/assets/4c4c58c9-2a5a-46bb-9af5-42ebf4ad7474) after: ![image](https://github.com/user-attachments/assets/135aa25b-b36f-4355-b01b-5beeb1f31797) ### Host Info before: ![image](https://github.com/user-attachments/assets/60099f88-d347-45d7-b2a5-df7ee96af9c8) after: ![image](https://github.com/user-attachments/assets/6d2fe9cf-d571-4e55-a725-add98b0adc3f) ### Edge Info before: ![image](https://github.com/user-attachments/assets/298a6d96-8a65-457c-a65b-ba8d76f1ee97) after: ![image](https://github.com/user-attachments/assets/b033cf2f-edd5-4a9e-800d-0d7d60de78c9) ### Packet Info before: ![image](https://github.com/user-attachments/assets/2afd6091-f1b7-4996-b97a-95f64458a544) after: ![image](https://github.com/user-attachments/assets/bcb8fc1e-2871-48f0-90b0-ba6f73691faf)
1 parent bff0ca5 commit ed0c452

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1972
-1562
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { TooltipManager } from "../renderables/tooltip_manager";
2+
3+
export interface ButtonProps {
4+
text: string; // Button text
5+
onClick: () => void; // Action on click
6+
classList?: string[]; // Optional list of CSS classes
7+
tooltip?: string; // Optional tooltip
8+
}
9+
10+
export class Button {
11+
private button: HTMLButtonElement;
12+
13+
constructor(private props: ButtonProps) {
14+
this.button = document.createElement("button");
15+
this.initializeButton();
16+
}
17+
18+
private initializeButton(): void {
19+
const { text, onClick, classList, tooltip } = this.props;
20+
21+
this.button.textContent = text;
22+
if (classList) {
23+
this.button.classList.add(...classList);
24+
}
25+
26+
if (tooltip) {
27+
TooltipManager.getInstance().attachTooltip(this.button, tooltip);
28+
}
29+
30+
this.button.onclick = onClick; // Assign the click handler
31+
}
32+
33+
toHTML(): HTMLButtonElement {
34+
return this.button;
35+
}
36+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { CSS_CLASSES } from "../../utils/constants/css_constants";
2+
import { TooltipManager } from "../renderables/tooltip_manager";
3+
import { Label } from "./label";
4+
5+
export interface DropdownOption {
6+
value: string; // Value associated with the option
7+
text: string; // Text to display for the option
8+
tooltip?: string; // Optional tooltip for the option
9+
}
10+
11+
export interface DropdownProps {
12+
label?: string; // Optional label for the dropdown
13+
default_text?: string; // Default text to display when no option is selected
14+
tooltip?: string; // Tooltip for the dropdown
15+
options: DropdownOption[]; // Array of dropdown options
16+
onchange?: (value: string, event: Event) => void; // Callback triggered when an option is selected
17+
}
18+
19+
export class Dropdown {
20+
private container: HTMLElement;
21+
private selected: HTMLElement;
22+
private optionsContainer: HTMLElement;
23+
private selectedValue: string | null = null;
24+
25+
constructor(
26+
private props: DropdownProps,
27+
not_push = false,
28+
) {
29+
this.container = document.createElement("div");
30+
31+
this.selected = document.createElement("div");
32+
this.optionsContainer = document.createElement("div");
33+
if (not_push) {
34+
this.optionsContainer.classList.add(
35+
CSS_CLASSES.OPTIONS_CONTAINER_NOT_PUSH,
36+
);
37+
}
38+
this.initializeDropdown();
39+
}
40+
41+
private initializeDropdown(): void {
42+
const { label, default_text, tooltip, options, onchange } = this.props;
43+
44+
// Create the label if provided
45+
if (label) {
46+
const dropdown_label = new Label(label, tooltip).toHTML();
47+
this.container.appendChild(dropdown_label);
48+
}
49+
50+
// Create the custom dropdown element
51+
const dropdown = document.createElement("div");
52+
dropdown.classList.add(CSS_CLASSES.CUSTOM_DROPDOWN);
53+
54+
// Create the element displaying the selected option
55+
this.selected.classList.add(CSS_CLASSES.SELECTED_OPTION);
56+
this.selected.textContent = default_text
57+
? `Select ${default_text}`
58+
: "Select option"; // Default text or fallback
59+
if (tooltip) {
60+
TooltipManager.getInstance().attachTooltip(this.selected, tooltip);
61+
}
62+
dropdown.appendChild(this.selected);
63+
64+
// Create the container for dropdown options
65+
this.optionsContainer.classList.add(CSS_CLASSES.OPTIONS_CONTAINER);
66+
67+
// Populate the options
68+
options.forEach((optionData) => {
69+
this.addOption(optionData, onchange);
70+
});
71+
72+
// Toggle the dropdown options visibility when clicking the selected option
73+
this.selected.onclick = () => {
74+
this.optionsContainer.classList.toggle(CSS_CLASSES.SHOW);
75+
};
76+
77+
dropdown.appendChild(this.optionsContainer);
78+
this.container.appendChild(dropdown);
79+
}
80+
81+
private addOption(
82+
optionData: DropdownOption,
83+
onchange?: (value: string, event: Event) => void,
84+
): void {
85+
// Validate the option object
86+
if (
87+
!optionData ||
88+
typeof optionData.value !== "string" ||
89+
typeof optionData.text !== "string"
90+
) {
91+
console.warn("Invalid option data:", optionData);
92+
return;
93+
}
94+
95+
// Create an element for the option
96+
const option = document.createElement("div");
97+
option.classList.add(CSS_CLASSES.DROPDOWN_OPTION);
98+
option.textContent = optionData.text;
99+
TooltipManager.getInstance().attachTooltip(option, optionData.text, true);
100+
101+
// Set up click event for option selection
102+
option.onclick = (e) => {
103+
this.selected.textContent = optionData.text;
104+
this.selectedValue = optionData.value;
105+
this.optionsContainer.classList.remove(CSS_CLASSES.SHOW); // Close the options container
106+
if (onchange) {
107+
onchange(optionData.value, e); // Trigger the onchange callback
108+
}
109+
};
110+
111+
this.optionsContainer.appendChild(option);
112+
}
113+
114+
// Public method to get the currently selected value
115+
getValue(): string | null {
116+
return this.selectedValue;
117+
}
118+
119+
// Public method to update the dropdown options
120+
setOptions(newOptions: DropdownOption[]): void {
121+
this.optionsContainer.innerHTML = ""; // Clear existing options
122+
newOptions.forEach((option) => {
123+
this.addOption(option, this.props.onchange);
124+
});
125+
}
126+
127+
setValue(value: string): void {
128+
const option = this.props.options.find((opt) => opt.value === value);
129+
if (!option) {
130+
console.warn(`Option with value "${value}" not found.`);
131+
return;
132+
}
133+
134+
this.selected.textContent = option.text;
135+
this.selectedValue = option.value;
136+
this.optionsContainer.classList.remove(CSS_CLASSES.SHOW); // Ensure the dropdown is closed
137+
}
138+
139+
// Public method to render the dropdown
140+
toHTML(): HTMLElement {
141+
return this.container;
142+
}
143+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { TooltipManager } from "../renderables/tooltip_manager";
2+
3+
export class Label {
4+
private labelElement: HTMLLabelElement;
5+
6+
constructor(label: string, tooltip?: string, className?: string) {
7+
this.labelElement = document.createElement("label");
8+
this.labelElement.innerHTML = `<strong>${label}</strong>`; // Use innerHTML for consistency
9+
10+
if (className) {
11+
this.labelElement.classList.add(className); // Add optional CSS class
12+
}
13+
14+
if (tooltip) {
15+
TooltipManager.getInstance().attachTooltip(this.labelElement, tooltip); // Attach tooltip
16+
}
17+
}
18+
19+
// Public method to render the label
20+
toHTML(): HTMLLabelElement {
21+
return this.labelElement;
22+
}
23+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { CSS_CLASSES } from "../../utils/constants/css_constants";
2+
import { TooltipManager } from "../renderables/tooltip_manager";
3+
4+
export interface EditableParameter {
5+
label: string;
6+
initialValue: number | string;
7+
onChange: (newValue: number | string) => void;
8+
}
9+
10+
export class ParameterEditor {
11+
private ParametersContainer: HTMLElement;
12+
13+
constructor(private parameters: EditableParameter[]) {
14+
// Create the main bordered container
15+
this.ParametersContainer = document.createElement("div");
16+
this.ParametersContainer.className = CSS_CLASSES.PARAMETER_GROUP;
17+
18+
// Add each parameter editor directly to the Parameters container
19+
this.parameters.forEach((parameter) => {
20+
const parameterEditor = this.createParameterEditor(parameter);
21+
this.ParametersContainer.appendChild(parameterEditor);
22+
});
23+
}
24+
25+
private createParameterEditor(parameter: EditableParameter): HTMLElement {
26+
const { label, initialValue, onChange } = parameter;
27+
28+
// Create the editor container
29+
const container = document.createElement("div");
30+
container.className = CSS_CLASSES.PARAMETER_EDITOR;
31+
32+
// Create the label
33+
const labelElement = document.createElement("label");
34+
labelElement.textContent = label;
35+
labelElement.className = CSS_CLASSES.PARAMETER_EDITOR_LABEL;
36+
TooltipManager.getInstance().attachTooltip(labelElement, label);
37+
38+
// Create the input
39+
const input = document.createElement("input");
40+
input.type = typeof initialValue === "number" ? "number" : "text";
41+
input.value = initialValue.toString();
42+
input.className = CSS_CLASSES.PARAMETER_EDITOR_INPUT;
43+
44+
// Add the change event
45+
let previousValue = initialValue;
46+
47+
input.addEventListener("change", () => {
48+
const newValue =
49+
input.type === "number"
50+
? (parseFloat(input.value) as number)
51+
: (input.value as string);
52+
53+
if (input.value === "") {
54+
input.value = previousValue.toString();
55+
} else {
56+
previousValue = newValue;
57+
onChange(newValue);
58+
}
59+
});
60+
61+
// Add the label and input to the container
62+
container.appendChild(labelElement);
63+
container.appendChild(input);
64+
65+
return container;
66+
}
67+
68+
toHTML(): HTMLElement {
69+
return this.ParametersContainer;
70+
}
71+
}

src/graphics/renderables/progress_bar.ts renamed to src/graphics/basic_components/progress_bar.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { CSS_CLASSES } from "../../utils/constants/css_constants";
2+
13
export interface ProgressBarProps {
24
current: number; // Current size of the queue
35
max: number; // Maximum size of the queue
@@ -10,13 +12,13 @@ export class ProgressBar {
1012

1113
constructor(props: ProgressBarProps) {
1214
this.container = document.createElement("div");
13-
this.container.className = "progress-bar-container";
15+
this.container.className = CSS_CLASSES.PROGRESS_BAR_CONTAINER;
1416

1517
this.progress = document.createElement("div");
16-
this.progress.className = "progress-bar";
18+
this.progress.className = CSS_CLASSES.PROGRESS_BAR;
1719

1820
this.text = document.createElement("div");
19-
this.text.className = "progress-bar-text";
21+
this.text.className = CSS_CLASSES.PROGRESS_BAR_TEXT;
2022

2123
this.container.appendChild(this.progress);
2224
this.container.appendChild(this.text);
@@ -32,7 +34,7 @@ export class ProgressBar {
3234
}
3335

3436
// Returns the HTML container of the component
35-
render(): HTMLElement {
37+
toHTML(): HTMLElement {
3638
return this.container;
3739
}
3840
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { CSS_CLASSES } from "../../utils/constants/css_constants";
2+
import { TooltipManager } from "../renderables/tooltip_manager";
3+
4+
export interface SliderProps {
5+
label: string; // Label displayed above the slider
6+
min: number; // Minimum value of the slider
7+
max: number; // Maximum value of the slider
8+
step: number; // Slider increment
9+
initialValue: number; // Initial value of the slider
10+
onChange?: (value: number) => void; // Callback executed when the value changes
11+
}
12+
13+
export class Slider {
14+
private container: HTMLElement;
15+
private valueDisplay: HTMLElement;
16+
private sliderInput: HTMLInputElement;
17+
18+
constructor(props: SliderProps) {
19+
this.container = document.createElement("div");
20+
this.container.classList.add(CSS_CLASSES.SLIDER_CONTAINER);
21+
22+
const label = document.createElement("div");
23+
label.textContent = props.label;
24+
label.classList.add(CSS_CLASSES.SLIDER_LABEL);
25+
TooltipManager.getInstance().attachTooltip(label, props.label);
26+
27+
this.valueDisplay = document.createElement("div");
28+
this.valueDisplay.textContent = `${props.initialValue}x`;
29+
this.valueDisplay.classList.add(CSS_CLASSES.SLIDER_VALUE);
30+
31+
this.sliderInput = document.createElement("input");
32+
this.sliderInput.type = "range";
33+
this.sliderInput.min = props.min.toString();
34+
this.sliderInput.max = props.max.toString();
35+
this.sliderInput.step = props.step.toString();
36+
this.sliderInput.value = props.initialValue.toString();
37+
this.sliderInput.classList.add(CSS_CLASSES.SLIDER_INPUT);
38+
39+
this.sliderInput.addEventListener("input", () => {
40+
const value = parseFloat(this.sliderInput.value);
41+
this.valueDisplay.textContent = `${value}x`;
42+
if (props.onChange) {
43+
props.onChange(value);
44+
}
45+
});
46+
47+
this.container.appendChild(label);
48+
this.container.appendChild(this.sliderInput);
49+
this.container.appendChild(this.valueDisplay);
50+
}
51+
52+
toHTML(): HTMLElement {
53+
return this.container;
54+
}
55+
}

0 commit comments

Comments
 (0)