Skip to content

Commit 28d16d0

Browse files
authored
Merge pull request #154 from cogip/149-dashboard-add-support-for-configuration-modals
149 dashboard add support for configuration modals
2 parents 8eb910c + 0d1845d commit 28d16d0

File tree

10 files changed

+219
-28
lines changed

10 files changed

+219
-28
lines changed

cogip/tools/copilot/copilot.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ async def handle_pid(self, message: bytes | None = None) -> None:
234234
pid_schema = pid.model_json_schema()
235235
# Add namespace in JSON Schema
236236
pid_schema["namespace"] = "/copilot"
237+
pid_schema["sio_event"] = "config_updated"
237238
# Add current values in JSON Schema
238239
pid_schema["title"] = pid.id.name
239240
for prop, value in pid.model_dump().items():
Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,3 @@
11
@layer components {
2-
/* width */
3-
::-webkit-scrollbar {
4-
width: 5px;
5-
}
62

7-
/* Track */
8-
::-webkit-scrollbar-track {
9-
background: rgba(0, 0, 0, 0.25);
10-
}
11-
12-
/* Handle */
13-
::-webkit-scrollbar-thumb {
14-
background: var(--grey-color);
15-
}
16-
17-
/* Handle on hover */
18-
::-webkit-scrollbar-thumb:hover {
19-
background: var(--grey-color);
20-
}
21-
22-
/* For Firefox */
23-
* {
24-
scrollbar-width: thin;
25-
scrollbar-color: var(--grey-color) rgba(0, 0, 0, 0.25);
26-
}
273
}

cogip/tools/dashboard/static/css/input.css

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,43 @@
1-
@import "custom-scrollbar.css";
2-
31
@tailwind base;
42
@tailwind components;
53
@tailwind utilities;
64

5+
@layer components {
6+
/* width */
7+
::-webkit-scrollbar {
8+
width: 5px;
9+
}
10+
11+
/* Track */
12+
::-webkit-scrollbar-track {
13+
background: rgba(0, 0, 0, 0.25);
14+
}
15+
16+
/* Handle */
17+
::-webkit-scrollbar-thumb {
18+
background: var(--grey-color);
19+
}
20+
21+
/* Handle on hover */
22+
::-webkit-scrollbar-thumb:hover {
23+
background: var(--grey-color);
24+
}
25+
26+
/* For Firefox */
27+
* {
28+
scrollbar-width: thin;
29+
scrollbar-color: var(--grey-color) rgba(0, 0, 0, 0.25);
30+
}
31+
32+
.thumb-red-cogip::-webkit-slider-thumb {
33+
@apply bg-red-cogip w-4 h-4 rounded-full appearance-none cursor-pointer;
34+
}
35+
36+
.thumb-red-cogip::-moz-range-thumb {
37+
@apply bg-red-cogip w-4 h-4 rounded-full cursor-pointer;
38+
}
39+
}
40+
741
:root {
842
--grey-color: #acaeb1;
943
--red-cogip: #d01b1e;

cogip/tools/dashboard/static/css/prod/output.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
let socket = null;
2+
3+
export function openConfigModal(config, send_socket) {
4+
socket = send_socket;
5+
document.getElementById("configModalTitle").textContent = config.title;
6+
7+
document.getElementById("configModalBody").innerHTML = "";
8+
9+
const form = document.createElement("div");
10+
form.classList.add("grid", "grid-cols-[30%_67%]", "gap-4", "items-center");
11+
12+
Object.entries(config.properties).forEach(([key, property]) => {
13+
const label = document.createElement("label");
14+
label.textContent = property.title;
15+
label.classList.add("font-medium", "text-grey-color", "text-left");
16+
label.setAttribute("key", key);
17+
18+
const fieldContainer = document.createElement("div");
19+
fieldContainer.classList.add("flex", "items-center", "gap-2");
20+
21+
if (property.type === "boolean") {
22+
const checkbox = document.createElement("input");
23+
checkbox.type = "checkbox";
24+
checkbox.checked = property.value;
25+
checkbox.classList.add("form-checkbox", "h-5", "w-5", "checked:accent-red-cogip", "checked:border-red-cogip");
26+
checkbox.addEventListener("change", () => {
27+
sendSocketUpdate(
28+
config.sio_event,
29+
key,
30+
checkbox.checked,
31+
config.namespace
32+
);
33+
});
34+
fieldContainer.appendChild(checkbox);
35+
} else if (property.type === "integer" || property.type === "number") {
36+
const input = document.createElement("input");
37+
input.type = "number";
38+
input.value = property.value;
39+
input.min = property.minimum;
40+
input.max = property.maximum;
41+
input.style.width = '75px';
42+
input.disabled = true;
43+
input.classList.add(
44+
"text-grey-color",
45+
"p-2",
46+
"bg-black",
47+
"rounded-md",
48+
"border",
49+
"border-slate-950",
50+
"use-keyboard-input",
51+
"focus:outline-none",
52+
"focus:caret-red-cogip",
53+
"focus:ring-2",
54+
"focus:ring-red-cogip"
55+
);
56+
57+
const slider = document.createElement("input");
58+
slider.type = "range";
59+
slider.min = property.minimum;
60+
slider.max = property.maximum;
61+
slider.value = property.value;
62+
slider.step = property.multipleOf || 1;
63+
slider.classList.add(
64+
"w-full",
65+
"appearance-none",
66+
"h-4",
67+
"bg-gray-300",
68+
"rounded-full",
69+
"thumb-red-cogip"
70+
);
71+
72+
const decrementBtn = document.createElement("button");
73+
decrementBtn.textContent = "-";
74+
decrementBtn.classList.add(
75+
"px-4",
76+
"py-2",
77+
"text-2xl",
78+
"bg-zinc-800",
79+
"text-grey-color",
80+
"rounded",
81+
"hover:bg-gray-600",
82+
"focus:outline-none"
83+
);
84+
decrementBtn.addEventListener("click", () => {
85+
let newValue = Math.max(
86+
parseFloat(input.value) - (property.multipleOf || 1),
87+
property.minimum
88+
);
89+
newValue = parseFloat(newValue.toFixed(3));
90+
input.value = slider.value = newValue;
91+
sendSocketUpdate(config.sio_event, key, newValue, config.namespace);
92+
});
93+
94+
const incrementBtn = document.createElement("button");
95+
incrementBtn.textContent = "+";
96+
incrementBtn.classList.add("px-4", "py-2", "text-2xl", "bg-zinc-800", "text-grey-color", "rounded", "hover:bg-gray-600", "focus:outline-none");
97+
incrementBtn.addEventListener("click", () => {
98+
let newValue = Math.min(
99+
parseFloat(input.value) + (property.multipleOf || 1),
100+
property.maximum
101+
);
102+
newValue = parseFloat(newValue.toFixed(3));
103+
input.value = slider.value = newValue;
104+
sendSocketUpdate(config.sio_event, key, newValue, config.namespace);
105+
});
106+
107+
input.addEventListener("input", () => {
108+
slider.value = input.value;
109+
sendSocketUpdate(config.sio_event, key, input.value, config.namespace);
110+
});
111+
slider.addEventListener("input", () => {
112+
input.value = slider.value;
113+
sendSocketUpdate(config.sio_event, key, slider.value, config.namespace);
114+
});
115+
fieldContainer.appendChild(input);
116+
fieldContainer.appendChild(slider);
117+
fieldContainer.appendChild(decrementBtn);
118+
fieldContainer.appendChild(incrementBtn);
119+
}
120+
121+
form.appendChild(label);
122+
form.appendChild(fieldContainer);
123+
});
124+
125+
document.getElementById("configModalBody").appendChild(form);
126+
127+
// Show the modal
128+
const configModal = document.getElementById("configModal");
129+
configModal.classList.remove("hidden");
130+
configModal.classList.add("flex");
131+
}
132+
133+
function sendSocketUpdate(event, name, value, namespace) {
134+
const socketUpdate = {
135+
name: name,
136+
namespace: namespace,
137+
sio_event: event,
138+
value: +value,
139+
};
140+
socket.emit(event, socketUpdate);
141+
}

cogip/tools/dashboard/templates/dashboard.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55
<title>COGIP Dashboard</title>
66
<link rel="shortcut icon" href="#" />
77
<link rel="stylesheet" href="{{ url_for('static', path='css/prod/output.css') }}" />
8+
<style>
9+
input[type='range']::-webkit-slider-thumb {
10+
-webkit-appearance: none;
11+
appearance: none;
12+
width: 15px;
13+
height: 15px;
14+
background: red;
15+
cursor: pointer;
16+
border-radius: 50%;
17+
}
18+
input[type='range']::-moz-range-thumb {
19+
width: 15px;
20+
height: 15px;
21+
background: red;
22+
cursor: pointer;
23+
border-radius: 50%;
24+
}
25+
</style>
826
</head>
927

1028
<body class="flex flex-col h-full w-full bg-zinc-900 overflow-hidden" data-robot-id="{{ robot_id }}">
@@ -106,6 +124,18 @@ <h5 class="text-lg font-medium text-grey-color" id="wizardModalTitle"></h5>
106124
</div>
107125
</div>
108126
</div>
127+
128+
<div id="configModal" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 hidden">
129+
<div id="configModalContent" class="bg-zinc-900 rounded-lg shadow-lg w-[85vw] h-[85vh]">
130+
<div id="configModalHeader" class="flex items-center justify-between p-4 border-b border-gray-600">
131+
<h5 class="text-lg font-medium text-grey-color " id="configModalTitle"></h5>
132+
<button type="button" class="text-grey-color text-2xl leading-none hover:text-gray-400" aria-label="Close" onclick="closeModal('configModal')">&times;</button>
133+
</div>
134+
<div id="configModalBody" class="m-4 text-center overflow-y-auto max-h-[24rem]">
135+
</div>
136+
</div>
137+
</div>
138+
109139
{% if robot_id != 0 %}
110140
<div class="fixed inset-0 z-50 hidden" id="scoreModal">
111141
<div class="flex h-full w-full p-4 text-center">
@@ -132,6 +162,7 @@ <h5 class="text-lg font-medium text-grey-color">Score</h5>
132162
import * as drawModule from "../static/js/drawModule.js";
133163
import * as cameraModule from "../static/js/cameraModule.js";
134164
import * as wizardModule from "../static/js/wizardModule.js";
165+
import * as configModule from "../static/js/configModule.js";
135166
import * as scoreModule from "../static/js/scoreModule.js";
136167
import { virtualKeyboard } from "../static/js/keyboardModule.js";
137168

@@ -189,6 +220,10 @@ <h5 class="text-lg font-medium text-grey-color">Score</h5>
189220
})
190221
.on("starter_changed", function(robot_id, pushed) {
191222
document.querySelector("#starter input").checked = pushed;
223+
})
224+
.on('config', function(msg) {
225+
configModule.openConfigModal(msg, this);
226+
virtualKeyboard._actualize();
192227
});
193228

194229
// add event listener on click for starter input

cogip/tools/detector/sio_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def on_command(self, cmd: str) -> None:
6060
schema = self._detector.properties.model_json_schema()
6161
# Add namespace in JSON Schema
6262
schema["namespace"] = "/detector"
63+
schema["sio_event"] = "config_updated"
6364
# Add current values in JSON Schema
6465
for prop, value in self._detector.properties.model_dump().items():
6566
schema["properties"][prop]["value"] = value

cogip/tools/planner/planner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,7 @@ async def command(self, cmd: str):
724724
schema = TypeAdapter(Properties).json_schema()
725725
# Add namespace in JSON Schema
726726
schema["namespace"] = "/planner"
727+
schema["sio_event"] = "config_updated"
727728
# Add current values in JSON Schema
728729
for prop, value in RootModel[Properties](self.properties).model_dump().items():
729730
schema["properties"][prop]["value"] = value

cogip/tools/server/namespaces/dashboard.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ async def on_shell_cmd(self, sid, cmd: str) -> None:
7373

7474
async def on_config_updated(self, sid, config: dict[str, Any]) -> None:
7575
namespace = config.pop("namespace")
76-
await self.emit("config_updated", config, namespace=namespace)
76+
sio_event = config.pop("sio_event")
77+
await self.emit(sio_event, config, namespace=namespace)
7778

7879
async def on_actuators_start(self, sid):
7980
"""

cogip/widgets/properties.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ def value_updated(self, name: str, value: int | float):
270270
self.property_updated.emit(
271271
{
272272
"namespace": self._config["namespace"],
273+
"sio_event": self._config.get("sio_event", "config_updated"),
273274
"name": name,
274275
"value": value,
275276
}

0 commit comments

Comments
 (0)